Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 3bbc9099 authored by Joseph Wen's avatar Joseph Wen
Browse files

Update Statement Service.

JSONObject parser is too lenient when parsing Json string. Security review
suggested us to use a stricter parser, which we implemented with
JsonReader in this CL.

BUG=20665035

Change-Id: I379976731a1d35ef8ec746f3a6e78be998370f00
parent f981ea95
Loading
Loading
Loading
Loading
+15 −1
Original line number Diff line number Diff line
@@ -16,6 +16,13 @@

package com.android.statementservice.retriever;

import android.util.JsonReader;

import org.json.JSONException;

import java.io.IOException;
import java.io.StringReader;

/**
 * A handle representing the identity and address of some digital asset. An asset is an online
 * entity that typically provides some service or content. Examples of assets are websites, Android
@@ -61,7 +68,14 @@ public abstract class AbstractAsset {
     */
    public static AbstractAsset create(String assetJson)
            throws AssociationServiceException {
        return AssetFactory.create(assetJson);
        JsonReader reader = new JsonReader(new StringReader(assetJson));
        reader.setLenient(false);
        try {
            return AssetFactory.create(JsonParser.parse(reader));
        } catch (JSONException | IOException e) {
            throw new AssociationServiceException(
                    "Input is not a well formatted asset descriptor.", e);
        }
    }

    /**
+1 −16
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@

package com.android.statementservice.retriever;

import org.json.JSONException;
import org.json.JSONObject;

/**
@@ -28,26 +27,12 @@ import org.json.JSONObject;

    private AssetFactory() {}

    /**
     * Creates a new Asset object from its JSON string representation.
     *
     * @throws AssociationServiceException if the assetJson is not well formatted.
     */
    public static AbstractAsset create(String assetJson) throws AssociationServiceException {
        try {
            return create(new JSONObject(assetJson));
        } catch (JSONException e) {
            throw new AssociationServiceException(
                    "Input is not a well formatted asset descriptor.");
        }
    }

    /**
     * Checks that the input is a valid asset with purposes.
     *
     * @throws AssociationServiceException if the asset is not well formatted.
     */
    private static AbstractAsset create(JSONObject asset)
    public static AbstractAsset create(JSONObject asset)
            throws AssociationServiceException {
        String namespace = asset.optString(Utils.NAMESPACE_FIELD, null);
        if (namespace == null) {
+2 −2
Original line number Diff line number Diff line
@@ -167,7 +167,7 @@ import java.util.List;
                                .getStatements());
            }
            return Result.create(statements, webContent.getExpireTimeMillis());
        } catch (JSONException e) {
        } catch (JSONException | IOException e) {
            return Result.create(statements, DO_NOT_CACHE_RESULT);
        }
    }
@@ -202,7 +202,7 @@ import java.util.List;
            }

            return Result.create(statements, DO_NOT_CACHE_RESULT);
        } catch (JSONException | NameNotFoundException e) {
        } catch (JSONException | IOException | NameNotFoundException e) {
            Log.w(DirectStatementRetriever.class.getSimpleName(), e);
            return Result.create(Collections.<Statement>emptyList(), DO_NOT_CACHE_RESULT);
        }
+94 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2015 The Android Open Source Project
 *
 * 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.android.statementservice.retriever;

import android.util.JsonReader;
import android.util.JsonToken;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

/**
 * A helper class that creates a {@link JSONObject} from a {@link JsonReader}.
 */
public final class JsonParser {

    private JsonParser() {}

    /**
     * Consumes and parses exactly one JSON object from the {@link JsonReader}.
     * The object's fields can only be objects, strings or arrays of strings.
     */
    public static JSONObject parse(JsonReader reader) throws IOException, JSONException {
        JSONObject output = new JSONObject();
        String errorMsg = null;

        reader.beginObject();
        while (reader.hasNext()) {
            String fieldName = reader.nextName();

            if (output.has(fieldName)) {
                errorMsg = "Duplicate field name.";
                reader.skipValue();
                continue;
            }

            JsonToken token = reader.peek();
            if (token.equals(JsonToken.BEGIN_ARRAY)) {
                output.put(fieldName, new JSONArray(parseArray(reader)));
            } else if (token.equals(JsonToken.STRING)) {
                output.put(fieldName, reader.nextString());
            } else if (token.equals(JsonToken.BEGIN_OBJECT)) {
                try {
                    output.put(fieldName, parse(reader));
                } catch (JSONException e) {
                    errorMsg = e.getMessage();
                }
            } else {
                reader.skipValue();
                errorMsg = "Unsupported value type.";
            }
        }
        reader.endObject();

        if (errorMsg != null) {
            throw new JSONException(errorMsg);
        }

        return output;
    }

    /**
     * Parses one string array from the {@link JsonReader}.
     */
    public static List<String> parseArray(JsonReader reader) throws IOException {
        ArrayList<String> output = new ArrayList<>();

        reader.beginArray();
        while (reader.hasNext()) {
            output.add(reader.nextString());
        }
        reader.endArray();

        return output;
    }
}
+49 −10
Original line number Diff line number Diff line
@@ -16,10 +16,14 @@

package com.android.statementservice.retriever;

import android.util.JsonReader;

import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;

import java.io.IOException;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.List;

@@ -28,20 +32,33 @@ import java.util.List;
 */
/* package private */ final class StatementParser {

    private static final String FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string.";
    private static final String FIELD_NOT_ARRAY_FORMAT_STRING = "Expected %s to be array.";

    /**
     * Parses a JSON array of statements.
     */
    static ParsedStatement parseStatementList(String statementList, AbstractAsset source)
            throws JSONException, AssociationServiceException {
            throws JSONException, IOException {
        List<Statement> statements = new ArrayList<Statement>();
        List<String> delegates = new ArrayList<String>();

        JSONArray statementsJson = new JSONArray(statementList);
        for (int i = 0; i < statementsJson.length(); i++) {
            ParsedStatement result = parseStatement(statementsJson.getString(i), source);
        JsonReader reader = new JsonReader(new StringReader(statementList));
        reader.setLenient(false);

        reader.beginArray();
        while (reader.hasNext()) {
            ParsedStatement result;
            try {
                result = parseStatement(reader, source);
            } catch (AssociationServiceException e) {
                // The element in the array is well formatted Json but not a well-formed Statement.
                continue;
            }
            statements.addAll(result.getStatements());
            delegates.addAll(result.getDelegates());
        }
        reader.endArray();

        return new ParsedStatement(statements, delegates);
    }
@@ -50,17 +67,39 @@ import java.util.List;
     * Parses a single JSON statement.
     */
    static ParsedStatement parseStatement(String statementString, AbstractAsset source)
            throws JSONException, AssociationServiceException {
            throws AssociationServiceException, IOException, JSONException {
        JsonReader reader = new JsonReader(new StringReader(statementString));
        reader.setLenient(false);
        return parseStatement(reader, source);
    }

    /**
     * Parses a single JSON statement. This method guarantees that exactly one JSON object
     * will be consumed.
     */
    static ParsedStatement parseStatement(JsonReader reader, AbstractAsset source)
            throws JSONException, AssociationServiceException, IOException {
        List<Statement> statements = new ArrayList<Statement>();
        List<String> delegates = new ArrayList<String>();
        JSONObject statement = new JSONObject(statementString);

        JSONObject statement = JsonParser.parse(reader);

        if (statement.optString(Utils.DELEGATE_FIELD_DELEGATE, null) != null) {
            delegates.add(statement.optString(Utils.DELEGATE_FIELD_DELEGATE));
        } else {
            AbstractAsset target = AssetFactory
                    .create(statement.getString(Utils.ASSET_DESCRIPTOR_FIELD_TARGET));
            JSONArray relations = statement.getJSONArray(
                    Utils.ASSET_DESCRIPTOR_FIELD_RELATION);
            JSONObject targetObject = statement.optJSONObject(Utils.ASSET_DESCRIPTOR_FIELD_TARGET);
            if (targetObject == null) {
                throw new AssociationServiceException(String.format(
                        FIELD_NOT_STRING_FORMAT_STRING, Utils.ASSET_DESCRIPTOR_FIELD_TARGET));
            }

            JSONArray relations = statement.optJSONArray(Utils.ASSET_DESCRIPTOR_FIELD_RELATION);
            if (relations == null) {
                throw new AssociationServiceException(String.format(
                        FIELD_NOT_ARRAY_FORMAT_STRING, Utils.ASSET_DESCRIPTOR_FIELD_RELATION));
            }

            AbstractAsset target = AssetFactory.create(targetObject);
            for (int i = 0; i < relations.length(); i++) {
                statements.add(Statement
                        .create(source, target, Relation.create(relations.getString(i))));