1 /*
2  * Copyright (C) 2023 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.adservices.service.measurement.registration;
18 
19 import android.net.Uri;
20 
21 import com.android.adservices.service.Flags;
22 
23 import java.util.ArrayList;
24 import java.util.List;
25 import java.util.Map;
26 
27 /** Class for handling redirects */
28 public class AsyncRedirects {
29     public static final String HEADER_ATTRIBUTION_REPORTING_REDIRECT_CONFIG =
30             "Attribution-Reporting-Redirect-Config";
31     public static final String REDIRECT_302_TO_WELL_KNOWN = "redirect-302-to-well-known";
32     public static final String WELL_KNOWN_PATH_SEGMENT =
33             ".well-known/attribution-reporting/register-redirect";
34     public static final String WELL_KNOWN_QUERY_PARAM = "302_url";
35     public static final String REDIRECT_LIST_HEADER_KEY = "Attribution-Reporting-Redirect";
36     public static final String REDIRECT_LOCATION_HEADER_KEY = "Location";
37     private final List<AsyncRedirect> mLocationRedirects;
38     private final List<AsyncRedirect> mListRedirects;
39 
AsyncRedirects()40     public AsyncRedirects() {
41         mLocationRedirects = new ArrayList<>();
42         mListRedirects = new ArrayList<>();
43     }
44 
45     /** Return flattened list of {@link AsyncRedirect} */
getRedirects()46     public List<AsyncRedirect> getRedirects() {
47         List<AsyncRedirect> allRedirects = new ArrayList<>(mListRedirects);
48         allRedirects.addAll(mLocationRedirects);
49 
50         return allRedirects;
51     }
52 
53     /** Get list of {@link AsyncRedirect} by redirect type */
getRedirectsByType(AsyncRegistration.RedirectType redirectType)54     public List<AsyncRedirect> getRedirectsByType(AsyncRegistration.RedirectType redirectType) {
55         if (redirectType == AsyncRegistration.RedirectType.LOCATION) {
56             return new ArrayList<>(mLocationRedirects);
57         } else {
58             return new ArrayList<>(mListRedirects);
59         }
60     }
61 
62     /** Process redirects based on the given headers */
configure( Map<String, List<String>> headers, Flags flags, AsyncRegistration parentRegistration)63     public void configure(
64             Map<String, List<String>> headers, Flags flags, AsyncRegistration parentRegistration) {
65         if (!parentRegistration.shouldProcessRedirects()) {
66             return;
67         }
68 
69         Map<AsyncRegistration.RedirectType, List<Uri>> urisByType =
70                 FetcherUtil.parseRedirects(headers);
71 
72         for (Uri locationRedirectUri : urisByType.get(AsyncRegistration.RedirectType.LOCATION)) {
73             if (shouldRedirect302ToWellKnown(headers, flags, parentRegistration)) {
74                 mLocationRedirects.add(
75                         new AsyncRedirect(
76                                 getLocationRedirectToWellKnownUri(locationRedirectUri),
77                                 AsyncRedirect.RedirectBehavior.LOCATION_TO_WELL_KNOWN));
78             } else {
79                 mLocationRedirects.add(
80                         new AsyncRedirect(
81                                 locationRedirectUri, AsyncRedirect.RedirectBehavior.AS_IS));
82             }
83         }
84 
85         for (Uri listRedirectUri : urisByType.get(AsyncRegistration.RedirectType.LIST)) {
86             mListRedirects.add(
87                     new AsyncRedirect(listRedirectUri, AsyncRedirect.RedirectBehavior.AS_IS));
88         }
89     }
90 
shouldRedirect302ToWellKnown( Map<String, List<String>> headers, Flags flags, AsyncRegistration parentRegistration)91     private static boolean shouldRedirect302ToWellKnown(
92             Map<String, List<String>> headers, Flags flags, AsyncRegistration parentRegistration) {
93         boolean isParentRegistrationRedirectsToWellKnown =
94                 AsyncRedirect.RedirectBehavior.LOCATION_TO_WELL_KNOWN.equals(
95                         parentRegistration.getRedirectBehavior());
96 
97         return flags.getMeasurementEnableRedirectToWellKnownPath()
98                 && (isParentRegistrationRedirectsToWellKnown
99                         || isRedirect302ToWellKnownPath(headers));
100     }
101 
102     /**
103      * Return true if the given headers indicate redirects should prepend well known prefix to the
104      * path.
105      */
isRedirect302ToWellKnownPath(Map<String, List<String>> headers)106     private static boolean isRedirect302ToWellKnownPath(Map<String, List<String>> headers) {
107         if (!headers.containsKey(HEADER_ATTRIBUTION_REPORTING_REDIRECT_CONFIG)) {
108             return false;
109         }
110         List<String> config = headers.get(HEADER_ATTRIBUTION_REPORTING_REDIRECT_CONFIG);
111         if (config == null || config.size() != 1) {
112             return false;
113         }
114 
115         return config.get(0).equalsIgnoreCase(REDIRECT_302_TO_WELL_KNOWN);
116     }
117 
getLocationRedirectToWellKnownUri(Uri redirectUri)118     private Uri getLocationRedirectToWellKnownUri(Uri redirectUri) {
119         return redirectUri
120                 .buildUpon()
121                 .encodedPath(WELL_KNOWN_PATH_SEGMENT)
122                 .clearQuery()
123                 .appendQueryParameter(WELL_KNOWN_QUERY_PARAM, redirectUri.toString())
124                 .build();
125     }
126 }
127