1/*
2 * Copyright (C) 2015 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
17package android.content.pm;
18
19import android.annotation.NonNull;
20import android.annotation.SystemApi;
21import android.content.IntentFilter;
22import android.net.Uri;
23import android.os.Parcel;
24import android.os.Parcelable;
25
26import java.security.MessageDigest;
27import java.security.NoSuchAlgorithmException;
28import java.util.ArrayList;
29import java.util.List;
30import java.util.Locale;
31
32/**
33 * Information about an ephemeral application.
34 * @hide
35 */
36@SystemApi
37public final class EphemeralResolveInfo implements Parcelable {
38    /** Algorithm that will be used to generate the domain digest */
39    public static final String SHA_ALGORITHM = "SHA-256";
40
41    private final EphemeralDigest mDigest;
42    private final String mPackageName;
43    /** The filters used to match domain */
44    private final List<IntentFilter> mFilters = new ArrayList<IntentFilter>();
45
46    public EphemeralResolveInfo(@NonNull Uri uri, @NonNull String packageName,
47            @NonNull List<IntentFilter> filters) {
48        // validate arguments
49        if (uri == null
50                || packageName == null
51                || filters == null
52                || filters.size() == 0) {
53            throw new IllegalArgumentException();
54        }
55
56        mDigest = new EphemeralDigest(uri, 0xFFFFFFFF, -1);
57        mFilters.addAll(filters);
58        mPackageName = packageName;
59    }
60
61    EphemeralResolveInfo(Parcel in) {
62        mDigest = in.readParcelable(null /*loader*/);
63        mPackageName = in.readString();
64        in.readList(mFilters, null /*loader*/);
65    }
66
67    public byte[] getDigestBytes() {
68        return mDigest.getDigestBytes()[0];
69    }
70
71    public int getDigestPrefix() {
72        return mDigest.getDigestPrefix()[0];
73    }
74
75    public String getPackageName() {
76        return mPackageName;
77    }
78
79    public List<IntentFilter> getFilters() {
80        return mFilters;
81    }
82
83    @Override
84    public int describeContents() {
85        return 0;
86    }
87
88    @Override
89    public void writeToParcel(Parcel out, int flags) {
90        out.writeParcelable(mDigest, flags);
91        out.writeString(mPackageName);
92        out.writeList(mFilters);
93    }
94
95    public static final Parcelable.Creator<EphemeralResolveInfo> CREATOR
96            = new Parcelable.Creator<EphemeralResolveInfo>() {
97        public EphemeralResolveInfo createFromParcel(Parcel in) {
98            return new EphemeralResolveInfo(in);
99        }
100
101        public EphemeralResolveInfo[] newArray(int size) {
102            return new EphemeralResolveInfo[size];
103        }
104    };
105
106    /** @hide */
107    public static final class EphemeralResolveIntentInfo extends IntentFilter {
108        private final EphemeralResolveInfo mResolveInfo;
109
110        public EphemeralResolveIntentInfo(@NonNull IntentFilter orig,
111                @NonNull EphemeralResolveInfo resolveInfo) {
112            super(orig);
113            this.mResolveInfo = resolveInfo;
114        }
115
116        public EphemeralResolveInfo getEphemeralResolveInfo() {
117            return mResolveInfo;
118        }
119    }
120
121    /**
122     * Helper class to generate and store each of the digests and prefixes
123     * sent to the Ephemeral Resolver.
124     * <p>
125     * Since intent filters may want to handle multiple hosts within a
126     * domain [eg “*.google.com”], the resolver is presented with multiple
127     * hash prefixes. For example, "a.b.c.d.e" generates digests for
128     * "d.e", "c.d.e", "b.c.d.e" and "a.b.c.d.e".
129     *
130     * @hide
131     */
132    public static final class EphemeralDigest implements Parcelable {
133        /** Full digest of the domain hashes */
134        private final byte[][] mDigestBytes;
135        /** The first 4 bytes of the domain hashes */
136        private final int[] mDigestPrefix;
137
138        public EphemeralDigest(@NonNull Uri uri, int digestMask, int maxDigests) {
139            if (uri == null) {
140                throw new IllegalArgumentException();
141            }
142            mDigestBytes = generateDigest(uri, maxDigests);
143            mDigestPrefix = new int[mDigestBytes.length];
144            for (int i = 0; i < mDigestBytes.length; i++) {
145                mDigestPrefix[i] =
146                        ((mDigestBytes[i][0] & 0xFF) << 24
147                                | (mDigestBytes[i][1] & 0xFF) << 16
148                                | (mDigestBytes[i][2] & 0xFF) << 8
149                                | (mDigestBytes[i][3] & 0xFF) << 0)
150                        & digestMask;
151            }
152        }
153
154        private static byte[][] generateDigest(Uri uri, int maxDigests) {
155            ArrayList<byte[]> digests = new ArrayList<>();
156            try {
157                final String host = uri.getHost().toLowerCase(Locale.ENGLISH);
158                final MessageDigest digest = MessageDigest.getInstance(SHA_ALGORITHM);
159                if (maxDigests <= 0) {
160                    final byte[] hostBytes = host.getBytes();
161                    digests.add(digest.digest(hostBytes));
162                } else {
163                    int prevDot = host.lastIndexOf('.');
164                    prevDot = host.lastIndexOf('.', prevDot - 1);
165                    // shortcut for short URLs
166                    if (prevDot < 0) {
167                        digests.add(digest.digest(host.getBytes()));
168                    } else {
169                        byte[] hostBytes = host.substring(prevDot + 1, host.length()).getBytes();
170                        digests.add(digest.digest(hostBytes));
171                        int digestCount = 1;
172                        while (prevDot >= 0 && digestCount < maxDigests) {
173                            prevDot = host.lastIndexOf('.', prevDot - 1);
174                            hostBytes = host.substring(prevDot + 1, host.length()).getBytes();
175                            digests.add(digest.digest(hostBytes));
176                            digestCount++;
177                        }
178                    }
179                }
180            } catch (NoSuchAlgorithmException e) {
181                throw new IllegalStateException("could not find digest algorithm");
182            }
183            return digests.toArray(new byte[digests.size()][]);
184        }
185
186        EphemeralDigest(Parcel in) {
187            final int digestCount = in.readInt();
188            if (digestCount == -1) {
189                mDigestBytes = null;
190            } else {
191                mDigestBytes = new byte[digestCount][];
192                for (int i = 0; i < digestCount; i++) {
193                    mDigestBytes[i] = in.createByteArray();
194                }
195            }
196            mDigestPrefix = in.createIntArray();
197        }
198
199        public byte[][] getDigestBytes() {
200            return mDigestBytes;
201        }
202
203        public int[] getDigestPrefix() {
204            return mDigestPrefix;
205        }
206
207        @Override
208        public int describeContents() {
209            return 0;
210        }
211
212        @Override
213        public void writeToParcel(Parcel out, int flags) {
214            if (mDigestBytes == null) {
215                out.writeInt(-1);
216            } else {
217                out.writeInt(mDigestBytes.length);
218                for (int i = 0; i < mDigestBytes.length; i++) {
219                    out.writeByteArray(mDigestBytes[i]);
220                }
221            }
222            out.writeIntArray(mDigestPrefix);
223        }
224
225        @SuppressWarnings("hiding")
226        public static final Parcelable.Creator<EphemeralDigest> CREATOR =
227                new Parcelable.Creator<EphemeralDigest>() {
228            public EphemeralDigest createFromParcel(Parcel in) {
229                return new EphemeralDigest(in);
230            }
231
232            public EphemeralDigest[] newArray(int size) {
233                return new EphemeralDigest[size];
234            }
235        };
236    }
237}
238