From 14ebf10a229021bb5d0fb1eae42a69c042699318 Mon Sep 17 00:00:00 2001 From: Udo Date: Wed, 22 Apr 2026 09:31:00 +0000 Subject: [PATCH] changing doc format and HTML literals --- README.md | 10 ++- site/doc/pages/3_C++ Preprocessor.txt | 29 ++++++--- site/test/demo_guard.h | 88 +++++++++++++++++++++++++++ site/test/error-reporting.uce | 7 +++ site/test/file_append.uce | 7 +++ site/test/index.uce | 28 ++++++--- site/test/memcached.uce | 7 +++ site/test/mysql.uce | 7 +++ site/test/shell.uce | 7 +++ site/test/task-status.uce | 7 +++ site/test/task.uce | 7 +++ site/test/task_repeat.uce | 7 +++ site/test/test2/working-dir-test.uce | 7 +++ site/test/unit-browser.uce | 24 ++++++-- site/test/unit-info.uce | 14 ++++- site/test/working-dir.uce | 7 +++ src/lib/compiler-parser.cpp | 86 ++++++++++++++++++++++++-- 17 files changed, 319 insertions(+), 30 deletions(-) create mode 100644 site/test/demo_guard.h diff --git a/README.md b/README.md index 05ce666..137c128 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,13 @@ Those are intended for sub-rendering through helpers such as `component("compone ## Template Output -Inside `<> ... ` literal blocks, UCE supports three inline forms: +UCE now treats template parsing as one shared code-vs-literal state machine. + +- `<>` and `?>` both enter literal output mode +- `` and `` to emit raw C++ statements - `` to print HTML-escaped output @@ -98,7 +104,7 @@ Inside `<> ... ` literal blocks, UCE supports three inline forms: Use `` by default for user-visible text. Use `` only for trusted markup or content that has already been escaped. -The parser now treats C++ `//` and `/* ... */` comments as comments in both normal code and `` islands, so quotes or `<>` markers inside comments do not confuse template parsing. +The parser now treats C++ `//` and `/* ... */` comments as comments in both normal code and `` islands, so quotes or delimiter markers inside comments do not confuse template parsing. The preprocessing implementation is now split between `src/lib/compiler.cpp` and `src/lib/compiler-parser.cpp`. `compiler.cpp` owns unit compilation and cache orchestration, while `compiler-parser.cpp` owns source rewriting and template parsing. diff --git a/site/doc/pages/3_C++ Preprocessor.txt b/site/doc/pages/3_C++ Preprocessor.txt index ac54af3..58b3110 100644 --- a/site/doc/pages/3_C++ Preprocessor.txt +++ b/site/doc/pages/3_C++ Preprocessor.txt @@ -14,12 +14,14 @@ unit_call :content UCE runs a small custom source-to-source preprocessor before Clang sees a `.uce` or `.ws.uce` file. -The implementation lives in `src/lib/compiler.cpp`. It does not try to parse all of C++. Instead, it performs a narrow character-wise rewrite that understands UCE literal blocks, inline code islands, `#load`, and `EXPORT` harvesting, then writes a generated `.cpp` file and compiles that file into a shared object. +The template rewriting implementation lives in `src/lib/compiler-parser.cpp`, with orchestration in `src/lib/compiler.cpp`. It does not try to parse all of C++. Instead, it performs a narrow character-wise rewrite that understands literal output, inline code islands, `#load`, and `EXPORT` harvesting, then writes a generated `.cpp` file and compiles that file into a shared object. ## Syntax - `<> ... ` enters literal-output mode. -- Inside a literal block, `` emits raw C++. +- `?> ... ... ... `, and the traditional matched forms all work. +- Inside literal output, `` emits raw C++. - Inside a literal block, `` emits `print(html_escape(expression));`. - Inside a literal block, `` emits `print(expression);` without HTML escaping. - `#load "other.uce"` injects another UCE unit at compile time. @@ -32,7 +34,9 @@ The implementation lives in `src/lib/compiler.cpp`. It does not try to parse all - The generated file starts by including `COMPILER_SYS_PATH/src/lib/uce_lib.h`. - It then inlines the configured setup template from `SETUP_TEMPLATE` (by default `scripts/setup.h.template`), which defines the internal hook `__uce_set_current_request(Request*)`. - It inserts `#line 1` before page code so compiler diagnostics point back to the original `.uce` file. -- Each literal block is rewritten into one or more `print(R"( ... )");` calls. +- Each literal region is rewritten into one or more `print(R"( ... )");` calls. +- `<>` and `?>` both switch from code mode into literal output. +- `` and `` temporarily breaks out of literal printing, emits the enclosed C++ unchanged, then resumes literal output. - `` becomes `print(html_escape(...));`. - `` becomes `print(...);` and is intended for trusted markup or already-escaped content. @@ -62,6 +66,15 @@ RENDER(Request& context) } ``` +The same thing can also be written with PHP-style literal delimiters: + +```cpp +RENDER(Request& context) +{ + ?>

