Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit a3547847 authored by William Loh's avatar William Loh Committed by Android (Google) Code Review
Browse files

Merge "Update statementservice assetlink.json parsing" into main

parents 221899ef 9a55bf35
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -35,6 +35,7 @@ android_app {
    privileged: true,
    certificate: "platform",
    static_libs: [
        "StatementServiceParser",
        "androidx.appcompat_appcompat",
        "androidx.collection_collection-ktx",
        "androidx.work_work-runtime",
+35 −1
Original line number Diff line number Diff line
@@ -28,6 +28,8 @@ import java.io.StringReader
import java.util.ArrayList
import com.android.statementservice.retriever.WebAsset
import com.android.statementservice.retriever.AndroidAppAsset
import com.android.statementservice.retriever.DynamicAppLinkComponent
import org.json.JSONObject

/**
 * Parses JSON from the Digital Asset Links specification. For examples, see [WebAsset],
@@ -97,13 +99,45 @@ object StatementParser {
                FIELD_NOT_ARRAY_FORMAT_STRING.format(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION)
            )
        val target = AssetFactory.create(targetObject)
        val dynamicAppLinkComponents = parseDynamicAppLinkComponents(
            statement.optJSONObject(StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS)
        )

        val statements = (0 until relations.length())
            .map { relations.getString(it) }
            .map(Relation::create)
            .map { Statement.create(source, target, it) }
            .map { Statement.create(source, target, it, dynamicAppLinkComponents) }
        return Result.Success(ParsedStatement(statements, listOfNotNull(delegate)))
    }

    private fun parseDynamicAppLinkComponents(
        statement: JSONObject?
    ): List<DynamicAppLinkComponent> {
        val relationExtensions = statement?.optJSONObject(
            StatementUtils.ASSET_DESCRIPTOR_FIELD_RELATION_EXTENSIONS
        ) ?: return emptyList()
        val handleAllUrlsRelationExtension = relationExtensions.optJSONObject(
            StatementUtils.RELATION.toString()
        ) ?: return emptyList()
        val components = handleAllUrlsRelationExtension.optJSONArray(
            StatementUtils.RELATION_EXTENSION_FIELD_DAL_COMPONENTS
        ) ?: return emptyList()

        return (0 until components.length())
            .map { components.getJSONObject(it) }
            .map { parseComponent(it) }
    }

    private fun parseComponent(component: JSONObject): DynamicAppLinkComponent {
        val query = component.optJSONObject("?")
        return DynamicAppLinkComponent.create(
            component.optBoolean("exclude", false),
            component.optString("#"),
            component.optString("/"),
            query?.keys()?.asSequence()?.associateWith { query.getString(it) },
            component.optString("comments")
        )
    }

    data class ParsedStatement(val statements: List<Statement>, val delegates: List<String>)
}
+145 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2024 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.annotation.Nullable;

import java.util.Map;

/**
 * A immutable value type representing a dynamic app link component
 */
public final class DynamicAppLinkComponent {
    private final boolean mExclude;
    private final String mFragment;
    private final String mPath;
    private final Map<String, String> mQuery;
    private final String mComments;

    private DynamicAppLinkComponent(boolean exclude, String fragment, String path,
                                    Map<String, String> query, String comments) {
        mExclude = exclude;
        mFragment = fragment;
        mPath = path;
        mQuery = query;
        mComments = comments;
    }

    /**
     * Returns true or false indicating whether this rule should be a exclusion rule.
     */
    public boolean getExclude() {
        return mExclude;
    }

    /**
     * Returns a optional pattern string for matching URL fragments.
     */
    @Nullable
    public String getFragment() {
        return mFragment;
    }

    /**
     * Returns a optional pattern string for matching URL paths.
     */
    @Nullable
    public String getPath() {
        return mPath;
    }

    /**
     * Returns a optional pattern string for matching a single key-value pair in the URL query
     * params.
     */
    @Nullable
    public Map<String, String> getQuery() {
        return mQuery;
    }

    /**
     * Returns a optional comment string for this component.
     */
    @Nullable
    public String getComments() {
        return mComments;
    }

    /**
     * Creates a new DynamicAppLinkComponent object.
     */
    public static DynamicAppLinkComponent create(boolean exclude, String fragment, String path,
                                                 Map<String, String> query, String comments) {
        return new DynamicAppLinkComponent(exclude, fragment, path, query, comments);
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }

        DynamicAppLinkComponent rule = (DynamicAppLinkComponent) o;

        if (mExclude != rule.mExclude) {
            return false;
        }
        if (!mFragment.equals(rule.mFragment)) {
            return false;
        }
        if (!mPath.equals(rule.mPath)) {
            return false;
        }
        if (!mQuery.equals(rule.mQuery)) {
            return false;
        }
        if (!mComments.equals(rule.mComments)) {
            return false;
        }

        return true;
    }

    @Override
    public int hashCode() {
        int result = Boolean.hashCode(mExclude);
        result = 31 * result + mFragment.hashCode();
        result = 31 * result + mPath.hashCode();
        result = 31 * result + mQuery.hashCode();
        result = 31 * result + mComments.hashCode();
        return result;
    }

    @Override
    public String toString() {
        StringBuilder statement = new StringBuilder();
        statement.append("HandleAllUriRule: ");
        statement.append(mExclude);
        statement.append(", ");
        statement.append(mFragment);
        statement.append(", ");
        statement.append(mPath);
        statement.append(", ");
        statement.append(mQuery);
        statement.append(", ");
        statement.append(mComments);
        return statement.toString();
    }
}
+0 −6
Original line number Diff line number Diff line
@@ -46,12 +46,6 @@ public final class JsonParser {
        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)));
