blob: 7aac4475b1e747bc683b465e93a168cf93add90a [file] [log] [blame]
Andreas Huber163c4932010-06-10 11:08:43 -07001//#define LOG_NDEBUG 0
2#define LOG_TAG "NuHTTPDataSource"
3#include <utils/Log.h>
4
5#include "include/NuHTTPDataSource.h"
6
Andreas Huber3a53dc52010-06-11 09:57:46 -07007#include <cutils/properties.h>
Andreas Huber163c4932010-06-10 11:08:43 -07008#include <media/stagefright/MediaDebug.h>
9#include <media/stagefright/MediaErrors.h>
10
11namespace android {
12
13static bool ParseSingleUnsignedLong(
14 const char *from, unsigned long *x) {
15 char *end;
16 *x = strtoul(from, &end, 10);
17
18 if (end == from || *end != '\0') {
19 return false;
20 }
21
22 return true;
23}
24
25static bool ParseURL(
26 const char *url, String8 *host, unsigned *port, String8 *path) {
27 host->setTo("");
28 *port = 0;
29 path->setTo("");
30
31 if (strncasecmp("http://", url, 7)) {
32 return false;
33 }
34
35 const char *slashPos = strchr(&url[7], '/');
36
37 if (slashPos == NULL) {
38 host->setTo(&url[7]);
39 path->setTo("/");
40 } else {
41 host->setTo(&url[7], slashPos - &url[7]);
42 path->setTo(slashPos);
43 }
44
Mike Lockwood5a23f8c2010-07-15 14:58:25 -040045 const char *colonPos = strchr(host->string(), ':');
Andreas Huber163c4932010-06-10 11:08:43 -070046
47 if (colonPos != NULL) {
48 unsigned long x;
49 if (!ParseSingleUnsignedLong(colonPos + 1, &x) || x >= 65536) {
50 return false;
51 }
52
53 *port = x;
54
55 size_t colonOffset = colonPos - host->string();
56 String8 tmp(host->string(), colonOffset);
57 *host = tmp;
58 } else {
59 *port = 80;
60 }
61
62 return true;
63}
64
65NuHTTPDataSource::NuHTTPDataSource()
66 : mState(DISCONNECTED),
67 mPort(0),
68 mOffset(0),
69 mContentLength(0),
Gloria Wangc2c22e72010-11-01 15:53:16 -070070 mContentLengthValid(false),
71 mDecryptHandle(NULL),
72 mDrmManagerClient(NULL) {
Andreas Huber163c4932010-06-10 11:08:43 -070073}
74
75NuHTTPDataSource::~NuHTTPDataSource() {
76}
77
Andreas Huber3a53dc52010-06-11 09:57:46 -070078status_t NuHTTPDataSource::connect(
79 const char *uri,
80 const KeyedVector<String8, String8> *overrides,
81 off_t offset) {
82 String8 headers;
83 MakeFullHeaders(overrides, &headers);
84
85 return connect(uri, headers, offset);
86}
87
88status_t NuHTTPDataSource::connect(
89 const char *uri,
90 const String8 &headers,
91 off_t offset) {
Andreas Huber163c4932010-06-10 11:08:43 -070092 String8 host, path;
93 unsigned port;
Gloria Wangc2c22e72010-11-01 15:53:16 -070094
95 mUri = uri;
96
Andreas Huber163c4932010-06-10 11:08:43 -070097 if (!ParseURL(uri, &host, &port, &path)) {
98 return ERROR_MALFORMED;
99 }
100
Andreas Huber3a53dc52010-06-11 09:57:46 -0700101 return connect(host, port, path, headers, offset);
Andreas Huber163c4932010-06-10 11:08:43 -0700102}
103
Andreas Huberab2116c2010-09-27 08:17:40 -0700104static bool IsRedirectStatusCode(int httpStatus) {
105 return httpStatus == 301 || httpStatus == 302
106 || httpStatus == 303 || httpStatus == 307;
107}
108
Andreas Huber163c4932010-06-10 11:08:43 -0700109status_t NuHTTPDataSource::connect(
Andreas Huber3a53dc52010-06-11 09:57:46 -0700110 const char *host, unsigned port, const char *path,
111 const String8 &headers,
112 off_t offset) {
Andreas Huber163c4932010-06-10 11:08:43 -0700113 LOGI("connect to %s:%u%s @%ld", host, port, path, offset);
114
115 bool needsToReconnect = true;
116
117 if (mState == CONNECTED && host == mHost && port == mPort
118 && offset == mOffset) {
119 if (mContentLengthValid && mOffset == mContentLength) {
120 LOGI("Didn't have to reconnect, old one's still good.");
121 needsToReconnect = false;
122 }
123 }
124
125 mHost = host;
126 mPort = port;
127 mPath = path;
Andreas Huber3a53dc52010-06-11 09:57:46 -0700128 mHeaders = headers;
Andreas Huber163c4932010-06-10 11:08:43 -0700129
130 status_t err = OK;
131
132 mState = CONNECTING;
133
134 if (needsToReconnect) {
135 mHTTP.disconnect();
136 err = mHTTP.connect(host, port);
137 }
138
139 if (err != OK) {
140 mState = DISCONNECTED;
141 } else if (mState != CONNECTING) {
142 err = UNKNOWN_ERROR;
143 } else {
144 mState = CONNECTED;
145
146 mOffset = offset;
147 mContentLength = 0;
148 mContentLengthValid = false;
149
150 String8 request("GET ");
151 request.append(mPath);
152 request.append(" HTTP/1.1\r\n");
153 request.append("Host: ");
154 request.append(mHost);
155 request.append("\r\n");
156
157 if (offset != 0) {
158 char rangeHeader[128];
159 sprintf(rangeHeader, "Range: bytes=%ld-\r\n", offset);
160 request.append(rangeHeader);
161 }
162
Andreas Huber3a53dc52010-06-11 09:57:46 -0700163 request.append(mHeaders);
Andreas Huber163c4932010-06-10 11:08:43 -0700164 request.append("\r\n");
165
166 int httpStatus;
167 if ((err = mHTTP.send(request.string(), request.size())) != OK
168 || (err = mHTTP.receive_header(&httpStatus)) != OK) {
169 mHTTP.disconnect();
170 mState = DISCONNECTED;
171 return err;
172 }
173
Andreas Huberab2116c2010-09-27 08:17:40 -0700174 if (IsRedirectStatusCode(httpStatus)) {
Andreas Huber163c4932010-06-10 11:08:43 -0700175 string value;
176 CHECK(mHTTP.find_header_value("Location", &value));
177
178 mState = DISCONNECTED;
179
180 mHTTP.disconnect();
181
Andreas Huber3a53dc52010-06-11 09:57:46 -0700182 return connect(value.c_str(), headers, offset);
Andreas Huber163c4932010-06-10 11:08:43 -0700183 }
184
Andreas Huber3a53dc52010-06-11 09:57:46 -0700185 if (httpStatus < 200 || httpStatus >= 300) {
186 mState = DISCONNECTED;
187 mHTTP.disconnect();
188
189 return ERROR_IO;
190 }
191
192 applyTimeoutResponse();
Andreas Huber163c4932010-06-10 11:08:43 -0700193
194 if (offset == 0) {
195 string value;
196 unsigned long x;
197 if (mHTTP.find_header_value(string("Content-Length"), &value)
198 && ParseSingleUnsignedLong(value.c_str(), &x)) {
199 mContentLength = (off_t)x;
200 mContentLengthValid = true;
201 }
202 } else {
203 string value;
204 unsigned long x;
205 if (mHTTP.find_header_value(string("Content-Range"), &value)) {
206 const char *slashPos = strchr(value.c_str(), '/');
207 if (slashPos != NULL
208 && ParseSingleUnsignedLong(slashPos + 1, &x)) {
209 mContentLength = x;
210 mContentLengthValid = true;
211 }
212 }
213 }
214 }
215
216 return err;
217}
218
219void NuHTTPDataSource::disconnect() {
220 if (mState == CONNECTING || mState == CONNECTED) {
221 mHTTP.disconnect();
222 }
223 mState = DISCONNECTED;
224}
225
226status_t NuHTTPDataSource::initCheck() const {
227 return mState == CONNECTED ? OK : NO_INIT;
228}
229
230ssize_t NuHTTPDataSource::readAt(off_t offset, void *data, size_t size) {
231 LOGV("readAt offset %ld, size %d", offset, size);
232
233 Mutex::Autolock autoLock(mLock);
234
235 if (offset != mOffset) {
236 String8 host = mHost;
237 String8 path = mPath;
Andreas Huber3a53dc52010-06-11 09:57:46 -0700238 String8 headers = mHeaders;
239 status_t err = connect(host, mPort, path, headers, offset);
Andreas Huber163c4932010-06-10 11:08:43 -0700240
241 if (err != OK) {
242 return err;
243 }
244 }
245
246 if (mContentLengthValid) {
247 size_t avail =
248 (offset >= mContentLength) ? 0 : mContentLength - offset;
249
250 if (size > avail) {
251 size = avail;
252 }
253 }
254
255 size_t numBytesRead = 0;
256 while (numBytesRead < size) {
257 ssize_t n =
258 mHTTP.receive((uint8_t *)data + numBytesRead, size - numBytesRead);
259
260 if (n < 0) {
261 return n;
262 }
263
264 numBytesRead += (size_t)n;
265
266 if (n == 0) {
267 if (mContentLengthValid) {
268 // We know the content length and made sure not to read beyond
269 // it and yet the server closed the connection on us.
270 return ERROR_IO;
271 }
272
273 break;
274 }
275 }
276
277 mOffset += numBytesRead;
278
279 return numBytesRead;
280}
281
282status_t NuHTTPDataSource::getSize(off_t *size) {
283 *size = 0;
284
285 if (mState != CONNECTED) {
286 return ERROR_IO;
287 }
288
289 if (mContentLengthValid) {
290 *size = mContentLength;
291 return OK;
292 }
293
294 return ERROR_UNSUPPORTED;
295}
296
297uint32_t NuHTTPDataSource::flags() {
298 return kWantsPrefetching;
299}
300
Andreas Huber3a53dc52010-06-11 09:57:46 -0700301// static
302void NuHTTPDataSource::MakeFullHeaders(
303 const KeyedVector<String8, String8> *overrides, String8 *headers) {
304 headers->setTo("");
305
306 headers->append("User-Agent: stagefright/1.1 (Linux;Android ");
307
308#if (PROPERTY_VALUE_MAX < 8)
309#error "PROPERTY_VALUE_MAX must be at least 8"
310#endif
311
312 char value[PROPERTY_VALUE_MAX];
313 property_get("ro.build.version.release", value, "Unknown");
314 headers->append(value);
315 headers->append(")\r\n");
316
317 if (overrides == NULL) {
318 return;
319 }
320
321 for (size_t i = 0; i < overrides->size(); ++i) {
322 String8 line;
323 line.append(overrides->keyAt(i));
324 line.append(": ");
325 line.append(overrides->valueAt(i));
326 line.append("\r\n");
327
328 headers->append(line);
329 }
330}
331
332void NuHTTPDataSource::applyTimeoutResponse() {
333 string timeout;
334 if (mHTTP.find_header_value("X-SocketTimeout", &timeout)) {
335 const char *s = timeout.c_str();
336 char *end;
337 long tmp = strtol(s, &end, 10);
338 if (end == s || *end != '\0') {
339 LOGW("Illegal X-SocketTimeout value given.");
340 return;
341 }
342
343 LOGI("overriding default timeout, new timeout is %ld seconds", tmp);
344 mHTTP.setReceiveTimeout(tmp);
345 }
346}
347
Gloria Wangc2c22e72010-11-01 15:53:16 -0700348DecryptHandle* NuHTTPDataSource::DrmInitialization(DrmManagerClient* client) {
349 if (client == NULL) {
350 return NULL;
351 }
352 mDrmManagerClient = client;
353
354 if (mDecryptHandle == NULL) {
355 /* Note if redirect occurs, mUri is the redirect uri instead of the
356 * original one
357 */
358 mDecryptHandle = mDrmManagerClient->openDecryptSession(mUri);
359 }
360
361 if (mDecryptHandle == NULL) {
362 mDrmManagerClient = NULL;
363 }
364
365 return mDecryptHandle;
366}
367
368void NuHTTPDataSource::getDrmInfo(DecryptHandle **handle, DrmManagerClient **client) {
369 *handle = mDecryptHandle;
370
371 *client = mDrmManagerClient;
372}
373
Andreas Huber163c4932010-06-10 11:08:43 -0700374} // namespace android