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
This commit is contained in:
Joseph Wen
2015-05-26 11:43:00 -04:00
parent f981ea95f2
commit 3bbc909926
5 changed files with 161 additions and 29 deletions

View File

@@ -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);
}
}
/**

View File

@@ -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) {

View File

@@ -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);
}

View File

@@ -0,0 +1,94 @@
/*
* 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;
}
}

View File

@@ -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))));