`. -- Literal mode ends only on the exact token ``. +- Literal mode can start on either `<>` or `?>`. +- Literal mode can end on either `` or `` should open literal mode. +- Outside literal blocks it tracks C++ quotes and comments while deciding whether `<>` or `?>` should open literal mode. - It does not understand comments, raw string literals, templates, or general C++ token structure. -- Inside literal blocks it tracks single and double quotes while scanning a `` island so quoted `?>` text does not close the island early. -- Literal blocks are not nested. +- Inside literal blocks it tracks quotes and comments while scanning ``, ``, and `` islands so quoted `?>` text does not close those islands early. - Because literal output is emitted as a C++ raw string literal `R"( ... )"`, literal content must not contain the exact terminator sequence `)"` or the generated C++ will break. - `#load` depends on the target unit's generated `.cpp` existing and being compilable. If the target cannot be preprocessed or compiled correctly, the including file will fail to compile as well. diff --git a/site/test/demo_guard.h b/site/test/demo_guard.h new file mode 100644 index 0000000..1bce35b --- /dev/null +++ b/site/test/demo_guard.h @@ -0,0 +1,88 @@ +String test_demo_normalize_ip(String ip) +{ + if(ip.find(",") != String::npos) + ip = trim(nibble(ip, ",")); + ip = trim(ip); + if(str_starts_with(ip, "::ffff:")) + ip = ip.substr(7); + return(ip); +} + +bool test_demo_ip_is_private(String ip) +{ + ip = trim(ip); + if(ip == "" || ip == "localhost" || ip == "::1") + return(true); + if(str_starts_with(ip, "127.")) + return(true); + if(str_starts_with(ip, "10.")) + return(true); + if(str_starts_with(ip, "192.168.")) + return(true); + if(str_starts_with(ip, "fc") || str_starts_with(ip, "fd") || str_starts_with(ip, "fe80:")) + return(true); + + auto parts = split(ip, "."); + if(parts.size() == 4 && parts[0] == "172") + { + s64 second = int_val(parts[1]); + if(second >= 16 && second <= 31) + return(true); + } + + return(false); +} + +String test_demo_request_ip(Request& context) +{ + String remote_ip = test_demo_normalize_ip(context.params["REMOTE_ADDR"]); + String forwarded_ip = test_demo_normalize_ip(first(context.params["HTTP_X_FORWARDED_FOR"], context.params["HTTP_X_REAL_IP"])); + + if(remote_ip != "" && !test_demo_ip_is_private(remote_ip)) + return(remote_ip); + if(forwarded_ip != "") + return(forwarded_ip); + return(remote_ip); +} + +bool test_demo_request_allowed(Request& context) +{ + return(test_demo_ip_is_private(test_demo_request_ip(context))); +} + +void test_demo_render_restricted_html(Request& context, String title, String risk, String style_href = "style.css", String back_href = "index.uce") +{ + context.set_status(403, "Restricted"); + String request_ip = first(test_demo_request_ip(context), "unknown"); + print(""); + print(""); + print(""); + print(""); + print("

UCE Test: ", html_escape(title), "

"); + print("

This test page is disabled for public access because it can ", html_escape(risk), ".

"); + print("

It remains available from localhost or a private network for local development and server administration.

"); + print("

Detected request source: ", html_escape(request_ip), "

