|
9 | 9 | #include <thread> |
10 | 10 | #include <atomic> |
11 | 11 | #include <sstream> |
| 12 | +#include <algorithm> |
| 13 | +#include <cctype> |
12 | 14 |
|
13 | 15 | #include <boost/asio.hpp> |
14 | 16 | #include <boost/asio/ssl.hpp> |
@@ -41,9 +43,9 @@ class HTTPServer::Impl { |
41 | 43 | std::unique_ptr<ssl::context> sslCtx; // present when scheme==https |
42 | 44 | std::thread ioThread; |
43 | 45 |
|
44 | | - HTTPServer::RequestHandler requestHandler; |
45 | | - HTTPServer::NotificationHandler notificationHandler; |
46 | | - HTTPServer::ErrorHandler errorHandler; |
| 46 | + ITransport::RequestHandler requestHandler; |
| 47 | + ITransport::NotificationHandler notificationHandler; |
| 48 | + ITransport::ErrorHandler errorHandler; |
47 | 49 |
|
48 | 50 | explicit Impl(const HTTPServer::Options& o) : opts(o) { |
49 | 51 | if (opts.scheme == "https") { |
@@ -206,6 +208,27 @@ class HTTPServer::Impl { |
206 | 208 |
|
207 | 209 | net::awaitable<void> acceptLoop() { |
208 | 210 | try { |
| 211 | + // Validate port strictly: numeric and within [0, 65535] |
| 212 | + if (opts.port.empty()) { |
| 213 | + setError("HTTPServer invalid port: empty"); |
| 214 | + co_return; |
| 215 | + } |
| 216 | + bool allDigits = std::all_of(opts.port.begin(), opts.port.end(), [](unsigned char ch){ return std::isdigit(ch) != 0; }); |
| 217 | + if (!allDigits) { |
| 218 | + setError(std::string("HTTPServer invalid port (non-numeric): ") + opts.port); |
| 219 | + co_return; |
| 220 | + } |
| 221 | + unsigned long portNum = 0; |
| 222 | + try { |
| 223 | + portNum = std::stoul(opts.port); |
| 224 | + } catch (...) { |
| 225 | + setError(std::string("HTTPServer invalid port (parse failure): ") + opts.port); |
| 226 | + co_return; |
| 227 | + } |
| 228 | + if (portNum > 65535ul) { |
| 229 | + setError(std::string("HTTPServer invalid port (out of range): ") + opts.port); |
| 230 | + co_return; |
| 231 | + } |
209 | 232 | tcp::resolver resolver(co_await net::this_coro::executor); |
210 | 233 | auto r = resolver.resolve(opts.address, opts.port); |
211 | 234 | tcp::endpoint ep = *r.begin(); |
@@ -284,14 +307,97 @@ std::future<void> HTTPServer::Stop() { |
284 | 307 | return fut; |
285 | 308 | } |
286 | 309 |
|
287 | | -void HTTPServer::SetRequestHandler(RequestHandler handler) { |
| 310 | +void HTTPServer::SetRequestHandler(ITransport::RequestHandler handler) { |
288 | 311 | pImpl->requestHandler = std::move(handler); |
289 | 312 | } |
290 | | -void HTTPServer::SetNotificationHandler(NotificationHandler handler) { |
| 313 | +void HTTPServer::SetNotificationHandler(ITransport::NotificationHandler handler) { |
291 | 314 | pImpl->notificationHandler = std::move(handler); |
292 | 315 | } |
293 | | -void HTTPServer::SetErrorHandler(ErrorHandler handler) { |
294 | | - pImpl->errorHandler = std::move(handler); |
| 316 | + |
| 317 | +void HTTPServer::SetErrorHandler(ITransport::ErrorHandler handler) { |
| 318 | + pImpl->errorHandler = std::move(handler); |
| 319 | + } |
| 320 | + |
| 321 | +std::unique_ptr<ITransportAcceptor> HTTPServerFactory::CreateTransportAcceptor(const std::string& config) { |
| 322 | + HTTPServer::Options opts; |
| 323 | + // Factory default: http if scheme omitted |
| 324 | + opts.scheme = "http"; |
| 325 | + |
| 326 | + std::string cfg = config; |
| 327 | + // Trim leading/trailing spaces |
| 328 | + auto trim = [](std::string& s){ |
| 329 | + auto notSpace = [](unsigned char c){ return !std::isspace(c); }; |
| 330 | + s.erase(s.begin(), std::find_if(s.begin(), s.end(), notSpace)); |
| 331 | + s.erase(std::find_if(s.rbegin(), s.rend(), notSpace).base(), s.end()); |
| 332 | + }; |
| 333 | + trim(cfg); |
| 334 | + |
| 335 | + // Detect scheme |
| 336 | + auto startsWith = [](const std::string& s, const char* pfx){ return s.rfind(pfx, 0) == 0; }; |
| 337 | + if (startsWith(cfg, "http://")) { |
| 338 | + opts.scheme = "http"; |
| 339 | + cfg = cfg.substr(7); |
| 340 | + } else if (startsWith(cfg, "https://")) { |
| 341 | + opts.scheme = "https"; |
| 342 | + cfg = cfg.substr(8); |
| 343 | + } |
| 344 | + |
| 345 | + // Split query params |
| 346 | + std::string hostPortPath = cfg; |
| 347 | + std::string query; |
| 348 | + auto qpos = cfg.find('?'); |
| 349 | + if (qpos != std::string::npos) { |
| 350 | + hostPortPath = cfg.substr(0, qpos); |
| 351 | + query = cfg.substr(qpos + 1); |
| 352 | + } |
| 353 | + |
| 354 | + // Strip path component if present |
| 355 | + std::string hostPort = hostPortPath; |
| 356 | + auto slash = hostPortPath.find('/'); |
| 357 | + if (slash != std::string::npos) { |
| 358 | + hostPort = hostPortPath.substr(0, slash); |
| 359 | + } |
| 360 | + trim(hostPort); |
| 361 | + |
| 362 | + // Parse host[:port] including IPv4/IPv6 in [addr]:port form |
| 363 | + if (!hostPort.empty()) { |
| 364 | + if (hostPort.front() == '[') { |
| 365 | + auto rb = hostPort.find(']'); |
| 366 | + if (rb != std::string::npos) { |
| 367 | + opts.address = hostPort.substr(1, rb - 1); |
| 368 | + if (rb + 1 < hostPort.size() && hostPort[rb + 1] == ':') { |
| 369 | + opts.port = hostPort.substr(rb + 2); |
| 370 | + } |
| 371 | + } |
| 372 | + } else { |
| 373 | + auto colon = hostPort.rfind(':'); |
| 374 | + if (colon != std::string::npos) { |
| 375 | + opts.address = hostPort.substr(0, colon); |
| 376 | + opts.port = hostPort.substr(colon + 1); |
| 377 | + } else { |
| 378 | + opts.address = hostPort; |
| 379 | + } |
| 380 | + } |
| 381 | + trim(opts.address); |
| 382 | + trim(opts.port); |
| 383 | + if (opts.port.empty()) opts.port = "9443"; // default |
| 384 | + } |
| 385 | + |
| 386 | + // Parse query parameters: cert, key |
| 387 | + if (!query.empty()) { |
| 388 | + std::stringstream ss(query); |
| 389 | + std::string kv; |
| 390 | + while (std::getline(ss, kv, '&')) { |
| 391 | + auto eq = kv.find('='); |
| 392 | + std::string key = (eq == std::string::npos) ? kv : kv.substr(0, eq); |
| 393 | + std::string val = (eq == std::string::npos) ? std::string() : kv.substr(eq + 1); |
| 394 | + if (key == "cert") opts.certFile = val; |
| 395 | + else if (key == "key") opts.keyFile = val; |
| 396 | + } |
| 397 | + } |
| 398 | + |
| 399 | + // Return server acceptor |
| 400 | + return std::unique_ptr<ITransportAcceptor>(new HTTPServer(opts)); |
295 | 401 | } |
296 | 402 |
|
297 | 403 | } // namespace mcp |
0 commit comments