//#define LOG_NDEBUG 0 #define LOG_TAG "NuHTTPDataSource" #include #include "include/NuHTTPDataSource.h" #include #include #include namespace android { static bool ParseSingleUnsignedLong( const char *from, unsigned long *x) { char *end; *x = strtoul(from, &end, 10); if (end == from || *end != '\0') { return false; } return true; } static bool ParseURL( const char *url, String8 *host, unsigned *port, String8 *path) { host->setTo(""); *port = 0; path->setTo(""); if (strncasecmp("http://", url, 7)) { return false; } const char *slashPos = strchr(&url[7], '/'); if (slashPos == NULL) { host->setTo(&url[7]); path->setTo("/"); } else { host->setTo(&url[7], slashPos - &url[7]); path->setTo(slashPos); } char *colonPos = strchr(host->string(), ':'); if (colonPos != NULL) { unsigned long x; if (!ParseSingleUnsignedLong(colonPos + 1, &x) || x >= 65536) { return false; } *port = x; size_t colonOffset = colonPos - host->string(); String8 tmp(host->string(), colonOffset); *host = tmp; } else { *port = 80; } return true; } NuHTTPDataSource::NuHTTPDataSource() : mState(DISCONNECTED), mPort(0), mOffset(0), mContentLength(0), mContentLengthValid(false) { } NuHTTPDataSource::~NuHTTPDataSource() { } status_t NuHTTPDataSource::connect( const char *uri, const KeyedVector *overrides, off_t offset) { String8 headers; MakeFullHeaders(overrides, &headers); return connect(uri, headers, offset); } status_t NuHTTPDataSource::connect( const char *uri, const String8 &headers, off_t offset) { String8 host, path; unsigned port; if (!ParseURL(uri, &host, &port, &path)) { return ERROR_MALFORMED; } return connect(host, port, path, headers, offset); } static bool IsRedirectStatusCode(int httpStatus) { return httpStatus == 301 || httpStatus == 302 || httpStatus == 303 || httpStatus == 307; } status_t NuHTTPDataSource::connect( const char *host, unsigned port, const char *path, const String8 &headers, off_t offset) { LOGI("connect to %s:%u%s @%ld", host, port, path, offset); bool needsToReconnect = true; if (mState == CONNECTED && host == mHost && port == mPort && offset == mOffset) { if (mContentLengthValid && mOffset == mContentLength) { LOGI("Didn't have to reconnect, old one's still good."); needsToReconnect = false; } } mHost = host; mPort = port; mPath = path; mHeaders = headers; status_t err = OK; mState = CONNECTING; if (needsToReconnect) { mHTTP.disconnect(); err = mHTTP.connect(host, port); } if (err != OK) { mState = DISCONNECTED; } else if (mState != CONNECTING) { err = UNKNOWN_ERROR; } else { mState = CONNECTED; mOffset = offset; mContentLength = 0; mContentLengthValid = false; String8 request("GET "); request.append(mPath); request.append(" HTTP/1.1\r\n"); request.append("Host: "); request.append(mHost); request.append("\r\n"); if (offset != 0) { char rangeHeader[128]; sprintf(rangeHeader, "Range: bytes=%ld-\r\n", offset); request.append(rangeHeader); } request.append(mHeaders); request.append("\r\n"); int httpStatus; if ((err = mHTTP.send(request.string(), request.size())) != OK || (err = mHTTP.receive_header(&httpStatus)) != OK) { mHTTP.disconnect(); mState = DISCONNECTED; return err; } if (IsRedirectStatusCode(httpStatus)) { string value; CHECK(mHTTP.find_header_value("Location", &value)); mState = DISCONNECTED; mHTTP.disconnect(); return connect(value.c_str(), headers, offset); } if (httpStatus < 200 || httpStatus >= 300) { mState = DISCONNECTED; mHTTP.disconnect(); return ERROR_IO; } applyTimeoutResponse(); if (offset == 0) { string value; unsigned long x; if (mHTTP.find_header_value(string("Content-Length"), &value) && ParseSingleUnsignedLong(value.c_str(), &x)) { mContentLength = (off_t)x; mContentLengthValid = true; } } else { string value; unsigned long x; if (mHTTP.find_header_value(string("Content-Range"), &value)) { const char *slashPos = strchr(value.c_str(), '/'); if (slashPos != NULL && ParseSingleUnsignedLong(slashPos + 1, &x)) { mContentLength = x; mContentLengthValid = true; } } } } return err; } void NuHTTPDataSource::disconnect() { if (mState == CONNECTING || mState == CONNECTED) { mHTTP.disconnect(); } mState = DISCONNECTED; } status_t NuHTTPDataSource::initCheck() const { return mState == CONNECTED ? OK : NO_INIT; } ssize_t NuHTTPDataSource::readAt(off_t offset, void *data, size_t size) { LOGV("readAt offset %ld, size %d", offset, size); Mutex::Autolock autoLock(mLock); if (offset != mOffset) { String8 host = mHost; String8 path = mPath; String8 headers = mHeaders; status_t err = connect(host, mPort, path, headers, offset); if (err != OK) { return err; } } if (mContentLengthValid) { size_t avail = (offset >= mContentLength) ? 0 : mContentLength - offset; if (size > avail) { size = avail; } } size_t numBytesRead = 0; while (numBytesRead < size) { ssize_t n = mHTTP.receive((uint8_t *)data + numBytesRead, size - numBytesRead); if (n < 0) { return n; } numBytesRead += (size_t)n; if (n == 0) { if (mContentLengthValid) { // We know the content length and made sure not to read beyond // it and yet the server closed the connection on us. return ERROR_IO; } break; } } mOffset += numBytesRead; return numBytesRead; } status_t NuHTTPDataSource::getSize(off_t *size) { *size = 0; if (mState != CONNECTED) { return ERROR_IO; } if (mContentLengthValid) { *size = mContentLength; return OK; } return ERROR_UNSUPPORTED; } uint32_t NuHTTPDataSource::flags() { return kWantsPrefetching; } // static void NuHTTPDataSource::MakeFullHeaders( const KeyedVector *overrides, String8 *headers) { headers->setTo(""); headers->append("User-Agent: stagefright/1.1 (Linux;Android "); #if (PROPERTY_VALUE_MAX < 8) #error "PROPERTY_VALUE_MAX must be at least 8" #endif char value[PROPERTY_VALUE_MAX]; property_get("ro.build.version.release", value, "Unknown"); headers->append(value); headers->append(")\r\n"); if (overrides == NULL) { return; } for (size_t i = 0; i < overrides->size(); ++i) { String8 line; line.append(overrides->keyAt(i)); line.append(": "); line.append(overrides->valueAt(i)); line.append("\r\n"); headers->append(line); } } void NuHTTPDataSource::applyTimeoutResponse() { string timeout; if (mHTTP.find_header_value("X-SocketTimeout", &timeout)) { const char *s = timeout.c_str(); char *end; long tmp = strtol(s, &end, 10); if (end == s || *end != '\0') { LOGW("Illegal X-SocketTimeout value given."); return; } LOGI("overriding default timeout, new timeout is %ld seconds", tmp); mHTTP.setReceiveTimeout(tmp); } } } // namespace android