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