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

com.github.robtimus.obfuscation.json.ObfuscatingJsonGenerator Maven / Gradle / Ivy

/*
 * ObfuscatingJsonGenerator.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.json;

import static com.github.robtimus.obfuscation.support.ObfuscatorUtils.writer;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.Map;
import javax.json.JsonArray;
import javax.json.JsonNumber;
import javax.json.JsonObject;
import javax.json.JsonString;
import javax.json.JsonValue;
import javax.json.stream.JsonGenerator;
import javax.json.stream.JsonGeneratorFactory;

class ObfuscatingJsonGenerator implements JsonGenerator {

    private final JsonGeneratorFactory jsonGeneratorFactory;
    private final JsonGenerator originalDelegate;
    private final JSONObfuscatorWriter writer;
    private final Map properties;
    private final boolean produceValidJSON;

    private final StringBuilder captured;
    private final Writer capturedWriter;

    private JsonGenerator delegate;
    private PropertyConfig currentProperty;
    private int depth = 0;

    @SuppressWarnings("resource")
    ObfuscatingJsonGenerator(JsonGeneratorFactory jsonGeneratorFactory, JSONObfuscatorWriter writer, Map properties,
            boolean produceValidJSON) {

        this.jsonGeneratorFactory = jsonGeneratorFactory;
        this.originalDelegate = jsonGeneratorFactory.createGenerator(new DontCloseWriter(writer));
        this.writer = writer;
        this.properties = properties;
        this.produceValidJSON = produceValidJSON;

        this.captured = new StringBuilder();
        this.capturedWriter = writer(captured);

        this.delegate = originalDelegate;
    }

    // Writing will flush the jsonGenerator after each write, so writes are done to the correct writer at the time.
    // The try-finally will ensure that these flushes are not propagated to the writer's backing writer.

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator writeStartObject() {
        if (currentProperty != null) {
            if (depth == 0) {
                if (currentProperty.obfuscateObjects) {
                    startObfuscating();

                    depth++;
                } else {
                    // There is an obfuscator for the object property, but the obfuscation mode prohibits obfuscating objects; reset the obfuscation
                    currentProperty = null;
                }
            } else {
                // In a nested object or array that's being obfuscated; do nothing
                depth++;
            }
        }
        // else not obfuscating

        try {
            writer.preventFlush();
            delegate.writeStartObject();
            delegate.flush();
        } finally {
            writer.allowFlush();
        }
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator writeStartObject(String name) {
        // Don't delegate but split instead
        writeKey(name);
        writeStartObject();
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator writeKey(String name) {
        if (currentProperty == null) {
            currentProperty = properties.get(name);
        }
        // else in a nested object or array that's being obfuscated; do nothing

        try {
            writer.preventFlush();
            delegate.writeKey(name);
            delegate.flush();
        } finally {
            writer.allowFlush();
        }
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator writeStartArray() {
        if (currentProperty != null) {
            if (depth == 0) {
                if (currentProperty.obfuscateArrays) {
                    startObfuscating();

                    depth++;
                } else {
                    // There is an obfuscator for the array property, but the obfuscation mode prohibits obfuscating arrays; reset the obfuscation
                    currentProperty = null;
                }
            } else {
                // In a nested object or array that's being obfuscated; do nothing
                depth++;
            }
        }
        // else not obfuscating

        try {
            writer.preventFlush();
            delegate.writeStartArray();
            delegate.flush();
        } finally {
            writer.allowFlush();
        }
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator writeStartArray(String name) {
        // Don't delegate but split instead
        writeKey(name);
        writeStartArray();
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(String name, JsonValue value) {
        // Don't delegate but split instead
        writeKey(name);
        write(value);
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(String name, String value) {
        // Don't delegate but split instead
        writeKey(name);
        write(value);
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(String name, BigInteger value) {
        // Don't delegate but split instead
        writeKey(name);
        write(value);
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(String name, BigDecimal value) {
        // Don't delegate but split instead
        writeKey(name);
        write(value);
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(String name, int value) {
        // Don't delegate but split instead
        writeKey(name);
        write(value);
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(String name, long value) {
        // Don't delegate but split instead
        writeKey(name);
        write(value);
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(String name, double value) {
        // Don't delegate but split instead
        writeKey(name);
        write(value);
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(String name, boolean value) {
        // Don't delegate but split instead
        writeKey(name);
        write(value);
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator writeNull(String name) {
        // Don't delegate but split instead
        writeKey(name);
        writeNull();
        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator writeEnd() {
        try {
            writer.preventFlush();
            delegate.writeEnd();
            delegate.flush();
        } finally {
            writer.allowFlush();
        }

        if (currentProperty != null) {
            depth--;
            if (depth == 0) {
                // The end of obfuscating an object or array
                endObfuscating();

                currentProperty = null;
            }
            // else still in a nested object that's being obfuscated
        }
        // else currently no object is being obfuscated
        return this;
    }

    private void startObfuscating() {
        if (currentProperty.isObfuscating()) {
            if (produceValidJSON) {
                // A new delegate is needed to be able to start a new object or array
                delegate = jsonGeneratorFactory.createGenerator(capturedWriter);
            } else {
                writer.startObfuscate(currentProperty.obfuscator);
            }
        }
    }

    private void endObfuscating() {
        if (currentProperty.isObfuscating()) {
            if (produceValidJSON) {
                // The delegate is a new generator around the writer around captured; close it and write the captured content as a string
                assert delegate != originalDelegate : "delegate must be a capturing generator"; //$NON-NLS-1$
                delegate.close();
                delegate = originalDelegate;
                writeQuoted(captured);
                captured.delete(0, captured.length());
            } else {
                // Obfuscation has started on writer; end obfuscation of the current object or array
                try {
                    writer.endObfuscate();
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            }
        }
    }

    @Override
    public JsonGenerator write(JsonValue value) {
        // Don't delegate but call correct write methods based on the type
        switch (value.getValueType()) {
        case OBJECT:
            write(value.asJsonObject());
            break;
        case ARRAY:
            write(value.asJsonArray());
            break;
        case STRING:
            write((JsonString) value);
            break;
        case NUMBER:
            write((JsonNumber) value);
            break;
        case TRUE:
            write(true);
            break;
        case FALSE:
            write(false);
            break;
        case NULL:
            writeNull();
            break;
        default:
            // Should not occur
            break;
        }

        return this;
    }

    @SuppressWarnings("resource")
    private void write(JsonObject object) {
        writeStartObject();
        for (Map.Entry entry : object.entrySet()) {
            write(entry.getKey(), entry.getValue());
        }
        writeEnd();
    }

    @SuppressWarnings("resource")
    private void write(JsonArray array) {
        writeStartArray();
        for (JsonValue element : array) {
            write(element);
        }
        writeEnd();
    }

    @SuppressWarnings("resource")
    private void write(JsonString string) {
        write(string.getString());
    }

    @SuppressWarnings("resource")
    private void write(JsonNumber number) {
        if (number.isIntegral()) {
            try {
                write(number.longValueExact());
            } catch (@SuppressWarnings("unused") ArithmeticException e) {
                write(number.bigIntegerValue());
            }
        } else {
            write(number.bigDecimalValue());
        }
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(String value) {
        if (currentProperty != null && depth == 0) {
            if (currentProperty.isObfuscating()) {
                writeString(value);
            } else {
                delegate.write(value);
            }
            currentProperty = null;
        } else {
            // Not obfuscating, or in a nested object or array that's being obfuscated; just delegate
            delegate.write(value);
        }

        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(BigDecimal value) {
        if (currentProperty != null && depth == 0) {
            if (currentProperty.isObfuscating()) {
                writeNonString(value.toString());
            } else {
                delegate.write(value);
            }
            currentProperty = null;
        } else {
            // Not obfuscating, or in a nested object or array that's being obfuscated; just delegate
            delegate.write(value);
        }

        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(BigInteger value) {
        if (currentProperty != null && depth == 0) {
            if (currentProperty.isObfuscating()) {
                writeNonString(value.toString());
            } else {
                delegate.write(value);
            }
            currentProperty = null;
        } else {
            // Not obfuscating, or in a nested object or array that's being obfuscated; just delegate
            delegate.write(value);
        }

        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(int value) {
        if (currentProperty != null && depth == 0) {
            if (currentProperty.isObfuscating()) {
                writeNonString(Integer.toString(value));
            } else {
                delegate.write(value);
            }
            currentProperty = null;
        } else {
            // Not obfuscating, or in a nested object or array that's being obfuscated; just delegate
            delegate.write(value);
        }

        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(long value) {
        if (currentProperty != null && depth == 0) {
            if (currentProperty.isObfuscating()) {
                writeNonString(Long.toString(value));
            } else {
                delegate.write(value);
            }
            currentProperty = null;
        } else {
            // Not obfuscating, or in a nested object or array that's being obfuscated; just delegate
            delegate.write(value);
        }

        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(double value) {
        if (currentProperty != null && depth == 0) {
            if (currentProperty.isObfuscating()) {
                writeNonString(Double.toString(value));
            } else {
                delegate.write(value);
            }
            currentProperty = null;
        } else {
            // Not obfuscating, or in a nested object or array that's being obfuscated; just delegate
            delegate.write(value);
        }

        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator write(boolean value) {
        if (currentProperty != null && depth == 0) {
            if (currentProperty.isObfuscating()) {
                writeNonString(Boolean.toString(value));
            } else {
                delegate.write(value);
            }
            currentProperty = null;
        } else {
            // Not obfuscating, or in a nested object or array that's being obfuscated; just delegate
            delegate.write(value);
        }

        return this;
    }

    @Override
    @SuppressWarnings("resource")
    public JsonGenerator writeNull() {
        if (currentProperty != null && depth == 0) {
            if (currentProperty.isObfuscating()) {
                writeNonString("null"); //$NON-NLS-1$
            } else {
                delegate.writeNull();
            }
            currentProperty = null;
        } else {
            // Not obfuscating, or in a nested object or array that's being obfuscated; just delegate
            delegate.writeNull();
        }

        return this;
    }

    private void writeString(CharSequence value) {
        writeQuoted(value);
    }

    private void writeNonString(CharSequence value) {
        if (produceValidJSON) {
            writeQuoted(value);
        } else {
            writeUnquoted(value);
        }
    }

    @SuppressWarnings("resource")
    private void writeQuoted(CharSequence value) {
        try {
            writer.preventFlush();
            delegate.write(currentProperty.obfuscator.obfuscateText(value).toString());
            delegate.flush();
        } finally {
            writer.allowFlush();
        }
    }

    @SuppressWarnings("resource")
    private void writeUnquoted(CharSequence value) {
        try {
            writer.preventFlush();
            writer.startUnquote();
            delegate.write(currentProperty.obfuscator.obfuscateText(value).toString());
            delegate.flush();
            writer.endUnquote();
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } finally {
            writer.allowFlush();
        }
    }

    @Override
    public void close() {
        delegate.close();
    }

    @Override
    public void flush() {
        delegate.flush();
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy