
org.linguafranca.pwdb.kdbx.dom.DomHelper Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of KeePassJava2-kdbx Show documentation
Show all versions of KeePassJava2-kdbx Show documentation
Base classes and resources for other KDBX modules.
KDBX Stream Support.
Security support.
The newest version!
/*
* Copyright 2015 Jo Rabin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.linguafranca.pwdb.kdbx.dom;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.linguafranca.pwdb.kdbx.Helpers;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.time.Instant;
import java.util.*;
/**
* The class contains static helper methods for access to the underlying XML DOM
*
* @author jo
*/
public class DomHelper {
public static XPath xpath = XPathFactory.newInstance().newXPath();
static final String GROUP_ELEMENT_NAME = "Group";
static final String ENTRY_ELEMENT_NAME = "Entry";
static final String ICON_ELEMENT_NAME = "IconID";
static final String UUID_ELEMENT_NAME = "UUID";
static final String NAME_ELEMENT_NAME = "Name";
static final String NOTES_ELEMENT_NAME = "Notes";
static final String TIMES_ELEMENT_NAME = "Times";
static final String IS_EXPANDED = "IsExpanded";
static final String HISTORY_ELEMENT_NAME = "History";
static final String LAST_MODIFICATION_TIME_ELEMENT_NAME = "Times/LastModificationTime";
static final String CREATION_TIME_ELEMENT_NAME = "Times/CreationTime";
static final String LAST_ACCESS_TIME_ELEMENT_NAME = "Times/LastAccessTime";
static final String EXPIRY_TIME_ELEMENT_NAME = "Times/ExpiryTime";
static final String EXPIRES_ELEMENT_NAME = "Times/Expires";
static final String USAGE_COUNT_ELEMENT_NAME = "Times/UsageCount";
static final String LOCATION_CHANGED = "Times/LocationChanged";
static final String PROPERTY_ELEMENT_FORMAT = "String[Key/text()='%s']";
static final String BINARY_PROPERTY_ELEMENT_FORMAT = "Binary[Key/text()='%s']";
static final String VALUE_ELEMENT_NAME = "Value";
static final String RECYCLE_BIN_UUID_ELEMENT_NAME = "RecycleBinUuid";
static final String RECYCLE_BIN_ENABLED_ELEMENT_NAME = "RecycleBinEnabled";
static final String RECYCLE_BIN_CHANGED_ELEMENT_NAME = "RecycleBinChanged";
interface ValueCreator {
String getValue();
}
static class ConstantValueCreator implements ValueCreator {
String value;
ConstantValueCreator(String value) {
this.value = value;
}
@Override
public String getValue() {
return value;
}
}
static class DateValueCreator implements ValueCreator {
@Override
public String getValue() {
return Helpers.fromDate(Date.from(Instant.now()));
}
}
static class UuidValueCreator implements ValueCreator {
@Override
public String getValue() {
return base64RandomUuid();
}
}
static void ensureElements (Element element, Map childElements) {
for (Map.Entry entry: childElements.entrySet()) {
ensureElementContent(entry.getKey(), element, entry.getValue().getValue());
}
}
@Nullable @Contract("_,_,true -> !null")
static Element getElement(String elementPath, Element parentElement, boolean create) {
try {
Element result = (Element) xpath.evaluate(elementPath, parentElement, XPathConstants.NODE);
if (result == null && create) {
result = createHierarchically(elementPath, parentElement);
}
return result;
} catch (XPathExpressionException e) {
throw new IllegalStateException(e);
}
}
static boolean removeElement(String elementPath, Element parentElement) {
Element toRemove = getElement(elementPath, parentElement, false);
if (toRemove == null) {
return false;
} else {
toRemove.getParentNode().removeChild(toRemove);
return true;
}
}
static List getElements (String elementPath, Element parentElement) {
try {
NodeList nodes = (NodeList) xpath.evaluate(elementPath, parentElement, XPathConstants.NODESET);
ArrayList result = new ArrayList<>(nodes.getLength());
for (int i = 0; i < nodes.getLength(); i++) {
result.add(((Element) nodes.item(i)));
}
return result;
} catch (XPathExpressionException e) {
throw new IllegalStateException(e);
}
}
static int getElementsCount (String elementPath, Element parentElement) {
try {
NodeList nodes = (NodeList) xpath.evaluate(elementPath, parentElement, XPathConstants.NODESET);
return nodes.getLength();
} catch (XPathExpressionException e) {
throw new IllegalStateException(e);
}
}
@Nullable
static Element newElement(String elementName, Element parentElement) {
Element newElement = parentElement.getOwnerDocument().createElement(elementName);
parentElement.appendChild(newElement);
return newElement;
}
@Nullable
public static String getElementContent(String elementPath, Element parentElement) {
Element result = getElement(elementPath, parentElement, false);
return (result == null) ? null : result.getTextContent();
}
@NotNull
public static String ensureElementContent(String elementPath, Element parentElement, @NotNull String value) {
Element result = getElement(elementPath, parentElement, false);
if (result == null) {
result = createHierarchically(elementPath, parentElement);
}
result.setTextContent(value);
return result.getTextContent();
}
@NotNull
public static Element setElementContent(String elementPath, Element parentElement, String value) {
Element result = getElement(elementPath, parentElement, true);
result.setTextContent(value);
return result;
}
@Nullable
static byte[] getBinaryElementContent(String elementPath, Element parentElement) {
Element result = getElement(elementPath, parentElement, false);
if (result == null) {
return null;
}
String id = result.getAttribute("Ref");
Element content = getElement("//Binaries/Binary[@ID=" + id + "]", parentElement.getOwnerDocument().getDocumentElement(),false);
if (content == null) {
throw new IllegalStateException("Could not find binary content with ID " + id);
}
return Helpers.decodeBase64Content(content.getTextContent().getBytes(), content.hasAttribute("Compressed"));
}
@NotNull
static Element setBinaryElementContent(String elementPath, Element parentElement, byte[] value) {
try {
String b64 = Helpers.encodeBase64Content(value, true);
//Find the highest numbered existing content
String max = xpath.evaluate("//Binaries/Binary/@ID[not(. < ../../Binary/@ID)][1]", parentElement.getOwnerDocument().getDocumentElement());
if (max.trim().isEmpty()){
max = "-1";
}
Integer newIndex = Integer.valueOf(max) + 1;
addBinary(parentElement.getOwnerDocument().getDocumentElement(), b64, newIndex);
Element result = getElement(elementPath, parentElement, true);
result.setAttribute("Ref", newIndex.toString());
return result;
} catch (XPathExpressionException e) {
throw new IllegalStateException(e);
}
}
/**
* Add a binary property value to the V3 Meta/Binaries element
*
* @param documentElement the document element
* @param b64 a base64 gzipped encoded representation of the binary content
* @param index the index by which it is known
*/
public static void addBinary(Element documentElement, String b64, Integer index) {
Element binaries = getElement("Meta/Binaries", documentElement,true);
Element binary = (Element) binaries.appendChild(binaries.getOwnerDocument().createElement("Binary"));
binary.setTextContent(b64);
binary.setAttribute("Compressed", "True");
binary.setAttribute("ID", index.toString());
}
public static String getBinary(Element documentElement, Integer index) {
Element binaries = getElement("Meta/Binaries", documentElement,false);
if (Objects.isNull(binaries)){
throw new IllegalArgumentException("No binaries found");
}
for (int i = 0; i < binaries.getChildNodes().getLength(); i++){
if (((Element) binaries.getChildNodes().item(i)).getAttribute("ID").equals(index.toString())) {
return ((Element) binaries.getChildNodes().item(i)).getTextContent();
}
}
throw new IllegalArgumentException("No binary with that index found");
}
public static int getBinaryCount(Element documentElement) {
Element binaries = getElement("Meta/Binaries", documentElement,false);
if (Objects.isNull(binaries)) {
return 0;
}
return binaries.getChildNodes().getLength();
}
@NotNull
static Element touchElement(String elementPath, Element parentElement) {
return setElementContent(elementPath, parentElement, Helpers.fromDate(new Date()));
}
private static Element createHierarchically(String elementPath, Element startElement) {
Element currentElement = startElement;
for (String elementName : elementPath.split("/")) {
try {
Element nextElement = (Element) xpath.evaluate(elementName, currentElement, XPathConstants.NODE);
if (nextElement == null) {
nextElement = (Element) currentElement.appendChild(currentElement.getOwnerDocument().createElement(elementName));
}
currentElement = nextElement;
} catch (XPathExpressionException e) {
throw new IllegalStateException(e);
}
}
return currentElement;
}
static String base64RandomUuid () {
return Helpers.base64FromUuid(UUID.randomUUID());
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy