/* * Copyright (C) 2009 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ //#define LOG_NDEBUG 0 #define LOG_TAG "HTTPDataSource" #include #include "include/stagefright_string.h" #include "include/HTTPStream.h" #include #include #include #include namespace android { status_t HTTPDataSource::connectWithRedirectsAndRange(off_t rangeStart) { string host = mStartingHost.string(); string path = mStartingPath.string(); int port = mStartingPort; LOGV("Connecting to host '%s', port %d, path '%s'", host.c_str(), port, path.c_str()); int numRedirectsRemaining = 5; while (numRedirectsRemaining-- > 0) { { Mutex::Autolock autoLock(mStateLock); if (mState == DISCONNECTED) { return UNKNOWN_ERROR; } } status_t err = mHttp->connect(host.c_str(), port); if (err != OK) { return err; } String8 request; request.append("GET "); request.append(path.c_str()); request.append(" HTTP/1.1\r\n"); request.append(mHeaders); request.append("Host: "); request.append(host.c_str()); request.append("\r\n"); if (rangeStart > 0) { char range[128]; sprintf(range, "Range: bytes=%ld-\r\n", rangeStart); request.append(range); } request.append("\r\n"); err = mHttp->send(request.string()); if (err != OK) { return err; } int httpStatus; err = mHttp->receive_header(&httpStatus); if (err != OK) { return err; } if (httpStatus >= 200 && httpStatus < 300) { applyTimeoutResponse(); return OK; } if (httpStatus != 301 && httpStatus != 302) { LOGE("HTTP request failed w/ http status %d", httpStatus); return ERROR_IO; } string location; CHECK(mHttp->find_header_value("Location", &location)); CHECK(string(location, 0, 7) == "http://"); location.erase(0, 7); string::size_type slashPos = location.find('/'); if (slashPos == string::npos) { slashPos = location.size(); location += '/'; } mHttp->disconnect(); LOGV("Redirecting to %s\n", location.c_str()); host = string(location, 0, slashPos); string::size_type colonPos = host.find(':'); if (colonPos != string::npos) { const char *start = host.c_str() + colonPos + 1; char *end; long tmp = strtol(start, &end, 10); CHECK(end > start && (*end == '\0')); port = (tmp >= 0 && tmp < 65536) ? (int)tmp : 80; host.erase(colonPos, host.size() - colonPos); } else { port = 80; } path = string(location, slashPos); mStartingHost = host.c_str(); mStartingPath = path.c_str(); mStartingPort = port; } return ERROR_IO; } void HTTPDataSource::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); } } HTTPDataSource::HTTPDataSource( const char *uri, const KeyedVector *headers) { CHECK(!strncasecmp("http://", uri, 7)); string host; string path; int port; char *slash = strchr(uri + 7, '/'); if (slash == NULL) { host = uri + 7; path = "/"; } else { host = string(uri + 7, slash - (uri + 7)); path = slash; } char *colon = strchr(host.c_str(), ':'); if (colon == NULL) { port = 80; } else { char *end; long tmp = strtol(colon + 1, &end, 10); CHECK(end > colon + 1); CHECK(tmp > 0 && tmp < 65536); port = tmp; host = string(host, 0, colon - host.c_str()); } mStartingHost = host.c_str(); mStartingPath = path.c_str(); mStartingPort = port; init(headers); } HTTPDataSource::HTTPDataSource( const char *_host, int port, const char *_path, const KeyedVector *headers) { mStartingHost = _host; mStartingPath = _path; mStartingPort = port; init(headers); } void HTTPDataSource::init(const KeyedVector *headers) { mState = DISCONNECTED; mHttp = new HTTPStream; initHeaders(headers); mBuffer = malloc(kBufferSize); mNumRetriesLeft = kMaxNumRetries; } status_t HTTPDataSource::connect() { { Mutex::Autolock autoLock(mStateLock); if (mState != DISCONNECTED) { return ERROR_ALREADY_CONNECTED; } mState = CONNECTING; } mBufferLength = 0; mBufferOffset = 0; mContentLengthValid = false; status_t err = connectWithRedirectsAndRange(0); if (err != OK) { Mutex::Autolock autoLock(mStateLock); if (mState != CONNECTING) { LOGV("connect() cancelled"); } mState = DISCONNECTED; return err; } string value; if (mHttp->find_header_value("Content-Length", &value)) { char *end; mContentLength = strtoull(value.c_str(), &end, 10); mContentLengthValid = true; } Mutex::Autolock autoLock(mStateLock); if (mState != CONNECTING) { // disconnect was called when we had just successfully connected. LOGV("connect() cancelled (we had just succeeded connecting)"); mHttp->disconnect(); return UNKNOWN_ERROR; } mState = CONNECTED; return OK; } void HTTPDataSource::disconnect() { Mutex::Autolock autoLock(mStateLock); if (mState == CONNECTING || mState == CONNECTED) { mHttp->disconnect(); mState = DISCONNECTED; } } status_t HTTPDataSource::initCheck() const { Mutex::Autolock autoLock(mStateLock); return (mState == CONNECTED) ? (status_t)OK : ERROR_NOT_CONNECTED; } status_t HTTPDataSource::getSize(off_t *size) { *size = 0; { Mutex::Autolock autoLock(mStateLock); if (mState != CONNECTED) { return ERROR_NOT_CONNECTED; } } if (!mContentLengthValid) { return ERROR_UNSUPPORTED; } *size = mContentLength; return OK; } HTTPDataSource::~HTTPDataSource() { disconnect(); delete mHttp; mHttp = NULL; free(mBuffer); mBuffer = NULL; } ssize_t HTTPDataSource::sendRangeRequest(size_t offset) { status_t err = connectWithRedirectsAndRange(offset); if (err != OK) { return err; } string value; if (!mHttp->find_header_value("Content-Length", &value)) { return kBufferSize; } char *end; unsigned long contentLength = strtoul(value.c_str(), &end, 10); return contentLength; } ssize_t HTTPDataSource::readAt(off_t offset, void *data, size_t size) { LOGV("readAt %ld, size %d", offset, size); rinse_repeat: { Mutex::Autolock autoLock(mStateLock); if (mState != CONNECTED) { return ERROR_NOT_CONNECTED; } } if (offset >= mBufferOffset && offset < (off_t)(mBufferOffset + mBufferLength)) { size_t num_bytes_available = mBufferLength - (offset - mBufferOffset); size_t copy = num_bytes_available; if (copy > size) { copy = size; } memcpy(data, (const char *)mBuffer + (offset - mBufferOffset), copy); if (copy < size) { LOGV("short read (1), returning %d vs. %d requested", copy, size); } return copy; } ssize_t contentLength = 0; if (offset != (off_t)(mBufferOffset + mBufferLength)) { LOGV("new range offset=%ld (old=%ld)", offset, mBufferOffset + mBufferLength); mHttp->disconnect(); contentLength = sendRangeRequest(offset); if (contentLength > kBufferSize) { contentLength = kBufferSize; } } else { contentLength = kBufferSize; } mBufferOffset = offset; if (mContentLengthValid && mBufferOffset + contentLength >= (off_t)mContentLength) { // If we never triggered a range request but know the content length, // make sure to not read more data than there could be, otherwise // we'd block indefinitely if the server doesn't close the connection. contentLength = mContentLength - mBufferOffset; } if (contentLength <= 0) { return contentLength; } ssize_t num_bytes_received = mHttp->receive(mBuffer, contentLength); if (num_bytes_received < 0 || (mContentLengthValid && num_bytes_received < contentLength)) { if (mNumRetriesLeft-- > 0) { mHttp->disconnect(); mBufferLength = 0; num_bytes_received = connectWithRedirectsAndRange(mBufferOffset); if (num_bytes_received == OK) { LOGI("retrying connection succeeded."); goto rinse_repeat; } LOGE("retrying connection failed"); } mBufferLength = 0; return num_bytes_received; } mBufferLength = (size_t)num_bytes_received; size_t copy = mBufferLength; if (copy > size) { copy = size; } memcpy(data, mBuffer, copy); return copy; } void HTTPDataSource::initHeaders( const KeyedVector *overrides) { mHeaders = String8(); mHeaders.append("User-Agent: stagefright/1.0 (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"); mHeaders.append(value); mHeaders.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"); mHeaders.append(line); } } uint32_t HTTPDataSource::flags() { uint32_t f = kWantsPrefetching; if (!strcasecmp(mStartingHost.string(), "localhost") || !strcmp(mStartingHost.string(), "127.0.0.1")) { f |= kStreamedFromLocalHost; } return f; } } // namespace android