OSDN Git Service

implement XML SAX handler.
authorOlyutorskii <olyutorskii@users.osdn.me>
Wed, 11 Mar 2020 05:04:44 +0000 (14:04 +0900)
committerOlyutorskii <olyutorskii@users.osdn.me>
Wed, 11 Mar 2020 05:04:44 +0000 (14:04 +0900)
src/main/java/jp/sfjp/jindolf/Controller.java
src/main/java/jp/sfjp/jindolf/data/xml/BotherHandler.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/data/xml/NoopEntityResolver.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/data/xml/VillageHandler.java [new file with mode: 0644]
src/main/java/jp/sfjp/jindolf/data/xml/VillageLoader.java

index 1790211..e76d139 100644 (file)
@@ -57,6 +57,7 @@ import jp.sfjp.jindolf.data.Village;
 import jp.sfjp.jindolf.data.html.PeriodLoader;
 import jp.sfjp.jindolf.data.html.VillageInfoLoader;
 import jp.sfjp.jindolf.data.html.VillageListLoader;
+import jp.sfjp.jindolf.data.xml.VillageLoader;
 import jp.sfjp.jindolf.dxchg.CsvExporter;
 import jp.sfjp.jindolf.dxchg.WebIPCDialog;
 import jp.sfjp.jindolf.dxchg.WolfBBS;
@@ -1215,6 +1216,12 @@ public class Controller
         if(result != JFileChooser.APPROVE_OPTION) return;
         File selected = chooser.getSelectedFile();
 
+        Village village;
+        try{
+            village = VillageLoader.parseVillage(selected);
+        }catch(IOException e){
+            System.out.println(e);
+        }
         //System.out.println(selected);
 
         return;
