diff --git a/bin/uce_fastcgi.debug.linux.bin b/bin/uce_fastcgi.debug.linux.bin index b5dc7f0..2872655 100755 Binary files a/bin/uce_fastcgi.debug.linux.bin and b/bin/uce_fastcgi.debug.linux.bin differ diff --git a/src/fastcgi/src/fcgicc.cc b/src/fastcgi/src/fcgicc.cc index 6570e70..4c90083 100644 --- a/src/fastcgi/src/fcgicc.cc +++ b/src/fastcgi/src/fcgicc.cc @@ -293,7 +293,7 @@ FastCGIServer::process(int timeout_ms) } if (it->second->close_socket && it->second->output_buffer.empty()) { - close_socket: + close_socket: int close_result = close(it->first); if (close_result == -1 && errno != ECONNRESET) throw std::runtime_error("close() failed"); diff --git a/src/lib/compiler.cpp b/src/lib/compiler.cpp new file mode 100644 index 0000000..29c83cd --- /dev/null +++ b/src/lib/compiler.cpp @@ -0,0 +1,316 @@ +#include "compiler.h" +#include + +String process_html_literal(Request* context, SharedUnit* su, String content) +{ + String pc; + String HT_START = "context->print(R\"("; + String HT_END = ")\");"; + + u8 mode = 0; + char quote_char; + bool inside_quote = false; + String code_buffer = ""; + bool is_field = false; + + for(u32 i = 0; i < content.length(); i++) + { + char c = content[i]; + + switch(mode) + { + case(0): + if(c == '<' && content[i+1] == '?') + { + code_buffer = ""; + if(content[i+2] == '=') + { + is_field = true; + i += 2; + } + else + { + is_field = false; + i += 1; + } + mode = 1; // code-parsing mode + } + else + { + pc.append(1, c); + } + break; + case(1): + if(inside_quote) + { + if(quote_char == c && content[i-1] != '\\') + inside_quote = false; + code_buffer.append(1, c); + } + else + { + if(c == '\"' || c == '\'') + { + inside_quote = true; + quote_char = c; + code_buffer.append(1, c); + } + else if(c == '?' && content[i+1] == '>') + { + mode = 0; + i += 1; + if(is_field) + { + pc.append( + HT_END + + "echo(html_escape( " + + code_buffer + + " )); " + + HT_START + ); + } + else + { + pc.append(HT_END + code_buffer + HT_START); + } + } + else + { + code_buffer.append(1, c); + } + } + break; + } + + } + + return(HT_START + pc + HT_END); +} + +String preprocess_shared_unit(Request* context, SharedUnit* su) +{ + String content = file_get_contents(su->file_name); + printf("(c) compiling with root dir %s\n", context->server->config.COMPILER_SYS_PATH.c_str()); + String pc = ("#include \"")+context->server->config.COMPILER_SYS_PATH +"/src/lib/uce_lib.h\" \n"; + String token = ""; + String html_buffer = ""; + u8 mode = 0; + bool inside_quote = false; + for(u32 i = 0; i < content.length(); i++) + { + char c = content[i]; + if(mode == 2) + { + auto end_pos = content.find(String("", i); + if(end_pos != String::npos) + { + u32 len = token.length() + 3 + end_pos - i; + html_buffer.append(content.substr(i, len - (token.length() == 0 ? 3 : 0))); + i += len - 1; + pc.append(process_html_literal(context, su, html_buffer)); + } + else + { + printf("(!) unterminated HTML literal <%s> in %s", token.c_str(), su->file_name.c_str()); + } + mode = 0; + } + else if(mode == 1) + { + if(isspace(c) || c == '>') + { + mode = 2; + if(token.length() > 0) + html_buffer.append(1, c); + } + else + { + token.append(1, c); + html_buffer.append(1, c); + } + } + else if(!inside_quote && c == '<' && (content[i+1] == '>'/* || isalpha(content[i+1])*/)) + { + mode = 1; + token = ""; + html_buffer = ""; + if(content[i+1] != '>') + html_buffer.append(1, c); + } + else if(c == '\"') + { + inside_quote = !inside_quote; + pc.append(1, c); + } + else + { + pc.append(1, c); + } + } + return(pc); +} + +void setup_unit_paths(Request* context, SharedUnit* su, String file_name) +{ + su->file_name = file_name; + + if(su->src_path.length() > 0) // we did this already + return; + + su->src_path = dirname(file_name); + su->bin_path = context->server->config.BIN_DIRECTORY + su->src_path; + su->pre_path = context->server->config.BIN_DIRECTORY + su->src_path; + + su->src_file_name = basename(file_name); + su->bin_file_name = su->src_file_name + ".so"; + su->pre_file_name = su->src_file_name + ".cpp"; + + su->so_name = su->bin_path + "/" + su->bin_file_name; +} + +void load_shared_unit(Request* context, SharedUnit* su, String file_name) +{ + //setup_unit_paths(context, su, file_name); + + su->on_render = 0; + su->on_setup = 0; + su->compiler_messages = ""; + + if(!file_exists(su->so_name)) + { + printf("(i) unit file not found: %s\n", su->so_name.c_str()); + su->compiler_messages = "unit file not found"; + return; + } + + su->so_handle = dlopen(su->so_name.c_str(), RTLD_NOW); + if(su->so_handle) + { + su->last_compiled = file_mtime(su->so_name); + char *error; + su->on_setup = (request_handler)dlsym(su->so_handle, "set_current_request"); + su->on_render = (call_handler)dlsym(su->so_handle, "render"); + if ((error = dlerror()) != NULL) + printf("Error - %s in %s\n", error, su->file_name.c_str()); + else + printf("(i) loaded unit %s\n", su->file_name.c_str()); + } + else + { + printf("Error loading unit %s, could not open %s\n", su->file_name.c_str(), su->so_name.c_str()); + } +} + +void compile_shared_unit(Request* context, SharedUnit* su, String file_name) +{ + //setup_unit_paths(context, su, file_name); + + if(!file_exists(su->file_name)) + { + su->compiler_messages = "source file not found (" + su->file_name + ")"; + return; + } + + shell_exec("mkdir -p " + shell_escape(su->pre_path)); + file_put_contents(su->pre_path + "/" + su->pre_file_name, preprocess_shared_unit(context, su)); + + printf("Config.COMPILE_SCRIPT %s\n", String(su->pre_path + "/" + su->pre_file_name).c_str()); + + su->compiler_messages = trim(shell_exec(context->server->config.COMPILE_SCRIPT+" "+ + shell_escape(su->src_path)+" "+ + shell_escape(su->bin_path)+" "+ + shell_escape(su->file_name)+" "+ + shell_escape(su->pre_file_name)+" "+ + shell_escape(su->bin_file_name) + )); + + if(su->compiler_messages.length() > 0) + { + printf("%s \n", su->compiler_messages.c_str()); + } + else + { + load_shared_unit(context, su, file_name); + printf("(i) compiled unit %s\n", file_name.c_str()); + } +} + +SharedUnit* get_shared_unit(Request* context, String file_name) +{ + SharedUnit* su = context->server->units[file_name]; + auto mod_time = file_mtime(file_name); + bool do_recompile = false; + if(su && (su->last_compiled < mod_time || mod_time == 0)) + { + delete su; + su = 0; + do_recompile = true; + } + if(!su) + { + su = new SharedUnit(); + setup_unit_paths(context, su, file_name); + + int fdlock = open((su->so_name+".lock").c_str(), O_RDWR | O_CREAT, 0666 ); + int fl_excl = flock(fdlock, LOCK_EX); + + if(do_recompile) + { + compile_shared_unit(context, su, file_name); + } + else + { + load_shared_unit(context, su, file_name); + if(!su->so_handle) + compile_shared_unit(context, su, file_name); + } + + flock(fdlock, LOCK_UN); + close(fdlock); + remove((su->so_name+".lock").c_str()); + + context->server->units[file_name] = su; + } + return(su); +} + +void compiler_invoke(Request* context, String file_name, DTree& call_param) +{ + + if(file_name[0] != '/') + { + file_name = expand_path(file_name); + } + //printf("(i) invoke %s\n", file_name.c_str()); + + //printf("(i) invoke(%s)\n", file_name.c_str()); + switch_to_system_alloc(); + auto su = get_shared_unit(context, file_name); + switch_to_arena(context->request_arena); + if(!su) + { + printf("Error loading unit %s\n", file_name.c_str()); + context->print("Error loading unit: "+file_name); + } + else if(!su->on_render) + { + context->header["Content-Type"] = "text/plain"; + context->print("Compiler error: "+su->compiler_messages); + } + else + { + if(su->compiler_messages.length() > 0) + context->print(su->compiler_messages); + else + { + String prev_wd = get_cwd(); + set_cwd(su->src_path); + su->on_setup(context); + su->on_render(call_param); + set_cwd(prev_wd); + } + } +} + + + diff --git a/src/lib/compiler.h b/src/lib/compiler.h index bb2f44d..d937ecf 100644 --- a/src/lib/compiler.h +++ b/src/lib/compiler.h @@ -1,9 +1,10 @@ #define RENDER() extern "C" void render(DTree& call) -string process_html_literal(Request* context, SharedUnit* su, string content); -string preprocess_shared_unit(Request* context, SharedUnit* su); -void setup_unit_paths(Request* context, SharedUnit* su, string file_name); -void load_shared_unit(Request* context, SharedUnit* su, string file_name); -void compile_shared_unit(Request* context, SharedUnit* su, string file_name); -SharedUnit* get_shared_unit(Request* context, string file_name); -void compiler_invoke(Request* context, string file_name, DTree& call_param); +String process_html_literal(Request* context, SharedUnit* su, String content); +String preprocess_shared_unit(Request* context, SharedUnit* su); +void setup_unit_paths(Request* context, SharedUnit* su, String file_name); +void load_shared_unit(Request* context, SharedUnit* su, String file_name); +void compile_shared_unit(Request* context, SharedUnit* su, String file_name); +SharedUnit* get_shared_unit(Request* context, String file_name); +void compiler_invoke(Request* context, String file_name, DTree& call_param); + diff --git a/src/lib/dtree.cpp b/src/lib/dtree.cpp new file mode 100644 index 0000000..ab0ee48 --- /dev/null +++ b/src/lib/dtree.cpp @@ -0,0 +1,263 @@ + +void DTree::each(std::function f) +{ + switch(type) + { + case('M'): + for (auto it = _map.begin(); it != _map.end(); ++it) + { + f(it->second, it->first); + } + break; + default: + f(*this, ""); + break; + } +} + +bool DTree::is_array() +{ + return(type == 'M'); +} + +String DTree::to_string() +{ + switch(type) + { + case('S'): + return(_String); + break; + case('F'): + return(std::to_string(_float)); + break; + case('B'): + return(_bool ? "(true)" : "(false)"); + break; + case('M'): + return(""); + break; + case('P'): + return(std::to_string((u64)_ptr)); + break; + } +} + +String DTree::to_json() +{ + switch(type) + { + case('S'): + return(json_escape(_String)); + break; + case('F'): + return(std::to_string(_float)); + break; + case('B'): + return(_bool ? "true" : "false"); + break; + case('M'): + return("\"(array)\""); + break; + case('P'): + return("\"(pointer)\""); + break; + } +} + +String DTree::get_type_name() +{ + switch(type) + { + case('S'): + return("String"); + break; + case('F'): + return("f64"); + break; + case('B'): + return("bool"); + break; + case('M'): + return("array"); + break; + case('P'): + return("pointer"); + break; + } +} + +void DTree::set_type(char t) +{ + if(type != t) + { + type = t; + switch(type) + { + case('M'): + _map.clear(); + _array_index = 0; + break; + } + } +} + +void DTree::set(String s) +{ + set_type('S'); + _String = s; +} + +void DTree::set(void* p) +{ + set_type('P'); + _ptr = p; +} + +void DTree::set(f64 f) +{ + set_type('F'); + _float = f; +} + +void DTree::set_bool(bool b) +{ + set_type('B'); + _bool = b; +} + +void DTree::set(DTree source) +{ + set_type(source.type); + switch(type) + { + case('S'): + _String = source._String; + break; + case('F'): + _float = source._float; + break; + case('B'): + _bool = source._bool; + break; + case('M'): + _map = source._map; + break; + case('P'): + _ptr = source._ptr; + break; + } +} + +void DTree::set(StringMap source) +{ + set_type('M'); + for (auto it = source.begin(); it != source.end(); ++it) + { + _map[it->first] = it->second; + } +} + +DTree* DTree::key(String s) +{ + set_type('M'); + return(&_map[s]); +} + +DTree& DTree::operator [] (String s) { + set_type('M'); + return(_map[s]); +} + +void DTree::operator = (String v) { set(v); } +void DTree::operator = (f64 v) { set(v); } +void DTree::operator = (void* v) { set(v); } +void DTree::operator = (DTree v) { set(v); } +void DTree::operator = (StringMap v) { set(v); } + +void DTree::push(DTree& child) +{ + set_type('M'); + _map[std::to_string(_array_index)] = child; + _array_index += 1; +} + +DTree DTree::pop() +{ + set_type('M'); + auto last = _map.rbegin(); + DTree result = last->second; + _map.erase(last->first); + return(result); +} + +void DTree::remove(String s) +{ + set_type('M'); + _map.erase(s); +} + +void DTree::clear() +{ + set_type('M'); + _map.clear(); +} + +String to_String(DTree t) +{ + return(t.to_string()); +} + +String var_dump(DTree map, String prefix, String postfix) +{ + String result = ""; + if(!map.is_array()) + return(map.to_string()); + map.each([&] (DTree item, String key) { + result += prefix + key + ": " + item.to_string() + postfix; + if(item.is_array()) + result += var_dump(item, prefix + "\t"); + }); + return(result); +} + +String json_escape(String s) +{ + //return(String("\"")+s+"\""); + String result; + u32 i = 0; + result.append(1, '"'); + while(i < s.length()) + { + char c = s[i]; + switch(c) + { + case('\t'): + result.append("\\t"); + break; + case('\n'): + result.append("\\n"); + break; + case('"'): + result.append("\\\""); + break; + case('\r'): + result.append("\\r"); + break; + case('\\'): + result.append("\\\\"); + break; + case('\b'): + result.append("\\b"); + break; + case('\f'): + result.append("\\f"); + break; + default: + result.append(1, c); + break; + } + i += 1; + } + result.append(1, '"'); + return(result); +} diff --git a/src/lib/dtree.h b/src/lib/dtree.h index a50b2f6..51df5f2 100644 --- a/src/lib/dtree.h +++ b/src/lib/dtree.h @@ -1,31 +1,31 @@ -string json_escape(string s); +String json_escape(String s); struct DTree { char type = 'S'; - string _string; + String _String; f64 _float; s64 _array_index; bool _bool; void* _ptr; - std::map _map; + std::map _map; - void each(std::function f); + void each(std::function f); bool is_array(); - string to_string(); - string to_json(); - string get_type_name(); + String to_string(); + String to_json(); + String get_type_name(); void set_type(char t); - void set(string s); + void set(String s); void set(void* p); void set(f64 f); void set_bool(bool b); void set(DTree source); void set(StringMap source); - DTree* key(string s); - DTree& operator [] (string s); - void operator = (string v); + DTree* key(String s); + DTree& operator [] (String s); + void operator = (String v); void operator = (f64 v); void operator = (void* v); void operator = (DTree v); @@ -33,9 +33,9 @@ struct DTree { void push(DTree& child); DTree pop(); - void remove(string s); + void remove(String s); void clear(); }; -string to_string(DTree t); -string var_dump(DTree map, string prefix = "", string postfix = "\n"); +String to_String(DTree t); +String var_dump(DTree map, String prefix = "", String postfix = "\n"); diff --git a/src/lib/functionlib.cpp b/src/lib/functionlib.cpp new file mode 100644 index 0000000..6dcb6d6 --- /dev/null +++ b/src/lib/functionlib.cpp @@ -0,0 +1,375 @@ +#include "functionlib.h" + +String var_dump(StringMap map, String prefix, String postfix) +{ + String result = ""; + + for (auto it = map.begin(); it != map.end(); ++it) + { + result.append(prefix + it->first + ": " + it->second + postfix); + } + + return(result); +} + +String var_dump(StringList slist, String prefix, String postfix) +{ + String result = ""; + + for (auto& s : slist) + { + result.append(prefix + s + postfix); + } + + return(result); +} + +u8 char_to_u8(char input) +{ + if(input >= '0' && input <= '9') + return input - '0'; + if(input >= 'A' && input <= 'F') + return input - 'A' + 10; + if(input >= 'a' && input <= 'f') + return input - 'a' + 10; + return(0); +} + +u8 hex_to_u8(String src) +{ + return(char_to_u8(src[0])*16 + char_to_u8(src[1])); +} + +String trim(String raw) +{ + u32 len = raw.length(); + u32 start_pos = 0; + u32 end_pos = len - 1; + if(len == 0 || (len == 1 && isspace(raw[0]))) + return(""); + while(start_pos < len && isspace(raw[start_pos])) + start_pos++; + while(end_pos >= 0 && isspace(raw[end_pos])) + end_pos--; + if(end_pos < start_pos) + return(""); + return(raw.substr(start_pos, 1 + end_pos - start_pos)); +} + +StringList split(String str, String delim) +{ + StringList result; + int start = 0; + int end = str.find(delim); + while (end != String::npos) + { + result.push_back(str.substr(start, end - start)); + start = end + delim.size(); + end = str.find(delim, start); + } + result.push_back(str.substr(start, end - start)); + return(result); +} + +String join(StringList l, String delim) +{ + String result; + u32 i = 0; + for(auto& s : l) + { + if(i > 0) + result.append(delim); + result.append(s); + i += 1; + } + return(result); +} + +StringList filter(StringList items, std::function f) +{ + StringList new_items; + for(auto item : items) + { + if(f(item)) + new_items.push_back(item); + } + return(new_items); +} + +String html_escape(String s) +{ + String result; + + for(u32 i = 0; i < s.length(); i++) + { + char c = s[i]; + switch(c) + { + case('&'): + result.append("&"); + break; + case('<'): + result.append("<"); + break; + case('>'): + result.append(">"); + break; + case('"'): + result.append("""); + break; + default: + result.append(1, c); + break; + } + } + + return(result); +} + +String html_escape(u64 a) +{ + return(std::to_string(a)); +} + +String html_escape(f64 a) +{ + return(std::to_string(a)); +} + +u64 int_val(String s, u32 base) +{ + return(strtoll(s.c_str(), 0, base)); +} + +String nibble(String& haystack, String delim) +{ + auto idx = haystack.find(delim); + if(idx == String::npos) + { + String result = haystack; + haystack = ""; + return(result); + } + else + { + String result = haystack.substr(0, idx); + haystack = haystack.substr(idx+delim.length()); + return(result); + } +} + +String json_encode(DTree t) +{ + String result = ""; + if(t.is_array()) + { + result += "{"; + u32 count = 0; + t.each([&] (DTree item, String key) { + if(count > 0) + result += ", "; + count += 1; + result += json_escape(key) + ": " + json_encode(item); + }); + result += "}"; + } + else + { + result = t.to_json(); + } + return(result); +} + +// https://i.stack.imgur.com/SHLOB.gif +String json_decode_String(String s, u32& i, char termination_char) +{ + String result; + //echo("json_decode_String " + s.substr(i) + "\n"); + while(i < s.length()) + { + char c = s[i]; + if(c == termination_char) + { + i += 1; + //echo("json_decode_String = " + result + "\n"); + return(result); + } + else if(c == '\\') + { + i += 1; + c = s[i]; + switch(c) + { + case('t'): + result.append(1, '\t'); + break; + case('n'): + result.append(1, '\n'); + break; + case('r'): + result.append(1, '\r'); + break; + case('\\'): + result.append(1, '\\'); + break; + case('b'): + result.append(1, '\b'); + break; + case('f'): + result.append(1, '\f'); + break; + case('u'): + // todo decode + break; + default: + result.append(1, c); + break; + } + } + else + { + result.append(1, c); + } + i += 1; + } + return(result); +} + +DTree json_decode_map(String s, u32& i); + +void json_consume_space(String s, u32& i) +{ + while(i < s.length() && isspace(s[i])) + i += 1; +} + +String json_decode_keyword(String s, u32& i) +{ + String result; + json_consume_space(s, i); + while(i < s.length()) + { + char c = s[i]; + if(isalnum(c)) + { + result.append(1, c); + } + else + { + return(result); + } + i += 1; + } + return(result); +} + +String json_decode_number(String s, u32& i) +{ + String result; + json_consume_space(s, i); + while(i < s.length()) + { + char c = s[i]; + if(isdigit(c) || c == '.') + { + result.append(1, c); + } + else + { + return(result); + } + i += 1; + } + return(result); +} + +DTree json_decode_value(String s, u32& i) +{ + DTree result; + String value = ""; + json_consume_space(s, i); + char c = s[i]; + //echo("json_decode_value " + s.substr(i) + "\n"); + if(c == '"' || c == '\'') // String value + { + result.type = 'S'; + i += 1; + result._String = json_decode_String(s, i, s[i-1]); + return(result); + } + else if(isdigit(c)) + { + result.type = 'S'; + result._String = json_decode_number(s, i); + //result._float = stod(json_decode_number(s, i)); + return(result); + } + else if(c == '{') + { + i += 1; + return(json_decode_map(s, i)); + } + else + { + value = json_decode_keyword(s, i); + if(value == "true") + result.set_bool(true); + else if(value == "false") + result.set_bool(false); + else if(value == "null") + result.set(""); + return(result); + } + return(result); +} + +DTree json_decode_map(String s, u32& i) +{ + DTree result; + result.type = 'M'; + String key = ""; + json_consume_space(s, i); + //echo("json_decode_map " + s.substr(i) + "\n"); + while(i < s.length()) + { + char c = s[i]; + if(c == '}') + { + i += 1; + return(result); + } + else if(c == ',') + { + i += 1; + } + else if(c == '"' || c == '\'') + { + i += 1; + key = json_decode_String(s, i, s[i-1]); + json_consume_space(s, i); + if(s[i] != ':') + return(result); // malformed + i += 1; + DTree v = json_decode_value(s, i); + //result._map[key] = json_decode_value(s, i); + //echo("KV " + key + " = " + to_String(v) + "\n"); + //printf("map add %s (%c) \n", key.c_str(), s[i]); + result._map[key] = v; + } + else + { + // malformed + return(result); + } + json_consume_space(s, i); + } + return(result); +} + +DTree json_decode(String s) +{ + u32 i = 0; + return(json_decode_value(s, i)); +} + + diff --git a/src/lib/functionlib.h b/src/lib/functionlib.h index d5c08bf..2a67d6c 100644 --- a/src/lib/functionlib.h +++ b/src/lib/functionlib.h @@ -1,20 +1,26 @@ -string var_dump(StringMap map, string prefix = "", string postfix = "\n"); -string var_dump(StringList slist, string prefix = "", string postfix = "\n"); + u8 char_to_u8(char input); -u8 hex_to_u8(string src); -string trim(string raw); -StringList split(string str, string delim = "\n"); -string join(StringList l, string delim = "\n"); -string html_escape(string s); -string html_escape(u64 a); -string html_escape(f64 a); -u64 int_val(string s, u32 base = 10); -string nibble(string& haystack, string delim); -string json_encode(DTree t); -string json_decode_string(string s, u32& i, char termination_char = '"'); -void json_consume_space(string s, u32& i); -string json_decode_keyword(string s, u32& i); -string json_decode_number(string s, u32& i); -DTree json_decode_value(string s, u32& i); -DTree json_decode_map(string s, u32& i); -DTree json_decode(string s); +u8 hex_to_u8(String src); +u64 int_val(String s, u32 base = 10); + +String trim(String raw); +StringList split(String str, String delim = "\n"); +String join(StringList l, String delim = "\n"); +String nibble(String& haystack, String delim); +void json_consume_space(String s, u32& i); +StringList filter(StringList items, std::function f); + +String html_escape(String s); +String html_escape(u64 a); +String html_escape(f64 a); + +String json_encode(DTree t); +String json_decode_String(String s, u32& i, char termination_char = '"'); +String json_decode_keyword(String s, u32& i); +String json_decode_number(String s, u32& i); +DTree json_decode_value(String s, u32& i); +DTree json_decode_map(String s, u32& i); +DTree json_decode(String s); + +String var_dump(StringMap map, String prefix = "", String postfix = "\n"); +String var_dump(StringList slist, String prefix = "", String postfix = "\n"); diff --git a/src/lib/mysql-connector.cpp b/src/lib/mysql-connector.cpp index 2f65f45..8833f55 100644 --- a/src/lib/mysql-connector.cpp +++ b/src/lib/mysql-connector.cpp @@ -3,7 +3,7 @@ #include #include "mysql-connector.h" -bool MySQL::connect(string host, string username, string password) +bool MySQL::connect(String host, String username, String password) { connection = mysql_init(NULL); if (connection == NULL) @@ -33,9 +33,9 @@ bool MySQL::connect(string host, string username, string password) return(true); } -string MySQL::escape(string raw, char quote_char) +String MySQL::escape(String raw, char quote_char) { - string result; + String result; result.append(1, quote_char); for(u32 i = 0; i < raw.length(); i++) @@ -97,7 +97,7 @@ DTree field_to_dtree_node(char* data_ptr, MySQLFieldInfo field_info, u32 len) case(MYSQL_TYPE_NULL): break; default: - string s; + String s; s.assign(data_ptr); result.set(s); break; @@ -166,7 +166,7 @@ DTree MySQL::get_pending_result() return(result_data); } -DTree MySQL::query(string q) +DTree MySQL::query(String q) { _preload_next_error_code = mysql_query((MYSQL*)connection, q.c_str()); DTree result; @@ -175,21 +175,21 @@ DTree MySQL::query(string q) return(result); } -DTree MySQL::query(string q, StringMap params) +DTree MySQL::query(String q, StringMap params) { return(query( parse_query_parameters(q, params).c_str() )); } -string MySQL::parse_query_parameters(string query, StringMap map) +String MySQL::parse_query_parameters(String query, StringMap map) { - string result; + String result; query.append(1, ' '); u8 mode = 0; char quote; - string identifier; + String identifier; for(u32 i = 0; i < query.length(); i++) { char c = query[i]; @@ -242,11 +242,11 @@ void MySQL::disconnect() connection = NULL; } -string MySQL::error() +String MySQL::error() { if(_preload_next_error_code) { - string p = "Unknown error"; + String p = "Unknown error"; switch(_preload_next_error_code) { case(CR_COMMANDS_OUT_OF_SYNC): @@ -272,7 +272,7 @@ string MySQL::error() const char* res = mysql_error((MYSQL*)connection); if(res) { - return(string(res)); + return(String(res)); } else { diff --git a/src/lib/mysql-connector.h b/src/lib/mysql-connector.h index 84ef2cc..ab44a8d 100644 --- a/src/lib/mysql-connector.h +++ b/src/lib/mysql-connector.h @@ -1,9 +1,9 @@ struct MySQLFieldInfo { - string name; - string table; - string db; + String name; + String table; + String db; u64 length; - string def; + String def; u64 max_length; u64 flags; u32 type; @@ -17,17 +17,17 @@ struct MySQL { u32 field_count = 0; u32 row_count = 0; u64 insert_id = 0; - string statement_info = ""; + String statement_info = ""; std::vector field_info; - bool connect(string host = "localhost", string username = "root", string password = ""); + bool connect(String host = "localhost", String username = "root", String password = ""); void disconnect(); - string error(); - string escape(string raw, char quote_char = '\''); - string parse_query_parameters(string query, StringMap m); - DTree query(string q); - DTree query(string q, StringMap params); + String error(); + String escape(String raw, char quote_char = '\''); + String parse_query_parameters(String query, StringMap m); + DTree query(String q); + DTree query(String q, StringMap params); DTree get_pending_result(); }; diff --git a/src/lib/sys.cpp b/src/lib/sys.cpp new file mode 100644 index 0000000..0f2c023 --- /dev/null +++ b/src/lib/sys.cpp @@ -0,0 +1,424 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include "sys.h" + +String shell_exec(String cmd) +{ + printf("(i) shell_exec(%s)\n", cmd.c_str()); + String data; + FILE * stream; + const int max_buffer = 256; + char buffer[max_buffer]; + cmd.append(" 2>&1"); + + stream = popen(cmd.c_str(), "r"); + + if (stream) { + while (!feof(stream)) + if (fgets(buffer, max_buffer, stream) != NULL) data.append(buffer); + pclose(stream); + } + return data; +} + +String shell_escape(String raw) +{ + // FIXME + return("\"" + raw + "\""); + /* + ` U+0060 (Grave Accent) Backtick Command substitution +~ U+007E Tilde Tilde expansion +! U+0021 Exclamation mark History expansion +# U+0023 Number sign Hash Comments +$ U+0024 Dollar sign Parameter expansion +& U+0026 Ampersand Background commands +* U+002A Asterisk Filename expansion and globbing +( U+0028 Left Parenthesis Subshells +) U+0029 Right Parenthesis Subshells + U+0009 Tab (⇥) Word splitting (whitespace) +{ U+007B Left Curly Bracket Left brace Brace expansion +[ U+005B Left Square Bracket Filename expansion and globbing +| U+007C Vertical Line Vertical bar Pipelines +\ U+005C Reverse Solidus Backslash Escape character +; U+003B Semicolon Separating commands +' U+0027 Apostrophe Single quote String quoting +" U+0022 Quotation Mark Double quote String quoting with interpolation +↩ U+000A Line Feed Newline Line break +< U+003C Less than Input redirection +> U+003E Greater than Output redirection +? U+003F Question mark Filename expansion and globbing +   U+0020 Space Word splitting1 (whitespace) + */ +} + +String basename(String fn) +{ + String result; + while(fn.length() > 0) + result = nibble("/", fn); + //printf("basename(%s) %s\n", fn.c_str(), result.c_str()); + return(result); +} + +String dirname(String fn) +{ + String result; + auto seg = split(fn, "/"); + seg.pop_back(); + result = join(seg, "/"); + //printf("dirname(%s) %s seg#%i\n", fn.c_str(), result.c_str(), seg.size()); + return(result); +} + +bool mkdir(String path) +{ + shell_exec(String("mkdir -p ")+" "+shell_escape(path)); + return(true); +} + +bool file_exists(String path) +{ + std::filesystem::path fp{ path }; + return(std::filesystem::exists(fp)); +} + +String file_get_contents(String file_name) +{ + try + { + std::ifstream ifs(file_name); + String content( + (std::istreambuf_iterator(ifs) ), + (std::istreambuf_iterator() ) ); + return(content); + } + catch(std::exception e) + { + return(""); + } +} + +bool file_put_contents(String file_name, String content) +{ + try + { + std::ofstream out(file_name); + out << content; + out.close(); + return(true); + } + catch(std::exception e) + { + return(false); + } +} + +String get_cwd() +{ + return(std::filesystem::current_path()); +} + +void set_cwd(String path) +{ + chdir(path.c_str()); +} + +time_t file_mtime(String file_name) +{ + struct stat info; + if (stat(file_name.c_str(), &info) != 0) + { + return(0); + } + else + { + return(info.st_mtime); + } +} + +void unlink(String file_name) +{ + remove(file_name.c_str()); +} + +String expand_path(String path) +{ + String result; + + auto base_path = split(get_cwd(), "/"); + auto rel_path = split(path, "/"); + + for(auto& s : rel_path) + { + if(s == "..") + { + base_path.pop_back(); + } + else if(s == ".") + { + + } + else + { + base_path.push_back(s); + } + } + + return(join(base_path, "/")); +} + +f64 microtime() +{ + return ((f64)std::chrono::duration_cast( + std::chrono::high_resolution_clock::now().time_since_epoch()).count()) / 1000000; +} + +u64 time() +{ + return(std::time(0)); +} + +String date(String format, u64 timestamp) +{ + String ts; + String fmt; + if(timestamp > 0) + ts = String("-d '@")+std::to_string(timestamp)+"'"; + if(format != "") + fmt = String("+'"+format+"'"); + return(trim(shell_exec("date "+ts+" "+fmt))); +} + +String gmdate(String format, u64 timestamp) +{ + String ts; + String fmt; + if(timestamp > 0) + ts = String("-d '@")+std::to_string(timestamp)+"'"; + if(format == "RFC1123") + format = "%a, %d %b %Y %T GMT"; + if(format != "") + fmt = String("+'"+format+"'"); + return(trim(shell_exec("date -u "+ts+" "+fmt))); +} + +u64 parse_time(String time_String) +{ + return(int_val(trim(shell_exec("date -u -d '"+time_String+"' +'%s'")))); +} + +u64 socket_connect(String host, short port) +{ + + /*String addrinfo { + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + socklen_t ai_addrlen; + String sockaddr *ai_addr; + char *ai_canonname; + String addrinfo *ai_next; + };*/ + + auto sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); + if(sockfd < 0) + { + echo("SOCKET ERROR (could not open socket)\n"); + perror("SOCKET ERROR "); + return(0); + } + + struct sockaddr_in addr = {0}; + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = inet_addr(host.c_str()); + + if(connect(sockfd, (struct sockaddr*) &addr, sizeof(addr)) < 0) + { + echo("SOCKET ERROR (could not connect to address " + String(host) + ":" + std::to_string(port) + ")\n"); + perror("SOCKET ERROR "); + return(0); + } + context->resources.sockets.push_back(sockfd); + return(sockfd); +} + +void socket_close(u64 sockfd) +{ + close(sockfd); +} + +bool socket_write(u64 sockfd, String data) +{ + return(write(sockfd, data.c_str(), data.length()) >= 0); +} + +String socket_read(u64 sockfd, u32 max_length, u32 timeout) +{ + struct timeval tv; + tv.tv_sec = timeout; + tv.tv_usec = 0; + setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, (const char*)&tv, sizeof tv); + char buf[max_length+1]; + auto byte_count = recv(sockfd, buf, sizeof(buf), 0); + if(byte_count > 0) + { + buf[byte_count] = 0; + String result(buf, byte_count+1); + return(result); + } + return(""); +} + +String memcache_escape_key(String key) +{ + String result; + for(auto c : key) + { + if(isspace(c)) + c = '_'; + result.append(1, c); + } + return(result); +} + +StringList memcache_escape_keys(StringList keys) +{ + StringList result; + for(auto s : keys) + { + result.push_back(memcache_escape_key(s)); + } + return(result); +} + +u64 memcache_connect(String host, short port) +{ + return(socket_connect(host, port)); +} + +String memcache_command(u64 connection, String command) +{ + socket_write(connection, command+"\r\n"); + return(socket_read(connection)); // FIXME: do multi-chunk until END line is received! +} + +bool memcache_set(u64 connection, String key, String value, u64 expires_in) +{ + socket_write(connection, + // set KEY META_DATA EXPIRY_TIME LENGTH_IN_BYTES + String("set ") + memcache_escape_key(key) + " 0 " + std::to_string(expires_in) + " " + std::to_string(value.length()) + "\r\n" + + value + "\r\n"); + return("STORED" == trim(socket_read(connection))); +} + +bool memcache_delete(u64 connection, String key) +{ + socket_write(connection, + // set KEY META_DATA EXPIRY_TIME LENGTH_IN_BYTES + String("delete ") + memcache_escape_key(key) + "\r\n" + ); + return("DELETED" == trim(socket_read(connection))); +} + +String memcache_get(u64 connection, String key, String default_value) +{ + auto res = memcache_command(connection, String("get ")+memcache_escape_key(key)); + String t = nibble(res, " "); + if(t == "VALUE") + { + String key = nibble(res, " "); + String meta = nibble(res, " "); + u32 length = stoi(nibble(res, "\r\n")); + return(res.substr(0, length)); + } + return(default_value); +} + +StringMap memcache_get_multiple(u64 connection, StringList keys) +{ + StringMap result; + // to do: escape key String + auto res = memcache_command(connection, String("get ")+join(memcache_escape_keys(keys), " ")); + while(res.length() > 0) + { + String t = nibble(res, " "); + if(t == "VALUE") + { + String key = nibble(res, " "); + String meta = nibble(res, " "); + u32 length = stoi(nibble(res, "\r\n")); + result[key] = res.substr(0, length); + res = res.substr(length+2); + } + } + return(result); +} + +void on_segfault(int sig) +{ + void *array[10]; + size_t size; + + // get void*'s for all entries on the stack + size = backtrace(array, 10); + + // print out all the frames to stderr + fprintf(stderr, "SEG FAULT: %d:\n", sig); + backtrace_symbols_fd(array, size, STDERR_FILENO); + exit(1); +} + +struct Worker { + pid_t pid; +}; + +std::map workers; +#include +#include +#include + +void spawn_subprocess(std::function exec_after_spawn) +{ + parent_pid = getpid(); + pid_t p; + p = fork(); + if(p == 0) + { + my_pid = getpid(); + //printf("(C) child procress started, PID:%i\n", my_pid); + prctl(PR_SET_PDEATHSIG, SIGHUP); + exec_after_spawn(); + } + else + { + Worker w; + w.pid = p; + workers[w.pid] = w; + printf("(P) child procress spawned: PID %i\n", p); + } +} + +void on_child_exit(int sig) +{ + pid_t pid; + int status; + if ((pid = waitpid(-1, &status, WNOHANG)) != -1) + { + if(workers.count(pid) > 0) + { + workers.erase(pid); + printf("(P) child terminated (PID:%i)\n", pid); + //spawn_subprocess(); + } + } +} + diff --git a/src/lib/sys.h b/src/lib/sys.h index 77e127e..651a9ca 100644 --- a/src/lib/sys.h +++ b/src/lib/sys.h @@ -1,36 +1,40 @@ #include -string shell_exec(string cmd); -string shell_escape(string raw); -string basename(string fn); -string dirname(string fn); -bool mkdir(string path); -bool file_exists(string path); -string file_get_contents(string file_name); -bool file_put_contents(string file_name, string content); -string get_cwd(); -void set_cwd(string path); -time_t file_mtime(string file_name); -void unlink(string file_name); -string expand_path(string path); +String shell_exec(String cmd); +String shell_escape(String raw); +String basename(String fn); +String dirname(String fn); +bool mkdir(String path); +bool file_exists(String path); +String file_get_contents(String file_name); +bool file_put_contents(String file_name, String content); +String get_cwd(); +void set_cwd(String path); +time_t file_mtime(String file_name); +void unlink(String file_name); +String expand_path(String path); void on_segfault(int sig); f64 microtime(); u64 time(); -string date(string format = "", u64 timestamp = 0); -string gmdate(string format = "", u64 timestamp = 0); -u64 parse_time(string time_string); +String date(String format = "", u64 timestamp = 0); +String gmdate(String format = "", u64 timestamp = 0); +u64 parse_time(String time_String); -u64 socket_connect(string host, short port); +u64 socket_connect(String host, short port); void socket_close(u64 sockfd); -bool socket_write(u64 sockfd, string data); -string socket_read(u64 sockfd, u32 max_length = 1024*128, u32 timeout = 1); +bool socket_write(u64 sockfd, String data); +String socket_read(u64 sockfd, u32 max_length = 1024*128, u32 timeout = 1); -string memcache_escape_key(string key); +String memcache_escape_key(String key); StringList memcache_escape_keys(StringList keys); -u64 memcache_connect(string host = "127.0.0.1", short port = 11211); -string memcache_command(u64 connection, string command); -bool memcache_set(u64 connection, string key, string value, u64 expires_in = 60*60); -bool memcache_delete(u64 connection, string key); -string memcache_get(u64 connection, string key, string default_value = ""); +u64 memcache_connect(String host = "127.0.0.1", short port = 11211); +String memcache_command(u64 connection, String command); +bool memcache_set(u64 connection, String key, String value, u64 expires_in = 60*60); +bool memcache_delete(u64 connection, String key); +String memcache_get(u64 connection, String key, String default_value = ""); StringMap memcache_get_multiple(u64 connection, StringList keys); + +pid_t parent_pid = 0; +pid_t my_pid = 0; + diff --git a/src/lib/types.cpp b/src/lib/types.cpp new file mode 100644 index 0000000..77fe370 --- /dev/null +++ b/src/lib/types.cpp @@ -0,0 +1,62 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "types.h" + +SharedUnit::~SharedUnit() +{ + if(so_handle) + { + dlclose(so_handle); + } +} + +String nibble(String div, String& haystack) +{ + auto pos = haystack.find(div); + if(pos == String::npos) + { + auto result = haystack; + haystack.clear(); + return(result); + } + else + { + auto result = haystack.substr(0, pos); + haystack.erase(0, pos+div.length()); + return(result); + } +} + +void Request::invoke(String file_name) +{ + DTree call_param; + compiler_invoke(this, file_name, call_param); +} + +void Request::invoke(String file_name, DTree& call_param) +{ + compiler_invoke(this, file_name, call_param); +} + +void Request::print(String s) +{ + out.append(s); +} + +Request::~Request() +{ + for(auto& sockfd : resources.sockets) + close(sockfd); +} diff --git a/src/lib/types.h b/src/lib/types.h index b156815..57a32d5 100644 --- a/src/lib/types.h +++ b/src/lib/types.h @@ -4,64 +4,35 @@ #include #include -typedef std::string string; - typedef unsigned char u8; typedef signed char s8; - typedef unsigned short u16; typedef signed short s16; - typedef unsigned int u32; typedef signed int s32; typedef float f32; - typedef double f64; typedef unsigned long long u64; typedef long long s64; -//#define to_string(a) std::to_string(a) -#define echo(s) context->print(s) +typedef std::string String; -typedef std::map StringMap; -typedef std::list StringList; - -struct Request; -struct DTree; - -typedef void (*call_handler)(DTree& call_param); -typedef void (*request_handler)(Request* request); - -string to_string(u64 v) { return(std::to_string(v)); } -string to_string(s64 v) { return(std::to_string(v)); } -string to_string(f64 v) { return(std::to_string(v)); } - -struct ServerSettings { - - string BIN_DIRECTORY = "work"; - string COMPILE_SCRIPT = "scripts/compile"; - string LIT_ESC = "3d5b5_1"; - string CONTENT_TYPE = "text/html; charset=utf-8"; - string SOCKET_PATH = "/run/uce.sock"; - string TMP_UPLOAD_PATH = "/tmp/uce/uploads"; - string SESSION_PATH = "/tmp/uce/sessions"; - string COMPILER_SYS_PATH = "."; - u32 LISTEN_PORT = 9993; - u64 SESSION_TIME = 60*60*24*30; - -}; +#define DEBUG_MEMORY_OFF; +#define GLOBAL_ARENA_ALLOCATOR; struct MemoryArena { - void* data; + u8* data; u64 size = 0; u64 capacity = 0; + String name = "unnamed"; - MemoryArena(u64 cap) + MemoryArena(u64 cap, String _name = "unnamed") { + name = _name; capacity = cap; - printf("(i) memory arena %p created with capacity of %i bytes\n", this, capacity); - data = malloc(cap); + printf("(i) memory arena %s created with capacity of %llu bytes\n", name.c_str(), capacity); + data = (u8*)malloc(cap); } ~MemoryArena() @@ -71,56 +42,132 @@ struct MemoryArena { void clear() { - printf("(i) memory arena %p cleared after high mark of %i bytes\n", this, size); + #ifdef DEBUG_MEMORY + printf("(i) memory arena %s cleared after high mark of %llu bytes\n", name.c_str(), size); + #endif size = 0; } void* get(u64 size_needed) { - if(size_needed + size >= capacity) + u64 size_aligned = 8 + (8 * ((size_needed) / 8)); + u8* result = data + size; + if(size_aligned + size >= capacity) { - printf("(!) memory arena %p capacity (%i) exceeded by allocation of %i bytes\n", this, capacity, size_needed); + printf("(!) memory arena '%s' capacity (%llu) exceeded %llu/%llu + %llu >= %llu\n", + name.c_str(), capacity, size_needed, size_aligned, size, capacity); return(0); } - void* result = (char *)data + size; - size += size_needed; + size += size_aligned; + #ifdef DEBUG_MEMORY + printf("(i) memory arena %s [+%llu]:%p alloc %llu/%llu bytes\n", name.c_str(), size, result, size_needed, size_aligned); + #endif return(result); } }; +MemoryArena* current_memory_arena = 0; + +void switch_to_system_alloc() +{ + current_memory_arena = 0; +} + +void switch_to_arena(MemoryArena* a) +{ + current_memory_arena = a; +} + +#ifdef GLOBAL_ARENA_ALLOCATOR +void * operator new(decltype(sizeof(0)) n) noexcept(false) +{ + if(current_memory_arena) + { + return(current_memory_arena->get(n)); + } + else + { + return(malloc(n)); + } +} + +void operator delete(void * p) throw() +{ + if(current_memory_arena) + { + + } + else + { + free(p); + } +} +#endif + +//#define to_String(a) std::to_String(a) +#define echo(s) context->print(s) + +typedef std::map StringMap; +typedef std::vector StringList; + +struct Request; +struct DTree; + +typedef void (*call_handler)(DTree& call_param); +typedef void (*request_handler)(Request* request); + +String to_string(s64 v) { return(std::to_string(v)); } + +struct ServerSettings { + + String BIN_DIRECTORY = "work"; + String COMPILE_SCRIPT = "scripts/compile"; + String LIT_ESC = "3d5b5_1"; + String CONTENT_TYPE = "text/html; charset=utf-8"; + String SOCKET_PATH = "/run/uce.sock"; + String TMP_UPLOAD_PATH = "/tmp/uce/uploads"; + String SESSION_PATH = "/tmp/uce/sessions"; + String COMPILER_SYS_PATH = "."; + u32 LISTEN_PORT = 9993; + u64 SESSION_TIME = 60*60*24*30; + u32 WORKER_COUNT = 4; + u32 MAX_MEMORY = 1024*1024*16; + +}; + struct SharedUnit { - string file_name; - string so_name; + String file_name; + String so_name; - string src_path; - string bin_path; - string pre_path; - string src_file_name; - string bin_file_name; - string pre_file_name; + String src_path; + String bin_path; + String pre_path; + String src_file_name; + String bin_file_name; + String pre_file_name; void* so_handle; request_handler on_setup; call_handler on_render; - string compiler_messages; + String compiler_messages; time_t last_compiled; ~SharedUnit(); }; struct UploadedFile { - string file_name; - string tmp_name; + String file_name; + String tmp_name; u32 size; }; struct ServerState { - std::map units; + std::map units; ServerSettings config; }; @@ -132,13 +179,13 @@ struct URI { }; -string nibble(string div, string& haystack); +String nibble(String div, String& haystack); template -string to_hex(ITYPE w, size_t hex_len = sizeof(ITYPE)<<1) +String to_hex(ITYPE w, size_t hex_len = sizeof(ITYPE)<<1) { static const char* digits = "0123456789ABCDEF"; - string rc(hex_len,'0'); + String rc(hex_len,'0'); for (size_t i=0, j=(hex_len-1)*4 ; i>j) & 0x0f]; return(rc); @@ -146,7 +193,7 @@ string to_hex(ITYPE w, size_t hex_len = sizeof(ITYPE)<<1) #include "dtree.h" -void compiler_invoke(Request* context, string file_name, DTree& call_param); +void compiler_invoke(Request* context, String file_name, DTree& call_param); struct Request { @@ -160,19 +207,18 @@ struct Request { DTree var; - string session_id = ""; - string session_name = ""; + String session_id = ""; + String session_name = ""; std::vector uploaded_files; StringMap header; StringList set_cookies; - MemoryArena* out_arena; - MemoryArena* memory_arena; + MemoryArena* request_arena; - std::string in; - std::string out; - std::string err; + String in; + String out; + String err; f64 time_init; f64 time_start; @@ -183,9 +229,9 @@ struct Request { std::vector mysql_connections; } resources; - void invoke(string file_name); - void invoke(string file_name, DTree& call_param); - void print(string s); + void invoke(String file_name); + void invoke(String file_name, DTree& call_param); + void print(String s); ~Request(); }; diff --git a/src/lib/uce_lib.cpp b/src/lib/uce_lib.cpp new file mode 100644 index 0000000..15777d0 --- /dev/null +++ b/src/lib/uce_lib.cpp @@ -0,0 +1,9 @@ + + +#include "types.cpp" +#include "dtree.cpp" +#include "functionlib.cpp" +#include "sys.cpp" +#include "uri.cpp" +#include "compiler.cpp" +#include "mysql-connector.cpp" diff --git a/src/lib/uce_lib.h b/src/lib/uce_lib.h new file mode 100644 index 0000000..04c65b0 --- /dev/null +++ b/src/lib/uce_lib.h @@ -0,0 +1,13 @@ + +#include "types.h" +#include "functionlib.h" +#include "sys.h" +#include "uri.h" +#include "compiler.h" +#include "mysql-connector.h" + +extern "C" void set_current_request(Request* _request) +{ + context = _request; + signal(SIGSEGV, on_segfault); +} diff --git a/src/lib/uri.cpp b/src/lib/uri.cpp new file mode 100644 index 0000000..481190a --- /dev/null +++ b/src/lib/uri.cpp @@ -0,0 +1,316 @@ +#include "uri.h" + +String var_dump(URI uri, String prefix, String postfix) +{ + return( + prefix + "URI Parts: " + postfix + + var_dump(uri.parts, prefix+" ", postfix)+ + prefix + " Query: " + postfix + + var_dump(uri.query, prefix+" ", postfix) + ); +} + +String uri_decode(String q) +{ + String result; + for(u32 i = 0; i < q.length(); i++) + { + char c = q[i]; + if(c == '%' && q[i+1] != '%') + { + result.append(1, hex_to_u8(q.substr(i+1, 2))); + i += 2; + } + else + { + result.append(1, c); + } + } + return(result); +} + +String uri_encode(String q) +{ + String result; + for(u32 i = 0; i < q.length(); i++) + { + char c = q[i]; + if(isalnum(c) || c == '~' || c == '.' || c == '_' || c == '-') + result.append(1, c); + else + { + result.append(1, '%'); + result.append(to_hex(c)); + } + } + return(result); +} + +StringMap parse_query(String q) +{ + StringMap result; + if(q.length() == 0) + return(result); + + bool is_key = true; + String key = ""; + String value = ""; + for (char &c: q) + { + if(c == '=') + { + is_key = !is_key; + } + else if(c == '&') + { + result[uri_decode(key)] = uri_decode(value); + key = ""; + value = ""; + is_key = true; + } + else if(is_key) + { + key.append(1, c); + } + else + { + value.append(1, c); + } + } + + result[uri_decode(key)] = uri_decode(value); + + return(result); +} + +String encode_query(StringMap map) +{ + String result; + + for (auto it = map.begin(); it != map.end(); ++it) + { + if(result.length() > 0) + result.append(1, '&'); + result.append(uri_encode(it->first) + "=" + uri_encode(it->second)); + } + + return(result); +} + +StringMap parse_multipart(String q, String boundary, std::vector& uploaded_files) +{ + StringMap result; + + auto i = boundary.length(); + while(i < q.length()) + { + auto end_pos = q.find(boundary, i); + if(end_pos != String::npos) + { + String field = q.substr(i, end_pos - i); + nibble(":", field); + String ftype = trim(nibble(";", field)); + if(ftype == "form-data") + { + nibble("=\"", field); + String field_name = nibble("\"", field); + result[field_name] = field.substr(4, field.length()-6); + } + else if(ftype == "attachment") + { + nibble("=\"", field); + UploadedFile f; + f.tmp_name = context->server->config.TMP_UPLOAD_PATH + std::to_string(rand()); + f.file_name = nibble("\"", field); + String bin = field.substr(4, field.length()-6); + f.size = bin.length(); + file_put_contents(f.tmp_name, bin); + uploaded_files.push_back(f); + } + } + else + { + // we're done + end_pos = q.length(); + } + i = end_pos + boundary.length(); + } + + return(result); +} + +URI parse_uri(String uri_String) +{ + URI result; + + u8 state = 0; + String current = ""; + char expect = 0; + + result.parts["raw"] = uri_String; + + String part_names[] = { + "scheme", + "host", + "port", + "path", + "query", + "fragment", + }; + + if(uri_String[0] == '/') + state = 3; + + for (char &c: uri_String) + { + bool append_it = true; + + if(expect && expect != c) + { + result.parts["error"] = String("\'") + c + String("\' expected"); + result.parts["error_parsing"] = current; + result.parts["error_part"] = part_names[state]; + return(result); + } + expect = 0; + + switch(state) + { + case(0): // scheme + if(c == ':') + { + result.parts[part_names[state]] = current; + append_it = false; + current = ""; + state = 1; + } + break; + case(1): // host name + if(c == '/') + { + if(current == "") + { + append_it = false; + break; + } + result.parts[part_names[state]] = current; + append_it = false; + current = ""; + state = 3; + expect = '/'; + } + else if(c == ':') + { + result.parts[part_names[state]] = current; + append_it = false; + current = ""; + state = 2; + } + break; + case(2): // port + if(c == '/') + { + result.parts[part_names[state]] = current; + append_it = false; + current = ""; + state = 3; + expect = '/'; + } + break; + case(3): // path + if(c == '/' && current == "") + { + append_it = false; + break; + } + if(c == '?') + { + result.parts[part_names[state]] = current; + append_it = false; + current = ""; + state = 4; + } + break; + case(4): // query + if(c == '#') + { + result.parts[part_names[state]] = current; + append_it = false; + current = ""; + state = 5; + } + break; + } + + if(append_it) + current.append(1, c); + } + + result.parts[part_names[state]] = current; + + result.query = parse_query(result.parts["query"]); + + return(result); +} + +void set_cookie( + String name, String value, + u64 expires, String path, String domain, + bool secure, bool http_only) +{ + String cookie = "Set-Cookie: "; + cookie.append(uri_encode(name) + "=" + uri_encode(value)); + if(expires > 0) + cookie.append(String("; Expires=") + gmdate("RFC1123", expires)); + context->set_cookies.push_back(cookie); + context->cookies[name] = value; +} + +StringMap parse_cookies(String cookie_String) +{ + StringMap result; + while(cookie_String.length() > 0) + { + String key = trim(nibble("=", cookie_String)); + String value = nibble(";", cookie_String); + result[key] = value; + } + return(result); +} + +String make_session_id() +{ + return(to_hex(rand())+to_hex(rand())+to_hex(rand())+to_hex(rand())); +} + +StringMap load_session_data(String session_id) +{ + return(parse_query(file_get_contents(context->server->config.SESSION_PATH + "/" + session_id))); +} + +void save_session_data(String session_id, StringMap data) +{ + file_put_contents(context->server->config.SESSION_PATH + "/" + session_id, encode_query(data)); +} + +String session_start(String session_name) +{ + if(context->cookies[session_name].length() == 0) + { + set_cookie(session_name, make_session_id(), time() + context->server->config.SESSION_TIME); + } + context->session_id = context->cookies[session_name]; + context->session_name = session_name; + context->session = load_session_data(context->session_id); + return(context->session_id); +} + +void session_destroy(String session_name) +{ + if(context->cookies[session_name].length() > 0) + { + set_cookie(session_name, "", time() - context->server->config.SESSION_TIME); + context->session.clear(); + context->session_id = ""; + } +} diff --git a/src/lib/uri.h b/src/lib/uri.h index b4bad3b..3ecbeab 100644 --- a/src/lib/uri.h +++ b/src/lib/uri.h @@ -1,19 +1,19 @@ -string var_dump(URI uri, string prefix = "", string postfix = "\n"); -string uri_decode(string q); -string uri_encode(string q); -StringMap parse_query(string q); -string encode_query(StringMap map); -StringMap parse_multipart(string q, string boundary, std::vector& uploaded_files); -URI parse_uri(string uri_string); +String var_dump(URI uri, String prefix = "", String postfix = "\n"); +String uri_decode(String q); +String uri_encode(String q); +StringMap parse_query(String q); +String encode_query(StringMap map); +StringMap parse_multipart(String q, String boundary, std::vector& uploaded_files); +URI parse_uri(String uri_String); void set_cookie( - string name, string value = "", - u64 expires = 0, string path = "/", string domain = "", + String name, String value = "", + u64 expires = 0, String path = "/", String domain = "", bool secure = false, bool http_only = true); -StringMap parse_cookies(string cookie_string); -string make_session_id(); -StringMap load_session_data(string session_id); -void save_session_data(string session_id, StringMap data); -string session_start(string session_name = "uce-session"); -void session_destroy(string session_name = "uce-session"); +StringMap parse_cookies(String cookie_String); +String make_session_id(); +StringMap load_session_data(String session_id); +void save_session_data(String session_id, StringMap data); +String session_start(String session_name = "uce-session"); +void session_destroy(String session_name = "uce-session"); diff --git a/src/linux_fastcgi.cpp b/src/linux_fastcgi.cpp index a670345..faa33b5 100644 --- a/src/linux_fastcgi.cpp +++ b/src/linux_fastcgi.cpp @@ -4,13 +4,14 @@ FastCGIServer server; ServerState server_state; +MemoryArena* request_arena; int handle_request(FastCGIRequest& request) { // This is always the first event to occur. It occurs when the // server receives all parameters. There may be more data coming on the // standard input stream. request.time_init = microtime(); - if (request.params.count("REQUEST_URI")) + if (request.params.count("REQUEST_URI")) return 0; // OK, continue processing else return 1; // stop processing and return error code @@ -18,7 +19,7 @@ int handle_request(FastCGIRequest& request) { int handle_data(FastCGIRequest& request) { // This event occurs when data is received on the standard input stream. - // A simple string is used to hold the input stream, so it is the + // A simple String is used to hold the input stream, so it is the // responsibility of the application to remember which data it has // processed. The application may modify it; new data will be appended // to it by the server. The same goes for the output and error streams: @@ -33,9 +34,6 @@ int handle_data(FastCGIRequest& request) { return 0; // still OK } -MemoryArena output_buffer(1024*1024*16); -MemoryArena memory_buffer(1024*1024*16); - int handle_complete(FastCGIRequest& request) { // The event handler can also be a class member function. This // event occurs when the parameters and standard input streams are @@ -44,26 +42,22 @@ int handle_complete(FastCGIRequest& request) { context = &request; request.server = &server_state; - request.out_arena = &output_buffer; - request.out_arena->clear(); - request.memory_arena = &memory_buffer; - request.memory_arena->clear(); request.time_start = microtime(); request.header["Content-Type"] = context->server->config.CONTENT_TYPE; - request.get = parse_query(request.params["QUERY_STRING"]); + request.get = parse_query(request.params["QUERY_String"]); if(request.params["HTTP_COOKIE"].length() > 0) request.cookies = parse_cookies(request.params["HTTP_COOKIE"]); - string ct_info = request.params["CONTENT_TYPE"]; - string ct_type = nibble(";", ct_info); + String ct_info = request.params["CONTENT_TYPE"]; + String ct_type = nibble(";", ct_info); if(request.params["REQUEST_METHOD"] == "POST") { if(ct_type == "multipart/form-data") { nibble("boundary=", ct_info); - request.post = parse_multipart(request.in, string("--")+ct_info, request.uploaded_files); + request.post = parse_multipart(request.in, String("--")+ct_info, request.uploaded_files); } else { @@ -72,9 +66,12 @@ int handle_complete(FastCGIRequest& request) { } // printf("(i) request ready\n"); + //current_memory_arena = request_arena; + //request_arena->clear(); request.invoke(request.params["SCRIPT_FILENAME"]); + switch_to_system_alloc(); - string headers = + String headers = var_dump(request.header, "", "\r\n") + var_dump(request.set_cookies, "", "\r\n") + "\r\n"; @@ -91,7 +88,8 @@ int handle_complete(FastCGIRequest& request) { cleanup_mysql_connections(); request.time_end = microtime(); - printf("(r) %s\t%0.6fs\t%0.1fkB\n", + printf("(r) pid:%i\t%s\t%0.6fs\t%0.1fkB\n", + my_pid, request.params["REQUEST_URI"].c_str(), request.time_end - request.time_start, (f32)(request.out.length()/1024) @@ -100,20 +98,30 @@ int handle_complete(FastCGIRequest& request) { return 0; } -int main(int argc, char** argv) +void listen_for_connections() { - signal(SIGSEGV, on_segfault); - srand(time()); - - // Set up our request handlers server.request_handler(&handle_request); server.data_handler(&handle_data); server.complete_handler(&handle_complete); + request_arena = new MemoryArena(server_state.config.MAX_MEMORY, "request"); + for(;;) + { + server.process(); + } +} + +int main(int argc, char** argv) +{ + printf("(P) Starting parent server PID:%i\n", getpid()); + + signal(SIGSEGV, on_segfault); + signal(SIGCHLD, on_child_exit); + srand(time()); //if(server_state.config.COMPILER_SYS_PATH == "") server_state.config.COMPILER_SYS_PATH = get_cwd(); - printf("MySQL client version: %s\n", mysql_get_client_info()); + // printf("MySQL client version: %s\n", mysql_get_client_info()); printf("Compiler base path: %s\n", server_state.config.COMPILER_SYS_PATH.c_str()); @@ -143,7 +151,15 @@ int main(int argc, char** argv) { std::cout << e.what(); }*/ - server.process_forever(); + + for(;;) + { + while(workers.size() < server_state.config.WORKER_COUNT) + { + spawn_subprocess(listen_for_connections); + } + sleep(1); + } return 0; } diff --git a/test/dtree.uce b/test/dtree.uce index 053641b..004007f 100644 --- a/test/dtree.uce +++ b/test/dtree.uce @@ -15,17 +15,17 @@ RENDER()
diff --git a/test/hello.uce b/test/hello.uce index b927a87..4f95998 100644 --- a/test/hello.uce +++ b/test/hello.uce @@ -3,7 +3,6 @@ void show_stuff() { //context->header["Content-Type"] = "text/plain"; - //echo(to_string(time())+"\n"); Mwahahaaha diff --git a/test/index.uce b/test/index.uce index b136b90..f47d7c5 100644 --- a/test/index.uce +++ b/test/index.uce @@ -24,7 +24,14 @@ RENDER()
  • Memcached
  • MySQL Connector
  • +
    + String test; + test.assign(context->out); + } diff --git a/test/json.uce b/test/json.uce index 0d9bde8..ffcea99 100644 --- a/test/json.uce +++ b/test/json.uce @@ -28,7 +28,7 @@ RENDER() t["g"]["x"]["y4"] = "Ünicödä"; t["l"] = context->params; - string j; + String j; echo(j = json_encode(t)); ?> diff --git a/test/mysql.uce b/test/mysql.uce index bd53a18..58f1437 100644 --- a/test/mysql.uce +++ b/test/mysql.uce @@ -13,7 +13,7 @@ RENDER()
    
     
     
    -	string action = context->get["action"];
    +	String action = context->get["action"];
     	if(context->cookies["uce-session"].length() > 0)
     		session_start();
     	if(action == "start")
    diff --git a/todo.txt b/todo.txt
    index 9253445..03d7dbd 100644
    --- a/todo.txt
    +++ b/todo.txt
    @@ -1,17 +1,17 @@
     Library
     =================================
    -- mysql
     - sha1 / md5 / meow
     
     
     Bugs
     =================================
     - shell_escape()
    -- we crash on illegal paths
     
     
     Framework
     =================================
    +- Locks for file i/o
    +- Locks for compiler
     - Check: File Upload
     - Proxy mode
     - WebSockets