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

com.github.robtimus.obfuscation.jackson.ObfuscatingJsonParser Maven / Gradle / Ivy

The newest version!
/*
 * ObfuscatingJsonParser.java
 * Copyright 2020 Rob Spoor
 *
 * 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 com.github.robtimus.obfuscation.jackson;

import java.io.IOException;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.Map;
import java.util.function.Function;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.core.util.JsonParserDelegate;
import com.github.robtimus.obfuscation.jackson.JSONObfuscator.PropertyConfigurer.ObfuscationMode;

final class ObfuscatingJsonParser extends JsonParserDelegate {

    private final Source source;
    private final Appendable destination;

    private final Map properties;

    private final int textOffset;
    private final int textEnd;
    private int textIndex;

    private String tokenValue;
    private int tokenStart;
    private int tokenEnd;

    private final Deque currentProperties = new ArrayDeque<>();

    ObfuscatingJsonParser(JsonParser parser, Source source, int start, int end, Appendable destination, Map properties) {
        super(parser);

        this.source = source;
        this.textOffset = start;
        this.textEnd = end;
        this.textIndex = start;
        this.destination = destination;
        this.properties = properties;
    }

    @Override
    public JsonToken nextToken() throws IOException {
        JsonToken token = super.nextToken();
        if (token == null) {
            return token;
        }
        switch (token) {
            case START_OBJECT:
                startObject();
                break;
            case END_OBJECT:
                endObject();
                break;
            case START_ARRAY:
                startArray();
                break;
            case END_ARRAY:
                endArray();
                break;
            case FIELD_NAME:
                fieldName();
                break;
            case VALUE_STRING:
                valueString();
                break;
            case VALUE_NUMBER_INT:
            case VALUE_NUMBER_FLOAT:
                valueNumber();
                break;
            case VALUE_TRUE:
            case VALUE_FALSE:
            case VALUE_NULL:
                valueOther(token);
                break;
            default:
                break;
        }
        return token;
    }

    private void startObject() throws IOException {
        startStructure(JsonToken.START_OBJECT, p -> p.forObjects);
    }

    private void endObject() throws IOException {
        endStructure(JsonToken.START_OBJECT, JsonToken.END_OBJECT);
    }

    private void startArray() throws IOException {
        startStructure(JsonToken.START_ARRAY, p -> p.forArrays);
    }

    private void endArray() throws IOException {
        endStructure(JsonToken.START_ARRAY, JsonToken.END_ARRAY);
    }

    private void startStructure(JsonToken startToken, Function getObfuscationMode) throws IOException {
        ObfuscatedProperty currentProperty = currentProperties.peekLast();
        if (currentProperty != null) {
            if (currentProperty.depth == 0) {
                // The start of the structure that's being obfuscated
                ObfuscationMode obfuscationMode = getObfuscationMode.apply(currentProperty.config);
                if (obfuscationMode == ObfuscationMode.EXCLUDE) {
                    // There is an obfuscator for the structure property, but the obfuscation mode prohibits handling it, so discard the property
                    currentProperties.removeLast();
                } else {
                    updateOtherTokenFields(startToken);
                    appendUntilToken();

                    currentProperty.structure = startToken;
                    currentProperty.obfuscationMode = obfuscationMode;
                    currentProperty.depth++;
                }
            } else if (currentProperty.structure == startToken) {
                // In a nested structure that's being obfuscated; do nothing
                currentProperty.depth++;
            }
            // else in a nested structure that's being obfuscated; do nothing
        }
        // else not obfuscating anything
    }

    private void endStructure(JsonToken startToken, JsonToken endToken) throws IOException {
        ObfuscatedProperty currentProperty = currentProperties.peekLast();
        if (currentProperty != null && currentProperty.structure == startToken) {
            currentProperty.depth--;
            if (currentProperty.depth == 0) {
                if (currentProperty.obfuscateStructure()) {
                    int originalTokenStart = tokenStart;
                    updateOtherTokenFields(endToken);
                    obfuscateUntilToken(currentProperty, originalTokenStart);
                }
                // else the obfuscator is Obfuscator.none(), which means we don't need to obfuscate,
                // or the structure itself should not be obfuscated

                currentProperties.removeLast();
            }
            // else still in a nested structure array that's being obfuscated
        }
        // else currently no structure is being obfuscated
    }

    private void fieldName() throws IOException {
        ObfuscatedProperty currentProperty = currentProperties.peekLast();
        if (currentProperty == null || currentProperty.allowsOverriding()) {
            PropertyConfig config = properties.get(getCurrentName());
            if (config != null) {
                currentProperty = new ObfuscatedProperty(config);
                currentProperties.addLast(currentProperty);
            }

            if (source.needsTruncating()) {
                updateOtherTokenFields(JsonToken.FIELD_NAME);
                appendUntilToken();
                source.truncate();
            }
        } else if (!currentProperty.config.performObfuscation && source.needsTruncating()) {
            // in a nested object or array that's being obfuscated using Obfuscator.none(), which means we can just append data already
            updateOtherTokenFields(JsonToken.FIELD_NAME);
            appendUntilToken();
            source.truncate();
        }
        // else in a nested object or array that's being obfuscated; do nothing
    }

    private void valueString() throws IOException {
        ObfuscatedProperty currentProperty = currentProperties.peekLast();
        if (currentProperty != null && currentProperty.obfuscateScalar()) {
            updateStringTokenFields();
            appendUntilToken();
            obfuscateCurrentToken(currentProperty);

            if (currentProperty.depth == 0) {
                currentProperties.removeLast();
            }
        }
        // else not obfuscating, or using Obfuscator.none(), or in a nested object or or array that's being obfuscated; do nothing
    }

    private void valueNumber() throws IOException {
        ObfuscatedProperty currentProperty = currentProperties.peekLast();
        if (currentProperty != null && currentProperty.obfuscateScalar()) {
            updateNumberTokenFields();
            appendUntilToken();
            obfuscateCurrentToken(currentProperty);

            if (currentProperty.depth == 0) {
                currentProperties.removeLast();
            }
        }
        // else not obfuscating, or using Obfuscator.none(), or in a nested object or or array that's being obfuscated; do nothing
    }

    private void valueOther(JsonToken token) throws IOException {
        ObfuscatedProperty currentProperty = currentProperties.peekLast();
        if (currentProperty != null && currentProperty.obfuscateScalar()) {
            updateOtherTokenFields(token);
            appendUntilToken();
            obfuscateCurrentToken(currentProperty);

            if (currentProperty.depth == 0) {
                currentProperties.removeLast();
            }
        }
        // else not obfuscating, or using Obfuscator.none(), or in a nested object or or array that's being obfuscated; do nothing
    }

    private void updateStringTokenFields() throws IOException {
        tokenValue = getValueAsString();
        tokenStart = stringValueStart();
        tokenEnd = stringValueEnd();
    }

    private void updateNumberTokenFields() throws IOException {
        tokenValue = getValueAsString();
        tokenStart = tokenStart();
        tokenEnd = tokenEnd();
    }

    private void updateOtherTokenFields(JsonToken token) {
        tokenValue = token.asString();
        tokenStart = tokenStart();
        tokenEnd = tokenEnd();
    }

    private int tokenStart() {
        return textOffset + (int) getTokenLocation().getCharOffset();
    }

    private int tokenEnd() {
        return textOffset + (int) getCurrentLocation().getCharOffset();
    }

    private int stringValueStart() {
        int start = tokenStart();
        // start points to the opening ", skip past it
        if (source.charAt(start) == '"') {
            start++;
        }
        return start;
    }

    private int stringValueEnd() {
        int end = tokenEnd();
        // end points to the closing ", skip back past it
        if (source.charAt(end - 1) == '"') {
            end--;
        }
        return end;
    }

    private void appendUntilToken() throws IOException {
        source.appendTo(textIndex, tokenStart, destination);
        textIndex = tokenStart;
    }

    private void obfuscateUntilToken(ObfuscatedProperty currentProperty, int originalTokenStart) throws IOException {
        source.obfuscateText(originalTokenStart, tokenEnd, currentProperty.config.obfuscator, destination);
        textIndex = tokenEnd;
    }

    private void obfuscateCurrentToken(ObfuscatedProperty currentProperty) throws IOException {
        currentProperty.config.obfuscator.obfuscateText(tokenValue, destination);
        textIndex = tokenEnd;
    }

    void appendRemainder() throws IOException {
        textIndex = source.appendRemainder(textIndex, textEnd, destination);
    }

    private static final class ObfuscatedProperty {

        private final PropertyConfig config;
        private JsonToken structure;
        private ObfuscationMode obfuscationMode;
        private int depth = 0;

        private ObfuscatedProperty(PropertyConfig config) {
            this.config = config;
        }

        private boolean allowsOverriding() {
            // OBFUSCATE and INHERITED do not allow overriding
            // No need to include EXCLUDE; if that occurs the ObfuscatedProperty is discarded
            return obfuscationMode == ObfuscationMode.INHERIT_OVERRIDABLE;
        }

        private boolean obfuscateStructure() {
            // Don't obfuscate the entire structure if Obfuscator.none() is used
            return config.performObfuscation && obfuscationMode == ObfuscationMode.OBFUSCATE;
        }

        private boolean obfuscateScalar() {
            // Don't obfuscate the scalar if Obfuscator.none() is used
            // Obfuscate if depth == 0 (the property is for the scalar itself),
            // or if the obfuscation mode is INHERITED or INHERITED_OVERRIDABLE (EXCLUDE is discarded)
            return config.performObfuscation
                    && (depth == 0 || obfuscationMode != ObfuscationMode.OBFUSCATE);
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy