blob: f72a6ccd22e01672f78a9fa835c9d2303332eb43 [file] [log] [blame]
/*
* 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 <utils/Log.h>
#include "include/stagefright_string.h"
#include "include/HTTPStream.h"
#include <stdlib.h>
#include <cutils/properties.h>
#include <media/stagefright/HTTPDataSource.h>
#include <media/stagefright/MediaDebug.h>
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<String8, String8> *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<String8, String8> *headers) {
mStartingHost = _host;
mStartingPath = _path;
mStartingPort = port;
init(headers);
}
void HTTPDataSource::init(const KeyedVector<String8, String8> *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<String8, String8> *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