| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "NuHTTPDataSource" |
| #include <utils/Log.h> |
| |
| #include "include/NuHTTPDataSource.h" |
| |
| #include <cutils/properties.h> |
| #include <media/stagefright/MediaDebug.h> |
| #include <media/stagefright/MediaErrors.h> |
| |
| 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); |
| } |
| |
| const 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<String8, String8> *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<String8, String8> *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 |