@@ -151,15 +151,14 @@ int main()
151151 std::cout << " [PASS] HTTP server does not set CORS by default\n " ;
152152 }
153153
154- // Test 5: HTTP server should set CORS header when explicitly configured
154+ // Test 5: HTTP server should set CORS header when using cors_origin convenience param
155155 {
156- std::cout << " Test: HTTP server sets CORS header when configured ...\n " ;
156+ std::cout << " Test: HTTP server sets CORS header via cors_origin param ...\n " ;
157157
158158 auto srv = std::make_shared<Server>();
159159 srv->route (" test" , [](const Json&) { return Json{{" result" , " ok" }}; });
160160
161- HttpServerWrapper http_server (srv, " 127.0.0.1" , 18603 , " " ,
162- {{" Access-Control-Allow-Origin" , " https://example.com" }});
161+ HttpServerWrapper http_server (srv, " 127.0.0.1" , 18603 , " " , " https://example.com" );
163162 if (!http_server.start ())
164163 {
165164 std::cerr << " Failed to start HTTP server\n " ;
@@ -231,8 +230,7 @@ int main()
231230 auto srv = std::make_shared<Server>();
232231 srv->route (" test" , [](const Json&) { return Json{{" result" , " ok" }}; });
233232
234- HttpServerWrapper http_server (srv, " 127.0.0.1" , 18605 , " " ,
235- {{" Access-Control-Allow-Origin" , " https://example.com" }});
233+ HttpServerWrapper http_server (srv, " 127.0.0.1" , 18605 , " " , " https://example.com" );
236234 if (!http_server.start ())
237235 {
238236 std::cerr << " Failed to start HTTP server with CORS config\n " ;
@@ -284,7 +282,7 @@ int main()
284282 { return Json{{" jsonrpc" , " 2.0" }, {" id" , req[" id" ]}, {" result" , {}}}; };
285283
286284 SseServerWrapper sse_server (handler, " 127.0.0.1" , 18605 , " /sse" , " /messages" , " " ,
287- {{ " Access-Control-Allow-Origin " , " https://example.com" }} );
285+ " https://example.com" );
288286 if (!sse_server.start ())
289287 {
290288 std::cerr << " Failed to start SSE server with CORS config\n " ;
@@ -328,6 +326,129 @@ int main()
328326 std::cout << " [PASS] SSE message endpoint handles CORS preflight\n " ;
329327 }
330328
329+ // Test 9: HTTP server CORS via response_headers map (new API)
330+ {
331+ std::cout << " Test: HTTP server sets CORS header via response_headers map...\n " ;
332+
333+ auto srv = std::make_shared<Server>();
334+ srv->route (" test" , [](const Json&) { return Json{{" result" , " ok" }}; });
335+
336+ HttpServerWrapper http_server (srv, " 127.0.0.1" , 18606 , " " , " " ,
337+ {{" Access-Control-Allow-Origin" , " *" },
338+ {" X-Custom-Header" , " custom-value" }});
339+ if (!http_server.start ())
340+ {
341+ std::cerr << " Failed to start HTTP server\n " ;
342+ return 1 ;
343+ }
344+
345+ std::this_thread::sleep_for (std::chrono::milliseconds (100 ));
346+
347+ httplib::Client client (" 127.0.0.1" , 18606 );
348+ auto res = client.Post (" /test" , R"( {"x":1})" , " application/json" );
349+
350+ if (!res || res->status != 200 )
351+ {
352+ std::cerr << " [FAIL] Request failed\n " ;
353+ http_server.stop ();
354+ return 1 ;
355+ }
356+
357+ auto cors_it = res->headers .find (" Access-Control-Allow-Origin" );
358+ if (cors_it == res->headers .end () || cors_it->second != " *" )
359+ {
360+ std::cerr << " [FAIL] CORS header missing or incorrect\n " ;
361+ http_server.stop ();
362+ return 1 ;
363+ }
364+
365+ auto custom_it = res->headers .find (" X-Custom-Header" );
366+ if (custom_it == res->headers .end () || custom_it->second != " custom-value" )
367+ {
368+ std::cerr << " [FAIL] Custom header missing or incorrect\n " ;
369+ http_server.stop ();
370+ return 1 ;
371+ }
372+
373+ http_server.stop ();
374+ std::cout << " [PASS] HTTP server sets headers via response_headers map\n " ;
375+ }
376+
377+ // Test 10: SSE server rejects protected headers (Content-Type)
378+ {
379+ std::cout << " Test: SSE server rejects protected Content-Type header...\n " ;
380+
381+ auto handler = [](const Json& req) -> Json
382+ { return Json{{" jsonrpc" , " 2.0" }, {" id" , req[" id" ]}, {" result" , {}}}; };
383+
384+ bool threw = false ;
385+ try
386+ {
387+ SseServerWrapper sse_server (handler, " 127.0.0.1" , 18607 , " /sse" , " /messages" , " " , " " ,
388+ {{" Content-Type" , " text/plain" }});
389+ }
390+ catch (const std::invalid_argument& e)
391+ {
392+ threw = true ;
393+ std::string msg = e.what ();
394+ if (msg.find (" Content-Type" ) == std::string::npos)
395+ {
396+ std::cerr << " [FAIL] Exception message should mention Content-Type: " << msg
397+ << " \n " ;
398+ return 1 ;
399+ }
400+ }
401+
402+ if (!threw)
403+ {
404+ std::cerr << " [FAIL] Should have thrown std::invalid_argument\n " ;
405+ return 1 ;
406+ }
407+
408+ std::cout << " [PASS] SSE server rejects protected Content-Type header\n " ;
409+ }
410+
411+ // Test 11: cors_origin is overridden when response_headers also sets it
412+ {
413+ std::cout << " Test: response_headers takes priority over cors_origin...\n " ;
414+
415+ auto srv = std::make_shared<Server>();
416+ srv->route (" test" , [](const Json&) { return Json{{" result" , " ok" }}; });
417+
418+ // cors_origin="*" but response_headers overrides with specific origin
419+ HttpServerWrapper http_server (srv, " 127.0.0.1" , 18608 , " " , " *" ,
420+ {{" Access-Control-Allow-Origin" , " https://specific.com" }});
421+ if (!http_server.start ())
422+ {
423+ std::cerr << " Failed to start HTTP server\n " ;
424+ return 1 ;
425+ }
426+
427+ std::this_thread::sleep_for (std::chrono::milliseconds (100 ));
428+
429+ httplib::Client client (" 127.0.0.1" , 18608 );
430+ auto res = client.Post (" /test" , R"( {"x":1})" , " application/json" );
431+
432+ if (!res || res->status != 200 )
433+ {
434+ std::cerr << " [FAIL] Request failed\n " ;
435+ http_server.stop ();
436+ return 1 ;
437+ }
438+
439+ auto cors_it = res->headers .find (" Access-Control-Allow-Origin" );
440+ if (cors_it == res->headers .end () || cors_it->second != " https://specific.com" )
441+ {
442+ std::cerr << " [FAIL] response_headers should take priority, got: "
443+ << (cors_it != res->headers .end () ? cors_it->second : " missing" ) << " \n " ;
444+ http_server.stop ();
445+ return 1 ;
446+ }
447+
448+ http_server.stop ();
449+ std::cout << " [PASS] response_headers takes priority over cors_origin\n " ;
450+ }
451+
331452 std::cout << " \n [OK] All HTTP/SSE auth and CORS security tests passed!\n " ;
332453 return 0 ;
333454}
0 commit comments