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

com.google.crypto.tink.internal.JsonParser Maven / Gradle / Ivy

Go to download

Tink is a small cryptographic library that provides a safe, simple, agile and fast way to accomplish some common cryptographic tasks.

There is a newer version: 1.2.2
Show newest version
/*
 * Copyright 2011 Google LLC
 *
 * 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.google.crypto.tink.internal;

import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;
import com.google.gson.stream.JsonToken;
import com.google.gson.stream.JsonWriter;
import java.io.IOException;
import java.io.NotSerializableException;
import java.io.ObjectInputStream;
import java.io.StringReader;
import java.math.BigDecimal;
import java.util.ArrayDeque;
import java.util.Deque;
import javax.annotation.Nullable;

/**
 * A JSON Parser based on the GSON JsonReader.
 *
 * 

The parsing is almost identical to the normal parser provided by GSON with these changes: it * never uses "lenient" mode, it rejects duplicated map keys and it rejects strings with invalid * UTF16 characters. * *

The implementation is adapted from almost identical to GSON's TypeAdapters.JSON_ELEMENT. */ public final class JsonParser { public static boolean isValidString(String s) { int length = s.length(); int i = 0; while (true) { char ch; do { if (i == length) { return true; } ch = s.charAt(i); i++; } while (!Character.isSurrogate(ch)); if (Character.isLowSurrogate(ch) || i == length || !Character.isLowSurrogate(s.charAt(i))) { return false; } i++; } } /** This is a modified copy of Gson's internal LazilyParsedNumber. */ @SuppressWarnings("serial") // Serialization is not supported. Throws NotSerializableException private static final class LazilyParsedNumber extends Number { private final String value; public LazilyParsedNumber(String value) { this.value = value; } @Override public int intValue() { try { return Integer.parseInt(value); } catch (NumberFormatException e) { try { return (int) Long.parseLong(value); } catch (NumberFormatException nfe) { return new BigDecimal(value).intValue(); } } } @Override public long longValue() { try { return Long.parseLong(value); } catch (NumberFormatException e) { return new BigDecimal(value).longValue(); } } @Override public float floatValue() { return Float.parseFloat(value); } @Override public double doubleValue() { return Double.parseDouble(value); } @Override public String toString() { return value; } private Object writeReplace() throws NotSerializableException { throw new NotSerializableException("serialization is not supported"); } private void readObject(ObjectInputStream in) throws NotSerializableException { throw new NotSerializableException("serialization is not supported"); } @Override public int hashCode() { return value.hashCode(); } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj instanceof LazilyParsedNumber) { LazilyParsedNumber other = (LazilyParsedNumber) obj; return value.equals(other.value); } return false; } } private static final class JsonElementTypeAdapter extends TypeAdapter { private static final int RECURSION_LIMIT = 100; /** * Tries to begin reading a JSON array or JSON object, returning {@code null} if the next * element is neither of those. */ @Nullable private JsonElement tryBeginNesting(JsonReader in, JsonToken peeked) throws IOException { switch (peeked) { case BEGIN_ARRAY: in.beginArray(); return new JsonArray(); case BEGIN_OBJECT: in.beginObject(); return new JsonObject(); default: return null; } } /** Reads a {@link JsonElement} which cannot have any nested elements */ private JsonElement readTerminal(JsonReader in, JsonToken peeked) throws IOException { switch (peeked) { case STRING: String value = in.nextString(); if (!isValidString(value)) { throw new IOException("illegal characters in string"); } return new JsonPrimitive(value); case NUMBER: String number = in.nextString(); return new JsonPrimitive(new LazilyParsedNumber(number)); case BOOLEAN: return new JsonPrimitive(in.nextBoolean()); case NULL: in.nextNull(); return JsonNull.INSTANCE; default: // When read(JsonReader) is called with JsonReader in invalid state throw new IllegalStateException("Unexpected token: " + peeked); } } @Override public JsonElement read(JsonReader in) throws IOException { // Either JsonArray or JsonObject JsonElement current; JsonToken peeked = in.peek(); current = tryBeginNesting(in, peeked); if (current == null) { return readTerminal(in, peeked); } Deque stack = new ArrayDeque<>(); while (true) { while (in.hasNext()) { String name = null; // Name is only used for JSON object members if (current instanceof JsonObject) { name = in.nextName(); if (!isValidString(name)) { throw new IOException("illegal characters in string"); } } peeked = in.peek(); JsonElement value = tryBeginNesting(in, peeked); boolean isNesting = value != null; if (value == null) { value = readTerminal(in, peeked); } if (current instanceof JsonArray) { ((JsonArray) current).add(value); } else { if (((JsonObject) current).has(name)) { throw new IOException("duplicate key: " + name); } ((JsonObject) current).add(name, value); } if (isNesting) { stack.addLast(current); if (stack.size() > RECURSION_LIMIT) { throw new IOException("too many recursions"); } current = value; } } // End current element if (current instanceof JsonArray) { in.endArray(); } else { in.endObject(); } if (stack.isEmpty()) { return current; } else { // Continue with enclosing element current = stack.removeLast(); } } } @Override public void write(JsonWriter out, JsonElement value) { throw new UnsupportedOperationException("write is not supported"); } } private static final JsonElementTypeAdapter JSON_ELEMENT = new JsonElementTypeAdapter(); public static JsonElement parse(String json) throws IOException { try { JsonReader jsonReader = new JsonReader(new StringReader(json)); jsonReader.setLenient(false); return JSON_ELEMENT.read(jsonReader); } catch (NumberFormatException e) { throw new IOException(e); } } /** * Converts a parsed {@link Number} into a long if it contains a valid long value. * *

Requires that {@code number} is part of a output produced by {@link #parse}. * * @throws NumberFormatException if {@code number} does not contain a valid long value. * */ public static long getParsedNumberAsLongOrThrow(Number number) { if (!(number instanceof LazilyParsedNumber)) { // We restrict this function to LazilyParsedNumber because then we know that "toString" will // return the unparsed number. For other implementations of Number interface, it is not // clearly defined what toString will return. throw new IllegalArgumentException("does not contain a parsed number."); } // Note that we cannot use number.longValue() here, because longValue() does not throw an // exception if number is not a valid long. return Long.parseLong(number.toString()); } private JsonParser() {} }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy