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.
|
// pre-existing leaks.
|
||||||
|
|
||||||
// rtc_unittest
|
// rtc_unittest
|
||||||
|
// https://code.google.com/p/webrtc/issues/detail?id=3827 for details.
|
||||||
|
"leak:rtc::unstarted_task_test_DoNotDeleteTask2_Test::TestBody\n"
|
||||||
|
"leak:rtc::HttpServer::HandleConnection\n"
|
||||||
|
"leak:rtc::HttpServer::Connection::onHttpHeaderComplete\n"
|
||||||
|
"leak:rtc::HttpResponseData::set_success\n"
|
||||||
|
"leak:rtc::HttpData::changeHeader\n"
|
||||||
// https://code.google.com/p/webrtc/issues/detail?id=4149 for details.
|
// https://code.google.com/p/webrtc/issues/detail?id=4149 for details.
|
||||||
"leak:StartDNSLookup\n"
|
"leak:StartDNSLookup\n"
|
||||||
// https://code.google.com/p/webrtc/issues/detail?id=2527
|
// https://code.google.com/p/webrtc/issues/detail?id=2527
|
||||||
|
|||||||
@ -31,6 +31,14 @@
|
|||||||
#-----------------------------------------------------------------------
|
#-----------------------------------------------------------------------
|
||||||
# webrtc stuff
|
# webrtc stuff
|
||||||
|
|
||||||
|
{
|
||||||
|
bug_6784
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:_Znw*
|
||||||
|
fun:_ZN3rtc10HttpServer16HandleConnectionEPNS_15StreamInterfaceE
|
||||||
|
fun:_ZN3rtc12_GLOBAL__N_122CreateClientConnectionERNS_10HttpServerERNS0_17HttpServerMonitorEb
|
||||||
|
fun:_ZN3rtc47HttpServer_SignalsCloseAfterForcedCloseAll_Test8TestBodyEv
|
||||||
|
}
|
||||||
{
|
{
|
||||||
bug_6773_3
|
bug_6773_3
|
||||||
Memcheck:Uninitialized
|
Memcheck:Uninitialized
|
||||||
@ -154,6 +162,13 @@
|
|||||||
fun:BIO_new_mem_buf
|
fun:BIO_new_mem_buf
|
||||||
fun:_ZN9rtc15OpenSSLIdentity14FromPEMStringsERKSsS2_
|
fun:_ZN9rtc15OpenSSLIdentity14FromPEMStringsERKSsS2_
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
SignalsCloseAfterForcedCloseAll
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:_Znw*
|
||||||
|
fun:_ZN3rtc10HttpServer10Connection12BeginProcessEPNS_15StreamInterfaceE
|
||||||
|
...
|
||||||
|
}
|
||||||
{
|
{
|
||||||
SignalsCloseAfterForcedCloseAll2
|
SignalsCloseAfterForcedCloseAll2
|
||||||
Memcheck:Leak
|
Memcheck:Leak
|
||||||
@ -468,6 +483,21 @@
|
|||||||
fun:_Znw*
|
fun:_Znw*
|
||||||
fun:_ZN6webrtc51AudioEncoderCopyRedDeathTest_NullSpeechEncoder_Test8TestBodyEv
|
fun:_ZN6webrtc51AudioEncoderCopyRedDeathTest_NullSpeechEncoder_Test8TestBodyEv
|
||||||
}
|
}
|
||||||
|
{
|
||||||
|
bug_5988
|
||||||
|
Memcheck:Leak
|
||||||
|
fun:_Znw*
|
||||||
|
fun:_ZNSt8_Rb_treeISsSt4pairIKSsSsESt10_Select1stIS2_EN3rtc5ilessESaIS2_EE15_M_insert_equalIS2_EESt17_Rb_tree_iteratorIS2_EOT_
|
||||||
|
fun:_ZN3rtc8HttpData12changeHeaderERKSsS2_NS0_13HeaderCombineE
|
||||||
|
fun:_ZN3rtc16HttpResponseData9set_errorEj
|
||||||
|
fun:_ZN3rtc12_GLOBAL__N_117HttpServerMonitor9OnRequestEPNS_10HttpServerEPNS_21HttpServerTransactionE
|
||||||
|
fun:_ZN3rtc10HttpServer10Connection14onHttpCompleteENS_8HttpModeENS_9HttpErrorE
|
||||||
|
fun:_ZN3rtc8HttpBase10OnCompleteENS_9HttpErrorE
|
||||||
|
fun:_ZN3rtc8HttpBase17OnHttpStreamEventEPNS_15StreamInterfaceEii
|
||||||
|
fun:_ZN7testing12StreamSource11QueueStringEPKc
|
||||||
|
fun:_ZN3rtc12_GLOBAL__N_122CreateClientConnectionERNS_10HttpServerERNS0_17HttpServerMonitorEb
|
||||||
|
fun:_ZN3rtc47HttpServer_SignalsCloseAfterForcedCloseAll_Test8TestBodyEv
|
||||||
|
}
|
||||||
{
|
{
|
||||||
bug_5989
|
bug_5989
|
||||||
Memcheck:Unaddressable
|
Memcheck:Unaddressable
|
||||||
|
|||||||
@ -380,6 +380,8 @@ rtc_static_library("rtc_base") {
|
|||||||
"asyncudpsocket.h",
|
"asyncudpsocket.h",
|
||||||
"crc32.cc",
|
"crc32.cc",
|
||||||
"crc32.h",
|
"crc32.h",
|
||||||
|
"cryptstring.cc",
|
||||||
|
"cryptstring.h",
|
||||||
"filerotatingstream.cc",
|
"filerotatingstream.cc",
|
||||||
"filerotatingstream.h",
|
"filerotatingstream.h",
|
||||||
"fileutils.cc",
|
"fileutils.cc",
|
||||||
@ -387,6 +389,11 @@ rtc_static_library("rtc_base") {
|
|||||||
"gunit_prod.h",
|
"gunit_prod.h",
|
||||||
"helpers.cc",
|
"helpers.cc",
|
||||||
"helpers.h",
|
"helpers.h",
|
||||||
|
"httpbase.cc",
|
||||||
|
"httpbase.h",
|
||||||
|
"httpcommon-inl.h",
|
||||||
|
"httpcommon.cc",
|
||||||
|
"httpcommon.h",
|
||||||
"ipaddress.cc",
|
"ipaddress.cc",
|
||||||
"ipaddress.h",
|
"ipaddress.h",
|
||||||
"messagedigest.cc",
|
"messagedigest.cc",
|
||||||
@ -414,6 +421,7 @@ rtc_static_library("rtc_base") {
|
|||||||
"opensslstreamadapter.h",
|
"opensslstreamadapter.h",
|
||||||
"physicalsocketserver.cc",
|
"physicalsocketserver.cc",
|
||||||
"physicalsocketserver.h",
|
"physicalsocketserver.h",
|
||||||
|
"proxyinfo.cc",
|
||||||
"proxyinfo.h",
|
"proxyinfo.h",
|
||||||
"ratelimiter.cc",
|
"ratelimiter.cc",
|
||||||
"ratelimiter.h",
|
"ratelimiter.h",
|
||||||
@ -675,6 +683,8 @@ if (rtc_include_tests) {
|
|||||||
"firewallsocketserver.cc",
|
"firewallsocketserver.cc",
|
||||||
"firewallsocketserver.h",
|
"firewallsocketserver.h",
|
||||||
"gunit.h",
|
"gunit.h",
|
||||||
|
"httpserver.cc",
|
||||||
|
"httpserver.h",
|
||||||
"memory_usage.cc",
|
"memory_usage.cc",
|
||||||
"memory_usage.h",
|
"memory_usage.h",
|
||||||
"natserver.cc",
|
"natserver.cc",
|
||||||
@ -828,6 +838,9 @@ if (rtc_include_tests) {
|
|||||||
"crc32_unittest.cc",
|
"crc32_unittest.cc",
|
||||||
"fileutils_unittest.cc",
|
"fileutils_unittest.cc",
|
||||||
"helpers_unittest.cc",
|
"helpers_unittest.cc",
|
||||||
|
"httpbase_unittest.cc",
|
||||||
|
"httpcommon_unittest.cc",
|
||||||
|
"httpserver_unittest.cc",
|
||||||
"ipaddress_unittest.cc",
|
"ipaddress_unittest.cc",
|
||||||
"memory_usage_unittest.cc",
|
"memory_usage_unittest.cc",
|
||||||
"messagedigest_unittest.cc",
|
"messagedigest_unittest.cc",
|
||||||
@ -835,6 +848,7 @@ if (rtc_include_tests) {
|
|||||||
"nat_unittest.cc",
|
"nat_unittest.cc",
|
||||||
"network_unittest.cc",
|
"network_unittest.cc",
|
||||||
"optionsfile_unittest.cc",
|
"optionsfile_unittest.cc",
|
||||||
|
"proxy_unittest.cc",
|
||||||
"ratelimiter_unittest.cc",
|
"ratelimiter_unittest.cc",
|
||||||
"rollingaccumulator_unittest.cc",
|
"rollingaccumulator_unittest.cc",
|
||||||
"rtccertificate_unittest.cc",
|
"rtccertificate_unittest.cc",
|
||||||
|
|||||||
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__
|
#ifndef WEBRTC_BASE_PROXYINFO_H__
|
||||||
#define WEBRTC_BASE_PROXYINFO_H__
|
#define WEBRTC_BASE_PROXYINFO_H__
|
||||||
|
|
||||||
|
#include <string>
|
||||||
|
#include "webrtc/base/socketaddress.h"
|
||||||
|
#include "webrtc/base/cryptstring.h"
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
// TODO(deadbeef): Remove this; it's not used any more but it's referenced in
|
|
||||||
// some places, including chromium.
|
|
||||||
enum ProxyType {
|
enum ProxyType {
|
||||||
PROXY_NONE,
|
PROXY_NONE,
|
||||||
|
PROXY_HTTPS,
|
||||||
|
PROXY_SOCKS5,
|
||||||
|
PROXY_UNKNOWN
|
||||||
};
|
};
|
||||||
|
const char * ProxyToString(ProxyType proxy);
|
||||||
|
|
||||||
struct ProxyInfo {
|
struct ProxyInfo {
|
||||||
|
ProxyType type;
|
||||||
|
SocketAddress address;
|
||||||
|
std::string autoconfig_url;
|
||||||
|
bool autodetect;
|
||||||
|
std::string bypass_list;
|
||||||
|
std::string username;
|
||||||
|
CryptString password;
|
||||||
|
|
||||||
|
ProxyInfo();
|
||||||
|
~ProxyInfo();
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|||||||
@ -149,4 +149,8 @@ void ProxyBinding::Destroy() {
|
|||||||
SignalDestroyed(this);
|
SignalDestroyed(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AsyncProxyServerSocket* SocksProxyServer::WrapSocket(AsyncSocket* socket) {
|
||||||
|
return new AsyncSocksProxyServerSocket(socket);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|||||||
@ -83,6 +83,18 @@ class ProxyServer : public sigslot::has_slots<> {
|
|||||||
RTC_DISALLOW_COPY_AND_ASSIGN(ProxyServer);
|
RTC_DISALLOW_COPY_AND_ASSIGN(ProxyServer);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// SocksProxyServer is a simple extension of ProxyServer to implement SOCKS.
|
||||||
|
class SocksProxyServer : public ProxyServer {
|
||||||
|
public:
|
||||||
|
SocksProxyServer(SocketFactory* int_factory, const SocketAddress& int_addr,
|
||||||
|
SocketFactory* ext_factory, const SocketAddress& ext_ip)
|
||||||
|
: ProxyServer(int_factory, int_addr, ext_factory, ext_ip) {
|
||||||
|
}
|
||||||
|
protected:
|
||||||
|
AsyncProxyServerSocket* WrapSocket(AsyncSocket* socket) override;
|
||||||
|
RTC_DISALLOW_COPY_AND_ASSIGN(SocksProxyServer);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|
||||||
#endif // WEBRTC_BASE_PROXYSERVER_H_
|
#endif // WEBRTC_BASE_PROXYSERVER_H_
|
||||||
|
|||||||
@ -28,6 +28,7 @@
|
|||||||
|
|
||||||
#include "webrtc/base/bytebuffer.h"
|
#include "webrtc/base/bytebuffer.h"
|
||||||
#include "webrtc/base/checks.h"
|
#include "webrtc/base/checks.h"
|
||||||
|
#include "webrtc/base/httpcommon.h"
|
||||||
#include "webrtc/base/logging.h"
|
#include "webrtc/base/logging.h"
|
||||||
#include "webrtc/base/socketadapters.h"
|
#include "webrtc/base/socketadapters.h"
|
||||||
#include "webrtc/base/stringencode.h"
|
#include "webrtc/base/stringencode.h"
|
||||||
@ -241,4 +242,607 @@ void AsyncSSLServerSocket::ProcessInput(char* data, size_t* len) {
|
|||||||
BufferInput(false);
|
BufferInput(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
AsyncHttpsProxySocket::AsyncHttpsProxySocket(AsyncSocket* socket,
|
||||||
|
const std::string& user_agent,
|
||||||
|
const SocketAddress& proxy,
|
||||||
|
const std::string& username,
|
||||||
|
const CryptString& password)
|
||||||
|
: BufferedReadAdapter(socket, 1024), proxy_(proxy), agent_(user_agent),
|
||||||
|
user_(username), pass_(password), force_connect_(false), state_(PS_ERROR),
|
||||||
|
context_(0) {
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncHttpsProxySocket::~AsyncHttpsProxySocket() {
|
||||||
|
delete context_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AsyncHttpsProxySocket::Connect(const SocketAddress& addr) {
|
||||||
|
int ret;
|
||||||
|
LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::Connect("
|
||||||
|
<< proxy_.ToSensitiveString() << ")";
|
||||||
|
dest_ = addr;
|
||||||
|
state_ = PS_INIT;
|
||||||
|
if (ShouldIssueConnect()) {
|
||||||
|
BufferInput(true);
|
||||||
|
}
|
||||||
|
ret = BufferedReadAdapter::Connect(proxy_);
|
||||||
|
// TODO: Set state_ appropriately if Connect fails.
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAddress AsyncHttpsProxySocket::GetRemoteAddress() const {
|
||||||
|
return dest_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AsyncHttpsProxySocket::Close() {
|
||||||
|
headers_.clear();
|
||||||
|
state_ = PS_ERROR;
|
||||||
|
dest_.Clear();
|
||||||
|
delete context_;
|
||||||
|
context_ = nullptr;
|
||||||
|
return BufferedReadAdapter::Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket::ConnState AsyncHttpsProxySocket::GetState() const {
|
||||||
|
if (state_ < PS_TUNNEL) {
|
||||||
|
return CS_CONNECTING;
|
||||||
|
} else if (state_ == PS_TUNNEL) {
|
||||||
|
return CS_CONNECTED;
|
||||||
|
} else {
|
||||||
|
return CS_CLOSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncHttpsProxySocket::OnConnectEvent(AsyncSocket * socket) {
|
||||||
|
LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnConnectEvent";
|
||||||
|
if (!ShouldIssueConnect()) {
|
||||||
|
state_ = PS_TUNNEL;
|
||||||
|
BufferedReadAdapter::OnConnectEvent(socket);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
SendRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncHttpsProxySocket::OnCloseEvent(AsyncSocket * socket, int err) {
|
||||||
|
LOG(LS_VERBOSE) << "AsyncHttpsProxySocket::OnCloseEvent(" << err << ")";
|
||||||
|
if ((state_ == PS_WAIT_CLOSE) && (err == 0)) {
|
||||||
|
state_ = PS_ERROR;
|
||||||
|
Connect(dest_);
|
||||||
|
} else {
|
||||||
|
BufferedReadAdapter::OnCloseEvent(socket, err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncHttpsProxySocket::ProcessInput(char* data, size_t* len) {
|
||||||
|
size_t start = 0;
|
||||||
|
for (size_t pos = start; state_ < PS_TUNNEL && pos < *len;) {
|
||||||
|
if (state_ == PS_SKIP_BODY) {
|
||||||
|
size_t consume = std::min(*len - pos, content_length_);
|
||||||
|
pos += consume;
|
||||||
|
start = pos;
|
||||||
|
content_length_ -= consume;
|
||||||
|
if (content_length_ == 0) {
|
||||||
|
EndResponse();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data[pos++] != '\n')
|
||||||
|
continue;
|
||||||
|
|
||||||
|
size_t len = pos - start - 1;
|
||||||
|
if ((len > 0) && (data[start + len - 1] == '\r'))
|
||||||
|
--len;
|
||||||
|
|
||||||
|
data[start + len] = 0;
|
||||||
|
ProcessLine(data + start, len);
|
||||||
|
start = pos;
|
||||||
|
}
|
||||||
|
|
||||||
|
*len -= start;
|
||||||
|
if (*len > 0) {
|
||||||
|
memmove(data, data + start, *len);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state_ != PS_TUNNEL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool remainder = (*len > 0);
|
||||||
|
BufferInput(false);
|
||||||
|
SignalConnectEvent(this);
|
||||||
|
|
||||||
|
// FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
|
||||||
|
if (remainder)
|
||||||
|
SignalReadEvent(this); // TODO: signal this??
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AsyncHttpsProxySocket::ShouldIssueConnect() const {
|
||||||
|
// TODO: Think about whether a more sophisticated test
|
||||||
|
// than dest port == 80 is needed.
|
||||||
|
return force_connect_ || (dest_.port() != 80);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncHttpsProxySocket::SendRequest() {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "CONNECT " << dest_.ToString() << " HTTP/1.0\r\n";
|
||||||
|
ss << "User-Agent: " << agent_ << "\r\n";
|
||||||
|
ss << "Host: " << dest_.HostAsURIString() << "\r\n";
|
||||||
|
ss << "Content-Length: 0\r\n";
|
||||||
|
ss << "Proxy-Connection: Keep-Alive\r\n";
|
||||||
|
ss << headers_;
|
||||||
|
ss << "\r\n";
|
||||||
|
std::string str = ss.str();
|
||||||
|
DirectSend(str.c_str(), str.size());
|
||||||
|
state_ = PS_LEADER;
|
||||||
|
expect_close_ = true;
|
||||||
|
content_length_ = 0;
|
||||||
|
headers_.clear();
|
||||||
|
|
||||||
|
LOG(LS_VERBOSE) << "AsyncHttpsProxySocket >> " << str;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncHttpsProxySocket::ProcessLine(char * data, size_t len) {
|
||||||
|
LOG(LS_VERBOSE) << "AsyncHttpsProxySocket << " << data;
|
||||||
|
|
||||||
|
if (len == 0) {
|
||||||
|
if (state_ == PS_TUNNEL_HEADERS) {
|
||||||
|
state_ = PS_TUNNEL;
|
||||||
|
} else if (state_ == PS_ERROR_HEADERS) {
|
||||||
|
Error(defer_error_);
|
||||||
|
return;
|
||||||
|
} else if (state_ == PS_SKIP_HEADERS) {
|
||||||
|
if (content_length_) {
|
||||||
|
state_ = PS_SKIP_BODY;
|
||||||
|
} else {
|
||||||
|
EndResponse();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
static bool report = false;
|
||||||
|
if (!unknown_mechanisms_.empty() && !report) {
|
||||||
|
report = true;
|
||||||
|
std::string msg(
|
||||||
|
"Unable to connect to the Google Talk service due to an incompatibility "
|
||||||
|
"with your proxy.\r\nPlease help us resolve this issue by submitting the "
|
||||||
|
"following information to us using our technical issue submission form "
|
||||||
|
"at:\r\n\r\n"
|
||||||
|
"http://www.google.com/support/talk/bin/request.py\r\n\r\n"
|
||||||
|
"We apologize for the inconvenience.\r\n\r\n"
|
||||||
|
"Information to submit to Google: "
|
||||||
|
);
|
||||||
|
//std::string msg("Please report the following information to foo@bar.com:\r\nUnknown methods: ");
|
||||||
|
msg.append(unknown_mechanisms_);
|
||||||
|
#if defined(WEBRTC_WIN)
|
||||||
|
MessageBoxA(0, msg.c_str(), "Oops!", MB_OK);
|
||||||
|
#endif
|
||||||
|
#if defined(WEBRTC_POSIX)
|
||||||
|
// TODO: Raise a signal so the UI can be separated.
|
||||||
|
LOG(LS_ERROR) << "Oops!\n\n" << msg;
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
// Unexpected end of headers
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (state_ == PS_LEADER) {
|
||||||
|
unsigned int code;
|
||||||
|
if (sscanf(data, "HTTP/%*u.%*u %u", &code) != 1) {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
switch (code) {
|
||||||
|
case 200:
|
||||||
|
// connection good!
|
||||||
|
state_ = PS_TUNNEL_HEADERS;
|
||||||
|
return;
|
||||||
|
#if defined(HTTP_STATUS_PROXY_AUTH_REQ) && (HTTP_STATUS_PROXY_AUTH_REQ != 407)
|
||||||
|
#error Wrong code for HTTP_STATUS_PROXY_AUTH_REQ
|
||||||
|
#endif
|
||||||
|
case 407: // HTTP_STATUS_PROXY_AUTH_REQ
|
||||||
|
state_ = PS_AUTHENTICATE;
|
||||||
|
return;
|
||||||
|
default:
|
||||||
|
defer_error_ = 0;
|
||||||
|
state_ = PS_ERROR_HEADERS;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if ((state_ == PS_AUTHENTICATE)
|
||||||
|
&& (_strnicmp(data, "Proxy-Authenticate:", 19) == 0)) {
|
||||||
|
std::string response, auth_method;
|
||||||
|
switch (HttpAuthenticate(data + 19, len - 19,
|
||||||
|
proxy_, "CONNECT", "/",
|
||||||
|
user_, pass_, context_, response, auth_method)) {
|
||||||
|
case HAR_IGNORE:
|
||||||
|
LOG(LS_VERBOSE) << "Ignoring Proxy-Authenticate: " << auth_method;
|
||||||
|
if (!unknown_mechanisms_.empty())
|
||||||
|
unknown_mechanisms_.append(", ");
|
||||||
|
unknown_mechanisms_.append(auth_method);
|
||||||
|
break;
|
||||||
|
case HAR_RESPONSE:
|
||||||
|
headers_ = "Proxy-Authorization: ";
|
||||||
|
headers_.append(response);
|
||||||
|
headers_.append("\r\n");
|
||||||
|
state_ = PS_SKIP_HEADERS;
|
||||||
|
unknown_mechanisms_.clear();
|
||||||
|
break;
|
||||||
|
case HAR_CREDENTIALS:
|
||||||
|
defer_error_ = SOCKET_EACCES;
|
||||||
|
state_ = PS_ERROR_HEADERS;
|
||||||
|
unknown_mechanisms_.clear();
|
||||||
|
break;
|
||||||
|
case HAR_ERROR:
|
||||||
|
defer_error_ = 0;
|
||||||
|
state_ = PS_ERROR_HEADERS;
|
||||||
|
unknown_mechanisms_.clear();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (_strnicmp(data, "Content-Length:", 15) == 0) {
|
||||||
|
content_length_ = strtoul(data + 15, 0, 0);
|
||||||
|
} else if (_strnicmp(data, "Proxy-Connection: Keep-Alive", 28) == 0) {
|
||||||
|
expect_close_ = false;
|
||||||
|
/*
|
||||||
|
} else if (_strnicmp(data, "Connection: close", 17) == 0) {
|
||||||
|
expect_close_ = true;
|
||||||
|
*/
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncHttpsProxySocket::EndResponse() {
|
||||||
|
if (!expect_close_) {
|
||||||
|
SendRequest();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// No point in waiting for the server to close... let's close now
|
||||||
|
// TODO: Refactor out PS_WAIT_CLOSE
|
||||||
|
state_ = PS_WAIT_CLOSE;
|
||||||
|
BufferedReadAdapter::Close();
|
||||||
|
OnCloseEvent(this, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncHttpsProxySocket::Error(int error) {
|
||||||
|
BufferInput(false);
|
||||||
|
Close();
|
||||||
|
SetError(error);
|
||||||
|
SignalCloseEvent(this, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
AsyncSocksProxySocket::AsyncSocksProxySocket(AsyncSocket* socket,
|
||||||
|
const SocketAddress& proxy,
|
||||||
|
const std::string& username,
|
||||||
|
const CryptString& password)
|
||||||
|
: BufferedReadAdapter(socket, 1024), state_(SS_ERROR), proxy_(proxy),
|
||||||
|
user_(username), pass_(password) {
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncSocksProxySocket::~AsyncSocksProxySocket() = default;
|
||||||
|
|
||||||
|
int AsyncSocksProxySocket::Connect(const SocketAddress& addr) {
|
||||||
|
int ret;
|
||||||
|
dest_ = addr;
|
||||||
|
state_ = SS_INIT;
|
||||||
|
BufferInput(true);
|
||||||
|
ret = BufferedReadAdapter::Connect(proxy_);
|
||||||
|
// TODO: Set state_ appropriately if Connect fails.
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
SocketAddress AsyncSocksProxySocket::GetRemoteAddress() const {
|
||||||
|
return dest_;
|
||||||
|
}
|
||||||
|
|
||||||
|
int AsyncSocksProxySocket::Close() {
|
||||||
|
state_ = SS_ERROR;
|
||||||
|
dest_.Clear();
|
||||||
|
return BufferedReadAdapter::Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
Socket::ConnState AsyncSocksProxySocket::GetState() const {
|
||||||
|
if (state_ < SS_TUNNEL) {
|
||||||
|
return CS_CONNECTING;
|
||||||
|
} else if (state_ == SS_TUNNEL) {
|
||||||
|
return CS_CONNECTED;
|
||||||
|
} else {
|
||||||
|
return CS_CLOSED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxySocket::OnConnectEvent(AsyncSocket* socket) {
|
||||||
|
SendHello();
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxySocket::ProcessInput(char* data, size_t* len) {
|
||||||
|
RTC_DCHECK(state_ < SS_TUNNEL);
|
||||||
|
|
||||||
|
ByteBufferReader response(data, *len);
|
||||||
|
|
||||||
|
if (state_ == SS_HELLO) {
|
||||||
|
uint8_t ver, method;
|
||||||
|
if (!response.ReadUInt8(&ver) ||
|
||||||
|
!response.ReadUInt8(&method))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (ver != 5) {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (method == 0) {
|
||||||
|
SendConnect();
|
||||||
|
} else if (method == 2) {
|
||||||
|
SendAuth();
|
||||||
|
} else {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else if (state_ == SS_AUTH) {
|
||||||
|
uint8_t ver, status;
|
||||||
|
if (!response.ReadUInt8(&ver) ||
|
||||||
|
!response.ReadUInt8(&status))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((ver != 1) || (status != 0)) {
|
||||||
|
Error(SOCKET_EACCES);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SendConnect();
|
||||||
|
} else if (state_ == SS_CONNECT) {
|
||||||
|
uint8_t ver, rep, rsv, atyp;
|
||||||
|
if (!response.ReadUInt8(&ver) ||
|
||||||
|
!response.ReadUInt8(&rep) ||
|
||||||
|
!response.ReadUInt8(&rsv) ||
|
||||||
|
!response.ReadUInt8(&atyp))
|
||||||
|
return;
|
||||||
|
|
||||||
|
if ((ver != 5) || (rep != 0)) {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
uint16_t port;
|
||||||
|
if (atyp == 1) {
|
||||||
|
uint32_t addr;
|
||||||
|
if (!response.ReadUInt32(&addr) ||
|
||||||
|
!response.ReadUInt16(&port))
|
||||||
|
return;
|
||||||
|
LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
|
||||||
|
} else if (atyp == 3) {
|
||||||
|
uint8_t len;
|
||||||
|
std::string addr;
|
||||||
|
if (!response.ReadUInt8(&len) ||
|
||||||
|
!response.ReadString(&addr, len) ||
|
||||||
|
!response.ReadUInt16(&port))
|
||||||
|
return;
|
||||||
|
LOG(LS_VERBOSE) << "Bound on " << addr << ":" << port;
|
||||||
|
} else if (atyp == 4) {
|
||||||
|
std::string addr;
|
||||||
|
if (!response.ReadString(&addr, 16) ||
|
||||||
|
!response.ReadUInt16(&port))
|
||||||
|
return;
|
||||||
|
LOG(LS_VERBOSE) << "Bound on <IPV6>:" << port;
|
||||||
|
} else {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state_ = SS_TUNNEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume parsed data
|
||||||
|
*len = response.Length();
|
||||||
|
memmove(data, response.Data(), *len);
|
||||||
|
|
||||||
|
if (state_ != SS_TUNNEL)
|
||||||
|
return;
|
||||||
|
|
||||||
|
bool remainder = (*len > 0);
|
||||||
|
BufferInput(false);
|
||||||
|
SignalConnectEvent(this);
|
||||||
|
|
||||||
|
// FIX: if SignalConnect causes the socket to be destroyed, we are in trouble
|
||||||
|
if (remainder)
|
||||||
|
SignalReadEvent(this); // TODO: signal this??
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxySocket::SendHello() {
|
||||||
|
ByteBufferWriter request;
|
||||||
|
request.WriteUInt8(5); // Socks Version
|
||||||
|
if (user_.empty()) {
|
||||||
|
request.WriteUInt8(1); // Authentication Mechanisms
|
||||||
|
request.WriteUInt8(0); // No authentication
|
||||||
|
} else {
|
||||||
|
request.WriteUInt8(2); // Authentication Mechanisms
|
||||||
|
request.WriteUInt8(0); // No authentication
|
||||||
|
request.WriteUInt8(2); // Username/Password
|
||||||
|
}
|
||||||
|
DirectSend(request.Data(), request.Length());
|
||||||
|
state_ = SS_HELLO;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxySocket::SendAuth() {
|
||||||
|
ByteBufferWriter request;
|
||||||
|
request.WriteUInt8(1); // Negotiation Version
|
||||||
|
request.WriteUInt8(static_cast<uint8_t>(user_.size()));
|
||||||
|
request.WriteString(user_); // Username
|
||||||
|
request.WriteUInt8(static_cast<uint8_t>(pass_.GetLength()));
|
||||||
|
size_t len = pass_.GetLength() + 1;
|
||||||
|
char * sensitive = new char[len];
|
||||||
|
pass_.CopyTo(sensitive, true);
|
||||||
|
request.WriteString(sensitive); // Password
|
||||||
|
memset(sensitive, 0, len);
|
||||||
|
delete [] sensitive;
|
||||||
|
DirectSend(request.Data(), request.Length());
|
||||||
|
state_ = SS_AUTH;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxySocket::SendConnect() {
|
||||||
|
ByteBufferWriter request;
|
||||||
|
request.WriteUInt8(5); // Socks Version
|
||||||
|
request.WriteUInt8(1); // CONNECT
|
||||||
|
request.WriteUInt8(0); // Reserved
|
||||||
|
if (dest_.IsUnresolvedIP()) {
|
||||||
|
std::string hostname = dest_.hostname();
|
||||||
|
request.WriteUInt8(3); // DOMAINNAME
|
||||||
|
request.WriteUInt8(static_cast<uint8_t>(hostname.size()));
|
||||||
|
request.WriteString(hostname); // Destination Hostname
|
||||||
|
} else {
|
||||||
|
request.WriteUInt8(1); // IPV4
|
||||||
|
request.WriteUInt32(dest_.ip()); // Destination IP
|
||||||
|
}
|
||||||
|
request.WriteUInt16(dest_.port()); // Destination Port
|
||||||
|
DirectSend(request.Data(), request.Length());
|
||||||
|
state_ = SS_CONNECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxySocket::Error(int error) {
|
||||||
|
state_ = SS_ERROR;
|
||||||
|
BufferInput(false);
|
||||||
|
Close();
|
||||||
|
SetError(SOCKET_EACCES);
|
||||||
|
SignalCloseEvent(this, error);
|
||||||
|
}
|
||||||
|
|
||||||
|
AsyncSocksProxyServerSocket::AsyncSocksProxyServerSocket(AsyncSocket* socket)
|
||||||
|
: AsyncProxyServerSocket(socket, kBufferSize), state_(SS_HELLO) {
|
||||||
|
BufferInput(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxyServerSocket::ProcessInput(char* data, size_t* len) {
|
||||||
|
// TODO: See if the whole message has arrived
|
||||||
|
RTC_DCHECK(state_ < SS_CONNECT_PENDING);
|
||||||
|
|
||||||
|
ByteBufferReader response(data, *len);
|
||||||
|
if (state_ == SS_HELLO) {
|
||||||
|
HandleHello(&response);
|
||||||
|
} else if (state_ == SS_AUTH) {
|
||||||
|
HandleAuth(&response);
|
||||||
|
} else if (state_ == SS_CONNECT) {
|
||||||
|
HandleConnect(&response);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consume parsed data
|
||||||
|
*len = response.Length();
|
||||||
|
memmove(data, response.Data(), *len);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxyServerSocket::DirectSend(const ByteBufferWriter& buf) {
|
||||||
|
BufferedReadAdapter::DirectSend(buf.Data(), buf.Length());
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxyServerSocket::HandleHello(ByteBufferReader* request) {
|
||||||
|
uint8_t ver, num_methods;
|
||||||
|
if (!request->ReadUInt8(&ver) ||
|
||||||
|
!request->ReadUInt8(&num_methods)) {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ver != 5) {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle either no-auth (0) or user/pass auth (2)
|
||||||
|
uint8_t method = 0xFF;
|
||||||
|
if (num_methods > 0 && !request->ReadUInt8(&method)) {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Ask the server which method to use.
|
||||||
|
SendHelloReply(method);
|
||||||
|
if (method == 0) {
|
||||||
|
state_ = SS_CONNECT;
|
||||||
|
} else if (method == 2) {
|
||||||
|
state_ = SS_AUTH;
|
||||||
|
} else {
|
||||||
|
state_ = SS_ERROR;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxyServerSocket::SendHelloReply(uint8_t method) {
|
||||||
|
ByteBufferWriter response;
|
||||||
|
response.WriteUInt8(5); // Socks Version
|
||||||
|
response.WriteUInt8(method); // Auth method
|
||||||
|
DirectSend(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxyServerSocket::HandleAuth(ByteBufferReader* request) {
|
||||||
|
uint8_t ver, user_len, pass_len;
|
||||||
|
std::string user, pass;
|
||||||
|
if (!request->ReadUInt8(&ver) ||
|
||||||
|
!request->ReadUInt8(&user_len) ||
|
||||||
|
!request->ReadString(&user, user_len) ||
|
||||||
|
!request->ReadUInt8(&pass_len) ||
|
||||||
|
!request->ReadString(&pass, pass_len)) {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Allow for checking of credentials.
|
||||||
|
SendAuthReply(0);
|
||||||
|
state_ = SS_CONNECT;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxyServerSocket::SendAuthReply(uint8_t result) {
|
||||||
|
ByteBufferWriter response;
|
||||||
|
response.WriteUInt8(1); // Negotiation Version
|
||||||
|
response.WriteUInt8(result);
|
||||||
|
DirectSend(response);
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxyServerSocket::HandleConnect(ByteBufferReader* request) {
|
||||||
|
uint8_t ver, command, reserved, addr_type;
|
||||||
|
uint32_t ip;
|
||||||
|
uint16_t port;
|
||||||
|
if (!request->ReadUInt8(&ver) ||
|
||||||
|
!request->ReadUInt8(&command) ||
|
||||||
|
!request->ReadUInt8(&reserved) ||
|
||||||
|
!request->ReadUInt8(&addr_type) ||
|
||||||
|
!request->ReadUInt32(&ip) ||
|
||||||
|
!request->ReadUInt16(&port)) {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ver != 5 || command != 1 ||
|
||||||
|
reserved != 0 || addr_type != 1) {
|
||||||
|
Error(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SignalConnectRequest(this, SocketAddress(ip, port));
|
||||||
|
state_ = SS_CONNECT_PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxyServerSocket::SendConnectResult(int result,
|
||||||
|
const SocketAddress& addr) {
|
||||||
|
if (state_ != SS_CONNECT_PENDING)
|
||||||
|
return;
|
||||||
|
|
||||||
|
ByteBufferWriter response;
|
||||||
|
response.WriteUInt8(5); // Socks version
|
||||||
|
response.WriteUInt8((result != 0)); // 0x01 is generic error
|
||||||
|
response.WriteUInt8(0); // reserved
|
||||||
|
response.WriteUInt8(1); // IPv4 address
|
||||||
|
response.WriteUInt32(addr.ip());
|
||||||
|
response.WriteUInt16(addr.port());
|
||||||
|
DirectSend(response);
|
||||||
|
BufferInput(false);
|
||||||
|
state_ = SS_TUNNEL;
|
||||||
|
}
|
||||||
|
|
||||||
|
void AsyncSocksProxyServerSocket::Error(int error) {
|
||||||
|
state_ = SS_ERROR;
|
||||||
|
BufferInput(false);
|
||||||
|
Close();
|
||||||
|
SetError(SOCKET_EACCES);
|
||||||
|
SignalCloseEvent(this, error);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|||||||
@ -16,10 +16,12 @@
|
|||||||
|
|
||||||
#include "webrtc/base/asyncsocket.h"
|
#include "webrtc/base/asyncsocket.h"
|
||||||
#include "webrtc/base/constructormagic.h"
|
#include "webrtc/base/constructormagic.h"
|
||||||
|
#include "webrtc/base/cryptstring.h"
|
||||||
#include "webrtc/base/logging.h"
|
#include "webrtc/base/logging.h"
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
|
struct HttpAuthContext;
|
||||||
class ByteBufferReader;
|
class ByteBufferReader;
|
||||||
class ByteBufferWriter;
|
class ByteBufferWriter;
|
||||||
|
|
||||||
@ -92,6 +94,114 @@ class AsyncSSLServerSocket : public BufferedReadAdapter {
|
|||||||
RTC_DISALLOW_COPY_AND_ASSIGN(AsyncSSLServerSocket);
|
RTC_DISALLOW_COPY_AND_ASSIGN(AsyncSSLServerSocket);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Implements a socket adapter that speaks the HTTP/S proxy protocol.
|
||||||
|
class AsyncHttpsProxySocket : public BufferedReadAdapter {
|
||||||
|
public:
|
||||||
|
AsyncHttpsProxySocket(AsyncSocket* socket, const std::string& user_agent,
|
||||||
|
const SocketAddress& proxy,
|
||||||
|
const std::string& username, const CryptString& password);
|
||||||
|
~AsyncHttpsProxySocket() override;
|
||||||
|
|
||||||
|
// If connect is forced, the adapter will always issue an HTTP CONNECT to the
|
||||||
|
// target address. Otherwise, it will connect only if the destination port
|
||||||
|
// is not port 80.
|
||||||
|
void SetForceConnect(bool force) { force_connect_ = force; }
|
||||||
|
|
||||||
|
int Connect(const SocketAddress& addr) override;
|
||||||
|
SocketAddress GetRemoteAddress() const override;
|
||||||
|
int Close() override;
|
||||||
|
ConnState GetState() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void OnConnectEvent(AsyncSocket* socket) override;
|
||||||
|
void OnCloseEvent(AsyncSocket* socket, int err) override;
|
||||||
|
void ProcessInput(char* data, size_t* len) override;
|
||||||
|
|
||||||
|
bool ShouldIssueConnect() const;
|
||||||
|
void SendRequest();
|
||||||
|
void ProcessLine(char* data, size_t len);
|
||||||
|
void EndResponse();
|
||||||
|
void Error(int error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
SocketAddress proxy_, dest_;
|
||||||
|
std::string agent_, user_, headers_;
|
||||||
|
CryptString pass_;
|
||||||
|
bool force_connect_;
|
||||||
|
size_t content_length_;
|
||||||
|
int defer_error_;
|
||||||
|
bool expect_close_;
|
||||||
|
enum ProxyState {
|
||||||
|
PS_INIT, PS_LEADER, PS_AUTHENTICATE, PS_SKIP_HEADERS, PS_ERROR_HEADERS,
|
||||||
|
PS_TUNNEL_HEADERS, PS_SKIP_BODY, PS_TUNNEL, PS_WAIT_CLOSE, PS_ERROR
|
||||||
|
} state_;
|
||||||
|
HttpAuthContext * context_;
|
||||||
|
std::string unknown_mechanisms_;
|
||||||
|
RTC_DISALLOW_COPY_AND_ASSIGN(AsyncHttpsProxySocket);
|
||||||
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Implements a socket adapter that speaks the SOCKS proxy protocol.
|
||||||
|
class AsyncSocksProxySocket : public BufferedReadAdapter {
|
||||||
|
public:
|
||||||
|
AsyncSocksProxySocket(AsyncSocket* socket, const SocketAddress& proxy,
|
||||||
|
const std::string& username, const CryptString& password);
|
||||||
|
~AsyncSocksProxySocket() override;
|
||||||
|
|
||||||
|
int Connect(const SocketAddress& addr) override;
|
||||||
|
SocketAddress GetRemoteAddress() const override;
|
||||||
|
int Close() override;
|
||||||
|
ConnState GetState() const override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void OnConnectEvent(AsyncSocket* socket) override;
|
||||||
|
void ProcessInput(char* data, size_t* len) override;
|
||||||
|
|
||||||
|
void SendHello();
|
||||||
|
void SendConnect();
|
||||||
|
void SendAuth();
|
||||||
|
void Error(int error);
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum State {
|
||||||
|
SS_INIT, SS_HELLO, SS_AUTH, SS_CONNECT, SS_TUNNEL, SS_ERROR
|
||||||
|
};
|
||||||
|
State state_;
|
||||||
|
SocketAddress proxy_, dest_;
|
||||||
|
std::string user_;
|
||||||
|
CryptString pass_;
|
||||||
|
RTC_DISALLOW_COPY_AND_ASSIGN(AsyncSocksProxySocket);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Implements a proxy server socket for the SOCKS protocol.
|
||||||
|
class AsyncSocksProxyServerSocket : public AsyncProxyServerSocket {
|
||||||
|
public:
|
||||||
|
explicit AsyncSocksProxyServerSocket(AsyncSocket* socket);
|
||||||
|
|
||||||
|
private:
|
||||||
|
void ProcessInput(char* data, size_t* len) override;
|
||||||
|
void DirectSend(const ByteBufferWriter& buf);
|
||||||
|
|
||||||
|
void HandleHello(ByteBufferReader* request);
|
||||||
|
void SendHelloReply(uint8_t method);
|
||||||
|
void HandleAuth(ByteBufferReader* request);
|
||||||
|
void SendAuthReply(uint8_t result);
|
||||||
|
void HandleConnect(ByteBufferReader* request);
|
||||||
|
void SendConnectResult(int result, const SocketAddress& addr) override;
|
||||||
|
|
||||||
|
void Error(int error);
|
||||||
|
|
||||||
|
static const int kBufferSize = 1024;
|
||||||
|
enum State {
|
||||||
|
SS_HELLO, SS_AUTH, SS_CONNECT, SS_CONNECT_PENDING, SS_TUNNEL, SS_ERROR
|
||||||
|
};
|
||||||
|
State state_;
|
||||||
|
RTC_DISALLOW_COPY_AND_ASSIGN(AsyncSocksProxyServerSocket);
|
||||||
|
};
|
||||||
|
|
||||||
} // namespace rtc
|
} // namespace rtc
|
||||||
|
|
||||||
#endif // WEBRTC_BASE_SOCKETADAPTERS_H_
|
#endif // WEBRTC_BASE_SOCKETADAPTERS_H_
|
||||||
|
|||||||
@ -237,6 +237,61 @@ void StreamAdapterInterface::OnEvent(StreamInterface* stream,
|
|||||||
SignalEvent(this, events, err);
|
SignalEvent(this, events, err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// StreamTap
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
StreamTap::StreamTap(StreamInterface* stream, StreamInterface* tap)
|
||||||
|
: StreamAdapterInterface(stream), tap_(), tap_result_(SR_SUCCESS),
|
||||||
|
tap_error_(0) {
|
||||||
|
AttachTap(tap);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamTap::~StreamTap() = default;
|
||||||
|
|
||||||
|
void StreamTap::AttachTap(StreamInterface* tap) {
|
||||||
|
tap_.reset(tap);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamInterface* StreamTap::DetachTap() {
|
||||||
|
return tap_.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamResult StreamTap::GetTapResult(int* error) {
|
||||||
|
if (error) {
|
||||||
|
*error = tap_error_;
|
||||||
|
}
|
||||||
|
return tap_result_;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamResult StreamTap::Read(void* buffer, size_t buffer_len,
|
||||||
|
size_t* read, int* error) {
|
||||||
|
size_t backup_read;
|
||||||
|
if (!read) {
|
||||||
|
read = &backup_read;
|
||||||
|
}
|
||||||
|
StreamResult res = StreamAdapterInterface::Read(buffer, buffer_len,
|
||||||
|
read, error);
|
||||||
|
if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) {
|
||||||
|
tap_result_ = tap_->WriteAll(buffer, *read, nullptr, &tap_error_);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamResult StreamTap::Write(const void* data, size_t data_len,
|
||||||
|
size_t* written, int* error) {
|
||||||
|
size_t backup_written;
|
||||||
|
if (!written) {
|
||||||
|
written = &backup_written;
|
||||||
|
}
|
||||||
|
StreamResult res = StreamAdapterInterface::Write(data, data_len,
|
||||||
|
written, error);
|
||||||
|
if ((res == SR_SUCCESS) && (tap_result_ == SR_SUCCESS)) {
|
||||||
|
tap_result_ = tap_->WriteAll(data, *written, nullptr, &tap_error_);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// NullStream
|
// NullStream
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
@ -606,6 +661,24 @@ StreamResult MemoryStream::DoReserve(size_t size, int* error) {
|
|||||||
return SR_ERROR;
|
return SR_ERROR;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
ExternalMemoryStream::ExternalMemoryStream() {
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalMemoryStream::ExternalMemoryStream(void* data, size_t length) {
|
||||||
|
SetData(data, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
ExternalMemoryStream::~ExternalMemoryStream() {
|
||||||
|
}
|
||||||
|
|
||||||
|
void ExternalMemoryStream::SetData(void* data, size_t length) {
|
||||||
|
data_length_ = buffer_length_ = length;
|
||||||
|
buffer_ = static_cast<char*>(data);
|
||||||
|
seek_position_ = 0;
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// FifoBuffer
|
// FifoBuffer
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
@ -821,6 +894,66 @@ StreamResult FifoBuffer::WriteOffsetLocked(const void* buffer,
|
|||||||
return SR_SUCCESS;
|
return SR_SUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// LoggingAdapter
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
LoggingAdapter::LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
|
||||||
|
const std::string& label, bool hex_mode)
|
||||||
|
: StreamAdapterInterface(stream), level_(level), hex_mode_(hex_mode) {
|
||||||
|
set_label(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingAdapter::set_label(const std::string& label) {
|
||||||
|
label_.assign("[");
|
||||||
|
label_.append(label);
|
||||||
|
label_.append("]");
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamResult LoggingAdapter::Read(void* buffer, size_t buffer_len,
|
||||||
|
size_t* read, int* error) {
|
||||||
|
size_t local_read; if (!read) read = &local_read;
|
||||||
|
StreamResult result = StreamAdapterInterface::Read(buffer, buffer_len, read,
|
||||||
|
error);
|
||||||
|
if (result == SR_SUCCESS) {
|
||||||
|
LogMultiline(level_, label_.c_str(), true, buffer, *read, hex_mode_, &lms_);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamResult LoggingAdapter::Write(const void* data, size_t data_len,
|
||||||
|
size_t* written, int* error) {
|
||||||
|
size_t local_written;
|
||||||
|
if (!written) written = &local_written;
|
||||||
|
StreamResult result = StreamAdapterInterface::Write(data, data_len, written,
|
||||||
|
error);
|
||||||
|
if (result == SR_SUCCESS) {
|
||||||
|
LogMultiline(level_, label_.c_str(), false, data, *written, hex_mode_,
|
||||||
|
&lms_);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingAdapter::Close() {
|
||||||
|
LogMultiline(level_, label_.c_str(), false, nullptr, 0, hex_mode_, &lms_);
|
||||||
|
LogMultiline(level_, label_.c_str(), true, nullptr, 0, hex_mode_, &lms_);
|
||||||
|
LOG_V(level_) << label_ << " Closed locally";
|
||||||
|
StreamAdapterInterface::Close();
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoggingAdapter::OnEvent(StreamInterface* stream, int events, int err) {
|
||||||
|
if (events & SE_OPEN) {
|
||||||
|
LOG_V(level_) << label_ << " Open";
|
||||||
|
} else if (events & SE_CLOSE) {
|
||||||
|
LogMultiline(level_, label_.c_str(), false, nullptr, 0, hex_mode_, &lms_);
|
||||||
|
LogMultiline(level_, label_.c_str(), true, nullptr, 0, hex_mode_, &lms_);
|
||||||
|
LOG_V(level_) << label_ << " Closed with error: " << err;
|
||||||
|
}
|
||||||
|
StreamAdapterInterface::OnEvent(stream, events, err);
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// StringStream - Reads/Writes to an external std::string
|
// StringStream - Reads/Writes to an external std::string
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
@ -899,6 +1032,31 @@ bool StringStream::ReserveSize(size_t size) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// StreamReference
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
StreamReference::StreamReference(StreamInterface* stream)
|
||||||
|
: StreamAdapterInterface(stream, false) {
|
||||||
|
// owner set to false so the destructor does not free the stream.
|
||||||
|
stream_ref_count_ = new StreamRefCount(stream);
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamInterface* StreamReference::NewReference() {
|
||||||
|
stream_ref_count_->AddReference();
|
||||||
|
return new StreamReference(stream_ref_count_, stream());
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamReference::~StreamReference() {
|
||||||
|
stream_ref_count_->Release();
|
||||||
|
}
|
||||||
|
|
||||||
|
StreamReference::StreamReference(StreamRefCount* stream_ref_count,
|
||||||
|
StreamInterface* stream)
|
||||||
|
: StreamAdapterInterface(stream, false),
|
||||||
|
stream_ref_count_(stream_ref_count) {
|
||||||
|
}
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
StreamResult Flow(StreamInterface* source,
|
StreamResult Flow(StreamInterface* source,
|
||||||
|
|||||||
@ -309,6 +309,38 @@ class StreamAdapterInterface : public StreamInterface,
|
|||||||
RTC_DISALLOW_COPY_AND_ASSIGN(StreamAdapterInterface);
|
RTC_DISALLOW_COPY_AND_ASSIGN(StreamAdapterInterface);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// StreamTap is a non-modifying, pass-through adapter, which copies all data
|
||||||
|
// in either direction to the tap. Note that errors or blocking on writing to
|
||||||
|
// the tap will prevent further tap writes from occurring.
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class StreamTap : public StreamAdapterInterface {
|
||||||
|
public:
|
||||||
|
explicit StreamTap(StreamInterface* stream, StreamInterface* tap);
|
||||||
|
~StreamTap() override;
|
||||||
|
|
||||||
|
void AttachTap(StreamInterface* tap);
|
||||||
|
StreamInterface* DetachTap();
|
||||||
|
StreamResult GetTapResult(int* error);
|
||||||
|
|
||||||
|
// StreamAdapterInterface Interface
|
||||||
|
StreamResult Read(void* buffer,
|
||||||
|
size_t buffer_len,
|
||||||
|
size_t* read,
|
||||||
|
int* error) override;
|
||||||
|
StreamResult Write(const void* data,
|
||||||
|
size_t data_len,
|
||||||
|
size_t* written,
|
||||||
|
int* error) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
std::unique_ptr<StreamInterface> tap_;
|
||||||
|
StreamResult tap_result_;
|
||||||
|
int tap_error_;
|
||||||
|
RTC_DISALLOW_COPY_AND_ASSIGN(StreamTap);
|
||||||
|
};
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// NullStream gives errors on read, and silently discards all written data.
|
// NullStream gives errors on read, and silently discards all written data.
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
@ -448,6 +480,18 @@ class MemoryStream : public MemoryStreamBase {
|
|||||||
char* buffer_alloc_;
|
char* buffer_alloc_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// ExternalMemoryStream adapts an external memory buffer, so writes which would
|
||||||
|
// extend past the end of the buffer will return end-of-stream.
|
||||||
|
|
||||||
|
class ExternalMemoryStream : public MemoryStreamBase {
|
||||||
|
public:
|
||||||
|
ExternalMemoryStream();
|
||||||
|
ExternalMemoryStream(void* data, size_t length);
|
||||||
|
~ExternalMemoryStream() override;
|
||||||
|
|
||||||
|
void SetData(void* data, size_t length);
|
||||||
|
};
|
||||||
|
|
||||||
// FifoBuffer allows for efficient, thread-safe buffering of data between
|
// FifoBuffer allows for efficient, thread-safe buffering of data between
|
||||||
// writer and reader. As the data can wrap around the end of the buffer,
|
// writer and reader. As the data can wrap around the end of the buffer,
|
||||||
// MemoryStreamBase can't help us here.
|
// MemoryStreamBase can't help us here.
|
||||||
@ -525,6 +569,37 @@ class FifoBuffer : public StreamInterface {
|
|||||||
RTC_DISALLOW_COPY_AND_ASSIGN(FifoBuffer);
|
RTC_DISALLOW_COPY_AND_ASSIGN(FifoBuffer);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
class LoggingAdapter : public StreamAdapterInterface {
|
||||||
|
public:
|
||||||
|
LoggingAdapter(StreamInterface* stream, LoggingSeverity level,
|
||||||
|
const std::string& label, bool hex_mode = false);
|
||||||
|
|
||||||
|
void set_label(const std::string& label);
|
||||||
|
|
||||||
|
StreamResult Read(void* buffer,
|
||||||
|
size_t buffer_len,
|
||||||
|
size_t* read,
|
||||||
|
int* error) override;
|
||||||
|
StreamResult Write(const void* data,
|
||||||
|
size_t data_len,
|
||||||
|
size_t* written,
|
||||||
|
int* error) override;
|
||||||
|
void Close() override;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void OnEvent(StreamInterface* stream, int events, int err) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
LoggingSeverity level_;
|
||||||
|
std::string label_;
|
||||||
|
bool hex_mode_;
|
||||||
|
LogMultilineState lms_;
|
||||||
|
|
||||||
|
RTC_DISALLOW_COPY_AND_ASSIGN(LoggingAdapter);
|
||||||
|
};
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
// StringStream - Reads/Writes to an external std::string
|
// StringStream - Reads/Writes to an external std::string
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
@ -556,6 +631,66 @@ class StringStream : public StreamInterface {
|
|||||||
bool read_only_;
|
bool read_only_;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
// StreamReference - A reference counting stream adapter
|
||||||
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
|
// Keep in mind that the streams and adapters defined in this file are
|
||||||
|
// not thread-safe, so this has limited uses.
|
||||||
|
|
||||||
|
// A StreamRefCount holds the reference count and a pointer to the
|
||||||
|
// wrapped stream. It deletes the wrapped stream when there are no
|
||||||
|
// more references. We can then have multiple StreamReference
|
||||||
|
// instances pointing to one StreamRefCount, all wrapping the same
|
||||||
|
// stream.
|
||||||
|
|
||||||
|
class StreamReference : public StreamAdapterInterface {
|
||||||
|
class StreamRefCount;
|
||||||
|
public:
|
||||||
|
// Constructor for the first reference to a stream
|
||||||
|
// Note: get more references through NewReference(). Use this
|
||||||
|
// constructor only once on a given stream.
|
||||||
|
explicit StreamReference(StreamInterface* stream);
|
||||||
|
StreamInterface* GetStream() { return stream(); }
|
||||||
|
StreamInterface* NewReference();
|
||||||
|
~StreamReference() override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
class StreamRefCount {
|
||||||
|
public:
|
||||||
|
explicit StreamRefCount(StreamInterface* stream)
|
||||||
|
: stream_(stream), ref_count_(1) {
|
||||||
|
}
|
||||||
|
void AddReference() {
|
||||||
|
CritScope lock(&cs_);
|
||||||
|
++ref_count_;
|
||||||
|
}
|
||||||
|
void Release() {
|
||||||
|
int ref_count;
|
||||||
|
{ // Atomic ops would have been a better fit here.
|
||||||
|
CritScope lock(&cs_);
|
||||||
|
ref_count = --ref_count_;
|
||||||
|
}
|
||||||
|
if (ref_count == 0) {
|
||||||
|
delete stream_;
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private:
|
||||||
|
StreamInterface* stream_;
|
||||||
|
int ref_count_;
|
||||||
|
CriticalSection cs_;
|
||||||
|
RTC_DISALLOW_COPY_AND_ASSIGN(StreamRefCount);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constructor for adding references
|
||||||
|
explicit StreamReference(StreamRefCount* stream_ref_count,
|
||||||
|
StreamInterface* stream);
|
||||||
|
|
||||||
|
StreamRefCount* stream_ref_count_;
|
||||||
|
RTC_DISALLOW_COPY_AND_ASSIGN(StreamReference);
|
||||||
|
};
|
||||||
|
|
||||||
///////////////////////////////////////////////////////////////////////////////
|
///////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
// Flow attempts to move bytes from source to sink via buffer of size
|
// Flow attempts to move bytes from source to sink via buffer of size
|
||||||
|
|||||||
@ -120,6 +120,16 @@ AsyncPacketSocket* BasicPacketSocketFactory::CreateClientTcpSocket(
|
|||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If using a proxy, wrap the socket in a proxy socket.
|
||||||
|
if (proxy_info.type == PROXY_SOCKS5) {
|
||||||
|
socket = new AsyncSocksProxySocket(
|
||||||
|
socket, proxy_info.address, proxy_info.username, proxy_info.password);
|
||||||
|
} else if (proxy_info.type == PROXY_HTTPS) {
|
||||||
|
socket =
|
||||||
|
new AsyncHttpsProxySocket(socket, user_agent, proxy_info.address,
|
||||||
|
proxy_info.username, proxy_info.password);
|
||||||
|
}
|
||||||
|
|
||||||
// Assert that at most one TLS option is used.
|
// Assert that at most one TLS option is used.
|
||||||
int tlsOpts =
|
int tlsOpts =
|
||||||
opts & (PacketSocketFactory::OPT_TLS | PacketSocketFactory::OPT_TLS_FAKE |
|
opts & (PacketSocketFactory::OPT_TLS | PacketSocketFactory::OPT_TLS_FAKE |
|
||||||
|
|||||||
@ -23,6 +23,7 @@
|
|||||||
#include "webrtc/base/natserver.h"
|
#include "webrtc/base/natserver.h"
|
||||||
#include "webrtc/base/natsocketfactory.h"
|
#include "webrtc/base/natsocketfactory.h"
|
||||||
#include "webrtc/base/physicalsocketserver.h"
|
#include "webrtc/base/physicalsocketserver.h"
|
||||||
|
#include "webrtc/base/proxyserver.h"
|
||||||
#include "webrtc/base/socketaddress.h"
|
#include "webrtc/base/socketaddress.h"
|
||||||
#include "webrtc/base/ssladapter.h"
|
#include "webrtc/base/ssladapter.h"
|
||||||
#include "webrtc/base/thread.h"
|
#include "webrtc/base/thread.h"
|
||||||
@ -63,6 +64,12 @@ static const SocketAddress kAlternateAddrs[2] = {
|
|||||||
static const SocketAddress kIPv6AlternateAddrs[2] = {
|
static const SocketAddress kIPv6AlternateAddrs[2] = {
|
||||||
SocketAddress("2401:4030:1:2c00:be30:abcd:efab:cdef", 0),
|
SocketAddress("2401:4030:1:2c00:be30:abcd:efab:cdef", 0),
|
||||||
SocketAddress("2601:0:1000:1b03:2e41:38ff:fea6:f2a4", 0)};
|
SocketAddress("2601:0:1000:1b03:2e41:38ff:fea6:f2a4", 0)};
|
||||||
|
// Addresses for HTTP proxy servers.
|
||||||
|
static const SocketAddress kHttpsProxyAddrs[2] =
|
||||||
|
{ SocketAddress("11.11.11.1", 443), SocketAddress("22.22.22.1", 443) };
|
||||||
|
// Addresses for SOCKS proxy servers.
|
||||||
|
static const SocketAddress kSocksProxyAddrs[2] =
|
||||||
|
{ SocketAddress("11.11.11.1", 1080), SocketAddress("22.22.22.1", 1080) };
|
||||||
// Internal addresses for NAT boxes.
|
// Internal addresses for NAT boxes.
|
||||||
static const SocketAddress kNatAddrs[2] =
|
static const SocketAddress kNatAddrs[2] =
|
||||||
{ SocketAddress("192.168.1.1", 0), SocketAddress("192.168.2.1", 0) };
|
{ SocketAddress("192.168.1.1", 0), SocketAddress("192.168.2.1", 0) };
|
||||||
@ -182,6 +189,14 @@ class P2PTransportChannelTestBase : public testing::Test,
|
|||||||
ss_scope_(ss_.get()),
|
ss_scope_(ss_.get()),
|
||||||
stun_server_(TestStunServer::Create(main_, kStunAddr)),
|
stun_server_(TestStunServer::Create(main_, kStunAddr)),
|
||||||
turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr),
|
turn_server_(main_, kTurnUdpIntAddr, kTurnUdpExtAddr),
|
||||||
|
socks_server1_(ss_.get(),
|
||||||
|
kSocksProxyAddrs[0],
|
||||||
|
ss_.get(),
|
||||||
|
kSocksProxyAddrs[0]),
|
||||||
|
socks_server2_(ss_.get(),
|
||||||
|
kSocksProxyAddrs[1],
|
||||||
|
ss_.get(),
|
||||||
|
kSocksProxyAddrs[1]),
|
||||||
force_relay_(false) {
|
force_relay_(false) {
|
||||||
ep1_.role_ = ICEROLE_CONTROLLING;
|
ep1_.role_ = ICEROLE_CONTROLLING;
|
||||||
ep2_.role_ = ICEROLE_CONTROLLED;
|
ep2_.role_ = ICEROLE_CONTROLLED;
|
||||||
@ -213,6 +228,9 @@ class P2PTransportChannelTestBase : public testing::Test,
|
|||||||
NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner
|
NAT_SYMMETRIC_THEN_CONE, // Double NAT, symmetric outer, cone inner
|
||||||
BLOCK_UDP, // Firewall, UDP in/out blocked
|
BLOCK_UDP, // Firewall, UDP in/out blocked
|
||||||
BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked
|
BLOCK_UDP_AND_INCOMING_TCP, // Firewall, UDP in/out and TCP in blocked
|
||||||
|
BLOCK_ALL_BUT_OUTGOING_HTTP, // Firewall, only TCP out on 80/443
|
||||||
|
PROXY_HTTPS, // All traffic through HTTPS proxy
|
||||||
|
PROXY_SOCKS, // All traffic through SOCKS proxy
|
||||||
NUM_CONFIGS
|
NUM_CONFIGS
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -435,6 +453,13 @@ class P2PTransportChannelTestBase : public testing::Test,
|
|||||||
GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr);
|
GetEndpoint(endpoint)->network_manager_.RemoveInterface(addr);
|
||||||
fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, addr);
|
fw()->AddRule(false, rtc::FP_ANY, rtc::FD_ANY, addr);
|
||||||
}
|
}
|
||||||
|
void SetProxy(int endpoint, rtc::ProxyType type) {
|
||||||
|
rtc::ProxyInfo info;
|
||||||
|
info.type = type;
|
||||||
|
info.address = (type == rtc::PROXY_HTTPS) ?
|
||||||
|
kHttpsProxyAddrs[endpoint] : kSocksProxyAddrs[endpoint];
|
||||||
|
GetAllocator(endpoint)->set_proxy("unittest/1.0", info);
|
||||||
|
}
|
||||||
void SetAllocatorFlags(int endpoint, int flags) {
|
void SetAllocatorFlags(int endpoint, int flags) {
|
||||||
GetAllocator(endpoint)->set_flags(flags);
|
GetAllocator(endpoint)->set_flags(flags);
|
||||||
}
|
}
|
||||||
@ -857,6 +882,8 @@ class P2PTransportChannelTestBase : public testing::Test,
|
|||||||
rtc::SocketServerScope ss_scope_;
|
rtc::SocketServerScope ss_scope_;
|
||||||
std::unique_ptr<TestStunServer> stun_server_;
|
std::unique_ptr<TestStunServer> stun_server_;
|
||||||
TestTurnServer turn_server_;
|
TestTurnServer turn_server_;
|
||||||
|
rtc::SocksProxyServer socks_server1_;
|
||||||
|
rtc::SocksProxyServer socks_server2_;
|
||||||
Endpoint ep1_;
|
Endpoint ep1_;
|
||||||
Endpoint ep2_;
|
Endpoint ep2_;
|
||||||
RemoteIceParameterSource remote_ice_parameter_source_ = FROM_CANDIDATE;
|
RemoteIceParameterSource remote_ice_parameter_source_ = FROM_CANDIDATE;
|
||||||
@ -998,6 +1025,9 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase {
|
|||||||
break;
|
break;
|
||||||
case BLOCK_UDP:
|
case BLOCK_UDP:
|
||||||
case BLOCK_UDP_AND_INCOMING_TCP:
|
case BLOCK_UDP_AND_INCOMING_TCP:
|
||||||
|
case BLOCK_ALL_BUT_OUTGOING_HTTP:
|
||||||
|
case PROXY_HTTPS:
|
||||||
|
case PROXY_SOCKS:
|
||||||
AddAddress(endpoint, kPublicAddrs[endpoint]);
|
AddAddress(endpoint, kPublicAddrs[endpoint]);
|
||||||
// Block all UDP
|
// Block all UDP
|
||||||
fw()->AddRule(false, rtc::FP_UDP, rtc::FD_ANY,
|
fw()->AddRule(false, rtc::FP_UDP, rtc::FD_ANY,
|
||||||
@ -1006,6 +1036,28 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase {
|
|||||||
// Block TCP inbound to the endpoint
|
// Block TCP inbound to the endpoint
|
||||||
fw()->AddRule(false, rtc::FP_TCP, SocketAddress(),
|
fw()->AddRule(false, rtc::FP_TCP, SocketAddress(),
|
||||||
kPublicAddrs[endpoint]);
|
kPublicAddrs[endpoint]);
|
||||||
|
} else if (config == BLOCK_ALL_BUT_OUTGOING_HTTP) {
|
||||||
|
// Block all TCP to/from the endpoint except 80/443 out
|
||||||
|
fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
|
||||||
|
SocketAddress(rtc::IPAddress(INADDR_ANY), 80));
|
||||||
|
fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
|
||||||
|
SocketAddress(rtc::IPAddress(INADDR_ANY), 443));
|
||||||
|
fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
|
||||||
|
kPublicAddrs[endpoint]);
|
||||||
|
} else if (config == PROXY_HTTPS) {
|
||||||
|
// Block all TCP to/from the endpoint except to the proxy server
|
||||||
|
fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
|
||||||
|
kHttpsProxyAddrs[endpoint]);
|
||||||
|
fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
|
||||||
|
kPublicAddrs[endpoint]);
|
||||||
|
SetProxy(endpoint, rtc::PROXY_HTTPS);
|
||||||
|
} else if (config == PROXY_SOCKS) {
|
||||||
|
// Block all TCP to/from the endpoint except to the proxy server
|
||||||
|
fw()->AddRule(true, rtc::FP_TCP, kPublicAddrs[endpoint],
|
||||||
|
kSocksProxyAddrs[endpoint]);
|
||||||
|
fw()->AddRule(false, rtc::FP_TCP, rtc::FD_ANY,
|
||||||
|
kPublicAddrs[endpoint]);
|
||||||
|
SetProxy(endpoint, rtc::PROXY_SOCKS5);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
@ -1036,19 +1088,23 @@ class P2PTransportChannelTest : public P2PTransportChannelTestBase {
|
|||||||
// Test matrix. Originator behavior defined by rows, receiever by columns.
|
// Test matrix. Originator behavior defined by rows, receiever by columns.
|
||||||
|
|
||||||
// TODO: Fix NULLs caused by lack of TCP support in NATSocket.
|
// TODO: Fix NULLs caused by lack of TCP support in NATSocket.
|
||||||
|
// TODO: Fix NULLs caused by no HTTP proxy support.
|
||||||
// TODO: Rearrange rows/columns from best to worst.
|
// TODO: Rearrange rows/columns from best to worst.
|
||||||
const P2PTransportChannelTest::Result*
|
const P2PTransportChannelTest::Result*
|
||||||
P2PTransportChannelTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = {
|
P2PTransportChannelTest::kMatrix[NUM_CONFIGS][NUM_CONFIGS] = {
|
||||||
// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP
|
// OPEN CONE ADDR PORT SYMM 2CON SCON !UDP !TCP HTTP PRXH PRXS
|
||||||
/*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, LTPT, LTPT},
|
/*OP*/ {LULU, LUSU, LUSU, LUSU, LUPU, LUSU, LUPU, LTPT, LTPT, LSRS, NULL, LTPT},
|
||||||
/*CO*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL},
|
/*CO*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, LTRT},
|
||||||
/*AD*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL},
|
/*AD*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, LTRT},
|
||||||
/*PO*/ {SULU, SUSU, SUSU, SUSU, RUPU, SUSU, RUPU, NULL, NULL},
|
/*PO*/ {SULU, SUSU, SUSU, SUSU, RUPU, SUSU, RUPU, NULL, NULL, LSRS, NULL, LTRT},
|
||||||
/*SY*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL},
|
/*SY*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT},
|
||||||
/*2C*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL},
|
/*2C*/ {SULU, SUSU, SUSU, SUSU, SUPU, SUSU, SUPU, NULL, NULL, LSRS, NULL, LTRT},
|
||||||
/*SC*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL},
|
/*SC*/ {PULU, PUSU, PUSU, PURU, PURU, PUSU, PURU, NULL, NULL, LSRS, NULL, LTRT},
|
||||||
/*!U*/ {LTPT, NULL, NULL, NULL, NULL, NULL, NULL, LTPT, LTPT},
|
/*!U*/ {LTPT, NULL, NULL, NULL, NULL, NULL, NULL, LTPT, LTPT, LSRS, NULL, LTRT},
|
||||||
/*!T*/ {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT},
|
/*!T*/ {PTLT, NULL, NULL, NULL, NULL, NULL, NULL, PTLT, LTRT, LSRS, NULL, LTRT},
|
||||||
|
/*HT*/ {LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, LSRS, NULL, LSRS},
|
||||||
|
/*PR*/ {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL},
|
||||||
|
/*PR*/ {LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LTRT, LSRS, NULL, LTRT},
|
||||||
};
|
};
|
||||||
|
|
||||||
// The actual tests that exercise all the various configurations.
|
// The actual tests that exercise all the various configurations.
|
||||||
@ -1075,7 +1131,10 @@ const P2PTransportChannelTest::Result*
|
|||||||
P2P_TEST(x, NAT_DOUBLE_CONE) \
|
P2P_TEST(x, NAT_DOUBLE_CONE) \
|
||||||
P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
|
P2P_TEST(x, NAT_SYMMETRIC_THEN_CONE) \
|
||||||
P2P_TEST(x, BLOCK_UDP) \
|
P2P_TEST(x, BLOCK_UDP) \
|
||||||
P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP)
|
P2P_TEST(x, BLOCK_UDP_AND_INCOMING_TCP) \
|
||||||
|
P2P_TEST(x, BLOCK_ALL_BUT_OUTGOING_HTTP) \
|
||||||
|
P2P_TEST(x, PROXY_HTTPS) \
|
||||||
|
P2P_TEST(x, PROXY_SOCKS)
|
||||||
|
|
||||||
P2P_TEST_SET(OPEN)
|
P2P_TEST_SET(OPEN)
|
||||||
P2P_TEST_SET(NAT_FULL_CONE)
|
P2P_TEST_SET(NAT_FULL_CONE)
|
||||||
@ -1086,6 +1145,9 @@ P2P_TEST_SET(NAT_DOUBLE_CONE)
|
|||||||
P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE)
|
P2P_TEST_SET(NAT_SYMMETRIC_THEN_CONE)
|
||||||
P2P_TEST_SET(BLOCK_UDP)
|
P2P_TEST_SET(BLOCK_UDP)
|
||||||
P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP)
|
P2P_TEST_SET(BLOCK_UDP_AND_INCOMING_TCP)
|
||||||
|
P2P_TEST_SET(BLOCK_ALL_BUT_OUTGOING_HTTP)
|
||||||
|
P2P_TEST_SET(PROXY_HTTPS)
|
||||||
|
P2P_TEST_SET(PROXY_SOCKS)
|
||||||
|
|
||||||
// Test that we restart candidate allocation when local ufrag&pwd changed.
|
// Test that we restart candidate allocation when local ufrag&pwd changed.
|
||||||
// Standard Ice protocol is used.
|
// Standard Ice protocol is used.
|
||||||
|
|||||||
@ -13,7 +13,6 @@
|
|||||||
|
|
||||||
#include "webrtc/base/constructormagic.h"
|
#include "webrtc/base/constructormagic.h"
|
||||||
#include "webrtc/base/proxyinfo.h"
|
#include "webrtc/base/proxyinfo.h"
|
||||||
#include "webrtc/base/socketaddress.h"
|
|
||||||
|
|
||||||
namespace rtc {
|
namespace rtc {
|
||||||
|
|
||||||
|
|||||||
@ -19,6 +19,7 @@
|
|||||||
#include "webrtc/p2p/base/port.h"
|
#include "webrtc/p2p/base/port.h"
|
||||||
#include "webrtc/p2p/base/portinterface.h"
|
#include "webrtc/p2p/base/portinterface.h"
|
||||||
#include "webrtc/base/helpers.h"
|
#include "webrtc/base/helpers.h"
|
||||||
|
#include "webrtc/base/proxyinfo.h"
|
||||||
#include "webrtc/base/sigslot.h"
|
#include "webrtc/base/sigslot.h"
|
||||||
#include "webrtc/base/thread.h"
|
#include "webrtc/base/thread.h"
|
||||||
|
|
||||||
@ -383,6 +384,16 @@ class PortAllocator : public sigslot::has_slots<> {
|
|||||||
uint32_t flags() const { return flags_; }
|
uint32_t flags() const { return flags_; }
|
||||||
void set_flags(uint32_t flags) { flags_ = flags; }
|
void set_flags(uint32_t flags) { flags_ = flags; }
|
||||||
|
|
||||||
|
// These three methods are deprecated. If connections need to go through a
|
||||||
|
// proxy, the application should create a BasicPortAllocator given a custom
|
||||||
|
// PacketSocketFactory that creates proxy sockets.
|
||||||
|
const std::string& user_agent() const { return agent_; }
|
||||||
|
const rtc::ProxyInfo& proxy() const { return proxy_; }
|
||||||
|
void set_proxy(const std::string& agent, const rtc::ProxyInfo& proxy) {
|
||||||
|
agent_ = agent;
|
||||||
|
proxy_ = proxy;
|
||||||
|
}
|
||||||
|
|
||||||
// Gets/Sets the port range to use when choosing client ports.
|
// Gets/Sets the port range to use when choosing client ports.
|
||||||
int min_port() const { return min_port_; }
|
int min_port() const { return min_port_; }
|
||||||
int max_port() const { return max_port_; }
|
int max_port() const { return max_port_; }
|
||||||
@ -435,6 +446,8 @@ class PortAllocator : public sigslot::has_slots<> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
uint32_t flags_;
|
uint32_t flags_;
|
||||||
|
std::string agent_;
|
||||||
|
rtc::ProxyInfo proxy_;
|
||||||
int min_port_;
|
int min_port_;
|
||||||
int max_port_;
|
int max_port_;
|
||||||
uint32_t step_delay_;
|
uint32_t step_delay_;
|
||||||
|
|||||||
@ -210,7 +210,15 @@ RelayPort::~RelayPort() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void RelayPort::AddServerAddress(const ProtocolAddress& addr) {
|
void RelayPort::AddServerAddress(const ProtocolAddress& addr) {
|
||||||
|
// Since HTTP proxies usually only allow 443,
|
||||||
|
// let's up the priority on PROTO_SSLTCP
|
||||||
|
if (addr.proto == PROTO_SSLTCP &&
|
||||||
|
(proxy().type == rtc::PROXY_HTTPS ||
|
||||||
|
proxy().type == rtc::PROXY_UNKNOWN)) {
|
||||||
|
server_addr_.push_front(addr);
|
||||||
|
} else {
|
||||||
server_addr_.push_back(addr);
|
server_addr_.push_back(addr);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void RelayPort::AddExternalAddress(const ProtocolAddress& addr) {
|
void RelayPort::AddExternalAddress(const ProtocolAddress& addr) {
|
||||||
|
|||||||
@ -699,6 +699,8 @@ void BasicPortAllocatorSession::AddAllocatedPort(Port* port,
|
|||||||
port->set_content_name(content_name());
|
port->set_content_name(content_name());
|
||||||
port->set_component(component());
|
port->set_component(component());
|
||||||
port->set_generation(generation());
|
port->set_generation(generation());
|
||||||
|
if (allocator_->proxy().type != rtc::PROXY_NONE)
|
||||||
|
port->set_proxy(allocator_->user_agent(), allocator_->proxy());
|
||||||
port->set_send_retransmit_count_attribute(
|
port->set_send_retransmit_count_attribute(
|
||||||
(flags() & PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE) != 0);
|
(flags() & PORTALLOCATOR_ENABLE_STUN_RETRANSMIT_ATTRIBUTE) != 0);
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user