From ae9962e7069c9293cd93d55f08b825d5e3717318 Mon Sep 17 00:00:00 2001
From: Sebastian Hahta <joseha@utu.fi>
Date: Mon, 1 Jun 2020 13:51:25 +0300
Subject: [PATCH] HTTP Basic Auth for websocket

---
 components/common/cpp/CMakeLists.txt          |   1 +
 components/common/cpp/include/ftl/uri.hpp     |   3 +
 .../common/cpp/include/ftl/utility/base64.hpp |  35 +++
 components/common/cpp/src/uri.cpp             | 220 ++++++++-------
 components/common/cpp/src/utility/base64.cpp  | 256 ++++++++++++++++++
 .../net/cpp/include/ftl/net/ws_internal.hpp   |   6 +-
 components/net/cpp/src/ws_internal.cpp        |  13 +-
 7 files changed, 434 insertions(+), 100 deletions(-)
 create mode 100644 components/common/cpp/include/ftl/utility/base64.hpp
 create mode 100644 components/common/cpp/src/utility/base64.cpp

diff --git a/components/common/cpp/CMakeLists.txt b/components/common/cpp/CMakeLists.txt
index 7c5794d1b..fe1d7671b 100644
--- a/components/common/cpp/CMakeLists.txt
+++ b/components/common/cpp/CMakeLists.txt
@@ -12,6 +12,7 @@ set(COMMONSRC
 	src/timer.cpp
 	src/profiler.cpp
 	src/exception.cpp
+	src/utility/base64.cpp
 )
 
 check_function_exists(uriParseSingleUriA HAVE_URIPARSESINGLE)
diff --git a/components/common/cpp/include/ftl/uri.hpp b/components/common/cpp/include/ftl/uri.hpp
index 24123f168..71e3bf456 100644
--- a/components/common/cpp/include/ftl/uri.hpp
+++ b/components/common/cpp/include/ftl/uri.hpp
@@ -44,6 +44,8 @@ namespace ftl {
 		const std::string &getFragment() const { return m_frag; }
 		std::string getQuery() const;
 		const std::string &getBaseURI() const { return m_base; };
+		bool hasUserInfo() const;
+		const std::string &getUserInfo() const; 
 
 		/**
 		 * Get the URI without query parameters, and limit path to length N.
@@ -74,6 +76,7 @@ namespace ftl {
 		std::string m_path;
 		std::string m_frag;
 		std::string m_base;
+		std::string m_userinfo;
 		std::vector<std::string> m_pathseg;
 		int m_port;
 		scheme_t m_proto;
diff --git a/components/common/cpp/include/ftl/utility/base64.hpp b/components/common/cpp/include/ftl/utility/base64.hpp
new file mode 100644
index 000000000..197bd7df3
--- /dev/null
+++ b/components/common/cpp/include/ftl/utility/base64.hpp
@@ -0,0 +1,35 @@
+//
+//  base64 encoding and decoding with C++.
+//  Version: 2.rc.04 (release candidate)
+//
+
+#ifndef BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A
+#define BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A
+
+#include <string>
+
+#if __cplusplus >= 201703L
+#include <string_view>
+#endif  // __cplusplus >= 201703L
+
+std::string base64_encode     (std::string const& s, bool url = false);
+std::string base64_encode_pem (std::string const& s);
+std::string base64_encode_mime(std::string const& s);
+
+std::string base64_decode(std::string const& s, bool remove_linebreaks = false);
+std::string base64_encode(unsigned char const*, size_t len, bool url = false);
+
+#if __cplusplus >= 201703L
+//
+// Interface with std::string_view rather than const std::string&
+// Requires C++17
+// Provided by Yannic Bonenberger (https://github.com/Yannic)
+//
+std::string base64_encode     (std::string_view s, bool url = false);
+std::string base64_encode_pem (std::string_view s);
+std::string base64_encode_mime(std::string_view s);
+
+std::string base64_decode(std::string_view s, bool remove_linebreaks = false);
+#endif  // __cplusplus >= 201703L
+
+#endif /* BASE64_H_C0CE2A47_D10E_42C9_A27C_C883944E704A */
diff --git a/components/common/cpp/src/uri.cpp b/components/common/cpp/src/uri.cpp
index 6884720d1..fa059a0a8 100644
--- a/components/common/cpp/src/uri.cpp
+++ b/components/common/cpp/src/uri.cpp
@@ -15,27 +15,28 @@ using ftl::uri_t;
 using std::string;
 
 URI::URI(uri_t puri) {
-    _parse(puri);
+	_parse(puri);
 }
 
 URI::URI(const std::string &puri) {
-    _parse(puri.c_str());
+	_parse(puri.c_str());
 }
 
 URI::URI(const URI &c) {
-    m_valid = c.m_valid;
-    m_host = c.m_host;
-    m_port = c.m_port;
-    m_proto = c.m_proto;
-    m_path = c.m_path;
-    m_pathseg = c.m_pathseg;
-    m_qmap = c.m_qmap;
-    m_base = c.m_base;
+	m_valid = c.m_valid;
+	m_host = c.m_host;
+	m_port = c.m_port;
+	m_proto = c.m_proto;
+	m_path = c.m_path;
+	m_pathseg = c.m_pathseg;
+	m_qmap = c.m_qmap;
+	m_base = c.m_base;
+	m_userinfo = c.m_userinfo;
 	m_frag = c.m_frag;
 }
 
 void URI::_parse(uri_t puri) {
-    UriUriA uri;
+	UriUriA uri;
 
 	std::string suri = puri;
 
@@ -56,83 +57,98 @@ void URI::_parse(uri_t puri) {
 	}
 
 #ifdef HAVE_URIPARSESINGLE
-    const char *errpos;
-    if (uriParseSingleUriA(&uri, puri, &errpos) != URI_SUCCESS) {
+	const char *errpos;
+	if (uriParseSingleUriA(&uri, puri, &errpos) != URI_SUCCESS) {
 #else
-    UriParserStateA uris;
-    uris.uri = &uri;
-    if (uriParseUriA(&uris, suri.c_str()) != URI_SUCCESS) {
+	UriParserStateA uris;
+	uris.uri = &uri;
+	if (uriParseUriA(&uris, suri.c_str()) != URI_SUCCESS) {
 #endif
-        m_valid = false;
-        m_host = "none";
-        m_port = -1;
-        m_proto = SCHEME_NONE;
-        m_path = "";
+		m_valid = false;
+		m_host = "none";
+		m_port = -1;
+		m_proto = SCHEME_NONE;
+		m_path = "";
 		m_frag = "";
-    } else {
-        m_host = std::string(uri.hostText.first, uri.hostText.afterLast - uri.hostText.first);
-        
-        std::string prototext = std::string(uri.scheme.first, uri.scheme.afterLast - uri.scheme.first);
-        if (prototext == "tcp") m_proto = SCHEME_TCP;
-        else if (prototext == "udp") m_proto = SCHEME_UDP;
-        else if (prototext == "ftl") m_proto = SCHEME_FTL;
-        else if (prototext == "http") m_proto = SCHEME_HTTP;
-        else if (prototext == "ws") m_proto = SCHEME_WS;
-        else if (prototext == "ipc") m_proto = SCHEME_IPC;
+	} else {
+		m_host = std::string(uri.hostText.first, uri.hostText.afterLast - uri.hostText.first);
+		
+		std::string prototext = std::string(uri.scheme.first, uri.scheme.afterLast - uri.scheme.first);
+		if (prototext == "tcp") m_proto = SCHEME_TCP;
+		else if (prototext == "udp") m_proto = SCHEME_UDP;
+		else if (prototext == "ftl") m_proto = SCHEME_FTL;
+		else if (prototext == "http") m_proto = SCHEME_HTTP;
+		else if (prototext == "ws") m_proto = SCHEME_WS;
+		else if (prototext == "ipc") m_proto = SCHEME_IPC;
 		else if (prototext == "device") m_proto = SCHEME_DEVICE;
 		else if (prototext == "file") m_proto = SCHEME_FILE;
-        else m_proto = SCHEME_OTHER;
-        m_protostr = prototext;
-
-        std::string porttext = std::string(uri.portText.first, uri.portText.afterLast - uri.portText.first);
-        m_port = atoi(porttext.c_str());
-
-        for (auto h=uri.pathHead; h!=NULL; h=h->next) {
-            auto pstr = std::string(
-                    h->text.first, h->text.afterLast - h->text.first);
-
-            m_path += "/";
-            m_path += pstr;
-            m_pathseg.push_back(pstr);
-        }
-
-        //string query = std::string(uri.query.first, uri.query.afterLast - uri.query.first);
-        if (uri.query.afterLast - uri.query.first > 0) {
-            UriQueryListA *queryList;
-            int itemCount;
-            if (uriDissectQueryMallocA(&queryList, &itemCount, uri.query.first,
-                    uri.query.afterLast) != URI_SUCCESS) {
-                // Failure
-            }
-            
-            UriQueryListA *item = queryList;
-            while (item) {
-                m_qmap[item->key] = item->value;
-                item = item->next;
-            }
-
-            uriFreeQueryListA(queryList);
-        }
-
-        uriFreeUriMembersA(&uri);
+		else m_proto = SCHEME_OTHER;
+		m_protostr = prototext;
+
+		std::string porttext = std::string(uri.portText.first, uri.portText.afterLast - uri.portText.first);
+		m_port = atoi(porttext.c_str());
+		m_userinfo = std::string(uri.userInfo.first, uri.userInfo.afterLast - uri.userInfo.first);
+
+		for (auto h=uri.pathHead; h!=NULL; h=h->next) {
+			auto pstr = std::string(
+					h->text.first, h->text.afterLast - h->text.first);
+
+			m_path += "/";
+			m_path += pstr;
+			m_pathseg.push_back(pstr);
+		}
+
+		//string query = std::string(uri.query.first, uri.query.afterLast - uri.query.first);
+		if (uri.query.afterLast - uri.query.first > 0) {
+			UriQueryListA *queryList;
+			int itemCount;
+			if (uriDissectQueryMallocA(&queryList, &itemCount, uri.query.first,
+					uri.query.afterLast) != URI_SUCCESS) {
+				// Failure
+			}
+			
+			UriQueryListA *item = queryList;
+			while (item) {
+				m_qmap[item->key] = item->value;
+				item = item->next;
+			}
+			uriFreeQueryListA(queryList);
+		}
+
+		uriFreeUriMembersA(&uri);
 
 		auto fraglast = (uri.query.first != NULL) ? uri.query.first : uri.fragment.afterLast;
 		if (uri.fragment.first != NULL && fraglast - uri.fragment.first > 0) {
 			m_frag = std::string(uri.fragment.first, fraglast - uri.fragment.first);
 		}
 
-        m_valid = m_proto != SCHEME_NONE && (m_host.size() > 0 || m_path.size() > 0);
-
-        if (m_valid) {
-            if (m_qmap.size() > 0) m_base = std::string(uri.scheme.first, uri.query.first - uri.scheme.first - 1);
-			else if (uri.fragment.first != NULL) m_base = std::string(uri.scheme.first, uri.fragment.first - uri.scheme.first - 1);
-            else m_base = std::string(uri.scheme.first);
-        }
-    }
+		m_valid = m_proto != SCHEME_NONE && (m_host.size() > 0 || m_path.size() > 0);
+
+		if (m_valid) {
+			// remove userinfo from base uri
+			const char *start = uri.scheme.first;
+			if (m_userinfo != "") {
+				m_base = std::string(start, uri.userInfo.first - start);
+				start = uri.userInfo.afterLast + 1;
+			}
+			else {
+				m_base = std::string("");
+			}
+			if (m_qmap.size() > 0) {
+				m_base += std::string(start, uri.query.first - start - 1);
+			}
+			else if (uri.fragment.first != NULL) {
+				m_base += std::string(start, uri.fragment.first - start - 1);
+			}
+			else {
+				m_base += std::string(start);
+			}
+		}
+	}
 }
 
 string URI::to_string() const {
-    return (m_qmap.size() > 0) ? m_base + "?" + getQuery() : m_base;
+	return (m_qmap.size() > 0) ? m_base + "?" + getQuery() : m_base;
 }
 
 string URI::getPathSegment(int n) const {
@@ -142,42 +158,42 @@ string URI::getPathSegment(int n) const {
 }
 
 string URI::getBaseURI(int n) {
-    if (n >= (int)m_pathseg.size()) return m_base;
-    if (n >= 0) {
-        string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : "");
-        for (int i=0; i<n; i++) {
+	if (n >= (int)m_pathseg.size()) return m_base;
+	if (n >= 0) {
+		string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : "");
+		for (int i=0; i<n; i++) {
 			r += "/";
-            r += getPathSegment(i);
-        }
-
-        return r;
-    } else if (m_pathseg.size()+n >= 0) {
-        string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : "");
-        size_t N = m_pathseg.size()+n;
-        for (size_t i=0; i<N; i++) {
+			r += getPathSegment(i);
+		}
+
+		return r;
+	} else if (m_pathseg.size()+n >= 0) {
+		string r = m_protostr + string("://") + m_host + ((m_port != 0) ? string(":") + std::to_string(m_port) : "");
+		size_t N = m_pathseg.size()+n;
+		for (size_t i=0; i<N; i++) {
 			r += "/";
-            r += getPathSegment(i);
-        }
+			r += getPathSegment(i);
+		}
 
-        return r;
-    } else return "";
+		return r;
+	} else return "";
 }
 
 string URI::getQuery() const {
-    string q;
-    for (auto x : m_qmap) {
-        if (q.length() > 0) q += "&";
-        q += x.first + "=" + x.second;
-    }
-    return q;
+	string q;
+	for (auto x : m_qmap) {
+		if (q.length() > 0) q += "&";
+		q += x.first + "=" + x.second;
+	}
+	return q;
 };
 
 void URI::setAttribute(const string &key, const string &value) {
-    m_qmap[key] = value;
+	m_qmap[key] = value;
 }
 
 void URI::setAttribute(const string &key, int value) {
-    m_qmap[key] = std::to_string(value);
+	m_qmap[key] = std::to_string(value);
 }
 
 void URI::to_json(nlohmann::json &json) {
@@ -202,3 +218,11 @@ void URI::to_json(nlohmann::json &json) {
 		}
 	}
 }
