/* * 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 #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(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(*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