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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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))));
|
||||
|
||||
Reference in New Issue
Block a user