Merge "Support for "chunked" HTTP transfer encoding."
diff --git a/media/libstagefright/NuHTTPDataSource.cpp b/media/libstagefright/NuHTTPDataSource.cpp
index 40f501a..c3c0d81 100644
--- a/media/libstagefright/NuHTTPDataSource.cpp
+++ b/media/libstagefright/NuHTTPDataSource.cpp
@@ -69,6 +69,8 @@
       mOffset(0),
       mContentLength(0),
       mContentLengthValid(false),
+      mHasChunkedTransferEncoding(false),
+      mChunkDataBytesLeft(0),
       mNumBandwidthHistoryItems(0),
       mTotalTransferTimeUs(0),
       mTotalTransferBytes(0),
@@ -193,17 +195,27 @@
             return ERROR_IO;
         }
 
+        mHasChunkedTransferEncoding = false;
+
         {
             string value;
-            if (mHTTP.find_header_value("Transfer-Encoding", &value)) {
-                // We don't currently support any transfer encodings.
+            if (mHTTP.find_header_value("Transfer-Encoding", &value)
+                    || mHTTP.find_header_value("Transfer-encoding", &value)) {
+                // We don't currently support any transfer encodings but
+                // chunked.
 
-                mState = DISCONNECTED;
-                mHTTP.disconnect();
+                if (!strcasecmp(value.c_str(), "chunked")) {
+                    LOGI("Chunked transfer encoding applied.");
+                    mHasChunkedTransferEncoding = true;
+                    mChunkDataBytesLeft = 0;
+                } else {
+                    mState = DISCONNECTED;
+                    mHTTP.disconnect();
 
-                LOGE("We don't support '%s' transfer encoding.", value.c_str());
+                    LOGE("We don't support '%s' transfer encoding.", value.c_str());
 
-                return ERROR_UNSUPPORTED;
+                    return ERROR_UNSUPPORTED;
+                }
             }
         }
 
@@ -216,8 +228,17 @@
                     && ParseSingleUnsignedLong(value.c_str(), &x)) {
                 mContentLength = (off_t)x;
                 mContentLengthValid = true;
+            } else {
+                LOGW("Server did not give us the content length!");
             }
         } else {
+            if (httpStatus != 206 /* Partial Content */) {
+                // We requested a range but the server didn't support that.
+                LOGE("We requested a range but the server didn't "
+                     "support that.");
+                return ERROR_UNSUPPORTED;
+            }
+
             string value;
             unsigned long x;
             if (mHTTP.find_header_value(string("Content-Range"), &value)) {
@@ -245,6 +266,71 @@
     return mState == CONNECTED ? OK : NO_INIT;
 }
 
+ssize_t NuHTTPDataSource::internalRead(void *data, size_t size) {
+    if (!mHasChunkedTransferEncoding) {
+        return mHTTP.receive(data, size);
+    }
+
+    if (mChunkDataBytesLeft < 0) {
+        return 0;
+    } else if (mChunkDataBytesLeft == 0) {
+        char line[1024];
+        status_t err = mHTTP.receive_line(line, sizeof(line));
+
+        if (err != OK) {
+            return err;
+        }
+
+        LOGV("line = '%s'", line);
+
+        char *end;
+        unsigned long n = strtoul(line, &end, 16);
+
+        if (end == line || (*end != ';' && *end != '\0')) {
+            LOGE("malformed HTTP chunk '%s'", line);
+            return ERROR_MALFORMED;
+        }
+
+        mChunkDataBytesLeft = n;
+        LOGV("chunk data size = %lu", n);
+
+        if (mChunkDataBytesLeft == 0) {
+            mChunkDataBytesLeft = -1;
+            return 0;
+        }
+
+        // fall through
+    }
+
+    if (size > (size_t)mChunkDataBytesLeft) {
+        size = mChunkDataBytesLeft;
+    }
+
+    ssize_t n = mHTTP.receive(data, size);
+
+    if (n < 0) {
+        return n;
+    }
+
+    mChunkDataBytesLeft -= (size_t)n;
+
+    if (mChunkDataBytesLeft == 0) {
+        char line[1024];
+        status_t err = mHTTP.receive_line(line, sizeof(line));
+
+        if (err != OK) {
+            return err;
+        }
+
+        if (line[0] != '\0') {
+            LOGE("missing HTTP chunk terminator.");
+            return ERROR_MALFORMED;
+        }
+    }
+
+    return n;
+}
+
 ssize_t NuHTTPDataSource::readAt(off_t offset, void *data, size_t size) {
     LOGV("readAt offset %ld, size %d", offset, size);
 
@@ -275,7 +361,7 @@
         int64_t startTimeUs = ALooper::GetNowUs();
 
         ssize_t n =
-            mHTTP.receive((uint8_t *)data + numBytesRead, size - numBytesRead);
+            internalRead((uint8_t *)data + numBytesRead, size - numBytesRead);
 
         if (n < 0) {
             return n;
diff --git a/media/libstagefright/httplive/LiveSource.cpp b/media/libstagefright/httplive/LiveSource.cpp
index f9d27eb..c19b6f7 100644
--- a/media/libstagefright/httplive/LiveSource.cpp
+++ b/media/libstagefright/httplive/LiveSource.cpp
@@ -555,20 +555,41 @@
     status_t err = source->getSize(&size);
 
     if (err != OK) {
-        return err;
+        size = 65536;
     }
 
     sp<ABuffer> buffer = new ABuffer(size);
-    size_t offset = 0;
-    while (offset < (size_t)size) {
-        ssize_t n = source->readAt(
-                offset, buffer->data() + offset, size - offset);
+    buffer->setRange(0, 0);
 
-        if (n <= 0) {
-            return ERROR_IO;
+    for (;;) {
+        size_t bufferRemaining = buffer->capacity() - buffer->size();
+
+        if (bufferRemaining == 0) {
+            bufferRemaining = 32768;
+
+            LOGV("increasing download buffer to %d bytes",
+                 buffer->size() + bufferRemaining);
+
+            sp<ABuffer> copy = new ABuffer(buffer->size() + bufferRemaining);
+            memcpy(copy->data(), buffer->data(), buffer->size());
+            copy->setRange(0, buffer->size());
+
+            buffer = copy;
         }
 
-        offset += n;
+        ssize_t n = source->readAt(
+                buffer->size(), buffer->data() + buffer->size(),
+                bufferRemaining);
+
+        if (n < 0) {
+            return err;
+        }
+
+        if (n == 0) {
+            break;
+        }
+
+        buffer->setRange(0, buffer->size() + (size_t)n);
     }
 
     *out = buffer;
diff --git a/media/libstagefright/include/HTTPStream.h b/media/libstagefright/include/HTTPStream.h
index 35b0865..793798f 100644
--- a/media/libstagefright/include/HTTPStream.h
+++ b/media/libstagefright/include/HTTPStream.h
@@ -55,6 +55,10 @@
     // Pass a negative value to disable the timeout.
     void setReceiveTimeout(int seconds);
 
+    // Receive a line of data terminated by CRLF, line will be '\0' terminated
+    // _excluding_ the termianting CRLF.
+    status_t receive_line(char *line, size_t size);
+
 private:
     enum State {
         READY,
@@ -68,10 +72,6 @@
 
     KeyedVector<string, string> mHeaders;
 
-    // Receive a line of data terminated by CRLF, line will be '\0' terminated
-    // _excluding_ the termianting CRLF.
-    status_t receive_line(char *line, size_t size);
-
     HTTPStream(const HTTPStream &);
     HTTPStream &operator=(const HTTPStream &);
 };
diff --git a/media/libstagefright/include/NuHTTPDataSource.h b/media/libstagefright/include/NuHTTPDataSource.h
index 0b840bd..c42691f 100644
--- a/media/libstagefright/include/NuHTTPDataSource.h
+++ b/media/libstagefright/include/NuHTTPDataSource.h
@@ -64,6 +64,11 @@
     off_t mOffset;
     off_t mContentLength;
     bool mContentLengthValid;
+    bool mHasChunkedTransferEncoding;
+
+    // The number of data bytes in the current chunk before any subsequent
+    // chunk header (or -1 if no more chunks).
+    ssize_t mChunkDataBytesLeft;
 
     List<BandwidthEntry> mBandwidthHistory;
     size_t mNumBandwidthHistoryItems;
@@ -81,6 +86,9 @@
             const String8 &headers,
             off_t offset);
 
+    // Read up to "size" bytes of data, respect transfer encoding.
+    ssize_t internalRead(void *data, size_t size);
+
     void applyTimeoutResponse();
     void addBandwidthMeasurement_l(size_t numBytes, int64_t delayUs);