blob: 06b73784582a734bf798b0e3f6b54b46622f3d54 [file] [log] [blame]
/*
* libjingle
* Copyright 2004--2005, Google Inc.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice,
* this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation
* and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote products
* derived from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef OSX
#include <errno.h>
#endif
#ifdef WIN32
#include "talk/base/win32.h"
#else // !WIN32
#define SEC_E_CERT_EXPIRED (-2146893016)
#endif // !WIN32
#include "talk/base/common.h"
#include "talk/base/httpbase.h"
#include "talk/base/logging.h"
#include "talk/base/socket.h"
#include "talk/base/stringutils.h"
namespace talk_base {
//////////////////////////////////////////////////////////////////////
// Helpers
//////////////////////////////////////////////////////////////////////
bool MatchHeader(const char* str, size_t len, HttpHeader header) {
const char* const header_str = ToString(header);
const size_t header_len = strlen(header_str);
return (len == header_len) && (_strnicmp(str, header_str, header_len) == 0);
}
//////////////////////////////////////////////////////////////////////
// HttpParser
//////////////////////////////////////////////////////////////////////
HttpParser::HttpParser() {
reset();
}
HttpParser::~HttpParser() {
}
void
HttpParser::reset() {
state_ = ST_LEADER;
chunked_ = false;
data_size_ = SIZE_UNKNOWN;
}
bool
HttpParser::process(const char* buffer, size_t len, size_t& processed,
HttpError& err) {
processed = 0;
err = HE_NONE;
if (state_ >= ST_COMPLETE) {
ASSERT(false);
return false;
}
while (true) {
if (state_ < ST_DATA) {
size_t pos = processed;
while ((pos < len) && (buffer[pos] != '\n')) {
pos += 1;
}
if (pos >= len) {
break; // don't have a full header
}
const char* line = buffer + processed;
size_t len = (pos - processed);
processed = pos + 1;
while ((len > 0) && isspace(static_cast<unsigned char>(line[len-1]))) {
len -= 1;
}
if (!process_line(line, len, err)) {
return false; // no more processing
}
} else if (data_size_ == 0) {
if (chunked_) {
state_ = ST_CHUNKTERM;
} else {
return false;
}
} else {
size_t available = len - processed;
if (available <= 0) {
break; // no more data
}
if ((data_size_ != SIZE_UNKNOWN) && (available > data_size_)) {
available = data_size_;
}
size_t read = 0;
err = onHttpRecvData(buffer + processed, available, read);
if (err != HE_NONE) {
return false; // error occurred
}
processed += read;
if (data_size_ != SIZE_UNKNOWN) {
data_size_ -= read;
}
}
}
return true;
}
bool
HttpParser::process_line(const char* line, size_t len, HttpError& err) {
switch (state_) {
case ST_LEADER:
state_ = ST_HEADERS;
err = onHttpRecvLeader(line, len);
break;
case ST_HEADERS:
if (len > 0) {
const char* value = strchrn(line, len, ':');
if (!value) {
err = HE_PROTOCOL;
break;
}
size_t nlen = (value - line);
const char* eol = line + len;
do {
value += 1;
} while ((value < eol) && isspace(static_cast<unsigned char>(*value)));
size_t vlen = eol - value;
if (MatchHeader(line, nlen, HH_CONTENT_LENGTH)) {
if (sscanf(value, "%d", &data_size_) != 1) {
err = HE_PROTOCOL;
break;
}
} else if (MatchHeader(line, nlen, HH_TRANSFER_ENCODING)) {
if ((vlen == 7) && (_strnicmp(value, "chunked", 7) == 0)) {
chunked_ = true;
} else if ((vlen == 8) && (_strnicmp(value, "identity", 8) == 0)) {
chunked_ = false;
} else {
err = HE_PROTOCOL;
break;
}
}
err = onHttpRecvHeader(line, nlen, value, vlen);
} else {
state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
err = onHttpRecvHeaderComplete(chunked_, data_size_);
}
break;
case ST_CHUNKSIZE:
if (len > 0) {
char* ptr = NULL;
data_size_ = strtoul(line, &ptr, 16);
if (ptr != line + len) {
err = HE_PROTOCOL;
break;
}
state_ = (data_size_ == 0) ? ST_TRAILERS : ST_DATA;
} else {
err = HE_PROTOCOL;
}
break;
case ST_CHUNKTERM:
if (len > 0) {
err = HE_PROTOCOL;
} else {
state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
}
break;
case ST_TRAILERS:
if (len == 0) {
return false;
}
// err = onHttpRecvTrailer();
break;
default:
break;
}
return (err == HE_NONE);
}
void
HttpParser::end_of_input() {
if ((state_ == ST_DATA) && (data_size_ == SIZE_UNKNOWN)) {
complete(HE_NONE);
} else {
complete(HE_DISCONNECTED);
}
}
void
HttpParser::complete(HttpError err) {
if (state_ < ST_COMPLETE) {
state_ = ST_COMPLETE;
onHttpRecvComplete(err);
}
}
//////////////////////////////////////////////////////////////////////
// HttpBase
//////////////////////////////////////////////////////////////////////
HttpBase::HttpBase() : mode_(HM_NONE), data_(NULL), notify_(NULL),
stream_(NULL) {
}
HttpBase::~HttpBase() {
}
bool
HttpBase::isConnected() const {
return (stream_ != NULL) && (stream_->GetState() == SS_OPEN);
}
bool
HttpBase::attach(StreamInterface* stream) {
if ((mode_ != HM_NONE) || (stream_ != NULL) || (stream == NULL)) {
ASSERT(false);
return false;
}
stream_ = stream;
stream_->SignalEvent.connect(this, &HttpBase::OnEvent);
mode_ = (stream_->GetState() == SS_OPENING) ? HM_CONNECT : HM_NONE;
return true;
}
StreamInterface*
HttpBase::detach() {
if (mode_ != HM_NONE) {
ASSERT(false);
return NULL;
}
StreamInterface* stream = stream_;
stream_ = NULL;
if (stream) {
stream->SignalEvent.disconnect(this);
}
return stream;
}
/*
bool
HttpBase::accept(PNSocket& socket) {
if (mode_ != HM_NONE) {
ASSERT(false);
return false;
}
return socket.accept(stream_);
}
void
HttpBase::connect(const SocketAddress& addr) {
if (mode_ != HM_NONE) {
ASSERT(false);
return;
}
mode_ = HM_CONNECT;
SocketAddress local;
if (!stream_.connect(local, addr) && !stream_.isBlocking()) {
onSocketConnect(&stream_, stream_.getError());
}
}
*/
void
HttpBase::send(HttpData* data) {
if (mode_ != HM_NONE) {
ASSERT(false);
return;
} else if (!isConnected()) {
OnEvent(stream_, SE_CLOSE, HE_DISCONNECTED);
return;
}
mode_ = HM_SEND;
data_ = data;
len_ = 0;
ignore_data_ = chunk_data_ = false;
std::string encoding;
if (data_->hasHeader(HH_TRANSFER_ENCODING, &encoding)
&& (encoding == "chunked")) {
chunk_data_ = true;
}
len_ = data_->formatLeader(buffer_, sizeof(buffer_));
len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
header_ = data_->begin();
queue_headers();
OnEvent(stream_, SE_WRITE, 0);
}
void
HttpBase::recv(HttpData* data) {
if (mode_ != HM_NONE) {
ASSERT(false);
return;
} else if (!isConnected()) {
OnEvent(stream_, SE_CLOSE, HE_DISCONNECTED);
return;
}
mode_ = HM_RECV;
data_ = data;
len_ = 0;
ignore_data_ = chunk_data_ = false;
reset();
OnEvent(stream_, SE_READ, 0);
}
void
HttpBase::abort(HttpError err) {
if (mode_ != HM_NONE) {
if (stream_ != NULL) {
stream_->Close();
}
do_complete(err);
}
}
void
HttpBase::flush_data() {
while (true) {
for (size_t start = 0; start < len_; ) {
size_t written;
int error;
StreamResult result = stream_->Write(buffer_ + start, len_ - start,
&written, &error);
if (result == SR_SUCCESS) {
//LOG_F(LS_INFO) << "wrote " << res << " bytes";
start += written;
continue;
} else if (result == SR_BLOCK) {
//LOG_F(LS_INFO) << "blocking";
len_ -= start;
memmove(buffer_, buffer_ + start, len_);
return;
} else {
ASSERT(result == SR_ERROR);
LOG_F(LS_ERROR) << "error";
OnEvent(stream_, SE_CLOSE, error);
return;
}
}
len_ = 0;
// Check for more headers
if (header_ != data_->end()) {
queue_headers();
continue;
}
// Check for document data
if (!data_->document.get())
break;
size_t offset = 0, reserve = 0;
if (chunk_data_) {
// Reserve 10 characters at the start for 8-byte hex value and \r\n
offset = 10;
// ... and 2 characters at the end for \r\n
reserve = offset + 2;
ASSERT(reserve < sizeof(buffer_));
}
int error = 0;
StreamResult result = data_->document->Read(buffer_ + offset,
sizeof(buffer_) - reserve,
&len_, &error);
if (result == SR_SUCCESS) {
if (!chunk_data_)
continue;
// Prepend the length and append \r\n
sprintfn(buffer_, offset, "%.*x", (offset - 2), len_);
memcpy(buffer_ + offset - 2, "\r\n", 2);
memcpy(buffer_ + offset + len_, "\r\n", 2);
ASSERT(len_ + reserve <= sizeof(buffer_));
len_ += reserve;
} else if (result == SR_EOS) {
if (!chunk_data_)
break;
// Append the empty chunk and empty trailers, then turn off chunking.
len_ = sprintfn(buffer_, sizeof(buffer_), "0\r\n\r\n");
chunk_data_ = false;
} else {
LOG_F(LS_ERROR) << "Read error: " << error;
do_complete(HE_STREAM);
return;
}
}
do_complete();
}
void
HttpBase::queue_headers() {
while (header_ != data_->end()) {
size_t len = sprintfn(buffer_ + len_, sizeof(buffer_) - len_,
"%.*s: %.*s\r\n",
header_->first.size(), header_->first.data(),
header_->second.size(), header_->second.data());
if (len_ + len < sizeof(buffer_) - 3) {
len_ += len;
++header_;
} else if (len_ == 0) {
LOG(WARNING) << "discarding header that is too long: " << header_->first;
++header_;
} else {
break;
}
}
if (header_ == data_->end()) {
len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
}
}
void
HttpBase::do_complete(HttpError err) {
ASSERT(mode_ != HM_NONE);
HttpMode mode = mode_;
mode_ = HM_NONE;
data_ = NULL;
if (notify_) {
notify_->onHttpComplete(mode, err);
}
}
void
HttpBase::OnEvent(StreamInterface* stream, int events, int error) {
if ((events & SE_OPEN) && (mode_ == HM_CONNECT)) {
do_complete();
return;
}
if ((events & SE_WRITE) && (mode_ == HM_SEND)) {
flush_data();
return;
}
if ((events & SE_READ) && (mode_ == HM_RECV)) {
// Do to the latency between receiving read notifications from
// pseudotcpchannel, we rely on repeated calls to read in order to acheive
// ideal throughput. The number of reads is limited to prevent starving
// the caller.
size_t loop_count = 0;
const size_t kMaxReadCount = 20;
while (true) {
if (len_ >= sizeof(buffer_)) {
do_complete(HE_OVERFLOW);
return;
}
size_t read;
int error;
StreamResult result = stream_->Read(buffer_ + len_,
sizeof(buffer_) - len_,
&read, &error);
if ((result == SR_BLOCK) || (result == SR_EOS))
return;
if (result == SR_ERROR) {
OnEvent(stream_, SE_CLOSE, error);
return;
}
ASSERT(result == SR_SUCCESS);
//LOG(INFO) << "HttpBase << " << std::string(buffer_ + len_, res);
len_ += read;
HttpError herr;
bool more = process(buffer_, len_, read, herr);
len_ -= read;
memcpy(buffer_, buffer_ + read, len_);
if (!more) {
complete(herr);
return;
}
if (++loop_count > kMaxReadCount) {
LOG_F(LS_WARNING) << "danger of starvation";
break;
}
}
return;
}
if ((events & SE_CLOSE) == 0)
return;
if (stream_ != NULL) {
stream_->Close();
}
HttpError herr;
// TODO: Pass through errors instead of translating them?
if (error == 0) {
herr = HE_DISCONNECTED;
} else if (error == SOCKET_EACCES) {
herr = HE_AUTH;
} else if (error == SEC_E_CERT_EXPIRED) {
herr = HE_CERTIFICATE_EXPIRED;
} else {
LOG_F(LS_ERROR) << "SE_CLOSE error: " << error;
herr = HE_SOCKET;
}
if ((mode_ == HM_RECV) && (error == HE_NONE)) {
end_of_input();
} else if (mode_ != HM_NONE) {
do_complete(mkerr(herr, HE_DISCONNECTED));
} else if (notify_) {
notify_->onHttpClosed(mkerr(herr, HE_DISCONNECTED));
}
}
//
// HttpParser Implementation
//
HttpError
HttpBase::onHttpRecvLeader(const char* line, size_t len) {
return data_->parseLeader(line, len);
}
HttpError
HttpBase::onHttpRecvHeader(const char* name, size_t nlen, const char* value,
size_t vlen) {
std::string sname(name, nlen), svalue(value, vlen);
data_->addHeader(sname, svalue);
//LOG(INFO) << sname << ": " << svalue;
return HE_NONE;
}
HttpError
HttpBase::onHttpRecvHeaderComplete(bool chunked, size_t& data_size) {
return notify_ ? notify_->onHttpHeaderComplete(chunked, data_size) : HE_NONE;
}
HttpError
HttpBase::onHttpRecvData(const char* data, size_t len, size_t& read) {
if (ignore_data_ || !data_->document.get()) {
read = len;
return HE_NONE;
}
int error = 0;
switch (data_->document->Write(data, len, &read, &error)) {
case SR_SUCCESS:
return HE_NONE;
case SR_EOS:
case SR_BLOCK:
LOG_F(LS_ERROR) << "Write EOS or block";
return HE_STREAM;
}
LOG_F(LS_ERROR) << "Write error: " << error;
return HE_STREAM;
}
void
HttpBase::onHttpRecvComplete(HttpError err) {
do_complete(err);
}
} // namespace talk_base