Skip to content
Snippets Groups Projects
Commit 034dbf78 authored by Sebastian Hahta's avatar Sebastian Hahta
Browse files

Merge branch 'feature/http-auth' into 'master'

HTTP Basic Auth for websocket

See merge request nicolas.pope/ftl!306
parents 50abd7b8 ae9962e7
No related branches found
No related tags found
1 merge request!306HTTP Basic Auth for websocket
Pipeline #26972 passed
......@@ -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)
......
......@@ -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;
......
//
// 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 */
......@@ -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;
}
/*
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
......@@ -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());
};
};
......
......@@ -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 << ")";
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment