1 /* <lambda>null2 * Copyright (C) 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.statementservice.network.retriever 18 19 import android.util.JsonReader 20 import com.android.statementservice.retriever.AbstractAsset 21 import com.android.statementservice.retriever.AssetFactory 22 import com.android.statementservice.retriever.JsonParser 23 import com.android.statementservice.retriever.Relation 24 import com.android.statementservice.retriever.Statement 25 import com.android.statementservice.utils.Result 26 import com.android.statementservice.utils.StatementUtils 27 import java.io.StringReader 28 import java.util.ArrayList 29 import com.android.statementservice.retriever.WebAsset 30 import com.android.statementservice.retriever.AndroidAppAsset 31 import com.android.statementservice.retriever.DynamicAppLinkComponent 32 import org.json.JSONObject 33 34 /** 35 * Parses JSON from the Digital Asset Links specification. For examples, see [WebAsset], 36 * [AndroidAppAsset], and [Statement]. 37 */ 38 object StatementParser { 39 40 private const val FIELD_NOT_STRING_FORMAT_STRING = "Expected %s to be string." 41 private const val FIELD_NOT_ARRAY_FORMAT_STRING = "Expected %s to be array." 42 private const val COMMENTS_NAME = "comments" 43 private const val EXCLUDE_NAME = "exclude" 44 private const val FRAGMENT_NAME = "#" 45 private const val QUERY_NAME = "?" 46 private const val PATH_NAME = "/" 47 48 /** 49 * Parses a JSON array of statements. 50 */ 51 fun parseStatementList(statementList: String, source: AbstractAsset): Result<ParsedStatement> { 52 val statements: MutableList<Statement> = ArrayList() 53 val delegates: MutableList<String> = ArrayList() 54 StringReader(statementList).use { stringReader -> 55 JsonReader(stringReader).use { reader -> 56 reader.isLenient = false 57 reader.beginArray() 58 while (reader.hasNext()) { 59 val result = parseOneStatement(reader, source) 60 if (result is Result.Failure) { 61 continue 62 } 63 result as Result.Success 64 statements.addAll(result.value.statements) 65 delegates.addAll(result.value.delegates) 66 } 67 reader.endArray() 68 } 69 } 70 return Result.Success(ParsedStatement(statements, delegates)) 71 } 72 73 /** 74 * Parses a single JSON statement. 75 */ 76 fun parseStatement(statementString: String, source: AbstractAsset) = 77 StringReader(statementString).use { stringReader -> 78 JsonReader(stringReader).use { reader -> 79 reader.isLenient = false 80 parseOneStatement(reader, source) 81 } 82 } 83 84 /** 85 * Parses a single JSON statement. This method guarantees that exactly one JSON object 86 * will be consumed. 87 */ 88 private fun parseOneStatement( 89 reader: JsonReader, 90 source: AbstractAsset 91 ): Result<ParsedStatement> { 92 val statement = JsonParser.parse(reader) 93 val delegate = statement.optString(StatementUtils.DELEGATE_FIELD_DELEGATE) 94 if (!delegate.isNullOrEmpty()) { 95 return Result.Success(ParsedStatement(emptyList(), listOfNotNull(delegate))) 96 } 97 98 val targetObject = statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_TARGET) 99 ?: return Result.Failure( 100 FIELD_NOT_STRING_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_TARGET) 101 ) 102 val relations = statement.optJSONArray(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION) 103 ?: return Result.Failure( 104 FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION) 105 ) 106 val target = AssetFactory.create(targetObject) 107 val dynamicAppLinkComponents = parseDynamicAppLinkComponents(statement) 108 109 val statements = (0 until relations.length()) 110 .map { relations.getString(it) } 111 .map(Relation::create) 112 .map { Statement.create(source, target, it, dynamicAppLinkComponents) } 113 return Result.Success(ParsedStatement(statements, listOfNotNull(delegate))) 114 } 115 116 private fun parseDynamicAppLinkComponents( 117 statement: JSONObject? 118 ): List<DynamicAppLinkComponent> { 119 val relationExtensions = statement?.optJSONObject( 120 StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS 121 ) ?: return emptyList() 122 val handleAllUrlsRelationExtension = relationExtensions.optJSONObject( 123 StatementUtils.RELATION.toString() 124 ) ?: return emptyList() 125 val components = handleAllUrlsRelationExtension.optJSONArray( 126 StatementUtils.RELATION_EXTENSION_FIELD_DAL_COMPONENTS 127 ) ?: return emptyList() 128 129 return (0 until components.length()) 130 .map { components.getJSONObject(it) } 131 .map { parseComponent(it) } 132 } 133 134 private fun parseComponent(component: JSONObject): DynamicAppLinkComponent { 135 val query = component.optJSONObject(QUERY_NAME) 136 return DynamicAppLinkComponent.create( 137 component.optBoolean(EXCLUDE_NAME, false), 138 if (component.has(FRAGMENT_NAME)) component.getString(FRAGMENT_NAME) else null, 139 if (component.has(PATH_NAME)) component.getString(PATH_NAME) else null, 140 query?.keys()?.asSequence()?.associateWith { query.getString(it) }, 141 component.optString(COMMENTS_NAME) 142 ) 143 } 144 145 data class ParsedStatement(val statements: List<Statement>, val delegates: List<String>) 146 } 147