ソースを参照

Fixed WebSocket URL parsing to handle user and password

Paul-Louis Ageneau 4 年 前
コミット
a92e63720c
2 ファイル変更37 行追加17 行削除
  1. 30 16
      src/websocket.cpp
  2. 7 1
      src/wstransport.cpp

+ 30 - 16
src/websocket.cpp

@@ -51,31 +51,45 @@ WebSocket::~WebSocket() {
 WebSocket::State WebSocket::readyState() const { return mState; }
 
 void WebSocket::open(const string &url) {
+	PLOG_VERBOSE << "Opening WebSocket to URL: " << url;
+
 	if (mState != State::Closed)
-		throw std::runtime_error("WebSocket must be closed before opening");
+		throw std::logic_error("WebSocket must be closed before opening");
+
+	// Modified regex from RFC 3986, see https://tools.ietf.org/html/rfc3986#appendix-B
+	static const char *rs =
+	    R"(^(([^:.@/?#]+):)?(/{0,2}((([^:@]*)(:([^@]*))?)@)?(([^:/?#]*)(:([^/?#]*))?))?([^?#]*)(\?([^#]*))?(#(.*))?)";
 
-	static const char *rs = R"(^(([^:\/?#]+):)?(//([^\/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?)";
-	static std::regex regex(rs, std::regex::extended);
+	static const std::regex r(rs, std::regex::extended);
 
-	std::smatch match;
-	if (!std::regex_match(url, match, regex))
-		throw std::invalid_argument("Malformed WebSocket URL: " + url);
+	std::smatch m;
+	if (!std::regex_match(url, m, r) || m[10].length() == 0)
+		throw std::invalid_argument("Invalid WebSocket URL: " + url);
 
-	mScheme = match[2];
-	if (mScheme != "ws" && mScheme != "wss")
+	mScheme = m[2];
+	if (mScheme.empty())
+		mScheme = "ws";
+	else if (mScheme != "ws" && mScheme != "wss")
 		throw std::invalid_argument("Invalid WebSocket scheme: " + mScheme);
 
-	mHost = match[4];
-	if (auto pos = mHost.find(':'); pos != string::npos) {
-		mHostname = mHost.substr(0, pos);
-		mService = mHost.substr(pos + 1);
-	} else {
-		mHostname = mHost;
+	mHostname = m[10];
+	mService = m[12];
+	if (mService.empty()) {
 		mService = mScheme == "ws" ? "80" : "443";
+		mHost = mHostname;
+	} else {
+		mHost = mHostname + ':' + mService;
 	}
 
-	mPath = match[5];
-	if (string query = match[7]; !query.empty())
+	while (!mHostname.empty() && mHostname.front() == '[')
+		mHostname.erase(mHostname.begin());
+	while (!mHostname.empty() && mHostname.back() == ']')
+		mHostname.pop_back();
+
+	mPath = m[13];
+	if (mPath.empty())
+		mPath += '/';
+	if (string query = m[15]; !query.empty())
 		mPath += "?" + query;
 
 	changeState(State::Connecting);

+ 7 - 1
src/wstransport.cpp

@@ -58,6 +58,12 @@ WsTransport::WsTransport(std::shared_ptr<Transport> lower, string host, string p
 	onRecv(recvCallback);
 
 	PLOG_DEBUG << "Initializing WebSocket transport";
+
+	if (mHost.empty())
+		throw std::invalid_argument("WebSocket HTTP host cannot be empty");
+
+	if (mPath.empty())
+		throw std::invalid_argument("WebSocket HTTP path cannot be empty");
 }
 
 WsTransport::~WsTransport() { stop(); }
@@ -147,7 +153,7 @@ void WsTransport::close() {
 }
 
 bool WsTransport::sendHttpRequest() {
-	PLOG_DEBUG << "Sending WebSocket HTTP request";
+	PLOG_DEBUG << "Sending WebSocket HTTP request for path " << mPath;
 	changeState(State::Connecting);
 
 	auto seed = static_cast<unsigned int>(system_clock::now().time_since_epoch().count());