// License: GPL. For details, see LICENSE file.
package org.openstreetmap.josm.plugins.trustosm.io;
import static org.openstreetmap.josm.tools.I18n.tr;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import org.bouncycastle.bcpg.ArmoredInputStream;
import org.bouncycastle.openpgp.PGPObjectFactory;
import org.bouncycastle.openpgp.PGPSignatureList;
import org.openstreetmap.josm.Main;
import org.openstreetmap.josm.data.osm.Node;
import org.openstreetmap.josm.data.osm.OsmPrimitive;
import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
import org.openstreetmap.josm.data.osm.Relation;
import org.openstreetmap.josm.data.osm.RelationMember;
import org.openstreetmap.josm.data.osm.Way;
import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
import org.openstreetmap.josm.gui.progress.ProgressMonitor;
import org.openstreetmap.josm.io.IllegalDataException;
import org.openstreetmap.josm.io.UTFInputStreamReader;
import org.openstreetmap.josm.plugins.trustosm.data.TrustNode;
import org.openstreetmap.josm.plugins.trustosm.data.TrustOsmPrimitive;
import org.openstreetmap.josm.plugins.trustosm.data.TrustRelation;
import org.openstreetmap.josm.plugins.trustosm.data.TrustSignatures;
import org.openstreetmap.josm.plugins.trustosm.data.TrustWay;
import org.openstreetmap.josm.tools.CheckParameterUtil;
import org.openstreetmap.josm.tools.XmlParsingException;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.helpers.DefaultHandler;
public class SigReader {
private final Map<String, TrustOsmPrimitive> trustitems = new HashMap<>();
private final Set<OsmPrimitive> missingData = new HashSet<>();
public Map<String, TrustOsmPrimitive> getTrustItems() {
return trustitems;
}
public Set<OsmPrimitive> getMissingData() {
return missingData;
}
private class Parser extends DefaultHandler {
private Locator locator;
@Override
public void setDocumentLocator(Locator locator) {
this.locator = locator;
}
protected void throwException(String msg) throws XmlParsingException {
throw new XmlParsingException(msg).rememberLocation(locator);
}
/**
* The current TrustOSMItem to be read.
*/
private TrustOsmPrimitive trust;
/**
* The current Signatures.
*/
private TrustSignatures tsigs;
private StringBuffer tmpbuf = new StringBuffer();
@Override public void startElement(String namespaceURI, String localName, String qName, Attributes atts) throws SAXException {
try {
if (qName.equals("trustnode") || qName.equals("trustway") || qName.equals("trustrelation")) {
if (atts == null) {
throwException(tr("Missing mandatory attribute ''{0}'' of XML element {1}.", "osmid", qName));
}
String osmid = atts.getValue("osmid");
if (osmid == null) {
throwException(tr("Missing mandatory attribute ''{0}''.", "osmid"));
} else if (!osmid.matches("\\d+")) {
throwException(tr("Only digits allowed in osmid: ''{0}''.", osmid));
}
long uid = Long.parseLong(osmid);
OsmPrimitiveType t = OsmPrimitiveType.NODE;
if (qName.equals("trustway")) t = OsmPrimitiveType.WAY;
else if (qName.equals("trustrelation")) t = OsmPrimitiveType.RELATION;
// search corresponding OsmPrimitive
OsmPrimitive osm = Main.getLayerManager().getEditDataSet().getPrimitiveById(uid, t);
if (osm == null) {
switch (t) {
case NODE: osm = new Node(uid); break;
case WAY: osm = new Way(uid); break;
case RELATION: osm = new Relation(uid); break;
default: throw new IllegalArgumentException(t.toString());
}
missingData.add(osm);
}
trust = TrustOsmPrimitive.createTrustOsmPrimitive(osm);
} else if (qName.equals("key") || qName.equals("node") || qName.equals("segment") || qName.equals("member")) {
tsigs = new TrustSignatures();
} else if (qName.equals("openpgp")) {
tmpbuf = new StringBuffer();
}
} catch (Exception e) {
throw new SAXParseException(e.getMessage(), locator, e);
}
}
@Override public void endElement(String namespaceURI, String localName, String qName) throws SAXException {
if (qName.equals("trustnode") || qName.equals("trustway") || qName.equals("trustrelation")) {
trustitems.put(TrustOsmPrimitive.createUniqueObjectIdentifier(trust.getOsmPrimitive()), trust);
} else if (qName.equals("openpgp")) {
try {
parseOpenPGP(tmpbuf.toString());
} catch (IOException e) {
throw new XmlParsingException(tr("Could not parse OpenPGP message."), e).rememberLocation(locator);
}
} else if (qName.equals("key")) {
String[] kv = TrustOsmPrimitive.generateTagsFromSigtext(tsigs.getOnePlainText());
trust.setTagRatings(kv[0], tsigs);
} else if (qName.equals("node")) {
((TrustNode) trust).setNodeRatings(tsigs);
} else if (qName.equals("segment")) {
List<Node> nodes = TrustWay.generateSegmentFromSigtext(tsigs.getOnePlainText());
((TrustWay) trust).setSegmentRatings(nodes, tsigs);
} else if (qName.equals("member")) {
RelationMember member = TrustRelation.generateRelationMemberFromSigtext(tsigs.getOnePlainText());
((TrustRelation) trust).setMemberRating(TrustOsmPrimitive.createUniqueObjectIdentifier(member.getMember()), tsigs);
}
}
@Override public void characters(char[] ch, int start, int length) {
tmpbuf.append(ch, start, length);
}
public void parseOpenPGP(String clearsigned) throws IOException {
// handle different newline characters and match them all to \n
//clearsigned = clearsigned.replace('\r', '\n').replaceAll("\n\n", "\n");
String plain = "";
ArmoredInputStream aIn = new ArmoredInputStream(new ByteArrayInputStream(clearsigned.getBytes(Charset.forName("UTF-8"))));
PGPObjectFactory pgpFact = new PGPObjectFactory(aIn);
// read plain text
ByteArrayOutputStream bout = new ByteArrayOutputStream();
if (aIn.isClearText()) {
int ch = aIn.read();
do {
bout.write(ch);
ch = aIn.read();
} while (aIn.isClearText());
}
plain = bout.toString();
// remove the last \n because it is not part of the plaintext
plain = plain.substring(0, plain.length()-1);
PGPSignatureList siglist = (PGPSignatureList) pgpFact.nextObject();
for (int i = 0; i < siglist.size(); i++) {
tsigs.addSignature(siglist.get(i), plain);
}
}
}
/**
* Parse the given input source and return the TrustosmItems.
*
* @param source the source input stream. Must not be null.
* @param progressMonitor the progress monitor. If null, {@see NullProgressMonitor#INSTANCE} is assumed
*
* @return a map of the parsed OSM Signatures (TrustOSMItem) with their related OSM-ID as key
* @throws IllegalDataException thrown if the an error was found while parsing the data from the source
* @throws IllegalArgumentException thrown if source is null
*/
public static Map<String, TrustOsmPrimitive> parseSignatureXML(InputStream source, ProgressMonitor progressMonitor)
throws IllegalDataException {
if (progressMonitor == null) {
progressMonitor = NullProgressMonitor.INSTANCE;
}
CheckParameterUtil.ensureParameterNotNull(source, "source");
SigReader reader = new SigReader();
try {
progressMonitor.beginTask(tr("Prepare stuff...", 2));
progressMonitor.indeterminateSubTask(tr("Parsing Signature data..."));
InputSource inputSource = new InputSource(UTFInputStreamReader.create(source, "UTF-8"));
SAXParserFactory.newInstance().newSAXParser().parse(inputSource, reader.new Parser());
// if (missingData != null)
// missingData.addAll(reader.getMissingData());
progressMonitor.worked(1);
return reader.getTrustItems();
} catch (ParserConfigurationException e) {
throw new IllegalDataException(e.getMessage(), e);
} catch (SAXParseException e) {
throw new IllegalDataException(tr("Line {0} column {1}: ", e.getLineNumber(), e.getColumnNumber()) + e.getMessage(), e);
} catch (SAXException e) {
throw new IllegalDataException(e.getMessage(), e);
} catch (Exception e) {
throw new IllegalDataException(e);
} finally {
progressMonitor.finishTask();
}
}
}