"); + print(""); +} + +void test_demo_render_restricted_text(Request& context, String title, String risk) +{ + context.set_status(403, "Restricted"); + context.header["Content-Type"] = "text/plain; charset=utf-8"; + print(title, ": restricted on public access\n"); + print("Reason: this test can ", risk, ".\n"); + print("Detected request source: ", first(test_demo_request_ip(context), "unknown"), "\n"); +} + +void test_demo_render_restricted_json(Request& context, String title, String risk) +{ + context.set_status(403, "Restricted"); + context.header["Content-Type"] = "application/json; charset=utf-8"; + DTree payload; + payload["ok"].set_bool(false); + payload["error"] = "restricted"; + payload["title"] = title; + payload["reason"] = "This test is disabled for public access because it can " + risk + "."; + payload["request_ip"] = first(test_demo_request_ip(context), "unknown"); + print(json_encode(payload)); +} diff --git a/site/test/error-reporting.uce b/site/test/error-reporting.uce index 7781640..89cce27 100644 --- a/site/test/error-reporting.uce +++ b/site/test/error-reporting.uce @@ -1,6 +1,13 @@ +#include "demo_guard.h" + RENDER(Request& context) { + if(!test_demo_request_allowed(context)) + { + test_demo_render_restricted_html(context, "Error reporting", "intentionally crash or abort request workers"); + return; + } String mode = context.get["mode"]; if(mode == "exception") diff --git a/site/test/file_append.uce b/site/test/file_append.uce index 442fe6b..ba425a9 100644 --- a/site/test/file_append.uce +++ b/site/test/file_append.uce @@ -1,9 +1,16 @@ +#include "demo_guard.h" + RENDER(Request& context) { + if(!test_demo_request_allowed(context)) + { + test_demo_render_restricted_html(context, "File Append", "write to server-side files"); + return; + } DTree t; <> diff --git a/site/test/index.uce b/site/test/index.uce index e74002d..df513a1 100644 --- a/site/test/index.uce +++ b/site/test/index.uce @@ -1,4 +1,6 @@ +#include "demo_guard.h" + void render_card(String url, String title, String desc) { @@ -12,6 +14,7 @@ RENDER(Request& context) { DTree p; p.set(context.params); + bool allow_server_demos = test_demo_request_allowed(context); <> @@ -26,9 +29,9 @@ RENDER(Request& context)
Basics
- - + +
Data Types & Parsing
@@ -49,21 +52,27 @@ RENDER(Request& context)
Storage & I/O
- - - - + + + +
Advanced
- - - + + +
+ +
+

Restricted On Public Access

+
Local-only demos are hidden here because they can execute shell commands, access infrastructure services, mutate server state, compile units, or expose internal runtime details.
+
+

System Info

request_count, "\n");
 			?>
