summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJoseph Wen <josephwen@google.com>2015-05-26 11:43:00 -0400
committerJoseph Wen <josephwen@google.com>2015-05-26 11:43:00 -0400
commit3bbc90992645934df523762f7dcdb097eae366d5 (patch)
tree58ddd8ac7aec0b2db28f2de93879bcf31bd9fefc
parentf981ea95f2e951a558f73fb941cf006423e52627 (diff)
downloadframeworks_base-3bbc90992645934df523762f7dcdb097eae366d5.zip
frameworks_base-3bbc90992645934df523762f7dcdb097eae366d5.tar.gz
frameworks_base-3bbc90992645934df523762f7dcdb097eae366d5.tar.bz2
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
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java16
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java17
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java4
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java94
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java59
5 files changed, 161 insertions, 29 deletions
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java b/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java
index bb6bdbb..8d6fd66 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AbstractAsset.java
@@ -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);
+ }
}
/**
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java b/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java
index 762365e..519d73a 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/AssetFactory.java
@@ -16,7 +16,6 @@
package com.android.statementservice.retriever;
-import org.json.JSONException;
import org.json.JSONObject;
/**
@@ -29,25 +28,11 @@ 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) {
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java b/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java
index 548149e..2ca85e9 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java
@@ -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);
}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
new file mode 100644
index 0000000..ce063ea
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/JsonParser.java
@@ -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;
+ }
+}
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java b/packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java
index bcd91bd..0369718 100644
--- a/packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java
+++ b/packages/StatementService/src/com/android/statementservice/retriever/StatementParser.java
@@ -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))));