Initial check in of a new http data source implementation.

Change-Id: I17c358288ffe9ef690d702c58723c766d0a0cf21
diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp
new file mode 100644
index 0000000..8587c1b
--- /dev/null
+++ b/media/libstagefright/NuHTTPDataSource.cpp
@@ -0,0 +1,265 @@
+//#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
diff --git a/media/libstagefright/include/NuHTTPDataSource.h b/media/libstagefright/include/NuHTTPDataSource.h
new file mode 100644
index 0000000..33339e2
--- /dev/null
+++ b/media/libstagefright/include/NuHTTPDataSource.h
@@ -0,0 +1,59 @@
+#ifndef NU_HTTP_DATA_SOURCE_H_
+
+#define NU_HTTP_DATA_SOURCE_H_
+
+#include <media/stagefright/DataSource.h>
+#include <utils/String8.h>
+#include <utils/threads.h>
+
+#include "HTTPStream.h"
+
+namespace android {
+
+struct NuHTTPDataSource : public DataSource {
+    NuHTTPDataSource();
+
+    status_t connect(const char *uri, off_t offset = 0);
+
+    status_t connect(
+            const char *host, unsigned port, const char *path,
+            off_t offset = 0);
+
+    void disconnect();
+
+    virtual status_t initCheck() const;
+
+    virtual ssize_t readAt(off_t offset, void *data, size_t size);
+    virtual status_t getSize(off_t *size);
+    virtual uint32_t flags();
+
+protected:
+    virtual ~NuHTTPDataSource();
+
+private:
+    enum State {
+        DISCONNECTED,
+        CONNECTING,
+        CONNECTED
+    };
+
+    Mutex mLock;
+
+    State mState;
+
+    String8 mHost;
+    unsigned mPort;
+    String8 mPath;
+
+    HTTPStream mHTTP;
+    off_t mOffset;
+    off_t mContentLength;
+    bool mContentLengthValid;
+
+    NuHTTPDataSource(const NuHTTPDataSource &);
+    NuHTTPDataSource &operator=(const NuHTTPDataSource &);
+};
+
+}  // namespace android
+
+#endif  // NU_HTTP_DATA_SOURCE_H_