/* * 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 #include "include/HTTPStream.h" #include #include #include #include #include #include #include #include #include #include namespace android { // static const char *HTTPStream::kStatusKey = ":status:"; // MUST be lowercase. HTTPStream::HTTPStream() : mState(READY), mSocket(-1) { } HTTPStream::~HTTPStream() { disconnect(); } status_t HTTPStream::connect(const char *server, int port) { Mutex::Autolock autoLock(mLock); status_t err = OK; if (mState == CONNECTED) { return ERROR_ALREADY_CONNECTED; } struct hostent *ent = gethostbyname(server); if (ent == NULL) { return ERROR_UNKNOWN_HOST; } CHECK_EQ(mSocket, -1); mSocket = socket(AF_INET, SOCK_STREAM, 0); if (mSocket < 0) { return UNKNOWN_ERROR; } setReceiveTimeout(30); // Time out reads after 30 secs by default mState = CONNECTING; int s = mSocket; mLock.unlock(); struct sockaddr_in addr; addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr; memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); int res = ::connect(s, (const struct sockaddr *)&addr, sizeof(addr)); mLock.lock(); if (mState != CONNECTING) { return UNKNOWN_ERROR; } if (res < 0) { close(mSocket); mSocket = -1; mState = READY; return UNKNOWN_ERROR; } mState = CONNECTED; return OK; } status_t HTTPStream::disconnect() { Mutex::Autolock autoLock(mLock); if (mState != CONNECTED && mState != CONNECTING) { return ERROR_NOT_CONNECTED; } 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 = ::send(mSocket, data, size, 0); if (n < 0) { if (errno == EINTR) { continue; } disconnect(); return ERROR_IO; } 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 = recv(mSocket, &c, 1, 0); if (n < 0) { if (errno == EINTR) { continue; } 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 = recv(mSocket, (char *)data + total, size - total, 0); if (n < 0) { if (errno == EINTR) { continue; } LOGE("recv failed, errno = %d (%s)", errno, strerror(errno)); 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