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