#include "compiler-parser.h" #include "compiler.h" namespace { const char* UCE_NAMED_RENDER_SYMBOL = "__uce_render"; const char* UCE_NAMED_COMPONENT_SYMBOL = "__uce_component"; const char* UCE_NAMED_SERVE_HTTP_SYMBOL = "__uce_serve_http"; struct CompilerCodeState { char quote_char = '\0'; bool inside_quote = false; bool inside_line_comment = false; bool inside_block_comment = false; }; bool compiler_code_state_is_neutral(const CompilerCodeState& state) { return(!state.inside_quote && !state.inside_line_comment && !state.inside_block_comment); } String compiler_cpp_raw_string_delimiter(const String& content) { StringList candidates = { "", "UCE", "UCE_LITERAL", "uce_literal_0", "uce_literal_1" }; u64 hash = 1469598103934665603ULL; for(unsigned char c : content) { hash ^= c; hash *= 1099511628211ULL; } for(u32 i = 0; i < 64; i += 1) { String suffix = to_hex(hash ^ ((u64)i * 0x9E3779B97F4A7C15ULL), 12); candidates.push_back("UCE" + suffix); } for(auto& delimiter : candidates) { String terminator = ")" + delimiter + "\""; if(content.find(terminator) == String::npos) return(delimiter); } return(""); } String compiler_cpp_string_literal(const String& content) { String delimiter = compiler_cpp_raw_string_delimiter(content); if(delimiter != "" || content.find(")\"") == String::npos) return("R\"" + delimiter + "(" + content + ")" + delimiter + "\""); // This fallback is only reachable for deliberately adversarial content that // contains every generated raw-string delimiter candidate. return(json_escape(content)); } void compiler_code_state_consume(CompilerCodeState& state, String& buffer, const String& content, u32& i) { char c = content[i]; char c1 = (i + 1 < content.length()) ? content[i + 1] : '\0'; buffer.append(1, c); if(state.inside_quote) { if(state.quote_char == c && (i == 0 || content[i-1] != '\\')) state.inside_quote = false; return; } if(state.inside_line_comment) { if(c == '\n') state.inside_line_comment = false; return; } if(state.inside_block_comment) { if(c == '*' && c1 == '/') { buffer.append(1, c1); state.inside_block_comment = false; i += 1; } return; } if(c == '/' && c1 == '/') { buffer.append(1, c1); state.inside_line_comment = true; i += 1; return; } if(c == '/' && c1 == '*') { buffer.append(1, c1); state.inside_block_comment = true; i += 1; return; } if(c == '"' || c == '\'') { state.inside_quote = true; state.quote_char = c; } } String compiler_capture_markup_literal(const String& content, u32& i) { String buffer = ""; u32 depth = 0; bool inside_code = false; CompilerCodeState code_state; u32 j = i + 2; while(j < content.length()) { char c = content[j]; char c1 = (j + 1 < content.length()) ? content[j + 1] : '\0'; char c2 = (j + 2 < content.length()) ? content[j + 2] : '\0'; if(inside_code) { if(compiler_code_state_is_neutral(code_state) && c == '?' && c1 == '>') { buffer.append(1, c); buffer.append(1, c1); inside_code = false; j += 2; continue; } compiler_code_state_consume(code_state, buffer, content, j); j += 1; continue; } if(c == '<' && c1 == '?') { inside_code = true; code_state = CompilerCodeState(); buffer.append(1, c); buffer.append(1, c1); j += 2; continue; } if(c == '<' && c1 == '/' && c2 == '>') { if(depth > 0) { depth -= 1; buffer.append(""); j += 3; continue; } i = j + 2; return(buffer); } if(c == '<' && c1 == '>') { depth += 1; buffer.append("<>"); j += 2; continue; } buffer.append(1, c); j += 1; } i = content.length(); 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(" + compiler_cpp_string_literal(literal_buffer) + ");"); literal_buffer.clear(); } String compiler_process_text_literal(Request* context, SharedUnit* su, String content) { String parsed_content; String code_buffer = ""; String literal_buffer = ""; CompilerCodeState code_state; bool inside_code = false; bool is_field = false; bool escape_field = false; for(u32 i = 0; i < content.length(); i++) { char c = content[i]; char c1 = (i + 1 < content.length()) ? content[i + 1] : '\0'; char c2 = (i + 2 < content.length()) ? content[i + 2] : '\0'; if(!inside_code) { if(c == '<' && c1 == '?') { inside_code = true; code_buffer = ""; code_state = CompilerCodeState(); compiler_append_text_literal_output(parsed_content, literal_buffer); if(c2 == '=') { is_field = true; escape_field = true; i += 2; } else if(c2 == ':') { is_field = true; escape_field = false; i += 2; } else { is_field = false; escape_field = false; i += 1; } continue; } literal_buffer.append(1, c); continue; } if(compiler_code_state_is_neutral(code_state) && c == '?' && c1 == '>') { inside_code = false; i += 1; if(is_field) { if(escape_field) { parsed_content.append( "print(html_escape( " + code_buffer + " )); " ); } else { parsed_content.append( "print( " + code_buffer + " ); " ); } } else { parsed_content.append(code_buffer); } continue; } if(compiler_code_state_is_neutral(code_state) && c == '<' && c1 == '>') { String nested_markup = compiler_capture_markup_literal(content, i); code_buffer.append(compiler_process_text_literal(context, su, nested_markup)); continue; } compiler_code_state_consume(code_state, code_buffer, content, i); } if(literal_buffer != "") compiler_append_text_literal_output(parsed_content, literal_buffer); return(parsed_content); } bool compiler_line_starts_entrypoint(String trimmed, String macro_name) { if(trimmed.rfind(macro_name + "(", 0) == 0) return(true); return(trimmed.rfind(macro_name + ":", 0) == 0 && trimmed.find("(") != String::npos); } bool compiler_line_starts_fragmentable_entrypoint(String trimmed, String& kind) { if(compiler_line_starts_entrypoint(trimmed, "ONCE")) { kind = "ONCE"; return(true); } if(compiler_line_starts_entrypoint(trimmed, "RENDER")) { kind = "RENDER"; return(true); } if(compiler_line_starts_entrypoint(trimmed, "COMPONENT")) { kind = "COMPONENT"; return(true); } return(false); } String compiler_fragment_capture_prelude(String slot) { return("\nUceFragmentCapture __uce_fragment_capture(context, " + compiler_cpp_string_literal(slot) + ");\n"); } String compiler_rewrite_fragment_attributes(String content) { StringList lines = split(content, "\n"); String result; for(u32 i = 0; i < lines.size(); i++) { String line = lines[i]; String kind; if(!compiler_line_starts_fragmentable_entrypoint(trim(line), kind)) { result += line; if(i + 1 < lines.size()) result += "\n"; continue; } String slot = (kind == "ONCE" ? "once" : ""); bool has_fragment_attr = false; u32 j = i + 1; while(j < lines.size()) { String attr_line = lines[j]; String attr_trimmed = trim(attr_line); if(attr_trimmed.rfind("@fragment", 0) == 0 && (attr_trimmed.length() == 9 || isspace((unsigned char)attr_trimmed[9]))) { slot = trim(attr_trimmed.substr(9)); has_fragment_attr = true; j++; continue; } break; } bool should_capture = (slot != "") && (kind == "ONCE" || has_fragment_attr); if(!should_capture) { result += line; if(j < lines.size()) result += "\n"; i = j - 1; continue; } auto declaration_brace_pos = line.find("{"); if(declaration_brace_pos != String::npos) { result += line.substr(0, declaration_brace_pos + 1) + compiler_fragment_capture_prelude(slot) + line.substr(declaration_brace_pos + 1); if(j < lines.size()) result += "\n"; i = j - 1; continue; } // The body must open on the next non-blank line after the attribute // lines; anything else (e.g. literal text that merely starts with // "ONCE(") is not an entry point and passes through untouched. u32 body_index = j; while(body_index < lines.size() && trim(lines[body_index]) == "") body_index++; bool body_opens = body_index < lines.size() && trim(lines[body_index]).rfind("{", 0) == 0; if(!body_opens) { result += line; if(i + 1 < lines.size()) result += "\n"; continue; } result += line; result += "\n"; for(u32 k = j; k < body_index; k++) result += lines[k] + "\n"; String body_line = lines[body_index]; auto brace_pos = body_line.find("{"); result += body_line.substr(0, brace_pos + 1) + compiler_fragment_capture_prelude(slot) + body_line.substr(brace_pos + 1); if(body_index + 1 < lines.size()) result += "\n"; i = body_index; } return(result); } bool compiler_rewrite_named_entrypoint_line(String& line, String macro_prefix, String symbol_prefix) { u32 indent_length = 0; while(indent_length < line.length() && isspace(line[indent_length])) indent_length += 1; String indent = line.substr(0, indent_length); String trimmed = trim(line); if(trimmed.rfind(macro_prefix, 0) != 0) return(false); String signature = trimmed.substr(macro_prefix.length()); auto open_paren_pos = signature.find("("); if(open_paren_pos == String::npos) return(false); String render_name = trim(signature.substr(0, open_paren_pos)); String render_signature = signature.substr(open_paren_pos); if(render_name == "") return(false); line = indent + "EXPORT void " + symbol_prefix + "_" + safe_name(render_name) + render_signature; return(true); } String compiler_rewrite_named_render_syntax(String content) { String result = ""; String current_line = ""; auto flush_line = [&]() { if(current_line.length() == 0) return; String line = current_line; String line_break = ""; if(line.length() > 0 && line.back() == '\n') { line_break = "\n"; line.pop_back(); } compiler_rewrite_named_entrypoint_line(line, "RENDER:", UCE_NAMED_RENDER_SYMBOL) || compiler_rewrite_named_entrypoint_line(line, "COMPONENT:", UCE_NAMED_COMPONENT_SYMBOL) || compiler_rewrite_named_entrypoint_line(line, "SERVE_HTTP:", UCE_NAMED_SERVE_HTTP_SYMBOL); result += line + line_break; current_line = ""; }; for(auto c : content) { current_line.append(1, c); if(c == '\n') flush_line(); } flush_line(); return(result); } String compiler_preprocess_shared_unit_char_wise(Request* context, SharedUnit* su, String content) { String parsed_content = "#include \"uce_lib.h\" \n"+ file_get_contents( context->server->config["COMPILER_SYS_PATH"] + "/" + context->server->config["SETUP_TEMPLATE"] )+ "#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'; char c2 = (i + 2 < content.length()) ? content[i + 2] : '\0'; if(inside_literal) { 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 ") { parsed_content.resize(parsed_content.length() - current_line.length()); nibble(current_line, "\""); String unit_name = nibble(current_line, "\""); String resolved_unit = unit_name; if(resolved_unit != "" && resolved_unit[0] != '/') resolved_unit = expand_path(resolved_unit, su->src_path); SharedUnit* sub_su = (resolved_unit == "" ? 0 : get_shared_unit(context, resolved_unit)); if(sub_su) parsed_content.append("#include \"" + sub_su->bin_path + "/" + sub_su->pre_file_name + "\"\n"); } else { String trimmed_line = trim(current_line); if(c == 10 && trimmed_line.length() > 7 && trimmed_line.substr(0, 6) == "EXPORT" && isspace(trimmed_line[6])) { current_line = ""; auto end_declaration_pos = content.find("{", i); if(end_declaration_pos != std::string::npos) { parsed_content.append(1, '\n'); String declaration = trim(content.substr(i, end_declaration_pos - i)); su->api_declarations.push_back(declaration+";\n"); } } } if(c == 10) current_line = ""; } if(inside_literal) compiler_append_text_literal_output(parsed_content, literal_buffer); return(parsed_content); } } String compiler_preprocess_source(Request* context, SharedUnit* su, String content) { content = compiler_rewrite_fragment_attributes(content); content = compiler_rewrite_named_render_syntax(content); return(compiler_preprocess_shared_unit_char_wise(context, su, content)); }