use of org.kxml2.kdom.Document in project briefcase by opendatakit.
the class XmlManipulationUtils method updateSubmissionMetadata.
public static final String updateSubmissionMetadata(File submissionFile, Document doc) throws MetadataUpdateException {
Element root = doc.getRootElement();
Element metadata = root.getElement(NAMESPACE_ODK, "submissionMetadata");
// and get the instanceID and submissionDate from the metadata.
// we need to put that back into the instance file if not already present
String instanceID = metadata.getAttributeValue("", INSTANCE_ID_ATTRIBUTE_NAME);
String submissionDate = metadata.getAttributeValue("", SUBMISSION_DATE_ATTRIBUTE_NAME);
// read the original document...
Document originalDoc = null;
try {
FileInputStream fs = new FileInputStream(submissionFile);
InputStreamReader fr = new InputStreamReader(fs, "UTF-8");
originalDoc = new Document();
KXmlParser parser = new KXmlParser();
parser.setInput(fr);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
originalDoc.parse(parser);
fr.close();
fs.close();
} catch (IOException e) {
String msg = "Original submission file could not be opened";
log.error(msg, e);
throw new MetadataUpdateException(msg);
} catch (XmlPullParserException e) {
String msg = "Original submission file could not be parsed as XML file";
log.error(msg, e);
throw new MetadataUpdateException(msg);
}
// determine whether it has the attributes already added.
// if they are already there, they better match the values returned by
// Aggregate 1.0
boolean hasInstanceID = false;
boolean hasSubmissionDate = false;
root = originalDoc.getRootElement();
for (int i = 0; i < root.getAttributeCount(); ++i) {
String name = root.getAttributeName(i);
if (name.equals(INSTANCE_ID_ATTRIBUTE_NAME)) {
if (!root.getAttributeValue(i).equals(instanceID)) {
String msg = "Original submission file's instanceID does not match that on server!";
log.error(msg);
throw new MetadataUpdateException(msg);
} else {
hasInstanceID = true;
}
}
if (name.equals(SUBMISSION_DATE_ATTRIBUTE_NAME)) {
Date oldDate = WebUtils.parseDate(submissionDate);
String returnDate = root.getAttributeValue(i);
Date newDate = WebUtils.parseDate(returnDate);
// cross-platform datetime resolution is 1 second.
if (Math.abs(newDate.getTime() - oldDate.getTime()) > 1000L) {
String msg = "Original submission file's submissionDate does not match that on server!";
log.error(msg);
throw new MetadataUpdateException(msg);
} else {
hasSubmissionDate = true;
}
}
}
if (hasInstanceID && hasSubmissionDate) {
log.info("submission already has instanceID and submissionDate attributes: " + submissionFile.getAbsolutePath());
return instanceID;
}
if (!hasInstanceID) {
root.setAttribute("", INSTANCE_ID_ATTRIBUTE_NAME, instanceID);
}
if (!hasSubmissionDate) {
root.setAttribute("", SUBMISSION_DATE_ATTRIBUTE_NAME, submissionDate);
}
// and write out the changes...
// write the file out...
File revisedFile = new File(submissionFile.getParentFile(), "." + submissionFile.getName());
try {
FileOutputStream fos = new FileOutputStream(revisedFile, false);
KXmlSerializer serializer = new KXmlSerializer();
serializer.setOutput(fos, "UTF-8");
originalDoc.write(serializer);
serializer.flush();
fos.close();
// and swap files...
boolean restoreTemp = false;
File temp = new File(submissionFile.getParentFile(), ".back." + submissionFile.getName());
try {
if (temp.exists()) {
if (!temp.delete()) {
String msg = "Unable to remove temporary submission backup file";
log.error(msg);
throw new MetadataUpdateException(msg);
}
}
if (!submissionFile.renameTo(temp)) {
String msg = "Unable to rename submission to temporary submission backup file";
log.error(msg);
throw new MetadataUpdateException(msg);
}
// recovery is possible...
restoreTemp = true;
if (!revisedFile.renameTo(submissionFile)) {
String msg = "Original submission file could not be updated";
log.error(msg);
throw new MetadataUpdateException(msg);
}
// we're successful...
restoreTemp = false;
} finally {
if (restoreTemp) {
if (!temp.renameTo(submissionFile)) {
String msg = "Unable to restore submission from temporary submission backup file";
log.error(msg);
throw new MetadataUpdateException(msg);
}
}
}
} catch (FileNotFoundException e) {
String msg = "Temporary submission file could not be opened";
log.error(msg, e);
throw new MetadataUpdateException(msg);
} catch (IOException e) {
String msg = "Temporary submission file could not be written";
log.error(msg, e);
throw new MetadataUpdateException(msg);
}
return instanceID;
}
use of org.kxml2.kdom.Document in project briefcase by opendatakit.
the class FileSystemUtils method decryptSubmissionFiles.
private static boolean decryptSubmissionFiles(String base64EncryptedSymmetricKey, FormInstanceMetadata fim, List<String> mediaNames, String encryptedSubmissionFile, String base64EncryptedElementSignature, PrivateKey rsaPrivateKey, File instanceDir, File unencryptedDir) throws FileSystemException, CryptoException, ParsingException {
EncryptionInformation ei = new EncryptionInformation(base64EncryptedSymmetricKey, fim.instanceId, rsaPrivateKey);
byte[] elementDigest;
try {
// construct the base64-encoded RSA-encrypted symmetric key
Cipher pkCipher;
pkCipher = Cipher.getInstance(ASYMMETRIC_ALGORITHM);
// extract digest
pkCipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
byte[] encryptedElementSignature = Base64.decodeBase64(base64EncryptedElementSignature);
elementDigest = pkCipher.doFinal(encryptedElementSignature);
} catch (NoSuchAlgorithmException | BadPaddingException | IllegalBlockSizeException | InvalidKeyException | NoSuchPaddingException e) {
String msg = "Error decrypting base64EncryptedElementSignature";
log.error(msg, e);
throw new CryptoException(msg + " Cause: " + e.toString());
}
// NOTE: will decrypt only the files in the media list, plus the encryptedSubmissionFile
File[] allFiles = instanceDir.listFiles();
List<File> filesToProcess = new ArrayList<>();
for (File f : allFiles) {
if (mediaNames.contains(f.getName())) {
filesToProcess.add(f);
} else if (encryptedSubmissionFile.equals(f.getName())) {
filesToProcess.add(f);
}
}
// should have all media files plus one submission.xml.enc file
if (filesToProcess.size() != mediaNames.size() + 1) {
// figure out what we're missing...
int lostFileCount = 0;
List<String> missing = new ArrayList<>();
for (String name : mediaNames) {
if (name == null) {
// this was lost due to an pre-ODK Aggregate 1.4.5 mark-as-complete action
++lostFileCount;
continue;
}
File f = new File(instanceDir, name);
if (!filesToProcess.contains(f)) {
missing.add(name);
}
}
StringBuilder b = new StringBuilder();
for (String name : missing) {
b.append(" ").append(name);
}
if (!filesToProcess.contains(new File(instanceDir, encryptedSubmissionFile))) {
b.append(" ").append(encryptedSubmissionFile);
throw new FileSystemException("Error decrypting: " + instanceDir.getName() + " Missing files:" + b.toString());
} else {
// ignore the fact that we don't have the lost files
if (filesToProcess.size() + lostFileCount != mediaNames.size() + 1) {
throw new FileSystemException("Error decrypting: " + instanceDir.getName() + " Missing files:" + b.toString());
}
}
}
// decrypt the media files IN ORDER.
for (String mediaName : mediaNames) {
String displayedName = (mediaName == null) ? "<missing .enc file>" : mediaName;
File f = (mediaName == null) ? null : new File(instanceDir, mediaName);
try {
decryptFile(ei, f, unencryptedDir);
} catch (InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
String msg = "Error decrypting:" + displayedName;
log.error(msg, e);
throw new CryptoException(msg + " Cause: " + e.toString());
} catch (IOException e) {
String msg = "Error decrypting:" + displayedName;
log.error(msg, e);
throw new FileSystemException(msg + " Cause: " + e.toString());
}
}
// decrypt the submission file
File f = new File(instanceDir, encryptedSubmissionFile);
try {
decryptFile(ei, f, unencryptedDir);
} catch (InvalidKeyException | NoSuchPaddingException | InvalidAlgorithmParameterException | NoSuchAlgorithmException e) {
log.error("Error decrypting file", e);
throw new CryptoException("Error decrypting:" + f.getName() + " Cause: " + e.toString());
} catch (IOException e) {
log.error("Error decrypting file", e);
throw new FileSystemException("Error decrypting:" + f.getName() + " Cause: " + e.toString());
}
// get the FIM for the decrypted submission file
File submissionFile = new File(unencryptedDir, encryptedSubmissionFile.substring(0, encryptedSubmissionFile.lastIndexOf(".enc")));
FormInstanceMetadata submissionFim;
try {
Document subDoc = XmlManipulationUtils.parseXml(submissionFile);
submissionFim = XmlManipulationUtils.getFormInstanceMetadata(subDoc.getRootElement());
} catch (ParsingException | FileSystemException e) {
log.error("Error decrypting submission", e);
throw new FileSystemException("Error decrypting: " + submissionFile.getName() + " Cause: " + e);
}
boolean same = submissionFim.xparam.formId.equals(fim.xparam.formId);
if (!same) {
throw new FileSystemException("Error decrypting:" + unencryptedDir.getName() + " Cause: form instance metadata differs from that in manifest");
}
// Construct the element signature string
StringBuilder b = new StringBuilder();
appendElementSignatureSource(b, fim.xparam.formId);
if (fim.xparam.modelVersion != null) {
appendElementSignatureSource(b, fim.xparam.modelVersion);
}
appendElementSignatureSource(b, base64EncryptedSymmetricKey);
appendElementSignatureSource(b, fim.instanceId);
boolean missingFile = false;
for (String encFilename : mediaNames) {
if (encFilename == null) {
missingFile = true;
continue;
}
File decryptedFile = new File(unencryptedDir, encFilename.substring(0, encFilename.lastIndexOf(".enc")));
if (decryptedFile.getName().endsWith(".missing")) {
// this is a missing file -- we will not be able to
// confirm the signature of the submission.
missingFile = true;
continue;
}
String md5 = FileSystemUtils.getMd5Hash(decryptedFile);
appendElementSignatureSource(b, decryptedFile.getName() + "::" + md5);
}
String md5 = FileSystemUtils.getMd5Hash(submissionFile);
appendElementSignatureSource(b, submissionFile.getName() + "::" + md5);
// compute the digest of the element signature string
byte[] messageDigest;
try {
MessageDigest md = MessageDigest.getInstance("MD5");
md.update(b.toString().getBytes("UTF-8"));
messageDigest = md.digest();
} catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
String msg = "Error computing xml signature";
log.error(msg, e);
throw new CryptoException(msg + " Cause: " + e);
}
same = true;
for (int i = 0; i < messageDigest.length; ++i) {
if (messageDigest[i] != elementDigest[i]) {
same = false;
break;
}
}
return same;
}
use of org.kxml2.kdom.Document in project briefcase by opendatakit.
the class AggregateUtils method httpRetrieveXmlDocument.
/**
* Common method for returning a parsed xml document given a url and the http
* context and client objects involved in the web connection. The document is
* the body of the response entity and should be xml.
*
* @return
*/
private static final DocumentFetchResult httpRetrieveXmlDocument(HttpUriRequest request, int[] validStatusList, ServerConnectionInfo serverInfo, boolean alwaysResetCredentials, DocumentDescription description, ResponseAction action) throws XmlDocumentFetchException {
HttpClient httpClient = WebUtils.createHttpClient();
// get shared HttpContext so that authentication and cookies are retained.
HttpClientContext localContext = WebUtils.getHttpContext();
URI uri = request.getURI();
log.info("Attempting URI {}", uri);
WebUtils.setCredentials(localContext, serverInfo, uri, alwaysResetCredentials);
if (description.isCancelled()) {
throw new XmlDocumentFetchException("Transfer of " + description.getDocumentDescriptionType() + " aborted.");
}
HttpResponse response = null;
try {
response = httpClient.execute(request, localContext);
int statusCode = response.getStatusLine().getStatusCode();
HttpEntity entity = response.getEntity();
String lcContentType = (entity == null) ? null : entity.getContentType().getValue().toLowerCase();
XmlDocumentFetchException ex = null;
boolean statusCodeValid = false;
for (int i : validStatusList) {
if (i == statusCode) {
statusCodeValid = true;
break;
}
}
if (!statusCodeValid) {
String webError = response.getStatusLine().getReasonPhrase() + " (" + statusCode + ")";
if (statusCode == 401) {
// We reset the Http context to force next request to authenticate itself
WebUtils.resetHttpContext();
ex = new XmlDocumentFetchException("Authentication failure");
} else if (statusCode == 400) {
ex = new XmlDocumentFetchException(description.getFetchDocFailed() + webError + " while accessing: " + uri.toString() + "\nPlease verify that the " + description.getDocumentDescriptionType() + " that is being uploaded is well-formed.");
} else {
ex = new XmlDocumentFetchException(description.getFetchDocFailed() + webError + " while accessing: " + uri.toString() + "\nPlease verify that the URL, your user credentials and your permissions are all correct.");
}
} else if (entity == null) {
log.warn("No entity body returned");
ex = new XmlDocumentFetchException(description.getFetchDocFailed() + " Server unexpectedly returned no content");
} else if (!(lcContentType.contains(HTTP_CONTENT_TYPE_TEXT_XML) || lcContentType.contains(HTTP_CONTENT_TYPE_APPLICATION_XML))) {
log.warn("Wrong ContentType: " + entity.getContentType().getValue() + "returned");
ex = new XmlDocumentFetchException(description.getFetchDocFailed() + "A non-XML document was returned. A network login screen may be interfering with the transmission to the server.");
}
if (ex != null) {
flushEntityBytes(entity);
// and throw the exception...
throw ex;
}
// parse the xml document...
Document doc = null;
try {
InputStream is = null;
InputStreamReader isr = null;
try {
is = entity.getContent();
isr = new InputStreamReader(is, "UTF-8");
doc = new Document();
KXmlParser parser = new KXmlParser();
parser.setInput(isr);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
doc.parse(parser);
isr.close();
} finally {
if (isr != null) {
try {
isr.close();
} catch (Exception e) {
// no-op
}
}
if (is != null) {
try {
is.close();
} catch (Exception e) {
// no-op
}
}
}
} catch (Exception e) {
log.warn("Parsing failed with " + e.getMessage(), e);
throw new XmlDocumentFetchException(description.getFetchDocFailed());
}
// examine header fields...
// is it an OpenRosa server?
boolean isOR = false;
Header[] fields = response.getHeaders(WebUtils.OPEN_ROSA_VERSION_HEADER);
if (fields != null && fields.length >= 1) {
isOR = true;
boolean versionMatch = false;
boolean first = true;
StringBuilder b = new StringBuilder();
for (Header h : fields) {
if (WebUtils.OPEN_ROSA_VERSION.equals(h.getValue())) {
versionMatch = true;
break;
}
if (!first) {
b.append("; ");
}
first = false;
b.append(h.getValue());
}
if (!versionMatch) {
log.warn(WebUtils.OPEN_ROSA_VERSION_HEADER + " unrecognized version(s): " + b);
}
}
// what about location?
Header[] locations = response.getHeaders("Location");
if (locations != null && locations.length == 1) {
try {
URL url = new URL(locations[0].getValue());
URI uNew = url.toURI();
log.info("Redirection to URI {}", uNew);
if (uri.getHost().equalsIgnoreCase(uNew.getHost())) {
// trust the server to tell us a new location
// ... and possibly to use https instead.
String fullUrl = url.toExternalForm();
int idx = fullUrl.lastIndexOf("/");
serverInfo.setUrl(fullUrl.substring(0, idx));
} else {
// Don't follow a redirection attempt to a different host.
// We can't tell if this is a spoof or not.
String msg = description.getFetchDocFailed() + "Unexpected redirection attempt";
log.warn(msg);
throw new XmlDocumentFetchException(msg);
}
} catch (MalformedURLException | URISyntaxException e) {
String msg = description.getFetchDocFailed() + "Unexpected exception: " + e.getMessage();
log.warn(msg, e);
throw new XmlDocumentFetchException(msg);
}
}
DocumentFetchResult result = new DocumentFetchResult(doc, isOR);
if (action != null) {
action.doAction(result);
}
return result;
} catch (UnknownHostException e) {
String msg = description.getFetchDocFailed() + "Unknown host";
log.warn(msg, e);
throw new XmlDocumentFetchException(msg);
} catch (IOException | MetadataUpdateException e) {
String msg = description.getFetchDocFailed() + "Unexpected exception: " + e;
log.warn(msg, e);
throw new XmlDocumentFetchException(msg);
}
}
use of org.kxml2.kdom.Document in project briefcase by opendatakit.
the class BaseFormParserForJavaRosa method parseFormDefinition.
private static final synchronized XFormParserWithBindEnhancements parseFormDefinition(String xml, BaseFormParserForJavaRosa parser) throws ODKIncompleteSubmissionData {
StringReader isr = null;
try {
isr = new StringReader(xml);
Document doc = XFormParser.getXMLDocument(isr);
return new XFormParserWithBindEnhancements(parser, doc);
} catch (Exception e) {
throw new ODKIncompleteSubmissionData(e, Reason.BAD_JR_PARSE);
} finally {
isr.close();
}
}
use of org.kxml2.kdom.Document in project javarosa by opendatakit.
the class XFormParser method getXMLDocument.
/**
* Uses xkml to parse the provided XML content, and then consolidates text elements.
*
* @param reader the XML content provider
* @param stringCache an optional string cache, whose presence will cause the use of
* {@link InterningKXmlParser} rather than {@link KXmlParser}.
* @return the parsed document
* @throws IOException
* @deprecated The InterningKXmlParser is not used.
*/
@Deprecated
public static Document getXMLDocument(Reader reader, CacheTable<String> stringCache) throws IOException {
final CodeTimer ctParse = new CodeTimer("Reading XML and parsing with kXML2");
Document doc = new Document();
try {
KXmlParser parser;
if (stringCache != null) {
parser = new InterningKXmlParser(stringCache);
} else {
parser = new KXmlParser();
}
parser.setInput(reader);
parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, true);
doc.parse(parser);
} catch (XmlPullParserException e) {
String errorMsg = "XML Syntax Error at Line: " + e.getLineNumber() + ", Column: " + e.getColumnNumber() + "!";
Std.err.println(errorMsg);
Std.printStack(e);
throw new XFormParseException(errorMsg);
} catch (IOException e) {
// CTS - 12/09/2012 - Stop swallowing IO Exceptions
throw e;
} catch (Exception e) {
// #if debug.output==verbose || debug.output==exception
String errorMsg = "Unhandled Exception while Parsing XForm";
Std.err.println(errorMsg);
Std.printStack(e);
throw new XFormParseException(errorMsg);
// #endif
}
try {
reader.close();
} catch (IOException e) {
Std.out.println("Error closing reader");
Std.printStack(e);
}
ctParse.logDone();
final CodeTimer ctConsolidate = new CodeTimer("Consolidating text");
XmlTextConsolidator.consolidateText(stringCache, doc.getRootElement());
ctConsolidate.logDone();
return doc;
}
Aggregations