Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9d202760 authored by Andreas Huber's avatar Andreas Huber
Browse files

Support for "chunked" HTTP transfer encoding.

Change-Id: I2f20d2d9ec0fa0c840b429049b0385289a30e774
related-to-bug: 3205131
parent b33be1c6
Loading
Loading
Loading
Loading
+96 −10
Original line number Diff line number Diff line
@@ -69,6 +69,8 @@ NuHTTPDataSource::NuHTTPDataSource()
      mOffset(0),
      mContentLength(0),
      mContentLengthValid(false),
      mHasChunkedTransferEncoding(false),
      mChunkDataBytesLeft(0),
      mNumBandwidthHistoryItems(0),
      mTotalTransferTimeUs(0),
      mTotalTransferBytes(0),
@@ -193,11 +195,20 @@ status_t NuHTTPDataSource::connect(
            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.

                if (!strcasecmp(value.c_str(), "chunked")) {
                    LOGI("Chunked transfer encoding applied.");
                    mHasChunkedTransferEncoding = true;
                    mChunkDataBytesLeft = 0;
                } else {
                    mState = DISCONNECTED;
                    mHTTP.disconnect();

@@ -206,6 +217,7 @@ status_t NuHTTPDataSource::connect(
                    return ERROR_UNSUPPORTED;
                }
            }
        }

        applyTimeoutResponse();

@@ -216,8 +228,17 @@ status_t NuHTTPDataSource::connect(
                    && 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 @@ status_t NuHTTPDataSource::initCheck() const {
    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 @@ ssize_t NuHTTPDataSource::readAt(off_t offset, void *data, size_t size) {
        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;
+28 −7
Original line number Diff line number Diff line
@@ -555,20 +555,41 @@ status_t LiveSource::fetchM3U(const char *url, sp<ABuffer> *out) {
    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) {
    buffer->setRange(0, 0);

    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;
        }

        ssize_t n = source->readAt(
                offset, buffer->data() + offset, size - offset);
                buffer->size(), buffer->data() + buffer->size(),
                bufferRemaining);

        if (n <= 0) {
            return ERROR_IO;
        if (n < 0) {
            return err;
        }

        offset += n;
        if (n == 0) {
            break;
        }

        buffer->setRange(0, buffer->size() + (size_t)n);
    }

    *out = buffer;
+4 −4
Original line number Diff line number Diff line
@@ -55,6 +55,10 @@ public:
    // 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 @@ private:

    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 &);
};
+8 −0
Original line number Diff line number Diff line
@@ -64,6 +64,11 @@ private:
    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 @@ private:
            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);