#include "LuaEngine/LuaEngine.h" #include "DirectoryWalker/DirectoryWalker.h" #include "Dom/Dom.h" #include "Logger/Logger.h" #include "Logger/LuaLogger.h" #include "Path/Path.h" #include "Version/Version.h" #include #include #include #include #include #include #include #include typedef enum { SCRIPT_INLINE, SCRIPT_EXTERNAL } ScriptType; typedef struct { char *source; ScriptType type; unsigned long line; } LuaScript; typedef struct { LuaScript *items; size_t length; size_t capacity; } ScriptList; static int is_lua_element(const xmlNode *node) { return node->type == XML_ELEMENT_NODE && strcasecmp((const char *)node->name, "lua") == 0; } static char *duplicate_string(const char *value) { size_t length = strlen(value); char *copy = malloc(length + 1); if (copy != NULL) { memcpy(copy, value, length + 1); } return copy; } static int grow_script_list(ScriptList *scripts) { size_t larger_capacity; LuaScript *larger_items; if (scripts->capacity > SIZE_MAX / (2 * sizeof(*scripts->items))) { return -1; } larger_capacity = scripts->capacity == 0 ? 4 : scripts->capacity * 2; larger_items = realloc(scripts->items, larger_capacity * sizeof(*scripts->items)); if (larger_items == NULL) { return -1; } scripts->items = larger_items; scripts->capacity = larger_capacity; return 0; } static int append_script(ScriptList *scripts, const char *source, ScriptType type, unsigned long line) { LuaScript *script; if (scripts->length == scripts->capacity && grow_script_list(scripts) != 0) { return -1; } script = &scripts->items[scripts->length]; script->source = duplicate_string(source); if (script->source == NULL) { return -1; } script->type = type; script->line = line; ++scripts->length; return 0; } static int collect_script(xmlNode *node, ScriptList *scripts) { xmlChar *source_path = xmlGetProp(node, BAD_CAST "src"); xmlChar *code; int result; if (source_path != NULL) { result = append_script(scripts, (const char *)source_path, SCRIPT_EXTERNAL, node->line); xmlFree(source_path); return result; } code = xmlNodeGetContent(node); result = append_script(scripts, code == NULL ? "" : (const char *)code, SCRIPT_INLINE, node->line); xmlFree(code); return result; } static int collect_scripts(xmlNode *node, ScriptList *scripts) { for (; node != NULL; node = node->next) { if (is_lua_element(node)) { if (collect_script(node, scripts) != 0) { return -1; } } else if (collect_scripts(node->children, scripts) != 0) { return -1; } } return 0; } static void free_scripts(ScriptList *scripts) { size_t index; for (index = 0; index < scripts->length; ++index) { free(scripts->items[index].source); } free(scripts->items); } static char *make_chunk_name(const char *input_path, unsigned long line) { size_t input_length = strlen(input_path); size_t capacity; char *chunk_name; if (input_length > SIZE_MAX - 32) { return NULL; } capacity = input_length + 32; chunk_name = malloc(capacity); if (chunk_name != NULL) { snprintf(chunk_name, capacity, "@%s:%lu", input_path, line); } return chunk_name; } static int run_script(lua_State *lua, const LuaScript *script, const char *input_path) { char *source_path = NULL; char *chunk_name = NULL; const char *display_path; int load_result; int result; if (script->type == SCRIPT_EXTERNAL) { source_path = path_resolve_from_file(input_path, script->source); if (source_path == NULL) { logger_error("unable to allocate Lua source path"); return -1; } display_path = source_path; load_result = luaL_loadfile(lua, source_path); } else { chunk_name = make_chunk_name(input_path, script->line); if (chunk_name == NULL) { logger_error("unable to allocate Lua chunk name"); return -1; } display_path = input_path; load_result = luaL_loadbuffer(lua, script->source, strlen(script->source), chunk_name); } result = load_result == 0 ? lua_pcall(lua, 0, 0, 0) : load_result; if (result != 0) { const char *message = lua_tostring(lua, -1); logger_error("Lua error in %s: %s", display_path, message == NULL ? "unknown error" : message); lua_pop(lua, 1); } free(chunk_name); free(source_path); return result == 0 ? 0 : -1; } static int run_scripts(lua_State *lua, const ScriptList *scripts, const char *input_path) { size_t index; for (index = 0; index < scripts->length; ++index) { if (run_script(lua, &scripts->items[index], input_path) != 0) { return -1; } } return 0; } static void register_yapssg(lua_State *lua) { lua_newtable(lua); lua_pushstring(lua, yapssg_version); lua_setfield(lua, -2, "version"); lua_setglobal(lua, "yapssg"); } static int replace_document_root(lua_State *lua, htmlDocPtr document, const char *input_path) { xmlNodePtr replacement; xmlNodePtr old_root; lua_getglobal(lua, "page"); if (!lua_istable(lua, -1)) { logger_error("Lua global 'page' must remain a table: %s", input_path); lua_pop(lua, 1); return -1; } replacement = dom_page_from_lua(lua, lua_gettop(lua)); lua_pop(lua, 1); if (replacement == NULL || replacement->type != XML_ELEMENT_NODE) { logger_error("Lua produced an invalid page root: %s", input_path); xmlFreeNode(replacement); return -1; } old_root = xmlDocSetRootElement(document, replacement); xmlFreeNode(old_root); return 0; } int process_html_document(htmlDocPtr document, const char *input_path) { xmlNodePtr root = xmlDocGetRootElement(document); ScriptList scripts = {0}; lua_State *lua; int result; if (root == NULL) { logger_error("HTML document has no root element: %s", input_path); return -1; } if (collect_scripts(root, &scripts) != 0) { logger_error("unable to collect Lua scripts: %s", input_path); free_scripts(&scripts); return -1; } if (scripts.length == 0) { free_scripts(&scripts); return 0; } lua = luaL_newstate(); if (lua == NULL) { logger_error("unable to create Lua state"); free_scripts(&scripts); return -1; } luaL_openlibs(lua); register_yapssg(lua); lua_logger_register(lua); directory_walker_register(lua, input_path); dom_push_page(lua, root); lua_setglobal(lua, "page"); result = run_scripts(lua, &scripts, input_path); if (result == 0) { result = replace_document_root(lua, document, input_path); } lua_close(lua); free_scripts(&scripts); return result; }