| /* |
| * 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 "HTTPStream" |
| #include <utils/Log.h> |
| |
| #include "include/HTTPStream.h" |
| |
| #include <sys/socket.h> |
| |
| #include <arpa/inet.h> |
| #include <ctype.h> |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <netdb.h> |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <string.h> |
| #include <unistd.h> |
| |
| #include <media/stagefright/foundation/ADebug.h> |
| |
| #include <openssl/ssl.h> |
| |
| namespace android { |
| |
| // static |
| const char *HTTPStream::kStatusKey = ":status:"; // MUST be lowercase. |
| |
| HTTPStream::HTTPStream() |
| : mState(READY), |
| mSocket(-1), |
| mSSLContext(NULL), |
| mSSL(NULL) { |
| } |
| |
| HTTPStream::~HTTPStream() { |
| disconnect(); |
| |
| if (mSSLContext != NULL) { |
| SSL_CTX_free((SSL_CTX *)mSSLContext); |
| mSSLContext = NULL; |
| } |
| } |
| |
| static bool MakeSocketBlocking(int s, bool blocking) { |
| // Make socket non-blocking. |
| int flags = fcntl(s, F_GETFL, 0); |
| if (flags == -1) { |
| return false; |
| } |
| |
| if (blocking) { |
| flags &= ~O_NONBLOCK; |
| } else { |
| flags |= O_NONBLOCK; |
| } |
| |
| return fcntl(s, F_SETFL, flags) != -1; |
| } |
| |
| static status_t MyConnect( |
| int s, const struct sockaddr *addr, socklen_t addrlen) { |
| status_t result = UNKNOWN_ERROR; |
| |
| MakeSocketBlocking(s, false); |
| |
| if (connect(s, addr, addrlen) == 0) { |
| result = OK; |
| } else if (errno != EINPROGRESS) { |
| result = -errno; |
| } else { |
| for (;;) { |
| fd_set rs, ws; |
| FD_ZERO(&rs); |
| FD_ZERO(&ws); |
| FD_SET(s, &rs); |
| FD_SET(s, &ws); |
| |
| struct timeval tv; |
| tv.tv_sec = 0; |
| tv.tv_usec = 100000ll; |
| |
| int nfds = ::select(s + 1, &rs, &ws, NULL, &tv); |
| |
| if (nfds < 0) { |
| if (errno == EINTR) { |
| continue; |
| } |
| |
| result = -errno; |
| break; |
| } |
| |
| if (FD_ISSET(s, &ws) && !FD_ISSET(s, &rs)) { |
| result = OK; |
| break; |
| } |
| |
| if (FD_ISSET(s, &rs) || FD_ISSET(s, &ws)) { |
| // Get the pending error. |
| int error = 0; |
| socklen_t errorLen = sizeof(error); |
| if (getsockopt(s, SOL_SOCKET, SO_ERROR, &error, &errorLen) == -1) { |
| // Couldn't get the real error, so report why not. |
| result = -errno; |
| } else { |
| result = -error; |
| } |
| break; |
| } |
| |
| // Timeout expired. Try again. |
| } |
| } |
| |
| MakeSocketBlocking(s, true); |
| |
| return result; |
| } |
| |
| // Apparently under out linux closing a socket descriptor from one thread |
| // will not unblock a pending send/recv on that socket on another thread. |
| static ssize_t MySendReceive( |
| int s, void *data, size_t size, int flags, bool sendData) { |
| ssize_t result = 0; |
| |
| while (size > 0) { |
| fd_set rs, ws, es; |
| FD_ZERO(&rs); |
| FD_ZERO(&ws); |
| FD_ZERO(&es); |
| FD_SET(s, sendData ? &ws : &rs); |
| FD_SET(s, &es); |
| |
| struct timeval tv; |
| tv.tv_sec = 0; |
| tv.tv_usec = 100000ll; |
| |
| int nfds = ::select( |
| s + 1, |
| sendData ? NULL : &rs, |
| sendData ? &ws : NULL, |
| &es, |
| &tv); |
| |
| if (nfds < 0) { |
| if (errno == EINTR) { |
| continue; |
| } |
| |
| result = -errno; |
| break; |
| } else if (nfds == 0) { |
| // timeout |
| |
| continue; |
| } |
| |
| CHECK_EQ(nfds, 1); |
| |
| ssize_t nbytes = |
| sendData ? send(s, data, size, flags) : recv(s, data, size, flags); |
| |
| if (nbytes < 0) { |
| if (errno == EINTR) { |
| continue; |
| } |
| |
| result = -errno; |
| break; |
| } else if (nbytes == 0) { |
| result = 0; |
| break; |
| } |
| |
| data = (uint8_t *)data + nbytes; |
| size -= nbytes; |
| |
| result = nbytes; |
| break; |
| } |
| |
| return result; |
| } |
| |
| static ssize_t MySend(int s, const void *data, size_t size, int flags) { |
| return MySendReceive( |
| s, const_cast<void *>(data), size, flags, true /* sendData */); |
| } |
| |
| static ssize_t MyReceive(int s, void *data, size_t size, int flags) { |
| return MySendReceive(s, data, size, flags, false /* sendData */); |
| } |
| |
| status_t HTTPStream::connect(const char *server, int port, bool https) { |
| if (port < 0) { |
| port = https ? 443 : 80; |
| } |
| |
| Mutex::Autolock autoLock(mLock); |
| |
| status_t err = OK; |
| |
| if (mState == CONNECTED) { |
| return ERROR_ALREADY_CONNECTED; |
| } |
| |
| if (port < 0 || port > (int) USHRT_MAX) { |
| return UNKNOWN_ERROR; |
| } |
| |
| char service[sizeof("65536")]; |
| sprintf(service, "%d", port); |
| struct addrinfo hints, *ai; |
| memset(&hints, 0, sizeof(hints)); |
| hints.ai_flags = AI_ADDRCONFIG | AI_NUMERICSERV; |
| hints.ai_socktype = SOCK_STREAM; |
| |
| int ret = getaddrinfo(server, service, &hints, &ai); |
| if (ret) { |
| return ERROR_UNKNOWN_HOST; |
| } |
| |
| CHECK_EQ(mSocket, -1); |
| |
| mState = CONNECTING; |
| status_t res = -1; |
| struct addrinfo *tmp; |
| for (tmp = ai; tmp; tmp = tmp->ai_next) { |
| mSocket = socket(tmp->ai_family, tmp->ai_socktype, tmp->ai_protocol); |
| if (mSocket < 0) { |
| continue; |
| } |
| |
| setReceiveTimeout(30); // Time out reads after 30 secs by default. |
| |
| int s = mSocket; |
| |
| mLock.unlock(); |
| |
| res = MyConnect(s, tmp->ai_addr, tmp->ai_addrlen); |
| |
| mLock.lock(); |
| |
| if (mState != CONNECTING) { |
| close(s); |
| freeaddrinfo(ai); |
| return UNKNOWN_ERROR; |
| } |
| |
| if (res == OK) { |
| break; |
| } |
| |
| close(s); |
| } |
| |
| freeaddrinfo(ai); |
| |
| if (res != OK) { |
| close(mSocket); |
| mSocket = -1; |
| |
| mState = READY; |
| return res; |
| } |
| |
| if (https) { |
| CHECK(mSSL == NULL); |
| |
| if (mSSLContext == NULL) { |
| SSL_library_init(); |
| |
| mSSLContext = SSL_CTX_new(TLSv1_client_method()); |
| |
| if (mSSLContext == NULL) { |
| LOGE("failed to create SSL context"); |
| mState = READY; |
| return ERROR_IO; |
| } |
| } |
| |
| mSSL = SSL_new((SSL_CTX *)mSSLContext); |
| |
| if (mSSL == NULL) { |
| LOGE("failed to create SSL session"); |
| |
| mState = READY; |
| return ERROR_IO; |
| } |
| |
| int res = SSL_set_fd((SSL *)mSSL, mSocket); |
| |
| if (res == 1) { |
| res = SSL_connect((SSL *)mSSL); |
| } |
| |
| if (res != 1) { |
| SSL_free((SSL *)mSSL); |
| mSSL = NULL; |
| |
| LOGE("failed to connect over SSL"); |
| mState = READY; |
| |
| return ERROR_IO; |
| } |
| } |
| |
| mState = CONNECTED; |
| |
| return OK; |
| } |
| |
| status_t HTTPStream::disconnect() { |
| Mutex::Autolock autoLock(mLock); |
| |
| if (mState != CONNECTED && mState != CONNECTING) { |
| return ERROR_NOT_CONNECTED; |
| } |
| |
| if (mSSL != NULL) { |
| SSL_shutdown((SSL *)mSSL); |
| |
| SSL_free((SSL *)mSSL); |
| mSSL = NULL; |
| } |
| |
| CHECK(mSocket >= 0); |
| close(mSocket); |
| mSocket = -1; |
| |
| mState = READY; |
| |
| return OK; |
| } |
| |
| status_t HTTPStream::send(const char *data, size_t size) { |
| if (mState != CONNECTED) { |
| return ERROR_NOT_CONNECTED; |
| } |
| |
| while (size > 0) { |
| ssize_t n; |
| if (mSSL != NULL) { |
| n = SSL_write((SSL *)mSSL, data, size); |
| |
| if (n < 0) { |
| n = -SSL_get_error((SSL *)mSSL, n); |
| } |
| } else { |
| n = MySend(mSocket, data, size, 0); |
| } |
| |
| if (n < 0) { |
| disconnect(); |
| |
| return n; |
| } else if (n == 0) { |
| disconnect(); |
| |
| return ERROR_CONNECTION_LOST; |
| } |
| |
| size -= (size_t)n; |
| data += (size_t)n; |
| } |
| |
| return OK; |
| } |
| |
| status_t HTTPStream::send(const char *data) { |
| return send(data, strlen(data)); |
| } |
| |
| // A certain application spawns a local webserver that sends invalid responses, |
| // specifically it terminates header line with only a newline instead of the |
| // CRLF (carriage-return followed by newline) required by the HTTP specs. |
| // The workaround accepts both behaviours but could potentially break |
| // legitimate responses that use a single newline to "fold" headers, which is |
| // why it's not yet on by default. |
| #define WORKAROUND_FOR_MISSING_CR 1 |
| |
| status_t HTTPStream::receive_line(char *line, size_t size) { |
| if (mState != CONNECTED) { |
| return ERROR_NOT_CONNECTED; |
| } |
| |
| bool saw_CR = false; |
| size_t length = 0; |
| |
| for (;;) { |
| char c; |
| ssize_t n; |
| if (mSSL != NULL) { |
| n = SSL_read((SSL *)mSSL, &c, 1); |
| |
| if (n < 0) { |
| n = -SSL_get_error((SSL *)mSSL, n); |
| } |
| } else { |
| n = MyReceive(mSocket, &c, 1, 0); |
| } |
| |
| if (n < 0) { |
| disconnect(); |
| |
| return ERROR_IO; |
| } else if (n == 0) { |
| disconnect(); |
| |
| return ERROR_CONNECTION_LOST; |
| } |
| |
| #if WORKAROUND_FOR_MISSING_CR |
| if (c == '\n') { |
| // We have a complete line. |
| |
| line[saw_CR ? length - 1 : length] = '\0'; |
| return OK; |
| } |
| #else |
| if (saw_CR && c == '\n') { |
| // We have a complete line. |
| |
| line[length - 1] = '\0'; |
| return OK; |
| } |
| #endif |
| |
| saw_CR = (c == '\r'); |
| |
| if (length + 1 >= size) { |
| return ERROR_MALFORMED; |
| } |
| line[length++] = c; |
| } |
| } |
| |
| status_t HTTPStream::receive_header(int *http_status) { |
| *http_status = -1; |
| mHeaders.clear(); |
| |
| char line[2048]; |
| status_t err = receive_line(line, sizeof(line)); |
| if (err != OK) { |
| return err; |
| } |
| |
| mHeaders.add(AString(kStatusKey), AString(line)); |
| |
| char *spacePos = strchr(line, ' '); |
| if (spacePos == NULL) { |
| // Malformed response? |
| return UNKNOWN_ERROR; |
| } |
| |
| char *status_start = spacePos + 1; |
| char *status_end = status_start; |
| while (isdigit(*status_end)) { |
| ++status_end; |
| } |
| |
| if (status_end == status_start) { |
| // Malformed response, status missing? |
| return UNKNOWN_ERROR; |
| } |
| |
| memmove(line, status_start, status_end - status_start); |
| line[status_end - status_start] = '\0'; |
| |
| long tmp = strtol(line, NULL, 10); |
| if (tmp < 0 || tmp > 999) { |
| return UNKNOWN_ERROR; |
| } |
| |
| *http_status = (int)tmp; |
| |
| for (;;) { |
| err = receive_line(line, sizeof(line)); |
| if (err != OK) { |
| return err; |
| } |
| |
| if (*line == '\0') { |
| // Empty line signals the end of the header. |
| break; |
| } |
| |
| // puts(line); |
| |
| char *colonPos = strchr(line, ':'); |
| if (colonPos == NULL) { |
| AString key = line; |
| key.tolower(); |
| |
| mHeaders.add(key, AString()); |
| } else { |
| char *end_of_key = colonPos; |
| while (end_of_key > line && isspace(end_of_key[-1])) { |
| --end_of_key; |
| } |
| |
| char *start_of_value = colonPos + 1; |
| while (isspace(*start_of_value)) { |
| ++start_of_value; |
| } |
| |
| *end_of_key = '\0'; |
| |
| AString key = line; |
| key.tolower(); |
| |
| mHeaders.add(key, AString(start_of_value)); |
| } |
| } |
| |
| return OK; |
| } |
| |
| ssize_t HTTPStream::receive(void *data, size_t size) { |
| size_t total = 0; |
| while (total < size) { |
| ssize_t n; |
| if (mSSL != NULL) { |
| n = SSL_read((SSL *)mSSL, (char *)data + total, size - total); |
| |
| if (n < 0) { |
| n = -SSL_get_error((SSL *)mSSL, n); |
| } |
| } else { |
| n = MyReceive(mSocket, (char *)data + total, size - total, 0); |
| } |
| |
| if (n < 0) { |
| LOGE("recv failed, errno = %d (%s)", (int)n, strerror(-n)); |
| |
| disconnect(); |
| return (ssize_t)ERROR_IO; |
| } else if (n == 0) { |
| disconnect(); |
| |
| LOGE("recv failed, server is gone, total received: %d bytes", |
| total); |
| |
| return total == 0 ? (ssize_t)ERROR_CONNECTION_LOST : total; |
| } |
| |
| total += (size_t)n; |
| } |
| |
| return (ssize_t)total; |
| } |
| |
| bool HTTPStream::find_header_value(const AString &key, AString *value) const { |
| AString key_lower = key; |
| key_lower.tolower(); |
| |
| ssize_t index = mHeaders.indexOfKey(key_lower); |
| if (index < 0) { |
| value->clear(); |
| return false; |
| } |
| |
| *value = mHeaders.valueAt(index); |
| |
| return true; |
| } |
| |
| void HTTPStream::setReceiveTimeout(int seconds) { |
| if (seconds < 0) { |
| // Disable the timeout. |
| seconds = 0; |
| } |
| |
| struct timeval tv; |
| tv.tv_usec = 0; |
| tv.tv_sec = seconds; |
| CHECK_EQ(0, setsockopt(mSocket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv))); |
| } |
| |
| } // namespace android |
| |