+
Request Parameters
diff --git a/site/test/memcached.uce b/site/test/memcached.uce index 8376e6a..1aac709 100644 --- a/site/test/memcached.uce +++ b/site/test/memcached.uce @@ -1,7 +1,14 @@ +#include "demo_guard.h" + RENDER(Request& context) { + if(!test_demo_request_allowed(context)) + { + test_demo_render_restricted_html(context, "MemcacheD", "inspect and mutate local memcached state"); + return; + } DTree t; <> diff --git a/site/test/mysql.uce b/site/test/mysql.uce index 70f7025..d2429cd 100644 --- a/site/test/mysql.uce +++ b/site/test/mysql.uce @@ -1,7 +1,14 @@ +#include "demo_guard.h" + RENDER(Request& context) { + if(!test_demo_request_allowed(context)) + { + test_demo_render_restricted_html(context, "MySQL Connector Test", "connect to local database services and expose database contents"); + return; + } <> diff --git a/site/test/shell.uce b/site/test/shell.uce index 07929de..647c3bc 100644 --- a/site/test/shell.uce +++ b/site/test/shell.uce @@ -1,7 +1,14 @@ +#include "demo_guard.h" + RENDER(Request& context) { + if(!test_demo_request_allowed(context)) + { + test_demo_render_restricted_html(context, "Shell Stuff", "execute shell commands and expose repository state"); + return; + } <> diff --git a/site/test/task-status.uce b/site/test/task-status.uce index 153555c..3f1c48e 100644 --- a/site/test/task-status.uce +++ b/site/test/task-status.uce @@ -1,6 +1,13 @@ +#include "demo_guard.h" + RENDER(Request& context) { + if(!test_demo_request_allowed(context)) + { + test_demo_render_restricted_text(context, "Task Status", "inspect background task state"); + return; + } String task_name = first(context.get["task-name"], "example-task"); print("Task Name: ", task_name, "\n"); diff --git a/site/test/task.uce b/site/test/task.uce index ce85595..d0b650f 100644 --- a/site/test/task.uce +++ b/site/test/task.uce @@ -1,6 +1,13 @@ +#include "demo_guard.h" + RENDER(Request& context) { + if(!test_demo_request_allowed(context)) + { + test_demo_render_restricted_html(context, "Tasks", "spawn and inspect background tasks"); + return; + } DTree t; String task_name = first(context.get["task-name"], "example-task"); diff --git a/site/test/task_repeat.uce b/site/test/task_repeat.uce index 6d07496..b617de0 100644 --- a/site/test/task_repeat.uce +++ b/site/test/task_repeat.uce @@ -1,6 +1,13 @@ +#include "demo_guard.h" + RENDER(Request& context) { + if(!test_demo_request_allowed(context)) + { + test_demo_render_restricted_html(context, "Task repeat", "schedule recurring background tasks"); + return; + } DTree t; String task_name = first(context.get["task-name"], "example-task"); diff --git a/site/test/test2/working-dir-test.uce b/site/test/test2/working-dir-test.uce index 6459123..7192ef1 100644 --- a/site/test/test2/working-dir-test.uce +++ b/site/test/test2/working-dir-test.uce @@ -1,6 +1,13 @@ +#include "../demo_guard.h" + RENDER(Request& context) { + if(!test_demo_request_allowed(context)) + { + test_demo_render_restricted_text(context, "Working Directory Sub-Invoke", "reveal internal filesystem layout"); + return; + } print("Sub-Invoke Working dir: ", cwd_get(), "\n"); } diff --git a/site/test/unit-browser.uce b/site/test/unit-browser.uce index e239ffa..5320e3c 100644 --- a/site/test/unit-browser.uce +++ b/site/test/unit-browser.uce @@ -1,3 +1,5 @@ +#include "demo_guard.h" + String unit_query(String selected_path, String compile_path = "") { String result = "?"; @@ -32,6 +34,7 @@ String unit_flag_label(DTree value) RENDER(Request& context) { + bool allow_mutation = test_demo_request_allowed(context); String compile_target = first(context.get["compile"], ""); String selected_path = first(context.get["selected"], ""); String compile_message = ""; @@ -39,10 +42,19 @@ RENDER(Request& context) if(compile_target != "") { - bool compile_ok = unit_compile(compile_target); selected_path = compile_target; - compile_message = (compile_ok ? "Compile succeeded for " : "Compile failed for ") + compile_target; - compile_message_class = compile_ok ? "status-ok" : "status-error"; + if(allow_mutation) + { + bool compile_ok = unit_compile(compile_target); + compile_message = (compile_ok ? "Compile succeeded for " : "Compile failed for ") + compile_target; + compile_message_class = compile_ok ? "status-ok" : "status-error"; + } + else + { + context.set_status(403, "Restricted"); + compile_message = "Manual compile is restricted to localhost and private-network access."; + compile_message_class = "status-error"; + } } auto unit_paths = units_list(); @@ -122,7 +134,11 @@ RENDER(Request& context)
- compile + + compile + + compile disabled on public access +
diff --git a/site/test/unit-info.uce b/site/test/unit-info.uce index a27e7f8..97c9f95 100644 --- a/site/test/unit-info.uce +++ b/site/test/unit-info.uce @@ -1,9 +1,21 @@ +#include "demo_guard.h" + RENDER(Request& context) { + bool allow_mutation = test_demo_request_allowed(context); DTree payload; if(context.get["compile"] != "") - payload["compile_ok"].set_bool(unit_compile(context.get["compile"])); + { + if(allow_mutation) + payload["compile_ok"].set_bool(unit_compile(context.get["compile"])); + else + { + context.set_status(403, "Restricted"); + payload["error"] = "restricted"; + payload["reason"] = "Manual compile is restricted to localhost and private-network access."; + } + } payload["current"] = unit_info(); diff --git a/site/test/working-dir.uce b/site/test/working-dir.uce index e1f0df8..6d877de 100644 --- a/site/test/working-dir.uce +++ b/site/test/working-dir.uce @@ -1,7 +1,14 @@ +#include "demo_guard.h" + RENDER(Request& context) { + if(!test_demo_request_allowed(context)) + { + test_demo_render_restricted_html(context, "Working Directory", "reveal internal filesystem layout"); + return; + } <> diff --git a/src/lib/compiler-parser.cpp b/src/lib/compiler-parser.cpp index a4effc8..088790c 100644 --- a/src/lib/compiler-parser.cpp +++ b/src/lib/compiler-parser.cpp @@ -145,6 +145,37 @@ String compiler_capture_markup_literal(const String& content, u32& i) return(buffer); } +String compiler_capture_code_until_php_close(const String& content, u32& i) +{ + String buffer = ""; + CompilerCodeState code_state; + + while(i < content.length()) + { + char c = content[i]; + char c1 = (i + 1 < content.length()) ? content[i + 1] : '\0'; + + if(compiler_code_state_is_neutral(code_state) && c == '?' && c1 == '>') + { + i += 1; + return(buffer); + } + + compiler_code_state_consume(code_state, buffer, content, i); + i += 1; + } + + return(buffer); +} + +void compiler_append_text_literal_output(String& parsed_content, String& literal_buffer) +{ + if(literal_buffer == "") + return; + parsed_content.append("print(R\"(" + literal_buffer + ")\");"); + literal_buffer.clear(); +} + String compiler_process_text_literal(Request* context, SharedUnit* su, String content) { String parsed_content; @@ -314,22 +345,62 @@ String compiler_preprocess_shared_unit_char_wise(Request* context, SharedUnit* s "#line 1\n"; CompilerCodeState code_state; String current_line = ""; + String literal_buffer = ""; + bool inside_literal = false; for(u32 i = 0; i < content.length(); i++) { char c = content[i]; char c1 = (i + 1 < content.length()) ? content[i + 1] : '\0'; - current_line.append(1, c); + char c2 = (i + 2 < content.length()) ? content[i + 2] : '\0'; - if(compiler_code_state_is_neutral(code_state) && c == '<' && c1 == '>') + if(inside_literal) { - String markup_literal = compiler_capture_markup_literal(content, i); - parsed_content.append(compiler_process_text_literal(context, su, markup_literal)); - if(i < content.length() && content[i] == '\n') - current_line = "\n"; + if(c == '<' && c1 == '?' && (c2 == '=' || c2 == ':')) + { + compiler_append_text_literal_output(parsed_content, literal_buffer); + bool escape_field = (c2 == '='); + i += 3; + String field_code = compiler_capture_code_until_php_close(content, i); + if(escape_field) + parsed_content.append("print(html_escape( " + field_code + " )); "); + else + parsed_content.append("print( " + field_code + " ); "); + continue; + } + + if(c == '<' && c1 == '?') + { + compiler_append_text_literal_output(parsed_content, literal_buffer); + inside_literal = false; + code_state = CompilerCodeState(); + i += 1; + continue; + } + + if(c == '<' && c1 == '/' && c2 == '>') + { + compiler_append_text_literal_output(parsed_content, literal_buffer); + inside_literal = false; + i += 2; + continue; + } + + literal_buffer.append(1, c); continue; } + if(compiler_code_state_is_neutral(code_state) && ((c == '<' && c1 == '>') || (c == '?' && c1 == '>'))) + { + inside_literal = true; + literal_buffer = ""; + current_line = ""; + i += 1; + continue; + } + + current_line.append(1, c); + compiler_code_state_consume(code_state, parsed_content, content, i); if(c == 10 && current_line.substr(0, 6) == "#load ") @@ -361,6 +432,9 @@ String compiler_preprocess_shared_unit_char_wise(Request* context, SharedUnit* s current_line = ""; } + if(inside_literal) + compiler_append_text_literal_output(parsed_content, literal_buffer); + return(parsed_content); }