+46 −5
Original line number Diff line number Diff line
@@ -23,6 +23,10 @@ import com.android.statementservice.network.retriever.StatementRetriever;

import kotlin.coroutines.Continuation;

import java.util.Collections;
import java.util.List;


/**
 * An immutable value type representing a statement, consisting of a source, target, and relation.
 * This reflects an assertion that the relation holds for the source, target pair. For example, if a
@@ -32,7 +36,21 @@ import kotlin.coroutines.Continuation;
 * {
 * "relation": ["delegate_permission/common.handle_all_urls"],
 * "target"  : {"namespace": "android_app", "package_name": "com.example.app",
 *              "sha256_cert_fingerprints": ["00:11:22:33"] }
 *              "sha256_cert_fingerprints": ["00:11:22:33"] },
 * "relation_extensions": {
 *     "delegate_permission/common_handle_all_urls": {
 *         "dynamic_app_link_components": [
 *             {
 *                 "/": "/foo*",
 *                 "exclude": true,
 *                 "comments": "App should not handle paths that start with foo"
 *             },
 *             {
 *                 "/": "*",
 *                 "comments": "Catch all other paths"
 *             }
 *         ]
 *     }
 * }
 * </pre>
 *
@@ -40,7 +58,7 @@ import kotlin.coroutines.Continuation;
 * return a {@link Statement} with {@link #getSource} equal to the input parameter,
 * {@link #getRelation} equal to
 *
 * <pre>Relation.create("delegate_permission", "common.get_login_creds");</pre>
 * <pre>Relation.create("delegate_permission", "common.handle_all_urls");</pre>
 *
 * and with {@link #getTarget} equal to
 *
@@ -48,17 +66,23 @@ import kotlin.coroutines.Continuation;
 *                           + "\"package_name\": \"com.example.app\"}"
 *                           + "\"sha256_cert_fingerprints\": \"[\"00:11:22:33\"]\"}");
 * </pre>
 *
 * If extensions exist for the handle_all_urls relation then {@link #getDynamicAppLinkComponents}
 * will return a list of parsed {@link DynamicAppLinkComponent}s.
 */
public final class Statement {

    private final AbstractAsset mTarget;
    private final Relation mRelation;
    private final AbstractAsset mSource;
    private final List<DynamicAppLinkComponent> mDynamicAppLinkComponents;

    private Statement(AbstractAsset source, AbstractAsset target, Relation relation) {
    private Statement(AbstractAsset source, AbstractAsset target, Relation relation,
                      List<DynamicAppLinkComponent> components) {
        mSource = source;
        mTarget = target;
        mRelation = relation;
        mDynamicAppLinkComponents = Collections.unmodifiableList(components);
    }

    /**
@@ -85,6 +109,14 @@ public final class Statement {
        return mRelation;
    }

    /**
     * Returns the relation matching rules of the statement.
     */
    @NonNull
    public List<DynamicAppLinkComponent> getDynamicAppLinkComponents() {
        return mDynamicAppLinkComponents;
    }

    /**
     * Creates a new Statement object for the specified target asset and relation. For example:
     * <pre>
@@ -95,8 +127,9 @@ public final class Statement {
     * </pre>
     */
    public static Statement create(@NonNull AbstractAsset source, @NonNull AbstractAsset target,
                                   @NonNull Relation relation) {
        return new Statement(source, target, relation);
                                   @NonNull Relation relation,
                                   @NonNull List<DynamicAppLinkComponent> components) {
        return new Statement(source, target, relation, components);
    }

    @Override
@@ -119,6 +152,9 @@ public final class Statement {
        if (!mSource.equals(statement.mSource)) {
            return false;
        }
        if (!mDynamicAppLinkComponents.equals(statement.mDynamicAppLinkComponents)) {
            return false;
        }

        return true;
    }
@@ -128,6 +164,7 @@ public final class Statement {
        int result = mTarget.hashCode();
        result = 31 * result + mRelation.hashCode();
        result = 31 * result + mSource.hashCode();
        result = 31 * result + mDynamicAppLinkComponents.hashCode();
        return result;
    }

@@ -140,6 +177,10 @@ public final class Statement {
        statement.append(mTarget);
        statement.append(", ");
        statement.append(mRelation);
        if (!mDynamicAppLinkComponents.isEmpty()) {
            statement.append(", ");
            statement.append(mDynamicAppLinkComponents);
        }
        return statement.toString();
    }
}
Loading