diff options
author | Joseph Wen <josephwen@google.com> | 2015-05-26 11:43:00 -0400 |
---|---|---|
committer | Joseph Wen <josephwen@google.com> | 2015-05-26 11:43:00 -0400 |
commit | 3bbc90992645934df523762f7dcdb097eae366d5 (patch) | |
tree | 58ddd8ac7aec0b2db28f2de93879bcf31bd9fefc /packages/StatementService | |
parent | f981ea95f2e951a558f73fb941cf006423e52627 (diff) | |
download | frameworks_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
Diffstat (limited to 'packages/StatementService')
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)))); |