Loading admin/static/app.js +3 −1 Changes for admin/static/app.js: 3 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -93,7 +93,7 @@ const ProxyPlusAdmin = (() => { ? " (" + r.tls_pinned_fingerprints_sha256.length + " gepinnt)" : "") : "keine"; tr.innerHTML = `<td>${r.id}</td><td>${r.domain}</td><td>${r.path_prefix}</td><td>${r.priority}</td><td>${r.upstream}</td><td>${r.upstream_pool}</td><td>${r.required_permission}</td><td>${tlsInfo}</td><td></td>`; tr.innerHTML = `<td>${r.id}</td><td>${r.domain}</td><td>${r.path_prefix}</td><td>${r.priority}</td><td>${r.upstream}</td><td>${r.upstream_pool}</td><td>${r.required_permission}</td><td>${tlsInfo}</td><td>${r.force_https_redirect ? "ja" : "nein"}</td><td></td>`; addEditButton(tr, () => editRoute(r)); addDeleteButton(tr, () => deleteRoute(r.id)); body.appendChild(tr); Loading @@ -112,6 +112,7 @@ const ProxyPlusAdmin = (() => { document.getElementById("route-permission").value = r.required_permission; document.getElementById("route-tls-verify").checked = r.tls_verify_upstream; document.getElementById("route-tls-pins").value = (r.tls_pinned_fingerprints_sha256 || []).join(", "); document.getElementById("route-force-https-redirect").checked = r.force_https_redirect; } async function deleteRoute(id) { Loading Loading @@ -411,6 +412,7 @@ const ProxyPlusAdmin = (() => { required_permission: document.getElementById("route-permission").value, tls_verify_upstream: document.getElementById("route-tls-verify").checked, tls_pinned_fingerprints_sha256: pins, force_https_redirect: document.getElementById("route-force-https-redirect").checked, }); await refreshRoutes(); } catch (e) { Loading admin/static/index.html +2 −1 Changes for admin/static/index.html: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -24,7 +24,7 @@ <table id="status-table"></table> <h2>HTTP-Routen</h2> <table id="http-routes"><thead><tr><th>ID</th><th>Domain</th><th>Pfad</th><th>Prioritaet</th><th>Upstream</th><th>Pool</th><th>Berechtigung</th><th>TLS-Pruefung</th><th>Aktionen</th></tr></thead><tbody></tbody></table> <table id="http-routes"><thead><tr><th>ID</th><th>Domain</th><th>Pfad</th><th>Prioritaet</th><th>Upstream</th><th>Pool</th><th>Berechtigung</th><th>TLS-Pruefung</th><th>Zwangs-HTTPS</th><th>Aktionen</th></tr></thead><tbody></tbody></table> <button onclick="ProxyPlusAdmin.showNewRouteForm()">Neue Route</button> <div id="new-route-form" hidden> <input id="route-id" placeholder="ID"> Loading @@ -36,6 +36,7 @@ <input id="route-permission" placeholder="Berechtigung, z.B. http.proxy.use"> <label><input type="checkbox" id="route-tls-verify" checked> Upstream-TLS-Zertifikat pruefen</label> <input id="route-tls-pins" placeholder="Gepinnte SHA-256-Fingerprints, kommagetrennt (fuer selbstsignierte Upstreams)"> <label><input type="checkbox" id="route-force-https-redirect"> Zwangsweise auf HTTPS umleiten (301, nur auf dem Klartext-Listener)</label> <button onclick="ProxyPlusAdmin.createRoute()">Anlegen</button> <div id="route-error" class="error"></div> </div> Loading src/api/JsonApiServer.cpp +2 −0 Changes for src/api/JsonApiServer.cpp: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -117,6 +117,7 @@ json_object *httpRouteToJson(const store::HttpRouteRecord &r) { json_object_array_add(fpArr, json_object_new_string(fp.c_str())); } json_object_object_add(item, "tls_pinned_fingerprints_sha256", fpArr); json_object_object_add(item, "force_https_redirect", json_object_new_boolean(r.forceHttpsRedirect)); return item; } Loading @@ -138,6 +139,7 @@ std::string httpRouteFromJson(json_object *body, store::HttpRouteRecord &rec) { if (fp) rec.tlsPinnedFingerprintsSha256.emplace_back(fp); } } rec.forceHttpsRedirect = getBoolField(body, "force_https_redirect", /*def=*/false); if (rec.id.empty() || rec.pathPrefix.empty()) return "id and path_prefix are required"; if (rec.upstream.empty() && rec.upstreamPool.empty()) { return "either upstream or upstream_pool is required"; Loading src/http/HttpProxyServer.cpp +22 −0 Changes for src/http/HttpProxyServer.cpp: 22 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -371,6 +371,18 @@ void HttpProxyController::sendError(libhttppp::HttpRequest &curreq, const char * } } void HttpProxyController::redirectToHttps(libhttppp::HttpRequest &curreq, const std::string &host, const std::string &target) { try { libhttppp::HttpResponse curres; curres.setState(HTTP301); curres.setHeaderData("location")->push_back("https://" + host + target); curres.send(curreq, std::string(), 0); } catch (const libhttppp::HTTPException &e) { std::cerr << "[http] failed to send https redirect: " << e.what() << std::endl; } } void HttpProxyController::RequestEvent(libhttppp::HttpRequest &curreq, const int /*tid*/, ULONG_PTR /*args*/) { try { Loading Loading @@ -410,6 +422,16 @@ void HttpProxyController::RequestEvent(libhttppp::HttpRequest &curreq, const int return; } // Forced-HTTPS routes only redirect the plain listener; a request that already // arrived over TLS is proxied normally regardless of this flag. requestHost can be // empty for a Host-less request (legal pre-HTTP/1.1, rare in practice) -- there's no // sensible Location to build in that case, so fall through to the normal proxy path // instead of sending a redirect to nowhere. if (matched.forceHttpsRedirect && !_tlsActive && !requestHost.empty()) { redirectToHttps(curreq, requestHost, curreq.getRequest()); return; } std::map<std::string, std::string> headers; for (auto *hd = curreq.getfirstHeaderData(); hd; hd = hd->nextHeaderData()) { if (auto *v = hd->getfirstValue()) headers[hd->getkey()] = v->getvalue(); Loading src/http/HttpProxyServer.h +6 −0 Changes for src/http/HttpProxyServer.h: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -68,6 +68,12 @@ private: bool tryServeAcmeChallenge(libhttppp::HttpRequest &curreq, const std::string &url); // Sends the 301 for a route with HttpRouteRecord::forceHttpsRedirect set, matched on a // plain-HTTP listener. target is the original request-target (path+query, e.g. from // curreq.getRequest()) so the client lands back on the exact resource it asked for. void redirectToHttps(libhttppp::HttpRequest &curreq, const std::string &host, const std::string &target); // Negotiates gzip/brotli against the client's Accept-Encoding and applies it to body // in place, setting Content-Encoding/Vary on curres to match. upstreamEncoding is the // upstream response's own Content-Encoding value (empty if none) -- an upstream that Loading Loading
admin/static/app.js +3 −1 Changes for admin/static/app.js: 3 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -93,7 +93,7 @@ const ProxyPlusAdmin = (() => { ? " (" + r.tls_pinned_fingerprints_sha256.length + " gepinnt)" : "") : "keine"; tr.innerHTML = `<td>${r.id}</td><td>${r.domain}</td><td>${r.path_prefix}</td><td>${r.priority}</td><td>${r.upstream}</td><td>${r.upstream_pool}</td><td>${r.required_permission}</td><td>${tlsInfo}</td><td></td>`; tr.innerHTML = `<td>${r.id}</td><td>${r.domain}</td><td>${r.path_prefix}</td><td>${r.priority}</td><td>${r.upstream}</td><td>${r.upstream_pool}</td><td>${r.required_permission}</td><td>${tlsInfo}</td><td>${r.force_https_redirect ? "ja" : "nein"}</td><td></td>`; addEditButton(tr, () => editRoute(r)); addDeleteButton(tr, () => deleteRoute(r.id)); body.appendChild(tr); Loading @@ -112,6 +112,7 @@ const ProxyPlusAdmin = (() => { document.getElementById("route-permission").value = r.required_permission; document.getElementById("route-tls-verify").checked = r.tls_verify_upstream; document.getElementById("route-tls-pins").value = (r.tls_pinned_fingerprints_sha256 || []).join(", "); document.getElementById("route-force-https-redirect").checked = r.force_https_redirect; } async function deleteRoute(id) { Loading Loading @@ -411,6 +412,7 @@ const ProxyPlusAdmin = (() => { required_permission: document.getElementById("route-permission").value, tls_verify_upstream: document.getElementById("route-tls-verify").checked, tls_pinned_fingerprints_sha256: pins, force_https_redirect: document.getElementById("route-force-https-redirect").checked, }); await refreshRoutes(); } catch (e) { Loading
admin/static/index.html +2 −1 Changes for admin/static/index.html: 2 added lines, 1 removed line. Original line number Diff line number Diff line Loading @@ -24,7 +24,7 @@ <table id="status-table"></table> <h2>HTTP-Routen</h2> <table id="http-routes"><thead><tr><th>ID</th><th>Domain</th><th>Pfad</th><th>Prioritaet</th><th>Upstream</th><th>Pool</th><th>Berechtigung</th><th>TLS-Pruefung</th><th>Aktionen</th></tr></thead><tbody></tbody></table> <table id="http-routes"><thead><tr><th>ID</th><th>Domain</th><th>Pfad</th><th>Prioritaet</th><th>Upstream</th><th>Pool</th><th>Berechtigung</th><th>TLS-Pruefung</th><th>Zwangs-HTTPS</th><th>Aktionen</th></tr></thead><tbody></tbody></table> <button onclick="ProxyPlusAdmin.showNewRouteForm()">Neue Route</button> <div id="new-route-form" hidden> <input id="route-id" placeholder="ID"> Loading @@ -36,6 +36,7 @@ <input id="route-permission" placeholder="Berechtigung, z.B. http.proxy.use"> <label><input type="checkbox" id="route-tls-verify" checked> Upstream-TLS-Zertifikat pruefen</label> <input id="route-tls-pins" placeholder="Gepinnte SHA-256-Fingerprints, kommagetrennt (fuer selbstsignierte Upstreams)"> <label><input type="checkbox" id="route-force-https-redirect"> Zwangsweise auf HTTPS umleiten (301, nur auf dem Klartext-Listener)</label> <button onclick="ProxyPlusAdmin.createRoute()">Anlegen</button> <div id="route-error" class="error"></div> </div> Loading
src/api/JsonApiServer.cpp +2 −0 Changes for src/api/JsonApiServer.cpp: 2 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -117,6 +117,7 @@ json_object *httpRouteToJson(const store::HttpRouteRecord &r) { json_object_array_add(fpArr, json_object_new_string(fp.c_str())); } json_object_object_add(item, "tls_pinned_fingerprints_sha256", fpArr); json_object_object_add(item, "force_https_redirect", json_object_new_boolean(r.forceHttpsRedirect)); return item; } Loading @@ -138,6 +139,7 @@ std::string httpRouteFromJson(json_object *body, store::HttpRouteRecord &rec) { if (fp) rec.tlsPinnedFingerprintsSha256.emplace_back(fp); } } rec.forceHttpsRedirect = getBoolField(body, "force_https_redirect", /*def=*/false); if (rec.id.empty() || rec.pathPrefix.empty()) return "id and path_prefix are required"; if (rec.upstream.empty() && rec.upstreamPool.empty()) { return "either upstream or upstream_pool is required"; Loading
src/http/HttpProxyServer.cpp +22 −0 Changes for src/http/HttpProxyServer.cpp: 22 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -371,6 +371,18 @@ void HttpProxyController::sendError(libhttppp::HttpRequest &curreq, const char * } } void HttpProxyController::redirectToHttps(libhttppp::HttpRequest &curreq, const std::string &host, const std::string &target) { try { libhttppp::HttpResponse curres; curres.setState(HTTP301); curres.setHeaderData("location")->push_back("https://" + host + target); curres.send(curreq, std::string(), 0); } catch (const libhttppp::HTTPException &e) { std::cerr << "[http] failed to send https redirect: " << e.what() << std::endl; } } void HttpProxyController::RequestEvent(libhttppp::HttpRequest &curreq, const int /*tid*/, ULONG_PTR /*args*/) { try { Loading Loading @@ -410,6 +422,16 @@ void HttpProxyController::RequestEvent(libhttppp::HttpRequest &curreq, const int return; } // Forced-HTTPS routes only redirect the plain listener; a request that already // arrived over TLS is proxied normally regardless of this flag. requestHost can be // empty for a Host-less request (legal pre-HTTP/1.1, rare in practice) -- there's no // sensible Location to build in that case, so fall through to the normal proxy path // instead of sending a redirect to nowhere. if (matched.forceHttpsRedirect && !_tlsActive && !requestHost.empty()) { redirectToHttps(curreq, requestHost, curreq.getRequest()); return; } std::map<std::string, std::string> headers; for (auto *hd = curreq.getfirstHeaderData(); hd; hd = hd->nextHeaderData()) { if (auto *v = hd->getfirstValue()) headers[hd->getkey()] = v->getvalue(); Loading
src/http/HttpProxyServer.h +6 −0 Changes for src/http/HttpProxyServer.h: 6 added lines, 0 removed lines. Original line number Diff line number Diff line Loading @@ -68,6 +68,12 @@ private: bool tryServeAcmeChallenge(libhttppp::HttpRequest &curreq, const std::string &url); // Sends the 301 for a route with HttpRouteRecord::forceHttpsRedirect set, matched on a // plain-HTTP listener. target is the original request-target (path+query, e.g. from // curreq.getRequest()) so the client lands back on the exact resource it asked for. void redirectToHttps(libhttppp::HttpRequest &curreq, const std::string &host, const std::string &target); // Negotiates gzip/brotli against the client's Accept-Encoding and applies it to body // in place, setting Content-Encoding/Vary on curres to match. upstreamEncoding is the // upstream response's own Content-Encoding value (empty if none) -- an upstream that Loading