Revert of Removing HTTPS and SOCKS proxy server code. (patchset #2 id:20001 of https://codereview.webrtc.org/2731673002/ )

Also needed to revert these CLs, which removed code used by the
code being un-removed:
https://codereview.webrtc.org/2745523004
https://codereview.webrtc.org/2754033003
https://codereview.webrtc.org/2758943002

Reason for revert:
This code is still being used by native application developers, so we should send a PSA announcing the deprecation and suggest an alternative before removing it.

Original issue's description:
> Removing HTTPS and SOCKS proxy server code.
>
> This isn't used any more so there's no point in maintaining it.
>
> BUG=None
>
> Review-Url: https://codereview.webrtc.org/2731673002
> Cr-Commit-Position: refs/heads/master@{#17016}
> Committed: a1991c5175

TBR=pthatcher@webrtc.org
NOPRESUBMIT=true
NOTRY=true
BUG=None

Review-Url: https://codereview.webrtc.org/2766063005
Cr-Commit-Position: refs/heads/master@{#17369}
This commit is contained in:
deadbeef
2017-03-23 15:45:49 -07:00
committed by Commit bot
parent dadb4dc3c9
commit f137e97adb
30 changed files with 5470 additions and 25 deletions

View File

@ -49,6 +49,12 @@ char kLSanDefaultSuppressions[] =
// pre-existing leaks. // pre-existing leaks.
// rtc_unittest // rtc_unittest
// https://code.google.com/p/webrtc/issues/detail?id=3827 for details.
"leak:rtc::unstarted_task_test_DoNotDeleteTask2_Test::TestBody\n"
"leak:rtc::HttpServer::HandleConnection\n"
"leak:rtc::HttpServer::Connection::onHttpHeaderComplete\n"
"leak:rtc::HttpResponseData::set_success\n"
"leak:rtc::HttpData::changeHeader\n"
// https://code.google.com/p/webrtc/issues/detail?id=4149 for details. // https://code.google.com/p/webrtc/issues/detail?id=4149 for details.
"leak:StartDNSLookup\n" "leak:StartDNSLookup\n"
// https://code.google.com/p/webrtc/issues/detail?id=2527 // https://code.google.com/p/webrtc/issues/detail?id=2527

View File

@ -31,6 +31,14 @@
#----------------------------------------------------------------------- #-----------------------------------------------------------------------
# webrtc stuff # webrtc stuff
{
bug_6784
Memcheck:Leak
fun:_Znw*
fun:_ZN3rtc10HttpServer16HandleConnectionEPNS_15StreamInterfaceE
fun:_ZN3rtc12_GLOBAL__N_122CreateClientConnectionERNS_10HttpServerERNS0_17HttpServerMonitorEb
fun:_ZN3rtc47HttpServer_SignalsCloseAfterForcedCloseAll_Test8TestBodyEv
}
{ {
bug_6773_3 bug_6773_3
Memcheck:Uninitialized Memcheck:Uninitialized
@ -154,6 +162,13 @@
fun:BIO_new_mem_buf fun:BIO_new_mem_buf
fun:_ZN9rtc15OpenSSLIdentity14FromPEMStringsERKSsS2_ fun:_ZN9rtc15OpenSSLIdentity14FromPEMStringsERKSsS2_
} }
{
SignalsCloseAfterForcedCloseAll
Memcheck:Leak
fun:_Znw*
fun:_ZN3rtc10HttpServer10Connection12BeginProcessEPNS_15StreamInterfaceE
...
}
{ {
SignalsCloseAfterForcedCloseAll2 SignalsCloseAfterForcedCloseAll2
Memcheck:Leak Memcheck:Leak
@ -468,6 +483,21 @@
fun:_Znw* fun:_Znw*
fun:_ZN6webrtc51AudioEncoderCopyRedDeathTest_NullSpeechEncoder_Test8TestBodyEv fun:_ZN6webrtc51AudioEncoderCopyRedDeathTest_NullSpeechEncoder_Test8TestBodyEv
} }
{
bug_5988
Memcheck:Leak
fun:_Znw*
fun:_ZNSt8_Rb_treeISsSt4pairIKSsSsESt10_Select1stIS2_EN3rtc5ilessESaIS2_EE15_M_insert_equalIS2_EESt17_Rb_tree_iteratorIS2_EOT_
fun:_ZN3rtc8HttpData12changeHeaderERKSsS2_NS0_13HeaderCombineE
fun:_ZN3rtc16HttpResponseData9set_errorEj
fun:_ZN3rtc12_GLOBAL__N_117HttpServerMonitor9OnRequestEPNS_10HttpServerEPNS_21HttpServerTransactionE
fun:_ZN3rtc10HttpServer10Connection14onHttpCompleteENS_8HttpModeENS_9HttpErrorE
fun:_ZN3rtc8HttpBase10OnCompleteENS_9HttpErrorE
fun:_ZN3rtc8HttpBase17OnHttpStreamEventEPNS_15StreamInterfaceEii
fun:_ZN7testing12StreamSource11QueueStringEPKc
fun:_ZN3rtc12_GLOBAL__N_122CreateClientConnectionERNS_10HttpServerERNS0_17HttpServerMonitorEb
fun:_ZN3rtc47HttpServer_SignalsCloseAfterForcedCloseAll_Test8TestBodyEv
}
{ {
bug_5989 bug_5989
Memcheck:Unaddressable Memcheck:Unaddressable

View File

@ -380,6 +380,8 @@ rtc_static_library("rtc_base") {
"asyncudpsocket.h", "asyncudpsocket.h",
"crc32.cc", "crc32.cc",
"crc32.h", "crc32.h",
"cryptstring.cc",
"cryptstring.h",
"filerotatingstream.cc", "filerotatingstream.cc",
"filerotatingstream.h", "filerotatingstream.h",
"fileutils.cc", "fileutils.cc",
@ -387,6 +389,11 @@ rtc_static_library("rtc_base") {
"gunit_prod.h", "gunit_prod.h",
"helpers.cc", "helpers.cc",
"helpers.h", "helpers.h",
"httpbase.cc",
"httpbase.h",
"httpcommon-inl.h",
"httpcommon.cc",
"httpcommon.h",
"ipaddress.cc", "ipaddress.cc",
"ipaddress.h", "ipaddress.h",
"messagedigest.cc", "messagedigest.cc",
@ -414,6 +421,7 @@ rtc_static_library("rtc_base") {
"opensslstreamadapter.h", "opensslstreamadapter.h",
"physicalsocketserver.cc", "physicalsocketserver.cc",
"physicalsocketserver.h", "physicalsocketserver.h",
"proxyinfo.cc",
"proxyinfo.h", "proxyinfo.h",
"ratelimiter.cc", "ratelimiter.cc",
"ratelimiter.h", "ratelimiter.h",
@ -675,6 +683,8 @@ if (rtc_include_tests) {
"firewallsocketserver.cc", "firewallsocketserver.cc",
"firewallsocketserver.h", "firewallsocketserver.h",
"gunit.h", "gunit.h",
"httpserver.cc",
"httpserver.h",
"memory_usage.cc", "memory_usage.cc",
"memory_usage.h", "memory_usage.h",
"natserver.cc", "natserver.cc",
@ -828,6 +838,9 @@ if (rtc_include_tests) {
"crc32_unittest.cc", "crc32_unittest.cc",
"fileutils_unittest.cc", "fileutils_unittest.cc",
"helpers_unittest.cc", "helpers_unittest.cc",
"httpbase_unittest.cc",
"httpcommon_unittest.cc",
"httpserver_unittest.cc",
"ipaddress_unittest.cc", "ipaddress_unittest.cc",
"memory_usage_unittest.cc", "memory_usage_unittest.cc",
"messagedigest_unittest.cc", "messagedigest_unittest.cc",
@ -835,6 +848,7 @@ if (rtc_include_tests) {
"nat_unittest.cc", "nat_unittest.cc",
"network_unittest.cc", "network_unittest.cc",
"optionsfile_unittest.cc", "optionsfile_unittest.cc",
"proxy_unittest.cc",
"ratelimiter_unittest.cc", "ratelimiter_unittest.cc",
"rollingaccumulator_unittest.cc", "rollingaccumulator_unittest.cc",
"rtccertificate_unittest.cc", "rtccertificate_unittest.cc",

View File

@ -0,0 +1,75 @@
/*
* Copyright 2015 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/base/cryptstring.h"
namespace rtc {
size_t EmptyCryptStringImpl::GetLength() const {
return 0;
}
void EmptyCryptStringImpl::CopyTo(char* dest, bool nullterminate) const {
if (nullterminate) {
*dest = '\0';
}
}
std::string EmptyCryptStringImpl::UrlEncode() const {
return "";
}
CryptStringImpl* EmptyCryptStringImpl::Copy() const {
return new EmptyCryptStringImpl();
}
void EmptyCryptStringImpl::CopyRawTo(std::vector<unsigned char>* dest) const {
dest->clear();
}
CryptString::CryptString() : impl_(new EmptyCryptStringImpl()) {
}
CryptString::CryptString(const CryptString& other)
: impl_(other.impl_->Copy()) {
}
CryptString::CryptString(const CryptStringImpl& impl) : impl_(impl.Copy()) {
}
CryptString::~CryptString() = default;
size_t InsecureCryptStringImpl::GetLength() const {
return password_.size();
}
void InsecureCryptStringImpl::CopyTo(char* dest, bool nullterminate) const {
memcpy(dest, password_.data(), password_.size());
if (nullterminate)
dest[password_.size()] = 0;
}
std::string InsecureCryptStringImpl::UrlEncode() const {
return password_;
}
CryptStringImpl* InsecureCryptStringImpl::Copy() const {
InsecureCryptStringImpl* copy = new InsecureCryptStringImpl;
copy->password() = password_;
return copy;
}
void InsecureCryptStringImpl::CopyRawTo(
std::vector<unsigned char>* dest) const {
dest->resize(password_.size());
memcpy(&dest->front(), password_.data(), password_.size());
}
}; // namespace rtc

167
webrtc/base/cryptstring.h Normal file
View File

@ -0,0 +1,167 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef _WEBRTC_BASE_CRYPTSTRING_H_
#define _WEBRTC_BASE_CRYPTSTRING_H_
#include <string.h>
#include <memory>
#include <string>
#include <vector>
namespace rtc {
class CryptStringImpl {
public:
virtual ~CryptStringImpl() {}
virtual size_t GetLength() const = 0;
virtual void CopyTo(char * dest, bool nullterminate) const = 0;
virtual std::string UrlEncode() const = 0;
virtual CryptStringImpl * Copy() const = 0;
virtual void CopyRawTo(std::vector<unsigned char> * dest) const = 0;
};
class EmptyCryptStringImpl : public CryptStringImpl {
public:
~EmptyCryptStringImpl() override {}
size_t GetLength() const override;
void CopyTo(char* dest, bool nullterminate) const override;
std::string UrlEncode() const override;
CryptStringImpl* Copy() const override;
void CopyRawTo(std::vector<unsigned char>* dest) const override;
};
class CryptString {
public:
CryptString();
size_t GetLength() const { return impl_->GetLength(); }
void CopyTo(char * dest, bool nullterminate) const { impl_->CopyTo(dest, nullterminate); }
CryptString(const CryptString& other);
explicit CryptString(const CryptStringImpl& impl);
~CryptString();
CryptString & operator=(const CryptString & other) {
if (this != &other) {
impl_.reset(other.impl_->Copy());
}
return *this;
}
void Clear() { impl_.reset(new EmptyCryptStringImpl()); }
std::string UrlEncode() const { return impl_->UrlEncode(); }
void CopyRawTo(std::vector<unsigned char> * dest) const {
return impl_->CopyRawTo(dest);
}
private:
std::unique_ptr<const CryptStringImpl> impl_;
};
// Used for constructing strings where a password is involved and we
// need to ensure that we zero memory afterwards
class FormatCryptString {
public:
FormatCryptString() {
storage_ = new char[32];
capacity_ = 32;
length_ = 0;
storage_[0] = 0;
}
void Append(const std::string & text) {
Append(text.data(), text.length());
}
void Append(const char * data, size_t length) {
EnsureStorage(length_ + length + 1);
memcpy(storage_ + length_, data, length);
length_ += length;
storage_[length_] = '\0';
}
void Append(const CryptString * password) {
size_t len = password->GetLength();
EnsureStorage(length_ + len + 1);
password->CopyTo(storage_ + length_, true);
length_ += len;
}
size_t GetLength() {
return length_;
}
const char * GetData() {
return storage_;
}
// Ensures storage of at least n bytes
void EnsureStorage(size_t n) {
if (capacity_ >= n) {
return;
}
size_t old_capacity = capacity_;
char * old_storage = storage_;
for (;;) {
capacity_ *= 2;
if (capacity_ >= n)
break;
}
storage_ = new char[capacity_];
if (old_capacity) {
memcpy(storage_, old_storage, length_);
// zero memory in a way that an optimizer won't optimize it out
old_storage[0] = 0;
for (size_t i = 1; i < old_capacity; i++) {
old_storage[i] = old_storage[i - 1];
}
delete[] old_storage;
}
}
~FormatCryptString() {
if (capacity_) {
storage_[0] = 0;
for (size_t i = 1; i < capacity_; i++) {
storage_[i] = storage_[i - 1];
}
}
delete[] storage_;
}
private:
char * storage_;
size_t capacity_;
size_t length_;
};
class InsecureCryptStringImpl : public CryptStringImpl {
public:
std::string& password() { return password_; }
const std::string& password() const { return password_; }
~InsecureCryptStringImpl() override = default;
size_t GetLength() const override;
void CopyTo(char* dest, bool nullterminate) const override;
std::string UrlEncode() const override;
CryptStringImpl* Copy() const override;
void CopyRawTo(std::vector<unsigned char>* dest) const override;
private:
std::string password_;
};
}
#endif // _WEBRTC_BASE_CRYPTSTRING_H_

886
webrtc/base/httpbase.cc Normal file
View File

@ -0,0 +1,886 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#if defined(WEBRTC_WIN)
#include "webrtc/base/win32.h"
#else // !WEBRTC_WIN
#define SEC_E_CERT_EXPIRED (-2146893016)
#endif // !WEBRTC_WIN
#include "webrtc/base/checks.h"
#include "webrtc/base/httpbase.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/socket.h"
#include "webrtc/base/stringutils.h"
#include "webrtc/base/thread.h"
namespace rtc {
//////////////////////////////////////////////////////////////////////
// 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);
}
enum {
MSG_READ
};
//////////////////////////////////////////////////////////////////////
// HttpParser
//////////////////////////////////////////////////////////////////////
HttpParser::HttpParser() {
reset();
}
HttpParser::~HttpParser() {
}
void
HttpParser::reset() {
state_ = ST_LEADER;
chunked_ = false;
data_size_ = SIZE_UNKNOWN;
}
HttpParser::ProcessResult
HttpParser::Process(const char* buffer, size_t len, size_t* processed,
HttpError* error) {
*processed = 0;
*error = HE_NONE;
if (state_ >= ST_COMPLETE) {
RTC_NOTREACHED();
return PR_COMPLETE;
}
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;
}
ProcessResult result = ProcessLine(line, len, error);
LOG(LS_VERBOSE) << "Processed line, result=" << result;
if (PR_CONTINUE != result) {
return result;
}
} else if (data_size_ == 0) {
if (chunked_) {
state_ = ST_CHUNKTERM;
} else {
return PR_COMPLETE;
}
} 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;
ProcessResult result = ProcessData(buffer + *processed, available, read,
error);
LOG(LS_VERBOSE) << "Processed data, result: " << result << " read: "
<< read << " err: " << error;
if (PR_CONTINUE != result) {
return result;
}
*processed += read;
if (data_size_ != SIZE_UNKNOWN) {
data_size_ -= read;
}
}
}
return PR_CONTINUE;
}
HttpParser::ProcessResult
HttpParser::ProcessLine(const char* line, size_t len, HttpError* error) {
LOG_F(LS_VERBOSE) << " state: " << state_ << " line: "
<< std::string(line, len) << " len: " << len << " err: "
<< error;
switch (state_) {
case ST_LEADER:
state_ = ST_HEADERS;
return ProcessLeader(line, len, error);
case ST_HEADERS:
if (len > 0) {
const char* value = strchrn(line, len, ':');
if (!value) {
*error = HE_PROTOCOL;
return PR_COMPLETE;
}
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)) {
// sscanf isn't safe with strings that aren't null-terminated, and there
// is no guarantee that |value| is.
// Create a local copy that is null-terminated.
std::string value_str(value, vlen);
unsigned int temp_size;
if (sscanf(value_str.c_str(), "%u", &temp_size) != 1) {
*error = HE_PROTOCOL;
return PR_COMPLETE;
}
data_size_ = static_cast<size_t>(temp_size);
} 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 {
*error = HE_PROTOCOL;
return PR_COMPLETE;
}
}
return ProcessHeader(line, nlen, value, vlen, error);
} else {
state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
return ProcessHeaderComplete(chunked_, data_size_, error);
}
break;
case ST_CHUNKSIZE:
if (len > 0) {
char* ptr = nullptr;
data_size_ = strtoul(line, &ptr, 16);
if (ptr != line + len) {
*error = HE_PROTOCOL;
return PR_COMPLETE;
}
state_ = (data_size_ == 0) ? ST_TRAILERS : ST_DATA;
} else {
*error = HE_PROTOCOL;
return PR_COMPLETE;
}
break;
case ST_CHUNKTERM:
if (len > 0) {
*error = HE_PROTOCOL;
return PR_COMPLETE;
} else {
state_ = chunked_ ? ST_CHUNKSIZE : ST_DATA;
}
break;
case ST_TRAILERS:
if (len == 0) {
return PR_COMPLETE;
}
// *error = onHttpRecvTrailer();
break;
default:
RTC_NOTREACHED();
break;
}
return PR_CONTINUE;
}
bool
HttpParser::is_valid_end_of_input() const {
return (state_ == ST_DATA) && (data_size_ == SIZE_UNKNOWN);
}
void
HttpParser::complete(HttpError error) {
if (state_ < ST_COMPLETE) {
state_ = ST_COMPLETE;
OnComplete(error);
}
}
//////////////////////////////////////////////////////////////////////
// HttpBase::DocumentStream
//////////////////////////////////////////////////////////////////////
class BlockingMemoryStream : public ExternalMemoryStream {
public:
BlockingMemoryStream(char* buffer, size_t size)
: ExternalMemoryStream(buffer, size) { }
StreamResult DoReserve(size_t size, int* error) override {
return (buffer_length_ >= size) ? SR_SUCCESS : SR_BLOCK;
}
};
class HttpBase::DocumentStream : public StreamInterface {
public:
DocumentStream(HttpBase* base) : base_(base), error_(HE_DEFAULT) { }
StreamState GetState() const override {
if (nullptr == base_)
return SS_CLOSED;
if (HM_RECV == base_->mode_)
return SS_OPEN;
return SS_OPENING;
}
StreamResult Read(void* buffer,
size_t buffer_len,
size_t* read,
int* error) override {
if (!base_) {
if (error) *error = error_;
return (HE_NONE == error_) ? SR_EOS : SR_ERROR;
}
if (HM_RECV != base_->mode_) {
return SR_BLOCK;
}
// DoReceiveLoop writes http document data to the StreamInterface* document
// member of HttpData. In this case, we want this data to be written
// directly to our buffer. To accomplish this, we wrap our buffer with a
// StreamInterface, and replace the existing document with our wrapper.
// When the method returns, we restore the old document. Ideally, we would
// pass our StreamInterface* to DoReceiveLoop, but due to the callbacks
// of HttpParser, we would still need to store the pointer temporarily.
std::unique_ptr<StreamInterface> stream(
new BlockingMemoryStream(reinterpret_cast<char*>(buffer), buffer_len));
// Replace the existing document with our wrapped buffer.
base_->data_->document.swap(stream);
// Pump the I/O loop. DoReceiveLoop is guaranteed not to attempt to
// complete the I/O process, which means that our wrapper is not in danger
// of being deleted. To ensure this, DoReceiveLoop returns true when it
// wants complete to be called. We make sure to uninstall our wrapper
// before calling complete().
HttpError http_error;
bool complete = base_->DoReceiveLoop(&http_error);
// Reinstall the original output document.
base_->data_->document.swap(stream);
// If we reach the end of the receive stream, we disconnect our stream
// adapter from the HttpBase, and further calls to read will either return
// EOS or ERROR, appropriately. Finally, we call complete().
StreamResult result = SR_BLOCK;
if (complete) {
HttpBase* base = Disconnect(http_error);
if (error) *error = error_;
result = (HE_NONE == error_) ? SR_EOS : SR_ERROR;
base->complete(http_error);
}
// Even if we are complete, if some data was read we must return SUCCESS.
// Future Reads will return EOS or ERROR based on the error_ variable.
size_t position;
stream->GetPosition(&position);
if (position > 0) {
if (read) *read = position;
result = SR_SUCCESS;
}
return result;
}
StreamResult Write(const void* data,
size_t data_len,
size_t* written,
int* error) override {
if (error) *error = -1;
return SR_ERROR;
}
void Close() override {
if (base_) {
HttpBase* base = Disconnect(HE_NONE);
if (HM_RECV == base->mode_ && base->http_stream_) {
// Read I/O could have been stalled on the user of this DocumentStream,
// so restart the I/O process now that we've removed ourselves.
base->http_stream_->PostEvent(SE_READ, 0);
}
}
}
bool GetAvailable(size_t* size) const override {
if (!base_ || HM_RECV != base_->mode_)
return false;
size_t data_size = base_->GetDataRemaining();
if (SIZE_UNKNOWN == data_size)
return false;
if (size)
*size = data_size;
return true;
}
HttpBase* Disconnect(HttpError error) {
RTC_DCHECK(nullptr != base_);
RTC_DCHECK(nullptr != base_->doc_stream_);
HttpBase* base = base_;
base_->doc_stream_ = nullptr;
base_ = nullptr;
error_ = error;
return base;
}
private:
HttpBase* base_;
HttpError error_;
};
//////////////////////////////////////////////////////////////////////
// HttpBase
//////////////////////////////////////////////////////////////////////
HttpBase::HttpBase()
: mode_(HM_NONE),
data_(nullptr),
notify_(nullptr),
http_stream_(nullptr),
doc_stream_(nullptr) {}
HttpBase::~HttpBase() {
RTC_DCHECK(HM_NONE == mode_);
}
bool
HttpBase::isConnected() const {
return (http_stream_ != nullptr) && (http_stream_->GetState() == SS_OPEN);
}
bool
HttpBase::attach(StreamInterface* stream) {
if ((mode_ != HM_NONE) || (http_stream_ != nullptr) || (stream == nullptr)) {
RTC_NOTREACHED();
return false;
}
http_stream_ = stream;
http_stream_->SignalEvent.connect(this, &HttpBase::OnHttpStreamEvent);
mode_ = (http_stream_->GetState() == SS_OPENING) ? HM_CONNECT : HM_NONE;
return true;
}
StreamInterface*
HttpBase::detach() {
RTC_DCHECK(HM_NONE == mode_);
if (mode_ != HM_NONE) {
return nullptr;
}
StreamInterface* stream = http_stream_;
http_stream_ = nullptr;
if (stream) {
stream->SignalEvent.disconnect(this);
}
return stream;
}
void
HttpBase::send(HttpData* data) {
RTC_DCHECK(HM_NONE == mode_);
if (mode_ != HM_NONE) {
return;
} else if (!isConnected()) {
OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED);
return;
}
mode_ = HM_SEND;
data_ = data;
len_ = 0;
ignore_data_ = chunk_data_ = false;
if (data_->document) {
data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent);
}
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();
if (header_ == data_->end()) {
// We must call this at least once, in the case where there are no headers.
queue_headers();
}
flush_data();
}
void
HttpBase::recv(HttpData* data) {
RTC_DCHECK(HM_NONE == mode_);
if (mode_ != HM_NONE) {
return;
} else if (!isConnected()) {
OnHttpStreamEvent(http_stream_, SE_CLOSE, HE_DISCONNECTED);
return;
}
mode_ = HM_RECV;
data_ = data;
len_ = 0;
ignore_data_ = chunk_data_ = false;
reset();
if (doc_stream_) {
doc_stream_->SignalEvent(doc_stream_, SE_OPEN | SE_READ, 0);
} else {
read_and_process_data();
}
}
void
HttpBase::abort(HttpError err) {
if (mode_ != HM_NONE) {
if (http_stream_ != nullptr) {
http_stream_->Close();
}
do_complete(err);
}
}
StreamInterface* HttpBase::GetDocumentStream() {
if (doc_stream_)
return nullptr;
doc_stream_ = new DocumentStream(this);
return doc_stream_;
}
HttpError HttpBase::HandleStreamClose(int error) {
if (http_stream_ != nullptr) {
http_stream_->Close();
}
if (error == 0) {
if ((mode_ == HM_RECV) && is_valid_end_of_input()) {
return HE_NONE;
} else {
return HE_DISCONNECTED;
}
} else if (error == SOCKET_EACCES) {
return HE_AUTH;
} else if (error == SEC_E_CERT_EXPIRED) {
return HE_CERTIFICATE_EXPIRED;
}
LOG_F(LS_ERROR) << "(" << error << ")";
return (HM_CONNECT == mode_) ? HE_CONNECT_FAILED : HE_SOCKET_ERROR;
}
bool HttpBase::DoReceiveLoop(HttpError* error) {
RTC_DCHECK(HM_RECV == mode_);
RTC_DCHECK(nullptr != error);
// 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;
bool process_requires_more_data = false;
do {
// The most frequent use of this function is response to new data available
// on http_stream_. Therefore, we optimize by attempting to read from the
// network first (as opposed to processing existing data first).
if (len_ < sizeof(buffer_)) {
// Attempt to buffer more data.
size_t read;
int read_error;
StreamResult read_result = http_stream_->Read(buffer_ + len_,
sizeof(buffer_) - len_,
&read, &read_error);
switch (read_result) {
case SR_SUCCESS:
RTC_DCHECK(len_ + read <= sizeof(buffer_));
len_ += read;
break;
case SR_BLOCK:
if (process_requires_more_data) {
// We're can't make progress until more data is available.
return false;
}
// Attempt to process the data already in our buffer.
break;
case SR_EOS:
// Clean close, with no error.
read_error = 0;
FALLTHROUGH(); // Fall through to HandleStreamClose.
case SR_ERROR:
*error = HandleStreamClose(read_error);
return true;
}
} else if (process_requires_more_data) {
// We have too much unprocessed data in our buffer. This should only
// occur when a single HTTP header is longer than the buffer size (32K).
// Anything longer than that is almost certainly an error.
*error = HE_OVERFLOW;
return true;
}
// Process data in our buffer. Process is not guaranteed to process all
// the buffered data. In particular, it will wait until a complete
// protocol element (such as http header, or chunk size) is available,
// before processing it in its entirety. Also, it is valid and sometimes
// necessary to call Process with an empty buffer, since the state machine
// may have interrupted state transitions to complete.
size_t processed;
ProcessResult process_result = Process(buffer_, len_, &processed,
error);
RTC_DCHECK(processed <= len_);
len_ -= processed;
memmove(buffer_, buffer_ + processed, len_);
switch (process_result) {
case PR_CONTINUE:
// We need more data to make progress.
process_requires_more_data = true;
break;
case PR_BLOCK:
// We're stalled on writing the processed data.
return false;
case PR_COMPLETE:
// *error already contains the correct code.
return true;
}
} while (++loop_count <= kMaxReadCount);
LOG_F(LS_WARNING) << "danger of starvation";
return false;
}
void
HttpBase::read_and_process_data() {
HttpError error;
if (DoReceiveLoop(&error)) {
complete(error);
}
}
void
HttpBase::flush_data() {
RTC_DCHECK(HM_SEND == mode_);
// When send_required is true, no more buffering can occur without a network
// write.
bool send_required = (len_ >= sizeof(buffer_));
while (true) {
RTC_DCHECK(len_ <= sizeof(buffer_));
// HTTP is inherently sensitive to round trip latency, since a frequent use
// case is for small requests and responses to be sent back and forth, and
// the lack of pipelining forces a single request to take a minimum of the
// round trip time. As a result, it is to our benefit to pack as much data
// into each packet as possible. Thus, we defer network writes until we've
// buffered as much data as possible.
if (!send_required && (header_ != data_->end())) {
// First, attempt to queue more header data.
send_required = queue_headers();
}
if (!send_required && data_->document) {
// Next, attempt to queue document data.
const size_t kChunkDigits = 8;
size_t offset, reserve;
if (chunk_data_) {
// Reserve characters at the start for X-byte hex value and \r\n
offset = len_ + kChunkDigits + 2;
// ... and 2 characters at the end for \r\n
reserve = offset + 2;
} else {
offset = len_;
reserve = offset;
}
if (reserve >= sizeof(buffer_)) {
send_required = true;
} else {
size_t read;
int error;
StreamResult result = data_->document->Read(buffer_ + offset,
sizeof(buffer_) - reserve,
&read, &error);
if (result == SR_SUCCESS) {
RTC_DCHECK(reserve + read <= sizeof(buffer_));
if (chunk_data_) {
// Prepend the chunk length in hex.
// Note: sprintfn appends a null terminator, which is why we can't
// combine it with the line terminator.
sprintfn(buffer_ + len_, kChunkDigits + 1, "%.*x",
kChunkDigits, read);
// Add line terminator to the chunk length.
memcpy(buffer_ + len_ + kChunkDigits, "\r\n", 2);
// Add line terminator to the end of the chunk.
memcpy(buffer_ + offset + read, "\r\n", 2);
}
len_ = reserve + read;
} else if (result == SR_BLOCK) {
// Nothing to do but flush data to the network.
send_required = true;
} else if (result == SR_EOS) {
if (chunk_data_) {
// Append the empty chunk and empty trailers, then turn off
// chunking.
RTC_DCHECK(len_ + 5 <= sizeof(buffer_));
memcpy(buffer_ + len_, "0\r\n\r\n", 5);
len_ += 5;
chunk_data_ = false;
} else if (0 == len_) {
// No more data to read, and no more data to write.
do_complete();
return;
}
// Although we are done reading data, there is still data which needs
// to be flushed to the network.
send_required = true;
} else {
LOG_F(LS_ERROR) << "Read error: " << error;
do_complete(HE_STREAM);
return;
}
}
}
if (0 == len_) {
// No data currently available to send.
if (!data_->document) {
// If there is no source document, that means we're done.
do_complete();
}
return;
}
size_t written;
int error;
StreamResult result = http_stream_->Write(buffer_, len_, &written, &error);
if (result == SR_SUCCESS) {
RTC_DCHECK(written <= len_);
len_ -= written;
memmove(buffer_, buffer_ + written, len_);
send_required = false;
} else if (result == SR_BLOCK) {
if (send_required) {
// Nothing more we can do until network is writeable.
return;
}
} else {
RTC_DCHECK(result == SR_ERROR);
LOG_F(LS_ERROR) << "error";
OnHttpStreamEvent(http_stream_, SE_CLOSE, error);
return;
}
}
RTC_NOTREACHED();
}
bool
HttpBase::queue_headers() {
RTC_DCHECK(HM_SEND == mode_);
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 {
// Not enough room for the next header, write to network first.
return true;
}
}
// End of headers
len_ += strcpyn(buffer_ + len_, sizeof(buffer_) - len_, "\r\n");
return false;
}
void
HttpBase::do_complete(HttpError err) {
RTC_DCHECK(mode_ != HM_NONE);
HttpMode mode = mode_;
mode_ = HM_NONE;
if (data_ && data_->document) {
data_->document->SignalEvent.disconnect(this);
}
data_ = nullptr;
if ((HM_RECV == mode) && doc_stream_) {
RTC_DCHECK(HE_NONE !=
err); // We should have Disconnected doc_stream_ already.
DocumentStream* ds = doc_stream_;
ds->Disconnect(err);
ds->SignalEvent(ds, SE_CLOSE, err);
}
if (notify_) {
notify_->onHttpComplete(mode, err);
}
}
//
// Stream Signals
//
void
HttpBase::OnHttpStreamEvent(StreamInterface* stream, int events, int error) {
RTC_DCHECK(stream == http_stream_);
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)) {
if (doc_stream_) {
doc_stream_->SignalEvent(doc_stream_, SE_READ, 0);
} else {
read_and_process_data();
}
return;
}
if ((events & SE_CLOSE) == 0)
return;
HttpError http_error = HandleStreamClose(error);
if (mode_ == HM_RECV) {
complete(http_error);
} else if (mode_ != HM_NONE) {
do_complete(http_error);
} else if (notify_) {
notify_->onHttpClosed(http_error);
}
}
void
HttpBase::OnDocumentEvent(StreamInterface* stream, int events, int error) {
RTC_DCHECK(stream == data_->document.get());
if ((events & SE_WRITE) && (mode_ == HM_RECV)) {
read_and_process_data();
return;
}
if ((events & SE_READ) && (mode_ == HM_SEND)) {
flush_data();
return;
}
if (events & SE_CLOSE) {
LOG_F(LS_ERROR) << "Read error: " << error;
do_complete(HE_STREAM);
return;
}
}
//
// HttpParser Implementation
//
HttpParser::ProcessResult
HttpBase::ProcessLeader(const char* line, size_t len, HttpError* error) {
*error = data_->parseLeader(line, len);
return (HE_NONE == *error) ? PR_CONTINUE : PR_COMPLETE;
}
HttpParser::ProcessResult
HttpBase::ProcessHeader(const char* name, size_t nlen, const char* value,
size_t vlen, HttpError* error) {
std::string sname(name, nlen), svalue(value, vlen);
data_->addHeader(sname, svalue);
return PR_CONTINUE;
}
HttpParser::ProcessResult
HttpBase::ProcessHeaderComplete(bool chunked, size_t& data_size,
HttpError* error) {
StreamInterface* old_docstream = doc_stream_;
if (notify_) {
*error = notify_->onHttpHeaderComplete(chunked, data_size);
// The request must not be aborted as a result of this callback.
RTC_DCHECK(nullptr != data_);
}
if ((HE_NONE == *error) && data_->document) {
data_->document->SignalEvent.connect(this, &HttpBase::OnDocumentEvent);
}
if (HE_NONE != *error) {
return PR_COMPLETE;
}
if (old_docstream != doc_stream_) {
// Break out of Process loop, since our I/O model just changed.
return PR_BLOCK;
}
return PR_CONTINUE;
}
HttpParser::ProcessResult
HttpBase::ProcessData(const char* data, size_t len, size_t& read,
HttpError* error) {
if (ignore_data_ || !data_->document) {
read = len;
return PR_CONTINUE;
}
int write_error = 0;
switch (data_->document->Write(data, len, &read, &write_error)) {
case SR_SUCCESS:
return PR_CONTINUE;
case SR_BLOCK:
return PR_BLOCK;
case SR_EOS:
LOG_F(LS_ERROR) << "Unexpected EOS";
*error = HE_STREAM;
return PR_COMPLETE;
case SR_ERROR:
default:
LOG_F(LS_ERROR) << "Write error: " << write_error;
*error = HE_STREAM;
return PR_COMPLETE;
}
}
void
HttpBase::OnComplete(HttpError err) {
LOG_F(LS_VERBOSE);
do_complete(err);
}
} // namespace rtc

187
webrtc/base/httpbase.h Normal file
View File

@ -0,0 +1,187 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef WEBRTC_BASE_HTTPBASE_H__
#define WEBRTC_BASE_HTTPBASE_H__
#include "webrtc/base/httpcommon.h"
namespace rtc {
class StreamInterface;
///////////////////////////////////////////////////////////////////////////////
// HttpParser - Parses an HTTP stream provided via Process and end_of_input, and
// generates events for:
// Structural Elements: Leader, Headers, Document Data
// Events: End of Headers, End of Document, Errors
///////////////////////////////////////////////////////////////////////////////
class HttpParser {
public:
enum ProcessResult { PR_CONTINUE, PR_BLOCK, PR_COMPLETE };
HttpParser();
virtual ~HttpParser();
void reset();
ProcessResult Process(const char* buffer, size_t len, size_t* processed,
HttpError* error);
bool is_valid_end_of_input() const;
void complete(HttpError err);
size_t GetDataRemaining() const { return data_size_; }
protected:
ProcessResult ProcessLine(const char* line, size_t len, HttpError* error);
// HttpParser Interface
virtual ProcessResult ProcessLeader(const char* line, size_t len,
HttpError* error) = 0;
virtual ProcessResult ProcessHeader(const char* name, size_t nlen,
const char* value, size_t vlen,
HttpError* error) = 0;
virtual ProcessResult ProcessHeaderComplete(bool chunked, size_t& data_size,
HttpError* error) = 0;
virtual ProcessResult ProcessData(const char* data, size_t len, size_t& read,
HttpError* error) = 0;
virtual void OnComplete(HttpError err) = 0;
private:
enum State {
ST_LEADER, ST_HEADERS,
ST_CHUNKSIZE, ST_CHUNKTERM, ST_TRAILERS,
ST_DATA, ST_COMPLETE
} state_;
bool chunked_;
size_t data_size_;
};
///////////////////////////////////////////////////////////////////////////////
// IHttpNotify
///////////////////////////////////////////////////////////////////////////////
enum HttpMode { HM_NONE, HM_CONNECT, HM_RECV, HM_SEND };
class IHttpNotify {
public:
virtual ~IHttpNotify() {}
virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) = 0;
virtual void onHttpComplete(HttpMode mode, HttpError err) = 0;
virtual void onHttpClosed(HttpError err) = 0;
};
///////////////////////////////////////////////////////////////////////////////
// HttpBase - Provides a state machine for implementing HTTP-based components.
// Attach HttpBase to a StreamInterface which represents a bidirectional HTTP
// stream, and then call send() or recv() to initiate sending or receiving one
// side of an HTTP transaction. By default, HttpBase operates as an I/O pump,
// moving data from the HTTP stream to the HttpData object and vice versa.
// However, it can also operate in stream mode, in which case the user of the
// stream interface drives I/O via calls to Read().
///////////////////////////////////////////////////////////////////////////////
class HttpBase
: private HttpParser,
public sigslot::has_slots<>
{
public:
HttpBase();
~HttpBase() override;
void notify(IHttpNotify* notify) { notify_ = notify; }
bool attach(StreamInterface* stream);
StreamInterface* stream() { return http_stream_; }
StreamInterface* detach();
bool isConnected() const;
void send(HttpData* data);
void recv(HttpData* data);
void abort(HttpError err);
HttpMode mode() const { return mode_; }
void set_ignore_data(bool ignore) { ignore_data_ = ignore; }
bool ignore_data() const { return ignore_data_; }
// Obtaining this stream puts HttpBase into stream mode until the stream
// is closed. HttpBase can only expose one open stream interface at a time.
// Further calls will return null.
StreamInterface* GetDocumentStream();
protected:
// Do cleanup when the http stream closes (error may be 0 for a clean
// shutdown), and return the error code to signal.
HttpError HandleStreamClose(int error);
// DoReceiveLoop acts as a data pump, pulling data from the http stream,
// pushing it through the HttpParser, and then populating the HttpData object
// based on the callbacks from the parser. One of the most interesting
// callbacks is ProcessData, which provides the actual http document body.
// This data is then written to the HttpData::document. As a result, data
// flows from the network to the document, with some incidental protocol
// parsing in between.
// Ideally, we would pass in the document* to DoReceiveLoop, to more easily
// support GetDocumentStream(). However, since the HttpParser is callback
// driven, we are forced to store the pointer somewhere until the callback
// is triggered.
// Returns true if the received document has finished, and
// HttpParser::complete should be called.
bool DoReceiveLoop(HttpError* err);
void read_and_process_data();
void flush_data();
bool queue_headers();
void do_complete(HttpError err = HE_NONE);
void OnHttpStreamEvent(StreamInterface* stream, int events, int error);
void OnDocumentEvent(StreamInterface* stream, int events, int error);
// HttpParser Interface
ProcessResult ProcessLeader(const char* line,
size_t len,
HttpError* error) override;
ProcessResult ProcessHeader(const char* name,
size_t nlen,
const char* value,
size_t vlen,
HttpError* error) override;
ProcessResult ProcessHeaderComplete(bool chunked,
size_t& data_size,
HttpError* error) override;
ProcessResult ProcessData(const char* data,
size_t len,
size_t& read,
HttpError* error) override;
void OnComplete(HttpError err) override;
private:
class DocumentStream;
friend class DocumentStream;
enum { kBufferSize = 32 * 1024 };
HttpMode mode_;
HttpData* data_;
IHttpNotify* notify_;
StreamInterface* http_stream_;
DocumentStream* doc_stream_;
char buffer_[kBufferSize];
size_t len_;
bool ignore_data_, chunk_data_;
HttpData::const_iterator header_;
};
///////////////////////////////////////////////////////////////////////////////
} // namespace rtc
#endif // WEBRTC_BASE_HTTPBASE_H__

View File

@ -0,0 +1,526 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <algorithm>
#include "webrtc/base/gunit.h"
#include "webrtc/base/httpbase.h"
#include "webrtc/base/testutils.h"
namespace rtc {
const char* const kHttpResponse =
"HTTP/1.1 200\r\n"
"Connection: Keep-Alive\r\n"
"Content-Type: text/plain\r\n"
"Proxy-Authorization: 42\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"00000008\r\n"
"Goodbye!\r\n"
"0\r\n\r\n";
const char* const kHttpEmptyResponse =
"HTTP/1.1 200\r\n"
"Connection: Keep-Alive\r\n"
"Content-Length: 0\r\n"
"Proxy-Authorization: 42\r\n"
"\r\n";
const char* const kHttpResponsePrefix =
"HTTP/1.1 200\r\n"
"Connection: Keep-Alive\r\n"
"Content-Type: text/plain\r\n"
"Proxy-Authorization: 42\r\n"
"Transfer-Encoding: chunked\r\n"
"\r\n"
"8\r\n"
"Goodbye!\r\n";
class HttpBaseTest : public testing::Test, public IHttpNotify {
public:
enum EventType { E_HEADER_COMPLETE, E_COMPLETE, E_CLOSED };
struct Event {
EventType event;
bool chunked;
size_t data_size;
HttpMode mode;
HttpError err;
};
HttpBaseTest() : mem(nullptr), obtain_stream(false), http_stream(nullptr) {}
virtual void SetUp() { }
virtual void TearDown() {
delete http_stream;
// Avoid an ASSERT, in case a test doesn't clean up properly
base.abort(HE_NONE);
}
virtual HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) {
LOG_F(LS_VERBOSE) << "chunked: " << chunked << " size: " << data_size;
Event e = { E_HEADER_COMPLETE, chunked, data_size, HM_NONE, HE_NONE};
events.push_back(e);
if (obtain_stream) {
ObtainDocumentStream();
}
return HE_NONE;
}
virtual void onHttpComplete(HttpMode mode, HttpError err) {
LOG_F(LS_VERBOSE) << "mode: " << mode << " err: " << err;
Event e = { E_COMPLETE, false, 0, mode, err };
events.push_back(e);
}
virtual void onHttpClosed(HttpError err) {
LOG_F(LS_VERBOSE) << "err: " << err;
Event e = { E_CLOSED, false, 0, HM_NONE, err };
events.push_back(e);
}
void SetupSource(const char* response);
void VerifyHeaderComplete(size_t event_count, bool empty_doc);
void VerifyDocumentContents(const char* expected_data,
size_t expected_length = SIZE_UNKNOWN);
void ObtainDocumentStream();
void VerifyDocumentStreamIsOpening();
void VerifyDocumentStreamOpenEvent();
void ReadDocumentStreamData(const char* expected_data);
void VerifyDocumentStreamIsEOS();
void SetupDocument(const char* response);
void VerifySourceContents(const char* expected_data,
size_t expected_length = SIZE_UNKNOWN);
void VerifyTransferComplete(HttpMode mode, HttpError error);
HttpBase base;
MemoryStream* mem;
HttpResponseData data;
// The source of http data, and source events
testing::StreamSource src;
std::vector<Event> events;
// Document stream, and stream events
bool obtain_stream;
StreamInterface* http_stream;
testing::StreamSink sink;
};
void HttpBaseTest::SetupSource(const char* http_data) {
LOG_F(LS_VERBOSE) << "Enter";
src.SetState(SS_OPENING);
src.QueueString(http_data);
base.notify(this);
base.attach(&src);
EXPECT_TRUE(events.empty());
src.SetState(SS_OPEN);
ASSERT_EQ(1U, events.size());
EXPECT_EQ(E_COMPLETE, events[0].event);
EXPECT_EQ(HM_CONNECT, events[0].mode);
EXPECT_EQ(HE_NONE, events[0].err);
events.clear();
mem = new MemoryStream;
data.document.reset(mem);
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyHeaderComplete(size_t event_count, bool empty_doc) {
LOG_F(LS_VERBOSE) << "Enter";
ASSERT_EQ(event_count, events.size());
EXPECT_EQ(E_HEADER_COMPLETE, events[0].event);
std::string header;
EXPECT_EQ(HVER_1_1, data.version);
EXPECT_EQ(static_cast<uint32_t>(HC_OK), data.scode);
EXPECT_TRUE(data.hasHeader(HH_PROXY_AUTHORIZATION, &header));
EXPECT_EQ("42", header);
EXPECT_TRUE(data.hasHeader(HH_CONNECTION, &header));
EXPECT_EQ("Keep-Alive", header);
if (empty_doc) {
EXPECT_FALSE(events[0].chunked);
EXPECT_EQ(0U, events[0].data_size);
EXPECT_TRUE(data.hasHeader(HH_CONTENT_LENGTH, &header));
EXPECT_EQ("0", header);
} else {
EXPECT_TRUE(events[0].chunked);
EXPECT_EQ(SIZE_UNKNOWN, events[0].data_size);
EXPECT_TRUE(data.hasHeader(HH_CONTENT_TYPE, &header));
EXPECT_EQ("text/plain", header);
EXPECT_TRUE(data.hasHeader(HH_TRANSFER_ENCODING, &header));
EXPECT_EQ("chunked", header);
}
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyDocumentContents(const char* expected_data,
size_t expected_length) {
LOG_F(LS_VERBOSE) << "Enter";
if (SIZE_UNKNOWN == expected_length) {
expected_length = strlen(expected_data);
}
EXPECT_EQ(mem, data.document.get());
size_t length;
mem->GetSize(&length);
EXPECT_EQ(expected_length, length);
EXPECT_TRUE(0 == memcmp(expected_data, mem->GetBuffer(), length));
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::ObtainDocumentStream() {
LOG_F(LS_VERBOSE) << "Enter";
EXPECT_FALSE(http_stream);
http_stream = base.GetDocumentStream();
ASSERT_TRUE(nullptr != http_stream);
sink.Monitor(http_stream);
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyDocumentStreamIsOpening() {
LOG_F(LS_VERBOSE) << "Enter";
ASSERT_TRUE(nullptr != http_stream);
EXPECT_EQ(0, sink.Events(http_stream));
EXPECT_EQ(SS_OPENING, http_stream->GetState());
size_t read = 0;
char buffer[5] = { 0 };
EXPECT_EQ(SR_BLOCK,
http_stream->Read(buffer, sizeof(buffer), &read, nullptr));
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyDocumentStreamOpenEvent() {
LOG_F(LS_VERBOSE) << "Enter";
ASSERT_TRUE(nullptr != http_stream);
EXPECT_EQ(SE_OPEN | SE_READ, sink.Events(http_stream));
EXPECT_EQ(SS_OPEN, http_stream->GetState());
// HTTP headers haven't arrived yet
EXPECT_EQ(0U, events.size());
EXPECT_EQ(static_cast<uint32_t>(HC_INTERNAL_SERVER_ERROR), data.scode);
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::ReadDocumentStreamData(const char* expected_data) {
LOG_F(LS_VERBOSE) << "Enter";
ASSERT_TRUE(nullptr != http_stream);
EXPECT_EQ(SS_OPEN, http_stream->GetState());
// Pump the HTTP I/O using Read, and verify the results.
size_t verified_length = 0;
const size_t expected_length = strlen(expected_data);
while (verified_length < expected_length) {
size_t read = 0;
char buffer[5] = { 0 };
size_t amt_to_read =
std::min(expected_length - verified_length, sizeof(buffer));
EXPECT_EQ(SR_SUCCESS,
http_stream->Read(buffer, amt_to_read, &read, nullptr));
EXPECT_EQ(amt_to_read, read);
EXPECT_TRUE(0 == memcmp(expected_data + verified_length, buffer, read));
verified_length += read;
}
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyDocumentStreamIsEOS() {
LOG_F(LS_VERBOSE) << "Enter";
ASSERT_TRUE(nullptr != http_stream);
size_t read = 0;
char buffer[5] = { 0 };
EXPECT_EQ(SR_EOS, http_stream->Read(buffer, sizeof(buffer), &read, nullptr));
EXPECT_EQ(SS_CLOSED, http_stream->GetState());
// When EOS is caused by Read, we don't expect SE_CLOSE
EXPECT_EQ(0, sink.Events(http_stream));
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::SetupDocument(const char* document_data) {
LOG_F(LS_VERBOSE) << "Enter";
src.SetState(SS_OPEN);
base.notify(this);
base.attach(&src);
EXPECT_TRUE(events.empty());
if (document_data) {
// Note: we could just call data.set_success("text/plain", mem), but that
// won't allow us to use the chunked transfer encoding.
mem = new MemoryStream(document_data);
data.document.reset(mem);
data.setHeader(HH_CONTENT_TYPE, "text/plain");
data.setHeader(HH_TRANSFER_ENCODING, "chunked");
} else {
data.setHeader(HH_CONTENT_LENGTH, "0");
}
data.scode = HC_OK;
data.setHeader(HH_PROXY_AUTHORIZATION, "42");
data.setHeader(HH_CONNECTION, "Keep-Alive");
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifySourceContents(const char* expected_data,
size_t expected_length) {
LOG_F(LS_VERBOSE) << "Enter";
if (SIZE_UNKNOWN == expected_length) {
expected_length = strlen(expected_data);
}
std::string contents = src.ReadData();
EXPECT_EQ(expected_length, contents.length());
EXPECT_TRUE(0 == memcmp(expected_data, contents.data(), expected_length));
LOG_F(LS_VERBOSE) << "Exit";
}
void HttpBaseTest::VerifyTransferComplete(HttpMode mode, HttpError error) {
LOG_F(LS_VERBOSE) << "Enter";
// Verify that http operation has completed
ASSERT_TRUE(events.size() > 0);
size_t last_event = events.size() - 1;
EXPECT_EQ(E_COMPLETE, events[last_event].event);
EXPECT_EQ(mode, events[last_event].mode);
EXPECT_EQ(error, events[last_event].err);
LOG_F(LS_VERBOSE) << "Exit";
}
//
// Tests
//
TEST_F(HttpBaseTest, SupportsSend) {
// Queue response document
SetupDocument("Goodbye!");
// Begin send
base.send(&data);
// Send completed successfully
VerifyTransferComplete(HM_SEND, HE_NONE);
VerifySourceContents(kHttpResponse);
}
TEST_F(HttpBaseTest, SupportsSendNoDocument) {
// Queue response document
SetupDocument(nullptr);
// Begin send
base.send(&data);
// Send completed successfully
VerifyTransferComplete(HM_SEND, HE_NONE);
VerifySourceContents(kHttpEmptyResponse);
}
TEST_F(HttpBaseTest, SignalsCompleteOnInterruptedSend) {
// This test is attempting to expose a bug that occurs when a particular
// base objects is used for receiving, and then used for sending. In
// particular, the HttpParser state is different after receiving. Simulate
// that here.
SetupSource(kHttpResponse);
base.recv(&data);
VerifyTransferComplete(HM_RECV, HE_NONE);
src.Clear();
data.clear(true);
events.clear();
base.detach();
// Queue response document
SetupDocument("Goodbye!");
// Prevent entire response from being sent
const size_t kInterruptedLength = strlen(kHttpResponse) - 1;
src.SetWriteBlock(kInterruptedLength);
// Begin send
base.send(&data);
// Document is mostly complete, but no completion signal yet.
EXPECT_TRUE(events.empty());
VerifySourceContents(kHttpResponse, kInterruptedLength);
src.SetState(SS_CLOSED);
// Send completed with disconnect error, and no additional data.
VerifyTransferComplete(HM_SEND, HE_DISCONNECTED);
EXPECT_TRUE(src.ReadData().empty());
}
TEST_F(HttpBaseTest, SupportsReceiveViaDocumentPush) {
// Queue response document
SetupSource(kHttpResponse);
// Begin receive
base.recv(&data);
// Document completed successfully
VerifyHeaderComplete(2, false);
VerifyTransferComplete(HM_RECV, HE_NONE);
VerifyDocumentContents("Goodbye!");
}
TEST_F(HttpBaseTest, SupportsReceiveViaStreamPull) {
// Switch to pull mode
ObtainDocumentStream();
VerifyDocumentStreamIsOpening();
// Queue response document
SetupSource(kHttpResponse);
VerifyDocumentStreamIsOpening();
// Begin receive
base.recv(&data);
// Pull document data
VerifyDocumentStreamOpenEvent();
ReadDocumentStreamData("Goodbye!");
VerifyDocumentStreamIsEOS();
// Document completed successfully
VerifyHeaderComplete(2, false);
VerifyTransferComplete(HM_RECV, HE_NONE);
VerifyDocumentContents("");
}
TEST_F(HttpBaseTest, DISABLED_AllowsCloseStreamBeforeDocumentIsComplete) {
// TODO: Remove extra logging once test failure is understood
LoggingSeverity old_sev = rtc::LogMessage::GetLogToDebug();
rtc::LogMessage::LogToDebug(LS_VERBOSE);
// Switch to pull mode
ObtainDocumentStream();
VerifyDocumentStreamIsOpening();
// Queue response document
SetupSource(kHttpResponse);
VerifyDocumentStreamIsOpening();
// Begin receive
base.recv(&data);
// Pull some of the data
VerifyDocumentStreamOpenEvent();
ReadDocumentStreamData("Goodb");
// We've seen the header by now
VerifyHeaderComplete(1, false);
// Close the pull stream, this will transition back to push I/O.
http_stream->Close();
Thread::Current()->ProcessMessages(0);
// Remainder of document completed successfully
VerifyTransferComplete(HM_RECV, HE_NONE);
VerifyDocumentContents("ye!");
rtc::LogMessage::LogToDebug(old_sev);
}
TEST_F(HttpBaseTest, AllowsGetDocumentStreamInResponseToHttpHeader) {
// Queue response document
SetupSource(kHttpResponse);
// Switch to pull mode in response to header arrival
obtain_stream = true;
// Begin receive
base.recv(&data);
// We've already seen the header, but not data has arrived
VerifyHeaderComplete(1, false);
VerifyDocumentContents("");
// Pull the document data
ReadDocumentStreamData("Goodbye!");
VerifyDocumentStreamIsEOS();
// Document completed successfully
VerifyTransferComplete(HM_RECV, HE_NONE);
VerifyDocumentContents("");
}
TEST_F(HttpBaseTest, AllowsGetDocumentStreamWithEmptyDocumentBody) {
// Queue empty response document
SetupSource(kHttpEmptyResponse);
// Switch to pull mode in response to header arrival
obtain_stream = true;
// Begin receive
base.recv(&data);
// We've already seen the header, but not data has arrived
VerifyHeaderComplete(1, true);
VerifyDocumentContents("");
// The document is still open, until we attempt to read
ASSERT_TRUE(nullptr != http_stream);
EXPECT_EQ(SS_OPEN, http_stream->GetState());
// Attempt to read data, and discover EOS
VerifyDocumentStreamIsEOS();
// Document completed successfully
VerifyTransferComplete(HM_RECV, HE_NONE);
VerifyDocumentContents("");
}
TEST_F(HttpBaseTest, SignalsDocumentStreamCloseOnUnexpectedClose) {
// Switch to pull mode
ObtainDocumentStream();
VerifyDocumentStreamIsOpening();
// Queue response document
SetupSource(kHttpResponsePrefix);
VerifyDocumentStreamIsOpening();
// Begin receive
base.recv(&data);
// Pull document data
VerifyDocumentStreamOpenEvent();
ReadDocumentStreamData("Goodbye!");
// Simulate unexpected close
src.SetState(SS_CLOSED);
// Observe error event on document stream
EXPECT_EQ(testing::SSE_ERROR, sink.Events(http_stream));
// Future reads give an error
int error = 0;
char buffer[5] = { 0 };
EXPECT_EQ(SR_ERROR,
http_stream->Read(buffer, sizeof(buffer), nullptr, &error));
EXPECT_EQ(HE_DISCONNECTED, error);
// Document completed with error
VerifyHeaderComplete(2, false);
VerifyTransferComplete(HM_RECV, HE_DISCONNECTED);
VerifyDocumentContents("");
}
} // namespace rtc

View File

@ -0,0 +1,132 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef WEBRTC_BASE_HTTPCOMMON_INL_H__
#define WEBRTC_BASE_HTTPCOMMON_INL_H__
#include "webrtc/base/arraysize.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/httpcommon.h"
namespace rtc {
///////////////////////////////////////////////////////////////////////////////
// Url
///////////////////////////////////////////////////////////////////////////////
template<class CTYPE>
void Url<CTYPE>::do_set_url(const CTYPE* val, size_t len) {
if (ascnicmp(val, "http://", 7) == 0) {
val += 7; len -= 7;
secure_ = false;
} else if (ascnicmp(val, "https://", 8) == 0) {
val += 8; len -= 8;
secure_ = true;
} else {
clear();
return;
}
const CTYPE* path = strchrn(val, len, static_cast<CTYPE>('/'));
if (!path) {
path = val + len;
}
size_t address_length = (path - val);
do_set_address(val, address_length);
do_set_full_path(path, len - address_length);
}
template<class CTYPE>
void Url<CTYPE>::do_set_address(const CTYPE* val, size_t len) {
if (const CTYPE* at = strchrn(val, len, static_cast<CTYPE>('@'))) {
// Everything before the @ is a user:password combo, so skip it.
len -= at - val + 1;
val = at + 1;
}
if (const CTYPE* colon = strchrn(val, len, static_cast<CTYPE>(':'))) {
host_.assign(val, colon - val);
// Note: In every case, we're guaranteed that colon is followed by a null,
// or non-numeric character.
port_ = static_cast<uint16_t>(::strtoul(colon + 1, nullptr, 10));
// TODO: Consider checking for invalid data following port number.
} else {
host_.assign(val, len);
port_ = HttpDefaultPort(secure_);
}
}
template<class CTYPE>
void Url<CTYPE>::do_set_full_path(const CTYPE* val, size_t len) {
const CTYPE* query = strchrn(val, len, static_cast<CTYPE>('?'));
if (!query) {
query = val + len;
}
size_t path_length = (query - val);
if (0 == path_length) {
// TODO: consider failing in this case.
path_.assign(1, static_cast<CTYPE>('/'));
} else {
RTC_DCHECK(val[0] == static_cast<CTYPE>('/'));
path_.assign(val, path_length);
}
query_.assign(query, len - path_length);
}
template<class CTYPE>
void Url<CTYPE>::do_get_url(string* val) const {
CTYPE protocol[9];
asccpyn(protocol, arraysize(protocol), secure_ ? "https://" : "http://");
val->append(protocol);
do_get_address(val);
do_get_full_path(val);
}
template<class CTYPE>
void Url<CTYPE>::do_get_address(string* val) const {
val->append(host_);
if (port_ != HttpDefaultPort(secure_)) {
CTYPE format[5], port[32];
asccpyn(format, arraysize(format), ":%hu");
sprintfn(port, arraysize(port), format, port_);
val->append(port);
}
}
template<class CTYPE>
void Url<CTYPE>::do_get_full_path(string* val) const {
val->append(path_);
val->append(query_);
}
template<class CTYPE>
bool Url<CTYPE>::get_attribute(const string& name, string* value) const {
if (query_.empty())
return false;
std::string::size_type pos = query_.find(name, 1);
if (std::string::npos == pos)
return false;
pos += name.length() + 1;
if ((pos > query_.length()) || (static_cast<CTYPE>('=') != query_[pos-1]))
return false;
std::string::size_type end = query_.find(static_cast<CTYPE>('&'), pos);
if (std::string::npos == end) {
end = query_.length();
}
value->assign(query_.substr(pos, end - pos));
return true;
}
///////////////////////////////////////////////////////////////////////////////
} // namespace rtc
#endif // WEBRTC_BASE_HTTPCOMMON_INL_H__

1009
webrtc/base/httpcommon.cc Normal file

File diff suppressed because it is too large Load Diff

458
webrtc/base/httpcommon.h Normal file
View File

@ -0,0 +1,458 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef WEBRTC_BASE_HTTPCOMMON_H__
#define WEBRTC_BASE_HTTPCOMMON_H__
#include <map>
#include <memory>
#include <string>
#include <vector>
#include "webrtc/base/basictypes.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/stringutils.h"
#include "webrtc/base/stream.h"
namespace rtc {
class CryptString;
class SocketAddress;
//////////////////////////////////////////////////////////////////////
// Constants
//////////////////////////////////////////////////////////////////////
enum HttpCode {
HC_OK = 200,
HC_NON_AUTHORITATIVE = 203,
HC_NO_CONTENT = 204,
HC_PARTIAL_CONTENT = 206,
HC_MULTIPLE_CHOICES = 300,
HC_MOVED_PERMANENTLY = 301,
HC_FOUND = 302,
HC_SEE_OTHER = 303,
HC_NOT_MODIFIED = 304,
HC_MOVED_TEMPORARILY = 307,
HC_BAD_REQUEST = 400,
HC_UNAUTHORIZED = 401,
HC_FORBIDDEN = 403,
HC_NOT_FOUND = 404,
HC_PROXY_AUTHENTICATION_REQUIRED = 407,
HC_GONE = 410,
HC_INTERNAL_SERVER_ERROR = 500,
HC_NOT_IMPLEMENTED = 501,
HC_SERVICE_UNAVAILABLE = 503,
};
enum HttpVersion {
HVER_1_0, HVER_1_1, HVER_UNKNOWN,
HVER_LAST = HVER_UNKNOWN
};
enum HttpVerb {
HV_GET, HV_POST, HV_PUT, HV_DELETE, HV_CONNECT, HV_HEAD,
HV_LAST = HV_HEAD
};
enum HttpError {
HE_NONE,
HE_PROTOCOL, // Received non-valid HTTP data
HE_DISCONNECTED, // Connection closed unexpectedly
HE_OVERFLOW, // Received too much data for internal buffers
HE_CONNECT_FAILED, // The socket failed to connect.
HE_SOCKET_ERROR, // An error occurred on a connected socket
HE_SHUTDOWN, // Http object is being destroyed
HE_OPERATION_CANCELLED, // Connection aborted locally
HE_AUTH, // Proxy Authentication Required
HE_CERTIFICATE_EXPIRED, // During SSL negotiation
HE_STREAM, // Problem reading or writing to the document
HE_CACHE, // Problem reading from cache
HE_DEFAULT
};
enum HttpHeader {
HH_AGE,
HH_CACHE_CONTROL,
HH_CONNECTION,
HH_CONTENT_DISPOSITION,
HH_CONTENT_LENGTH,
HH_CONTENT_RANGE,
HH_CONTENT_TYPE,
HH_COOKIE,
HH_DATE,
HH_ETAG,
HH_EXPIRES,
HH_HOST,
HH_IF_MODIFIED_SINCE,
HH_IF_NONE_MATCH,
HH_KEEP_ALIVE,
HH_LAST_MODIFIED,
HH_LOCATION,
HH_PROXY_AUTHENTICATE,
HH_PROXY_AUTHORIZATION,
HH_PROXY_CONNECTION,
HH_RANGE,
HH_SET_COOKIE,
HH_TE,
HH_TRAILERS,
HH_TRANSFER_ENCODING,
HH_UPGRADE,
HH_USER_AGENT,
HH_WWW_AUTHENTICATE,
HH_LAST = HH_WWW_AUTHENTICATE
};
const uint16_t HTTP_DEFAULT_PORT = 80;
const uint16_t HTTP_SECURE_PORT = 443;
//////////////////////////////////////////////////////////////////////
// Utility Functions
//////////////////////////////////////////////////////////////////////
inline HttpError mkerr(HttpError err, HttpError def_err = HE_DEFAULT) {
return (err != HE_NONE) ? err : def_err;
}
const char* ToString(HttpVersion version);
bool FromString(HttpVersion& version, const std::string& str);
const char* ToString(HttpVerb verb);
bool FromString(HttpVerb& verb, const std::string& str);
const char* ToString(HttpHeader header);
bool FromString(HttpHeader& header, const std::string& str);
inline bool HttpCodeIsInformational(uint32_t code) {
return ((code / 100) == 1);
}
inline bool HttpCodeIsSuccessful(uint32_t code) {
return ((code / 100) == 2);
}
inline bool HttpCodeIsRedirection(uint32_t code) {
return ((code / 100) == 3);
}
inline bool HttpCodeIsClientError(uint32_t code) {
return ((code / 100) == 4);
}
inline bool HttpCodeIsServerError(uint32_t code) {
return ((code / 100) == 5);
}
bool HttpCodeHasBody(uint32_t code);
bool HttpCodeIsCacheable(uint32_t code);
bool HttpHeaderIsEndToEnd(HttpHeader header);
bool HttpHeaderIsCollapsible(HttpHeader header);
struct HttpData;
bool HttpShouldKeepAlive(const HttpData& data);
typedef std::pair<std::string, std::string> HttpAttribute;
typedef std::vector<HttpAttribute> HttpAttributeList;
void HttpComposeAttributes(const HttpAttributeList& attributes, char separator,
std::string* composed);
void HttpParseAttributes(const char * data, size_t len,
HttpAttributeList& attributes);
bool HttpHasAttribute(const HttpAttributeList& attributes,
const std::string& name,
std::string* value);
bool HttpHasNthAttribute(HttpAttributeList& attributes,
size_t index,
std::string* name,
std::string* value);
// Convert RFC1123 date (DoW, DD Mon YYYY HH:MM:SS TZ) to unix timestamp
bool HttpDateToSeconds(const std::string& date, time_t* seconds);
inline uint16_t HttpDefaultPort(bool secure) {
return secure ? HTTP_SECURE_PORT : HTTP_DEFAULT_PORT;
}
// Returns the http server notation for a given address
std::string HttpAddress(const SocketAddress& address, bool secure);
// functional for insensitive std::string compare
struct iless {
bool operator()(const std::string& lhs, const std::string& rhs) const {
return (::_stricmp(lhs.c_str(), rhs.c_str()) < 0);
}
};
// put quotes around a string and escape any quotes inside it
std::string quote(const std::string& str);
//////////////////////////////////////////////////////////////////////
// Url
//////////////////////////////////////////////////////////////////////
template<class CTYPE>
class Url {
public:
typedef typename Traits<CTYPE>::string string;
// TODO: Implement Encode/Decode
static int Encode(const CTYPE* source, CTYPE* destination, size_t len);
static int Encode(const string& source, string& destination);
static int Decode(const CTYPE* source, CTYPE* destination, size_t len);
static int Decode(const string& source, string& destination);
Url(const string& url) { do_set_url(url.c_str(), url.size()); }
Url(const string& path, const string& host, uint16_t port = HTTP_DEFAULT_PORT)
: host_(host), port_(port), secure_(HTTP_SECURE_PORT == port) {
set_full_path(path);
}
bool valid() const { return !host_.empty(); }
void clear() {
host_.clear();
port_ = HTTP_DEFAULT_PORT;
secure_ = false;
path_.assign(1, static_cast<CTYPE>('/'));
query_.clear();
}
void set_url(const string& val) {
do_set_url(val.c_str(), val.size());
}
string url() const {
string val; do_get_url(&val); return val;
}
void set_address(const string& val) {
do_set_address(val.c_str(), val.size());
}
string address() const {
string val; do_get_address(&val); return val;
}
void set_full_path(const string& val) {
do_set_full_path(val.c_str(), val.size());
}
string full_path() const {
string val; do_get_full_path(&val); return val;
}
void set_host(const string& val) { host_ = val; }
const string& host() const { return host_; }
void set_port(uint16_t val) { port_ = val; }
uint16_t port() const { return port_; }
void set_secure(bool val) { secure_ = val; }
bool secure() const { return secure_; }
void set_path(const string& val) {
if (val.empty()) {
path_.assign(1, static_cast<CTYPE>('/'));
} else {
RTC_DCHECK(val[0] == static_cast<CTYPE>('/'));
path_ = val;
}
}
const string& path() const { return path_; }
void set_query(const string& val) {
RTC_DCHECK(val.empty() || (val[0] == static_cast<CTYPE>('?')));
query_ = val;
}
const string& query() const { return query_; }
bool get_attribute(const string& name, string* value) const;
private:
void do_set_url(const CTYPE* val, size_t len);
void do_set_address(const CTYPE* val, size_t len);
void do_set_full_path(const CTYPE* val, size_t len);
void do_get_url(string* val) const;
void do_get_address(string* val) const;
void do_get_full_path(string* val) const;
string host_, path_, query_;
uint16_t port_;
bool secure_;
};
//////////////////////////////////////////////////////////////////////
// HttpData
//////////////////////////////////////////////////////////////////////
struct HttpData {
typedef std::multimap<std::string, std::string, iless> HeaderMap;
typedef HeaderMap::const_iterator const_iterator;
typedef HeaderMap::iterator iterator;
HttpVersion version;
std::unique_ptr<StreamInterface> document;
HttpData();
enum HeaderCombine { HC_YES, HC_NO, HC_AUTO, HC_REPLACE, HC_NEW };
void changeHeader(const std::string& name, const std::string& value,
HeaderCombine combine);
inline void addHeader(const std::string& name, const std::string& value,
bool append = true) {
changeHeader(name, value, append ? HC_AUTO : HC_NO);
}
inline void setHeader(const std::string& name, const std::string& value,
bool overwrite = true) {
changeHeader(name, value, overwrite ? HC_REPLACE : HC_NEW);
}
// Returns count of erased headers
size_t clearHeader(const std::string& name);
// Returns iterator to next header
iterator clearHeader(iterator header);
// keep in mind, this may not do what you want in the face of multiple headers
bool hasHeader(const std::string& name, std::string* value) const;
inline const_iterator begin() const {
return headers_.begin();
}
inline const_iterator end() const {
return headers_.end();
}
inline iterator begin() {
return headers_.begin();
}
inline iterator end() {
return headers_.end();
}
inline const_iterator begin(const std::string& name) const {
return headers_.lower_bound(name);
}
inline const_iterator end(const std::string& name) const {
return headers_.upper_bound(name);
}
inline iterator begin(const std::string& name) {
return headers_.lower_bound(name);
}
inline iterator end(const std::string& name) {
return headers_.upper_bound(name);
}
// Convenience methods using HttpHeader
inline void changeHeader(HttpHeader header, const std::string& value,
HeaderCombine combine) {
changeHeader(ToString(header), value, combine);
}
inline void addHeader(HttpHeader header, const std::string& value,
bool append = true) {
addHeader(ToString(header), value, append);
}
inline void setHeader(HttpHeader header, const std::string& value,
bool overwrite = true) {
setHeader(ToString(header), value, overwrite);
}
inline void clearHeader(HttpHeader header) {
clearHeader(ToString(header));
}
inline bool hasHeader(HttpHeader header, std::string* value) const {
return hasHeader(ToString(header), value);
}
inline const_iterator begin(HttpHeader header) const {
return headers_.lower_bound(ToString(header));
}
inline const_iterator end(HttpHeader header) const {
return headers_.upper_bound(ToString(header));
}
inline iterator begin(HttpHeader header) {
return headers_.lower_bound(ToString(header));
}
inline iterator end(HttpHeader header) {
return headers_.upper_bound(ToString(header));
}
void setContent(const std::string& content_type, StreamInterface* document);
void setDocumentAndLength(StreamInterface* document);
virtual size_t formatLeader(char* buffer, size_t size) const = 0;
virtual HttpError parseLeader(const char* line, size_t len) = 0;
protected:
virtual ~HttpData();
void clear(bool release_document);
void copy(const HttpData& src);
private:
HeaderMap headers_;
};
struct HttpRequestData : public HttpData {
HttpVerb verb;
std::string path;
HttpRequestData() : verb(HV_GET) { }
void clear(bool release_document);
void copy(const HttpRequestData& src);
size_t formatLeader(char* buffer, size_t size) const override;
HttpError parseLeader(const char* line, size_t len) override;
bool getAbsoluteUri(std::string* uri) const;
bool getRelativeUri(std::string* host, std::string* path) const;
};
struct HttpResponseData : public HttpData {
uint32_t scode;
std::string message;
HttpResponseData() : scode(HC_INTERNAL_SERVER_ERROR) { }
void clear(bool release_document);
void copy(const HttpResponseData& src);
// Convenience methods
void set_success(uint32_t scode = HC_OK);
void set_success(const std::string& content_type,
StreamInterface* document,
uint32_t scode = HC_OK);
void set_redirect(const std::string& location,
uint32_t scode = HC_MOVED_TEMPORARILY);
void set_error(uint32_t scode);
size_t formatLeader(char* buffer, size_t size) const override;
HttpError parseLeader(const char* line, size_t len) override;
};
struct HttpTransaction {
HttpRequestData request;
HttpResponseData response;
};
//////////////////////////////////////////////////////////////////////
// Http Authentication
//////////////////////////////////////////////////////////////////////
struct HttpAuthContext {
std::string auth_method;
HttpAuthContext(const std::string& auth) : auth_method(auth) { }
virtual ~HttpAuthContext() { }
};
enum HttpAuthResult { HAR_RESPONSE, HAR_IGNORE, HAR_CREDENTIALS, HAR_ERROR };
// 'context' is used by this function to record information between calls.
// Start by passing a null pointer, then pass the same pointer each additional
// call. When the authentication attempt is finished, delete the context.
HttpAuthResult HttpAuthenticate(
const char * challenge, size_t len,
const SocketAddress& server,
const std::string& method, const std::string& uri,
const std::string& username, const CryptString& password,
HttpAuthContext *& context, std::string& response, std::string& auth_method);
//////////////////////////////////////////////////////////////////////
} // namespace rtc
#endif // WEBRTC_BASE_HTTPCOMMON_H__

View File

@ -0,0 +1,165 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/base/gunit.h"
#include "webrtc/base/httpcommon-inl.h"
#include "webrtc/base/httpcommon.h"
namespace rtc {
#define TEST_PROTOCOL "http://"
#define TEST_HOST "www.google.com"
#define TEST_PATH "/folder/file.html"
#define TEST_QUERY "?query=x&attr=y"
#define TEST_URL TEST_PROTOCOL TEST_HOST TEST_PATH TEST_QUERY
TEST(Url, DecomposesUrls) {
Url<char> url(TEST_URL);
EXPECT_TRUE(url.valid());
EXPECT_FALSE(url.secure());
EXPECT_STREQ(TEST_HOST, url.host().c_str());
EXPECT_EQ(80, url.port());
EXPECT_STREQ(TEST_PATH, url.path().c_str());
EXPECT_STREQ(TEST_QUERY, url.query().c_str());
EXPECT_STREQ(TEST_HOST, url.address().c_str());
EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
EXPECT_STREQ(TEST_URL, url.url().c_str());
}
TEST(Url, ComposesUrls) {
// Set in constructor
Url<char> url(TEST_PATH TEST_QUERY, TEST_HOST, 80);
EXPECT_TRUE(url.valid());
EXPECT_FALSE(url.secure());
EXPECT_STREQ(TEST_HOST, url.host().c_str());
EXPECT_EQ(80, url.port());
EXPECT_STREQ(TEST_PATH, url.path().c_str());
EXPECT_STREQ(TEST_QUERY, url.query().c_str());
EXPECT_STREQ(TEST_HOST, url.address().c_str());
EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
EXPECT_STREQ(TEST_URL, url.url().c_str());
url.clear();
EXPECT_FALSE(url.valid());
EXPECT_FALSE(url.secure());
EXPECT_STREQ("", url.host().c_str());
EXPECT_EQ(80, url.port());
EXPECT_STREQ("/", url.path().c_str());
EXPECT_STREQ("", url.query().c_str());
// Set component-wise
url.set_host(TEST_HOST);
url.set_port(80);
url.set_path(TEST_PATH);
url.set_query(TEST_QUERY);
EXPECT_TRUE(url.valid());
EXPECT_FALSE(url.secure());
EXPECT_STREQ(TEST_HOST, url.host().c_str());
EXPECT_EQ(80, url.port());
EXPECT_STREQ(TEST_PATH, url.path().c_str());
EXPECT_STREQ(TEST_QUERY, url.query().c_str());
EXPECT_STREQ(TEST_HOST, url.address().c_str());
EXPECT_STREQ(TEST_PATH TEST_QUERY, url.full_path().c_str());
EXPECT_STREQ(TEST_URL, url.url().c_str());
}
TEST(Url, EnsuresNonEmptyPath) {
Url<char> url(TEST_PROTOCOL TEST_HOST);
EXPECT_TRUE(url.valid());
EXPECT_STREQ("/", url.path().c_str());
url.clear();
EXPECT_STREQ("/", url.path().c_str());
url.set_path("");
EXPECT_STREQ("/", url.path().c_str());
url.clear();
EXPECT_STREQ("/", url.path().c_str());
url.set_full_path("");
EXPECT_STREQ("/", url.path().c_str());
}
TEST(Url, GetQueryAttributes) {
Url<char> url(TEST_URL);
std::string value;
EXPECT_TRUE(url.get_attribute("query", &value));
EXPECT_STREQ("x", value.c_str());
value.clear();
EXPECT_TRUE(url.get_attribute("attr", &value));
EXPECT_STREQ("y", value.c_str());
value.clear();
EXPECT_FALSE(url.get_attribute("Query", &value));
EXPECT_TRUE(value.empty());
}
TEST(Url, SkipsUserAndPassword) {
Url<char> url("https://mail.google.com:pwd@badsite.com:12345/asdf");
EXPECT_TRUE(url.valid());
EXPECT_TRUE(url.secure());
EXPECT_STREQ("badsite.com", url.host().c_str());
EXPECT_EQ(12345, url.port());
EXPECT_STREQ("/asdf", url.path().c_str());
EXPECT_STREQ("badsite.com:12345", url.address().c_str());
}
TEST(Url, SkipsUser) {
Url<char> url("https://mail.google.com@badsite.com:12345/asdf");
EXPECT_TRUE(url.valid());
EXPECT_TRUE(url.secure());
EXPECT_STREQ("badsite.com", url.host().c_str());
EXPECT_EQ(12345, url.port());
EXPECT_STREQ("/asdf", url.path().c_str());
EXPECT_STREQ("badsite.com:12345", url.address().c_str());
}
TEST(HttpResponseData, parseLeaderHttp1_0) {
static const char kResponseString[] = "HTTP/1.0 200 OK";
HttpResponseData response;
EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
sizeof(kResponseString) - 1));
EXPECT_EQ(HVER_1_0, response.version);
EXPECT_EQ(200U, response.scode);
}
TEST(HttpResponseData, parseLeaderHttp1_1) {
static const char kResponseString[] = "HTTP/1.1 200 OK";
HttpResponseData response;
EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
sizeof(kResponseString) - 1));
EXPECT_EQ(HVER_1_1, response.version);
EXPECT_EQ(200U, response.scode);
}
TEST(HttpResponseData, parseLeaderHttpUnknown) {
static const char kResponseString[] = "HTTP 200 OK";
HttpResponseData response;
EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
sizeof(kResponseString) - 1));
EXPECT_EQ(HVER_UNKNOWN, response.version);
EXPECT_EQ(200U, response.scode);
}
TEST(HttpResponseData, parseLeaderHttpFailure) {
static const char kResponseString[] = "HTTP/1.1 503 Service Unavailable";
HttpResponseData response;
EXPECT_EQ(HE_NONE, response.parseLeader(kResponseString,
sizeof(kResponseString) - 1));
EXPECT_EQ(HVER_1_1, response.version);
EXPECT_EQ(503U, response.scode);
}
TEST(HttpResponseData, parseLeaderHttpInvalid) {
static const char kResponseString[] = "Durrrrr, what's HTTP?";
HttpResponseData response;
EXPECT_EQ(HE_PROTOCOL, response.parseLeader(kResponseString,
sizeof(kResponseString) - 1));
}
} // namespace rtc

288
webrtc/base/httpserver.cc Normal file
View File

@ -0,0 +1,288 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <algorithm>
#include "webrtc/base/httpcommon-inl.h"
#include "webrtc/base/asyncsocket.h"
#include "webrtc/base/checks.h"
#include "webrtc/base/httpserver.h"
#include "webrtc/base/logging.h"
#include "webrtc/base/socketstream.h"
#include "webrtc/base/thread.h"
namespace rtc {
///////////////////////////////////////////////////////////////////////////////
// HttpServer
///////////////////////////////////////////////////////////////////////////////
HttpServer::HttpServer() : next_connection_id_(1), closing_(false) {
}
HttpServer::~HttpServer() {
if (closing_) {
LOG(LS_WARNING) << "HttpServer::CloseAll has not completed";
}
for (ConnectionMap::iterator it = connections_.begin();
it != connections_.end();
++it) {
StreamInterface* stream = it->second->EndProcess();
delete stream;
delete it->second;
}
}
int
HttpServer::HandleConnection(StreamInterface* stream) {
int connection_id = next_connection_id_++;
RTC_DCHECK(connection_id != HTTP_INVALID_CONNECTION_ID);
Connection* connection = new Connection(connection_id, this);
connections_.insert(ConnectionMap::value_type(connection_id, connection));
connection->BeginProcess(stream);
return connection_id;
}
void
HttpServer::Respond(HttpServerTransaction* transaction) {
int connection_id = transaction->connection_id();
if (Connection* connection = Find(connection_id)) {
connection->Respond(transaction);
} else {
delete transaction;
// We may be tempted to SignalHttpComplete, but that implies that a
// connection still exists.
}
}
void
HttpServer::Close(int connection_id, bool force) {
if (Connection* connection = Find(connection_id)) {
connection->InitiateClose(force);
}
}
void
HttpServer::CloseAll(bool force) {
if (connections_.empty()) {
SignalCloseAllComplete(this);
return;
}
closing_ = true;
std::list<Connection*> connections;
for (ConnectionMap::const_iterator it = connections_.begin();
it != connections_.end(); ++it) {
connections.push_back(it->second);
}
for (std::list<Connection*>::const_iterator it = connections.begin();
it != connections.end(); ++it) {
(*it)->InitiateClose(force);
}
}
HttpServer::Connection*
HttpServer::Find(int connection_id) {
ConnectionMap::iterator it = connections_.find(connection_id);
if (it == connections_.end())
return nullptr;
return it->second;
}
void
HttpServer::Remove(int connection_id) {
ConnectionMap::iterator it = connections_.find(connection_id);
if (it == connections_.end()) {
RTC_NOTREACHED();
return;
}
Connection* connection = it->second;
connections_.erase(it);
SignalConnectionClosed(this, connection_id, connection->EndProcess());
delete connection;
if (closing_ && connections_.empty()) {
closing_ = false;
SignalCloseAllComplete(this);
}
}
///////////////////////////////////////////////////////////////////////////////
// HttpServer::Connection
///////////////////////////////////////////////////////////////////////////////
HttpServer::Connection::Connection(int connection_id, HttpServer* server)
: connection_id_(connection_id),
server_(server),
current_(nullptr),
signalling_(false),
close_(false) {}
HttpServer::Connection::~Connection() {
// It's possible that an object hosted inside this transaction signalled
// an event which caused the connection to close.
Thread::Current()->Dispose(current_);
}
void
HttpServer::Connection::BeginProcess(StreamInterface* stream) {
base_.notify(this);
base_.attach(stream);
current_ = new HttpServerTransaction(connection_id_);
if (base_.mode() != HM_CONNECT)
base_.recv(&current_->request);
}
StreamInterface*
HttpServer::Connection::EndProcess() {
base_.notify(nullptr);
base_.abort(HE_DISCONNECTED);
return base_.detach();
}
void
HttpServer::Connection::Respond(HttpServerTransaction* transaction) {
RTC_DCHECK(current_ == nullptr);
current_ = transaction;
if (current_->response.begin() == current_->response.end()) {
current_->response.set_error(HC_INTERNAL_SERVER_ERROR);
}
bool keep_alive = HttpShouldKeepAlive(current_->request);
current_->response.setHeader(HH_CONNECTION,
keep_alive ? "Keep-Alive" : "Close",
false);
close_ = !HttpShouldKeepAlive(current_->response);
base_.send(&current_->response);
}
void
HttpServer::Connection::InitiateClose(bool force) {
bool request_in_progress = (HM_SEND == base_.mode()) || (nullptr == current_);
if (!signalling_ && (force || !request_in_progress)) {
server_->Remove(connection_id_);
} else {
close_ = true;
}
}
//
// IHttpNotify Implementation
//
HttpError
HttpServer::Connection::onHttpHeaderComplete(bool chunked, size_t& data_size) {
if (data_size == SIZE_UNKNOWN) {
data_size = 0;
}
RTC_DCHECK(current_ != nullptr);
bool custom_document = false;
server_->SignalHttpRequestHeader(server_, current_, &custom_document);
if (!custom_document) {
current_->request.document.reset(new MemoryStream);
}
return HE_NONE;
}
void
HttpServer::Connection::onHttpComplete(HttpMode mode, HttpError err) {
if (mode == HM_SEND) {
RTC_DCHECK(current_ != nullptr);
signalling_ = true;
server_->SignalHttpRequestComplete(server_, current_, err);
signalling_ = false;
if (close_) {
// Force a close
err = HE_DISCONNECTED;
}
}
if (err != HE_NONE) {
server_->Remove(connection_id_);
} else if (mode == HM_CONNECT) {
base_.recv(&current_->request);
} else if (mode == HM_RECV) {
RTC_DCHECK(current_ != nullptr);
// TODO: do we need this?
//request_.document_->rewind();
HttpServerTransaction* transaction = current_;
current_ = nullptr;
server_->SignalHttpRequest(server_, transaction);
} else if (mode == HM_SEND) {
Thread::Current()->Dispose(current_->response.document.release());
current_->request.clear(true);
current_->response.clear(true);
base_.recv(&current_->request);
} else {
RTC_NOTREACHED();
}
}
void
HttpServer::Connection::onHttpClosed(HttpError err) {
server_->Remove(connection_id_);
}
///////////////////////////////////////////////////////////////////////////////
// HttpListenServer
///////////////////////////////////////////////////////////////////////////////
HttpListenServer::HttpListenServer() {
SignalConnectionClosed.connect(this, &HttpListenServer::OnConnectionClosed);
}
HttpListenServer::~HttpListenServer() {
}
int HttpListenServer::Listen(const SocketAddress& address) {
AsyncSocket* sock =
Thread::Current()->socketserver()->CreateAsyncSocket(address.family(),
SOCK_STREAM);
if (!sock) {
return SOCKET_ERROR;
}
listener_.reset(sock);
listener_->SignalReadEvent.connect(this, &HttpListenServer::OnReadEvent);
if ((listener_->Bind(address) != SOCKET_ERROR) &&
(listener_->Listen(5) != SOCKET_ERROR))
return 0;
return listener_->GetError();
}
bool HttpListenServer::GetAddress(SocketAddress* address) const {
if (!listener_) {
return false;
}
*address = listener_->GetLocalAddress();
return !address->IsNil();
}
void HttpListenServer::StopListening() {
if (listener_) {
listener_->Close();
}
}
void HttpListenServer::OnReadEvent(AsyncSocket* socket) {
RTC_DCHECK(socket == listener_.get());
AsyncSocket* incoming = listener_->Accept(nullptr);
if (incoming) {
StreamInterface* stream = new SocketStream(incoming);
//stream = new LoggingAdapter(stream, LS_VERBOSE, "HttpServer", false);
HandleConnection(stream);
}
}
void HttpListenServer::OnConnectionClosed(HttpServer* server,
int connection_id,
StreamInterface* stream) {
Thread::Current()->Dispose(stream);
}
///////////////////////////////////////////////////////////////////////////////
} // namespace rtc

139
webrtc/base/httpserver.h Normal file
View File

@ -0,0 +1,139 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#ifndef WEBRTC_BASE_HTTPSERVER_H__
#define WEBRTC_BASE_HTTPSERVER_H__
#include <map>
#include <memory>
#include "webrtc/base/httpbase.h"
namespace rtc {
class AsyncSocket;
class HttpServer;
class SocketAddress;
//////////////////////////////////////////////////////////////////////
// HttpServer
//////////////////////////////////////////////////////////////////////
const int HTTP_INVALID_CONNECTION_ID = 0;
struct HttpServerTransaction : public HttpTransaction {
public:
HttpServerTransaction(int id) : connection_id_(id) { }
int connection_id() const { return connection_id_; }
private:
int connection_id_;
};
class HttpServer {
public:
HttpServer();
virtual ~HttpServer();
int HandleConnection(StreamInterface* stream);
// Due to sigslot issues, we can't destroy some streams at an arbitrary time.
sigslot::signal3<HttpServer*, int, StreamInterface*> SignalConnectionClosed;
// This signal occurs when the HTTP request headers have been received, but
// before the request body is written to the request document. By default,
// the request document is a MemoryStream. By handling this signal, the
// document can be overridden, in which case the third signal argument should
// be set to true. In the case where the request body should be ignored,
// the document can be set to null. Note that the transaction object is still
// owened by the HttpServer at this point.
sigslot::signal3<HttpServer*, HttpServerTransaction*, bool*>
SignalHttpRequestHeader;
// An HTTP request has been made, and is available in the transaction object.
// Populate the transaction's response, and then return the object via the
// Respond method. Note that during this time, ownership of the transaction
// object is transferred, so it may be passed between threads, although
// respond must be called on the server's active thread.
sigslot::signal2<HttpServer*, HttpServerTransaction*> SignalHttpRequest;
void Respond(HttpServerTransaction* transaction);
// If you want to know when a request completes, listen to this event.
sigslot::signal3<HttpServer*, HttpServerTransaction*, int>
SignalHttpRequestComplete;
// Stop processing the connection indicated by connection_id.
// Unless force is true, the server will complete sending a response that is
// in progress.
void Close(int connection_id, bool force);
void CloseAll(bool force);
// After calling CloseAll, this event is signalled to indicate that all
// outstanding connections have closed.
sigslot::signal1<HttpServer*> SignalCloseAllComplete;
private:
class Connection : private IHttpNotify {
public:
Connection(int connection_id, HttpServer* server);
~Connection() override;
void BeginProcess(StreamInterface* stream);
StreamInterface* EndProcess();
void Respond(HttpServerTransaction* transaction);
void InitiateClose(bool force);
// IHttpNotify Interface
HttpError onHttpHeaderComplete(bool chunked, size_t& data_size) override;
void onHttpComplete(HttpMode mode, HttpError err) override;
void onHttpClosed(HttpError err) override;
int connection_id_;
HttpServer* server_;
HttpBase base_;
HttpServerTransaction* current_;
bool signalling_, close_;
};
Connection* Find(int connection_id);
void Remove(int connection_id);
friend class Connection;
typedef std::map<int,Connection*> ConnectionMap;
ConnectionMap connections_;
int next_connection_id_;
bool closing_;
};
//////////////////////////////////////////////////////////////////////
class HttpListenServer : public HttpServer, public sigslot::has_slots<> {
public:
HttpListenServer();
~HttpListenServer() override;
int Listen(const SocketAddress& address);
bool GetAddress(SocketAddress* address) const;
void StopListening();
private:
void OnReadEvent(AsyncSocket* socket);
void OnConnectionClosed(HttpServer* server, int connection_id,
StreamInterface* stream);
std::unique_ptr<AsyncSocket> listener_;
};
//////////////////////////////////////////////////////////////////////
} // namespace rtc
#endif // WEBRTC_BASE_HTTPSERVER_H__

View File

@ -0,0 +1,130 @@
/*
* Copyright 2007 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/base/gunit.h"
#include "webrtc/base/httpserver.h"
#include "webrtc/base/testutils.h"
using namespace testing;
namespace rtc {
namespace {
const char* const kRequest =
"GET /index.html HTTP/1.1\r\n"
"Host: localhost\r\n"
"\r\n";
struct HttpServerMonitor : public sigslot::has_slots<> {
HttpServerTransaction* transaction;
bool server_closed, connection_closed;
HttpServerMonitor(HttpServer* server)
: transaction(nullptr), server_closed(false), connection_closed(false) {
server->SignalCloseAllComplete.connect(this,
&HttpServerMonitor::OnClosed);
server->SignalHttpRequest.connect(this, &HttpServerMonitor::OnRequest);
server->SignalHttpRequestComplete.connect(this,
&HttpServerMonitor::OnRequestComplete);
server->SignalConnectionClosed.connect(this,
&HttpServerMonitor::OnConnectionClosed);
}
void OnRequest(HttpServer*, HttpServerTransaction* t) {
ASSERT_FALSE(transaction);
transaction = t;
transaction->response.set_success();
transaction->response.setHeader(HH_CONNECTION, "Close");
}
void OnRequestComplete(HttpServer*, HttpServerTransaction* t, int) {
ASSERT_EQ(transaction, t);
transaction = nullptr;
}
void OnClosed(HttpServer*) {
server_closed = true;
}
void OnConnectionClosed(HttpServer*, int, StreamInterface* stream) {
connection_closed = true;
delete stream;
}
};
void CreateClientConnection(HttpServer& server,
HttpServerMonitor& monitor,
bool send_request) {
StreamSource* client = new StreamSource;
client->SetState(SS_OPEN);
server.HandleConnection(client);
EXPECT_FALSE(monitor.server_closed);
EXPECT_FALSE(monitor.transaction);
if (send_request) {
// Simulate a request
client->QueueString(kRequest);
EXPECT_FALSE(monitor.server_closed);
}
}
} // anonymous namespace
TEST(HttpServer, DoesNotSignalCloseUnlessCloseAllIsCalled) {
HttpServer server;
HttpServerMonitor monitor(&server);
// Add an active client connection
CreateClientConnection(server, monitor, true);
// Simulate a response
ASSERT_TRUE(nullptr != monitor.transaction);
server.Respond(monitor.transaction);
EXPECT_FALSE(monitor.transaction);
// Connection has closed, but no server close signal
EXPECT_FALSE(monitor.server_closed);
EXPECT_TRUE(monitor.connection_closed);
}
TEST(HttpServer, SignalsCloseWhenNoConnectionsAreActive) {
HttpServer server;
HttpServerMonitor monitor(&server);
// Add an idle client connection
CreateClientConnection(server, monitor, false);
// Perform graceful close
server.CloseAll(false);
// Connections have all closed
EXPECT_TRUE(monitor.server_closed);
EXPECT_TRUE(monitor.connection_closed);
}
TEST(HttpServer, SignalsCloseAfterGracefulCloseAll) {
HttpServer server;
HttpServerMonitor monitor(&server);
// Add an active client connection
CreateClientConnection(server, monitor, true);
// Initiate a graceful close
server.CloseAll(false);
EXPECT_FALSE(monitor.server_closed);
// Simulate a response
ASSERT_TRUE(nullptr != monitor.transaction);
server.Respond(monitor.transaction);
EXPECT_FALSE(monitor.transaction);
// Connections have all closed
EXPECT_TRUE(monitor.server_closed);
EXPECT_TRUE(monitor.connection_closed);
}
TEST(HttpServer, SignalsCloseAfterForcedCloseAll) {
HttpServer server;
HttpServerMonitor monitor(&server);
// Add an active client connection
CreateClientConnection(server, monitor, true);
// Initiate a forceful close
server.CloseAll(true);
// Connections have all closed
EXPECT_TRUE(monitor.server_closed);
EXPECT_TRUE(monitor.connection_closed);
}
} // namespace rtc

View File

@ -0,0 +1,76 @@
/*
* Copyright 2009 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include <memory>
#include <string>
#include "webrtc/base/gunit.h"
#include "webrtc/base/httpserver.h"
#include "webrtc/base/proxyserver.h"
#include "webrtc/base/socketadapters.h"
#include "webrtc/base/testclient.h"
#include "webrtc/base/testechoserver.h"
#include "webrtc/base/virtualsocketserver.h"
using rtc::Socket;
using rtc::Thread;
using rtc::SocketAddress;
static const SocketAddress kSocksProxyIntAddr("1.2.3.4", 1080);
static const SocketAddress kSocksProxyExtAddr("1.2.3.5", 0);
static const SocketAddress kHttpsProxyIntAddr("1.2.3.4", 443);
static const SocketAddress kHttpsProxyExtAddr("1.2.3.5", 0);
static const SocketAddress kBogusProxyIntAddr("1.2.3.4", 999);
// Sets up a virtual socket server and HTTPS/SOCKS5 proxy servers.
class ProxyTest : public testing::Test {
public:
ProxyTest() : ss_(new rtc::VirtualSocketServer(nullptr)) {
Thread::Current()->set_socketserver(ss_.get());
socks_.reset(new rtc::SocksProxyServer(
ss_.get(), kSocksProxyIntAddr, ss_.get(), kSocksProxyExtAddr));
https_.reset(new rtc::HttpListenServer());
https_->Listen(kHttpsProxyIntAddr);
}
~ProxyTest() { Thread::Current()->set_socketserver(nullptr); }
rtc::SocketServer* ss() { return ss_.get(); }
private:
std::unique_ptr<rtc::SocketServer> ss_;
std::unique_ptr<rtc::SocksProxyServer> socks_;
// TODO: Make this a real HTTPS proxy server.
std::unique_ptr<rtc::HttpListenServer> https_;
};
// Tests whether we can use a SOCKS5 proxy to connect to a server.
TEST_F(ProxyTest, TestSocks5Connect) {
rtc::AsyncSocket* socket =
ss()->CreateAsyncSocket(kSocksProxyIntAddr.family(), SOCK_STREAM);
rtc::AsyncSocksProxySocket* proxy_socket =
new rtc::AsyncSocksProxySocket(socket, kSocksProxyIntAddr,
"", rtc::CryptString());
// TODO: IPv6-ize these tests when proxy supports IPv6.
rtc::TestEchoServer server(Thread::Current(),
SocketAddress(INADDR_ANY, 0));
rtc::AsyncTCPSocket* packet_socket = rtc::AsyncTCPSocket::Create(
proxy_socket, SocketAddress(INADDR_ANY, 0), server.address());
EXPECT_TRUE(packet_socket != nullptr);
rtc::TestClient client(packet_socket);
EXPECT_EQ(Socket::CS_CONNECTING, proxy_socket->GetState());
EXPECT_TRUE(client.CheckConnected());
EXPECT_EQ(Socket::CS_CONNECTED, proxy_socket->GetState());
EXPECT_EQ(server.address(), client.remote_address());
client.Send("foo", 3);
EXPECT_TRUE(client.CheckNextPacket("foo", 3, nullptr));
EXPECT_TRUE(client.CheckNoPacket());
}

24
webrtc/base/proxyinfo.cc Normal file
View File

@ -0,0 +1,24 @@
/*
* Copyright 2004 The WebRTC Project Authors. All rights reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
#include "webrtc/base/proxyinfo.h"
namespace rtc {
const char * ProxyToString(ProxyType proxy) {
const char * const PROXY_NAMES[] = { "none", "https", "socks5", "unknown" };
return PROXY_NAMES[proxy];
}
ProxyInfo::ProxyInfo() : type(PROXY_NONE), autodetect(false) {
}
ProxyInfo::~ProxyInfo() = default;
} // namespace rtc

View File

@ -11,15 +11,31 @@
#ifndef WEBRTC_BASE_PROXYINFO_H__ #ifndef WEBRTC_BASE_PROXYINFO_H__
#define WEBRTC_BASE_PROXYINFO_H__ #define WEBRTC_BASE_PROXYINFO_H__
#include <string>
#include "webrtc/base/socketaddress.h"
#include "webrtc/base/cryptstring.h"
namespace rtc { namespace rtc {
// TODO(deadbeef): Remove this; it's not used any more but it's referenced in
// some places, including chromium.
enum ProxyType { enum ProxyType {
PROXY_NONE, PROXY_NONE,
PROXY_HTTPS,
PROXY_SOCKS5,
PROXY_UNKNOWN
}; };
const char * ProxyToString(ProxyType proxy);
struct ProxyInfo { struct ProxyInfo {
ProxyType type;
SocketAddress address;
std::string autoconfig_url;
bool autodetect;
std::string bypass_list;
std::string username;
CryptString password;
ProxyInfo();
~ProxyInfo();
}; };
} // namespace rtc } // namespace rtc

View File

@ -149,4 +149,8 @@ void ProxyBinding::Destroy() {
SignalDestroyed(this); SignalDestroyed(this);
} }
AsyncProxyServerSocket* SocksProxyServer::WrapSocket(AsyncSocket* socket) {
return new AsyncSocksProxyServerSocket(socket);
}
} // namespace rtc } // namespace rtc

View File

@ -83,6 +83,18 @@ class ProxyServer : public sigslot::has_slots<> {
RTC_DISALLOW_COPY_AND_ASSIGN(ProxyServer); RTC_DISALLOW_COPY_AND_ASSIGN(ProxyServer);
}; };
// SocksProxyServer is a simple extension of ProxyServer to implement SOCKS.
class SocksProxyServer : public ProxyServer {
public:
SocksProxyServer(SocketFactory* int_factory, const SocketAddress& int_addr,
SocketFactory* ext_factory, const SocketAddress& ext_ip)
: ProxyServer(int_factory, int_addr, ext_factory, ext_ip) {
}
protected:
AsyncProxyServerSocket* WrapSocket(AsyncSocket* socket) override;
RTC_DISALLOW_COPY_AND_ASSIGN(SocksProxyServer);
};
} // namespace rtc } // namespace rtc
#endif // WEBRTC_BASE_PROXYSERVER_H_ #endif // WEBRTC_BASE_PROXYSERVER_H_

View File

@ -28,6 +28,7 @@
#include "webrtc/base/bytebuffer.h" #include "webrtc/base/bytebuffer.h"
#include "webrtc/base/checks.h" #include "webrtc/base/checks.h"
#include "webrtc/base/httpcommon.h"
#include "webrtc/base/logging.h" #include "webrtc/base/logging.h"
#include "webrtc/base/socketadapters.h" #include "webrtc/base/socketadapters.h"
#include "webrtc/base/stringencode.h" #include "webrtc/base/stringencode.h"
@ -241,4 +242,607 @@ void AsyncSSLServerSocket::ProcessInput(char* data, size_t* len) {
BufferInput(false); BufferInput(false);
} }
///////////////////////////////////////////////////////////////////////////////
AsyncHttpsProxySocket::AsyncHttpsProxySocket(AsyncSocket* socket,
const std::string& user_agent,
const SocketAddress& proxy,
const std::string& username,
const CryptString& password)
: BufferedReadAdapter(socket, 1024), proxy_(proxy), agent_(user_agent),
user_(username), pass_(password), force_connect_(false), state_(PS_ERROR),
context_(0) {
}
AsyncHttpsProxySocket::~AsyncHttpsProxySocket() {
delete context_;
}
int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) {
int ret;
LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect("
<< proxy_.ToSensitiveString() << ")";
dest_ = addr;
state_ = PS_INIT;
if (ShouldIssueConnect()) {
BufferInput(true);
}
ret = BufferedReadAdapter::Connect(proxy_);
// TODO: Set state_ appropriately if Connect fails.
return ret;
}
SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const {
return dest_;
}
int AsyncHttpsProxySocket::Close() {
headers_.clear();
state_ = PS_ERROR;
dest_.Clear();
delete context_;
context_ = nullptr;
return BufferedReadAdapter::Close();
}
Socket::ConnState AsyncHttpsProxySocket::GetState() const {
if (state_ < PS_TUNNEL) {
return CS_CONNECTING;
} else if (state_ == PS_TUNNEL) {
return CS_CONNECTED;
} else {
return CS_CLOSED;
}
}
void AsyncHttpsProxySocket::OnConnectEvent(AsyncSocket * socket) {
LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent";
if (!ShouldIssueConnect()) {
state_ = PS_TUNNEL;
BufferedReadAdapter::OnConnectEvent(socket);
return;
}
SendRequest();
}
void AsyncHttpsProxySocket::OnCloseEvent(AsyncSocket * socket, int err) {
LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnCloseEvent(" << err << ")";
if ((state_ == PS_WAIT_CLOSE) && (err == 0)) {
state_ = PS_ERROR;
Connect(dest_);
} else {
BufferedReadAdapter::OnCloseEvent(socket, err);
}
}
void AsyncHttpsProxySocket::ProcessInput(char* data, size_t* len) {
size_t start = 0;
for (size_t pos = start; state_ < PS_TUNNEL && pos < *len;) {
if (state_ == PS_SKIP_BODY) {
size_t consume = std::min(*len - pos, content_length_);
pos += consume;
start = pos;
content_length_ -= consume;
if (content_length_ == 0) {
EndResponse();
}
continue;
}
if (data[pos++] != '\n')
continue;
size_t len = pos - start - 1;
if ((len > 0) && (data[start + len - 1] == '\r'))
--len;
data[start + len] = 0;
ProcessLine(data + start, len);
start = pos;
}
*len -= start;
if (*len > 0) {
memmove(data, data + start, *len);
}
if (state_ != PS_TUNNEL)
return;
bool remainder = (*len > 0);
BufferInput(false);
SignalConnectEvent(this);
// FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
if (remainder)
SignalReadEvent(this); // TODO: signal this??
}
bool AsyncHttpsProxySocket::ShouldIssueConnect() const {
// TODO: Think about whether a more sophisticated test
// than dest port == 80 is needed.
return force_connect_ || (dest_.port() != 80);
}
void AsyncHttpsProxySocket::SendRequest() {
std::stringstream ss;
ss << "CONNECT " << dest_.ToString() << " HTTP/1.0\r\n";
ss << "User-Agent: " << agent_ << "\r\n";
ss << "Host: " << dest_.HostAsURIString() << "\r\n";
ss << "Content-Length: 0\r\n";
ss << "Proxy-Connection: Keep-Alive\r\n";
ss << headers_;
ss << "\r\n";
std::string str = ss.str();
DirectSend(str.c_str(), str.size());
state_ = PS_LEADER;
expect_close_ = true;
content_length_ = 0;
headers_.clear();
LOG(LS_VERBOSE) << "AsyncHttpsProxySocket >> " << str;
}
void AsyncHttpsProxySocket::ProcessLine(char * data, size_t len) {
LOG(LS_VERBOSE) << "AsyncHttpsProxySocket << " << data;
if (len == 0) {
if (state_ == PS_TUNNEL_HEADERS) {
state_ = PS_TUNNEL;
} else if (state_ == PS_ERROR_HEADERS) {
Error(defer_error_);
return;
} else if (state_ == PS_SKIP_HEADERS) {
if (content_length_) {
state_ = PS_SKIP_BODY;
} else {
EndResponse();
return;
}
} else {
static bool report = false;
if (!unknown_mechanisms_.empty() && !report) {
report = true;
std::string msg(
"Unable to connect to the Google Talk service due to an incompatibility "
"with your proxy.\r\nPlease help us resolve this issue by submitting the "
"following information to us using our technical issue submission form "
"at:\r\n\r\n"
"http://www.google.com/support/talk/bin/request.py\r\n\r\n"
"We apologize for the inconvenience.\r\n\r\n"
"Information to submit to Google: "
);
//std::string msg("Please report the following information to foo@bar.com:\r\nUnknown methods: ");
msg.append(unknown_mechanisms_);
#if defined(WEBRTC_WIN)
MessageBoxA(0, msg.c_str(), "Oops!", MB_OK);
#endif
#if defined(WEBRTC_POSIX)
// TODO: Raise a signal so the UI can be separated.
LOG(LS_ERROR) << "Oops!\n\n" << msg;
#endif
}
// Unexpected end of headers
Error(0);
return;
}
} else if (state_ == PS_LEADER) {
unsigned int code;
if (sscanf(data, "HTTP/%*u.%*u %u", &code) != 1) {
Error(0);
return;
}
switch (code) {
case 200:
// connection good!
state_ = PS_TUNNEL_HEADERS;
return;
#if defined(HTTP_STATUS_PROXY_AUTH_REQ) && (HTTP_STATUS_PROXY_AUTH_REQ != 407)
#error Wrong code for HTTP_STATUS_PROXY_AUTH_REQ
#endif
case 407: // HTTP_STATUS_PROXY_AUTH_REQ
state_ = PS_AUTHENTICATE;
return;
default:
defer_error_ = 0;
state_ = PS_ERROR_HEADERS;
return;
}
} else if ((state_ == PS_AUTHENTICATE)
&& (_strnicmp(data, "Proxy-Authenticate:", 19) == 0)) {
std::string response, auth_method;
switch (HttpAuthenticate(data + 19, len - 19,
proxy_, "CONNECT", "/",
user_, pass_, context_, response, auth_method)) {
case HAR_IGNORE:
LOG(LS_VERBOSE) << "Ignoring Proxy-Authenticate: " << auth_method;
if (!unknown_mechanisms_.empty())
unknown_mechanisms_.append(", ");
unknown_mechanisms_.append(auth_method);
break;
case HAR_RESPONSE:
headers_ = "Proxy-Authorization: ";
headers_.append(response);
headers_.append("\r\n");
state_ = PS_SKIP_HEADERS;
unknown_mechanisms_.clear();
break;
case HAR_CREDENTIALS:
defer_error_ = SOCKET_EACCES;
state_ = PS_ERROR_HEADERS;
unknown_mechanisms_.clear();
break;
case HAR_ERROR:
defer_error_ = 0;
state_ = PS_ERROR_HEADERS;
unknown_mechanisms_.clear();
break;
}
} else if (_strnicmp(data, "Content-Length:", 15) == 0) {
content_length_ = strtoul(data + 15, 0, 0);
} else if (_strnicmp(data, "Proxy-Connection: Keep-Alive", 28) == 0) {
expect_close_ = false;
/*
} else if (_strnicmp(data, "Connection: close", 17) == 0) {
expect_close_ = true;
*/
}
}
void AsyncHttpsProxySocket::EndResponse() {
if (!expect_close_) {
SendRequest();
return;
}
// No point in waiting for the server to close... let's close now
// TODO: Refactor out PS_WAIT_CLOSE
state_ = PS_WAIT_CLOSE;
BufferedReadAdapter::Close();
OnCloseEvent(this, 0);
}
void AsyncHttpsProxySocket::Error(int error) {
BufferInput(false);
Close();
SetError(error);
SignalCloseEvent(this, error);
}
///////////////////////////////////////////////////////////////////////////////
AsyncSocksProxySocket::AsyncSocksProxySocket(AsyncSocket* socket,
const SocketAddress& proxy,
const std::string& username,
const CryptString& password)
: BufferedReadAdapter(socket, 1024), state_(SS_ERROR), proxy_(proxy),
user_(username), pass_(password) {
}
AsyncSocksProxySocket::~AsyncSocksProxySocket() = default;
int AsyncSocksProxySocket::Connect(const SocketAddress& addr) {
int ret;
dest_ = addr;
state_ = SS_INIT;
BufferInput(true);
ret = BufferedReadAdapter::Connect(proxy_);
// TODO: Set state_ appropriately if Connect fails.
return ret;
}
SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const {
return dest_;
}
int AsyncSocksProxySocket::Close() {
state_ = SS_ERROR;
dest_.Clear();
return BufferedReadAdapter::Close();
}
Socket::ConnState AsyncSocksProxySocket::GetState() const {
if (state_ < SS_TUNNEL) {
return CS_CONNECTING;
} else if (state_ == SS_TUNNEL) {
return CS_CONNECTED;
} else {
return CS_CLOSED;
}
}
void AsyncSocksProxySocket::OnConnectEvent(AsyncSocket* socket) {
SendHello();
}
void AsyncSocksProxySocket::ProcessInput(char* data, size_t* len) {
RTC_DCHECK(state_ < SS_TUNNEL);
ByteBufferReader response(data, *len);
if (state_ == SS_HELLO) {
uint8_t ver, method;
if (!response.ReadUInt8(&ver) ||
!response.ReadUInt8(&method))
return;
if (ver != 5) {
Error(0);
return;
}
if (method == 0) {
SendConnect();
} else if (method == 2) {
SendAuth();
} else {
Error(0);
return;
}
} else if (state_ == SS_AUTH) {
uint8_t ver, status;
if (!response.ReadUInt8(&ver) ||
!response.ReadUInt8(&status))
return;
if ((ver != 1) || (status != 0)) {
Error(SOCKET_EACCES);
return;
}
SendConnect();
} else if (state_ == SS_CONNECT) {
uint8_t ver, rep, rsv, atyp;
if (!response.ReadUInt8(&ver) ||
!response.ReadUInt8(&rep) ||
!response.ReadUInt8(&rsv) ||
!response.ReadUInt8(&atyp))
return;
if ((ver != 5) || (rep != 0)) {
Error(0);
return;
}
uint16_t port;
if (atyp == 1) {
uint32_t addr;
if (!response.ReadUInt32(&addr) ||
!response.ReadUInt16(&port))
return;
LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
} else if (atyp == 3) {
uint8_t len;
std::string addr;
if (!response.ReadUInt8(&len) ||
!response.ReadString(&addr, len) ||
!response.ReadUInt16(&port))
return;
LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
} else if (atyp == 4) {
std::string addr;
if (!response.ReadString(&addr, 16) ||
!response.ReadUInt16(&port))
return;
LOG(LS_VERBOSE) << "Bound on <IPV6>:" << port;
} else {
Error(0);
return;
}
state_ = SS_TUNNEL;
}
// Consume parsed data
*len = response.Length();
memmove(data, response.Data(), *len);
if (state_ != SS_TUNNEL)
return;
bool remainder = (*len > 0);
BufferInput(false);
SignalConnectEvent(this);
// FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
if (remainder)
SignalReadEvent(this); // TODO: signal this??
}
void AsyncSocksProxySocket::SendHello() {
ByteBufferWriter request;
request.WriteUInt8(5); // Socks Version
if (user_.empty()) {
request.WriteUInt8(1); // Authentication Mechanisms
request.WriteUInt8(0); // No authentication
} else {
request.WriteUInt8(2); // Authentication Mechanisms
request.WriteUInt8(0); // No authentication
request.WriteUInt8(2); // Username/Password
}
DirectSend(request.Data(), request.Length());
state_ = SS_HELLO;
}
void AsyncSocksProxySocket::SendAuth() {
ByteBufferWriter request;
request.WriteUInt8(1); // Negotiation Version
request.WriteUInt8(static_cast<uint8_t>(user_.size()));
request.WriteString(user_); // Username
request.WriteUInt8(static_cast<uint8_t>(pass_.GetLength()));
size_t len = pass_.GetLength() + 1;
char * sensitive = new char[len];
pass_.CopyTo(sensitive, true);
request.WriteString(sensitive); // Password
memset(sensitive, 0, len);
delete [] sensitive;
DirectSend(request.Data(), request.Length());
state_ = SS_AUTH;
}
void AsyncSocksProxySocket::SendConnect() {
ByteBufferWriter request;
request.WriteUInt8(5); // Socks Version
request.WriteUInt8(1); // CONNECT
request.WriteUInt8(0); // Reserved
if (dest_.IsUnresolvedIP()) {
std::string hostname = dest_.hostname();
request.WriteUInt8(3); // DOMAINNAME
request.WriteUInt8(static_cast<uint8_t>(hostname.size()));
request.WriteString(hostname); // Destination Hostname
} else {
request.WriteUInt8(1); // IPV4
request.WriteUInt32(dest_.ip()); // Destination IP
}
request.WriteUInt16(dest_.port()); // Destination Port
DirectSend(request.Data(), request.Length());
state_ = SS_CONNECT;
}
void AsyncSocksProxySocket::Error(int error) {
state_ = SS_ERROR;
BufferInput(false);
Close();
SetError(SOCKET_EACCES);
SignalCloseEvent(this, error);
}
AsyncSocksProxyServerSocket::AsyncSocksProxyServerSocket(AsyncSocket* socket)
: AsyncProxyServerSocket(socket, kBufferSize), state_(SS_HELLO) {
BufferInput(true);
}
void AsyncSocksProxyServerSocket::ProcessInput(char* data, size_t* len) {
// TODO: See if the whole message has arrived
RTC_DCHECK(state_ < SS_CONNECT_PENDING);
ByteBufferReader response(data, *len);
if (state_ == SS_HELLO) {
HandleHello(&response);
} else if (state_ == SS_AUTH) {
HandleAuth(&response);
} else if (state_ == SS_CONNECT) {
HandleConnect(&response);
}
// Consume parsed data
*len = response.Length();
memmove(data, response.Data(), *len);
}
void AsyncSocksProxyServerSocket::DirectSend(const ByteBufferWriter& buf) {
BufferedReadAdapter::DirectSend(buf.Data(), buf.Length());
}
void AsyncSocksProxyServerSocket::HandleHello(ByteBufferReader* request) {
uint8_t ver, num_methods;
if (!request->ReadUInt8(&ver) ||
!request->ReadUInt8(&num_methods)) {
Error(0);
return;
}
if (ver != 5) {
Error(0);
return;
}
// Handle either no-auth (0) or user/pass auth (2)
uint8_t method = 0xFF;
if (num_methods > 0 && !request->ReadUInt8(&method)) {
Error(0);
return;
}
// TODO: Ask the server which method to use.
SendHelloReply(method);
if (method == 0) {
state_ = SS_CONNECT;
} else if (method == 2) {
state_ = SS_AUTH;
} else {
state_ = SS_ERROR;
}
}
void AsyncSocksProxyServerSocket::SendHelloReply(uint8_t method) {
ByteBufferWriter response;
response.WriteUInt8(5); // Socks Version
response.WriteUInt8(method); // Auth method
DirectSend(response);
}
void AsyncSocksProxyServerSocket::HandleAuth(ByteBufferReader* request) {
uint8_t ver, user_len, pass_len;
std::string user, pass;
if (!request->ReadUInt8(&ver) ||
!request->ReadUInt8(&user_len) ||
!request->ReadString(&user, user_len) ||
!request->ReadUInt8(&pass_len) ||
!request->ReadString(&pass, pass_len)) {
Error(0);
return;
}
// TODO: Allow for checking of credentials.
SendAuthReply(0);
state_ = SS_CONNECT;
}
void AsyncSocksProxyServerSocket::SendAuthReply(uint8_t result) {
ByteBufferWriter response;
response.WriteUInt8(1); // Negotiation Version
response.WriteUInt8(result);
DirectSend(response);
}
void AsyncSocksProxyServerSocket::HandleConnect(ByteBufferReader* request) {
uint8_t ver, command, reserved, addr_type;
uint32_t ip;
uint16_t port;
if (!request->ReadUInt8(&ver) ||
!request->ReadUInt8(&command) ||
!request->ReadUInt8(&reserved) ||
!request->ReadUInt8(&addr_type) ||
!request->ReadUInt32(&ip) ||
!request->ReadUInt16(&port)) {
Error(0);
return;
}
if (ver != 5 || command != 1 ||
reserved != 0 || addr_type != 1) {
Error(0);
return;
}
SignalConnectRequest(this, SocketAddress(ip, port));
state_ = SS_CONNECT_PENDING;
}
void AsyncSocksProxyServerSocket::SendConnectResult(int result,
const SocketAddress& addr) {
if (state_ != SS_CONNECT_PENDING)
return;
ByteBufferWriter response;
response.WriteUInt8(5); // Socks version
response.WriteUInt8((result != 0)); // 0x01 is generic error
response.WriteUInt8(0); // reserved
response.WriteUInt8(1); // IPv4 address
response.WriteUInt32(addr.ip());
response.WriteUInt16(addr.port());
DirectSend(response);
BufferInput(false);
state_ = SS_TUNNEL;
}
void AsyncSocksProxyServerSocket::Error(int error) {
state_ = SS_ERROR;
BufferInput(false);
Close();
SetError(SOCKET_EACCES);
SignalCloseEvent(this, error);
}
} // namespace rtc } // namespace rtc

View File

@ -16,10 +16,12 @@
#include "webrtc/base/asyncsocket.h" #include "webrtc/base/asyncsocket.h"
#include "webrtc/base/constructormagic.h" #include "webrtc/base/constructormagic.h"
#include "webrtc/base/cryptstring.h"
#include "webrtc/base/logging.h" #include "webrtc/base/logging.h"
namespace rtc { namespace rtc {
struct HttpAuthContext;
class ByteBufferReader; class ByteBufferReader;
class ByteBufferWriter; class ByteBufferWriter;
@ -92,6 +94,114 @@ class AsyncSSLServerSocket : public BufferedReadAdapter {
RTC_DISALLOW_COPY_AND_ASSIGN(AsyncSSLServerSocket); RTC_DISALLOW_COPY_AND_ASSIGN(AsyncSSLServerSocket);
}; };
///////////////////////////////////////////////////////////////////////////////
// Implements a socket adapter that speaks the HTTP/S proxy protocol.
class AsyncHttpsProxySocket : public BufferedReadAdapter {
public:
AsyncHttpsProxySocket(AsyncSocket* socket, const std::string& user_agent,
const SocketAddress& proxy,
const std::string& username, const CryptString& password);
~AsyncHttpsProxySocket() override;
// If connect is forced, the adapter will always issue an HTTP CONNECT to the
// target address. Otherwise, it will connect only if the destination port
// is not port 80.
void SetForceConnect(bool force) { force_connect_ = force; }
int Connect(const SocketAddress& addr) override;
SocketAddress GetRemoteAddress() const override;
int Close() override;
ConnState GetState() const override;
protected:
void OnConnectEvent(AsyncSocket* socket) override;
void OnCloseEvent(AsyncSocket* socket, int err) override;
void ProcessInput(char* data, size_t* len) override;
bool ShouldIssueConnect() const;
void SendRequest();
void ProcessLine(char* data, size_t len);
void EndResponse();
void Error(int error);
private:
SocketAddress proxy_, dest_;
std::string agent_, user_, headers_;
CryptString pass_;
bool force_connect_;
size_t content_length_;
int defer_error_;
bool expect_close_;
enum ProxyState {
PS_INIT, PS_LEADER, PS_AUTHENTICATE, PS_SKIP_HEADERS, PS_ERROR_HEADERS,
PS_TUNNEL_HEADERS, PS_SKIP_BODY, PS_TUNNEL, PS_WAIT_CLOSE, PS_ERROR
} state_;
HttpAuthContext * context_;
std::string unknown_mechanisms_;
RTC_DISALLOW_COPY_AND_ASSIGN(AsyncHttpsProxySocket);
};
///////////////////////////////////////////////////////////////////////////////
// Implements a socket adapter that speaks the SOCKS proxy protocol.
class AsyncSocksProxySocket : public BufferedReadAdapter {
public:
AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy,
const std::string& username, const CryptString& password);
~AsyncSocksProxySocket() override;
int Connect(const SocketAddress& addr) override;
SocketAddress GetRemoteAddress() const override;
int Close() override;
ConnState GetState() const override;
protected:
void OnConnectEvent(AsyncSocket* socket) override;
void ProcessInput(char* data, size_t* len) override;
void SendHello();
void SendConnect();
void SendAuth();
void Error(int error);
private:
enum State {
SS_INIT, SS_HELLO, SS_AUTH, SS_CONNECT, SS_TUNNEL, SS_ERROR
};
State state_;
SocketAddress proxy_, dest_;
std::string user_;
CryptString pass_;
RTC_DISALLOW_COPY_AND_ASSIGN(AsyncSocksProxySocket);
};
// Implements a proxy server socket for the SOCKS protocol.
class AsyncSocksProxyServerSocket : public AsyncProxyServerSocket {
public:
explicit AsyncSocksProxyServerSocket(AsyncSocket* socket);
private:
void ProcessInput(char* data, size_t* len) override;
void DirectSend(const ByteBufferWriter& buf);
void HandleHello(ByteBufferReader* request);
void SendHelloReply(uint8_t method);
void HandleAuth(ByteBufferReader* request);
void SendAuthReply(uint8_t result);
void HandleConnect(ByteBufferReader* request);
void SendConnectResult(int result, const SocketAddress& addr) override;
void Error(int error);
static const int kBufferSize = 1024;
enum State {
SS_HELLO, SS_AUTH, SS_CONNECT, SS_CONNECT_PENDING, SS_TUNNEL, SS_ERROR
};
State state_;
RTC_DISALLOW_COPY_AND_ASSIGN(AsyncSocksProxyServerSocket);
};
} // namespace rtc } // namespace rtc
#endif // WEBRTC_BASE_SOCKETADAPTERS_H_ #endif // WEBRTC_BASE_SOCKETADAPTERS_H_

View File

@ -237,6 +237,61 @@ void StreamAdapterInterface::OnEvent(StreamInterface* stream,
SignalEvent(this, events, err); SignalEvent(this, events, err);
} }
///////////////////////////////////////////////////////////////////////////////
// StreamTap
///////////////////////////////////////////////////////////////////////////////
StreamTap::StreamTap(StreamInterface* stream, StreamInterface* tap)
: StreamAdapterInterface(stream), tap_(), tap_result_(SR_SUCCESS),
tap_error_(0) {
AttachTap(tap);
}
StreamTap::~StreamTap() = default;
void StreamTap::AttachTap(StreamInterface* tap) {
tap_.reset(tap);
}
StreamInterface* StreamTap::DetachTap() {
return tap_.release();
}
StreamResult StreamTap::GetTapResult(int* error) {
if (error) {
*error = tap_error_;
}
return tap_result_;
}
StreamResult StreamTap::Read(void* buffer, size_t buffer_len,
size_t* read, int* error) {
size_t backup_read;
if (!read) {
read = &backup_read;
}
StreamResult res = StreamAdapterInterface::Read(buffer, buffer_len,
read, error);
if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) {
tap_result_ = tap_->WriteAll(buffer, *read, nullptr, &tap_error_);
}
return res;
}
StreamResult StreamTap::Write(const void* data, size_t data_len,
size_t* written, int* error) {
size_t backup_written;
if (!written) {
written = &backup_written;
}
StreamResult res = StreamAdapterInterface::Write(data, data_len,
written, error);
if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) {
tap_result_ = tap_->WriteAll(data, *written, nullptr, &tap_error_);
}
return res;
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// NullStream // NullStream
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -606,6 +661,24 @@ StreamResult MemoryStream::DoReserve(size_t size, int* error) {
return SR_ERROR; return SR_ERROR;
} }
///////////////////////////////////////////////////////////////////////////////
ExternalMemoryStream::ExternalMemoryStream() {
}
ExternalMemoryStream::ExternalMemoryStream(void* data, size_t length) {
SetData(data, length);
}
ExternalMemoryStream::~ExternalMemoryStream() {
}
void ExternalMemoryStream::SetData(void* data, size_t length) {
data_length_ = buffer_length_ = length;
buffer_ = static_cast<char*>(data);
seek_position_ = 0;
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// FifoBuffer // FifoBuffer
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -821,6 +894,66 @@ StreamResult FifoBuffer::WriteOffsetLocked(const void* buffer,
return SR_SUCCESS; return SR_SUCCESS;
} }
///////////////////////////////////////////////////////////////////////////////
// LoggingAdapter
///////////////////////////////////////////////////////////////////////////////
LoggingAdapter::LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
const std::string& label, bool hex_mode)
: StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode) {
set_label(label);
}
void LoggingAdapter::set_label(const std::string& label) {
label_.assign("[");
label_.append(label);
label_.append("]");
}
StreamResult LoggingAdapter::Read(void* buffer, size_t buffer_len,
size_t* read, int* error) {
size_t local_read; if (!read) read = &local_read;
StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len, read,
error);
if (result == SR_SUCCESS) {
LogMultiline(level_, label_.c_str(), true, buffer, *read, hex_mode_, &lms_);
}
return result;
}
StreamResult LoggingAdapter::Write(const void* data, size_t data_len,
size_t* written, int* error) {
size_t local_written;
if (!written) written = &local_written;
StreamResult result = StreamAdapterInterface::Write(data, data_len, written,
error);
if (result == SR_SUCCESS) {
LogMultiline(level_, label_.c_str(), false, data, *written, hex_mode_,
&lms_);
}
return result;
}
void LoggingAdapter::Close() {
LogMultiline(level_, label_.c_str(), false, nullptr, 0, hex_mode_, &lms_);
LogMultiline(level_, label_.c_str(), true, nullptr, 0, hex_mode_, &lms_);
LOG_V(level_) << label_ << " Closed locally";
StreamAdapterInterface::Close();
}
void LoggingAdapter::OnEvent(StreamInterface* stream, int events, int err) {
if (events & SE_OPEN) {
LOG_V(level_) << label_ << " Open";
} else if (events & SE_CLOSE) {
LogMultiline(level_, label_.c_str(), false, nullptr, 0, hex_mode_, &lms_);
LogMultiline(level_, label_.c_str(), true, nullptr, 0, hex_mode_, &lms_);
LOG_V(level_) << label_ << " Closed with error: " << err;
}
StreamAdapterInterface::OnEvent(stream, events, err);
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// StringStream - Reads/Writes to an external std::string // StringStream - Reads/Writes to an external std::string
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -899,6 +1032,31 @@ bool StringStream::ReserveSize(size_t size) {
return true; return true;
} }
///////////////////////////////////////////////////////////////////////////////
// StreamReference
///////////////////////////////////////////////////////////////////////////////
StreamReference::StreamReference(StreamInterface* stream)
: StreamAdapterInterface(stream, false) {
// owner set to false so the destructor does not free the stream.
stream_ref_count_ = new StreamRefCount(stream);
}
StreamInterface* StreamReference::NewReference() {
stream_ref_count_->AddReference();
return new StreamReference(stream_ref_count_, stream());
}
StreamReference::~StreamReference() {
stream_ref_count_->Release();
}
StreamReference::StreamReference(StreamRefCount* stream_ref_count,
StreamInterface* stream)
: StreamAdapterInterface(stream, false),
stream_ref_count_(stream_ref_count) {
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
StreamResult Flow(StreamInterface* source, StreamResult Flow(StreamInterface* source,

View File

@ -309,6 +309,38 @@ class StreamAdapterInterface : public StreamInterface,
RTC_DISALLOW_COPY_AND_ASSIGN(StreamAdapterInterface); RTC_DISALLOW_COPY_AND_ASSIGN(StreamAdapterInterface);
}; };
///////////////////////////////////////////////////////////////////////////////
// StreamTap is a non-modifying, pass-through adapter, which copies all data
// in either direction to the tap. Note that errors or blocking on writing to
// the tap will prevent further tap writes from occurring.
///////////////////////////////////////////////////////////////////////////////
class StreamTap : public StreamAdapterInterface {
public:
explicit StreamTap(StreamInterface* stream, StreamInterface* tap);
~StreamTap() override;
void AttachTap(StreamInterface* tap);
StreamInterface* DetachTap();
StreamResult GetTapResult(int* error);
// StreamAdapterInterface Interface
StreamResult Read(void* buffer,
size_t buffer_len,
size_t* read,
int* error) override;
StreamResult Write(const void* data,
size_t data_len,
size_t* written,
int* error) override;
private:
std::unique_ptr<StreamInterface> tap_;
StreamResult tap_result_;
int tap_error_;
RTC_DISALLOW_COPY_AND_ASSIGN(StreamTap);
};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// NullStream gives errors on read, and silently discards all written data. // NullStream gives errors on read, and silently discards all written data.
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -448,6 +480,18 @@ class MemoryStream : public MemoryStreamBase {
char* buffer_alloc_; char* buffer_alloc_;
}; };
// ExternalMemoryStream adapts an external memory buffer, so writes which would
// extend past the end of the buffer will return end-of-stream.
class ExternalMemoryStream : public MemoryStreamBase {
public:
ExternalMemoryStream();
ExternalMemoryStream(void* data, size_t length);
~ExternalMemoryStream() override;
void SetData(void* data, size_t length);
};
// FifoBuffer allows for efficient, thread-safe buffering of data between // FifoBuffer allows for efficient, thread-safe buffering of data between
// writer and reader. As the data can wrap around the end of the buffer, // writer and reader. As the data can wrap around the end of the buffer,
// MemoryStreamBase can't help us here. // MemoryStreamBase can't help us here.
@ -525,6 +569,37 @@ class FifoBuffer : public StreamInterface {
RTC_DISALLOW_COPY_AND_ASSIGN(FifoBuffer); RTC_DISALLOW_COPY_AND_ASSIGN(FifoBuffer);
}; };
///////////////////////////////////////////////////////////////////////////////
class LoggingAdapter : public StreamAdapterInterface {
public:
LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
const std::string& label, bool hex_mode = false);
void set_label(const std::string& label);
StreamResult Read(void* buffer,
size_t buffer_len,
size_t* read,
int* error) override;
StreamResult Write(const void* data,
size_t data_len,
size_t* written,
int* error) override;
void Close() override;
protected:
void OnEvent(StreamInterface* stream, int events, int err) override;
private:
LoggingSeverity level_;
std::string label_;
bool hex_mode_;
LogMultilineState lms_;
RTC_DISALLOW_COPY_AND_ASSIGN(LoggingAdapter);
};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// StringStream - Reads/Writes to an external std::string // StringStream - Reads/Writes to an external std::string
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
@ -556,6 +631,66 @@ class StringStream : public StreamInterface {
bool read_only_; bool read_only_;
}; };
///////////////////////////////////////////////////////////////////////////////
// StreamReference - A reference counting stream adapter
///////////////////////////////////////////////////////////////////////////////
// Keep in mind that the streams and adapters defined in this file are
// not thread-safe, so this has limited uses.
// A StreamRefCount holds the reference count and a pointer to the
// wrapped stream. It deletes the wrapped stream when there are no
// more references. We can then have multiple StreamReference
// instances pointing to one StreamRefCount, all wrapping the same
// stream.
class StreamReference : public StreamAdapterInterface {
class StreamRefCount;
public:
// Constructor for the first reference to a stream
// Note: get more references through NewReference(). Use this
// constructor only once on a given stream.
explicit StreamReference(StreamInterface* stream);
StreamInterface* GetStream() { return stream(); }
StreamInterface* NewReference();
~StreamReference() override;
private:
class StreamRefCount {
public:
explicit StreamRefCount(StreamInterface* stream)
: stream_(stream), ref_count_(1) {
}
void AddReference() {
CritScope lock(&cs_);
++ref_count_;
}
void Release() {
int ref_count;
{ // Atomic ops would have been a better fit here.
CritScope lock(&cs_);
ref_count = --ref_count_;
}
if (ref_count == 0) {
delete stream_;
delete this;
}
}
private:
StreamInterface* stream_;
int ref_count_;
CriticalSection cs_;
RTC_DISALLOW_COPY_AND_ASSIGN(StreamRefCount);
};
// Constructor for adding references
explicit StreamReference(StreamRefCount* stream_ref_count,
StreamInterface* stream);
StreamRefCount* stream_ref_count_;
RTC_DISALLOW_COPY_AND_ASSIGN(StreamReference);
};
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// Flow attempts to move bytes from source to sink via buffer of size // Flow attempts to move bytes from source to sink via buffer of size

