1 //#define LOG_NDEBUG 0 2 #define LOG_TAG "NuHTTPDataSource" 3 #include <utils/Log.h> 4 5 #include "include/NuHTTPDataSource.h" 6 7 #include <cutils/properties.h> 8 #include <media/stagefright/MediaDebug.h> 9 #include <media/stagefright/MediaErrors.h> 10 11 namespace android { 12 13 static 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 25 static 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 45 char *colonPos = strchr(host->string(), ':'); 46 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 65 NuHTTPDataSource::NuHTTPDataSource() 66 : mState(DISCONNECTED), 67 mPort(0), 68 mOffset(0), 69 mContentLength(0), 70 mContentLengthValid(false) { 71 } 72 73 NuHTTPDataSource::~NuHTTPDataSource() { 74 } 75 76 status_t NuHTTPDataSource::connect( 77 const char *uri, 78 const KeyedVector<String8, String8> *overrides, 79 off_t offset) { 80 String8 headers; 81 MakeFullHeaders(overrides, &headers); 82 83 return connect(uri, headers, offset); 84 } 85 86 status_t NuHTTPDataSource::connect( 87 const char *uri, 88 const String8 &headers, 89 off_t offset) { 90 String8 host, path; 91 unsigned port; 92 if (!ParseURL(uri, &host, &port, &path)) { 93 return ERROR_MALFORMED; 94 } 95 96 return connect(host, port, path, headers, offset); 97 } 98 99 static bool IsRedirectStatusCode(int httpStatus) { 100 return httpStatus == 301 || httpStatus == 302 101 || httpStatus == 303 || httpStatus == 307; 102 } 103 104 status_t NuHTTPDataSource::connect( 105 const char *host, unsigned port, const char *path, 106 const String8 &headers, 107 off_t offset) { 108 LOGI("connect to %s:%u%s @%ld", host, port, path, offset); 109 110 bool needsToReconnect = true; 111 112 if (mState == CONNECTED && host == mHost && port == mPort 113 && offset == mOffset) { 114 if (mContentLengthValid && mOffset == mContentLength) { 115 LOGI("Didn't have to reconnect, old one's still good."); 116 needsToReconnect = false; 117 } 118 } 119 120 mHost = host; 121 mPort = port; 122 mPath = path; 123 mHeaders = headers; 124 125 status_t err = OK; 126 127 mState = CONNECTING; 128 129 if (needsToReconnect) { 130 mHTTP.disconnect(); 131 err = mHTTP.connect(host, port); 132 } 133 134 if (err != OK) { 135 mState = DISCONNECTED; 136 } else if (mState != CONNECTING) { 137 err = UNKNOWN_ERROR; 138 } else { 139 mState = CONNECTED; 140 141 mOffset = offset; 142 mContentLength = 0; 143 mContentLengthValid = false; 144 145 String8 request("GET "); 146 request.append(mPath); 147 request.append(" HTTP/1.1\r\n"); 148 request.append("Host: "); 149 request.append(mHost); 150 request.append("\r\n"); 151 152 if (offset != 0) { 153 char rangeHeader[128]; 154 sprintf(rangeHeader, "Range: bytes=%ld-\r\n", offset); 155 request.append(rangeHeader); 156 } 157 158 request.append(mHeaders); 159 request.append("\r\n"); 160 161 int httpStatus; 162 if ((err = mHTTP.send(request.string(), request.size())) != OK 163 || (err = mHTTP.receive_header(&httpStatus)) != OK) { 164 mHTTP.disconnect(); 165 mState = DISCONNECTED; 166 return err; 167 } 168 169 if (IsRedirectStatusCode(httpStatus)) { 170 string value; 171 CHECK(mHTTP.find_header_value("Location", &value)); 172 173 mState = DISCONNECTED; 174 175 mHTTP.disconnect(); 176 177 return connect(value.c_str(), headers, offset); 178 } 179 180 if (httpStatus < 200 || httpStatus >= 300) { 181 mState = DISCONNECTED; 182 mHTTP.disconnect(); 183 184 return ERROR_IO; 185 } 186 187 applyTimeoutResponse(); 188 189 if (offset == 0) { 190 string value; 191 unsigned long x; 192 if (mHTTP.find_header_value(string("Content-Length"), &value) 193 && ParseSingleUnsignedLong(value.c_str(), &x)) { 194 mContentLength = (off_t)x; 195 mContentLengthValid = true; 196 } 197 } else { 198 string value; 199 unsigned long x; 200 if (mHTTP.find_header_value(string("Content-Range"), &value)) { 201 const char *slashPos = strchr(value.c_str(), '/'); 202 if (slashPos != NULL 203 && ParseSingleUnsignedLong(slashPos + 1, &x)) { 204 mContentLength = x; 205 mContentLengthValid = true; 206 } 207 } 208 } 209 } 210 211 return err; 212 } 213 214 void NuHTTPDataSource::disconnect() { 215 if (mState == CONNECTING || mState == CONNECTED) { 216 mHTTP.disconnect(); 217 } 218 mState = DISCONNECTED; 219 } 220 221 status_t NuHTTPDataSource::initCheck() const { 222 return mState == CONNECTED ? OK : NO_INIT; 223 } 224 225 ssize_t NuHTTPDataSource::readAt(off_t offset, void *data, size_t size) { 226 LOGV("readAt offset %ld, size %d", offset, size); 227 228 Mutex::Autolock autoLock(mLock); 229 230 if (offset != mOffset) { 231 String8 host = mHost; 232 String8 path = mPath; 233 String8 headers = mHeaders; 234 status_t err = connect(host, mPort, path, headers, offset); 235 236 if (err != OK) { 237 return err; 238 } 239 } 240 241 if (mContentLengthValid) { 242 size_t avail = 243 (offset >= mContentLength) ? 0 : mContentLength - offset; 244 245 if (size > avail) { 246 size = avail; 247 } 248 } 249 250 size_t numBytesRead = 0; 251 while (numBytesRead < size) { 252 ssize_t n = 253 mHTTP.receive((uint8_t *)data + numBytesRead, size - numBytesRead); 254 255 if (n < 0) { 256 return n; 257 } 258 259 numBytesRead += (size_t)n; 260 261 if (n == 0) { 262 if (mContentLengthValid) { 263 // We know the content length and made sure not to read beyond 264 // it and yet the server closed the connection on us. 265 return ERROR_IO; 266 } 267 268 break; 269 } 270 } 271 272 mOffset += numBytesRead; 273 274 return numBytesRead; 275 } 276 277 status_t NuHTTPDataSource::getSize(off_t *size) { 278 *size = 0; 279 280 if (mState != CONNECTED) { 281 return ERROR_IO; 282 } 283 284 if (mContentLengthValid) { 285 *size = mContentLength; 286 return OK; 287 } 288 289 return ERROR_UNSUPPORTED; 290 } 291 292 uint32_t NuHTTPDataSource::flags() { 293 return kWantsPrefetching; 294 } 295 296 // static 297 void NuHTTPDataSource::MakeFullHeaders( 298 const KeyedVector<String8, String8> *overrides, String8 *headers) { 299 headers->setTo(""); 300 301 headers->append("User-Agent: stagefright/1.1 (Linux;Android "); 302 303 #if (PROPERTY_VALUE_MAX < 8) 304 #error "PROPERTY_VALUE_MAX must be at least 8" 305 #endif 306 307 char value[PROPERTY_VALUE_MAX]; 308 property_get("ro.build.version.release", value, "Unknown"); 309 headers->append(value); 310 headers->append(")\r\n"); 311 312 if (overrides == NULL) { 313 return; 314 } 315 316 for (size_t i = 0; i < overrides->size(); ++i) { 317 String8 line; 318 line.append(overrides->keyAt(i)); 319 line.append(": "); 320 line.append(overrides->valueAt(i)); 321 line.append("\r\n"); 322 323 headers->append(line); 324 } 325 } 326 327 void NuHTTPDataSource::applyTimeoutResponse() { 328 string timeout; 329 if (mHTTP.find_header_value("X-SocketTimeout", &timeout)) { 330 const char *s = timeout.c_str(); 331 char *end; 332 long tmp = strtol(s, &end, 10); 333 if (end == s || *end != '\0') { 334 LOGW("Illegal X-SocketTimeout value given."); 335 return; 336 } 337 338 LOGI("overriding default timeout, new timeout is %ld seconds", tmp); 339 mHTTP.setReceiveTimeout(tmp); 340 } 341 } 342 343 } // namespace android 344