Home | History | Annotate | Download | only in libstagefright
      1 //#define LOG_NDEBUG 0
      2 #define LOG_TAG "NuHTTPDataSource"
      3 #include <utils/Log.h>
      4 
      5 #include "include/NuHTTPDataSource.h"
      6 
      7 #include <cutils/properties.h>
      8 #include <media/stagefright/MediaDebug.h>
      9 #include <media/stagefright/MediaErrors.h>
     10 
     11 namespace android {
     12 
     13 static bool ParseSingleUnsignedLong(
     14         const char *from, unsigned long *x) {
     15     char *end;
     16     *x = strtoul(from, &end, 10);
     17 
     18     if (end == from || *end != '\0') {
     19         return false;
     20     }
     21 
     22     return true;
     23 }
     24 
     25 static bool ParseURL(
     26         const char *url, String8 *host, unsigned *port, String8 *path) {
     27     host->setTo("");
     28     *port = 0;
     29     path->setTo("");
     30 
     31     if (strncasecmp("http://", url, 7)) {
     32         return false;
     33     }
     34 
     35     const char *slashPos = strchr(&url[7], '/');
     36 
     37     if (slashPos == NULL) {
     38         host->setTo(&url[7]);
     39         path->setTo("/");
     40     } else {
     41         host->setTo(&url[7], slashPos - &url[7]);
     42         path->setTo(slashPos);
     43     }
     44 
     45     char *colonPos = strchr(host->string(), ':');
     46 
     47     if (colonPos != NULL) {
     48         unsigned long x;
     49         if (!ParseSingleUnsignedLong(colonPos + 1, &x) || x >= 65536) {
     50             return false;
     51         }
     52 
     53         *port = x;
     54 
     55         size_t colonOffset = colonPos - host->string();
     56         String8 tmp(host->string(), colonOffset);
     57         *host = tmp;
     58     } else {
     59         *port = 80;
     60     }
     61 
     62     return true;
     63 }
     64 
     65 NuHTTPDataSource::NuHTTPDataSource()
     66     : mState(DISCONNECTED),
     67       mPort(0),
     68       mOffset(0),
     69       mContentLength(0),
     70       mContentLengthValid(false) {
     71 }
     72 
     73 NuHTTPDataSource::~NuHTTPDataSource() {
     74 }
     75 
     76 status_t NuHTTPDataSource::connect(
     77         const char *uri,
     78         const KeyedVector<String8, String8> *overrides,
     79         off_t offset) {
     80     String8 headers;
     81     MakeFullHeaders(overrides, &headers);
     82 
     83     return connect(uri, headers, offset);
     84 }
     85 
     86 status_t NuHTTPDataSource::connect(
     87         const char *uri,
     88         const String8 &headers,
     89         off_t offset) {
     90     String8 host, path;
     91     unsigned port;
     92     if (!ParseURL(uri, &host, &port, &path)) {
     93         return ERROR_MALFORMED;
     94     }
     95 
     96     return connect(host, port, path, headers, offset);
     97 }
     98 
     99 static bool IsRedirectStatusCode(int httpStatus) {
    100     return httpStatus == 301 || httpStatus == 302
    101         || httpStatus == 303 || httpStatus == 307;
    102 }
    103 
    104 status_t NuHTTPDataSource::connect(
    105         const char *host, unsigned port, const char *path,
    106         const String8 &headers,
    107         off_t offset) {
    108     LOGI("connect to %s:%u%s @%ld", host, port, path, offset);
    109 
    110     bool needsToReconnect = true;
    111 
    112     if (mState == CONNECTED && host == mHost && port == mPort
    113             && offset == mOffset) {
    114         if (mContentLengthValid && mOffset == mContentLength) {
    115             LOGI("Didn't have to reconnect, old one's still good.");
    116             needsToReconnect = false;
    117         }
    118     }
    119 
    120     mHost = host;
    121     mPort = port;
    122     mPath = path;
    123     mHeaders = headers;
    124 
    125     status_t err = OK;
    126 
    127     mState = CONNECTING;
    128 
    129     if (needsToReconnect) {
    130         mHTTP.disconnect();
    131         err = mHTTP.connect(host, port);
    132     }
    133 
    134     if (err != OK) {
    135         mState = DISCONNECTED;
    136     } else if (mState != CONNECTING) {
    137         err = UNKNOWN_ERROR;
    138     } else {
    139         mState = CONNECTED;
    140 
    141         mOffset = offset;
    142         mContentLength = 0;
    143         mContentLengthValid = false;
    144 
    145         String8 request("GET ");
    146         request.append(mPath);
    147         request.append(" HTTP/1.1\r\n");
    148         request.append("Host: ");
    149         request.append(mHost);
    150         request.append("\r\n");
    151 
    152         if (offset != 0) {
    153             char rangeHeader[128];
    154             sprintf(rangeHeader, "Range: bytes=%ld-\r\n", offset);
    155             request.append(rangeHeader);
    156         }
    157 
    158         request.append(mHeaders);
    159         request.append("\r\n");
    160 
    161         int httpStatus;
    162         if ((err = mHTTP.send(request.string(), request.size())) != OK
    163                 || (err = mHTTP.receive_header(&httpStatus)) != OK) {
    164             mHTTP.disconnect();
    165             mState = DISCONNECTED;
    166             return err;
    167         }
    168 
    169         if (IsRedirectStatusCode(httpStatus)) {
    170             string value;
    171             CHECK(mHTTP.find_header_value("Location", &value));
    172 
    173             mState = DISCONNECTED;
    174 
    175             mHTTP.disconnect();
    176 
    177             return connect(value.c_str(), headers, offset);
    178         }
    179 
    180         if (httpStatus < 200 || httpStatus >= 300) {
    181             mState = DISCONNECTED;
    182             mHTTP.disconnect();
    183 
    184             return ERROR_IO;
    185         }
    186 
    187         applyTimeoutResponse();
    188 
    189         if (offset == 0) {
    190             string value;
    191             unsigned long x;
    192             if (mHTTP.find_header_value(string("Content-Length"), &value)
    193                     && ParseSingleUnsignedLong(value.c_str(), &x)) {
    194                 mContentLength = (off_t)x;
    195                 mContentLengthValid = true;
    196             }
    197         } else {
    198             string value;
    199             unsigned long x;
    200             if (mHTTP.find_header_value(string("Content-Range"), &value)) {
    201                 const char *slashPos = strchr(value.c_str(), '/');
    202                 if (slashPos != NULL
    203                         && ParseSingleUnsignedLong(slashPos + 1, &x)) {
    204                     mContentLength = x;
    205                     mContentLengthValid = true;
    206                 }
    207             }
    208         }
    209     }
    210 
    211     return err;
    212 }
    213 
    214 void NuHTTPDataSource::disconnect() {
    215     if (mState == CONNECTING || mState == CONNECTED) {
    216         mHTTP.disconnect();
    217     }
    218     mState = DISCONNECTED;
    219 }
    220 
    221 status_t NuHTTPDataSource::initCheck() const {
    222     return mState == CONNECTED ? OK : NO_INIT;
    223 }
    224 
    225 ssize_t NuHTTPDataSource::readAt(off_t offset, void *data, size_t size) {
    226     LOGV("readAt offset %ld, size %d", offset, size);
    227 
    228     Mutex::Autolock autoLock(mLock);
    229 
    230     if (offset != mOffset) {
    231         String8 host = mHost;
    232         String8 path = mPath;
    233         String8 headers = mHeaders;
    234         status_t err = connect(host, mPort, path, headers, offset);
    235 
    236         if (err != OK) {
    237             return err;
    238         }
    239     }
    240 
    241     if (mContentLengthValid) {
    242         size_t avail =
    243             (offset >= mContentLength) ? 0 : mContentLength - offset;
    244 
    245         if (size > avail) {
    246             size = avail;
    247         }
    248     }
    249 
    250     size_t numBytesRead = 0;
    251     while (numBytesRead < size) {
    252         ssize_t n =
    253             mHTTP.receive((uint8_t *)data + numBytesRead, size - numBytesRead);
    254 
    255         if (n < 0) {
    256             return n;
    257         }
    258 
    259         numBytesRead += (size_t)n;
    260 
    261         if (n == 0) {
    262             if (mContentLengthValid) {
    263                 // We know the content length and made sure not to read beyond
    264                 // it and yet the server closed the connection on us.
    265                 return ERROR_IO;
    266             }
    267 
    268             break;
    269         }
    270     }
    271 
    272     mOffset += numBytesRead;
    273 
    274     return numBytesRead;
    275 }
    276 
    277 status_t NuHTTPDataSource::getSize(off_t *size) {
    278     *size = 0;
    279 
    280     if (mState != CONNECTED) {
    281         return ERROR_IO;
    282     }
    283 
    284     if (mContentLengthValid) {
    285         *size = mContentLength;
    286         return OK;
    287     }
    288 
    289     return ERROR_UNSUPPORTED;
    290 }
    291 
    292 uint32_t NuHTTPDataSource::flags() {
    293     return kWantsPrefetching;
    294 }
    295 
    296 // static
    297 void NuHTTPDataSource::MakeFullHeaders(
    298         const KeyedVector<String8, String8> *overrides, String8 *headers) {
    299     headers->setTo("");
    300 
    301     headers->append("User-Agent: stagefright/1.1 (Linux;Android ");
    302 
    303 #if (PROPERTY_VALUE_MAX < 8)
    304 #error "PROPERTY_VALUE_MAX must be at least 8"
    305 #endif
    306 
    307     char value[PROPERTY_VALUE_MAX];
    308     property_get("ro.build.version.release", value, "Unknown");
    309     headers->append(value);
    310     headers->append(")\r\n");
    311 
    312     if (overrides == NULL) {
    313         return;
    314     }
    315 
    316     for (size_t i = 0; i < overrides->size(); ++i) {
    317         String8 line;
    318         line.append(overrides->keyAt(i));
    319         line.append(": ");
    320         line.append(overrides->valueAt(i));
    321         line.append("\r\n");
    322 
    323         headers->append(line);
    324     }
    325 }
    326 
    327 void NuHTTPDataSource::applyTimeoutResponse() {
    328     string timeout;
    329     if (mHTTP.find_header_value("X-SocketTimeout", &timeout)) {
    330         const char *s = timeout.c_str();
    331         char *end;
    332         long tmp = strtol(s, &end, 10);
    333         if (end == s || *end != '\0') {
    334             LOGW("Illegal X-SocketTimeout value given.");
    335             return;
    336         }
    337 
    338         LOGI("overriding default timeout, new timeout is %ld seconds", tmp);
    339         mHTTP.setReceiveTimeout(tmp);
    340     }
    341 }
    342 
    343 }  // namespace android
    344