1 /*
2  * Copyright (C) 2016 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 package com.google.android.exoplayer2.upstream;
17 
18 import android.support.annotation.IntDef;
19 import android.text.TextUtils;
20 import com.google.android.exoplayer2.util.Predicate;
21 import com.google.android.exoplayer2.util.Util;
22 import java.io.IOException;
23 import java.lang.annotation.Retention;
24 import java.lang.annotation.RetentionPolicy;
25 import java.util.Collections;
26 import java.util.HashMap;
27 import java.util.List;
28 import java.util.Map;
29 
30 /**
31  * An HTTP {@link DataSource}.
32  */
33 public interface HttpDataSource extends DataSource {
34 
35   /**
36    * A factory for {@link HttpDataSource} instances.
37    */
38   interface Factory extends DataSource.Factory {
39 
40     @Override
createDataSource()41     HttpDataSource createDataSource();
42 
43     /**
44      * Gets the default request properties used by all {@link HttpDataSource}s created by the
45      * factory. Changes to the properties will be reflected in any future requests made by
46      * {@link HttpDataSource}s created by the factory.
47      *
48      * @return The default request properties of the factory.
49      */
getDefaultRequestProperties()50     RequestProperties getDefaultRequestProperties();
51 
52     /**
53      * Sets a default request header for {@link HttpDataSource} instances created by the factory.
54      *
55      * @deprecated Use {@link #getDefaultRequestProperties} instead.
56      * @param name The name of the header field.
57      * @param value The value of the field.
58      */
59     @Deprecated
setDefaultRequestProperty(String name, String value)60     void setDefaultRequestProperty(String name, String value);
61 
62     /**
63      * Clears a default request header for {@link HttpDataSource} instances created by the factory.
64      *
65      * @deprecated Use {@link #getDefaultRequestProperties} instead.
66      * @param name The name of the header field.
67      */
68     @Deprecated
clearDefaultRequestProperty(String name)69     void clearDefaultRequestProperty(String name);
70 
71     /**
72      * Clears all default request headers for all {@link HttpDataSource} instances created by the
73      * factory.
74      *
75      * @deprecated Use {@link #getDefaultRequestProperties} instead.
76      */
77     @Deprecated
clearAllDefaultRequestProperties()78     void clearAllDefaultRequestProperties();
79 
80   }
81 
82   /**
83    * Stores HTTP request properties (aka HTTP headers) and provides methods to modify the headers
84    * in a thread safe way to avoid the potential of creating snapshots of an inconsistent or
85    * unintended state.
86    */
87   final class RequestProperties {
88 
89     private final Map<String, String> requestProperties;
90     private Map<String, String> requestPropertiesSnapshot;
91 
RequestProperties()92     public RequestProperties() {
93       requestProperties = new HashMap<>();
94     }
95 
96     /**
97      * Sets the specified property {@code value} for the specified {@code name}. If a property for
98      * this name previously existed, the old value is replaced by the specified value.
99      *
100      * @param name The name of the request property.
101      * @param value The value of the request property.
102      */
set(String name, String value)103     public synchronized void set(String name, String value) {
104       requestPropertiesSnapshot = null;
105       requestProperties.put(name, value);
106     }
107 
108     /**
109      * Sets the keys and values contained in the map. If a property previously existed, the old
110      * value is replaced by the specified value. If a property previously existed and is not in the
111      * map, the property is left unchanged.
112      *
113      * @param properties The request properties.
114      */
set(Map<String, String> properties)115     public synchronized void set(Map<String, String> properties) {
116       requestPropertiesSnapshot = null;
117       requestProperties.putAll(properties);
118     }
119 
120     /**
121      * Removes all properties previously existing and sets the keys and values of the map.
122      *
123      * @param properties The request properties.
124      */
clearAndSet(Map<String, String> properties)125     public synchronized void clearAndSet(Map<String, String> properties) {
126       requestPropertiesSnapshot = null;
127       requestProperties.clear();
128       requestProperties.putAll(properties);
129     }
130 
131     /**
132      * Removes a request property by name.
133      *
134      * @param name The name of the request property to remove.
135      */
remove(String name)136     public synchronized void remove(String name) {
137       requestPropertiesSnapshot = null;
138       requestProperties.remove(name);
139     }
140 
141     /**
142      * Clears all request properties.
143      */
clear()144     public synchronized void clear() {
145       requestPropertiesSnapshot = null;
146       requestProperties.clear();
147     }
148 
149     /**
150      * Gets a snapshot of the request properties.
151      *
152      * @return A snapshot of the request properties.
153      */
getSnapshot()154     public synchronized Map<String, String> getSnapshot() {
155       if (requestPropertiesSnapshot == null) {
156         requestPropertiesSnapshot = Collections.unmodifiableMap(new HashMap<>(requestProperties));
157       }
158       return requestPropertiesSnapshot;
159     }
160 
161   }
162 
163   /**
164    * Base implementation of {@link Factory} that sets default request properties.
165    */
166   abstract class BaseFactory implements Factory {
167 
168     private final RequestProperties defaultRequestProperties;
169 
BaseFactory()170     public BaseFactory() {
171       defaultRequestProperties = new RequestProperties();
172     }
173 
174     @Override
createDataSource()175     public final HttpDataSource createDataSource() {
176       return createDataSourceInternal(defaultRequestProperties);
177     }
178 
179     @Override
getDefaultRequestProperties()180     public final RequestProperties getDefaultRequestProperties() {
181       return defaultRequestProperties;
182     }
183 
184     @Deprecated
185     @Override
setDefaultRequestProperty(String name, String value)186     public final void setDefaultRequestProperty(String name, String value) {
187       defaultRequestProperties.set(name, value);
188     }
189 
190     @Deprecated
191     @Override
clearDefaultRequestProperty(String name)192     public final void clearDefaultRequestProperty(String name) {
193       defaultRequestProperties.remove(name);
194     }
195 
196     @Deprecated
197     @Override
clearAllDefaultRequestProperties()198     public final void clearAllDefaultRequestProperties() {
199       defaultRequestProperties.clear();
200     }
201 
202     /**
203      * Called by {@link #createDataSource()} to create a {@link HttpDataSource} instance.
204      *
205      * @param defaultRequestProperties The default {@code RequestProperties} to be used by the
206      *     {@link HttpDataSource} instance.
207      * @return A {@link HttpDataSource} instance.
208      */
createDataSourceInternal(RequestProperties defaultRequestProperties)209     protected abstract HttpDataSource createDataSourceInternal(RequestProperties
210         defaultRequestProperties);
211 
212   }
213 
214   /**
215    * A {@link Predicate} that rejects content types often used for pay-walls.
216    */
217   Predicate<String> REJECT_PAYWALL_TYPES = new Predicate<String>() {
218 
219     @Override
220     public boolean evaluate(String contentType) {
221       contentType = Util.toLowerInvariant(contentType);
222       return !TextUtils.isEmpty(contentType)
223           && (!contentType.contains("text") || contentType.contains("text/vtt"))
224           && !contentType.contains("html") && !contentType.contains("xml");
225     }
226 
227   };
228 
229   /**
230    * Thrown when an error is encountered when trying to read from a {@link HttpDataSource}.
231    */
232   class HttpDataSourceException extends IOException {
233 
234     @Retention(RetentionPolicy.SOURCE)
235     @IntDef({TYPE_OPEN, TYPE_READ, TYPE_CLOSE})
236     public @interface Type {}
237     public static final int TYPE_OPEN = 1;
238     public static final int TYPE_READ = 2;
239     public static final int TYPE_CLOSE = 3;
240 
241     @Type public final int type;
242 
243     /**
244      * The {@link DataSpec} associated with the current connection.
245      */
246     public final DataSpec dataSpec;
247 
HttpDataSourceException(DataSpec dataSpec, @Type int type)248     public HttpDataSourceException(DataSpec dataSpec, @Type int type) {
249       super();
250       this.dataSpec = dataSpec;
251       this.type = type;
252     }
253 
HttpDataSourceException(String message, DataSpec dataSpec, @Type int type)254     public HttpDataSourceException(String message, DataSpec dataSpec, @Type int type) {
255       super(message);
256       this.dataSpec = dataSpec;
257       this.type = type;
258     }
259 
HttpDataSourceException(IOException cause, DataSpec dataSpec, @Type int type)260     public HttpDataSourceException(IOException cause, DataSpec dataSpec, @Type int type) {
261       super(cause);
262       this.dataSpec = dataSpec;
263       this.type = type;
264     }
265 
HttpDataSourceException(String message, IOException cause, DataSpec dataSpec, @Type int type)266     public HttpDataSourceException(String message, IOException cause, DataSpec dataSpec,
267         @Type int type) {
268       super(message, cause);
269       this.dataSpec = dataSpec;
270       this.type = type;
271     }
272 
273   }
274 
275   /**
276    * Thrown when the content type is invalid.
277    */
278   final class InvalidContentTypeException extends HttpDataSourceException {
279 
280     public final String contentType;
281 
InvalidContentTypeException(String contentType, DataSpec dataSpec)282     public InvalidContentTypeException(String contentType, DataSpec dataSpec) {
283       super("Invalid content type: " + contentType, dataSpec, TYPE_OPEN);
284       this.contentType = contentType;
285     }
286 
287   }
288 
289   /**
290    * Thrown when an attempt to open a connection results in a response code not in the 2xx range.
291    */
292   final class InvalidResponseCodeException extends HttpDataSourceException {
293 
294     /**
295      * The response code that was outside of the 2xx range.
296      */
297     public final int responseCode;
298 
299     /**
300      * An unmodifiable map of the response header fields and values.
301      */
302     public final Map<String, List<String>> headerFields;
303 
InvalidResponseCodeException(int responseCode, Map<String, List<String>> headerFields, DataSpec dataSpec)304     public InvalidResponseCodeException(int responseCode, Map<String, List<String>> headerFields,
305         DataSpec dataSpec) {
306       super("Response code: " + responseCode, dataSpec, TYPE_OPEN);
307       this.responseCode = responseCode;
308       this.headerFields = headerFields;
309     }
310 
311   }
312 
313   @Override
open(DataSpec dataSpec)314   long open(DataSpec dataSpec) throws HttpDataSourceException;
315 
316   @Override
close()317   void close() throws HttpDataSourceException;
318 
319   @Override
read(byte[] buffer, int offset, int readLength)320   int read(byte[] buffer, int offset, int readLength) throws HttpDataSourceException;
321 
322   /**
323    * Sets the value of a request header. The value will be used for subsequent connections
324    * established by the source.
325    *
326    * @param name The name of the header field.
327    * @param value The value of the field.
328    */
setRequestProperty(String name, String value)329   void setRequestProperty(String name, String value);
330 
331   /**
332    * Clears the value of a request header. The change will apply to subsequent connections
333    * established by the source.
334    *
335    * @param name The name of the header field.
336    */
clearRequestProperty(String name)337   void clearRequestProperty(String name);
338 
339   /**
340    * Clears all request headers that were set by {@link #setRequestProperty(String, String)}.
341    */
clearAllRequestProperties()342   void clearAllRequestProperties();
343 
344   /**
345    * Returns the headers provided in the response, or {@code null} if response headers are
346    * unavailable.
347    */
getResponseHeaders()348   Map<String, List<String>> getResponseHeaders();
349 
350 }
351