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 #include <inttypes.h>
35 
36 namespace android {
37 
HTTPDownloader(const sp<IMediaHTTPService> & httpService,const KeyedVector<String8,String8> & headers)38 HTTPDownloader::HTTPDownloader(
39         const sp<IMediaHTTPService> &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             memcpy(copy->data(), buffer->data(), buffer->size());
161             copy->setRange(0, buffer->size());
162 
163             buffer = copy;
164         }
165 
166         size_t maxBytesToRead = bufferRemaining;
167         if (range_length >= 0) {
168             int64_t bytesLeftInRange = range_length - buffer->size();
169             if (bytesLeftInRange < 0) {
170                 ALOGE("range_length %" PRId64 " wrapped around", range_length);
171                 return ERROR_OUT_OF_RANGE;
172             } else if (bytesLeftInRange < (int64_t)maxBytesToRead) {
173                 maxBytesToRead = bytesLeftInRange;
174 
175                 if (bytesLeftInRange == 0) {
176                     break;
177                 }
178             }
179         }
180 
181         // The DataSource is responsible for informing us of error (n < 0) or eof (n == 0)
182         // to help us break out of the loop.
183         ssize_t n = mDataSource->readAt(
184                 buffer->size(), buffer->data() + buffer->size(),
185                 maxBytesToRead);
186 
187         if (isDisconnecting()) {
188             return ERROR_NOT_CONNECTED;
189         }
190 
191         if (n < 0) {
192             return n;
193         }
194 
195         if (n == 0) {
196             break;
197         }
198 
199         buffer->setRange(0, buffer->size() + (size_t)n);
200         bytesRead += n;
201     }
202 
203     *out = buffer;
204     if (actualUrl != NULL) {
205         *actualUrl = mDataSource->getUri();
206         if (actualUrl->isEmpty()) {
207             *actualUrl = url;
208         }
209     }
210 
211     return bytesRead;
212 }
213 
fetchFile(const char * url,sp<ABuffer> * out,String8 * actualUrl)214 ssize_t HTTPDownloader::fetchFile(
215         const char *url, sp<ABuffer> *out, String8 *actualUrl) {
216     ssize_t err = fetchBlock(url, out, 0, -1, 0, actualUrl, true /* reconnect */);
217 
218     // close off the connection after use
219     mHTTPDataSource->disconnect();
220 
221     return err;
222 }
223 
fetchPlaylist(const char * url,uint8_t * curPlaylistHash,bool * unchanged)224 sp<M3UParser> HTTPDownloader::fetchPlaylist(
225         const char *url, uint8_t *curPlaylistHash, bool *unchanged) {
226     ALOGV("fetchPlaylist '%s'", url);
227 
228     *unchanged = false;
229 
230     sp<ABuffer> buffer;
231     String8 actualUrl;
232     ssize_t err = fetchFile(url, &buffer, &actualUrl);
233 
234     // close off the connection after use
235     mHTTPDataSource->disconnect();
236 
237     if (err <= 0) {
238         return NULL;
239     }
240 
241     // MD5 functionality is not available on the simulator, treat all
242     // playlists as changed.
243 
244 #if defined(__ANDROID__)
245     uint8_t hash[16];
246 
247     MD5_CTX m;
248     MD5_Init(&m);
249     MD5_Update(&m, buffer->data(), buffer->size());
250 
251     MD5_Final(hash, &m);
252 
253     if (curPlaylistHash != NULL && !memcmp(hash, curPlaylistHash, 16)) {
254         // playlist unchanged
255         *unchanged = true;
256 
257         return NULL;
258     }
259 
260     if (curPlaylistHash != NULL) {
261         memcpy(curPlaylistHash, hash, sizeof(hash));
262     }
263 #endif
264 
265     sp<M3UParser> playlist =
266         new M3UParser(actualUrl.string(), buffer->data(), buffer->size());
267 
268     if (playlist->initCheck() != OK) {
269         ALOGE("failed to parse .m3u8 playlist");
270 
271         return NULL;
272     }
273 
274     return playlist;
275 }
276 
277 }  // namespace android
278