1 /*
2  * Copyright 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 
17 //#define LOG_NDEBUG 0
18 #define LOG_TAG "HTTPDownloader"
19 #include <utils/Log.h>
20 
21 #include "HTTPDownloader.h"
22 #include "M3UParser.h"
23 
24 #include <media/IMediaHTTPConnection.h>
25 #include <media/IMediaHTTPService.h>
26 #include <media/stagefright/foundation/ABuffer.h>
27 #include <media/stagefright/foundation/ADebug.h>
28 #include <media/stagefright/MediaHTTP.h>
29 #include <media/stagefright/DataSource.h>
30 #include <media/stagefright/FileSource.h>
31 #include <openssl/aes.h>
32 #include <openssl/md5.h>
33 #include <utils/Mutex.h>
34 
35 namespace android {
36 
HTTPDownloader(const sp<IMediaHTTPService> & httpService,const KeyedVector<String8,String8> & headers)37 HTTPDownloader::HTTPDownloader(
38         const sp<IMediaHTTPService> &httpService,
39         const KeyedVector<String8, String8> &headers) :
40     mHTTPDataSource(new MediaHTTP(httpService->makeHTTPConnection())),
41     mExtraHeaders(headers),
42     mDisconnecting(false) {
43 }
44 
reconnect()45 void HTTPDownloader::reconnect() {
46     AutoMutex _l(mLock);
47     mDisconnecting = false;
48 }
49 
disconnect()50 void HTTPDownloader::disconnect() {
51     {
52         AutoMutex _l(mLock);
53         mDisconnecting = true;
54     }
55     mHTTPDataSource->disconnect();
56 }
57 
isDisconnecting()58 bool HTTPDownloader::isDisconnecting() {
59     AutoMutex _l(mLock);
60     return mDisconnecting;
61 }
62 
63 /*
64  * Illustration of parameters:
65  *
66  * 0      `range_offset`
67  * +------------+-------------------------------------------------------+--+--+
68  * |            |                                 | next block to fetch |  |  |
69  * |            | `source` handle => `out` buffer |                     |  |  |
70  * | `url` file |<--------- buffer size --------->|<--- `block_size` -->|  |  |
71  * |            |<----------- `range_length` / buffer capacity ----------->|  |
72  * |<------------------------------ file_size ------------------------------->|
73  *
74  * Special parameter values:
75  * - range_length == -1 means entire file
76  * - block_size == 0 means entire range
77  *
78  */
fetchBlock(const char * url,sp<ABuffer> * out,int64_t range_offset,int64_t range_length,uint32_t block_size,String8 * actualUrl,bool reconnect)79 ssize_t HTTPDownloader::fetchBlock(
80         const char *url, sp<ABuffer> *out,
81         int64_t range_offset, int64_t range_length,
82         uint32_t block_size, /* download block size */
83         String8 *actualUrl,
84         bool reconnect /* force connect HTTP when resuing source */) {
85     if (isDisconnecting()) {
86         return ERROR_NOT_CONNECTED;
87     }
88 
89     off64_t size;
90 
91     if (reconnect) {
92         if (!strncasecmp(url, "file://", 7)) {
93             mDataSource = new FileSource(url + 7);
94         } else if (strncasecmp(url, "http://", 7)
95                 && strncasecmp(url, "https://", 8)) {
96             return ERROR_UNSUPPORTED;
97         } else {
98             KeyedVector<String8, String8> headers = mExtraHeaders;
99             if (range_offset > 0 || range_length >= 0) {
100                 headers.add(
101                         String8("Range"),
102                         String8(
103                             AStringPrintf(
104                                 "bytes=%lld-%s",
105                                 range_offset,
106                                 range_length < 0
107                                     ? "" : AStringPrintf("%lld",
108                                             range_offset + range_length - 1).c_str()).c_str()));
109             }
110 
111             status_t err = mHTTPDataSource->connect(url, &headers);
112 
113             if (isDisconnecting()) {
114                 return ERROR_NOT_CONNECTED;
115             }
116 
117             if (err != OK) {
118                 return err;
119             }
120 
121             mDataSource = mHTTPDataSource;
122         }
123     }
124 
125     status_t getSizeErr = mDataSource->getSize(&size);
126 
127     if (isDisconnecting()) {
128         return ERROR_NOT_CONNECTED;
129     }
130 
131     if (getSizeErr != OK) {
132         size = 65536;
133     }
134 
135     sp<ABuffer> buffer = *out != NULL ? *out : new ABuffer(size);
136     if (*out == NULL) {
137         buffer->setRange(0, 0);
138     }
139 
140     ssize_t bytesRead = 0;
141     // adjust range_length if only reading partial block
142     if (block_size > 0 && (range_length == -1 || (int64_t)(buffer->size() + block_size) < range_length)) {
143         range_length = buffer->size() + block_size;
144     }
145     for (;;) {
146         // Only resize when we don't know the size.
147         size_t bufferRemaining = buffer->capacity() - buffer->size();
148         if (bufferRemaining == 0 && getSizeErr != OK) {
149             size_t bufferIncrement = buffer->size() / 2;
150             if (bufferIncrement < 32768) {
151                 bufferIncrement = 32768;
152             }
153             bufferRemaining = bufferIncrement;
154 
155             ALOGV("increasing download buffer to %zu bytes",
156                  buffer->size() + bufferRemaining);
157 
158             sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
159             memcpy(copy->data(), buffer->data(), buffer->size());
160             copy->setRange(0, buffer->size());
161 
162             buffer = copy;
163         }
164 
165         size_t maxBytesToRead = bufferRemaining;
166         if (range_length >= 0) {
167             int64_t bytesLeftInRange = range_length - buffer->size();
168             if (bytesLeftInRange < (int64_t)maxBytesToRead) {
169                 maxBytesToRead = bytesLeftInRange;
170 
171                 if (bytesLeftInRange == 0) {
172                     break;
173                 }
174             }
175         }
176 
177         // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
178         // to help us break out of the loop.
179         ssize_t n = mDataSource->readAt(
180                 buffer->size(), buffer->data() + buffer->size(),
181                 maxBytesToRead);
182 
183         if (isDisconnecting()) {
184             return ERROR_NOT_CONNECTED;
185         }
186 
187         if (n < 0) {
188             return n;
189         }
190 
191         if (n == 0) {
192             break;
193         }
194 
195         buffer->setRange(0, buffer->size() + (size_t)n);
196         bytesRead += n;
197     }
198 
199     *out = buffer;
200     if (actualUrl != NULL) {
201         *actualUrl = mDataSource->getUri();
202         if (actualUrl->isEmpty()) {
203             *actualUrl = url;
204         }
205     }
206 
207     return bytesRead;
208 }
209 
fetchFile(const char * url,sp<ABuffer> * out,String8 * actualUrl)210 ssize_t HTTPDownloader::fetchFile(
211         const char *url, sp<ABuffer> *out, String8 *actualUrl) {
212     ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */);
213 
214     // close off the connection after use
215     mHTTPDataSource->disconnect();
216 
217     return err;
218 }
219 
fetchPlaylist(const char * url,uint8_t * curPlaylistHash,bool * unchanged)220 sp<M3UParser> HTTPDownloader::fetchPlaylist(
221         const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
222     ALOGV("fetchPlaylist '%s'", url);
223 
224     *unchanged = false;
225 
226     sp<ABuffer> buffer;
227     String8 actualUrl;
228     ssize_t err = fetchFile(url, &buffer, &actualUrl);
229 
230     // close off the connection after use
231     mHTTPDataSource->disconnect();
232 
233     if (err <= 0) {
234         return NULL;
235     }
236 
237     // MD5 functionality is not available on the simulator, treat all
238     // playlists as changed.
239 
240 #if defined(HAVE_ANDROID_OS)
241     uint8_t hash[16];
242 
243     MD5_CTX m;
244     MD5_Init(&m);
245     MD5_Update(&m, buffer->data(), buffer->size());
246 
247     MD5_Final(hash, &m);
248 
249     if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
250         // playlist unchanged
251         *unchanged = true;
252 
253         return NULL;
254     }
255 
256     if (curPlaylistHash != NULL) {
257         memcpy(curPlaylistHash, hash, sizeof(hash));
258     }
259 #endif
260 
261     sp<M3UParser> playlist =
262         new M3UParser(actualUrl.string(), buffer->data(), buffer->size());
263 
264     if (playlist->initCheck() != OK) {
265         ALOGE("failed to parse .m3u8 playlist");
266 
267         return NULL;
268     }
269 
270     return playlist;
271 }
272 
273 }  // namespace android
274