View File

@ -120,6 +120,16 @@ AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
return NULL; return NULL;
} }
// If using a proxy, wrap the socket in a proxy socket.
if (proxy_info.type == PROXY_SOCKS5) {
socket = new AsyncSocksProxySocket(
socket, proxy_info.address, proxy_info.username, proxy_info.password);
} else if (proxy_info.type == PROXY_HTTPS) {
socket =
new AsyncHttpsProxySocket(socket, user_agent, proxy_info.address,
proxy_info.username, proxy_info.password);
}
// Assert that at most one TLS option is used. // Assert that at most one TLS option is used.
int tlsOpts = int tlsOpts =
opts & (PacketSocketFactory::OPT_TLS | PacketSocketFactory::OPT_TLS_FAKE | opts & (PacketSocketFactory::OPT_TLS | PacketSocketFactory::OPT_TLS_FAKE |

View File

@ -23,6 +23,7 @@
#include "webrtc/base/natserver.h" #include "webrtc/base/natserver.h"
#include "webrtc/base/natsocketfactory.h" #include "webrtc/base/natsocketfactory.h"
#include "webrtc/base/physicalsocketserver.h" #include "webrtc/base/physicalsocketserver.h"
#include "webrtc/base/proxyserver.h"
#include "webrtc/base/socketaddress.h" #include "webrtc/base/socketaddress.h"
#include "webrtc/base/ssladapter.h" #include "webrtc/base/ssladapter.h"
#include "webrtc/base/thread.h" #include "webrtc/base/thread.h"
@ -63,6 +64,12 @@ static const SocketAddress kAlternateAddrs[2] = {
static const SocketAddress kIPv6AlternateAddrs[2] = { static const SocketAddress kIPv6AlternateAddrs[2] = {
SocketAddress("2401:4030:1:2c00:be30:abcd:efab:cdef", 0), SocketAddress("2401:4030:1:2c00:be30:abcd:efab:cdef", 0),
SocketAddress("2601:0:1000:1b03:2e41:38ff:fea6:f2a4", 0)}; SocketAddress("2601:0:1000:1b03:2e41:38ff:fea6:f2a4", 0)};
// Addresses for HTTP proxy servers.
static const SocketAddress kHttpsProxyAddrs[2] =
{ SocketAddress("11.11.11.1", 443), SocketAddress("22.22.22.1", 443) };
// Addresses for SOCKS proxy servers.
static const SocketAddress kSocksProxyAddrs[2] =
{ SocketAddress("11.11.11.1", 1080), SocketAddress("22.22.22.1", 1080) };
// Internal addresses for NAT boxes. // Internal addresses for NAT boxes.
static const SocketAddress kNatAddrs[2] = static const SocketAddress kNatAddrs[2] =
{ SocketAddress("192.168.1.1", 0), SocketAddress("192.168.2.1", 0) }; { SocketAddress("192.168.1.1", 0), SocketAddress("192.168.2.1", 0) };
@ -182,6 +189,14 @@ class P2PTransportChannelTestBase : public testing::Test,
ss_scope_(ss_.get()), ss_scope_(ss_.get()),
stun_server_(TestStunServer::Create(main_, kStunAddr)), stun_server_(TestStunServer::Create(main_, kStunAddr)),
turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr), turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr),
socks_server1_(ss_.get(),
kSocksProxyAddrs[0],
ss_.get(),
kSocksProxyAddrs[0]),
socks_server2_(ss_.get(),
kSocksProxyAddrs[1],
ss_.get(),
kSocksProxyAddrs[1]),
force_relay_(false) { force_relay_(false) {
ep1_.role_ = ICEROLE_CONTROLLING; ep1_.role_ = ICEROLE_CONTROLLING;
ep2_.role_ = ICEROLE_CONTROLLED; ep2_.role_ = ICEROLE_CONTROLLED;
@ -213,6 +228,9 @@ class P2PTransportChannelTestBase : public testing::Test,
NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner
BLOCK_UDP, // Firewall, UDP in/out blocked BLOCK_UDP, // Firewall, UDP in/out blocked
BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked
BLOCK_ALL_BUT_OUTGOING_HTTP, // Firewall, only TCP out on 80/443
PROXY_HTTPS, // All traffic through HTTPS proxy
PROXY_SOCKS, // All traffic through SOCKS proxy
NUM_CONFIGS NUM_CONFIGS
}; };
@ -435,6 +453,13 @@ class P2PTransportChannelTestBase : public testing::Test,
GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr); GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr);
fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, addr); fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, addr);
} }
void SetProxy(int endpoint, rtc::ProxyType type) {
rtc::ProxyInfo info;
info.type = type;
info.address = (type == rtc::PROXY_HTTPS) ?
kHttpsProxyAddrs[endpoint] : kSocksProxyAddrs[endpoint];
GetAllocator(endpoint)->set_proxy("unittest/1.0", info);
}
void SetAllocatorFlags(int endpoint, int flags) { void SetAllocatorFlags(int endpoint, int flags) {
GetAllocator(endpoint)->set_flags(flags); GetAllocator(endpoint)->set_flags(flags);
} }
@ -857,6 +882,8 @@ class P2PTransportChannelTestBase : public testing::Test,
rtc::SocketServerScope ss_scope_; rtc::SocketServerScope ss_scope_;
std::unique_ptr<TestStunServer> stun_server_; std::unique_ptr<TestStunServer> stun_server_;
TestTurnServer turn_server_; TestTurnServer turn_server_;
rtc::SocksProxyServer socks_server1_;
rtc::SocksProxyServer socks_server2_;
Endpoint ep1_; Endpoint ep1_;
Endpoint ep2_; Endpoint ep2_;
RemoteIceParameterSource remote_ice_parameter_source_ = FROM_CANDIDATE; RemoteIceParameterSource remote_ice_parameter_source_ = FROM_CANDIDATE;
@ -998,6 +1025,9 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase {
break; break;
case BLOCK_UDP: case BLOCK_UDP:
case BLOCK_UDP_AND_INCOMING_TCP: case BLOCK_UDP_AND_INCOMING_TCP:
case BLOCK_ALL_BUT_OUTGOING_HTTP:
case PROXY_HTTPS:
case PROXY_SOCKS:
AddAddress(endpoint, kPublicAddrs[endpoint]); AddAddress(endpoint, kPublicAddrs[endpoint]);
// Block all UDP // Block all UDP
fw()->AddRule(false, rtc::FP_UDP, rtc::FD_ANY, fw()->AddRule(false, rtc::FP_UDP, rtc::FD_ANY,
@ -1006,6 +1036,28 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase {
// Block TCP inbound to the endpoint // Block TCP inbound to the endpoint
fw()->AddRule(false, rtc::FP_TCP, SocketAddress(), fw()->AddRule(false, rtc::FP_TCP, SocketAddress(),
kPublicAddrs[endpoint]); kPublicAddrs[endpoint]);
} else if (config == BLOCK_ALL_BUT_OUTGOING_HTTP) {
// Block all TCP to/from the endpoint except 80/443 out
fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
SocketAddress(rtc::IPAddress(INADDR_ANY), 80));
fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
SocketAddress(rtc::IPAddress(INADDR_ANY), 443));
fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
kPublicAddrs[endpoint]);
} else if (config == PROXY_HTTPS) {
// Block all TCP to/from the endpoint except to the proxy server
fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
kHttpsProxyAddrs[endpoint]);
fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
kPublicAddrs[endpoint]);
SetProxy(endpoint, rtc::PROXY_HTTPS);
} else if (config == PROXY_SOCKS) {
// Block all TCP to/from the endpoint except to the proxy server
fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
kSocksProxyAddrs[endpoint]);
fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
kPublicAddrs[endpoint]);
SetProxy(endpoint, rtc::PROXY_SOCKS5);
} }
break; break;
default: default:
@ -1036,19 +1088,23 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase {
// Test matrix. Originator behavior defined by rows, receiever by columns. // Test matrix. Originator behavior defined by rows, receiever by columns.
// TODO: Fix NULLs caused by lack of TCP support in NATSocket. // TODO: Fix NULLs caused by lack of TCP support in NATSocket.
// TODO: Fix NULLs caused by no HTTP proxy support.
// TODO: Rearrange rows/columns from best to worst. // TODO: Rearrange rows/columns from best to worst.
const P2PTransportChannelTest::Result* const P2PTransportChannelTest::Result*
P2PTransportChannelTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = { P2PTransportChannelTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = {
// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP // OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS
/*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, LTPT, LTPT}, /*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, LTPT, LTPT, LSRS, NULL, LTPT},
/*CO*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL}, /*CO*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, LTRT},
/*AD*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL}, /*AD*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, LTRT},
/*PO*/ {SULU, SUSU, SUSU, SUSU, RUPU, SUSU, RUPU, NULL, NULL}, /*PO*/ {SULU, SUSU, SUSU, SUSU, RUPU, SUSU, RUPU, NULL, NULL, LSRS, NULL, LTRT},
/*SY*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL}, /*SY*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT},
/*2C*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL}, /*2C*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, LTRT},
/*SC*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL}, /*SC*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT},
/*!U*/ {LTPT, NULL, NULL, NULL, NULL, NULL, NULL, LTPT, LTPT}, /*!U*/ {LTPT, NULL, NULL, NULL, NULL, NULL, NULL, LTPT, LTPT, LSRS, NULL, LTRT},
/*!T*/ {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT}, /*!T*/ {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT, LSRS, NULL, LTRT},
/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS},
/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},
/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT},
}; };
// The actual tests that exercise all the various configurations. // The actual tests that exercise all the various configurations.
@ -1075,7 +1131,10 @@ const P2PTransportChannelTest::Result*
P2P_TEST(x, NAT_DOUBLE_CONE) \ P2P_TEST(x, NAT_DOUBLE_CONE) \
P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \ P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
P2P_TEST(x, BLOCK_UDP) \ P2P_TEST(x, BLOCK_UDP) \
P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
P2P_TEST(x, PROXY_HTTPS) \
P2P_TEST(x, PROXY_SOCKS)
P2P_TEST_SET(OPEN) P2P_TEST_SET(OPEN)
P2P_TEST_SET(NAT_FULL_CONE) P2P_TEST_SET(NAT_FULL_CONE)
@ -1086,6 +1145,9 @@ P2P_TEST_SET(NAT_DOUBLE_CONE)
P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE) P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE)
P2P_TEST_SET(BLOCK_UDP) P2P_TEST_SET(BLOCK_UDP)
P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP) P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP)
P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP)
P2P_TEST_SET(PROXY_HTTPS)
P2P_TEST_SET(PROXY_SOCKS)
// Test that we restart candidate allocation when local ufrag&pwd changed. // Test that we restart candidate allocation when local ufrag&pwd changed.
// Standard Ice protocol is used. // Standard Ice protocol is used.

View File

@ -13,7 +13,6 @@
#include "webrtc/base/constructormagic.h" #include "webrtc/base/constructormagic.h"
#include "webrtc/base/proxyinfo.h" #include "webrtc/base/proxyinfo.h"
#include "webrtc/base/socketaddress.h"
namespace rtc { namespace rtc {

View File

@ -19,6 +19,7 @@
#include "webrtc/p2p/base/port.h" #include "webrtc/p2p/base/port.h"
#include "webrtc/p2p/base/portinterface.h" #include "webrtc/p2p/base/portinterface.h"
#include "webrtc/base/helpers.h" #include "webrtc/base/helpers.h"
#include "webrtc/base/proxyinfo.h"
#include "webrtc/base/sigslot.h" #include "webrtc/base/sigslot.h"
#include "webrtc/base/thread.h" #include "webrtc/base/thread.h"
@ -383,6 +384,16 @@ class PortAllocator : public sigslot::has_slots<> {
uint32_t flags() const { return flags_; } uint32_t flags() const { return flags_; }
void set_flags(uint32_t flags) { flags_ = flags; } void set_flags(uint32_t flags) { flags_ = flags; }
// These three methods are deprecated. If connections need to go through a
// proxy, the application should create a BasicPortAllocator given a custom
// PacketSocketFactory that creates proxy sockets.
const std::string& user_agent() const { return agent_; }
const rtc::ProxyInfo& proxy() const { return proxy_; }
void set_proxy(const std::string& agent, const rtc::ProxyInfo& proxy) {
agent_ = agent;
proxy_ = proxy;
}
// Gets/Sets the port range to use when choosing client ports. // Gets/Sets the port range to use when choosing client ports.
int min_port() const { return min_port_; } int min_port() const { return min_port_; }
int max_port() const { return max_port_; } int max_port() const { return max_port_; }
@ -435,6 +446,8 @@ class PortAllocator : public sigslot::has_slots<> {
} }
uint32_t flags_; uint32_t flags_;
std::string agent_;
rtc::ProxyInfo proxy_;
int min_port_; int min_port_;
int max_port_; int max_port_;
uint32_t step_delay_; uint32_t step_delay_;

View File

@ -210,7 +210,15 @@ RelayPort::~RelayPort() {
} }
void RelayPort::AddServerAddress(const ProtocolAddress& addr) { void RelayPort::AddServerAddress(const ProtocolAddress& addr) {
// Since HTTP proxies usually only allow 443,
// let's up the priority on PROTO_SSLTCP
if (addr.proto == PROTO_SSLTCP &&
(proxy().type == rtc::PROXY_HTTPS ||
proxy().type == rtc::PROXY_UNKNOWN)) {
server_addr_.push_front(addr);
} else {
server_addr_.push_back(addr); server_addr_.push_back(addr);
}
} }
void RelayPort::AddExternalAddress(const ProtocolAddress& addr) { void RelayPort::AddExternalAddress(const ProtocolAddress& addr) {

View File

@ -699,6 +699,8 @@ void BasicPortAllocatorSession::AddAllocatedPort(Port* port,
port->set_content_name(content_name()); port->set_content_name(content_name());
port->set_component(component()); port->set_component(component());
port->set_generation(generation()); port->set_generation(generation());
if (allocator_->proxy().type != rtc::PROXY_NONE)
port->set_proxy(allocator_->user_agent(), allocator_->proxy());
port->set_send_retransmit_count_attribute( port->set_send_retransmit_count_attribute(
(flags() & PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE) != 0); (flags() & PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE) != 0);