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:
@ -49,6 +49,12 @@ char kLSanDefaultSuppressions[] =
|
||||
// pre-existing leaks.
|
||||
|
||||
// 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.
|
||||
"leak:StartDNSLookup\n"
|
||||
// https://code.google.com/p/webrtc/issues/detail?id=2527
|
||||
|
||||
@ -31,6 +31,14 @@
|
||||
#-----------------------------------------------------------------------
|
||||
# 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
|
||||
Memcheck:Uninitialized
|
||||
@ -154,6 +162,13 @@
|
||||
fun:BIO_new_mem_buf
|
||||
fun:_ZN9rtc15OpenSSLIdentity14FromPEMStringsERKSsS2_
|
||||
}
|
||||
{
|
||||
SignalsCloseAfterForcedCloseAll
|
||||
Memcheck:Leak
|
||||
fun:_Znw*
|
||||
fun:_ZN3rtc10HttpServer10Connection12BeginProcessEPNS_15StreamInterfaceE
|
||||
...
|
||||
}
|
||||
{
|
||||
SignalsCloseAfterForcedCloseAll2
|
||||
Memcheck:Leak
|
||||
@ -468,6 +483,21 @@
|
||||
fun:_Znw*
|
||||
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
|
||||
Memcheck:Unaddressable
|
||||
|
||||
@ -380,6 +380,8 @@ rtc_static_library("rtc_base") {
|
||||
"asyncudpsocket.h",
|
||||
"crc32.cc",
|
||||
"crc32.h",
|
||||
"cryptstring.cc",
|
||||
"cryptstring.h",
|
||||
"filerotatingstream.cc",
|
||||
"filerotatingstream.h",
|
||||
"fileutils.cc",
|
||||
@ -387,6 +389,11 @@ rtc_static_library("rtc_base") {
|
||||
"gunit_prod.h",
|
||||
"helpers.cc",
|
||||
"helpers.h",
|
||||
"httpbase.cc",
|
||||
"httpbase.h",
|
||||
"httpcommon-inl.h",
|
||||
"httpcommon.cc",
|
||||
"httpcommon.h",
|
||||
"ipaddress.cc",
|
||||
"ipaddress.h",
|
||||
"messagedigest.cc",
|
||||
@ -414,6 +421,7 @@ rtc_static_library("rtc_base") {
|
||||
"opensslstreamadapter.h",
|
||||
"physicalsocketserver.cc",
|
||||
"physicalsocketserver.h",
|
||||
"proxyinfo.cc",
|
||||
"proxyinfo.h",
|
||||
"ratelimiter.cc",
|
||||
"ratelimiter.h",
|
||||
@ -675,6 +683,8 @@ if (rtc_include_tests) {
|
||||
"firewallsocketserver.cc",
|
||||
"firewallsocketserver.h",
|
||||
"gunit.h",
|
||||
"httpserver.cc",
|
||||
"httpserver.h",
|
||||
"memory_usage.cc",
|
||||
"memory_usage.h",
|
||||
"natserver.cc",
|
||||
@ -828,6 +838,9 @@ if (rtc_include_tests) {
|
||||
"crc32_unittest.cc",
|
||||
"fileutils_unittest.cc",
|
||||
"helpers_unittest.cc",
|
||||
"httpbase_unittest.cc",
|
||||
"httpcommon_unittest.cc",
|
||||
"httpserver_unittest.cc",
|
||||
"ipaddress_unittest.cc",
|
||||
"memory_usage_unittest.cc",
|
||||
"messagedigest_unittest.cc",
|
||||
@ -835,6 +848,7 @@ if (rtc_include_tests) {
|
||||
"nat_unittest.cc",
|
||||
"network_unittest.cc",
|
||||
"optionsfile_unittest.cc",
|
||||
"proxy_unittest.cc",
|
||||
"ratelimiter_unittest.cc",
|
||||
"rollingaccumulator_unittest.cc",
|
||||
"rtccertificate_unittest.cc",
|
||||
|
||||
75
webrtc/base/cryptstring.cc
Normal file
75
webrtc/base/cryptstring.cc
Normal 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
167
webrtc/base/cryptstring.h
Normal 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
886
webrtc/base/httpbase.cc
Normal 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
187
webrtc/base/httpbase.h
Normal 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__
|
||||
526
webrtc/base/httpbase_unittest.cc
Normal file
526
webrtc/base/httpbase_unittest.cc
Normal 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
|
||||
132
webrtc/base/httpcommon-inl.h
Normal file
132
webrtc/base/httpcommon-inl.h
Normal 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
1009
webrtc/base/httpcommon.cc
Normal file
File diff suppressed because it is too large
Load Diff
458
webrtc/base/httpcommon.h
Normal file
458
webrtc/base/httpcommon.h
Normal 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__
|
||||
165
webrtc/base/httpcommon_unittest.cc
Normal file
165
webrtc/base/httpcommon_unittest.cc
Normal 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
288
webrtc/base/httpserver.cc
Normal 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(¤t_->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(¤t_->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(¤t_->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(¤t_->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
139
webrtc/base/httpserver.h
Normal 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__
|
||||
130
webrtc/base/httpserver_unittest.cc
Normal file
130
webrtc/base/httpserver_unittest.cc
Normal 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
|
||||
76
webrtc/base/proxy_unittest.cc
Normal file
76
webrtc/base/proxy_unittest.cc
Normal 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
24
webrtc/base/proxyinfo.cc
Normal 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
|
||||
@ -11,15 +11,31 @@
|
||||
#ifndef WEBRTC_BASE_PROXYINFO_H__
|
||||
#define WEBRTC_BASE_PROXYINFO_H__
|
||||
|
||||
#include <string>
|
||||
#include "webrtc/base/socketaddress.h"
|
||||
#include "webrtc/base/cryptstring.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
// TODO(deadbeef): Remove this; it's not used any more but it's referenced in
|
||||
// some places, including chromium.
|
||||
enum ProxyType {
|
||||
PROXY_NONE,
|
||||
PROXY_HTTPS,
|
||||
PROXY_SOCKS5,
|
||||
PROXY_UNKNOWN
|
||||
};
|
||||
const char * ProxyToString(ProxyType proxy);
|
||||
|
||||
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
|
||||
|
||||
@ -149,4 +149,8 @@ void ProxyBinding::Destroy() {
|
||||
SignalDestroyed(this);
|
||||
}
|
||||
|
||||
AsyncProxyServerSocket* SocksProxyServer::WrapSocket(AsyncSocket* socket) {
|
||||
return new AsyncSocksProxyServerSocket(socket);
|
||||
}
|
||||
|
||||
} // namespace rtc
|
||||
|
||||
@ -83,6 +83,18 @@ class ProxyServer : public sigslot::has_slots<> {
|
||||
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
|
||||
|
||||
#endif // WEBRTC_BASE_PROXYSERVER_H_
|
||||
|
||||
@ -28,6 +28,7 @@
|
||||
|
||||
#include "webrtc/base/bytebuffer.h"
|
||||
#include "webrtc/base/checks.h"
|
||||
#include "webrtc/base/httpcommon.h"
|
||||
#include "webrtc/base/logging.h"
|
||||
#include "webrtc/base/socketadapters.h"
|
||||
#include "webrtc/base/stringencode.h"
|
||||
@ -241,4 +242,607 @@ void AsyncSSLServerSocket::ProcessInput(char* data, size_t* len) {
|
||||
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
|
||||
|
||||
@ -16,10 +16,12 @@
|
||||
|
||||
#include "webrtc/base/asyncsocket.h"
|
||||
#include "webrtc/base/constructormagic.h"
|
||||
#include "webrtc/base/cryptstring.h"
|
||||
#include "webrtc/base/logging.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
struct HttpAuthContext;
|
||||
class ByteBufferReader;
|
||||
class ByteBufferWriter;
|
||||
|
||||
@ -92,6 +94,114 @@ class AsyncSSLServerSocket : public BufferedReadAdapter {
|
||||
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
|
||||
|
||||
#endif // WEBRTC_BASE_SOCKETADAPTERS_H_
|
||||
|
||||
@ -237,6 +237,61 @@ void StreamAdapterInterface::OnEvent(StreamInterface* stream,
|
||||
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
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -606,6 +661,24 @@ StreamResult MemoryStream::DoReserve(size_t size, int* 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
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -821,6 +894,66 @@ StreamResult FifoBuffer::WriteOffsetLocked(const void* buffer,
|
||||
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
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -899,6 +1032,31 @@ bool StringStream::ReserveSize(size_t size) {
|
||||
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,
|
||||
|
||||
@ -309,6 +309,38 @@ class StreamAdapterInterface : public StreamInterface,
|
||||
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.
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -448,6 +480,18 @@ class MemoryStream : public MemoryStreamBase {
|
||||
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
|
||||
// writer and reader. As the data can wrap around the end of the buffer,
|
||||
// MemoryStreamBase can't help us here.
|
||||
@ -525,6 +569,37 @@ class FifoBuffer : public StreamInterface {
|
||||
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
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
@ -556,6 +631,66 @@ class StringStream : public StreamInterface {
|
||||
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
|
||||
|
||||
@ -120,6 +120,16 @@ AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
|
||||
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.
|
||||
int tlsOpts =
|
||||
opts & (PacketSocketFactory::OPT_TLS | PacketSocketFactory::OPT_TLS_FAKE |
|
||||
|
||||
@ -23,6 +23,7 @@
|
||||
#include "webrtc/base/natserver.h"
|
||||
#include "webrtc/base/natsocketfactory.h"
|
||||
#include "webrtc/base/physicalsocketserver.h"
|
||||
#include "webrtc/base/proxyserver.h"
|
||||
#include "webrtc/base/socketaddress.h"
|
||||
#include "webrtc/base/ssladapter.h"
|
||||
#include "webrtc/base/thread.h"
|
||||
@ -63,6 +64,12 @@ static const SocketAddress kAlternateAddrs[2] = {
|
||||
static const SocketAddress kIPv6AlternateAddrs[2] = {
|
||||
SocketAddress("2401:4030:1:2c00:be30:abcd:efab:cdef", 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.
|
||||
static const SocketAddress kNatAddrs[2] =
|
||||
{ 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()),
|
||||
stun_server_(TestStunServer::Create(main_, kStunAddr)),
|
||||
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) {
|
||||
ep1_.role_ = ICEROLE_CONTROLLING;
|
||||
ep2_.role_ = ICEROLE_CONTROLLED;
|
||||
@ -213,6 +228,9 @@ class P2PTransportChannelTestBase : public testing::Test,
|
||||
NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner
|
||||
BLOCK_UDP, // Firewall, UDP in/out 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
|
||||
};
|
||||
|
||||
@ -435,6 +453,13 @@ class P2PTransportChannelTestBase : public testing::Test,
|
||||
GetEndpoint(endpoint)->network_manager_.RemoveInterface(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) {
|
||||
GetAllocator(endpoint)->set_flags(flags);
|
||||
}
|
||||
@ -857,6 +882,8 @@ class P2PTransportChannelTestBase : public testing::Test,
|
||||
rtc::SocketServerScope ss_scope_;
|
||||
std::unique_ptr<TestStunServer> stun_server_;
|
||||
TestTurnServer turn_server_;
|
||||
rtc::SocksProxyServer socks_server1_;
|
||||
rtc::SocksProxyServer socks_server2_;
|
||||
Endpoint ep1_;
|
||||
Endpoint ep2_;
|
||||
RemoteIceParameterSource remote_ice_parameter_source_ = FROM_CANDIDATE;
|
||||
@ -998,6 +1025,9 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase {
|
||||
break;
|
||||
case BLOCK_UDP:
|
||||
case BLOCK_UDP_AND_INCOMING_TCP:
|
||||
case BLOCK_ALL_BUT_OUTGOING_HTTP:
|
||||
case PROXY_HTTPS:
|
||||
case PROXY_SOCKS:
|
||||
AddAddress(endpoint, kPublicAddrs[endpoint]);
|
||||
// Block all UDP
|
||||
fw()->AddRule(false, rtc::FP_UDP, rtc::FD_ANY,
|
||||
@ -1006,6 +1036,28 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase {
|
||||
// Block TCP inbound to the endpoint
|
||||
fw()->AddRule(false, rtc::FP_TCP, SocketAddress(),
|
||||
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;
|
||||
default:
|
||||
@ -1036,19 +1088,23 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase {
|
||||
// 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 no HTTP proxy support.
|
||||
// TODO: Rearrange rows/columns from best to worst.
|
||||
const P2PTransportChannelTest::Result*
|
||||
P2PTransportChannelTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = {
|
||||
// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP
|
||||
/*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, LTPT, LTPT},
|
||||
/*CO*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL},
|
||||
/*AD*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL},
|
||||
/*PO*/ {SULU, SUSU, SUSU, SUSU, RUPU, SUSU, RUPU, NULL, NULL},
|
||||
/*SY*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL},
|
||||
/*2C*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL},
|
||||
/*SC*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL},
|
||||
/*!U*/ {LTPT, NULL, NULL, NULL, NULL, NULL, NULL, LTPT, LTPT},
|
||||
/*!T*/ {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT},
|
||||
// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS
|
||||
/*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, LTPT, LTPT, LSRS, NULL, LTPT},
|
||||
/*CO*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, LTRT},
|
||||
/*AD*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, LTRT},
|
||||
/*PO*/ {SULU, SUSU, SUSU, SUSU, RUPU, SUSU, RUPU, NULL, NULL, LSRS, NULL, LTRT},
|
||||
/*SY*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT},
|
||||
/*2C*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, LTRT},
|
||||
/*SC*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT},
|
||||
/*!U*/ {LTPT, NULL, NULL, NULL, NULL, NULL, NULL, LTPT, LTPT, LSRS, NULL, 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.
|
||||
@ -1075,7 +1131,10 @@ const P2PTransportChannelTest::Result*
|
||||
P2P_TEST(x, NAT_DOUBLE_CONE) \
|
||||
P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
|
||||
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(NAT_FULL_CONE)
|
||||
@ -1086,6 +1145,9 @@ P2P_TEST_SET(NAT_DOUBLE_CONE)
|
||||
P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE)
|
||||
P2P_TEST_SET(BLOCK_UDP)
|
||||
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.
|
||||
// Standard Ice protocol is used.
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
|
||||
#include "webrtc/base/constructormagic.h"
|
||||
#include "webrtc/base/proxyinfo.h"
|
||||
#include "webrtc/base/socketaddress.h"
|
||||
|
||||
namespace rtc {
|
||||
|
||||
|
||||
@ -19,6 +19,7 @@
|
||||
#include "webrtc/p2p/base/port.h"
|
||||
#include "webrtc/p2p/base/portinterface.h"
|
||||
#include "webrtc/base/helpers.h"
|
||||
#include "webrtc/base/proxyinfo.h"
|
||||
#include "webrtc/base/sigslot.h"
|
||||
#include "webrtc/base/thread.h"
|
||||
|
||||
@ -383,6 +384,16 @@ class PortAllocator : public sigslot::has_slots<> {
|
||||
uint32_t flags() const { return 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.
|
||||
int min_port() const { return min_port_; }
|
||||
int max_port() const { return max_port_; }
|
||||
@ -435,6 +446,8 @@ class PortAllocator : public sigslot::has_slots<> {
|
||||
}
|
||||
|
||||
uint32_t flags_;
|
||||
std::string agent_;
|
||||
rtc::ProxyInfo proxy_;
|
||||
int min_port_;
|
||||
int max_port_;
|
||||
uint32_t step_delay_;
|
||||
|
||||
@ -210,7 +210,15 @@ RelayPort::~RelayPort() {
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
void RelayPort::AddExternalAddress(const ProtocolAddress& addr) {
|
||||
|
||||
@ -699,6 +699,8 @@ void BasicPortAllocatorSession::AddAllocatedPort(Port* port,
|
||||
port->set_content_name(content_name());
|
||||
port->set_component(component());
|
||||
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(
|
||||
(flags() & PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE) != 0);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user