All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.purejava.kwallet.MapEntries Maven / Gradle / Ivy

Go to download

A Java library for storing secrets on linux in a KDE wallet over D-Bus, implements kwallet.

The newest version!
package org.purejava.kwallet;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * KWallet allows to store secrets as byte fields, that contain maps.
 * This is a helper class to manage these maps.
 */
public class MapEntries {

    private final Logger log = LoggerFactory.getLogger(MapEntries.class);
    private Map map = new HashMap<>();
    private final byte[] EMPTY_ENTRY = new byte[]{-1, -1, -1, -1};
    private final byte[] EMPTY_VALUE = new byte[]{0, 0, 0, 0};

    /**
     * Add or replace an entry to the map.
     *
     * @param key   The key for the entry in the map.
     * @param value The secret to be stored with the key.
     */
    public void storeEntry(String key, String value) {
        map.put(Objects.toString(key, ""), Objects.toString(value, ""));
    }

    /**
     * Delete an entry from the map.
     *
     * @param key   The key for the entry in the map.
     * @param value The secret to be deleted in conjunction with the key.
     */
    public void removeEntry(String key, String value) {
        map.remove(key, value);
    }

    /**
     * Check, whether the map contains an entry with the given key.
     *
     * @param key The key to search for.
     * @return True if the key was found, false otherwise.
     */
    public boolean hasKey(String key) {
        return map.containsKey(key);
    }

    /**
     * Check, whether the map contains an entry with the given key and value.
     *
     * @param key   The key to search for.
     * @param value The value that is stored with that key.
     * @return True if the according entry was found, false otherwise.
     */
    public boolean hasValue(String key, String value) {
        return hasKey(key) && map.get(key).equals(value);
    }

    /**
     * Get the value of an entry for a given key.
     *
     * @param key The key to search for.
     * @return The value if set, an empty String otherwise.
     */
    public String getValue(String key) {
        return hasKey(key) && null != map.get(key) ? map.get(key) : "";
    }

    /**
     * Change the value of an entry in the map.
     *
     * @param key   The key on which the value should be changed.
     * @param value The new value.
     * @return True, if the key exists. The value was changed then. False otherwise.
     */
    public boolean changeValue(String key, String value) {
        if (hasKey(key)) {
            storeEntry(key, value);
            return true;
        }
        return false;
    }

    /**
     * Count number of entries in the map.
     *
     * @return Number or 0 if map is empty.
     */
    public int count() {
        return null != map && !map.isEmpty() ? map.size() : 0;
    }

    /**
     * Take the intern representation of the map entries and convert it to a KWallet map compatible byte field.
     * @see MapEntries#setByteField(byte[])
     *
     * @return The byte field or byte[0] in case something went wrong.
     */
    public byte[] getByteField() {
        if (null == map || map.isEmpty()) return new byte[0];
        try (var b = new ByteArrayOutputStream();
             var s = new DataOutputStream(b)) {

            s.writeInt(map.size());

            for (var entry : map.entrySet()) {
                if (null == entry.getKey() || entry.getKey().isEmpty()) {
                    s.write(EMPTY_ENTRY);
                    s.write(EMPTY_ENTRY);

                } else if (null == entry.getValue() || entry.getValue().isEmpty()) {
                    s.writeInt(entry.getKey().length() * 2);
                    s.writeChars(entry.getKey());
                    s.write(EMPTY_VALUE);

                } else {
                    s.writeInt(entry.getKey().length() * 2);
                    s.writeChars(entry.getKey());
                    s.writeInt(entry.getValue().length() * 2);
                    s.writeChars(entry.getValue());
                }
            }
            return b.toByteArray();

        } catch (IOException e) {
            log.error(e.toString(), e.getCause());
            return new byte[0];
        }
    }

    /**
     * Take a KWallet map compatible byte field and store it as a HashMap.
     *
     * @param s The byte field. Format is as follows:
     *          {0,0,0,2,                   // always starts with the number of entries (int) - key/value-combinations
     *                                      // followed by the entries themselves, which are _either_
     *          0,0,0,2,                    // the number of bytes for the key
     *          0,65,                       // the key as bytes
     *          0,0,0,2,                    // the number of bytes for the value
     *          0,66,                       // the value as bytes
     *                                      / note: empty values are 0,0,0,0 instead of number of bytes for the key and value bytes
     *          -1,-1,-1,-1,-1,-1,-1,-1}    // _or_ an empty entry
     * @return True if converting the byte field succeeded without errors, false otherwise.
     */
    public boolean setByteField(byte[] s) {
        map = new HashMap<>();
        if (null == s || s.length == 0) return true;

        try (var b = new ByteArrayInputStream(s);
             var x = new DataInputStream(b)) {

            var mapSize = x.readInt() & 0xFF;

            for (var i = 0; i < mapSize; i++) {
                // check if the mext part is a number or an EMPTY_ENTRY
                var nextPart = new byte[4];
                for (var k = 0; k < nextPart.length; k++) {
                    nextPart[k] = x.readByte();
                }
                if (Arrays.equals(nextPart, EMPTY_ENTRY)) {
                    map.put("", "");
                    x.skipBytes(4);
                    continue;
                }
                // we have a number
                var keySize = fourBytesToInt(nextPart) / 2;
                var k = new StringBuilder();

                for (var j = 0; j < keySize; j++) {
                    k.append(x.readChar());
                }

                // check if the next part is a number or an EMPTY_VALUE
                nextPart = new byte[4];
                for (var l = 0; l < nextPart.length; l++) {
                    nextPart[l] = x.readByte();
                }
                if (Arrays.equals(nextPart, EMPTY_VALUE)) {
                    map.put(k.toString(), "");
                    continue;
                }
                var valueSize = fourBytesToInt(nextPart) / 2;
                var v = new StringBuilder();
                for (var j = 0; j < valueSize; j++) {
                    v.append(x.readChar());
                }
                map.put(k.toString(), v.toString());
            }
            return true;

        } catch (IOException e) {
            log.error(e.toString(), e.getCause());
            return false;
        }
    }

    private int fourBytesToInt(byte[] b) {
        return ((b[0] << 24) + (b[1] << 16) + (b[2] << 8) + (b[3] << 0)) & 0xFF;
    }

    @Override
    public String toString() {
        var sout = new StringBuilder();
        var i = 1;
        for (Map.Entry entry : map.entrySet()) {
            var key = entry.getKey();
            if (key.isEmpty()) key = "''";
            var value = entry.getValue();
            if (value.isEmpty()) value = "''";
            sout.append("MapEntries (").append(i).append(") {key: ").append(key).append(", value: ").append(value).append("}\n");
            i++;
        }
        return sout.substring(0, sout.toString().length()-1);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy