Fail unresolved component renders with HTTP 500
This commit is contained in:
@@ -14,6 +14,8 @@ Renders another `.uce` file as a component and returns the captured output as a
|
|||||||
|
|
||||||
Component props are passed in `context.props`.
|
Component props are passed in `context.props`.
|
||||||
|
|
||||||
|
Calling a missing component or named component handler is a server error and sets HTTP status 500. Probe intentionally optional components with `component_exists()` before calling `component()`.
|
||||||
|
|
||||||
Because `<?= ... ?>` HTML-escapes its value, embed component markup with `<?: component(...) ?>`, `print(component(...))`, or use `component_render(...)` for direct output.
|
Because `<?= ... ?>` HTML-escapes its value, embed component markup with `<?: component(...) ?>`, `print(component(...))`, or use `component_render(...)` for direct output.
|
||||||
|
|
||||||
## Named Components
|
## Named Components
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ Component props are passed through `context.props`, and `name:COMPONENTFUNC` may
|
|||||||
|
|
||||||
When `name` starts with `:`, the runtime resolves that named handler against the current `.uce` file.
|
When `name` starts with `:`, the runtime resolves that named handler against the current `.uce` file.
|
||||||
|
|
||||||
|
Calling a missing component or named component handler is a server error and sets HTTP status 500. Probe intentionally optional components with `component_exists()` before calling `component_render()`.
|
||||||
|
|
||||||
If the target file defines `ONCE(Request& context)`, that hook runs once per request before the file's first component or render entrypoint.
|
If the target file defines `ONCE(Request& context)`, that hook runs once per request before the file's first component or render entrypoint.
|
||||||
|
|
||||||
Use `component_render()` when you want to write component output directly from C++ code instead of capturing it as a `String`.
|
Use `component_render()` when you want to write component output directly from C++ code instead of capturing it as a `String`.
|
||||||
|
|||||||
@@ -329,6 +329,21 @@ void cli_run_site_suite(bool skip_local_service_pages = false)
|
|||||||
|
|
||||||
void cli_run_security_smoke()
|
void cli_run_security_smoke()
|
||||||
{
|
{
|
||||||
|
CliHttpResponse missing_component = cli_frontend("/tests/component_failure.uce");
|
||||||
|
cli_test_case("uce_component_failure:component_render missing target is HTTP 500",
|
||||||
|
missing_component.status == 500 && cli_contains(missing_component.body, "component not found: components/does-not-exist"),
|
||||||
|
"status=" + std::to_string(missing_component.status) + " body=" + cli_truncate(missing_component.body));
|
||||||
|
|
||||||
|
CliHttpResponse missing_captured_component = cli_frontend("/tests/component_failure.uce?mode=capture");
|
||||||
|
cli_test_case("uce_component_failure:component missing target is HTTP 500",
|
||||||
|
missing_captured_component.status == 500 && cli_contains(missing_captured_component.body, "component not found: components/does-not-exist"),
|
||||||
|
"status=" + std::to_string(missing_captured_component.status) + " body=" + cli_truncate(missing_captured_component.body));
|
||||||
|
|
||||||
|
CliHttpResponse optional_component = cli_frontend("/tests/component_failure.uce?mode=optional");
|
||||||
|
cli_test_case("uce_component_failure:component_exists keeps optional lookup non-fatal",
|
||||||
|
optional_component.status == 200 && optional_component.body == "optional component absent",
|
||||||
|
"status=" + std::to_string(optional_component.status) + " body=" + cli_truncate(optional_component.body));
|
||||||
|
|
||||||
CliHttpResponse dotdot = cli_direct_http("/../site/demo/hello.uce");
|
CliHttpResponse dotdot = cli_direct_http("/../site/demo/hello.uce");
|
||||||
cli_test_case("uce_security_smoke:direct HTTP rejects dot-dot script traversal", dotdot.status == 404 && !cli_contains(dotdot.body, "hello world"), "direct HTTP dot-dot traversal returned HTTP " + std::to_string(dotdot.status));
|
cli_test_case("uce_security_smoke:direct HTTP rejects dot-dot script traversal", dotdot.status == 404 && !cli_contains(dotdot.body, "hello world"), "direct HTTP dot-dot traversal returned HTTP " + std::to_string(dotdot.status));
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
RENDER(Request& context)
|
||||||
|
{
|
||||||
|
if(context.get["mode"] == "optional")
|
||||||
|
print(component_exists("components/does-not-exist") ? "unexpected" : "optional component absent");
|
||||||
|
else if(context.get["mode"] == "capture")
|
||||||
|
print(component("components/does-not-exist", context));
|
||||||
|
else
|
||||||
|
component_render("components/does-not-exist", context);
|
||||||
|
}
|
||||||
@@ -686,6 +686,7 @@ static void component_render_with_props(String name, DValue& props, Request& req
|
|||||||
s32 slot = wasm_resolve_target(file_name, handler, &resolved);
|
s32 slot = wasm_resolve_target(file_name, handler, &resolved);
|
||||||
if(!slot)
|
if(!slot)
|
||||||
{
|
{
|
||||||
|
request.set_status(500, "Internal Server Error");
|
||||||
print(component_error_banner("component not found: " + trim(name)));
|
print(component_error_banner("component not found: " + trim(name)));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user