| //#define LOG_NDEBUG 0 |
| #define LOG_TAG "NuHTTPDataSource" |
| #include <utils/Log.h> |
| |
| #include "include/NuHTTPDataSource.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); |
| } |
| |
| 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, off_t offset) { |
| String8 host, path; |
| unsigned port; |
| if (!ParseURL(uri, &host, &port, &path)) { |
| return ERROR_MALFORMED; |
| } |
| |
| return connect(host, port, path, offset); |
| } |
| |
| status_t NuHTTPDataSource::connect( |
| const char *host, unsigned port, const char *path, 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; |
| |
| 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("\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 (httpStatus == 302) { |
| string value; |
| CHECK(mHTTP.find_header_value("Location", &value)); |
| |
| mState = DISCONNECTED; |
| |
| mHTTP.disconnect(); |
| |
| return connect(value.c_str()); |
| } |
| |
| CHECK(httpStatus >= 200 && httpStatus < 300); |
| |
| 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; |
| status_t err = connect(host, mPort, path, 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; |
| } |
| |
| } // namespace android |