1 /*
2  * Copyright (C) 2024 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 android.content;
18 
19 import android.annotation.FlaggedApi;
20 import android.annotation.IntDef;
21 import android.annotation.NonNull;
22 import android.annotation.Nullable;
23 import android.content.pm.Flags;
24 import android.net.Uri;
25 import android.os.Parcel;
26 import android.os.PatternMatcher;
27 import android.util.proto.ProtoOutputStream;
28 
29 import org.xmlpull.v1.XmlPullParser;
30 import org.xmlpull.v1.XmlPullParserException;
31 import org.xmlpull.v1.XmlSerializer;
32 
33 import java.io.IOException;
34 import java.lang.annotation.Retention;
35 import java.lang.annotation.RetentionPolicy;
36 
37 /**
38  * A filter for matching Intent URI Data as part of a
39  * {@link UriRelativeFilterGroup}. A single filter can only be
40  * matched against either a URI path, query or fragment
41  */
42 @FlaggedApi(Flags.FLAG_RELATIVE_REFERENCE_INTENT_FILTERS)
43 public final class UriRelativeFilter {
44     private static final String FILTER_STR = "filter";
45     private static final String PART_STR = "part";
46     private static final String PATTERN_STR = "pattern";
47     static final String URI_RELATIVE_FILTER_STR = "uriRelativeFilter";
48 
49     /**
50      * Value to indicate that the filter is to be applied to a URI path.
51      */
52     public static final int PATH = 0;
53     /**
54      * Value to indicate that the filter is to be applied to a URI query.
55      */
56     public static final int QUERY = 1;
57     /**
58      * Value to indicate that the filter is to be applied to a URI fragment.
59      */
60     public static final int FRAGMENT = 2;
61 
62     /** @hide */
63     @IntDef(value = {
64             PATH,
65             QUERY,
66             FRAGMENT
67     })
68     @Retention(RetentionPolicy.SOURCE)
69     public @interface UriPart {}
70 
71     private final @UriPart int mUriPart;
72     private final @PatternMatcher.PatternType int mPatternType;
73     private final String mFilter;
74 
75     /**
76      * Creates a new UriRelativeFilter.
77      *
78      * @param uriPart The URI part this filter operates on. Can be either a
79      *                {@link UriRelativeFilter#PATH}, {@link UriRelativeFilter#QUERY},
80      *                or {@link UriRelativeFilter#FRAGMENT}.
81      * @param patternType The pattern type of the filter. Can be either a
82      *                    {@link PatternMatcher#PATTERN_LITERAL},
83      *                    {@link PatternMatcher#PATTERN_PREFIX},
84 *                         {@link PatternMatcher#PATTERN_SUFFIX},
85      *                    {@link PatternMatcher#PATTERN_SIMPLE_GLOB},
86      *                    or {@link PatternMatcher#PATTERN_ADVANCED_GLOB}.
87      * @param filter A literal or pattern string depedning on patterType
88      *               used to match a uriPart .
89      */
UriRelativeFilter( @riPart int uriPart, @PatternMatcher.PatternType int patternType, @NonNull String filter)90     public UriRelativeFilter(
91             @UriPart int uriPart,
92             @PatternMatcher.PatternType int patternType,
93             @NonNull String filter) {
94         mUriPart = uriPart;
95         com.android.internal.util.AnnotationValidations.validate(
96                 UriPart.class, null, mUriPart);
97         mPatternType = patternType;
98         com.android.internal.util.AnnotationValidations.validate(
99                 PatternMatcher.PatternType.class, null, mPatternType);
100         mFilter = filter;
101         com.android.internal.util.AnnotationValidations.validate(
102                 NonNull.class, null, mFilter);
103     }
104 
105     /**
106      * The URI part this filter operates on.
107      */
getUriPart()108     public @UriPart int getUriPart() {
109         return mUriPart;
110     }
111 
112     /**
113      * The pattern type of the filter.
114      */
getPatternType()115     public @PatternMatcher.PatternType int getPatternType() {
116         return mPatternType;
117     }
118 
119     /**
120      * The string used to filter the URI.
121      */
getFilter()122     public @NonNull String getFilter() {
123         return mFilter;
124     }
125 
126     /**
127      * Match this URI filter against an Intent's data. QUERY filters can
128      * match against any key value pair in the query string. PATH and
129      * FRAGMENT filters must match the entire string.
130      *
131      * @param data The full data string to match against, as supplied in
132      *             Intent.data.
133      *
134      * @return true if there is a match.
135      */
matchData(@onNull Uri data)136     public boolean matchData(@NonNull Uri data) {
137         PatternMatcher pe = new PatternMatcher(mFilter, mPatternType);
138         switch (getUriPart()) {
139             case PATH:
140                 return pe.match(data.getPath());
141             case QUERY:
142                 return matchQuery(pe, data.getQuery());
143             case FRAGMENT:
144                 return pe.match(data.getFragment());
145             default:
146                 return false;
147         }
148     }
149 
matchQuery(PatternMatcher pe, String query)150     private boolean matchQuery(PatternMatcher pe, String query) {
151         if (query != null) {
152             String[] params = query.split("&");
153             if (params.length == 1) {
154                 params = query.split(";");
155             }
156             for (int i = 0; i < params.length; i++) {
157                 if (pe.match(params[i])) return true;
158             }
159         }
160         return false;
161     }
162 
163     /** @hide */
dumpDebug(ProtoOutputStream proto, long fieldId)164     public void dumpDebug(ProtoOutputStream proto, long fieldId) {
165         long token = proto.start(fieldId);
166         proto.write(UriRelativeFilterProto.URI_PART, mUriPart);
167         proto.write(UriRelativeFilterProto.PATTERN_TYPE, mPatternType);
168         proto.write(UriRelativeFilterProto.FILTER, mFilter);
169         proto.end(token);
170     }
171 
172     /** @hide */
writeToXml(XmlSerializer serializer)173     public void writeToXml(XmlSerializer serializer) throws IOException {
174         serializer.startTag(null, URI_RELATIVE_FILTER_STR);
175         serializer.attribute(null, PATTERN_STR, Integer.toString(mPatternType));
176         serializer.attribute(null, PART_STR, Integer.toString(mUriPart));
177         serializer.attribute(null, FILTER_STR, mFilter);
178         serializer.endTag(null, URI_RELATIVE_FILTER_STR);
179     }
180 
uriPartToString()181     private String uriPartToString() {
182         switch (mUriPart) {
183             case PATH:
184                 return "PATH";
185             case QUERY:
186                 return "QUERY";
187             case FRAGMENT:
188                 return "FRAGMENT";
189             default:
190                 return "UNKNOWN";
191         }
192     }
193 
patternTypeToString()194     private String patternTypeToString() {
195         switch (mPatternType) {
196             case PatternMatcher.PATTERN_LITERAL:
197                 return "LITERAL";
198             case PatternMatcher.PATTERN_PREFIX:
199                 return "PREFIX";
200             case PatternMatcher.PATTERN_SIMPLE_GLOB:
201                 return "GLOB";
202             case PatternMatcher.PATTERN_ADVANCED_GLOB:
203                 return "ADVANCED_GLOB";
204             case PatternMatcher.PATTERN_SUFFIX:
205                 return "SUFFIX";
206             default:
207                 return "UNKNOWN";
208         }
209     }
210 
211     @Override
toString()212     public String toString() {
213         return "UriRelativeFilter { "
214                 + "uriPart = " + uriPartToString() + ", "
215                 + "patternType = " + patternTypeToString() + ", "
216                 + "filter = " + mFilter
217                 + " }";
218     }
219 
220     /** @hide */
toParcel()221     public UriRelativeFilterParcel toParcel() {
222         UriRelativeFilterParcel parcel = new UriRelativeFilterParcel();
223         parcel.uriPart = mUriPart;
224         parcel.patternType = mPatternType;
225         parcel.filter = mFilter;
226         return parcel;
227     }
228 
229     @Override
equals(@ullable Object o)230     public boolean equals(@Nullable Object o) {
231         if (this == o) return true;
232         if (o == null || getClass() != o.getClass()) return false;
233         @SuppressWarnings("unchecked")
234         UriRelativeFilter that = (UriRelativeFilter) o;
235         return mUriPart == that.mUriPart
236                 && mPatternType == that.mPatternType
237                 && java.util.Objects.equals(mFilter, that.mFilter);
238     }
239 
240     @Override
hashCode()241     public int hashCode() {
242         int _hash = 1;
243         _hash = 31 * _hash + mUriPart;
244         _hash = 31 * _hash + mPatternType;
245         _hash = 31 * _hash + java.util.Objects.hashCode(mFilter);
246         return _hash;
247     }
248 
249     /** @hide */
writeToParcel(@onNull Parcel dest, int flags)250     public void writeToParcel(@NonNull Parcel dest, int flags) {
251         dest.writeInt(mUriPart);
252         dest.writeInt(mPatternType);
253         dest.writeString(mFilter);
254     }
255 
256     /** @hide */
UriRelativeFilter(@onNull android.os.Parcel in)257     UriRelativeFilter(@NonNull android.os.Parcel in) {
258         mUriPart = in.readInt();
259         mPatternType = in.readInt();
260         mFilter = in.readString();
261     }
262 
263     /** @hide */
UriRelativeFilter(XmlPullParser parser)264     public UriRelativeFilter(XmlPullParser parser) throws XmlPullParserException, IOException {
265         mUriPart = Integer.parseInt(parser.getAttributeValue(null, PART_STR));
266         mPatternType = Integer.parseInt(parser.getAttributeValue(null, PATTERN_STR));
267         mFilter = parser.getAttributeValue(null, FILTER_STR);
268     }
269 
270     /** @hide */
UriRelativeFilter(UriRelativeFilterParcel parcel)271     public UriRelativeFilter(UriRelativeFilterParcel parcel) {
272         mUriPart = parcel.uriPart;
273         mPatternType = parcel.patternType;
274         mFilter = parcel.filter;
275     }
276 }
277