diff --git a/src/main/java/jp/sfjp/jindolf/data/xml/BotherHandler.java b/src/main/java/jp/sfjp/jindolf/data/xml/BotherHandler.java
new file mode 100644 (file)
index 0000000..e6e383c
--- /dev/null
@@ -0,0 +1,69 @@
+/*
+ * XML custom error-handler
+ *
+ * License : The MIT License
+ * Copyright(c) 2010 MikuToga Partners
+ */
+
+package jp.sfjp.jindolf.data.xml;
+
+import org.xml.sax.ErrorHandler;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXParseException;
+
+/**
+ * 自製エラーハンドラ。
+ *
+ * <p>例外を渡されれば即投げ返す。
+ */
+public final class BotherHandler implements ErrorHandler{
+
+    /**
+     * 唯一のシングルトン。
+     */
+    public static final ErrorHandler HANDLER = new BotherHandler();
+
+
+    /**
+     * 隠しコンストラクタ。
+     */
+    private BotherHandler(){
+        super();
+        return;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param exception {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void error(SAXParseException exception) throws SAXException{
+        throw exception;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param exception {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void fatalError(SAXParseException exception) throws SAXException{
+        throw exception;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param exception {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void warning(SAXParseException exception) throws SAXException{
+        throw exception;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/data/xml/NoopEntityResolver.java b/src/main/java/jp/sfjp/jindolf/data/xml/NoopEntityResolver.java
new file mode 100644 (file)
index 0000000..638df4e
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * No-operation Entity Resolver for XML.
+ *
+ * License : The MIT License
+ * Copyright(c) 2019 olyutorskii
+ */
+
+package jp.sfjp.jindolf.data.xml;
+
+import java.io.Reader;
+import java.io.StringReader;
+import org.xml.sax.EntityResolver;
+import org.xml.sax.InputSource;
+
+/**
+ * No-operation Entity Resolver implementation for preventing XXE.
+ *
+ * @see <a href="https://en.wikipedia.org/wiki/XML_external_entity_attack">
+ *     XML external entity attack (Wikipedia)
+ *     </a>
+ */
+public final class NoopEntityResolver implements EntityResolver{
+
+    /** Singleton resolver. */
+    public static final EntityResolver NOOP_RESOLVER =
+            new NoopEntityResolver();
+
+
+    /**
+     * Constructor.
+     */
+    private NoopEntityResolver(){
+        super();
+        return;
+    }
+
+
+    /**
+     * {@inheritDoc}
+     *
+     * <p>Prevent any external entity reference XXE.
+     *
+     * @param publicId {@inheritDoc}
+     * @param systemId {@inheritDoc}
+     * @return empty input source
+     */
+    @Override
+    public InputSource resolveEntity(String publicId, String systemId){
+        Reader emptyReader = new StringReader("");
+        InputSource source = new InputSource(emptyReader);
+
+        source.setPublicId(publicId);
+        source.setSystemId(systemId);
+
+        return source;
+    }
+
+}
diff --git a/src/main/java/jp/sfjp/jindolf/data/xml/VillageHandler.java b/src/main/java/jp/sfjp/jindolf/data/xml/VillageHandler.java
new file mode 100644 (file)
index 0000000..4e67d01
--- /dev/null
@@ -0,0 +1,183 @@
+/*
+ * village handler
+ *
+ * License : The MIT License
+ * Copyright(c) 2020 olyutorskii
+ */
+
+package jp.sfjp.jindolf.data.xml;
+
+import jp.sfjp.jindolf.data.Village;
+import org.xml.sax.Attributes;
+import org.xml.sax.ContentHandler;
+import org.xml.sax.Locator;
+import org.xml.sax.SAXException;
+
+/**
+ * VillageのXMLパーサ本体。
+ */
+public class VillageHandler implements ContentHandler{
+
+    /**
+     * constructor.
+     */
+    public VillageHandler(){
+        super();
+        return;
+    }
+
+
+    /**
+     * パースした結果のVillageを返す。
+     *
+     * @return 村
+     */
+    public Village getVillage(){
+        return null;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param locator {@inheritDoc}
+     */
+    @Override
+    public void setDocumentLocator(Locator locator) {
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void startDocument() throws SAXException {
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void endDocument() throws SAXException {
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param prefix {@inheritDoc}
+     * @param uri {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void startPrefixMapping(String prefix, String uri)
+            throws SAXException {
+        //System.out.println(prefix);
+        //System.out.println(uri);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param prefix {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void endPrefixMapping(String prefix)
+            throws SAXException {
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param uri {@inheritDoc}
+     * @param localName {@inheritDoc}
+     * @param qName {@inheritDoc}
+     * @param atts {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void startElement(String uri,
+                             String localName,
+                             String qName,
+                             Attributes atts)
+            throws SAXException {
+        //System.out.println(uri);
+        //System.out.println(localName);
+        //System.out.println(qName);
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param uri {@inheritDoc}
+     * @param localName {@inheritDoc}
+     * @param qName {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void endElement(String uri, String localName, String qName)
+            throws SAXException {
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param ch {@inheritDoc}
+     * @param start {@inheritDoc}
+     * @param length {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void characters(char[] ch, int start, int length)
+            throws SAXException {
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param ch {@inheritDoc}
+     * @param start {@inheritDoc}
+     * @param length {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void ignorableWhitespace(char[] ch, int start, int length)
+            throws SAXException {
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param target {@inheritDoc}
+     * @param data {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void processingInstruction(String target, String data)
+            throws SAXException {
+        return;
+    }
+
+    /**
+     * {@inheritDoc}
+     *
+     * @param name {@inheritDoc}
+     * @throws SAXException {@inheritDoc}
+     */
+    @Override
+    public void skippedEntity(String name) throws SAXException {
+        return;
+    }
+
+}
index f798909..e39d807 100644 (file)
@@ -7,8 +7,24 @@
 
 package jp.sfjp.jindolf.data.xml;
 
+import java.io.BufferedInputStream;
 import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
+import javax.xml.XMLConstants;
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import javax.xml.validation.Schema;
 import jp.sfjp.jindolf.data.Village;
+import org.xml.sax.InputSource;
+import org.xml.sax.SAXException;
+import org.xml.sax.SAXNotRecognizedException;
+import org.xml.sax.SAXNotSupportedException;
+import org.xml.sax.XMLReader;
 
 /**
  * JinArchiverなどでXMLファイルにアーカイブされた人狼BBSの村プレイ記録を
@@ -16,6 +32,16 @@ import jp.sfjp.jindolf.data.Village;
  */
 public class VillageLoader {
 
+    private static final String F_DISALLOW_DOCTYPE_DECL =
+            "http://apache.org/xml/features/disallow-doctype-decl";
+    private static final String F_EXTERNAL_GENERAL_ENTITIES =
+            "http://xml.org/sax/features/external-general-entities";
+    private static final String F_EXTERNAL_PARAMETER_ENTITIES =
+            "http://xml.org/sax/features/external-parameter-entities";
+    private static final String F_LOAD_EXTERNAL_DTD =
+            "http://apache.org/xml/features/nonvalidating/load-external-dtd";
+
+
     /**
      * constructor.
      */
@@ -25,9 +51,193 @@ public class VillageLoader {
     }
 
 
-    public static Village parseVillage(File xmlFile){
-        Village result = null;
+    /**
+     * XMLファイルをパースする。
+     *
+     * @param xmlFile XMLファイル
+     * @return 村
+     * @throws IOException I/Oエラー
+     */
+    public static Village parseVillage(File xmlFile) throws IOException{
+        Objects.nonNull(xmlFile);
+
+        boolean isNormal;
+        isNormal = xmlFile.isFile()
+                && xmlFile.exists()
+                && xmlFile.canRead();
+        if(!isNormal){
+            return null;
+        }
+
+        Path path = xmlFile.toPath();
+
+        Village result = parseVillage(path);
+        return result;
+    }
+
+    /**
+     * XMLファイルをパースする。
+     *
+     * @param path XMLファイルのPath
+     * @return 村
+     * @throws IOException I/Oエラー
+     */
+    public static Village parseVillage(Path path) throws IOException{
+        Objects.nonNull(path);
+
+        path = path.normalize();
+
+        boolean isNormal;
+        isNormal = Files.exists(path)
+                && Files.isRegularFile(path)
+                && Files.isReadable(path);
+        if(!isNormal){
+            return null;
+        }
+
+        Village result;
+        try(InputStream is = pathToStream(path)){
+            result = parseVillage(is);
+        }
+
+        return result;
+    }
+
+    /**
+     * Pathから入力ストリームを得る。
+     *
+     * @param path Path
+     * @return バッファリングされた入力ストリーム
+     * @throws IOException I/Oエラー
+     */
+    private static InputStream pathToStream(Path path) throws IOException{
+        InputStream is;
+        is = Files.newInputStream(path);
+        is = new BufferedInputStream(is, 4*1024);
+        return is;
+    }
+
+    /**
+     * XML入力をパースする。
+     *
+     * @param istream XML入力
+     * @return 村
+     * @throws IOException I/Oエラー
+     */
+    public static Village parseVillage(InputStream istream) throws IOException{
+        InputSource isource = new InputSource(istream);
+        Village result = parseVillage(isource);
+        return result;
+    }
+
+    /**
+     * XML入力をパースする。
+     *
+     * @param isource XML入力
+     * @return 村
+     * @throws IOException I/Oエラー
+     */
+    public static Village parseVillage(InputSource isource) throws IOException{
+        XMLReader reader = buildReader();
+        VillageHandler handler = new VillageHandler();
+        reader.setContentHandler(handler);
+
+        try{
+            reader.parse(isource);
+        }catch(SAXException e){
+            System.out.println(e);
+            return null;
+        }
+
+        Village result = handler.getVillage();
         return result;
     }
 
+    /**
+     * SAXパーサファクトリを生成する。
+     *
+     * <ul>
+     * <li>XML名前空間機能は有効になる。
+     * <li>DTDによる形式検証は無効となる。
+     * <li>XIncludeによる差し込み機能は無効となる。
+     * </ul>
+     *
+     * @param schema スキーマ
+     * @return ファクトリ
+     */
+    private static SAXParserFactory buildFactory(Schema schema){
+        SAXParserFactory factory = SAXParserFactory.newInstance();
+
+        factory.setNamespaceAware(true);
+        factory.setValidating(false);
+        factory.setXIncludeAware(false);
+
+        try{
+            factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
+//            factory.setFeature(F_DISALLOW_DOCTYPE_DECL, true);
+            factory.setFeature(F_EXTERNAL_GENERAL_ENTITIES, false);
+            factory.setFeature(F_EXTERNAL_PARAMETER_ENTITIES, false);
+            factory.setFeature(F_LOAD_EXTERNAL_DTD, false);
+        }catch(   ParserConfigurationException
+                | SAXNotRecognizedException
+                | SAXNotSupportedException e ){
+            assert false;
+            throw new AssertionError(e);
+        }
+
+        factory.setSchema(schema);
+
+        return factory;
+    }
+
+    /**
+     * SAXパーサを生成する。
+     *
+     * @param schema スキーマ
+     * @return SAXパーサ
+     */
+    private static SAXParser buildParser(Schema schema){
+        SAXParserFactory factory = buildFactory(schema);
+
+        SAXParser parser;
+        try{
+            parser = factory.newSAXParser();
+        }catch(ParserConfigurationException | SAXException e){
+            assert false;
+            throw new AssertionError(e);
+        }
+
+        try{
+            parser.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
+            parser.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
+        }catch(SAXNotRecognizedException | SAXNotSupportedException e){
+            assert false;
+            throw new AssertionError(e);
+        }
+
+        return parser;
+    }
+
+    /**
+     * XMLリーダを生成する。
+     *
+     * @return XMLリーダ
+     */
+    private static XMLReader buildReader(){
+        SAXParser parser = buildParser((Schema)null);
+
+        XMLReader reader;
+        try{
+            reader = parser.getXMLReader();
+        }catch(SAXException e){
+            assert false;
+            throw new AssertionError(e);
+        }
+
+        reader.setEntityResolver(NoopEntityResolver.NOOP_RESOLVER);
+        reader.setErrorHandler(BotherHandler.HANDLER);
+
+        return reader;
+    }
+
 }