Add callback for custom certificate selection (issue #1824)
This commit is contained in:
parent
e006ec0ab5
commit
676cb1f07d
|
@ -360,6 +360,8 @@
|
|||
'libcef_dll/cpptoc/scheme_registrar_cpptoc.h',
|
||||
'libcef_dll/cpptoc/views/scroll_view_cpptoc.cc',
|
||||
'libcef_dll/cpptoc/views/scroll_view_cpptoc.h',
|
||||
'libcef_dll/cpptoc/select_client_certificate_callback_cpptoc.cc',
|
||||
'libcef_dll/cpptoc/select_client_certificate_callback_cpptoc.h',
|
||||
'libcef_dll/ctocpp/set_cookie_callback_ctocpp.cc',
|
||||
'libcef_dll/ctocpp/set_cookie_callback_ctocpp.h',
|
||||
'libcef_dll/cpptoc/stream_reader_cpptoc.cc',
|
||||
|
@ -616,6 +618,8 @@
|
|||
'libcef_dll/ctocpp/scheme_registrar_ctocpp.h',
|
||||
'libcef_dll/ctocpp/views/scroll_view_ctocpp.cc',
|
||||
'libcef_dll/ctocpp/views/scroll_view_ctocpp.h',
|
||||
'libcef_dll/ctocpp/select_client_certificate_callback_ctocpp.cc',
|
||||
'libcef_dll/ctocpp/select_client_certificate_callback_ctocpp.h',
|
||||
'libcef_dll/cpptoc/set_cookie_callback_cpptoc.cc',
|
||||
'libcef_dll/cpptoc/set_cookie_callback_cpptoc.h',
|
||||
'libcef_dll/ctocpp/stream_reader_ctocpp.cc',
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
#include "include/capi/cef_response_capi.h"
|
||||
#include "include/capi/cef_response_filter_capi.h"
|
||||
#include "include/capi/cef_ssl_info_capi.h"
|
||||
#include "include/capi/cef_x509_certificate_capi.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
|
@ -75,6 +76,25 @@ typedef struct _cef_request_callback_t {
|
|||
} cef_request_callback_t;
|
||||
|
||||
|
||||
///
|
||||
// Callback structure used to select a client certificate for authentication.
|
||||
///
|
||||
typedef struct _cef_select_client_certificate_callback_t {
|
||||
///
|
||||
// Base structure.
|
||||
///
|
||||
cef_base_t base;
|
||||
|
||||
///
|
||||
// Chooses the specified certificate for client certificate authentication.
|
||||
// NULL value means that no client certificate should be used.
|
||||
///
|
||||
void (CEF_CALLBACK *select)(
|
||||
struct _cef_select_client_certificate_callback_t* self,
|
||||
struct _cef_x509certificate_t* cert);
|
||||
} cef_select_client_certificate_callback_t;
|
||||
|
||||
|
||||
///
|
||||
// Implement this structure to handle events related to browser requests. The
|
||||
// functions of this structure will be called on the thread indicated.
|
||||
|
@ -241,6 +261,26 @@ typedef struct _cef_request_handler_t {
|
|||
const cef_string_t* request_url, struct _cef_sslinfo_t* ssl_info,
|
||||
struct _cef_request_callback_t* callback);
|
||||
|
||||
///
|
||||
// Called on the UI thread when a client certificate is being requested for
|
||||
// authentication. Return false (0) to use the default behavior and
|
||||
// automatically select the first certificate available. Return true (1) and
|
||||
// call cef_select_client_certificate_callback_t::Select either in this
|
||||
// function or at a later time to select a certificate. Do not call Select or
|
||||
// call it with NULL to continue without using any certificate. |isProxy|
|
||||
// indicates whether the host is an HTTPS proxy or the origin server. |host|
|
||||
// and |port| contains the hostname and port of the SSL server. |certificates|
|
||||
// is the list of certificates to choose from; this list has already been
|
||||
// pruned by Chromium so that it only contains certificates from issuers that
|
||||
// the server trusts.
|
||||
///
|
||||
int (CEF_CALLBACK *on_select_client_certificate)(
|
||||
struct _cef_request_handler_t* self, struct _cef_browser_t* browser,
|
||||
int isProxy, const cef_string_t* host, int port,
|
||||
size_t certificatesCount,
|
||||
struct _cef_x509certificate_t* const* certificates,
|
||||
struct _cef_select_client_certificate_callback_t* callback);
|
||||
|
||||
///
|
||||
// Called on the browser process UI thread when a plugin has crashed.
|
||||
// |plugin_path| is the path of the plugin that crashed.
|
||||
|
|
|
@ -47,7 +47,7 @@
|
|||
#include "include/cef_response_filter.h"
|
||||
#include "include/cef_request.h"
|
||||
#include "include/cef_ssl_info.h"
|
||||
|
||||
#include "include/cef_x509_certificate.h"
|
||||
|
||||
///
|
||||
// Callback interface used for asynchronous continuation of url requests.
|
||||
|
@ -70,6 +70,21 @@ class CefRequestCallback : public virtual CefBase {
|
|||
};
|
||||
|
||||
|
||||
///
|
||||
// Callback interface used to select a client certificate for authentication.
|
||||
///
|
||||
/*--cef(source=library)--*/
|
||||
class CefSelectClientCertificateCallback : public virtual CefBase {
|
||||
public:
|
||||
///
|
||||
// Chooses the specified certificate for client certificate authentication.
|
||||
// NULL value means that no client certificate should be used.
|
||||
///
|
||||
/*--cef(optional_param=cert)--*/
|
||||
virtual void Select(CefRefPtr<CefX509Certificate> cert) =0;
|
||||
};
|
||||
|
||||
|
||||
///
|
||||
// Implement this interface to handle events related to browser requests. The
|
||||
// methods of this class will be called on the thread indicated.
|
||||
|
@ -282,6 +297,29 @@ class CefRequestHandler : public virtual CefBase {
|
|||
return false;
|
||||
}
|
||||
|
||||
///
|
||||
// Called on the UI thread when a client certificate is being requested for
|
||||
// authentication. Return false to use the default behavior and automatically
|
||||
// select the first certificate available. Return true and call
|
||||
// CefSelectClientCertificateCallback::Select either in this method or at a
|
||||
// later time to select a certificate. Do not call Select or call it with NULL
|
||||
// to continue without using any certificate. |isProxy| indicates whether the
|
||||
// host is an HTTPS proxy or the origin server. |host| and |port| contains the
|
||||
// hostname and port of the SSL server. |certificates| is the list of
|
||||
// certificates to choose from; this list has already been pruned by Chromium
|
||||
// so that it only contains certificates from issuers that the server trusts.
|
||||
///
|
||||
/*--cef()--*/
|
||||
virtual bool OnSelectClientCertificate(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
bool isProxy,
|
||||
const CefString& host,
|
||||
int port,
|
||||
const CefX509CertificateList& certificates,
|
||||
CefRefPtr<CefSelectClientCertificateCallback> callback) {
|
||||
return false;
|
||||
}
|
||||
|
||||
///
|
||||
// Called on the browser process UI thread when a plugin has crashed.
|
||||
// |plugin_path| is the path of the plugin that crashed.
|
||||
|
|
|
@ -185,4 +185,6 @@ class CefX509Certificate : public virtual CefBase {
|
|||
virtual void GetPEMEncodedIssuerChain(IssuerChainBinaryList& chain) =0;
|
||||
};
|
||||
|
||||
typedef std::vector<CefRefPtr<CefX509Certificate> > CefX509CertificateList;
|
||||
|
||||
#endif // CEF_INCLUDE_CEF_X509_CERTIFICATE_H_
|
||||
|
|
|
@ -27,6 +27,7 @@
|
|||
#include "libcef/browser/speech_recognition_manager_delegate.h"
|
||||
#include "libcef/browser/ssl_info_impl.h"
|
||||
#include "libcef/browser/thread_util.h"
|
||||
#include "libcef/browser/x509_certificate_impl.h"
|
||||
#include "libcef/common/cef_messages.h"
|
||||
#include "libcef/common/cef_switches.h"
|
||||
#include "libcef/common/command_line_impl.h"
|
||||
|
@ -207,6 +208,58 @@ class CefAllowCertificateErrorCallbackImpl : public CefRequestCallback {
|
|||
DISALLOW_COPY_AND_ASSIGN(CefAllowCertificateErrorCallbackImpl);
|
||||
};
|
||||
|
||||
class CefSelectClientCertificateCallbackImpl :
|
||||
public CefSelectClientCertificateCallback {
|
||||
public:
|
||||
explicit CefSelectClientCertificateCallbackImpl(
|
||||
std::unique_ptr<content::ClientCertificateDelegate> delegate)
|
||||
: delegate_(std::move(delegate)) {
|
||||
}
|
||||
|
||||
~CefSelectClientCertificateCallbackImpl() {
|
||||
// If Select has not been called, call it with NULL to continue without any
|
||||
// client certificate.
|
||||
if (delegate_)
|
||||
DoSelect(NULL);
|
||||
}
|
||||
|
||||
void Select(CefRefPtr<CefX509Certificate> cert) override {
|
||||
if (delegate_)
|
||||
DoSelect(cert);
|
||||
}
|
||||
|
||||
private:
|
||||
void DoSelect(CefRefPtr<CefX509Certificate> cert) {
|
||||
if (CEF_CURRENTLY_ON_UIT()) {
|
||||
RunNow(std::move(delegate_), cert);
|
||||
} else {
|
||||
CEF_POST_TASK(CEF_UIT,
|
||||
base::Bind(&CefSelectClientCertificateCallbackImpl::RunNow,
|
||||
base::Passed(std::move(delegate_)), cert));
|
||||
}
|
||||
}
|
||||
|
||||
static void RunNow(
|
||||
std::unique_ptr<content::ClientCertificateDelegate> delegate,
|
||||
CefRefPtr<CefX509Certificate> cert) {
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
scoped_refptr<net::X509Certificate> x509cert = NULL;
|
||||
if (cert) {
|
||||
CefX509CertificateImpl* certImpl =
|
||||
static_cast<CefX509CertificateImpl*>(cert.get());
|
||||
x509cert = certImpl->GetInternalCertObject();
|
||||
}
|
||||
|
||||
delegate->ContinueWithCertificate(x509cert.get());
|
||||
}
|
||||
|
||||
std::unique_ptr<content::ClientCertificateDelegate> delegate_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(CefSelectClientCertificateCallbackImpl);
|
||||
DISALLOW_COPY_AND_ASSIGN(CefSelectClientCertificateCallbackImpl);
|
||||
};
|
||||
|
||||
class CefQuotaPermissionContext : public content::QuotaPermissionContext {
|
||||
public:
|
||||
CefQuotaPermissionContext() {
|
||||
|
@ -711,9 +764,40 @@ void CefContentBrowserClient::SelectClientCertificate(
|
|||
content::WebContents* web_contents,
|
||||
net::SSLCertRequestInfo* cert_request_info,
|
||||
std::unique_ptr<content::ClientCertificateDelegate> delegate) {
|
||||
if (!cert_request_info->client_certs.empty()) {
|
||||
// Use the first certificate.
|
||||
delegate->ContinueWithCertificate(cert_request_info->client_certs[0].get());
|
||||
CEF_REQUIRE_UIT();
|
||||
|
||||
CefRefPtr<CefRequestHandler> handler;
|
||||
CefRefPtr<CefBrowserHostImpl> browser =
|
||||
CefBrowserHostImpl::GetBrowserForContents(web_contents);
|
||||
if (browser.get()) {
|
||||
CefRefPtr<CefClient> client = browser->GetClient();
|
||||
if (client.get())
|
||||
handler = client->GetRequestHandler();
|
||||
}
|
||||
|
||||
if (!handler.get()) {
|
||||
delegate->ContinueWithCertificate(NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
CefX509CertificateList certs;
|
||||
for (std::vector<scoped_refptr<net::X509Certificate> >::iterator iter =
|
||||
cert_request_info->client_certs.begin();
|
||||
iter != cert_request_info->client_certs.end(); iter++) {
|
||||
certs.push_back(new CefX509CertificateImpl(*iter));
|
||||
}
|
||||
|
||||
CefRefPtr<CefSelectClientCertificateCallbackImpl> callbackImpl(
|
||||
new CefSelectClientCertificateCallbackImpl(std::move(delegate)));
|
||||
|
||||
bool proceed = handler->OnSelectClientCertificate(
|
||||
browser.get(), cert_request_info->is_proxy,
|
||||
cert_request_info->host_and_port.host(),
|
||||
cert_request_info->host_and_port.port(),
|
||||
certs, callbackImpl.get());
|
||||
|
||||
if (!proceed && !certs.empty()) {
|
||||
callbackImpl->Select(certs[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ CefSSLInfoImpl::CefSSLInfoImpl(const net::SSLInfo& value)
|
|||
: cert_status_(CERT_STATUS_NONE) {
|
||||
cert_status_ = static_cast<cef_cert_status_t>(value.cert_status);
|
||||
if (value.cert.get()) {
|
||||
cert_ = new CefX509CertificateImpl(*value.cert);
|
||||
cert_ = new CefX509CertificateImpl(value.cert);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -34,6 +34,6 @@ cef_ssl_content_status_t CefSSLStatusImpl::GetContentStatus() {
|
|||
|
||||
CefRefPtr<CefX509Certificate> CefSSLStatusImpl::GetX509Certificate() {
|
||||
if (certificate_ && !cef_certificate_)
|
||||
cef_certificate_ = new CefX509CertificateImpl(*certificate_);
|
||||
cef_certificate_ = new CefX509CertificateImpl(certificate_);
|
||||
return cef_certificate_;
|
||||
}
|
||||
|
|
|
@ -8,97 +8,115 @@
|
|||
|
||||
namespace {
|
||||
|
||||
void EncodeCertificate(
|
||||
const net::X509Certificate::OSCertHandle& os_handle,
|
||||
CefRefPtr<CefBinaryValue>& der_encoded,
|
||||
CefRefPtr<CefBinaryValue>& pem_encoded) {
|
||||
CefRefPtr<CefBinaryValue> EncodeCertificate(
|
||||
const net::X509Certificate::OSCertHandle& os_handle, bool der) {
|
||||
CefRefPtr<CefBinaryValue> bin_encoded;
|
||||
std::string encoded;
|
||||
if (net::X509Certificate::GetDEREncoded(os_handle, &encoded)) {
|
||||
der_encoded = CefBinaryValue::Create(encoded.c_str(),
|
||||
encoded.size());
|
||||
}
|
||||
encoded.clear();
|
||||
if (net::X509Certificate::GetPEMEncoded(os_handle, &encoded)) {
|
||||
pem_encoded = CefBinaryValue::Create(encoded.c_str(),
|
||||
encoded.size());
|
||||
|
||||
if (( der && net::X509Certificate::GetDEREncoded(os_handle, &encoded)) ||
|
||||
(!der && net::X509Certificate::GetPEMEncoded(os_handle, &encoded))) {
|
||||
bin_encoded = CefBinaryValue::Create(encoded.c_str(), encoded.size());
|
||||
}
|
||||
|
||||
return bin_encoded;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
CefX509CertificateImpl::CefX509CertificateImpl(
|
||||
const net::X509Certificate& value) {
|
||||
subject_ = new CefX509CertPrincipalImpl(value.subject());
|
||||
issuer_ = new CefX509CertPrincipalImpl(value.issuer());
|
||||
|
||||
const std::string& serial_number = value.serial_number();
|
||||
serial_number_ = CefBinaryValue::Create(serial_number.c_str(),
|
||||
serial_number.size());
|
||||
|
||||
const base::Time& valid_start = value.valid_start();
|
||||
if (!valid_start.is_null())
|
||||
cef_time_from_basetime(valid_start, valid_start_);
|
||||
|
||||
const base::Time& valid_expiry = value.valid_expiry();
|
||||
if (!valid_expiry.is_null())
|
||||
cef_time_from_basetime(valid_expiry, valid_expiry_);
|
||||
|
||||
net::X509Certificate::OSCertHandle os_handle = value.os_cert_handle();
|
||||
if (os_handle)
|
||||
EncodeCertificate(os_handle, der_encoded_, pem_encoded_);
|
||||
|
||||
const net::X509Certificate::OSCertHandles& issuer_chain =
|
||||
value.GetIntermediateCertificates();
|
||||
for (net::X509Certificate::OSCertHandles::const_iterator it =
|
||||
issuer_chain.begin(); it != issuer_chain.end(); it++) {
|
||||
CefRefPtr<CefBinaryValue> der_encoded, pem_encoded;
|
||||
EncodeCertificate(*it, der_encoded, pem_encoded);
|
||||
|
||||
// Add each to the chain, even if one conversion unexpectedly failed.
|
||||
// GetIssuerChainSize depends on these being the same length.
|
||||
der_encoded_issuer_chain_.push_back(der_encoded);
|
||||
pem_encoded_issuer_chain_.push_back(pem_encoded);
|
||||
}
|
||||
scoped_refptr<net::X509Certificate> cert)
|
||||
:cert_(cert) {
|
||||
}
|
||||
|
||||
CefRefPtr<CefX509CertPrincipal> CefX509CertificateImpl::GetSubject() {
|
||||
return subject_;
|
||||
if (cert_)
|
||||
return new CefX509CertPrincipalImpl(cert_->subject());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CefRefPtr<CefX509CertPrincipal> CefX509CertificateImpl::GetIssuer() {
|
||||
return issuer_;
|
||||
if (cert_)
|
||||
return new CefX509CertPrincipalImpl(cert_->issuer());
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CefRefPtr<CefBinaryValue> CefX509CertificateImpl::GetSerialNumber() {
|
||||
return serial_number_;
|
||||
if (cert_) {
|
||||
const std::string& serial = cert_->serial_number();
|
||||
return CefBinaryValue::Create(serial.c_str(), serial.size());
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CefTime CefX509CertificateImpl::GetValidStart() {
|
||||
return valid_start_;
|
||||
CefTime validity;
|
||||
if (cert_) {
|
||||
const base::Time& valid_time = cert_->valid_start();
|
||||
if (!valid_time.is_null())
|
||||
cef_time_from_basetime(valid_time, validity);
|
||||
}
|
||||
return validity;
|
||||
}
|
||||
|
||||
CefTime CefX509CertificateImpl::GetValidExpiry() {
|
||||
return valid_expiry_;
|
||||
CefTime validity;
|
||||
if (cert_) {
|
||||
const base::Time& valid_time = cert_->valid_expiry();
|
||||
if (!valid_time.is_null())
|
||||
cef_time_from_basetime(valid_time, validity);
|
||||
}
|
||||
return validity;
|
||||
}
|
||||
|
||||
CefRefPtr<CefBinaryValue> CefX509CertificateImpl::GetDEREncoded() {
|
||||
return der_encoded_;
|
||||
if (cert_) {
|
||||
net::X509Certificate::OSCertHandle os_handle = cert_->os_cert_handle();
|
||||
if (os_handle)
|
||||
return EncodeCertificate(os_handle, true);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
CefRefPtr<CefBinaryValue> CefX509CertificateImpl::GetPEMEncoded() {
|
||||
return pem_encoded_;
|
||||
if (cert_) {
|
||||
net::X509Certificate::OSCertHandle os_handle = cert_->os_cert_handle();
|
||||
if (os_handle)
|
||||
return EncodeCertificate(os_handle, false);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
size_t CefX509CertificateImpl::GetIssuerChainSize() {
|
||||
return der_encoded_issuer_chain_.size();
|
||||
if (cert_)
|
||||
return cert_->GetIntermediateCertificates().size();
|
||||
return 0;
|
||||
}
|
||||
|
||||
void CefX509CertificateImpl::GetEncodedIssuerChain(
|
||||
CefX509Certificate::IssuerChainBinaryList& chain, bool der) {
|
||||
chain.clear();
|
||||
if (cert_) {
|
||||
const net::X509Certificate::OSCertHandles& handles =
|
||||
cert_->GetIntermediateCertificates();
|
||||
for (net::X509Certificate::OSCertHandles::const_iterator it =
|
||||
handles.begin(); it != handles.end(); it++) {
|
||||
// Add each to the chain, even if one conversion unexpectedly failed.
|
||||
// GetIssuerChainSize depends on these being the same length.
|
||||
chain.push_back(EncodeCertificate(*it, der));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CefX509CertificateImpl::GetDEREncodedIssuerChain(
|
||||
CefX509Certificate::IssuerChainBinaryList& chain) {
|
||||
if (der_encoded_issuer_chain_.empty())
|
||||
GetEncodedIssuerChain(der_encoded_issuer_chain_, true);
|
||||
chain = der_encoded_issuer_chain_;
|
||||
}
|
||||
|
||||
void CefX509CertificateImpl::GetPEMEncodedIssuerChain(
|
||||
CefX509Certificate::IssuerChainBinaryList& chain) {
|
||||
if (pem_encoded_issuer_chain_.empty())
|
||||
GetEncodedIssuerChain(pem_encoded_issuer_chain_, false);
|
||||
chain = pem_encoded_issuer_chain_;
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
// CefX509Certificate implementation
|
||||
class CefX509CertificateImpl : public CefX509Certificate {
|
||||
public:
|
||||
explicit CefX509CertificateImpl(const net::X509Certificate& value);
|
||||
explicit CefX509CertificateImpl(scoped_refptr<net::X509Certificate> cert);
|
||||
|
||||
// CefX509Certificate methods.
|
||||
CefRefPtr<CefX509CertPrincipal> GetSubject() override;
|
||||
|
@ -27,16 +27,14 @@ class CefX509CertificateImpl : public CefX509Certificate {
|
|||
void GetDEREncodedIssuerChain(IssuerChainBinaryList& chain) override;
|
||||
void GetPEMEncodedIssuerChain(IssuerChainBinaryList& chain) override;
|
||||
|
||||
scoped_refptr<net::X509Certificate> GetInternalCertObject() { return cert_; }
|
||||
|
||||
private:
|
||||
CefRefPtr<CefX509CertPrincipal> subject_;
|
||||
CefRefPtr<CefX509CertPrincipal> issuer_;
|
||||
CefRefPtr<CefBinaryValue> serial_number_;
|
||||
CefTime valid_start_;
|
||||
CefTime valid_expiry_;
|
||||
CefRefPtr<CefBinaryValue> der_encoded_;
|
||||
CefRefPtr<CefBinaryValue> pem_encoded_;
|
||||
IssuerChainBinaryList der_encoded_issuer_chain_;
|
||||
void GetEncodedIssuerChain(IssuerChainBinaryList& chain, bool der);
|
||||
|
||||
scoped_refptr<net::X509Certificate> cert_;
|
||||
IssuerChainBinaryList pem_encoded_issuer_chain_;
|
||||
IssuerChainBinaryList der_encoded_issuer_chain_;
|
||||
|
||||
IMPLEMENT_REFCOUNTING(CefX509CertificateImpl);
|
||||
DISALLOW_COPY_AND_ASSIGN(CefX509CertificateImpl);
|
||||
|
|
|
@ -20,6 +20,8 @@
|
|||
#include "libcef_dll/ctocpp/request_callback_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/response_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/sslinfo_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/select_client_certificate_callback_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/x509certificate_ctocpp.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
@ -466,6 +468,56 @@ int CEF_CALLBACK request_handler_on_certificate_error(
|
|||
return _retval;
|
||||
}
|
||||
|
||||
int CEF_CALLBACK request_handler_on_select_client_certificate(
|
||||
struct _cef_request_handler_t* self, cef_browser_t* browser, int isProxy,
|
||||
const cef_string_t* host, int port, size_t certificatesCount,
|
||||
struct _cef_x509certificate_t* const* certificates,
|
||||
cef_select_client_certificate_callback_t* callback) {
|
||||
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
|
||||
|
||||
DCHECK(self);
|
||||
if (!self)
|
||||
return 0;
|
||||
// Verify param: browser; type: refptr_diff
|
||||
DCHECK(browser);
|
||||
if (!browser)
|
||||
return 0;
|
||||
// Verify param: host; type: string_byref_const
|
||||
DCHECK(host);
|
||||
if (!host)
|
||||
return 0;
|
||||
// Verify param: certificates; type: refptr_vec_diff_byref_const
|
||||
DCHECK(certificatesCount == 0 || certificates);
|
||||
if (certificatesCount > 0 && !certificates)
|
||||
return 0;
|
||||
// Verify param: callback; type: refptr_diff
|
||||
DCHECK(callback);
|
||||
if (!callback)
|
||||
return 0;
|
||||
|
||||
// Translate param: certificates; type: refptr_vec_diff_byref_const
|
||||
std::vector<CefRefPtr<CefX509Certificate> > certificatesList;
|
||||
if (certificatesCount > 0) {
|
||||
for (size_t i = 0; i < certificatesCount; ++i) {
|
||||
CefRefPtr<CefX509Certificate> certificatesVal =
|
||||
CefX509CertificateCToCpp::Wrap(certificates[i]);
|
||||
certificatesList.push_back(certificatesVal);
|
||||
}
|
||||
}
|
||||
|
||||
// Execute
|
||||
bool _retval = CefRequestHandlerCppToC::Get(self)->OnSelectClientCertificate(
|
||||
CefBrowserCToCpp::Wrap(browser),
|
||||
isProxy?true:false,
|
||||
CefString(host),
|
||||
port,
|
||||
certificatesList,
|
||||
CefSelectClientCertificateCallbackCToCpp::Wrap(callback));
|
||||
|
||||
// Return type: bool
|
||||
return _retval;
|
||||
}
|
||||
|
||||
void CEF_CALLBACK request_handler_on_plugin_crashed(
|
||||
struct _cef_request_handler_t* self, cef_browser_t* browser,
|
||||
const cef_string_t* plugin_path) {
|
||||
|
@ -546,6 +598,8 @@ CefRequestHandlerCppToC::CefRequestHandlerCppToC() {
|
|||
GetStruct()->on_quota_request = request_handler_on_quota_request;
|
||||
GetStruct()->on_protocol_execution = request_handler_on_protocol_execution;
|
||||
GetStruct()->on_certificate_error = request_handler_on_certificate_error;
|
||||
GetStruct()->on_select_client_certificate =
|
||||
request_handler_on_select_client_certificate;
|
||||
GetStruct()->on_plugin_crashed = request_handler_on_plugin_crashed;
|
||||
GetStruct()->on_render_view_ready = request_handler_on_render_view_ready;
|
||||
GetStruct()->on_render_process_terminated =
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
// Copyright (c) 2016 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// This file was generated by the CEF translator tool. If making changes by
|
||||
// hand only do so within the body of existing method and function
|
||||
// implementations. See the translator.README.txt file in the tools directory
|
||||
// for more information.
|
||||
//
|
||||
|
||||
#include "libcef_dll/cpptoc/select_client_certificate_callback_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/x509certificate_cpptoc.h"
|
||||
|
||||
|
||||
namespace {
|
||||
|
||||
// MEMBER FUNCTIONS - Body may be edited by hand.
|
||||
|
||||
void CEF_CALLBACK select_client_certificate_callback_select(
|
||||
struct _cef_select_client_certificate_callback_t* self,
|
||||
struct _cef_x509certificate_t* cert) {
|
||||
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
|
||||
|
||||
DCHECK(self);
|
||||
if (!self)
|
||||
return;
|
||||
// Unverified params: cert
|
||||
|
||||
// Execute
|
||||
CefSelectClientCertificateCallbackCppToC::Get(self)->Select(
|
||||
CefX509CertificateCppToC::Unwrap(cert));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
|
||||
// CONSTRUCTOR - Do not edit by hand.
|
||||
|
||||
CefSelectClientCertificateCallbackCppToC::CefSelectClientCertificateCallbackCppToC(
|
||||
) {
|
||||
GetStruct()->select = select_client_certificate_callback_select;
|
||||
}
|
||||
|
||||
template<> CefRefPtr<CefSelectClientCertificateCallback> CefCppToC<CefSelectClientCertificateCallbackCppToC,
|
||||
CefSelectClientCertificateCallback,
|
||||
cef_select_client_certificate_callback_t>::UnwrapDerived(
|
||||
CefWrapperType type, cef_select_client_certificate_callback_t* s) {
|
||||
NOTREACHED() << "Unexpected class type: " << type;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if DCHECK_IS_ON()
|
||||
template<> base::AtomicRefCount CefCppToC<CefSelectClientCertificateCallbackCppToC,
|
||||
CefSelectClientCertificateCallback,
|
||||
cef_select_client_certificate_callback_t>::DebugObjCt = 0;
|
||||
#endif
|
||||
|
||||
template<> CefWrapperType CefCppToC<CefSelectClientCertificateCallbackCppToC,
|
||||
CefSelectClientCertificateCallback,
|
||||
cef_select_client_certificate_callback_t>::kWrapperType =
|
||||
WT_SELECT_CLIENT_CERTIFICATE_CALLBACK;
|
|
@ -0,0 +1,36 @@
|
|||
// Copyright (c) 2016 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// This file was generated by the CEF translator tool. If making changes by
|
||||
// hand only do so within the body of existing method and function
|
||||
// implementations. See the translator.README.txt file in the tools directory
|
||||
// for more information.
|
||||
//
|
||||
|
||||
#ifndef CEF_LIBCEF_DLL_CPPTOC_SELECT_CLIENT_CERTIFICATE_CALLBACK_CPPTOC_H_
|
||||
#define CEF_LIBCEF_DLL_CPPTOC_SELECT_CLIENT_CERTIFICATE_CALLBACK_CPPTOC_H_
|
||||
#pragma once
|
||||
|
||||
#ifndef BUILDING_CEF_SHARED
|
||||
#pragma message("Warning: "__FILE__" may be accessed DLL-side only")
|
||||
#else // BUILDING_CEF_SHARED
|
||||
|
||||
#include "include/cef_request_handler.h"
|
||||
#include "include/capi/cef_request_handler_capi.h"
|
||||
#include "libcef_dll/cpptoc/cpptoc.h"
|
||||
|
||||
// Wrap a C++ class with a C structure.
|
||||
// This class may be instantiated and accessed DLL-side only.
|
||||
class CefSelectClientCertificateCallbackCppToC
|
||||
: public CefCppToC<CefSelectClientCertificateCallbackCppToC,
|
||||
CefSelectClientCertificateCallback,
|
||||
cef_select_client_certificate_callback_t> {
|
||||
public:
|
||||
CefSelectClientCertificateCallbackCppToC();
|
||||
};
|
||||
|
||||
#endif // BUILDING_CEF_SHARED
|
||||
#endif // CEF_LIBCEF_DLL_CPPTOC_SELECT_CLIENT_CERTIFICATE_CALLBACK_CPPTOC_H_
|
|
@ -17,6 +17,8 @@
|
|||
#include "libcef_dll/cpptoc/request_callback_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/response_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/sslinfo_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/select_client_certificate_callback_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/x509certificate_cpptoc.h"
|
||||
#include "libcef_dll/ctocpp/request_handler_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/resource_handler_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/response_filter_ctocpp.h"
|
||||
|
@ -451,6 +453,60 @@ bool CefRequestHandlerCToCpp::OnCertificateError(CefRefPtr<CefBrowser> browser,
|
|||
return _retval?true:false;
|
||||
}
|
||||
|
||||
bool CefRequestHandlerCToCpp::OnSelectClientCertificate(
|
||||
CefRefPtr<CefBrowser> browser, bool isProxy, const CefString& host,
|
||||
int port, const CefX509CertificateList& certificates,
|
||||
CefRefPtr<CefSelectClientCertificateCallback> callback) {
|
||||
cef_request_handler_t* _struct = GetStruct();
|
||||
if (CEF_MEMBER_MISSING(_struct, on_select_client_certificate))
|
||||
return false;
|
||||
|
||||
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
|
||||
|
||||
// Verify param: browser; type: refptr_diff
|
||||
DCHECK(browser.get());
|
||||
if (!browser.get())
|
||||
return false;
|
||||
// Verify param: host; type: string_byref_const
|
||||
DCHECK(!host.empty());
|
||||
if (host.empty())
|
||||
return false;
|
||||
// Verify param: callback; type: refptr_diff
|
||||
DCHECK(callback.get());
|
||||
if (!callback.get())
|
||||
return false;
|
||||
|
||||
// Translate param: certificates; type: refptr_vec_diff_byref_const
|
||||
const size_t certificatesCount = certificates.size();
|
||||
cef_x509certificate_t** certificatesList = NULL;
|
||||
if (certificatesCount > 0) {
|
||||
certificatesList = new cef_x509certificate_t*[certificatesCount];
|
||||
DCHECK(certificatesList);
|
||||
if (certificatesList) {
|
||||
for (size_t i = 0; i < certificatesCount; ++i) {
|
||||
certificatesList[i] = CefX509CertificateCppToC::Wrap(certificates[i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute
|
||||
int _retval = _struct->on_select_client_certificate(_struct,
|
||||
CefBrowserCppToC::Wrap(browser),
|
||||
isProxy,
|
||||
host.GetStruct(),
|
||||
port,
|
||||
certificatesCount,
|
||||
certificatesList,
|
||||
CefSelectClientCertificateCallbackCppToC::Wrap(callback));
|
||||
|
||||
// Restore param:certificates; type: refptr_vec_diff_byref_const
|
||||
if (certificatesList)
|
||||
delete [] certificatesList;
|
||||
|
||||
// Return type: bool
|
||||
return _retval?true:false;
|
||||
}
|
||||
|
||||
void CefRequestHandlerCToCpp::OnPluginCrashed(CefRefPtr<CefBrowser> browser,
|
||||
const CefString& plugin_path) {
|
||||
cef_request_handler_t* _struct = GetStruct();
|
||||
|
|
|
@ -69,6 +69,10 @@ class CefRequestHandlerCToCpp
|
|||
cef_errorcode_t cert_error, const CefString& request_url,
|
||||
CefRefPtr<CefSSLInfo> ssl_info,
|
||||
CefRefPtr<CefRequestCallback> callback) override;
|
||||
bool OnSelectClientCertificate(CefRefPtr<CefBrowser> browser, bool isProxy,
|
||||
const CefString& host, int port,
|
||||
const CefX509CertificateList& certificates,
|
||||
CefRefPtr<CefSelectClientCertificateCallback> callback) override;
|
||||
void OnPluginCrashed(CefRefPtr<CefBrowser> browser,
|
||||
const CefString& plugin_path) override;
|
||||
void OnRenderViewReady(CefRefPtr<CefBrowser> browser) override;
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
// Copyright (c) 2016 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// This file was generated by the CEF translator tool. If making changes by
|
||||
// hand only do so within the body of existing method and function
|
||||
// implementations. See the translator.README.txt file in the tools directory
|
||||
// for more information.
|
||||
//
|
||||
|
||||
#include "libcef_dll/ctocpp/select_client_certificate_callback_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/x509certificate_ctocpp.h"
|
||||
|
||||
|
||||
// VIRTUAL METHODS - Body may be edited by hand.
|
||||
|
||||
void CefSelectClientCertificateCallbackCToCpp::Select(
|
||||
CefRefPtr<CefX509Certificate> cert) {
|
||||
cef_select_client_certificate_callback_t* _struct = GetStruct();
|
||||
if (CEF_MEMBER_MISSING(_struct, select))
|
||||
return;
|
||||
|
||||
// AUTO-GENERATED CONTENT - DELETE THIS COMMENT BEFORE MODIFYING
|
||||
|
||||
// Unverified params: cert
|
||||
|
||||
// Execute
|
||||
_struct->select(_struct,
|
||||
CefX509CertificateCToCpp::Unwrap(cert));
|
||||
}
|
||||
|
||||
|
||||
// CONSTRUCTOR - Do not edit by hand.
|
||||
|
||||
CefSelectClientCertificateCallbackCToCpp::CefSelectClientCertificateCallbackCToCpp(
|
||||
) {
|
||||
}
|
||||
|
||||
template<> cef_select_client_certificate_callback_t* CefCToCpp<CefSelectClientCertificateCallbackCToCpp,
|
||||
CefSelectClientCertificateCallback,
|
||||
cef_select_client_certificate_callback_t>::UnwrapDerived(
|
||||
CefWrapperType type, CefSelectClientCertificateCallback* c) {
|
||||
NOTREACHED() << "Unexpected class type: " << type;
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if DCHECK_IS_ON()
|
||||
template<> base::AtomicRefCount CefCToCpp<CefSelectClientCertificateCallbackCToCpp,
|
||||
CefSelectClientCertificateCallback,
|
||||
cef_select_client_certificate_callback_t>::DebugObjCt = 0;
|
||||
#endif
|
||||
|
||||
template<> CefWrapperType CefCToCpp<CefSelectClientCertificateCallbackCToCpp,
|
||||
CefSelectClientCertificateCallback,
|
||||
cef_select_client_certificate_callback_t>::kWrapperType =
|
||||
WT_SELECT_CLIENT_CERTIFICATE_CALLBACK;
|
|
@ -0,0 +1,39 @@
|
|||
// Copyright (c) 2016 The Chromium Embedded Framework Authors. All rights
|
||||
// reserved. Use of this source code is governed by a BSD-style license that
|
||||
// can be found in the LICENSE file.
|
||||
//
|
||||
// ---------------------------------------------------------------------------
|
||||
//
|
||||
// This file was generated by the CEF translator tool. If making changes by
|
||||
// hand only do so within the body of existing method and function
|
||||
// implementations. See the translator.README.txt file in the tools directory
|
||||
// for more information.
|
||||
//
|
||||
|
||||
#ifndef CEF_LIBCEF_DLL_CTOCPP_SELECT_CLIENT_CERTIFICATE_CALLBACK_CTOCPP_H_
|
||||
#define CEF_LIBCEF_DLL_CTOCPP_SELECT_CLIENT_CERTIFICATE_CALLBACK_CTOCPP_H_
|
||||
#pragma once
|
||||
|
||||
#ifndef USING_CEF_SHARED
|
||||
#pragma message("Warning: "__FILE__" may be accessed wrapper-side only")
|
||||
#else // USING_CEF_SHARED
|
||||
|
||||
#include "include/cef_request_handler.h"
|
||||
#include "include/capi/cef_request_handler_capi.h"
|
||||
#include "libcef_dll/ctocpp/ctocpp.h"
|
||||
|
||||
// Wrap a C structure with a C++ class.
|
||||
// This class may be instantiated and accessed wrapper-side only.
|
||||
class CefSelectClientCertificateCallbackCToCpp
|
||||
: public CefCToCpp<CefSelectClientCertificateCallbackCToCpp,
|
||||
CefSelectClientCertificateCallback,
|
||||
cef_select_client_certificate_callback_t> {
|
||||
public:
|
||||
CefSelectClientCertificateCallbackCToCpp();
|
||||
|
||||
// CefSelectClientCertificateCallback methods.
|
||||
void Select(CefRefPtr<CefX509Certificate> cert) OVERRIDE;
|
||||
};
|
||||
|
||||
#endif // USING_CEF_SHARED
|
||||
#endif // CEF_LIBCEF_DLL_CTOCPP_SELECT_CLIENT_CERTIFICATE_CALLBACK_CTOCPP_H_
|
|
@ -75,6 +75,7 @@
|
|||
#include "libcef_dll/cpptoc/sslstatus_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/scheme_registrar_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/views/scroll_view_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/select_client_certificate_callback_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/stream_reader_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/stream_writer_cpptoc.h"
|
||||
#include "libcef_dll/cpptoc/task_runner_cpptoc.h"
|
||||
|
@ -310,6 +311,8 @@ CEF_EXPORT void cef_shutdown() {
|
|||
&CefSchemeHandlerFactoryCToCpp::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(&CefSchemeRegistrarCppToC::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(&CefScrollViewCppToC::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(
|
||||
&CefSelectClientCertificateCallbackCppToC::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(&CefSetCookieCallbackCToCpp::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(&CefStreamReaderCppToC::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(&CefStreamWriterCppToC::DebugObjCt));
|
||||
|
|
|
@ -129,6 +129,7 @@
|
|||
#include "libcef_dll/ctocpp/sslstatus_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/scheme_registrar_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/views/scroll_view_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/select_client_certificate_callback_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/stream_reader_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/stream_writer_ctocpp.h"
|
||||
#include "libcef_dll/ctocpp/task_runner_ctocpp.h"
|
||||
|
@ -302,6 +303,8 @@ CEF_GLOBAL void CefShutdown() {
|
|||
&CefSchemeHandlerFactoryCppToC::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(&CefSchemeRegistrarCToCpp::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(&CefScrollViewCToCpp::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(
|
||||
&CefSelectClientCertificateCallbackCToCpp::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(&CefSetCookieCallbackCppToC::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(&CefStreamReaderCToCpp::DebugObjCt));
|
||||
DCHECK(base::AtomicRefCountIsZero(&CefStreamWriterCToCpp::DebugObjCt));
|
||||
|
|
|
@ -106,6 +106,7 @@ enum CefWrapperType {
|
|||
WT_SCHEME_HANDLER_FACTORY,
|
||||
WT_SCHEME_REGISTRAR,
|
||||
WT_SCROLL_VIEW,
|
||||
WT_SELECT_CLIENT_CERTIFICATE_CALLBACK,
|
||||
WT_SET_COOKIE_CALLBACK,
|
||||
WT_STREAM_READER,
|
||||
WT_STREAM_WRITER,
|
||||
|
|
|
@ -700,6 +700,42 @@ bool ClientHandler::OnCertificateError(
|
|||
return false; // Cancel the request.
|
||||
}
|
||||
|
||||
bool ClientHandler::OnSelectClientCertificate(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
bool isProxy,
|
||||
const CefString& host,
|
||||
int port,
|
||||
const CefX509CertificateList& certificates,
|
||||
CefRefPtr<CefSelectClientCertificateCallback> callback) {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
|
||||
CefRefPtr<CefCommandLine> command_line =
|
||||
CefCommandLine::GetGlobalCommandLine();
|
||||
if (!command_line->HasSwitch(switches::kSslClientCertificate)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string& cert_name =
|
||||
command_line->GetSwitchValue(switches::kSslClientCertificate);
|
||||
|
||||
if (cert_name.empty()) {
|
||||
callback->Select(NULL);
|
||||
return true;
|
||||
}
|
||||
|
||||
std::vector<CefRefPtr<CefX509Certificate> >::const_iterator it =
|
||||
certificates.begin();
|
||||
for (; it != certificates.end(); ++it) {
|
||||
CefString subject((*it)->GetSubject()->GetDisplayName());
|
||||
if (subject == cert_name) {
|
||||
callback->Select(*it);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void ClientHandler::OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser,
|
||||
TerminationStatus status) {
|
||||
CEF_REQUIRE_UI_THREAD();
|
||||
|
|
|
@ -252,6 +252,13 @@ class ClientHandler : public CefClient,
|
|||
const CefString& request_url,
|
||||
CefRefPtr<CefSSLInfo> ssl_info,
|
||||
CefRefPtr<CefRequestCallback> callback) OVERRIDE;
|
||||
bool OnSelectClientCertificate(
|
||||
CefRefPtr<CefBrowser> browser,
|
||||
bool isProxy,
|
||||
const CefString& host,
|
||||
int port,
|
||||
const CefX509CertificateList& certificates,
|
||||
CefRefPtr<CefSelectClientCertificateCallback> callback) OVERRIDE;
|
||||
void OnRenderProcessTerminated(CefRefPtr<CefBrowser> browser,
|
||||
TerminationStatus status) OVERRIDE;
|
||||
|
||||
|
|
|
@ -35,6 +35,7 @@ const char kUseViews[] = "use-views";
|
|||
const char kHideFrame[] = "hide-frame";
|
||||
const char kHideControls[] = "hide-controls";
|
||||
const char kWidevineCdmPath[] = "widevine-cdm-path";
|
||||
const char kSslClientCertificate[] = "ssl-client-certificate";
|
||||
|
||||
} // namespace switches
|
||||
} // namespace client
|
||||
|
|
|
@ -29,6 +29,7 @@ extern const char kUseViews[];
|
|||
extern const char kHideFrame[];
|
||||
extern const char kHideControls[];
|
||||
extern const char kWidevineCdmPath[];
|
||||
extern const char kSslClientCertificate[];
|
||||
|
||||
} // namespace switches
|
||||
} // namespace client
|
||||
|
|
Loading…
Reference in New Issue