summaryrefslogtreecommitdiffstats
path: root/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java
diff options
context:
space:
mode:
Diffstat (limited to 'packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java')
-rw-r--r--packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java204
1 files changed, 204 insertions, 0 deletions
diff --git a/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java b/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java
new file mode 100644
index 0000000..3ad71c4
--- /dev/null
+++ b/packages/StatementService/src/com/android/statementservice/retriever/DirectStatementRetriever.java
@@ -0,0 +1,204 @@
+/*
+ * 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.content.pm.PackageManager.NameNotFoundException;
+import android.util.Log;
+
+import org.json.JSONException;
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/**
+ * An implementation of {@link AbstractStatementRetriever} that directly retrieves statements from
+ * the asset.
+ */
+/* package private */ final class DirectStatementRetriever extends AbstractStatementRetriever {
+
+ private static final long DO_NOT_CACHE_RESULT = 0L;
+ private static final int HTTP_CONNECTION_TIMEOUT_MILLIS = 5000;
+ private static final long HTTP_CONTENT_SIZE_LIMIT_IN_BYTES = 1024 * 1024;
+ private static final int MAX_INCLUDE_LEVEL = 1;
+ private static final String WELL_KNOWN_STATEMENT_PATH = "/.well-known/associations.json";
+
+ private final URLFetcher mUrlFetcher;
+ private final AndroidPackageInfoFetcher mAndroidFetcher;
+
+ /**
+ * An immutable value type representing the retrieved statements and the expiration date.
+ */
+ public static class Result implements AbstractStatementRetriever.Result {
+
+ private final List<Statement> mStatements;
+ private final Long mExpireMillis;
+
+ @Override
+ public List<Statement> getStatements() {
+ return mStatements;
+ }
+
+ @Override
+ public long getExpireMillis() {
+ return mExpireMillis;
+ }
+
+ private Result(List<Statement> statements, Long expireMillis) {
+ mStatements = statements;
+ mExpireMillis = expireMillis;
+ }
+
+ public static Result create(List<Statement> statements, Long expireMillis) {
+ return new Result(statements, expireMillis);
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder result = new StringBuilder();
+ result.append("Result: ");
+ result.append(mStatements.toString());
+ result.append(", mExpireMillis=");
+ result.append(mExpireMillis);
+ return result.toString();
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ Result result = (Result) o;
+
+ if (!mExpireMillis.equals(result.mExpireMillis)) {
+ return false;
+ }
+ if (!mStatements.equals(result.mStatements)) {
+ return false;
+ }
+
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = mStatements.hashCode();
+ result = 31 * result + mExpireMillis.hashCode();
+ return result;
+ }
+ }
+
+ public DirectStatementRetriever(URLFetcher urlFetcher,
+ AndroidPackageInfoFetcher androidFetcher) {
+ this.mUrlFetcher = urlFetcher;
+ this.mAndroidFetcher = androidFetcher;
+ }
+
+ @Override
+ public Result retrieveStatements(AbstractAsset source) throws AssociationServiceException {
+ if (source instanceof AndroidAppAsset) {
+ return retrieveFromAndroid((AndroidAppAsset) source);
+ } else if (source instanceof WebAsset) {
+ return retrieveFromWeb((WebAsset) source);
+ } else {
+ throw new AssociationServiceException("Namespace is not supported.");
+ }
+ }
+
+ private String computeAssociationJsonUrl(WebAsset asset) {
+ try {
+ return new URL(asset.getScheme(), asset.getDomain(), asset.getPort(),
+ WELL_KNOWN_STATEMENT_PATH)
+ .toExternalForm();
+ } catch (MalformedURLException e) {
+ throw new AssertionError("Invalid domain name in database.");
+ }
+ }
+
+ private Result retrieveStatementFromUrl(String url, int maxIncludeLevel, AbstractAsset source)
+ throws AssociationServiceException {
+ List<Statement> statements = new ArrayList<Statement>();
+ if (maxIncludeLevel < 0) {
+ return Result.create(statements, DO_NOT_CACHE_RESULT);
+ }
+
+ WebContent webContent;
+ try {
+ webContent = mUrlFetcher.getWebContentFromUrl(new URL(url),
+ HTTP_CONTENT_SIZE_LIMIT_IN_BYTES, HTTP_CONNECTION_TIMEOUT_MILLIS);
+ } catch (IOException e) {
+ return Result.create(statements, DO_NOT_CACHE_RESULT);
+ }
+
+ try {
+ ParsedStatement result = StatementParser
+ .parseStatementList(webContent.getContent(), source);
+ statements.addAll(result.getStatements());
+ for (String delegate : result.getDelegates()) {
+ statements.addAll(
+ retrieveStatementFromUrl(delegate, maxIncludeLevel - 1, source)
+ .getStatements());
+ }
+ return Result.create(statements, webContent.getExpireTimeMillis());
+ } catch (JSONException e) {
+ return Result.create(statements, DO_NOT_CACHE_RESULT);
+ }
+ }
+
+ private Result retrieveFromWeb(WebAsset asset)
+ throws AssociationServiceException {
+ return retrieveStatementFromUrl(computeAssociationJsonUrl(asset), MAX_INCLUDE_LEVEL, asset);
+ }
+
+ private Result retrieveFromAndroid(AndroidAppAsset asset) throws AssociationServiceException {
+ try {
+ List<String> delegates = new ArrayList<String>();
+ List<Statement> statements = new ArrayList<Statement>();
+
+ List<String> certFps = mAndroidFetcher.getCertFingerprints(asset.getPackageName());
+ if (!Utils.hasCommonString(certFps, asset.getCertFingerprints())) {
+ throw new AssociationServiceException(
+ "Specified certs don't match the installed app.");
+ }
+
+ AndroidAppAsset actualSource = AndroidAppAsset.create(asset.getPackageName(), certFps);
+ for (String statementJson : mAndroidFetcher.getStatements(asset.getPackageName())) {
+ ParsedStatement result =
+ StatementParser.parseStatement(statementJson, actualSource);
+ statements.addAll(result.getStatements());
+ delegates.addAll(result.getDelegates());
+ }
+
+ for (String delegate : delegates) {
+ statements.addAll(retrieveStatementFromUrl(delegate, MAX_INCLUDE_LEVEL,
+ actualSource).getStatements());
+ }
+
+ return Result.create(statements, DO_NOT_CACHE_RESULT);
+ } catch (JSONException | NameNotFoundException e) {
+ Log.w(DirectStatementRetriever.class.getSimpleName(), e);
+ return Result.create(Collections.<Statement>emptyList(), DO_NOT_CACHE_RESULT);
+ }
+ }
+}