+
+bool URI::hasUserInfo() const {
+	return m_userinfo != "";
+}
+
+const std::string &URI::getUserInfo() const {
+	return m_userinfo;
+}
diff --git a/components/common/cpp/src/utility/base64.cpp b/components/common/cpp/src/utility/base64.cpp
new file mode 100644
index 000000000..1dc38826a
--- /dev/null
+++ b/components/common/cpp/src/utility/base64.cpp
@@ -0,0 +1,256 @@
+/*
+	base64.cpp and base64.h
+
+	base64 encoding and decoding with C++.
+	More information at
+		https://renenyffenegger.ch/notes/development/Base64/Encoding-and-decoding-base-64-with-cpp
+
+	Version: 2.rc.04 (release candidate)
+
+	Copyright (C) 2004-2017, 2020 René Nyffenegger
+
+	This source code is provided 'as-is', without any express or implied
+	warranty. In no event will the author be held liable for any damages
+	arising from the use of this software.
+
+	Permission is granted to anyone to use this software for any purpose,
+	including commercial applications, and to alter it and redistribute it
+	freely, subject to the following restrictions:
+
+	1. The origin of this source code must not be misrepresented; you must not
+		claim that you wrote the original source code. If you use this source code
+		in a product, an acknowledgment in the product documentation would be
+		appreciated but is not required.
+
+	2. Altered source versions must be plainly marked as such, and must not be
+		misrepresented as being the original source code.
+
+	3. This notice may not be removed or altered from any source distribution.
+
+	René Nyffenegger rene.nyffenegger@adp-gmbh.ch
+
+*/
+
+#include "ftl/utility/base64.hpp"
+
+ //
+ // Depending on the url parameter in base64_chars, one of
+ // two sets of base64 characters needs to be chosen.
+ // They differ in their last two characters.
+ //
+const char* base64_chars[2] = {
+			 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+			 "abcdefghijklmnopqrstuvwxyz"
+			 "0123456789"
+			 "+/",
+
+			 "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
+			 "abcdefghijklmnopqrstuvwxyz"
+			 "0123456789"
+			 "-_"};
+
+static unsigned int pos_of_char(const unsigned char chr) {
+ //
+ // Return the position of chr within base64_encode()
+ //
+
+	if      (chr >= 'A' && chr <= 'Z') return chr - 'A';
+	else if (chr >= 'a' && chr <= 'z') return chr - 'a' + ('Z' - 'A')               + 1;
+	else if (chr >= '0' && chr <= '9') return chr - '0' + ('Z' - 'A') + ('z' - 'a') + 2;
+	else if (chr == '+' || chr == '-') return 62; // Be liberal with input and accept both url ('-') and non-url ('+') base 64 characters (
+	else if (chr == '/' || chr == '_') return 63; // Ditto for '/' and '_'
+
+	throw "If input is correct, this line should never be reached.";
+}
+
+static std::string insert_linebreaks(std::string str, size_t distance) {
+ //
+ // Provided by https://github.com/JomaCorpFX, adapted by me.
+ //
+	if (!str.length()) {
+		return "";
+	}
+
+	size_t pos = distance;
+
+	while (pos < str.size()) {
+		str.insert(pos, "\n");
+		pos += distance + 1;
+	}
+
+	return str;
+}
+
+template <typename String, unsigned int line_length>
+static std::string encode_with_line_breaks(String s) {
+  return insert_linebreaks(base64_encode(s, false), line_length);
+}
+
+template <typename String>
+static std::string encode_pem(String s) {
+  return encode_with_line_breaks<String, 64>(s);
+}
+
+template <typename String>
+static std::string encode_mime(String s) {
+  return encode_with_line_breaks<String, 76>(s);
+}
+
+template <typename String>
+static std::string encode(String s, bool url) {
+  return base64_encode(reinterpret_cast<const unsigned char*>(s.data()), s.length(), url);
+}
+
+std::string base64_encode(unsigned char const* bytes_to_encode, size_t in_len, bool url) {
+
+	size_t len_encoded = (in_len +2) / 3 * 4;
+
+	unsigned char trailing_char = url ? '.' : '=';
+
+ //
+ // Choose set of base64 characters. They differ
+ // for the last two positions, depending on the url
+ // parameter.
+ // A bool (as is the parameter url) is guaranteed
+ // to evaluate to either 0 or 1 in C++ therfore,
+ // the correct character set is chosen by subscripting
+ // base64_chars with url.
+ //
+	const char* base64_chars_ = base64_chars[url];
+
+	std::string ret;
+	ret.reserve(len_encoded);
+
+	unsigned int pos = 0;
+
+	while (pos < in_len) {
+		ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0xfc) >> 2]);
+
+		if (pos+1 < in_len) {
+			ret.push_back(base64_chars_[((bytes_to_encode[pos + 0] & 0x03) << 4) + ((bytes_to_encode[pos + 1] & 0xf0) >> 4)]);
+
+			if (pos+2 < in_len) {
+				ret.push_back(base64_chars_[((bytes_to_encode[pos + 1] & 0x0f) << 2) + ((bytes_to_encode[pos + 2] & 0xc0) >> 6)]);
+				ret.push_back(base64_chars_[  bytes_to_encode[pos + 2] & 0x3f]);
+			}
+			else {
+				ret.push_back(base64_chars_[(bytes_to_encode[pos + 1] & 0x0f) << 2]);
+				ret.push_back(trailing_char);
+			}
+		}
+		else {
+
+			ret.push_back(base64_chars_[(bytes_to_encode[pos + 0] & 0x03) << 4]);
+			ret.push_back(trailing_char);
+			ret.push_back(trailing_char);
+		}
+
+		pos += 3;
+	}
+
+
+	return ret;
+}
+
+template <typename String>
+static std::string decode(String encoded_string, bool remove_linebreaks) {
+ //
+ // decode(…) is templated so that it can be used with String = const std::string&
+ // or std::string_view (requires at least C++17)
+ //
+
+	if (remove_linebreaks) {
+
+		if (! encoded_string.length() ) {
+			return "";
+		}
+
+		std::string copy(encoded_string);
+
+		size_t pos=0;
+		while ((pos = copy.find("\n", pos)) != std::string::npos) {
+			copy.erase(pos, 1);
+		}
+
+		return base64_decode(copy, false);
+
+	}
+
+	size_t length_of_string = encoded_string.length();
+	if (!length_of_string) return std::string("");
+
+	size_t in_len = length_of_string;
+	size_t pos = 0;
+
+ //
+ // The approximate length (bytes) of the decoded string might be one ore
+ // two bytes smaller, depending on the amount of trailing equal signs
+ // in the encoded string. This approximation is needed to reserve
+ // enough space in the string to be returned.
+ //
+	size_t approx_length_of_decoded_string = length_of_string / 4 * 3;
+	std::string ret;
+	ret.reserve(approx_length_of_decoded_string);
+
+	while (pos < in_len) {
+
+		unsigned int pos_of_char_1 = pos_of_char(encoded_string[pos+1] );
+
+		ret.push_back(static_cast<std::string::value_type>( ( (pos_of_char(encoded_string[pos+0]) ) << 2 ) + ( (pos_of_char_1 & 0x30 ) >> 4)));
+
+		if (encoded_string[pos+2] != '=' && encoded_string[pos+2] != '.') { // accept URL-safe base 64 strings, too, so check for '.' also.
+
+			unsigned int pos_of_char_2 = pos_of_char(encoded_string[pos+2] );
+			ret.push_back(static_cast<std::string::value_type>( (( pos_of_char_1 & 0x0f) << 4) + (( pos_of_char_2 & 0x3c) >> 2)));
+
+			if (encoded_string[pos+3] != '=' && encoded_string[pos+3] != '.') {
+				ret.push_back(static_cast<std::string::value_type>( ( (pos_of_char_2 & 0x03 ) << 6 ) + pos_of_char(encoded_string[pos+3])   ));
+			}
+		}
+
+		pos += 4;
+	}
+
+	return ret;
+}
+
+std::string base64_decode(std::string const& s, bool remove_linebreaks) {
+	return decode(s, remove_linebreaks);
+}
+
+std::string base64_encode(std::string const& s, bool url) {
+	return encode(s, url);
+}
+
+std::string base64_encode_pem (std::string const& s) {
+	return encode_pem(s);
+}
+
+std::string base64_encode_mime(std::string const& s) {
+	return encode_mime(s);
+}
+
+#if __cplusplus >= 201703L
+//
+// Interface with std::string_view rather than const std::string&
+// Requires C++17
+// Provided by Yannic Bonenberger (https://github.com/Yannic)
+//
+
+std::string base64_encode(std::string_view s, bool url) {
+	return encode(s, url);
+}
+
+std::string base64_encode_pem(std::string_view s) {
+	return encode_pem(s);
+}
+
+std::string base64_encode_mime(std::string_view s) {
+	return encode_mime(s);
+}
+
+std::string base64_decode(std::string_view s, bool remove_linebreaks) {
+	return decode(s, remove_linebreaks);
+}
+
+#endif  // __cplusplus >= 201703L
diff --git a/components/net/cpp/include/ftl/net/ws_internal.hpp b/components/net/cpp/include/ftl/net/ws_internal.hpp
index 29fa3ff68..fb457578e 100644
--- a/components/net/cpp/include/ftl/net/ws_internal.hpp
+++ b/components/net/cpp/include/ftl/net/ws_internal.hpp
@@ -32,6 +32,10 @@ struct wsheader_type {
 	uint8_t masking_key[4];
 };
 
+struct ws_options {
+	std::string userinfo = "";
+};
+
 /**
  * Websocket dispatch parser. Given a raw socket buffer and its length, this
  * function parses the websocket header and if valid and containing enough data
@@ -49,7 +53,7 @@ int ws_parse(msgpack::unpacker &buf, wsheader_type &ws);
  */
 int ws_prepare(wsheader_type::opcode_type, bool useMask, size_t len, char *buffer, size_t maxlen);
 
-bool ws_connect(SOCKET sockfd, const ftl::URI &uri);
+bool ws_connect(SOCKET sockfd, const ftl::URI &uri, const ws_options &options=ws_options());
 
 };
 };
diff --git a/components/net/cpp/src/ws_internal.cpp b/components/net/cpp/src/ws_internal.cpp
index db9393d2a..28318ab5b 100644
--- a/components/net/cpp/src/ws_internal.cpp
+++ b/components/net/cpp/src/ws_internal.cpp
@@ -6,7 +6,10 @@
 #include <loguru.hpp>
 
 #include <cstring>
+
 #include <ftl/net/ws_internal.hpp>
+#include <ftl/utility/base64.hpp>
+
 #include <memory>
 
 
@@ -184,7 +187,7 @@ int ftl::net::ws_prepare(wsheader_type::opcode_type op, bool useMask, size_t len
 	return (int)header_size;
 }
 
-bool ftl::net::ws_connect(SOCKET sockfd, const URI &uri) {
+bool ftl::net::ws_connect(SOCKET sockfd, const URI &uri, const ws_options &options) {
 	string http = "";
 	int status;
 	int i;
@@ -196,11 +199,19 @@ bool ftl::net::ws_connect(SOCKET sockfd, const URI &uri) {
 	} else {
 		http += "Host: "+uri.getHost()+":"+std::to_string(uri.getPort())+"\r\n";
 	}
+	if (uri.hasUserInfo()) {
+		//https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Authorization
+		http += "Authorization: Basic ";
+		http += base64_encode(uri.getUserInfo()) + "\r\n";
+	}
+
 	http += "Upgrade: websocket\r\n";
 	http += "Connection: Upgrade\r\n";
 	http += "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==\r\n";
 	http += "Sec-WebSocket-Version: 13\r\n";
 	http += "\r\n";
+	// TODO: Check/process HTTP response code 
+
 	int rc = ::send(sockfd, http.c_str(), (int)http.length(), 0);
 	if (rc != (int)http.length()) {
 		LOG(ERROR) << "Could not send Websocket http request... (" << rc << ", " << errno << ")";
-- 
GitLab