diff options
99 files changed, 39390 insertions, 188 deletions
diff --git a/.travis.yml b/.travis.yml index 9df2388b8e..552a4c44b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,6 +8,7 @@ env: global: - SCONS_CACHE=$HOME/.scons_cache - SCONS_CACHE_LIMIT=1024 + - OPTIONS="verbose=yes progress=no openmp=no gdnative_wrapper=yes" cache: directories: @@ -18,10 +19,10 @@ matrix: - env: STATIC_CHECKS=yes os: linux compiler: clang - - env: GODOT_TARGET=x11 TOOLS=yes CACHE_NAME=${GODOT_TARGET}-gcc-tools + - env: GODOT_TARGET=x11 TOOLS=yes CACHE_NAME=${GODOT_TARGET}-gcc-tools OPTIONS="${OPTIONS} builtin_openssl=yes" os: linux compiler: gcc - - env: GODOT_TARGET=x11 TOOLS=no CACHE_NAME=${GODOT_TARGET}-clang + - env: GODOT_TARGET=x11 TOOLS=no CACHE_NAME=${GODOT_TARGET}-clang OPTIONS="${OPTIONS} builtin_openssl=yes" os: linux compiler: clang #- env: GODOT_TARGET=windows TOOLS=yes CACHE_NAME=${GODOT_TARGET}-gcc-tools @@ -90,5 +91,5 @@ script: - if [ "$STATIC_CHECKS" = "yes" ]; then sh ./misc/travis/clang-format.sh; else - scons -j2 CC=$CC CXX=$CXX platform=$GODOT_TARGET TOOLS=$TOOLS verbose=yes progress=no openmp=no gdnative_wrapper=yes; + scons -j2 CC=$CC CXX=$CXX platform=$GODOT_TARGET TOOLS=$TOOLS $OPTIONS; fi diff --git a/core/math/triangulate.cpp b/core/math/triangulate.cpp index 957e16f92c..5bae74ac7e 100644 --- a/core/math/triangulate.cpp +++ b/core/math/triangulate.cpp @@ -74,7 +74,7 @@ bool Triangulate::is_inside_triangle(real_t Ax, real_t Ay, cCROSSap = cx * apy - cy * apx; bCROSScp = bx * cpy - by * cpx; - return ((aCROSSbp >= 0.0) && (bCROSScp >= 0.0) && (cCROSSap >= 0.0)); + return ((aCROSSbp > 0.0) && (bCROSScp > 0.0) && (cCROSSap > 0.0)); }; bool Triangulate::snip(const Vector<Vector2> &p_contour, int u, int v, int w, int n, const Vector<int> &V) { diff --git a/core/script_debugger_local.cpp b/core/script_debugger_local.cpp index 0da377453e..c0e115e300 100644 --- a/core/script_debugger_local.cpp +++ b/core/script_debugger_local.cpp @@ -291,7 +291,8 @@ void ScriptDebuggerLocal::profiling_end() { void ScriptDebuggerLocal::send_message(const String &p_message, const Array &p_args) { - print_line("MESSAGE: '" + p_message + "' - " + String(Variant(p_args))); + // This needs to be cleaned up entirely. + // print_line("MESSAGE: '" + p_message + "' - " + String(Variant(p_args))); } void ScriptDebuggerLocal::send_error(const String &p_func, const String &p_file, int p_line, const String &p_err, const String &p_descr, ErrorHandlerType p_type, const Vector<ScriptLanguage::StackInfo> &p_stack_info) { diff --git a/core/string_buffer.h b/core/string_buffer.h index f0ead66bb8..b148e45544 100644 --- a/core/string_buffer.h +++ b/core/string_buffer.h @@ -32,9 +32,10 @@ #define STRING_BUFFER_H #include "ustring.h" +#include <string.h> +template <int SHORT_BUFFER_SIZE = 64> class StringBuffer { - static const int SHORT_BUFFER_SIZE = 64; CharType short_buffer[SHORT_BUFFER_SIZE]; String buffer; @@ -80,4 +81,83 @@ public: } }; +template <int SHORT_BUFFER_SIZE> +StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(CharType p_char) { + reserve(string_length + 2); + current_buffer_ptr()[string_length++] = p_char; + return *this; +} + +template <int SHORT_BUFFER_SIZE> +StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const String &p_string) { + return append(p_string.c_str()); +} + +template <int SHORT_BUFFER_SIZE> +StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const char *p_str) { + int len = strlen(p_str); + reserve(string_length + len + 1); + + CharType *buf = current_buffer_ptr(); + for (const char *c_ptr = p_str; *c_ptr; ++c_ptr) { + buf[string_length++] = *c_ptr; + } + return *this; +} + +template <int SHORT_BUFFER_SIZE> +StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::append(const CharType *p_str, int p_clip_to_len) { + int len = 0; + while ((p_clip_to_len < 0 || len < p_clip_to_len) && p_str[len]) { + ++len; + } + reserve(string_length + len + 1); + memcpy(&(current_buffer_ptr()[string_length]), p_str, len * sizeof(CharType)); + string_length += len; + + return *this; +} + +template <int SHORT_BUFFER_SIZE> +StringBuffer<SHORT_BUFFER_SIZE> &StringBuffer<SHORT_BUFFER_SIZE>::reserve(int p_size) { + if (p_size < SHORT_BUFFER_SIZE || p_size < buffer.size()) + return *this; + + bool need_copy = string_length > 0 && buffer.empty(); + buffer.resize(next_power_of_2(p_size)); + if (need_copy) { + memcpy(buffer.ptrw(), short_buffer, string_length * sizeof(CharType)); + } + + return *this; +} + +template <int SHORT_BUFFER_SIZE> +int StringBuffer<SHORT_BUFFER_SIZE>::length() const { + return string_length; +} + +template <int SHORT_BUFFER_SIZE> +String StringBuffer<SHORT_BUFFER_SIZE>::as_string() { + current_buffer_ptr()[string_length] = '\0'; + if (buffer.empty()) { + return String(short_buffer); + } else { + buffer.resize(string_length + 1); + return buffer; + } +} + +template <int SHORT_BUFFER_SIZE> +double StringBuffer<SHORT_BUFFER_SIZE>::as_double() { + current_buffer_ptr()[string_length] = '\0'; + return String::to_double(current_buffer_ptr()); +} + +template <int SHORT_BUFFER_SIZE> +int64_t StringBuffer<SHORT_BUFFER_SIZE>::as_int() { + current_buffer_ptr()[string_length] = '\0'; + return String::to_int(current_buffer_ptr()); +} + #endif diff --git a/core/variant_parser.cpp b/core/variant_parser.cpp index 54edb02347..446aee286d 100644 --- a/core/variant_parser.cpp +++ b/core/variant_parser.cpp @@ -178,7 +178,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri }; case '#': { - StringBuffer color_str; + StringBuffer<> color_str; color_str += '#'; while (true) { CharType ch = p_stream->get_char(); @@ -299,7 +299,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri if (cchar == '-' || (cchar >= '0' && cchar <= '9')) { //a number - StringBuffer num; + StringBuffer<> num; #define READING_SIGN 0 #define READING_INT 1 #define READING_DEC 2 @@ -378,7 +378,7 @@ Error VariantParser::get_token(Stream *p_stream, Token &r_token, int &line, Stri } else if ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_') { - StringBuffer id; + StringBuffer<> id; bool first = true; while ((cchar >= 'A' && cchar <= 'Z') || (cchar >= 'a' && cchar <= 'z') || cchar == '_' || (!first && cchar >= '0' && cchar <= '9')) { diff --git a/doc/classes/@GDScript.xml b/doc/classes/@GDScript.xml index d4bd937f49..4e9a6a5fc0 100644 --- a/doc/classes/@GDScript.xml +++ b/doc/classes/@GDScript.xml @@ -608,6 +608,7 @@ <description> Parse JSON text to a Variant (use [method typeof] to check if it is what you expect). Be aware that the JSON specification does not define integer or float types, but only a number type. Therefore, parsing a JSON text will convert all numerical values to [float] types. + Note that JSON objects do not preserve key order like Godot dictionaries, thus you should not rely on keys being in a certain order if a dictionary is constructed from JSON. In contrast, JSON arrays retain the order of their elements: [codeblock] p = parse_json('["a", "b", "c"]') if typeof(p) == TYPE_ARRAY: diff --git a/doc/classes/JSON.xml b/doc/classes/JSON.xml index e69c05c3df..078c293fc0 100644 --- a/doc/classes/JSON.xml +++ b/doc/classes/JSON.xml @@ -4,7 +4,7 @@ Helper class for parsing JSON data. </brief_description> <description> - Helper class for parsing JSON data. For usage example, see [JSONParseResult]. + Helper class for parsing JSON data. For usage example and other important hints, see [JSONParseResult]. </description> <tutorials> </tutorials> diff --git a/doc/classes/JSONParseResult.xml b/doc/classes/JSONParseResult.xml index 424720a871..0e8d9b66fa 100644 --- a/doc/classes/JSONParseResult.xml +++ b/doc/classes/JSONParseResult.xml @@ -24,7 +24,8 @@ </member> <member name="result" type="Variant" setter="set_result" getter="get_result"> A [Variant] containing the parsed JSON. Use typeof() to check if it is what you expect. For example, if JSON source starts with curly braces ([code]{}[/code]) a [Dictionary] will be returned, if JSON source starts with braces ([code][][/code]) an [Array] will be returned. - [i]Be aware that the JSON specification does not define integer or float types, but only a number type. Therefore, parsing a JSON text will convert all numerical values to float types.[/i] + [i]Be aware that the JSON specification does not define integer or float types, but only a number type. Therefore, parsing a JSON text will convert all numerical values to float types. + Note that JSON objects do not preserve key order like Godot dictionaries, thus you should not rely on keys being in a certain order if a dictionary is constructed from JSON. In contrast, JSON arrays retain the order of their elements:[/i] [codeblock] p = JSON.parse('["hello", "world", "!"]') if typeof(p) == TYPE_ARRAY: diff --git a/doc/classes/KinematicBody.xml b/doc/classes/KinematicBody.xml index 2c22ae4f93..1735501a47 100644 --- a/doc/classes/KinematicBody.xml +++ b/doc/classes/KinematicBody.xml @@ -85,7 +85,7 @@ [code]linear_velocity[/code] is a value in pixels per second. Unlike in for example [method move_and_collide], you should [i]not[/i] multiply it with [code]delta[/code] — this is done by the method. [code]floor_normal[/code] is the up direction, used to determine what is a wall and what is a floor or a ceiling. If set to the default value of [code]Vector3(0, 0, 0)[/code], everything is considered a wall. This is useful for topdown games. If the body is standing on a slope and the horizontal speed (relative to the floor's speed) goes below [code]slope_stop_min_velocity[/code], the body will stop completely. This prevents the body from sliding down slopes when you include gravity in [code]linear_velocity[/code]. When set to lower values, the body will not be able to stand still on steep slopes. - If the body collides, it will change direction a maximum of [code]max_bounces[/code] times before it stops. + If the body collides, it will change direction a maximum of [code]max_slides[/code] times before it stops. [code]floor_max_angle[/code] is the maximum angle (in radians) where a slope is still considered a floor (or a ceiling), rather than a wall. The default value equals 45 degrees. Returns the movement that remained when the body stopped. To get more detailed information about collisions that occurred, use [method get_slide_collision]. </description> diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 98991cd7c0..bec6d581f8 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -339,7 +339,7 @@ void EditorNode::_notification(int p_what) { if (ScriptEditor::get_singleton()->get_debugger()->is_visible()) bottom_panel->add_style_override("panel", gui_base->get_stylebox("BottomPanelDebuggerOverride", "EditorStyles")); - //_update_icons + // update_icons for (int i = 0; i < singleton->main_editor_buttons.size(); i++) { Ref<Texture> icon = singleton->main_editor_buttons[i]->get_icon(); @@ -709,7 +709,7 @@ void EditorNode::_dialog_display_load_error(String p_file, Error p_error) { case ERR_CANT_OPEN: { - accept->set_text(vformat(TTR("Can't open '%s'."), p_file.get_file())); + accept->set_text(vformat(TTR("Can't open '%s'. The file could have been moved or deleted."), p_file.get_file())); } break; case ERR_PARSE_ERROR: { @@ -1110,7 +1110,7 @@ void EditorNode::_dialog_action(String p_file) { if (res.is_null()) { current_option = -1; - accept->get_ok()->set_text("ok :("); + accept->get_ok()->set_text("Ugh"); accept->set_text(TTR("Failed to load resource.")); return; }; @@ -1145,6 +1145,7 @@ void EditorNode::_dialog_action(String p_file) { _save_default_environment(); _save_scene_with_preview(p_file, scene_idx); + _add_to_recent_scenes(p_file); if (scene_idx != -1) _discard_changes(); @@ -1919,7 +1920,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { if (!scene) { current_option = -1; - //confirmation->get_cancel()->hide(); accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("This operation can't be done without a tree root.")); accept->popup_centered_minsize(); @@ -1937,7 +1937,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { file->add_filter("*." + extensions[i] + " ; " + extensions[i].to_upper()); } - //file->set_current_path(current_path); if (scene->get_filename() != "") { file->set_current_path(scene->get_filename()); if (extensions.size()) { @@ -1987,7 +1986,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { if (!editor_data.get_edited_scene_root()) { current_option = -1; - //confirmation->get_cancel()->hide(); accept->get_ok()->set_text(TTR("I see..")); accept->set_text(TTR("This operation can't be done without a scene.")); accept->popup_centered_minsize(); @@ -2036,8 +2034,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { } break; case FILE_IMPORT_SUBSCENE: { - //import_subscene->popup_centered_ratio(); - if (!editor_data.get_edited_scene_root()) { current_option = -1; @@ -2056,7 +2052,6 @@ void EditorNode::_menu_option_confirm(int p_option, bool p_confirmed) { if (unsaved_cache && !p_confirmed) { confirmation->get_ok()->set_text(TTR("Open")); - //confirmation->get_cancel()->show(); confirmation->set_text(TTR("Current scene not saved. Open anyway?")); confirmation->popup_centered_minsize(); break; @@ -2843,7 +2838,7 @@ void EditorNode::_remove_scene(int index) { //Scene to remove is current scene _remove_edited_scene(); } else { - // Scene to remove is not active scene + //Scene to remove is not active scene editor_data.remove_scene(index); } } @@ -3240,48 +3235,47 @@ void EditorNode::_show_messages() { void EditorNode::_add_to_recent_scenes(const String &p_scene) { - String base = "_" + ProjectSettings::get_singleton()->get_resource_path().replace("\\", "::").replace("/", "::"); - Vector<String> rc = EDITOR_DEF(base + "/_recent_scenes", Array()); - String name = p_scene; - name = name.replace("res://", ""); - if (rc.find(name) != -1) - rc.erase(name); - rc.insert(0, name); + Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scenes", Array()); + if (rc.find(p_scene) != -1) + rc.erase(p_scene); + rc.push_front(p_scene); if (rc.size() > 10) rc.resize(10); - EditorSettings::get_singleton()->set(base + "/_recent_scenes", rc); - EditorSettings::get_singleton()->save(); + EditorSettings::get_singleton()->set_project_metadata("recent_files", "scenes", rc); _update_recent_scenes(); } void EditorNode::_open_recent_scene(int p_idx) { - String base = "_" + ProjectSettings::get_singleton()->get_resource_path().replace("\\", "::").replace("/", "::"); - if (p_idx == recent_scenes->get_item_count() - 1) { - EditorSettings::get_singleton()->erase(base + "/_recent_scenes"); + EditorSettings::get_singleton()->set_project_metadata("recent_files", "scenes", Array()); call_deferred("_update_recent_scenes"); } else { - Vector<String> rc = EDITOR_DEF(base + "/_recent_scenes", Array()); + Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scenes", Array()); ERR_FAIL_INDEX(p_idx, rc.size()); - String path = "res://" + rc[p_idx]; - load_scene(path); + if (load_scene(rc[p_idx]) != OK) { + + rc.remove(p_idx); + EditorSettings::get_singleton()->set_project_metadata("recent_files", "scenes", rc); + _update_recent_scenes(); + } } } void EditorNode::_update_recent_scenes() { - String base = "_" + ProjectSettings::get_singleton()->get_resource_path().replace("\\", "::").replace("/", "::"); - Vector<String> rc = EDITOR_DEF(base + "/_recent_scenes", Array()); + Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scenes", Array()); recent_scenes->clear(); + String path; for (int i = 0; i < rc.size(); i++) { - recent_scenes->add_item(rc[i], i); + path = rc[i]; + recent_scenes->add_item(path.replace("res://", ""), i); } recent_scenes->add_separator(); @@ -5118,7 +5112,6 @@ EditorNode::EditorNode() { gui_base->add_child(dependency_fixer); settings_config_dialog = memnew(EditorSettingsDialog); - // settings_config_dialog->add_style_override("panel", gui_base->get_stylebox("EditorSettingsDialog", "EditorStyles")); gui_base->add_child(settings_config_dialog); project_settings = memnew(ProjectSettingsEditor(&editor_data)); @@ -5192,7 +5185,6 @@ EditorNode::EditorNode() { p->add_item(TTR("Project Settings"), RUN_SETTINGS); p->add_separator(); p->connect("id_pressed", this, "_menu_option"); - //p->add_item(TTR("Run Script"), FILE_RUN_SCRIPT, KEY_MASK_SHIFT + KEY_MASK_CMD + KEY_R); p->add_item(TTR("Export"), FILE_EXPORT_PROJECT); PopupMenu *tool_menu = memnew(PopupMenu); @@ -5283,7 +5275,6 @@ EditorNode::EditorNode() { menu_hb->add_child(play_cc); play_button_panel = memnew(PanelContainer); - // play_button_panel->add_style_override("panel", gui_base->get_stylebox("PlayButtonPanel", "EditorStyles")); play_cc->add_child(play_button_panel); HBoxContainer *play_hb = memnew(HBoxContainer); diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index 433f501fc8..c6676a1f0f 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -382,8 +382,6 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { _initial_set("text_editor/completion/callhint_tooltip_offset", Vector2()); _initial_set("text_editor/files/restore_scripts_on_load", true); _initial_set("text_editor/completion/complete_file_paths", true); - _initial_set("text_editor/files/maximum_recent_files", 20); - hints["text_editor/files/maximum_recent_files"] = PropertyInfo(Variant::INT, "text_editor/files/maximum_recent_files", PROPERTY_HINT_RANGE, "1, 200, 1"); _initial_set("docks/scene_tree/start_create_dialog_fully_expanded", false); _initial_set("docks/scene_tree/draw_relationship_lines", false); diff --git a/editor/plugins/canvas_item_editor_plugin.cpp b/editor/plugins/canvas_item_editor_plugin.cpp index 7d6025cb03..90969752d3 100644 --- a/editor/plugins/canvas_item_editor_plugin.cpp +++ b/editor/plugins/canvas_item_editor_plugin.cpp @@ -4225,9 +4225,9 @@ CanvasItemEditor::CanvasItemEditor(EditorNode *p_editor) { p = view_menu->get_popup(); p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_grid", TTR("Show Grid"), KEY_G), SHOW_GRID); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show helpers"), KEY_H), SHOW_HELPERS); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show rulers"), KEY_R), SHOW_RULERS); - p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show guides"), KEY_Y), SHOW_GUIDES); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_helpers", TTR("Show Helpers"), KEY_H), SHOW_HELPERS); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_rulers", TTR("Show Rulers"), KEY_R), SHOW_RULERS); + p->add_check_shortcut(ED_SHORTCUT("canvas_item_editor/show_guides", TTR("Show Guides"), KEY_Y), SHOW_GUIDES); p->add_separator(); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/center_selection", TTR("Center Selection"), KEY_F), VIEW_CENTER_TO_SELECTION); p->add_shortcut(ED_SHORTCUT("canvas_item_editor/frame_selection", TTR("Frame Selection"), KEY_MASK_SHIFT | KEY_F), VIEW_FRAME_TO_SELECTION); diff --git a/editor/plugins/script_editor_plugin.cpp b/editor/plugins/script_editor_plugin.cpp index d18422c0c0..eea93029e6 100644 --- a/editor/plugins/script_editor_plugin.cpp +++ b/editor/plugins/script_editor_plugin.cpp @@ -429,36 +429,32 @@ void ScriptEditor::_add_recent_script(String p_path) { return; } - // remove if already stored - int already_recent = previous_scripts.find(p_path); - if (already_recent >= 0) { - previous_scripts.remove(already_recent); + Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array()); + if (rc.find(p_path) != -1) { + rc.erase(p_path); + } + rc.push_front(p_path); + if (rc.size() > 10) { + rc.resize(10); } - // add to list - previous_scripts.insert(0, p_path); - + EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", rc); _update_recent_scripts(); } void ScriptEditor::_update_recent_scripts() { - // make sure we don't exceed max size - const int max_history = EDITOR_DEF("text_editor/files/maximum_recent_files", 20); - if (previous_scripts.size() > max_history) { - previous_scripts.resize(max_history); - } - + Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array()); recent_scripts->clear(); recent_scripts->add_shortcut(ED_SHORTCUT("script_editor/open_recent", TTR("Open Recent"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_T)); recent_scripts->add_separator(); - const int max_shown = 8; - for (int i = 0; i < previous_scripts.size() && i <= max_shown; i++) { - String path = previous_scripts.get(i); - // just show script name and last dir - recent_scripts->add_item(path.get_slice("/", path.get_slice_count("/") - 2) + "/" + path.get_file()); + String path; + for (int i = 0; i < rc.size(); i++) { + + path = rc[i]; + recent_scripts->add_item(path.replace("res://", "")); } recent_scripts->add_separator(); @@ -471,7 +467,7 @@ void ScriptEditor::_open_recent_script(int p_idx) { // clear button if (p_idx == recent_scripts->get_item_count() - 1) { - previous_scripts.clear(); + EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", Array()); call_deferred("_update_recent_scripts"); return; } @@ -481,22 +477,34 @@ void ScriptEditor::_open_recent_script(int p_idx) { p_idx -= 2; } - if (p_idx < previous_scripts.size() && p_idx >= 0) { + Array rc = EditorSettings::get_singleton()->get_project_metadata("recent_files", "scripts", Array()); + ERR_FAIL_INDEX(p_idx, rc.size()); - String path = previous_scripts.get(p_idx); - // if its not on disk its a help file or deleted - if (FileAccess::exists(path)) { - Ref<Script> script = ResourceLoader::load(path); - if (script.is_valid()) { - edit(script, true); - } - // if it's a path then its most likely a delted file not help - } else if (!path.is_resource_file()) { - _help_class_open(path); + String path = rc[p_idx]; + // if its not on disk its a help file or deleted + if (FileAccess::exists(path)) { + Ref<Script> script = ResourceLoader::load(path); + if (script.is_valid()) { + edit(script, true); + return; } - previous_scripts.remove(p_idx); - _update_recent_scripts(); + + // if it's a path then its most likely a deleted file not help + } else if (!path.is_resource_file()) { + _help_class_open(path); + return; } + + rc.remove(p_idx); + EditorSettings::get_singleton()->set_project_metadata("recent_files", "scripts", rc); + _update_recent_scripts(); + _show_error_dialog(path); +} + +void ScriptEditor::_show_error_dialog(String p_path) { + + error_dialog->set_text(vformat(TTR("Can't open '%s'. The file could have been moved or deleted."), p_path)); + error_dialog->popup_centered_minsize(); } void ScriptEditor::_close_tab(int p_idx, bool p_save) { @@ -508,14 +516,10 @@ void ScriptEditor::_close_tab(int p_idx, bool p_save) { Node *tselected = tab_container->get_child(selected); ScriptEditorBase *current = Object::cast_to<ScriptEditorBase>(tab_container->get_child(selected)); if (current) { - _add_recent_script(current->get_edited_script()->get_path()); if (p_save) { apply_scripts(); } notify_script_close(current->get_edited_script()); - } else { - EditorHelp *help = Object::cast_to<EditorHelp>(tab_container->get_child(selected)); - _add_recent_script(help->get_class()); } // roll back to previous tab @@ -1787,6 +1791,7 @@ bool ScriptEditor::edit(const Ref<Script> &p_script, int p_line, int p_col, bool se->goto_line(p_line - 1); notify_script_changed(p_script); + _add_recent_script(p_script->get_path()); return true; } @@ -2304,6 +2309,7 @@ void ScriptEditor::_help_class_open(const String &p_class) { _go_to_tab(tab_container->get_tab_count() - 1); eh->go_to_class(p_class, 0); eh->connect("go_to_help", this, "_help_class_goto"); + _add_recent_script(p_class); _update_script_names(); _save_layout(); } @@ -2332,6 +2338,7 @@ void ScriptEditor::_help_class_goto(const String &p_desc) { _go_to_tab(tab_container->get_tab_count() - 1); eh->go_to_help(p_desc); eh->connect("go_to_help", this, "_help_class_goto"); + _add_recent_script(eh->get_class()); _update_script_names(); _save_layout(); } @@ -2738,6 +2745,10 @@ ScriptEditor::ScriptEditor(EditorNode *p_editor) { add_child(file_dialog); file_dialog->connect("file_selected", this, "_file_dialog_action"); + error_dialog = memnew(AcceptDialog); + add_child(error_dialog); + error_dialog->get_ok()->set_text(TTR("I see..")); + debugger = memnew(ScriptEditorDebugger(editor)); debugger->connect("goto_script_line", this, "_goto_script_line"); debugger->connect("show_debugger", this, "_show_debugger"); diff --git a/editor/plugins/script_editor_plugin.h b/editor/plugins/script_editor_plugin.h index e60e4cf8c0..e98a4c97a6 100644 --- a/editor/plugins/script_editor_plugin.h +++ b/editor/plugins/script_editor_plugin.h @@ -198,6 +198,7 @@ class ScriptEditor : public PanelContainer { VSplitContainer *list_split; TabContainer *tab_container; EditorFileDialog *file_dialog; + AcceptDialog *error_dialog; ConfirmationDialog *erase_tab_confirm; ScriptCreateDialog *script_create_dialog; ScriptEditorDebugger *debugger; @@ -227,8 +228,6 @@ class ScriptEditor : public PanelContainer { Vector<ScriptHistory> history; int history_pos; - Vector<String> previous_scripts; - EditorHelpIndex *help_index; void _tab_changed(int p_which); @@ -250,6 +249,8 @@ class ScriptEditor : public PanelContainer { void _update_recent_scripts(); void _open_recent_script(int p_idx); + void _show_error_dialog(String p_path); + void _close_tab(int p_idx, bool p_save = true); void _close_current_tab(); diff --git a/editor/plugins/script_text_editor.cpp b/editor/plugins/script_text_editor.cpp index a3728a1d46..ecd98d5d3e 100644 --- a/editor/plugins/script_text_editor.cpp +++ b/editor/plugins/script_text_editor.cpp @@ -519,6 +519,7 @@ void ScriptTextEditor::tag_saved_version() { void ScriptTextEditor::goto_line(int p_line, bool p_with_error) { TextEdit *tx = code_editor->get_text_edit(); + tx->deselect(); tx->unfold_line(p_line); tx->call_deferred("cursor_set_line", p_line); } diff --git a/editor/project_settings_editor.cpp b/editor/project_settings_editor.cpp index 058f517ae9..9625bc19c0 100644 --- a/editor/project_settings_editor.cpp +++ b/editor/project_settings_editor.cpp @@ -1595,7 +1595,7 @@ ProjectSettingsEditor::ProjectSettingsEditor(EditorData *p_data) { hbc->set_h_size_flags(Control::SIZE_EXPAND_FILL); props_base->add_child(hbc); - search_button = memnew(ToolButton); + search_button = memnew(Button); search_button->set_toggle_mode(true); search_button->set_pressed(false); search_button->set_text(TTR("Search")); diff --git a/editor/project_settings_editor.h b/editor/project_settings_editor.h index d6c2c0f5a8..452cf5b060 100644 --- a/editor/project_settings_editor.h +++ b/editor/project_settings_editor.h @@ -67,7 +67,7 @@ class ProjectSettingsEditor : public AcceptDialog { SectionedPropertyEditor *globals_editor; HBoxContainer *search_bar; - ToolButton *search_button; + Button *search_button; LineEdit *search_box; ToolButton *clear_button; diff --git a/editor/spatial_editor_gizmos.cpp b/editor/spatial_editor_gizmos.cpp index 72c0f050d2..aa7bfab2a5 100644 --- a/editor/spatial_editor_gizmos.cpp +++ b/editor/spatial_editor_gizmos.cpp @@ -308,15 +308,6 @@ void EditorSpatialGizmo::add_solid_box(Ref<Material> &p_material, Vector3 p_size m->add_surface_from_arrays(cubem.surface_get_primitive_type(0), cubem.surface_get_arrays(0)); m->surface_set_material(0, p_material); add_mesh(m); - - Instance ins; - ins.mesh = m; - if (valid) { - ins.create_instance(spatial_node); - VS::get_singleton()->instance_set_transform(ins.instance, spatial_node->get_global_transform()); - } - - instances.push_back(ins); } void EditorSpatialGizmo::set_spatial_node(Spatial *p_node) { diff --git a/modules/gdnative/SCsub b/modules/gdnative/SCsub index c92c3f30a2..acfb83bc10 100644 --- a/modules/gdnative/SCsub +++ b/modules/gdnative/SCsub @@ -28,7 +28,7 @@ def _build_gdnative_api_struct_header(api): '\textern const godot_gdnative_ext_{0}_api_struct *_gdnative_wrapper_{0}_api_struct;'.format(name)) gdnative_api_init_macro.append('\t_gdnative_wrapper_api_struct = options->api_struct;') - gdnative_api_init_macro.append('\tfor (int i = 0; i < _gdnative_wrapper_api_struct->num_extensions; i++) { ') + gdnative_api_init_macro.append('\tfor (unsigned int i = 0; i < _gdnative_wrapper_api_struct->num_extensions; i++) { ') gdnative_api_init_macro.append('\t\tswitch (_gdnative_wrapper_api_struct->extensions[i]->type) {') for name in api['extensions']: @@ -66,19 +66,30 @@ def _build_gdnative_api_struct_header(api): out += ['};', ''] - for name in api['extensions']: - out += [ - 'typedef struct godot_gdnative_ext_' + name + '_api_struct {', + + def generate_extension_struct(name, ext, include_version=True): + ret_val = [] + if ext['next']: + ret_val += generate_extension_struct(name, ext['next']) + + ret_val += [ + 'typedef struct godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct {', '\tunsigned int type;', '\tgodot_gdnative_api_version version;', '\tconst godot_gdnative_api_struct *next;' ] - for funcdef in api['extensions'][name]['api']: + for funcdef in ext['api']: args = ', '.join(['%s%s' % (_spaced(t), n) for t, n in funcdef['arguments']]) - out.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args)) + ret_val.append('\t%s(*%s)(%s);' % (_spaced(funcdef['return_type']), funcdef['name'], args)) + + ret_val += ['} godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct;', ''] + + return ret_val - out += ['} godot_gdnative_ext_' + name + '_api_struct;', ''] + + for name in api['extensions']: + out += generate_extension_struct(name, api['extensions'][name], False) out += [ 'typedef struct godot_gdnative_core_api_struct {', @@ -113,18 +124,35 @@ def _build_gdnative_api_struct_source(api): '' ] - for name in api['extensions']: - out += [ - 'extern const godot_gdnative_ext_' + name + '_api_struct api_extension_' + name + '_struct = {', - '\tGDNATIVE_EXT_' + api['extensions'][name]['type'] + ',', - '\t{' + str(api['extensions'][name]['version']['major']) + ', ' + str(api['extensions'][name]['version']['minor']) + '},', - '\tNULL,' + def get_extension_struct_name(name, ext, include_version=True): + return 'godot_gdnative_ext_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_api_struct' + + def get_extension_struct_instance_name(name, ext, include_version=True): + return 'api_extension_' + name + ('' if not include_version else ('_{0}_{1}'.format(ext['version']['major'], ext['version']['minor']))) + '_struct' + + def get_extension_struct_definition(name, ext, include_version=True): + + ret_val = [] + + if ext['next']: + ret_val += get_extension_struct_definition(name, ext['next']) + + ret_val += [ + 'extern const ' + get_extension_struct_name(name, ext, include_version) + ' ' + get_extension_struct_instance_name(name, ext, include_version) + ' = {', + '\tGDNATIVE_EXT_' + ext['type'] + ',', + '\t{' + str(ext['version']['major']) + ', ' + str(ext['version']['minor']) + '},', + '\t' + ('NULL' if not ext['next'] else ('(const godot_gdnative_api_struct *)&' + get_extension_struct_instance_name(name, ext['next']))) + ',' ] - for funcdef in api['extensions'][name]['api']: - out.append('\t%s,' % funcdef['name']) + for funcdef in ext['api']: + ret_val.append('\t%s,' % funcdef['name']) + + ret_val += ['};\n'] - out += ['};\n'] + return ret_val + + for name in api['extensions']: + out += get_extension_struct_definition(name, api['extensions'][name], False) out += ['', 'const godot_gdnative_api_struct *gdnative_extensions_pointers[] = {'] diff --git a/modules/gdnative/doc_classes/NativeScript.xml b/modules/gdnative/doc_classes/NativeScript.xml index f713e4112e..6a71cd8d4d 100644 --- a/modules/gdnative/doc_classes/NativeScript.xml +++ b/modules/gdnative/doc_classes/NativeScript.xml @@ -1,5 +1,5 @@ <?xml version="1.0" encoding="UTF-8" ?> -<class name="NativeScript" inherits="Script" category="Core" version="3.0-stable"> +<class name="NativeScript" inherits="Script" category="Core" version="3.1-dev"> <brief_description> </brief_description> <description> @@ -9,10 +9,46 @@ <demos> </demos> <methods> + <method name="get_class_documentation" qualifiers="const"> + <return type="String"> + </return> + <description> + Returns the documentation string that was previously set with [code]godot_nativescript_set_class_documentation[/code]. + </description> + </method> + <method name="get_method_documentation" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="method" type="String"> + </argument> + <description> + Returns the documentation string that was previously set with [code]godot_nativescript_set_method_documentation[/code]. + </description> + </method> + <method name="get_property_documentation" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="path" type="String"> + </argument> + <description> + Returns the documentation string that was previously set with [code]godot_nativescript_set_property_documentation[/code]. + </description> + </method> + <method name="get_signal_documentation" qualifiers="const"> + <return type="String"> + </return> + <argument index="0" name="signal_name" type="String"> + </argument> + <description> + Returns the documentation string that was previously set with [code]godot_nativescript_set_signal_documentation[/code]. + </description> + </method> <method name="new" qualifiers="vararg"> <return type="Object"> </return> <description> + Constructs a new object of the base type with a script of this type already attached. + [i]Note[/i]: Any arguments passed to this function will be ignored and not passed to the native constructor function. This will change with in a future API extension. </description> </method> </methods> diff --git a/modules/gdnative/gdnative_api.json b/modules/gdnative/gdnative_api.json index 59a9c0b090..a8919f7130 100644 --- a/modules/gdnative/gdnative_api.json +++ b/modules/gdnative/gdnative_api.json @@ -5,6 +5,7 @@ "major": 1, "minor": 0 }, + "next": null, "api": [ { "name": "godot_color_new_rgba", @@ -3963,7 +3964,7 @@ "name": "godot_variant_new_bool", "return_type": "void", "arguments": [ - ["godot_variant *", "p_v"], + ["godot_variant *", "r_dest"], ["const godot_bool", "p_b"] ] }, @@ -5762,6 +5763,104 @@ "major": 1, "minor": 0 }, + "next": { + "type": "NATIVESCRIPT", + "version": { + "major": 1, + "minor": 1 + }, + "next": null, + "api": [ + { + "name": "godot_nativescript_set_method_argument_information", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["const char *", "p_function_name"], + ["int", "p_num_args"], + ["const godot_method_arg *", "p_args"] + ] + }, + { + "name": "godot_nativescript_set_class_documentation", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["godot_string", "p_documentation"] + ] + }, + { + "name": "godot_nativescript_set_method_documentation", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["const char *", "p_function_name"], + ["godot_string", "p_documentation"] + ] + }, + { + "name": "godot_nativescript_set_property_documentation", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["const char *", "p_path"], + ["godot_string", "p_documentation"] + ] + }, + { + "name": "godot_nativescript_set_signal_documentation", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["const char *", "p_signal_name"], + ["godot_string", "p_documentation"] + ] + }, + { + "name": "godot_nativescript_set_type_tag", + "return_type": "void", + "arguments": [ + ["void *", "p_gdnative_handle"], + ["const char *", "p_name"], + ["const void *", "p_type_tag"] + ] + }, + { + "name": "godot_nativescript_get_type_tag", + "return_type": "const void *", + "arguments": [ + ["const godot_object *", "p_object"] + ] + }, + { + "name": "godot_nativescript_register_instance_binding_data_functions", + "return_type": "int", + "arguments": [ + ["godot_instance_binding_functions", "p_binding_functions"] + ] + }, + { + "name": "godot_nativescript_unregister_instance_binding_data_functions", + "return_type": "void", + "arguments": [ + ["int", "p_idx"] + ] + }, + { + "name": "godot_nativescript_get_instance_binding_data", + "return_type": "void *", + "arguments": [ + ["int", "p_idx"], + ["godot_object *", "p_object"] + ] + } + ] + }, "api": [ { "name": "godot_nativescript_register_class", @@ -5832,6 +5931,7 @@ "major": 1, "minor": 0 }, + "next": null, "api": [ { "name": "godot_pluginscript_register_language", @@ -5848,6 +5948,7 @@ "major": 1, "minor": 0 }, + "next": null, "api": [ { "name": "godot_arvr_register_interface", diff --git a/modules/gdnative/include/gdnative/variant.h b/modules/gdnative/include/gdnative/variant.h index d2e8246bfb..6779dc4092 100644 --- a/modules/gdnative/include/gdnative/variant.h +++ b/modules/gdnative/include/gdnative/variant.h @@ -135,7 +135,7 @@ void GDAPI godot_variant_new_copy(godot_variant *r_dest, const godot_variant *p_ void GDAPI godot_variant_new_nil(godot_variant *r_dest); -void GDAPI godot_variant_new_bool(godot_variant *p_v, const godot_bool p_b); +void GDAPI godot_variant_new_bool(godot_variant *r_dest, const godot_bool p_b); void GDAPI godot_variant_new_uint(godot_variant *r_dest, const uint64_t p_i); void GDAPI godot_variant_new_int(godot_variant *r_dest, const int64_t p_i); void GDAPI godot_variant_new_real(godot_variant *r_dest, const double p_r); diff --git a/modules/gdnative/include/nativescript/godot_nativescript.h b/modules/gdnative/include/nativescript/godot_nativescript.h index 11017ae78d..747328bc41 100644 --- a/modules/gdnative/include/nativescript/godot_nativescript.h +++ b/modules/gdnative/include/nativescript/godot_nativescript.h @@ -185,6 +185,52 @@ void GDAPI godot_nativescript_register_signal(void *p_gdnative_handle, const cha void GDAPI *godot_nativescript_get_userdata(godot_object *p_instance); +/* + * + * + * NativeScript 1.1 + * + * + */ + +// method registering with argument names + +typedef struct { + godot_string name; + + godot_variant_type type; + godot_property_hint hint; + godot_string hint_string; +} godot_method_arg; + +void GDAPI godot_nativescript_set_method_argument_information(void *p_gdnative_handle, const char *p_name, const char *p_function_name, int p_num_args, const godot_method_arg *p_args); + +// documentation + +void GDAPI godot_nativescript_set_class_documentation(void *p_gdnative_handle, const char *p_name, godot_string p_documentation); +void GDAPI godot_nativescript_set_method_documentation(void *p_gdnative_handle, const char *p_name, const char *p_function_name, godot_string p_documentation); +void GDAPI godot_nativescript_set_property_documentation(void *p_gdnative_handle, const char *p_name, const char *p_path, godot_string p_documentation); +void GDAPI godot_nativescript_set_signal_documentation(void *p_gdnative_handle, const char *p_name, const char *p_signal_name, godot_string p_documentation); + +// type tag API + +void GDAPI godot_nativescript_set_type_tag(void *p_gdnative_handle, const char *p_name, const void *p_type_tag); +const void GDAPI *godot_nativescript_get_type_tag(const godot_object *p_object); + +// instance binding API + +typedef struct { + void *(*alloc_instance_binding_data)(void *, godot_object *); + void (*free_instance_binding_data)(void *, void *); + void *data; + void (*free_func)(void *); +} godot_instance_binding_functions; + +int GDAPI godot_nativescript_register_instance_binding_data_functions(godot_instance_binding_functions p_binding_functions); +void GDAPI godot_nativescript_unregister_instance_binding_data_functions(int p_idx); + +void GDAPI *godot_nativescript_get_instance_binding_data(int p_idx, godot_object *p_object); + #ifdef __cplusplus } #endif diff --git a/modules/gdnative/nativescript/godot_nativescript.cpp b/modules/gdnative/nativescript/godot_nativescript.cpp index b4f7e1555e..aea595d0f0 100644 --- a/modules/gdnative/nativescript/godot_nativescript.cpp +++ b/modules/gdnative/nativescript/godot_nativescript.cpp @@ -106,7 +106,7 @@ void GDAPI godot_nativescript_register_method(void *p_gdnative_handle, const cha Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); if (!E) { - ERR_EXPLAIN("Attempt to register method on non-existant class!"); + ERR_EXPLAIN("Attempted to register method on non-existent class!"); ERR_FAIL(); } @@ -125,7 +125,7 @@ void GDAPI godot_nativescript_register_property(void *p_gdnative_handle, const c Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); if (!E) { - ERR_EXPLAIN("Attempt to register method on non-existant class!"); + ERR_EXPLAIN("Attempted to register method on non-existent class!"); ERR_FAIL(); } @@ -150,7 +150,7 @@ void GDAPI godot_nativescript_register_signal(void *p_gdnative_handle, const cha Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); if (!E) { - ERR_EXPLAIN("Attempt to register method on non-existant class!"); + ERR_EXPLAIN("Attempted to register method on non-existent class!"); ERR_FAIL(); } @@ -201,6 +201,164 @@ void GDAPI *godot_nativescript_get_userdata(godot_object *p_instance) { return NULL; } +/* + * + * + * NativeScript 1.1 + * + * + */ + +void GDAPI godot_nativescript_set_method_argument_information(void *p_gdnative_handle, const char *p_name, const char *p_function_name, int p_num_args, const godot_method_arg *p_args) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to add argument information for a method on a non-existent class!"); + ERR_FAIL(); + } + + Map<StringName, NativeScriptDesc::Method>::Element *method = E->get().methods.find(p_function_name); + if (!method) { + ERR_EXPLAIN("Attempted to add argument information to non-existent method!"); + ERR_FAIL(); + } + + MethodInfo *method_information = &method->get().info; + + List<PropertyInfo> args; + + for (int i = 0; i < p_num_args; i++) { + godot_method_arg arg = p_args[i]; + String name = *(String *)&arg.name; + String hint_string = *(String *)&arg.hint_string; + + Variant::Type type = (Variant::Type)arg.type; + PropertyHint hint = (PropertyHint)arg.hint; + + args.push_back(PropertyInfo(type, p_name, hint, hint_string)); + } + + method_information->arguments = args; +} + +void GDAPI godot_nativescript_set_class_documentation(void *p_gdnative_handle, const char *p_name, godot_string p_documentation) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to add documentation to a non-existent class!"); + ERR_FAIL(); + } + + E->get().documentation = *(String *)&p_documentation; +} + +void GDAPI godot_nativescript_set_method_documentation(void *p_gdnative_handle, const char *p_name, const char *p_function_name, godot_string p_documentation) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to add documentation to a method on a non-existent class!"); + ERR_FAIL(); + } + + Map<StringName, NativeScriptDesc::Method>::Element *method = E->get().methods.find(p_function_name); + if (!method) { + ERR_EXPLAIN("Attempted to add documentatino to non-existent method!"); + ERR_FAIL(); + } + + method->get().documentation = *(String *)&p_documentation; +} + +void GDAPI godot_nativescript_set_property_documentation(void *p_gdnative_handle, const char *p_name, const char *p_path, godot_string p_documentation) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to add documentation to a property on a non-existent class!"); + ERR_FAIL(); + } + + OrderedHashMap<StringName, NativeScriptDesc::Property>::Element property = E->get().properties.find(p_path); + if (!property) { + ERR_EXPLAIN("Attempted to add documentation to non-existent property!"); + ERR_FAIL(); + } + + property.get().documentation = *(String *)&p_documentation; +} + +void GDAPI godot_nativescript_set_signal_documentation(void *p_gdnative_handle, const char *p_name, const char *p_signal_name, godot_string p_documentation) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to add documentation to a signal on a non-existent class!"); + ERR_FAIL(); + } + + Map<StringName, NativeScriptDesc::Signal>::Element *signal = E->get().signals_.find(p_signal_name); + if (!signal) { + ERR_EXPLAIN("Attempted to add documentation to non-existent signal!"); + ERR_FAIL(); + } + + signal->get().documentation = *(String *)&p_documentation; +} + +void GDAPI godot_nativescript_set_type_tag(void *p_gdnative_handle, const char *p_name, const void *p_type_tag) { + String *s = (String *)p_gdnative_handle; + + Map<StringName, NativeScriptDesc>::Element *E = NSL->library_classes[*s].find(p_name); + + if (!E) { + ERR_EXPLAIN("Attempted to set type tag on a non-existent class!"); + ERR_FAIL(); + } + + E->get().type_tag = p_type_tag; +} + +const void GDAPI *godot_nativescript_get_type_tag(const godot_object *p_object) { + + const Object *o = (Object *)p_object; + + if (!o->get_script_instance()) { + ERR_EXPLAIN("Attempted to get type tag on an object without a script!"); + ERR_FAIL_V(NULL); + } else { + NativeScript *script = Object::cast_to<NativeScript>(o->get_script_instance()->get_script().ptr()); + if (!script) { + ERR_EXPLAIN("Attempted to get type tag on an object without a nativescript attached"); + ERR_FAIL_V(NULL); + } + + if (script->get_script_desc()) + return script->get_script_desc()->type_tag; + } + + return NULL; +} + #ifdef __cplusplus } #endif + +int GDAPI godot_nativescript_register_instance_binding_data_functions(godot_instance_binding_functions p_binding_functions) { + return NativeScriptLanguage::get_singleton()->register_binding_functions(p_binding_functions); +} + +void GDAPI godot_nativescript_unregister_instance_binding_data_functions(int p_idx) { + NativeScriptLanguage::get_singleton()->unregister_binding_functions(p_idx); +} + +void GDAPI *godot_nativescript_get_instance_binding_data(int p_idx, godot_object *p_object) { + return NativeScriptLanguage::get_singleton()->get_instance_binding_data(p_idx, (Object *)p_object); +} diff --git a/modules/gdnative/nativescript/nativescript.cpp b/modules/gdnative/nativescript/nativescript.cpp index aaa7d634d1..f2e9bef467 100644 --- a/modules/gdnative/nativescript/nativescript.cpp +++ b/modules/gdnative/nativescript/nativescript.cpp @@ -68,6 +68,11 @@ void NativeScript::_bind_methods() { ClassDB::bind_method(D_METHOD("set_library", "library"), &NativeScript::set_library); ClassDB::bind_method(D_METHOD("get_library"), &NativeScript::get_library); + ClassDB::bind_method(D_METHOD("get_class_documentation"), &NativeScript::get_class_documentation); + ClassDB::bind_method(D_METHOD("get_method_documentation", "method"), &NativeScript::get_method_documentation); + ClassDB::bind_method(D_METHOD("get_signal_documentation", "signal_name"), &NativeScript::get_signal_documentation); + ClassDB::bind_method(D_METHOD("get_property_documentation", "path"), &NativeScript::get_property_documentation); + ADD_PROPERTYNZ(PropertyInfo(Variant::STRING, "class_name"), "set_class_name", "get_class_name"); ADD_PROPERTYNZ(PropertyInfo(Variant::OBJECT, "library", PROPERTY_HINT_RESOURCE_TYPE, "GDNativeLibrary"), "set_library", "get_library"); @@ -373,6 +378,86 @@ void NativeScript::get_script_property_list(List<PropertyInfo> *p_list) const { } } +String NativeScript::get_class_documentation() const { + NativeScriptDesc *script_data = get_script_desc(); + + if (!script_data) { + ERR_EXPLAIN("Attempt to get class documentation on invalid NativeScript"); + ERR_FAIL_V(""); + } + + return script_data->documentation; +} + +String NativeScript::get_method_documentation(const StringName &p_method) const { + NativeScriptDesc *script_data = get_script_desc(); + + if (!script_data) { + ERR_EXPLAIN("Attempt to get method documentation on invalid NativeScript"); + ERR_FAIL_V(""); + } + + while (script_data) { + + Map<StringName, NativeScriptDesc::Method>::Element *method = script_data->methods.find(p_method); + + if (method) { + return method->get().documentation; + } + + script_data = script_data->base_data; + } + + ERR_EXPLAIN("Attempt to get method documentation for non-existent method"); + ERR_FAIL_V(""); +} + +String NativeScript::get_signal_documentation(const StringName &p_signal_name) const { + NativeScriptDesc *script_data = get_script_desc(); + + if (!script_data) { + ERR_EXPLAIN("Attempt to get signal documentation on invalid NativeScript"); + ERR_FAIL_V(""); + } + + while (script_data) { + + Map<StringName, NativeScriptDesc::Signal>::Element *signal = script_data->signals_.find(p_signal_name); + + if (signal) { + return signal->get().documentation; + } + + script_data = script_data->base_data; + } + + ERR_EXPLAIN("Attempt to get signal documentation for non-existent signal"); + ERR_FAIL_V(""); +} + +String NativeScript::get_property_documentation(const StringName &p_path) const { + NativeScriptDesc *script_data = get_script_desc(); + + if (!script_data) { + ERR_EXPLAIN("Attempt to get property documentation on invalid NativeScript"); + ERR_FAIL_V(""); + } + + while (script_data) { + + OrderedHashMap<StringName, NativeScriptDesc::Property>::Element property = script_data->properties.find(p_path); + + if (property) { + return property.get().documentation; + } + + script_data = script_data->base_data; + } + + ERR_EXPLAIN("Attempt to get property documentation for non-existent signal"); + ERR_FAIL_V(""); +} + Variant NativeScript::_new(const Variant **p_args, int p_argcount, Variant::CallError &r_error) { if (lib_path.empty() || class_name.empty() || library.is_null()) { @@ -610,7 +695,7 @@ Variant::Type NativeScriptInstance::get_property_type(const StringName &p_name, } void NativeScriptInstance::get_method_list(List<MethodInfo> *p_list) const { - script->get_method_list(p_list); + script->get_script_method_list(p_list); } bool NativeScriptInstance::has_method(const StringName &p_method) const { @@ -824,6 +909,25 @@ void NativeScriptLanguage::_unload_stuff(bool p_reload) { } } + Map<String, Ref<GDNative> >::Element *E = library_gdnatives.find(lib_path); + Ref<GDNative> gdn; + + if (E) { + gdn = E->get(); + } + + if (gdn.is_valid() && gdn->get_library().is_valid()) { + Ref<GDNativeLibrary> lib = gdn->get_library(); + void *terminate_fn; + Error err = gdn->get_symbol(lib->get_symbol_prefix() + _terminate_call_name, terminate_fn, true); + + if (err == OK) { + void (*terminate)(void *) = (void (*)(void *))terminate_fn; + + terminate((void *)&lib_path); + } + } + for (Map<StringName, NativeScriptDesc>::Element *C = classes.front(); C; C = C->next()) { // free property stuff first @@ -1011,6 +1115,116 @@ int NativeScriptLanguage::profiling_get_frame_data(ProfilingInfo *p_info_arr, in return 0; } +int NativeScriptLanguage::register_binding_functions(godot_instance_binding_functions p_binding_functions) { + + // find index + + int idx = -1; + + for (int i = 0; i < binding_functions.size(); i++) { + if (!binding_functions[i].first) { + // free, we'll take it + idx = i; + break; + } + } + + if (idx == -1) { + idx = binding_functions.size(); + binding_functions.resize(idx + 1); + } + + // set the functions + binding_functions[idx].first = true; + binding_functions[idx].second = p_binding_functions; + + return idx; +} + +void NativeScriptLanguage::unregister_binding_functions(int p_idx) { + ERR_FAIL_INDEX(p_idx, binding_functions.size()); + + for (Set<Vector<void *> *>::Element *E = binding_instances.front(); E; E = E->next()) { + Vector<void *> &binding_data = *E->get(); + + if (binding_data[p_idx] && binding_functions[p_idx].second.free_instance_binding_data) + binding_functions[p_idx].second.free_instance_binding_data(binding_functions[p_idx].second.data, binding_data[p_idx]); + } + + binding_functions[p_idx].first = false; + + if (binding_functions[p_idx].second.free_func) + binding_functions[p_idx].second.free_func(binding_functions[p_idx].second.data); +} + +void *NativeScriptLanguage::get_instance_binding_data(int p_idx, Object *p_object) { + ERR_FAIL_INDEX_V(p_idx, binding_functions.size(), NULL); + + if (!binding_functions[p_idx].first) { + ERR_EXPLAIN("Tried to get binding data for a nativescript binding that does not exist"); + ERR_FAIL_V(NULL); + } + + Vector<void *> *binding_data = (Vector<void *> *)p_object->get_script_instance_binding(lang_idx); + + if (!binding_data) + return NULL; // should never happen. + + if (binding_data->size() <= p_idx) { + // okay, add new elements here. + int old_size = binding_data->size(); + + binding_data->resize(p_idx + 1); + + for (int i = old_size; i <= p_idx; i++) { + (*binding_data)[i] = NULL; + } + } + + if (!(*binding_data)[p_idx]) { + // no binding data yet, soooooo alloc new one \o/ + (*binding_data)[p_idx] = binding_functions[p_idx].second.alloc_instance_binding_data(binding_functions[p_idx].second.data, (godot_object *)p_object); + } + + return (*binding_data)[p_idx]; +} + +void *NativeScriptLanguage::alloc_instance_binding_data(Object *p_object) { + + Vector<void *> *binding_data = new Vector<void *>; + + binding_data->resize(binding_functions.size()); + + for (int i = 0; i < binding_functions.size(); i++) { + (*binding_data)[i] = NULL; + } + + binding_instances.insert(binding_data); + + return (void *)binding_data; +} + +void NativeScriptLanguage::free_instance_binding_data(void *p_data) { + + if (!p_data) + return; + + Vector<void *> &binding_data = *(Vector<void *> *)p_data; + + for (int i = 0; i < binding_data.size(); i++) { + if (!binding_data[i]) + continue; + + if (binding_functions[i].first && binding_functions[i].second.free_instance_binding_data) { + binding_functions[i].second.free_instance_binding_data(binding_functions[i].second.data, binding_data[i]); + } + } + + binding_instances.erase(&binding_data); + + delete &binding_data; +} + #ifndef NO_THREADS void NativeScriptLanguage::defer_init_library(Ref<GDNativeLibrary> lib, NativeScript *script) { MutexLock lock(mutex); diff --git a/modules/gdnative/nativescript/nativescript.h b/modules/gdnative/nativescript/nativescript.h index ac94c84bc4..17b6ddc747 100644 --- a/modules/gdnative/nativescript/nativescript.h +++ b/modules/gdnative/nativescript/nativescript.h @@ -53,6 +53,7 @@ struct NativeScriptDesc { godot_instance_method method; MethodInfo info; int rpc_mode; + String documentation; }; struct Property { godot_property_set_func setter; @@ -60,12 +61,16 @@ struct NativeScriptDesc { PropertyInfo info; Variant default_value; int rset_mode; + String documentation; }; struct Signal { MethodInfo signal; + String documentation; }; + String documentation; + Map<StringName, Method> methods; OrderedHashMap<StringName, Property> properties; Map<StringName, Signal> signals_; // QtCreator doesn't like the name signals @@ -75,6 +80,8 @@ struct NativeScriptDesc { godot_instance_create_func create_func; godot_instance_destroy_func destroy_func; + const void *type_tag; + bool is_tool; inline NativeScriptDesc() : @@ -82,7 +89,9 @@ struct NativeScriptDesc { properties(), signals_(), base(), - base_native_type() { + base_native_type(), + documentation(), + type_tag(NULL) { zeromem(&create_func, sizeof(godot_instance_create_func)); zeromem(&destroy_func, sizeof(godot_instance_destroy_func)); } @@ -154,6 +163,11 @@ public: virtual void get_script_method_list(List<MethodInfo> *p_list) const; virtual void get_script_property_list(List<PropertyInfo> *p_list) const; + String get_class_documentation() const; + String get_method_documentation(const StringName &p_method) const; + String get_signal_documentation(const StringName &p_signal_name) const; + String get_property_documentation(const StringName &p_path) const; + Variant _new(const Variant **p_args, int p_argcount, Variant::CallError &r_error); NativeScript(); @@ -204,6 +218,7 @@ class NativeScriptLanguage : public ScriptLanguage { private: static NativeScriptLanguage *singleton; + int lang_idx; void _unload_stuff(bool p_reload = false); @@ -222,6 +237,9 @@ private: void call_libraries_cb(const StringName &name); + Vector<Pair<bool, godot_instance_binding_functions> > binding_functions; + Set<Vector<void *> *> binding_instances; + public: // These two maps must only be touched on the main thread Map<String, Map<StringName, NativeScriptDesc> > library_classes; @@ -232,6 +250,8 @@ public: const StringName _init_call_type = "nativescript_init"; const StringName _init_call_name = "nativescript_init"; + const StringName _terminate_call_name = "nativescript_terminate"; + const StringName _noarg_call_type = "nativescript_no_arg"; const StringName _frame_call_name = "nativescript_frame"; @@ -250,6 +270,8 @@ public: void _hacky_api_anchor(); + _FORCE_INLINE_ void set_language_index(int p_idx) { lang_idx = p_idx; } + #ifndef NO_THREADS virtual void thread_enter(); virtual void thread_exit(); @@ -293,6 +315,14 @@ public: virtual void profiling_stop(); virtual int profiling_get_accumulated_data(ProfilingInfo *p_info_arr, int p_info_max); virtual int profiling_get_frame_data(ProfilingInfo *p_info_arr, int p_info_max); + + int register_binding_functions(godot_instance_binding_functions p_binding_functions); + void unregister_binding_functions(int p_idx); + + void *get_instance_binding_data(int p_idx, Object *p_object); + + virtual void *alloc_instance_binding_data(Object *p_object); + virtual void free_instance_binding_data(void *p_data); }; inline NativeScriptDesc *NativeScript::get_script_desc() const { diff --git a/modules/gdnative/nativescript/register_types.cpp b/modules/gdnative/nativescript/register_types.cpp index cb55a13b3e..9a0e764391 100644 --- a/modules/gdnative/nativescript/register_types.cpp +++ b/modules/gdnative/nativescript/register_types.cpp @@ -47,6 +47,7 @@ void register_nativescript_types() { ClassDB::register_class<NativeScript>(); + native_script_language->set_language_index(ScriptServer::get_language_count()); ScriptServer::register_language(native_script_language); resource_saver_gdns = memnew(ResourceFormatSaverNativeScript); diff --git a/modules/websocket/SCsub b/modules/websocket/SCsub new file mode 100644 index 0000000000..067a99ffff --- /dev/null +++ b/modules/websocket/SCsub @@ -0,0 +1,70 @@ +#!/usr/bin/env python + +Import('env') +Import('env_modules') + +# Thirdparty source files + +env_lws = env_modules.Clone() + +thirdparty_dir = "#thirdparty/lws/" +helper_dir = "win32helpers/" +openssl_dir = "#thirdparty/openssl/" +thirdparty_sources = [ + "client/client.c", + "client/client-handshake.c", + "client/client-parser.c", + "client/ssl-client.c", + + "ext/extension.c", + "ext/extension-permessage-deflate.c", + + "server/fops-zip.c", + "server/lejp-conf.c", + "server/parsers.c", + "server/ranges.c", + "server/server.c", + "server/server-handshake.c", + "server/ssl-server.c", + + "misc/base64-decode.c", + "misc/lejp.c", + "misc/sha-1.c", + + "alloc.c", + "context.c", + "handshake.c", + "header.c", + "libwebsockets.c", + "minilex.c", + "output.c", + "pollfd.c", + "service.c", + "ssl.c", + +] + +if env_lws["platform"] == "android": # Builtin getifaddrs + thirdparty_sources += ["misc/getifaddrs.c"] + +if env_lws["platform"] == "windows": # Winsock + thirdparty_sources += ["plat/lws-plat-win.c", helper_dir + "getopt.c", helper_dir + "getopt_long.c", helper_dir + "gettimeofday.c"] +else: # Unix socket + thirdparty_sources += ["plat/lws-plat-unix.c"] + + +thirdparty_sources = [thirdparty_dir + file for file in thirdparty_sources] + +if env_lws["platform"] == "javascript": # No need to add third party libraries at all + pass +else: + env_lws.add_source_files(env.modules_sources, thirdparty_sources) + env_lws.Append(CPPPATH=[thirdparty_dir]) + + if env['builtin_openssl']: + env_lws.Append(CPPPATH=[openssl_dir]) + + if env_lws["platform"] == "windows": + env_lws.Append(CPPPATH=[thirdparty_dir + helper_dir]) + +env_lws.add_source_files(env.modules_sources, "*.cpp") diff --git a/modules/websocket/config.py b/modules/websocket/config.py new file mode 100644 index 0000000000..fb920482f5 --- /dev/null +++ b/modules/websocket/config.py @@ -0,0 +1,7 @@ + +def can_build(platform): + return True + + +def configure(env): + pass diff --git a/modules/websocket/emws_client.cpp b/modules/websocket/emws_client.cpp new file mode 100644 index 0000000000..38fe520fc1 --- /dev/null +++ b/modules/websocket/emws_client.cpp @@ -0,0 +1,224 @@ +/*************************************************************************/ +/* emws_client.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifdef JAVASCRIPT_ENABLED + +#include "emws_client.h" +#include "core/io/ip.h" +#include "emscripten.h" + +extern "C" { +EMSCRIPTEN_KEEPALIVE void _esws_on_connect(void *obj, char *proto) { + EMWSClient *client = static_cast<EMWSClient *>(obj); + client->_is_connecting = false; + client->_on_connect(String(proto)); +} + +EMSCRIPTEN_KEEPALIVE void _esws_on_message(void *obj, uint8_t *p_data, int p_data_size, int p_is_string) { + EMWSClient *client = static_cast<EMWSClient *>(obj); + + static_cast<EMWSPeer *>(*client->get_peer(1))->read_msg(p_data, p_data_size, p_is_string == 1); + client->_on_peer_packet(); +} + +EMSCRIPTEN_KEEPALIVE void _esws_on_error(void *obj) { + EMWSClient *client = static_cast<EMWSClient *>(obj); + client->_is_connecting = false; + client->_on_error(); +} + +EMSCRIPTEN_KEEPALIVE void _esws_on_close(void *obj, int code, char *reason, int was_clean) { + EMWSClient *client = static_cast<EMWSClient *>(obj); + client->_is_connecting = false; + client->_on_disconnect(); +} +} + +Error EMWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { + + String str = "ws://"; + String proto_string = ""; + int i = 0; + + if (p_ssl) + str = "wss://"; + str += p_host + ":" + itos(p_port) + p_path; + for (int i = 0; i < p_protocols.size(); i++) { + proto_string += p_protocols[i]; + proto_string += ","; + } + if (proto_string == "") + proto_string = "binary,"; + + proto_string = proto_string.substr(0, proto_string.length() - 1); + + _is_connecting = true; + /* clang-format off */ + int peer_sock = EM_ASM_INT({ + var socket = new WebSocket(UTF8ToString($1), UTF8ToString($2).split(",")); + var c_ptr = Module.IDHandler.get($0); + socket.binaryType = "arraybuffer"; + + // Connection opened + socket.addEventListener("open", function (event) { + if (!Module.IDHandler.has($0)) + return; // Godot Object is gone! + ccall("_esws_on_connect", + "void", + ["number", "string"], + [c_ptr, socket.protocol] + ); + }); + + // Listen for messages + socket.addEventListener("message", function (event) { + if (!Module.IDHandler.has($0)) + return; // Godot Object is gone! + var buffer; + var is_string = 0; + if (event.data instanceof ArrayBuffer) { + + buffer = new Uint8Array(event.data); + + } else if (event.data instanceof Blob) { + + alert("Blob type not supported"); + return; + + } else if (typeof event.data === "string") { + + is_string = 1; + var enc = new TextEncoder("utf-8"); + buffer = new Uint8Array(enc.encode(event.data)); + + } else { + + alert("Unknown message type"); + return; + + } + var len = buffer.length*buffer.BYTES_PER_ELEMENT; + var out = Module._malloc(len); + Module.HEAPU8.set(buffer, out); + ccall("_esws_on_message", + "void", + ["number", "number", "number", "number"], + [c_ptr, out, len, is_string] + ); + Module._free(out); + }); + + socket.addEventListener("error", function (event) { + if (!Module.IDHandler.has($0)) + return; // Godot Object is gone! + ccall("_esws_on_error", + "void", + ["number"], + [c_ptr] + ); + }); + + socket.addEventListener("close", function (event) { + if (!Module.IDHandler.has($0)) + return; // Godot Object is gone! + var was_clean = 0; + if (event.was_clean) + was_clean = 1; + ccall("_esws_on_close", + "void", + ["number", "number", "string", "number"], + [c_ptr, event.code, event.reason, was_clean] + ); + }); + + return Module.IDHandler.add(socket); + }, _js_id, str.utf8().get_data(), proto_string.utf8().get_data()); + /* clang-format on */ + + static_cast<Ref<EMWSPeer> >(_peer)->set_sock(peer_sock); + + return OK; +}; + +void EMWSClient::poll() { +} + +Ref<WebSocketPeer> EMWSClient::get_peer(int p_peer_id) const { + + return _peer; +} + +NetworkedMultiplayerPeer::ConnectionStatus EMWSClient::get_connection_status() const { + + if (_peer->is_connected_to_host()) + return CONNECTION_CONNECTED; + + if (_is_connecting) + return CONNECTION_CONNECTING; + + return CONNECTION_DISCONNECTED; +}; + +void EMWSClient::disconnect_from_host() { + + _peer->close(); +}; + +IP_Address EMWSClient::get_connected_host() const { + + return IP_Address(); +}; + +uint16_t EMWSClient::get_connected_port() const { + + return 1025; +}; + +EMWSClient::EMWSClient() { + _is_connecting = false; + _peer = Ref<EMWSPeer>(memnew(EMWSPeer)); + /* clang-format off */ + _js_id = EM_ASM_INT({ + return Module.IDHandler.add($0); + }, this); + /* clang-format on */ +}; + +EMWSClient::~EMWSClient() { + + disconnect_from_host(); + _peer = Ref<EMWSPeer>(); + /* clang-format off */ + EM_ASM({ + Module.IDHandler.remove($0); + }, _js_id); + /* clang-format on */ +}; + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/emws_client.h b/modules/websocket/emws_client.h new file mode 100644 index 0000000000..8801f37007 --- /dev/null +++ b/modules/websocket/emws_client.h @@ -0,0 +1,62 @@ +/*************************************************************************/ +/* emws_client.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef EMWSCLIENT_H +#define EMWSCLIENT_H + +#ifdef JAVASCRIPT_ENABLED + +#include "core/error_list.h" +#include "emws_peer.h" +#include "websocket_client.h" + +class EMWSClient : public WebSocketClient { + + GDCIIMPL(EMWSClient, WebSocketClient); + +private: + int _js_id; + +public: + bool _is_connecting; + + Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); + Ref<WebSocketPeer> get_peer(int p_peer_id) const; + void disconnect_from_host(); + IP_Address get_connected_host() const; + uint16_t get_connected_port() const; + virtual ConnectionStatus get_connection_status() const; + virtual void poll(); + EMWSClient(); + ~EMWSClient(); +}; + +#endif // JAVASCRIPT_ENABLED + +#endif // EMWSCLIENT_H diff --git a/modules/websocket/emws_peer.cpp b/modules/websocket/emws_peer.cpp new file mode 100644 index 0000000000..93665e6428 --- /dev/null +++ b/modules/websocket/emws_peer.cpp @@ -0,0 +1,173 @@ +/*************************************************************************/ +/* emws_peer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifdef JAVASCRIPT_ENABLED + +#include "emws_peer.h" +#include "core/io/ip.h" + +void EMWSPeer::set_sock(int p_sock) { + + peer_sock = p_sock; + in_buffer.clear(); + queue_count = 0; +} + +void EMWSPeer::set_write_mode(WriteMode p_mode) { + write_mode = p_mode; +} + +EMWSPeer::WriteMode EMWSPeer::get_write_mode() const { + return write_mode; +} + +void EMWSPeer::read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string) { + + if (in_buffer.space_left() < p_size + 5) { + ERR_EXPLAIN("Buffer full! Dropping data"); + ERR_FAIL(); + } + + uint8_t is_string = p_is_string ? 1 : 0; + in_buffer.write((uint8_t *)&p_size, 4); + in_buffer.write((uint8_t *)&is_string, 1); + in_buffer.write(p_data, p_size); + queue_count++; +} + +Error EMWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + + int is_bin = write_mode == WebSocketPeer::WRITE_MODE_BINARY ? 1 : 0; + + /* clang-format off */ + EM_ASM({ + var sock = Module.IDHandler.get($0); + var bytes_array = new Uint8Array($2); + var i = 0; + + for(i=0; i<$2; i++) { + bytes_array[i] = getValue($1+i, 'i8'); + } + + if ($3) { + sock.send(bytes_array.buffer); + } else { + var string = new TextDecoder("utf-8").decode(bytes_array); + sock.send(string); + } + }, peer_sock, p_buffer, p_buffer_size, is_bin); + /* clang-format on */ + + return OK; +}; + +Error EMWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + + if (queue_count == 0) + return ERR_UNAVAILABLE; + + uint32_t to_read = 0; + uint32_t left = 0; + uint8_t is_string = 0; + r_buffer_size = 0; + + in_buffer.read((uint8_t *)&to_read, 4); + --queue_count; + left = in_buffer.data_left(); + + if (left < to_read + 1) { + in_buffer.advance_read(left); + return FAILED; + } + + in_buffer.read(&is_string, 1); + _was_string = is_string == 1; + in_buffer.read(packet_buffer, to_read); + *r_buffer = packet_buffer; + r_buffer_size = to_read; + + return OK; +}; + +int EMWSPeer::get_available_packet_count() const { + + return queue_count; +}; + +bool EMWSPeer::was_string_packet() const { + + return _was_string; +}; + +bool EMWSPeer::is_connected_to_host() const { + + return peer_sock != -1; +}; + +void EMWSPeer::close() { + + if (peer_sock != -1) { + /* clang-format off */ + EM_ASM({ + var sock = Module.IDHandler.get($0); + sock.close(); + Module.IDHandler.remove($0); + }, peer_sock); + /* clang-format on */ + } + peer_sock = -1; + queue_count = 0; + in_buffer.clear(); +}; + +IP_Address EMWSPeer::get_connected_host() const { + + return IP_Address(); +}; + +uint16_t EMWSPeer::get_connected_port() const { + + return 1025; +}; + +EMWSPeer::EMWSPeer() { + peer_sock = -1; + queue_count = 0; + _was_string = false; + in_buffer.resize(16); + write_mode = WRITE_MODE_BINARY; +}; + +EMWSPeer::~EMWSPeer() { + + in_buffer.resize(0); + close(); +}; + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/emws_peer.h b/modules/websocket/emws_peer.h new file mode 100644 index 0000000000..a50d1874ba --- /dev/null +++ b/modules/websocket/emws_peer.h @@ -0,0 +1,85 @@ +/*************************************************************************/ +/* emws_peer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef EMWSPEER_H +#define EMWSPEER_H + +#ifdef JAVASCRIPT_ENABLED + +#include "core/error_list.h" +#include "core/io/packet_peer.h" +#include "core/ring_buffer.h" +#include "emscripten.h" +#include "websocket_peer.h" + +class EMWSPeer : public WebSocketPeer { + + GDCIIMPL(EMWSPeer, WebSocketPeer); + +private: + enum { + PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for for type + }; + + int peer_sock; + WriteMode write_mode; + + uint8_t packet_buffer[PACKET_BUFFER_SIZE]; + RingBuffer<uint8_t> in_buffer; + int queue_count; + bool _was_string; + +public: + void read_msg(uint8_t *p_data, uint32_t p_size, bool p_is_string); + void set_sock(int sock); + virtual int get_available_packet_count() const; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + virtual int get_max_packet_size() const { return PACKET_BUFFER_SIZE; }; + + virtual void close(); + virtual bool is_connected_to_host() const; + virtual IP_Address get_connected_host() const; + virtual uint16_t get_connected_port() const; + + virtual WriteMode get_write_mode() const; + virtual void set_write_mode(WriteMode p_mode); + virtual bool was_string_packet() const; + + void set_wsi(struct lws *wsi); + Error read_wsi(void *in, size_t len); + Error write_wsi(); + + EMWSPeer(); + ~EMWSPeer(); +}; + +#endif // JAVASCRIPT_ENABLED + +#endif // LSWPEER_H diff --git a/modules/websocket/emws_server.cpp b/modules/websocket/emws_server.cpp new file mode 100644 index 0000000000..60e9133225 --- /dev/null +++ b/modules/websocket/emws_server.cpp @@ -0,0 +1,67 @@ +/*************************************************************************/ +/* emws_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifdef JAVASCRIPT_ENABLED + +#include "emws_server.h" +#include "core/os/os.h" + +Error EMWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { + + return FAILED; +} + +bool EMWSServer::is_listening() const { + return false; +} + +void EMWSServer::stop() { +} + +bool EMWSServer::has_peer(int p_id) const { + return false; +} + +Ref<WebSocketPeer> EMWSServer::get_peer(int p_id) const { + return NULL; +} + +PoolVector<String> EMWSServer::get_protocols() const { + PoolVector<String> out; + + return out; +} + +EMWSServer::EMWSServer() { +} + +EMWSServer::~EMWSServer() { +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/emws_server.h b/modules/websocket/emws_server.h new file mode 100644 index 0000000000..59f1d76346 --- /dev/null +++ b/modules/websocket/emws_server.h @@ -0,0 +1,58 @@ +/*************************************************************************/ +/* emws_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef EMWSSERVER_H +#define EMWSSERVER_H + +#ifdef JAVASCRIPT_ENABLED + +#include "core/reference.h" +#include "emws_peer.h" +#include "websocket_server.h" + +class EMWSServer : public WebSocketServer { + + GDCIIMPL(EMWSServer, WebSocketServer); + +public: + Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); + void stop(); + bool is_listening() const; + bool has_peer(int p_id) const; + Ref<WebSocketPeer> get_peer(int p_id) const; + virtual void poll(); + virtual PoolVector<String> get_protocols() const; + + EMWSServer(); + ~EMWSServer(); +}; + +#endif + +#endif // LWSSERVER_H diff --git a/modules/websocket/lws_client.cpp b/modules/websocket/lws_client.cpp new file mode 100644 index 0000000000..604b1886ad --- /dev/null +++ b/modules/websocket/lws_client.cpp @@ -0,0 +1,203 @@ +/*************************************************************************/ +/* lws_client.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef JAVASCRIPT_ENABLED + +#include "lws_client.h" +#include "core/io/ip.h" + +Error LWSClient::connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocols) { + + ERR_FAIL_COND_V(context != NULL, FAILED); + + IP_Address addr; + + if (!p_host.is_valid_ip_address()) { + addr = IP::get_singleton()->resolve_hostname(p_host); + } else { + addr = p_host; + } + + ERR_FAIL_COND_V(!addr.is_valid(), ERR_INVALID_PARAMETER); + + // prepare protocols + if (p_protocols.size() == 0) // default to binary protocol + p_protocols.append("binary"); + _lws_make_protocols(this, &LWSClient::_lws_gd_callback, p_protocols, &_lws_ref); + + // init lws client + struct lws_context_creation_info info; + struct lws_client_connect_info i; + + memset(&i, 0, sizeof i); + memset(&info, 0, sizeof info); + + info.port = CONTEXT_PORT_NO_LISTEN; + info.protocols = _lws_ref->lws_structs; + info.gid = -1; + info.uid = -1; + //info.ws_ping_pong_interval = 5; + info.user = _lws_ref; + context = lws_create_context(&info); + + if (context == NULL) { + _lws_free_ref(_lws_ref); + _lws_ref = NULL; + ERR_EXPLAIN("Unable to create lws context"); + ERR_FAIL_V(FAILED); + } + + char abuf[1024]; + char hbuf[1024]; + char pbuf[2048]; + String addr_str = (String)addr; + strncpy(abuf, addr_str.ascii().get_data(), 1024); + strncpy(hbuf, p_host.utf8().get_data(), 1024); + strncpy(pbuf, p_path.utf8().get_data(), 2048); + + i.context = context; + i.protocol = _lws_ref->lws_names; + i.address = abuf; + i.host = hbuf; + i.path = pbuf; + i.port = p_port; + i.ssl_connection = p_ssl; + + lws_client_connect_via_info(&i); + return OK; +}; + +void LWSClient::poll() { + + _lws_poll(); +} + +int LWSClient::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { + + Ref<LWSPeer> peer = static_cast<Ref<LWSPeer> >(_peer); + LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; + + switch (reason) { + + case LWS_CALLBACK_CLIENT_ESTABLISHED: + peer->set_wsi(wsi); + peer_data->peer_id = 0; + peer_data->in_size = 0; + peer_data->in_count = 0; + peer_data->out_count = 0; + peer_data->rbw.resize(16); + peer_data->rbr.resize(16); + peer_data->force_close = false; + _on_connect(lws_get_protocol(wsi)->name); + break; + + case LWS_CALLBACK_CLIENT_CONNECTION_ERROR: + _on_error(); + destroy_context(); + return -1; // we should close the connection (would probably happen anyway) + + case LWS_CALLBACK_CLOSED: + peer_data->in_count = 0; + peer_data->out_count = 0; + peer_data->rbw.resize(0); + peer_data->rbr.resize(0); + peer->close(); + destroy_context(); + _on_disconnect(); + return 0; // we can end here + + case LWS_CALLBACK_CLIENT_RECEIVE: + peer->read_wsi(in, len); + if (peer->get_available_packet_count() > 0) + _on_peer_packet(); + break; + + case LWS_CALLBACK_CLIENT_WRITEABLE: + if (peer_data->force_close) + return -1; + + peer->write_wsi(); + break; + + default: + break; + } + + return 0; +} + +Ref<WebSocketPeer> LWSClient::get_peer(int p_peer_id) const { + + return _peer; +} + +NetworkedMultiplayerPeer::ConnectionStatus LWSClient::get_connection_status() const { + + if (context == NULL) + return CONNECTION_DISCONNECTED; + + if (_peer->is_connected_to_host()) + return CONNECTION_CONNECTED; + + return CONNECTION_CONNECTING; +} + +void LWSClient::disconnect_from_host() { + + if (context == NULL) + return; + + _peer->close(); + destroy_context(); +}; + +IP_Address LWSClient::get_connected_host() const { + + return IP_Address(); +}; + +uint16_t LWSClient::get_connected_port() const { + + return 1025; +}; + +LWSClient::LWSClient() { + context = NULL; + _lws_ref = NULL; + _peer = Ref<LWSPeer>(memnew(LWSPeer)); +}; + +LWSClient::~LWSClient() { + + invalidate_lws_ref(); // We do not want any more callback + disconnect_from_host(); + _peer = Ref<LWSPeer>(); +}; + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_client.h b/modules/websocket/lws_client.h new file mode 100644 index 0000000000..2e082175df --- /dev/null +++ b/modules/websocket/lws_client.h @@ -0,0 +1,61 @@ +/*************************************************************************/ +/* lws_client.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef LWSCLIENT_H +#define LWSCLIENT_H + +#ifndef JAVASCRIPT_ENABLED + +#include "core/error_list.h" +#include "lws_helper.h" +#include "lws_peer.h" +#include "websocket_client.h" + +class LWSClient : public WebSocketClient { + + GDCIIMPL(LWSClient, WebSocketClient); + + LWS_HELPER(LWSClient); + +public: + Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()); + Ref<WebSocketPeer> get_peer(int p_peer_id) const; + void disconnect_from_host(); + IP_Address get_connected_host() const; + uint16_t get_connected_port() const; + virtual ConnectionStatus get_connection_status() const; + virtual void poll(); + + LWSClient(); + ~LWSClient(); +}; + +#endif // JAVASCRIPT_ENABLED + +#endif // LWSCLIENT_H diff --git a/modules/websocket/lws_helper.h b/modules/websocket/lws_helper.h new file mode 100644 index 0000000000..ac0c340aa9 --- /dev/null +++ b/modules/websocket/lws_helper.h @@ -0,0 +1,214 @@ +/*************************************************************************/ +/* lws_helper.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef LWS_HELPER_H +#define LWS_HELPER_H + +#include "core/io/stream_peer.h" +#include "core/os/os.h" +#include "core/reference.h" +#include "core/ring_buffer.h" +#include "lws_peer.h" + +struct _LWSRef { + bool free_context; + bool is_polling; + bool is_valid; + bool is_destroying; + void *obj; + struct lws_protocols *lws_structs; + char *lws_names; +}; + +static _LWSRef *_lws_create_ref(void *obj) { + + _LWSRef *out = (_LWSRef *)memalloc(sizeof(_LWSRef)); + out->is_destroying = false; + out->free_context = false; + out->is_polling = false; + out->obj = obj; + out->is_valid = true; + out->lws_structs = NULL; + out->lws_names = NULL; + return out; +} + +static void _lws_free_ref(_LWSRef *ref) { + // Free strings and structs + memfree(ref->lws_structs); + memfree(ref->lws_names); + // Free ref + memfree(ref); +} + +static bool _lws_destroy(struct lws_context *context, _LWSRef *ref) { + if (context == NULL || ref->is_destroying) + return false; + + if (ref->is_polling) { + ref->free_context = true; + return false; + } + + ref->is_destroying = true; + lws_context_destroy(context); + _lws_free_ref(ref); + return true; +} + +static bool _lws_poll(struct lws_context *context, _LWSRef *ref) { + + ERR_FAIL_COND_V(context == NULL, false); + ERR_FAIL_COND_V(ref == NULL, false); + + ref->is_polling = true; + lws_service(context, 0); + ref->is_polling = false; + + if (!ref->free_context) + return false; // Nothing to do + + bool is_valid = ref->is_valid; // Might have been destroyed by poll + + _lws_destroy(context, ref); // Will destroy context and ref + + return is_valid; // If the object should NULL its context and ref +} + +/* + * prepare the protocol_structs to be fed to context + * also prepare the protocol string used by the client + */ +static void _lws_make_protocols(void *p_obj, lws_callback_function *p_callback, PoolVector<String> p_names, _LWSRef **r_lws_ref) { + /* the input strings might go away after this call, + * we need to copy them. Will clear them when + * detroying the context */ + int i; + int len = p_names.size(); + size_t data_size = sizeof(struct LWSPeer::PeerData); + PoolVector<String>::Read pnr = p_names.read(); + + /* + * This is a reference connecting the object with lws + * keep track of status, mallocs, etc. + * Must survive as long the context + * Must be freed manually when context creation fails. + */ + _LWSRef *ref = _lws_create_ref(p_obj); + + /* LWS protocol structs */ + ref->lws_structs = (struct lws_protocols *)memalloc(sizeof(struct lws_protocols) * (len + 2)); + + CharString strings = p_names.join(",").ascii(); + int str_len = strings.length(); + + /* Joined string of protocols, double the size: comma separated first, NULL separated last */ + ref->lws_names = (char *)memalloc((str_len + 1) * 2); /* plus the terminator */ + + char *names_ptr = ref->lws_names; + struct lws_protocols *structs_ptr = ref->lws_structs; + + copymem(names_ptr, strings.get_data(), str_len); + names_ptr[str_len] = '\0'; /* NULL terminator */ + /* NULL terminated strings to be used in protocol structs */ + copymem(&names_ptr[str_len + 1], strings.get_data(), str_len); + names_ptr[(str_len * 2) + 1] = '\0'; /* NULL terminator */ + int pos = str_len + 1; + + /* the first protocol is always http-only */ + structs_ptr[0].name = "http-only"; + structs_ptr[0].callback = p_callback; + structs_ptr[0].per_session_data_size = data_size; + structs_ptr[0].rx_buffer_size = 0; + /* add user defined protocols */ + for (i = 0; i < len; i++) { + structs_ptr[i + 1].name = (const char *)&names_ptr[pos]; + structs_ptr[i + 1].callback = p_callback; + structs_ptr[i + 1].per_session_data_size = data_size; + structs_ptr[i + 1].rx_buffer_size = 0; + pos += pnr[i].ascii().length() + 1; + names_ptr[pos - 1] = '\0'; + } + /* add protocols terminator */ + structs_ptr[len + 1].name = NULL; + structs_ptr[len + 1].callback = NULL; + structs_ptr[len + 1].per_session_data_size = 0; + structs_ptr[len + 1].rx_buffer_size = 0; + + *r_lws_ref = ref; +} + +/* clang-format off */ +#define LWS_HELPER(CNAME) \ +protected: \ + struct _LWSRef *_lws_ref; \ + struct lws_context *context; \ + \ + static int _lws_gd_callback(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { \ + \ + if (wsi == NULL) { \ + return 0; \ + } \ + \ + struct _LWSRef *ref = (struct _LWSRef *)lws_context_user(lws_get_context(wsi)); \ + if (!ref->is_valid) \ + return 0; \ + CNAME *helper = (CNAME *)ref->obj; \ + return helper->_handle_cb(wsi, reason, user, in, len); \ + } \ + \ + void invalidate_lws_ref() { \ + if (_lws_ref != NULL) \ + _lws_ref->is_valid = false; \ + } \ + \ + void destroy_context() { \ + if (_lws_destroy(context, _lws_ref)) { \ + context = NULL; \ + _lws_ref = NULL; \ + } \ + } \ + \ +public: \ + virtual int _handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len); \ + \ + void _lws_poll() { \ + ERR_FAIL_COND(context == NULL); \ + \ + if (::_lws_poll(context, _lws_ref)) { \ + context = NULL; \ + _lws_ref = NULL; \ + } \ + } \ + \ +protected: + + /* clang-format on */ + +#endif // LWS_HELPER_H diff --git a/modules/websocket/lws_peer.cpp b/modules/websocket/lws_peer.cpp new file mode 100644 index 0000000000..fdaa79f9d4 --- /dev/null +++ b/modules/websocket/lws_peer.cpp @@ -0,0 +1,200 @@ +/*************************************************************************/ +/* lws_peer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef JAVASCRIPT_ENABLED + +#include "lws_peer.h" +#include "core/io/ip.h" + +void LWSPeer::set_wsi(struct lws *p_wsi) { + wsi = p_wsi; +}; + +void LWSPeer::set_write_mode(WriteMode p_mode) { + write_mode = p_mode; +} + +LWSPeer::WriteMode LWSPeer::get_write_mode() const { + return write_mode; +} + +Error LWSPeer::read_wsi(void *in, size_t len) { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + PeerData *peer_data = (PeerData *)(lws_wsi_user(wsi)); + uint32_t size = peer_data->in_size; + uint8_t is_string = lws_frame_is_binary(wsi) ? 0 : 1; + + if (peer_data->rbr.space_left() < len + 5) { + ERR_EXPLAIN("Buffer full! Dropping data"); + ERR_FAIL_V(FAILED); + } + + copymem(&(peer_data->input_buffer[size]), in, len); + size += len; + + peer_data->in_size = size; + if (lws_is_final_fragment(wsi)) { + peer_data->rbr.write((uint8_t *)&size, 4); + peer_data->rbr.write((uint8_t *)&is_string, 1); + peer_data->rbr.write(peer_data->input_buffer, size); + peer_data->in_count++; + peer_data->in_size = 0; + } + + return OK; +} + +Error LWSPeer::write_wsi() { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + PeerData *peer_data = (PeerData *)(lws_wsi_user(wsi)); + PoolVector<uint8_t> tmp; + int left = peer_data->rbw.data_left(); + uint32_t to_write = 0; + + if (left == 0 || peer_data->out_count == 0) + return OK; + + peer_data->rbw.read((uint8_t *)&to_write, 4); + peer_data->out_count--; + + if (left < to_write) { + peer_data->rbw.advance_read(left); + return FAILED; + } + + tmp.resize(LWS_PRE + to_write); + peer_data->rbw.read(&(tmp.write()[LWS_PRE]), to_write); + lws_write(wsi, &(tmp.write()[LWS_PRE]), to_write, (enum lws_write_protocol)write_mode); + tmp.resize(0); + + if (peer_data->out_count > 0) + lws_callback_on_writable(wsi); // we want to write more! + + return OK; +} + +Error LWSPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + PeerData *peer_data = (PeerData *)lws_wsi_user(wsi); + peer_data->rbw.write((uint8_t *)&p_buffer_size, 4); + peer_data->rbw.write(p_buffer, MIN(p_buffer_size, peer_data->rbw.space_left())); + peer_data->out_count++; + + lws_callback_on_writable(wsi); // notify that we want to write + return OK; +}; + +Error LWSPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + + ERR_FAIL_COND_V(!is_connected_to_host(), FAILED); + + PeerData *peer_data = (PeerData *)lws_wsi_user(wsi); + + if (peer_data->in_count == 0) + return ERR_UNAVAILABLE; + + uint32_t to_read = 0; + uint32_t left = 0; + uint8_t is_string = 0; + r_buffer_size = 0; + + peer_data->rbr.read((uint8_t *)&to_read, 4); + peer_data->in_count--; + left = peer_data->rbr.data_left(); + + if (left < to_read + 1) { + peer_data->rbr.advance_read(left); + return FAILED; + } + + peer_data->rbr.read(&is_string, 1); + peer_data->rbr.read(packet_buffer, to_read); + *r_buffer = packet_buffer; + r_buffer_size = to_read; + _was_string = is_string; + + return OK; +}; + +int LWSPeer::get_available_packet_count() const { + + if (!is_connected_to_host()) + return 0; + + return ((PeerData *)lws_wsi_user(wsi))->in_count; +}; + +bool LWSPeer::was_string_packet() const { + + return _was_string; +}; + +bool LWSPeer::is_connected_to_host() const { + + return wsi != NULL; +}; + +void LWSPeer::close() { + if (wsi != NULL) { + struct lws *tmp = wsi; + PeerData *data = ((PeerData *)lws_wsi_user(wsi)); + data->force_close = true; + wsi = NULL; + lws_callback_on_writable(tmp); // notify that we want to disconnect + } +}; + +IP_Address LWSPeer::get_connected_host() const { + + return IP_Address(); +}; + +uint16_t LWSPeer::get_connected_port() const { + + return 1025; +}; + +LWSPeer::LWSPeer() { + wsi = NULL; + _was_string = false; + write_mode = WRITE_MODE_BINARY; +}; + +LWSPeer::~LWSPeer() { + + close(); +}; + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_peer.h b/modules/websocket/lws_peer.h new file mode 100644 index 0000000000..0a62b65d24 --- /dev/null +++ b/modules/websocket/lws_peer.h @@ -0,0 +1,92 @@ +/*************************************************************************/ +/* lws_peer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef LWSPEER_H +#define LWSPEER_H + +#ifndef JAVASCRIPT_ENABLED + +#include "core/error_list.h" +#include "core/io/packet_peer.h" +#include "core/ring_buffer.h" +#include "libwebsockets.h" +#include "lws_config.h" +#include "websocket_peer.h" + +class LWSPeer : public WebSocketPeer { + + GDCIIMPL(LWSPeer, WebSocketPeer); + +private: + enum { + PACKET_BUFFER_SIZE = 65536 - 5 // 4 bytes for the size, 1 for the type + }; + + uint8_t packet_buffer[PACKET_BUFFER_SIZE]; + struct lws *wsi; + WriteMode write_mode; + bool _was_string; + +public: + struct PeerData { + uint32_t peer_id; + bool force_close; + RingBuffer<uint8_t> rbw; + RingBuffer<uint8_t> rbr; + mutable uint8_t input_buffer[PACKET_BUFFER_SIZE]; + uint32_t in_size; + int in_count; + int out_count; + }; + + virtual int get_available_packet_count() const; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + virtual int get_max_packet_size() const { return PACKET_BUFFER_SIZE; }; + + virtual void close(); + virtual bool is_connected_to_host() const; + virtual IP_Address get_connected_host() const; + virtual uint16_t get_connected_port() const; + + virtual WriteMode get_write_mode() const; + virtual void set_write_mode(WriteMode p_mode); + virtual bool was_string_packet() const; + + void set_wsi(struct lws *wsi); + Error read_wsi(void *in, size_t len); + Error write_wsi(); + + LWSPeer(); + ~LWSPeer(); +}; + +#endif // JAVASCRIPT_ENABLED + +#endif // LSWPEER_H diff --git a/modules/websocket/lws_server.cpp b/modules/websocket/lws_server.cpp new file mode 100644 index 0000000000..8a47ba557d --- /dev/null +++ b/modules/websocket/lws_server.cpp @@ -0,0 +1,177 @@ +/*************************************************************************/ +/* lws_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef JAVASCRIPT_ENABLED + +#include "lws_server.h" +#include "core/os/os.h" + +Error LWSServer::listen(int p_port, PoolVector<String> p_protocols, bool gd_mp_api) { + + ERR_FAIL_COND_V(context != NULL, FAILED); + + _is_multiplayer = gd_mp_api; + + struct lws_context_creation_info info; + memset(&info, 0, sizeof info); + + if (p_protocols.size() == 0) // default to binary protocol + p_protocols.append(String("binary")); + + // Prepare lws protocol structs + _lws_make_protocols(this, &LWSServer::_lws_gd_callback, p_protocols, &_lws_ref); + + info.port = p_port; + info.user = _lws_ref; + info.protocols = _lws_ref->lws_structs; + info.gid = -1; + info.uid = -1; + //info.ws_ping_pong_interval = 5; + + context = lws_create_context(&info); + + if (context == NULL) { + _lws_free_ref(_lws_ref); + _lws_ref = NULL; + ERR_EXPLAIN("Unable to create LWS context"); + ERR_FAIL_V(FAILED); + } + + return OK; +} + +bool LWSServer::is_listening() const { + return context != NULL; +} + +int LWSServer::_handle_cb(struct lws *wsi, enum lws_callback_reasons reason, void *user, void *in, size_t len) { + + LWSPeer::PeerData *peer_data = (LWSPeer::PeerData *)user; + + switch (reason) { + case LWS_CALLBACK_HTTP: + // no http for now + // closing immediately returning -1; + return -1; + + case LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION: + // check header here? + break; + + case LWS_CALLBACK_ESTABLISHED: { + int32_t id = _gen_unique_id(); + + Ref<LWSPeer> peer = Ref<LWSPeer>(memnew(LWSPeer)); + peer->set_wsi(wsi); + _peer_map[id] = peer; + + peer_data->peer_id = id; + peer_data->in_size = 0; + peer_data->in_count = 0; + peer_data->out_count = 0; + peer_data->rbw.resize(16); + peer_data->rbr.resize(16); + peer_data->force_close = false; + + _on_connect(id, lws_get_protocol(wsi)->name); + break; + } + + case LWS_CALLBACK_CLOSED: { + if (peer_data == NULL) + return 0; + int32_t id = peer_data->peer_id; + if (_peer_map.has(id)) { + _peer_map[id]->close(); + _peer_map.erase(id); + } + peer_data->in_count = 0; + peer_data->out_count = 0; + peer_data->rbr.resize(0); + peer_data->rbw.resize(0); + _on_disconnect(id); + return 0; // we can end here + } + + case LWS_CALLBACK_RECEIVE: { + int32_t id = peer_data->peer_id; + if (_peer_map.has(id)) { + static_cast<Ref<LWSPeer> >(_peer_map[id])->read_wsi(in, len); + if (_peer_map[id]->get_available_packet_count() > 0) + _on_peer_packet(id); + } + break; + } + + case LWS_CALLBACK_SERVER_WRITEABLE: { + if (peer_data->force_close) + return -1; + + int id = peer_data->peer_id; + if (_peer_map.has(id)) + static_cast<Ref<LWSPeer> >(_peer_map[id])->write_wsi(); + break; + } + + default: + break; + } + + return 0; +} + +void LWSServer::stop() { + if (context == NULL) + return; + + _peer_map.clear(); + destroy_context(); + context = NULL; +} + +bool LWSServer::has_peer(int p_id) const { + return _peer_map.has(p_id); +} + +Ref<WebSocketPeer> LWSServer::get_peer(int p_id) const { + ERR_FAIL_COND_V(!has_peer(p_id), NULL); + return _peer_map[p_id]; +} + +LWSServer::LWSServer() { + context = NULL; + _lws_ref = NULL; +} + +LWSServer::~LWSServer() { + invalidate_lws_ref(); // we do not want any more callbacks + stop(); +} + +#endif // JAVASCRIPT_ENABLED diff --git a/modules/websocket/lws_server.h b/modules/websocket/lws_server.h new file mode 100644 index 0000000000..5f7ac4850a --- /dev/null +++ b/modules/websocket/lws_server.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* lws_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef LWSSERVER_H +#define LWSSERVER_H + +#ifndef JAVASCRIPT_ENABLED + +#include "core/reference.h" +#include "lws_helper.h" +#include "lws_peer.h" +#include "websocket_server.h" + +class LWSServer : public WebSocketServer { + + GDCIIMPL(LWSServer, WebSocketServer); + + LWS_HELPER(LWSServer); + +private: + Map<int, Ref<LWSPeer> > peer_map; + +public: + Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); + void stop(); + bool is_listening() const; + bool has_peer(int p_id) const; + Ref<WebSocketPeer> get_peer(int p_id) const; + virtual void poll() { _lws_poll(); } + + LWSServer(); + ~LWSServer(); +}; + +#endif // JAVASCRIPT_ENABLED + +#endif // LWSSERVER_H diff --git a/modules/websocket/register_types.cpp b/modules/websocket/register_types.cpp new file mode 100644 index 0000000000..39d03ff1f0 --- /dev/null +++ b/modules/websocket/register_types.cpp @@ -0,0 +1,79 @@ +/*************************************************************************/ +/* register_types.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "register_types.h" +#include "error_macros.h" +#ifdef JAVASCRIPT_ENABLED +#include "emscripten.h" +#include "emws_client.h" +#include "emws_peer.h" +#include "emws_server.h" +#else +#include "lws_client.h" +#include "lws_peer.h" +#include "lws_server.h" +#endif + +void register_websocket_types() { +#ifdef JAVASCRIPT_ENABLED + EM_ASM({ + var IDHandler = {}; + IDHandler["ids"] = {}; + IDHandler["has"] = function(id) { + return IDHandler.ids.hasOwnProperty(id); + }; + IDHandler["add"] = function(obj) { + var id = crypto.getRandomValues(new Int32Array(32))[0]; + IDHandler.ids[id] = obj; + return id; + }; + IDHandler["get"] = function(id) { + return IDHandler.ids[id]; + }; + IDHandler["remove"] = function(id) { + delete IDHandler.ids[id]; + }; + Module["IDHandler"] = IDHandler; + }); + EMWSPeer::make_default(); + EMWSClient::make_default(); + EMWSServer::make_default(); +#else + LWSPeer::make_default(); + LWSClient::make_default(); + LWSServer::make_default(); +#endif + + ClassDB::register_virtual_class<WebSocketMultiplayerPeer>(); + ClassDB::register_custom_instance_class<WebSocketServer>(); + ClassDB::register_custom_instance_class<WebSocketClient>(); + ClassDB::register_custom_instance_class<WebSocketPeer>(); +} + +void unregister_websocket_types() {} diff --git a/modules/websocket/register_types.h b/modules/websocket/register_types.h new file mode 100644 index 0000000000..010d88789b --- /dev/null +++ b/modules/websocket/register_types.h @@ -0,0 +1,31 @@ +/*************************************************************************/ +/* register_types.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +void register_websocket_types(); +void unregister_websocket_types(); diff --git a/modules/websocket/websocket_client.cpp b/modules/websocket/websocket_client.cpp new file mode 100644 index 0000000000..f92a386988 --- /dev/null +++ b/modules/websocket/websocket_client.cpp @@ -0,0 +1,124 @@ +/*************************************************************************/ +/* websocket_client.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "websocket_client.h" + +GDCINULL(WebSocketClient); + +WebSocketClient::WebSocketClient() { +} + +WebSocketClient::~WebSocketClient() { +} + +Error WebSocketClient::connect_to_url(String p_url, PoolVector<String> p_protocols, bool gd_mp_api) { + _is_multiplayer = gd_mp_api; + + String host = p_url; + String path = "/"; + int p_len = -1; + int port = 80; + bool ssl = false; + if (host.begins_with("wss://")) { + ssl = true; // we should implement this + host = host.substr(6, host.length() - 6); + port = 443; + } else { + ssl = false; + if (host.begins_with("ws://")) + host = host.substr(5, host.length() - 5); + } + + // Path + p_len = host.find("/"); + if (p_len != -1) { + path = host.substr(p_len, host.length() - p_len); + host = host.substr(0, p_len); + } + + // Port + p_len = host.find_last(":"); + if (p_len != -1 && p_len == host.find(":")) { + port = host.substr(p_len, host.length() - p_len).to_int(); + host = host.substr(0, p_len); + } + + return connect_to_host(host, path, port, ssl, p_protocols); +} + +bool WebSocketClient::is_server() const { + + return false; +} + +void WebSocketClient::_on_peer_packet() { + + if (_is_multiplayer) { + _process_multiplayer(get_peer(1), 1); + } else { + emit_signal("data_received"); + } +} + +void WebSocketClient::_on_connect(String p_protocol) { + + if (_is_multiplayer) { + // need to wait for ID confirmation... + } else { + emit_signal("connection_established", p_protocol); + } +} + +void WebSocketClient::_on_disconnect() { + + if (_is_multiplayer) { + emit_signal("connection_failed"); + } else { + emit_signal("connection_closed"); + } +} + +void WebSocketClient::_on_error() { + + if (_is_multiplayer) { + emit_signal("connection_failed"); + } else { + emit_signal("connection_error"); + } +} + +void WebSocketClient::_bind_methods() { + ClassDB::bind_method(D_METHOD("connect_to_url", "url", "protocols", "gd_mp_api"), &WebSocketClient::connect_to_url, DEFVAL(PoolVector<String>()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("disconnect_from_host"), &WebSocketClient::disconnect_from_host); + + ADD_SIGNAL(MethodInfo("data_received")); + ADD_SIGNAL(MethodInfo("connection_established", PropertyInfo(Variant::STRING, "protocol"))); + ADD_SIGNAL(MethodInfo("connection_closed")); + ADD_SIGNAL(MethodInfo("connection_error")); +} diff --git a/modules/websocket/websocket_client.h b/modules/websocket/websocket_client.h new file mode 100644 index 0000000000..0e87825222 --- /dev/null +++ b/modules/websocket/websocket_client.h @@ -0,0 +1,68 @@ +/*************************************************************************/ +/* websocket_client.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef WEBSOCKET_CLIENT_H +#define WEBSOCKET_CLIENT_H + +#include "core/error_list.h" +#include "websocket_multiplayer.h" +#include "websocket_peer.h" + +class WebSocketClient : public WebSocketMultiplayerPeer { + + GDCLASS(WebSocketClient, WebSocketMultiplayerPeer); + GDCICLASS(WebSocketClient); + +protected: + Ref<WebSocketPeer> _peer; + + static void _bind_methods(); + +public: + Error connect_to_url(String p_url, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false); + + virtual void poll() = 0; + virtual Error connect_to_host(String p_host, String p_path, uint16_t p_port, bool p_ssl, PoolVector<String> p_protocol = PoolVector<String>()) = 0; + virtual void disconnect_from_host() = 0; + virtual IP_Address get_connected_host() const = 0; + virtual uint16_t get_connected_port() const = 0; + + virtual bool is_server() const; + virtual ConnectionStatus get_connection_status() const = 0; + + void _on_peer_packet(); + void _on_connect(String p_protocol); + void _on_disconnect(); + void _on_error(); + + WebSocketClient(); + ~WebSocketClient(); +}; + +#endif // WEBSOCKET_CLIENT_H diff --git a/modules/websocket/websocket_macros.h b/modules/websocket/websocket_macros.h new file mode 100644 index 0000000000..b5c2159806 --- /dev/null +++ b/modules/websocket/websocket_macros.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* websocket_macros.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef WEBSOCKETMACTOS_H +#define WEBSOCKETMACTOS_H + +/* clang-format off */ +#define GDCICLASS(CNAME) \ +public:\ + static CNAME *(*_create)();\ +\ + static Ref<CNAME > create_ref() {\ +\ + if (!_create)\ + return Ref<CNAME >();\ + return Ref<CNAME >(_create());\ + }\ +\ + static CNAME *create() {\ +\ + if (!_create)\ + return NULL;\ + return _create();\ + }\ +protected:\ + +#define GDCINULL(CNAME) \ +CNAME *(*CNAME::_create)() = NULL; + +#define GDCIIMPL(IMPNAME, CNAME) \ +public:\ + static CNAME *_create() { return memnew(IMPNAME); }\ + static void make_default() { CNAME::_create = IMPNAME::_create; }\ +protected:\ +/* clang-format on */ + +#endif // WEBSOCKETMACTOS_H diff --git a/modules/websocket/websocket_multiplayer.cpp b/modules/websocket/websocket_multiplayer.cpp new file mode 100644 index 0000000000..8cd4dff38b --- /dev/null +++ b/modules/websocket/websocket_multiplayer.cpp @@ -0,0 +1,361 @@ +/*************************************************************************/ +/* websocket_multiplayer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "websocket_multiplayer.h" +#include "core/os/os.h" + +WebSocketMultiplayerPeer::WebSocketMultiplayerPeer() { + + _is_multiplayer = false; + _peer_id = 0; + _target_peer = 0; + _refusing = false; + + _current_packet.source = 0; + _current_packet.destination = 0; + _current_packet.size = 0; + _current_packet.data = NULL; +} + +WebSocketMultiplayerPeer::~WebSocketMultiplayerPeer() { + + _clear(); +} + +int WebSocketMultiplayerPeer::_gen_unique_id() const { + + uint32_t hash = 0; + + while (hash == 0 || hash == 1) { + + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_ticks_usec()); + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_unix_time(), hash); + hash = hash_djb2_one_32( + (uint32_t)OS::get_singleton()->get_data_path().hash64(), hash); + hash = hash_djb2_one_32( + (uint32_t)((uint64_t)this), hash); //rely on aslr heap + hash = hash_djb2_one_32( + (uint32_t)((uint64_t)&hash), hash); //rely on aslr stack + hash = hash & 0x7FFFFFFF; // make it compatible with unsigned, since negatie id is used for exclusion + } + + return hash; +} +void WebSocketMultiplayerPeer::_clear() { + + _peer_map.clear(); + if (_current_packet.data != NULL) + memfree(_current_packet.data); + + for (List<Packet>::Element *E = _incoming_packets.front(); E; E = E->next()) { + memfree(E->get().data); + E->get().data = NULL; + } + + _incoming_packets.clear(); +} + +void WebSocketMultiplayerPeer::_bind_methods() { + + ClassDB::bind_method(D_METHOD("get_peer", "peer_id"), &WebSocketMultiplayerPeer::get_peer); + + ADD_SIGNAL(MethodInfo("peer_packet", PropertyInfo(Variant::INT, "peer_source"))); +} + +// +// PacketPeer +// +int WebSocketMultiplayerPeer::get_available_packet_count() const { + + ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + + return _incoming_packets.size(); +} + +int WebSocketMultiplayerPeer::get_max_packet_size() const { + + ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + + return MAX_PACKET_SIZE; +} + +Error WebSocketMultiplayerPeer::get_packet(const uint8_t **r_buffer, int &r_buffer_size) { + + r_buffer_size = 0; + ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + + if (_current_packet.data != NULL) { + memfree(_current_packet.data); + _current_packet.data = NULL; + } + + _current_packet = _incoming_packets.front()->get(); + _incoming_packets.pop_front(); + + *r_buffer = _current_packet.data; + r_buffer_size = _current_packet.size; + + return OK; +} + +Error WebSocketMultiplayerPeer::put_packet(const uint8_t *p_buffer, int p_buffer_size) { + + ERR_FAIL_COND_V(!_is_multiplayer, ERR_UNCONFIGURED); + + PoolVector<uint8_t> buffer = _make_pkt(SYS_NONE, get_unique_id(), _target_peer, p_buffer, p_buffer_size); + + if (is_server()) { + return _server_relay(1, _target_peer, &(buffer.read()[0]), buffer.size()); + } else { + return get_peer(1)->put_packet(&(buffer.read()[0]), buffer.size()); + } +} + +// +// NetworkedMultiplayerPeer +// +void WebSocketMultiplayerPeer::set_transfer_mode(TransferMode p_mode) { + + // Websocket uses TCP, reliable +} + +NetworkedMultiplayerPeer::TransferMode WebSocketMultiplayerPeer::get_transfer_mode() const { + + // Websocket uses TCP, reliable + return TRANSFER_MODE_RELIABLE; +} + +void WebSocketMultiplayerPeer::set_target_peer(int p_target_peer) { + + _target_peer = p_target_peer; +} + +int WebSocketMultiplayerPeer::get_packet_peer() const { + + ERR_FAIL_COND_V(!_is_multiplayer, 1); + ERR_FAIL_COND_V(_incoming_packets.size() == 0, 1); + + return _incoming_packets.front()->get().source; +} + +int WebSocketMultiplayerPeer::get_unique_id() const { + + return _peer_id; +} + +void WebSocketMultiplayerPeer::set_refuse_new_connections(bool p_enable) { + + _refusing = p_enable; +} + +bool WebSocketMultiplayerPeer::is_refusing_new_connections() const { + + return _refusing; +} + +void WebSocketMultiplayerPeer::_send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id) { + + ERR_FAIL_COND(!p_peer.is_valid()); + ERR_FAIL_COND(!p_peer->is_connected_to_host()); + + PoolVector<uint8_t> message = _make_pkt(p_type, 1, 0, (uint8_t *)&p_peer_id, 4); + p_peer->put_packet(&(message.read()[0]), message.size()); +} + +PoolVector<uint8_t> WebSocketMultiplayerPeer::_make_pkt(uint32_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size) { + + PoolVector<uint8_t> out; + out.resize(PROTO_SIZE + p_data_size); + + PoolVector<uint8_t>::Write w = out.write(); + copymem(&w[0], &p_type, 1); + copymem(&w[1], &p_from, 4); + copymem(&w[5], &p_to, 4); + copymem(&w[PROTO_SIZE], p_data, p_data_size); + + return out; +} + +void WebSocketMultiplayerPeer::_send_add(int32_t p_peer_id) { + + // First of all, confirm the ID! + _send_sys(get_peer(p_peer_id), SYS_ID, p_peer_id); + + // Then send the server peer (which will trigger connection_succeded in client) + _send_sys(get_peer(p_peer_id), SYS_ADD, 1); + + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + uint32_t id = E->key(); + if (p_peer_id == id) + continue; // Skip the newwly added peer (already confirmed) + + // Send new peer to others + _send_sys(get_peer(id), SYS_ADD, p_peer_id); + // Send others to new peer + _send_sys(get_peer(p_peer_id), SYS_ADD, id); + } +} + +void WebSocketMultiplayerPeer::_send_del(int32_t p_peer_id) { + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + uint32_t id = E->key(); + if (p_peer_id != id) + _send_sys(get_peer(id), SYS_DEL, p_peer_id); + } +} + +void WebSocketMultiplayerPeer::_store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size) { + Packet packet; + packet.data = (uint8_t *)memalloc(p_data_size); + packet.size = p_data_size; + packet.source = p_source; + packet.destination = p_dest; + copymem(packet.data, &p_data[PROTO_SIZE], p_data_size); + _incoming_packets.push_back(packet); + emit_signal("peer_packet", p_source); +} + +Error WebSocketMultiplayerPeer::_server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size) { + if (p_to == 1) { + + return OK; // Will not send to self + + } else if (p_to == 0) { + + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + if (E->key() != p_from) + E->get()->put_packet(p_buffer, p_buffer_size); + } + return OK; // Sent to all but sender + + } else if (p_to < 0) { + + for (Map<int, Ref<WebSocketPeer> >::Element *E = _peer_map.front(); E; E = E->next()) { + if (E->key() != p_from && E->key() != -p_to) + E->get()->put_packet(p_buffer, p_buffer_size); + } + return OK; // Sent to all but sender and excluded + + } else { + + ERR_FAIL_COND_V(p_to == p_from, FAILED); + + return get_peer(p_to)->put_packet(p_buffer, p_buffer_size); // Sending to specific peer + } +} + +void WebSocketMultiplayerPeer::_process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id) { + + ERR_FAIL_COND(!p_peer.is_valid()); + + const uint8_t *in_buffer; + int size = 0; + int data_size = 0; + + Error err = p_peer->get_packet(&in_buffer, size); + + ERR_FAIL_COND(err != OK); + ERR_FAIL_COND(size < PROTO_SIZE); + + data_size = size - PROTO_SIZE; + + uint8_t type = 0; + int32_t from = 0; + int32_t to = 0; + copymem(&type, in_buffer, 1); + copymem(&from, &in_buffer[1], 4); + copymem(&to, &in_buffer[5], 4); + + if (is_server()) { // Server can resend + + ERR_FAIL_COND(type != SYS_NONE); // Only server sends sys messages + ERR_FAIL_COND(from != p_peer_id); // Someone is cheating + + _server_relay(from, to, in_buffer, size); // Relay if needed + + if (to == 1) { // This is for the server + + _store_pkt(from, to, in_buffer, data_size); + + } else if (to == 0) { + + // Broadcast, for us too + _store_pkt(from, to, in_buffer, data_size); + + } else if (to < 0) { + + // All but one, for us if not excluded + if (_peer_id != -p_peer_id) + _store_pkt(from, to, in_buffer, data_size); + + } else { + + // Send to specific peer + ERR_FAIL_COND(!_peer_map.has(to)); + get_peer(to)->put_packet(in_buffer, size); + } + + } else { + + if (type == SYS_NONE) { // Payload message + + _store_pkt(from, to, in_buffer, data_size); + return; + } + + // System message + ERR_FAIL_COND(data_size < 4); + int id = 0; + copymem(&id, &in_buffer[PROTO_SIZE], 4); + + switch (type) { + + case SYS_ADD: // Add peer + _peer_map[id] = Ref<WebSocketPeer>(); + emit_signal("peer_connected", id); + if (id == 1) // We just connected to the server + emit_signal("connection_succeeded"); + break; + + case SYS_DEL: // Remove peer + _peer_map.erase(id); + emit_signal("peer_disconnected", id); + break; + case SYS_ID: // Helo, server assigned ID + _peer_id = id; + break; + default: + ERR_EXPLAIN("Invalid multiplayer message"); + ERR_FAIL(); + break; + } + } +} diff --git a/modules/websocket/websocket_multiplayer.h b/modules/websocket/websocket_multiplayer.h new file mode 100644 index 0000000000..e8e795e97f --- /dev/null +++ b/modules/websocket/websocket_multiplayer.h @@ -0,0 +1,110 @@ +/*************************************************************************/ +/* websocket_multiplayer.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef WEBSOCKET_MULTIPLAYER_PEER_H +#define WEBSOCKET_MULTIPLAYER_PEER_H + +#include "core/error_list.h" +#include "core/io/networked_multiplayer_peer.h" +#include "core/list.h" +#include "websocket_peer.h" + +class WebSocketMultiplayerPeer : public NetworkedMultiplayerPeer { + + GDCLASS(WebSocketMultiplayerPeer, NetworkedMultiplayerPeer); + +private: + PoolVector<uint8_t> _make_pkt(uint32_t p_type, int32_t p_from, int32_t p_to, const uint8_t *p_data, uint32_t p_data_size); + void _store_pkt(int32_t p_source, int32_t p_dest, const uint8_t *p_data, uint32_t p_data_size); + Error _server_relay(int32_t p_from, int32_t p_to, const uint8_t *p_buffer, uint32_t p_buffer_size); + +protected: + enum { + SYS_NONE = 0, + SYS_ADD = 1, + SYS_DEL = 2, + SYS_ID = 3, + + PROTO_SIZE = 9, + SYS_PACKET_SIZE = 13, + MAX_PACKET_SIZE = 65536 - 14 // 5 websocket, 9 multiplayer + }; + + struct Packet { + int source; + int destination; + uint8_t *data; + uint32_t size; + }; + + List<Packet> _incoming_packets; + Map<int, Ref<WebSocketPeer> > _peer_map; + Packet _current_packet; + + bool _is_multiplayer; + int _target_peer; + int _peer_id; + int _refusing; + + static void _bind_methods(); + + void _send_add(int32_t p_peer_id); + void _send_sys(Ref<WebSocketPeer> p_peer, uint8_t p_type, int32_t p_peer_id); + void _send_del(int32_t p_peer_id); + int _gen_unique_id() const; + +public: + /* NetworkedMultiplayerPeer */ + void set_transfer_mode(TransferMode p_mode); + TransferMode get_transfer_mode() const; + void set_target_peer(int p_peer_id); + int get_packet_peer() const; + int get_unique_id() const; + virtual bool is_server() const = 0; + void set_refuse_new_connections(bool p_enable); + bool is_refusing_new_connections() const; + virtual ConnectionStatus get_connection_status() const = 0; + + /* PacketPeer */ + virtual int get_available_packet_count() const; + virtual int get_max_packet_size() const; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size); + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size); + + /* WebSocketPeer */ + virtual Ref<WebSocketPeer> get_peer(int p_peer_id) const = 0; + + void _process_multiplayer(Ref<WebSocketPeer> p_peer, uint32_t p_peer_id); + void _clear(); + + WebSocketMultiplayerPeer(); + ~WebSocketMultiplayerPeer(); +}; + +#endif // WEBSOCKET_MULTIPLAYER_PEER_H diff --git a/modules/websocket/websocket_peer.cpp b/modules/websocket/websocket_peer.cpp new file mode 100644 index 0000000000..a6fbb4481b --- /dev/null +++ b/modules/websocket/websocket_peer.cpp @@ -0,0 +1,49 @@ +/*************************************************************************/ +/* websocket_peer.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "websocket_peer.h" + +GDCINULL(WebSocketPeer); + +WebSocketPeer::WebSocketPeer() { +} + +WebSocketPeer::~WebSocketPeer() { +} + +void WebSocketPeer::_bind_methods() { + ClassDB::bind_method(D_METHOD("get_write_mode"), &WebSocketPeer::get_write_mode); + ClassDB::bind_method(D_METHOD("set_write_mode", "mode"), &WebSocketPeer::set_write_mode); + ClassDB::bind_method(D_METHOD("is_connected_to_host"), &WebSocketPeer::is_connected_to_host); + ClassDB::bind_method(D_METHOD("was_string_packet"), &WebSocketPeer::was_string_packet); + ClassDB::bind_method(D_METHOD("close"), &WebSocketPeer::close); + + BIND_ENUM_CONSTANT(WRITE_MODE_TEXT); + BIND_ENUM_CONSTANT(WRITE_MODE_BINARY); +} diff --git a/core/string_buffer.cpp b/modules/websocket/websocket_peer.h index aac2090378..f4d8ce3e38 100644 --- a/core/string_buffer.cpp +++ b/modules/websocket/websocket_peer.h @@ -1,12 +1,12 @@ /*************************************************************************/ -/* string_buffer.cpp */ +/* websocket_peer.h */ /*************************************************************************/ /* This file is part of: */ /* GODOT ENGINE */ /* https://godotengine.org */ /*************************************************************************/ -/* Copyright (c) 2007-2018 Juan Linietsky, Ariel Manzur. */ -/* Copyright (c) 2014-2018 Godot Engine contributors (cf. AUTHORS.md) */ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ /* */ /* Permission is hereby granted, free of charge, to any person obtaining */ /* a copy of this software and associated documentation files (the */ @@ -27,77 +27,47 @@ /* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ /* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ /*************************************************************************/ +#ifndef WEBSOCKETPEER_H +#define WEBSOCKETPEER_H -#include "string_buffer.h" +#include "core/error_list.h" +#include "core/io/packet_peer.h" +#include "core/ring_buffer.h" +#include "websocket_macros.h" -#include <string.h> +class WebSocketPeer : public PacketPeer { -StringBuffer &StringBuffer::append(CharType p_char) { - reserve(string_length + 2); - current_buffer_ptr()[string_length++] = p_char; - return *this; -} + GDCLASS(WebSocketPeer, PacketPeer); + GDCICLASS(WebSocketPeer); -StringBuffer &StringBuffer::append(const String &p_string) { - return append(p_string.c_str()); -} +public: + enum WriteMode { + WRITE_MODE_TEXT, + WRITE_MODE_BINARY, + }; -StringBuffer &StringBuffer::append(const char *p_str) { - int len = strlen(p_str); - reserve(string_length + len + 1); +protected: + static void _bind_methods(); - CharType *buf = current_buffer_ptr(); - for (const char *c_ptr = p_str; *c_ptr; ++c_ptr) { - buf[string_length++] = *c_ptr; - } - return *this; -} +public: + virtual int get_available_packet_count() const = 0; + virtual Error get_packet(const uint8_t **r_buffer, int &r_buffer_size) = 0; + virtual Error put_packet(const uint8_t *p_buffer, int p_buffer_size) = 0; + virtual int get_max_packet_size() const = 0; -StringBuffer &StringBuffer::append(const CharType *p_str, int p_clip_to_len) { - int len = 0; - while ((p_clip_to_len < 0 || len < p_clip_to_len) && p_str[len]) { - ++len; - } - reserve(string_length + len + 1); - memcpy(&(current_buffer_ptr()[string_length]), p_str, len * sizeof(CharType)); - string_length += len; + virtual WriteMode get_write_mode() const = 0; + virtual void set_write_mode(WriteMode p_mode) = 0; - return *this; -} + virtual void close() = 0; -StringBuffer &StringBuffer::reserve(int p_size) { - if (p_size < SHORT_BUFFER_SIZE || p_size < buffer.size()) - return *this; + virtual bool is_connected_to_host() const = 0; + virtual IP_Address get_connected_host() const = 0; + virtual uint16_t get_connected_port() const = 0; + virtual bool was_string_packet() const = 0; - bool need_copy = string_length > 0 && buffer.empty(); - buffer.resize(next_power_of_2(p_size)); - if (need_copy) { - memcpy(buffer.ptrw(), short_buffer, string_length * sizeof(CharType)); - } + WebSocketPeer(); + ~WebSocketPeer(); +}; - return *this; -} - -int StringBuffer::length() const { - return string_length; -} - -String StringBuffer::as_string() { - current_buffer_ptr()[string_length] = '\0'; - if (buffer.empty()) { - return String(short_buffer); - } else { - buffer.resize(string_length + 1); - return buffer; - } -} - -double StringBuffer::as_double() { - current_buffer_ptr()[string_length] = '\0'; - return String::to_double(current_buffer_ptr()); -} - -int64_t StringBuffer::as_int() { - current_buffer_ptr()[string_length] = '\0'; - return String::to_int(current_buffer_ptr()); -} +VARIANT_ENUM_CAST(WebSocketPeer::WriteMode); +#endif // WEBSOCKETPEER_H diff --git a/modules/websocket/websocket_server.cpp b/modules/websocket/websocket_server.cpp new file mode 100644 index 0000000000..ba77019f55 --- /dev/null +++ b/modules/websocket/websocket_server.cpp @@ -0,0 +1,94 @@ +/*************************************************************************/ +/* websocket_server.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#include "websocket_server.h" + +GDCINULL(WebSocketServer); + +WebSocketServer::WebSocketServer() { + _peer_id = 1; +} + +WebSocketServer::~WebSocketServer() { +} + +void WebSocketServer::_bind_methods() { + + ClassDB::bind_method(D_METHOD("is_listening"), &WebSocketServer::is_listening); + ClassDB::bind_method(D_METHOD("listen", "port", "protocols", "gd_mp_api"), &WebSocketServer::listen, DEFVAL(PoolVector<String>()), DEFVAL(false)); + ClassDB::bind_method(D_METHOD("stop"), &WebSocketServer::stop); + ClassDB::bind_method(D_METHOD("has_peer", "id"), &WebSocketServer::has_peer); + + ADD_SIGNAL(MethodInfo("client_disconnected", PropertyInfo(Variant::INT, "id"))); + ADD_SIGNAL(MethodInfo("client_connected", PropertyInfo(Variant::INT, "id"), PropertyInfo(Variant::STRING, "protocol"))); + ADD_SIGNAL(MethodInfo("data_received", PropertyInfo(Variant::INT, "id"))); +} + +NetworkedMultiplayerPeer::ConnectionStatus WebSocketServer::get_connection_status() const { + if (is_listening()) + return CONNECTION_CONNECTED; + + return CONNECTION_DISCONNECTED; +}; + +bool WebSocketServer::is_server() const { + + return true; +} + +void WebSocketServer::_on_peer_packet(int32_t p_peer_id) { + + if (_is_multiplayer) { + _process_multiplayer(get_peer(p_peer_id), p_peer_id); + } else { + emit_signal("data_received", p_peer_id); + } +} + +void WebSocketServer::_on_connect(int32_t p_peer_id, String p_protocol) { + + if (_is_multiplayer) { + // Send add to clients + _send_add(p_peer_id); + emit_signal("peer_connected", p_peer_id); + } else { + emit_signal("client_connected", p_peer_id, p_protocol); + } +} + +void WebSocketServer::_on_disconnect(int32_t p_peer_id) { + + if (_is_multiplayer) { + // Send delete to clients + _send_del(p_peer_id); + emit_signal("peer_disconnected", p_peer_id); + } else { + emit_signal("client_disconnected", p_peer_id); + } +} diff --git a/modules/websocket/websocket_server.h b/modules/websocket/websocket_server.h new file mode 100644 index 0000000000..db188811fd --- /dev/null +++ b/modules/websocket/websocket_server.h @@ -0,0 +1,63 @@ +/*************************************************************************/ +/* websocket_server.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2017 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2017 Godot Engine contributors (cf. AUTHORS.md) */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ +#ifndef WEBSOCKET_H +#define WEBSOCKET_H + +#include "core/reference.h" +#include "websocket_multiplayer.h" +#include "websocket_peer.h" + +class WebSocketServer : public WebSocketMultiplayerPeer { + + GDCLASS(WebSocketServer, WebSocketMultiplayerPeer); + GDCICLASS(WebSocketServer); + +protected: + static void _bind_methods(); + +public: + virtual void poll() = 0; + virtual Error listen(int p_port, PoolVector<String> p_protocols = PoolVector<String>(), bool gd_mp_api = false) = 0; + virtual void stop() = 0; + virtual bool is_listening() const = 0; + virtual bool has_peer(int p_id) const = 0; + virtual Ref<WebSocketPeer> get_peer(int p_id) const = 0; + virtual bool is_server() const; + ConnectionStatus get_connection_status() const; + + void _on_peer_packet(int32_t p_peer_id); + void _on_connect(int32_t p_peer_id, String p_protocol); + void _on_disconnect(int32_t p_peer_id); + + WebSocketServer(); + ~WebSocketServer(); +}; + +#endif // WEBSOCKET_H diff --git a/scene/2d/area_2d.cpp b/scene/2d/area_2d.cpp index 6fff7ac0a4..bb914b90fc 100644 --- a/scene/2d/area_2d.cpp +++ b/scene/2d/area_2d.cpp @@ -399,7 +399,7 @@ void Area2D::set_monitoring(bool p_enable) { if (p_enable == monitoring) return; if (locked) { - ERR_EXPLAIN("Function blocked during in/out signal. Use call_deferred(\"set_enable_monitoring\",true/false)"); + ERR_EXPLAIN("Function blocked during in/out signal. Use call_deferred(\"set_monitoring\",true/false)"); } ERR_FAIL_COND(locked); diff --git a/thirdparty/README.md b/thirdparty/README.md index 92f24446d4..2957842f9c 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -217,6 +217,23 @@ changes to ensure they build for Javascript/HTML5. Those changes are marked with `// -- GODOT --` comments. +## libwebsockets + +- Upstream: https://github.com/warmcat/libwebsockets +- Version: 2.4.1 +- License: LGPLv2.1 + static linking exception + +File extracted from upstream source: +- Everything in `lib/` except `mbedtls_wrapper/`, `http2/`, `event-libs/`. + - From `misc/` exclude `lws-genhash.c`, `lws-ring.c`, `romfs.{c,h}`, `smtp.c`. + - From `plat/` exclude `lws-plat-{esp*,optee}.c`. + - From `server/` exclude `access-log.c`, `cgi.c`, `daemonize.c`, `lws-spa.c`, +`peer-limits.c`, `rewrite.c` +- Also copy `win32helpers/` from `win32port/` + +Important: `lws_config.h` and `lws_config_private.h` contains custom +Godot build configurations, check them out when updating. + ## minizip - Upstream: http://www.zlib.net diff --git a/thirdparty/lws/LICENSE.txt b/thirdparty/lws/LICENSE.txt new file mode 100644 index 0000000000..34a42d5687 --- /dev/null +++ b/thirdparty/lws/LICENSE.txt @@ -0,0 +1,555 @@ +Libwebsockets and included programs are provided under the terms of the GNU +Library General Public License (LGPL) 2.1, with the following exceptions: + +1) Any reference, whether in these modifications or in the GNU +Library General Public License 2.1, to this License, these terms, the +GNU Lesser Public License, GNU Library General Public License, LGPL, or +any similar reference shall refer to the GNU Library General Public +License 2.1 as modified by these paragraphs 1) through 4). + +2) Static linking of programs with the libwebsockets library does not +constitute a derivative work and does not require the author to provide +source code for the program, use the shared libwebsockets libraries, or +link their program against a user-supplied version of libwebsockets. + +If you link the program to a modified version of libwebsockets, then the +changes to libwebsockets must be provided under the terms of the LGPL in +sections 1, 2, and 4. + +3) You do not have to provide a copy of the libwebsockets license with +programs that are linked to the libwebsockets library, nor do you have to +identify the libwebsockets license in your program or documentation as +required by section 6 of the LGPL. + +However, programs must still identify their use of libwebsockets. The +following example statement can be included in user documentation to +satisfy this requirement: + +"[program] is based in part on the work of the libwebsockets project +(https://libwebsockets.org)" + +4) Some sources included have their own, more liberal licenses, or options +to get original sources with the liberal terms. + +Original liberal license retained + + - lib/sha-1.c - 3-clause BSD license retained, link to original + - win32port/zlib - ZLIB license (see zlib.h) + +Relicensed to libwebsocket license + + - lib/base64-decode.c - relicensed to LGPL2.1+SLE, link to original + - lib/daemonize.c - relicensed from Public Domain to LGPL2.1+SLE, + link to original Public Domain version + +Public Domain (CC-zero) to simplify reuse + + - test-server/*.c + - test-server/*.h + - lwsws/* + +------ end of exceptions + + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Libraries + + If you develop a new library, and you want it to be of the greatest +possible use to the public, we recommend making it free software that +everyone can redistribute and change. You can do so by permitting +redistribution under these terms (or, alternatively, under the terms of the +ordinary General Public License). + + To apply these terms, attach the following notices to the library. It is +safest to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least the +"copyright" line and a pointer to where the full notice is found. + + <one line to give the library's name and a brief idea of what it does.> + Copyright (C) <year> <name of author> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with this library; if not, write to the Free Software + Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the library, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the + library `Frob' (a library for tweaking knobs) written by James Random Hacker. + + <signature of Ty Coon>, 1 April 1990 + Ty Coon, President of Vice + +That's all there is to it! + diff --git a/thirdparty/lws/alloc.c b/thirdparty/lws/alloc.c new file mode 100644 index 0000000000..898db12464 --- /dev/null +++ b/thirdparty/lws/alloc.c @@ -0,0 +1,90 @@ +#include "private-libwebsockets.h" + +#if defined(LWS_PLAT_OPTEE) + +#define TEE_USER_MEM_HINT_NO_FILL_ZERO 0x80000000 + +void *__attribute__((weak)) + TEE_Malloc(uint32_t size, uint32_t hint) +{ + return NULL; +} +void *__attribute__((weak)) + TEE_Realloc(void *buffer, uint32_t newSize) +{ + return NULL; +} +void __attribute__((weak)) + TEE_Free(void *buffer) +{ +} + +void *lws_realloc(void *ptr, size_t size, const char *reason) +{ + return TEE_Realloc(ptr, size); +} + +void *lws_malloc(size_t size, const char *reason) +{ + return TEE_Malloc(size, TEE_USER_MEM_HINT_NO_FILL_ZERO); +} + +void lws_free(void *p) +{ + TEE_Free(p); +} + +void *lws_zalloc(size_t size, const char *reason) +{ + void *ptr = TEE_Malloc(size, TEE_USER_MEM_HINT_NO_FILL_ZERO); + if (ptr) + memset(ptr, 0, size); + return ptr; +} + +void lws_set_allocator(void *(*cb)(void *ptr, size_t size, const char *reason)) +{ + (void)cb; +} +#else + +static void *_realloc(void *ptr, size_t size, const char *reason) +{ + if (size) { +#if defined(LWS_PLAT_ESP32) + lwsl_notice("%s: size %lu: %s\n", __func__, (unsigned long)size, reason); +#else + lwsl_debug("%s: size %lu: %s\n", __func__, (unsigned long)size, reason); +#endif +#if defined(LWS_PLAT_OPTEE) + return (void *)TEE_Realloc(ptr, size); +#else + return (void *)realloc(ptr, size); +#endif + } + if (ptr) + free(ptr); + + return NULL; +} + +void *(*_lws_realloc)(void *ptr, size_t size, const char *reason) = _realloc; + +void *lws_realloc(void *ptr, size_t size, const char *reason) +{ + return _lws_realloc(ptr, size, reason); +} + +void *lws_zalloc(size_t size, const char *reason) +{ + void *ptr = _lws_realloc(NULL, size, reason); + if (ptr) + memset(ptr, 0, size); + return ptr; +} + +void lws_set_allocator(void *(*cb)(void *ptr, size_t size, const char *reason)) +{ + _lws_realloc = cb; +} +#endif diff --git a/thirdparty/lws/client/client-handshake.c b/thirdparty/lws/client/client-handshake.c new file mode 100644 index 0000000000..c2720d9283 --- /dev/null +++ b/thirdparty/lws/client/client-handshake.c @@ -0,0 +1,1051 @@ +#include "private-libwebsockets.h" + +static int +lws_getaddrinfo46(struct lws *wsi, const char *ads, struct addrinfo **result) +{ + struct addrinfo hints; + + memset(&hints, 0, sizeof(hints)); + *result = NULL; + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) { + +#if !defined(__ANDROID__) + hints.ai_family = AF_INET6; + hints.ai_flags = AI_V4MAPPED; +#endif + } else +#endif + { + hints.ai_family = PF_UNSPEC; + hints.ai_socktype = SOCK_STREAM; + hints.ai_flags = AI_CANONNAME; + } + + return getaddrinfo(ads, NULL, &hints, result); +} + +struct lws * +lws_client_connect_2(struct lws *wsi) +{ + sockaddr46 sa46; + struct addrinfo *result; + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws_pollfd pfd; + const char *cce = "", *iface; + int n, port; + ssize_t plen = 0; + const char *ads; +#ifdef LWS_WITH_IPV6 + char ipv6only = lws_check_opt(wsi->vhost->options, + LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY | + LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); + +#if defined(__ANDROID__) + ipv6only = 0; +#endif +#endif + + lwsl_client("%s\n", __func__); + + if (!wsi->u.hdr.ah) { + cce = "ah was NULL at cc2"; + lwsl_err("%s\n", cce); + goto oom4; + } + + /* + * start off allowing ipv6 on connection if vhost allows it + */ + wsi->ipv6 = LWS_IPV6_ENABLED(wsi->vhost); + + /* Decide what it is we need to connect to: + * + * Priority 1: connect to http proxy */ + + if (wsi->vhost->http_proxy_port) { + plen = sprintf((char *)pt->serv_buf, + "CONNECT %s:%u HTTP/1.0\x0d\x0a" + "User-agent: libwebsockets\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS), + wsi->c_port); + + if (wsi->vhost->proxy_basic_auth_token[0]) + plen += sprintf((char *)pt->serv_buf + plen, + "Proxy-authorization: basic %s\x0d\x0a", + wsi->vhost->proxy_basic_auth_token); + + plen += sprintf((char *)pt->serv_buf + plen, "\x0d\x0a"); + ads = wsi->vhost->http_proxy_address; + port = wsi->vhost->http_proxy_port; + +#if defined(LWS_WITH_SOCKS5) + + /* Priority 2: Connect to SOCK5 Proxy */ + + } else if (wsi->vhost->socks_proxy_port) { + socks_generate_msg(wsi, SOCKS_MSG_GREETING, &plen); + lwsl_client("Sending SOCKS Greeting\n"); + ads = wsi->vhost->socks_proxy_address; + port = wsi->vhost->socks_proxy_port; +#endif + } else { + + /* Priority 3: Connect directly */ + + ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); + port = wsi->c_port; + } + + /* + * prepare the actual connection + * to whatever we decided to connect to + */ + + lwsl_notice("%s: %p: address %s\n", __func__, wsi, ads); + + n = lws_getaddrinfo46(wsi, ads, &result); + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) { + + if (n) { + /* lws_getaddrinfo46 failed, there is no usable result */ + lwsl_notice("%s: lws_getaddrinfo46 failed %d\n", + __func__, n); + cce = "ipv6 lws_getaddrinfo46 failed"; + goto oom4; + } + + memset(&sa46, 0, sizeof(sa46)); + + sa46.sa6.sin6_family = AF_INET6; + switch (result->ai_family) { + case AF_INET: + if (ipv6only) + break; + /* map IPv4 to IPv6 */ + bzero((char *)&sa46.sa6.sin6_addr, + sizeof(sa46.sa6.sin6_addr)); + sa46.sa6.sin6_addr.s6_addr[10] = 0xff; + sa46.sa6.sin6_addr.s6_addr[11] = 0xff; + memcpy(&sa46.sa6.sin6_addr.s6_addr[12], + &((struct sockaddr_in *)result->ai_addr)->sin_addr, + sizeof(struct in_addr)); + lwsl_notice("uplevelling AF_INET to AF_INET6\n"); + break; + + case AF_INET6: + memcpy(&sa46.sa6.sin6_addr, + &((struct sockaddr_in6 *)result->ai_addr)->sin6_addr, + sizeof(struct in6_addr)); + sa46.sa6.sin6_scope_id = ((struct sockaddr_in6 *)result->ai_addr)->sin6_scope_id; + sa46.sa6.sin6_flowinfo = ((struct sockaddr_in6 *)result->ai_addr)->sin6_flowinfo; + break; + default: + lwsl_err("Unknown address family\n"); + freeaddrinfo(result); + cce = "unknown address family"; + goto oom4; + } + } else +#endif /* use ipv6 */ + + /* use ipv4 */ + { + void *p = NULL; + + if (!n) { + struct addrinfo *res = result; + + /* pick the first AF_INET (IPv4) result */ + + while (!p && res) { + switch (res->ai_family) { + case AF_INET: + p = &((struct sockaddr_in *)res->ai_addr)->sin_addr; + break; + } + + res = res->ai_next; + } +#if defined(LWS_FALLBACK_GETHOSTBYNAME) + } else if (n == EAI_SYSTEM) { + struct hostent *host; + + lwsl_info("getaddrinfo (ipv4) failed, trying gethostbyname\n"); + host = gethostbyname(ads); + if (host) { + p = host->h_addr; + } else { + lwsl_err("gethostbyname failed\n"); + cce = "gethostbyname (ipv4) failed"; + goto oom4; + } +#endif + } else { + lwsl_err("getaddrinfo failed\n"); + cce = "getaddrinfo failed"; + goto oom4; + } + + if (!p) { + if (result) + freeaddrinfo(result); + lwsl_err("Couldn't identify address\n"); + cce = "unable to lookup address"; + goto oom4; + } + + sa46.sa4.sin_family = AF_INET; + sa46.sa4.sin_addr = *((struct in_addr *)p); + bzero(&sa46.sa4.sin_zero, 8); + } + + if (result) + freeaddrinfo(result); + + /* now we decided on ipv4 or ipv6, set the port */ + + if (!lws_socket_is_valid(wsi->desc.sockfd)) { + +#if defined(LWS_WITH_LIBUV) + if (LWS_LIBUV_ENABLED(context)) + if (lws_libuv_check_watcher_active(wsi)) { + lwsl_warn("Waiting for libuv watcher to close\n"); + cce = "waiting for libuv watcher to close"; + goto oom4; + } +#endif + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) + wsi->desc.sockfd = socket(AF_INET6, SOCK_STREAM, 0); + else +#endif + wsi->desc.sockfd = socket(AF_INET, SOCK_STREAM, 0); + + if (!lws_socket_is_valid(wsi->desc.sockfd)) { + lwsl_warn("Unable to open socket\n"); + cce = "unable to open socket"; + goto oom4; + } + + if (lws_plat_set_socket_options(wsi->vhost, wsi->desc.sockfd)) { + lwsl_err("Failed to set wsi socket options\n"); + compatible_close(wsi->desc.sockfd); + cce = "set socket opts failed"; + goto oom4; + } + + wsi->mode = LWSCM_WSCL_WAITING_CONNECT; + + lws_libev_accept(wsi, wsi->desc); + lws_libuv_accept(wsi, wsi->desc); + lws_libevent_accept(wsi, wsi->desc); + + if (insert_wsi_socket_into_fds(context, wsi)) { + compatible_close(wsi->desc.sockfd); + cce = "insert wsi failed"; + goto oom4; + } + + lws_change_pollfd(wsi, 0, LWS_POLLIN); + + /* + * past here, we can't simply free the structs as error + * handling as oom4 does. We have to run the whole close flow. + */ + + if (!wsi->protocol) + wsi->protocol = &wsi->vhost->protocols[0]; + + wsi->protocol->callback(wsi, LWS_CALLBACK_WSI_CREATE, + wsi->user_space, NULL, 0); + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE, + AWAITING_TIMEOUT); + + iface = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); + + if (iface) { + n = lws_socket_bind(wsi->vhost, wsi->desc.sockfd, 0, iface); + if (n < 0) { + cce = "unable to bind socket"; + goto failed; + } + } + } + +#ifdef LWS_WITH_IPV6 + if (wsi->ipv6) { + sa46.sa6.sin6_port = htons(port); + n = sizeof(struct sockaddr_in6); + } else +#endif + { + sa46.sa4.sin_port = htons(port); + n = sizeof(struct sockaddr); + } + + if (connect(wsi->desc.sockfd, (const struct sockaddr *)&sa46, n) == -1 || + LWS_ERRNO == LWS_EISCONN) { + if (LWS_ERRNO == LWS_EALREADY || + LWS_ERRNO == LWS_EINPROGRESS || + LWS_ERRNO == LWS_EWOULDBLOCK +#ifdef _WIN32 + || LWS_ERRNO == WSAEINVAL +#endif + ) { + lwsl_client("nonblocking connect retry (errno = %d)\n", + LWS_ERRNO); + + if (lws_plat_check_connection_error(wsi)) { + cce = "socket connect failed"; + goto failed; + } + + /* + * must do specifically a POLLOUT poll to hear + * about the connect completion + */ + if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { + cce = "POLLOUT set failed"; + goto failed; + } + + return wsi; + } + + if (LWS_ERRNO != LWS_EISCONN) { + lwsl_notice("Connect failed errno=%d\n", LWS_ERRNO); + cce = "connect failed"; + goto failed; + } + } + + lwsl_client("connected\n"); + + /* we are connected to server, or proxy */ + + /* http proxy */ + if (wsi->vhost->http_proxy_port) { + + /* + * OK from now on we talk via the proxy, so connect to that + * + * (will overwrite existing pointer, + * leaving old string/frag there but unreferenced) + */ + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, + wsi->vhost->http_proxy_address)) + goto failed; + wsi->c_port = wsi->vhost->http_proxy_port; + + n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen, + MSG_NOSIGNAL); + if (n < 0) { + lwsl_debug("ERROR writing to proxy socket\n"); + cce = "proxy write failed"; + goto failed; + } + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE, + AWAITING_TIMEOUT); + + wsi->mode = LWSCM_WSCL_WAITING_PROXY_REPLY; + + return wsi; + } +#if defined(LWS_WITH_SOCKS5) + /* socks proxy */ + else if (wsi->vhost->socks_proxy_port) { + n = send(wsi->desc.sockfd, (char *)pt->serv_buf, plen, + MSG_NOSIGNAL); + if (n < 0) { + lwsl_debug("ERROR writing socks greeting\n"); + cce = "socks write failed"; + goto failed; + } + + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY, + AWAITING_TIMEOUT); + + wsi->mode = LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY; + + return wsi; + } +#endif + + /* + * provoke service to issue the handshake directly + * we need to do it this way because in the proxy case, this is the + * next state and executed only if and when we get a good proxy + * response inside the state machine... but notice in SSL case this + * may not have sent anything yet with 0 return, and won't until some + * many retries from main loop. To stop that becoming endless, + * cover with a timeout. + */ + + lws_set_timeout(wsi, PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE, + AWAITING_TIMEOUT); + + wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE; + pfd.fd = wsi->desc.sockfd; + pfd.events = LWS_POLLIN; + pfd.revents = LWS_POLLIN; + + n = lws_service_fd(context, &pfd); + if (n < 0) { + cce = "first service failed"; + goto failed; + } + if (n) /* returns 1 on failure after closing wsi */ + return NULL; + + return wsi; + +oom4: + /* we're closing, losing some rx is OK */ + lws_header_table_force_to_detachable_state(wsi); + + if (wsi->mode == LWSCM_HTTP_CLIENT || + wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED || + wsi->mode == LWSCM_WSCL_WAITING_CONNECT) { + wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, strlen(cce)); + wsi->already_did_cce = 1; + } + /* take care that we might be inserted in fds already */ + if (wsi->position_in_fds_table != -1) + goto failed1; + lws_remove_from_timeout_list(wsi); + lws_header_table_detach(wsi, 0); + lws_free(wsi); + + return NULL; + +failed: + wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, strlen(cce)); + wsi->already_did_cce = 1; +failed1: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + + return NULL; +} + +/** + * lws_client_reset() - retarget a connected wsi to start over with a new connection (ie, redirect) + * this only works if still in HTTP, ie, not upgraded yet + * wsi: connection to reset + * address: network address of the new server + * port: port to connect to + * path: uri path to connect to on the new server + * host: host header to send to the new server + */ +LWS_VISIBLE struct lws * +lws_client_reset(struct lws **pwsi, int ssl, const char *address, int port, + const char *path, const char *host) +{ + char origin[300] = "", protocol[300] = "", method[32] = "", iface[16] = "", *p; + struct lws *wsi = *pwsi; + + if (wsi->redirects == 3) { + lwsl_err("%s: Too many redirects\n", __func__); + return NULL; + } + wsi->redirects++; + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN); + if (p) + strncpy(origin, p, sizeof(origin) - 1); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + if (p) + strncpy(protocol, p, sizeof(protocol) - 1); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); + if (p) + strncpy(method, p, sizeof(method) - 1); + + p = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_IFACE); + if (p) + strncpy(method, p, sizeof(iface) - 1); + + lwsl_info("redirect ads='%s', port=%d, path='%s', ssl = %d\n", + address, port, path, ssl); + + /* close the connection by hand */ + +#ifdef LWS_OPENSSL_SUPPORT + lws_ssl_close(wsi); +#endif + +#ifdef LWS_WITH_LIBUV + if (LWS_LIBUV_ENABLED(wsi->context)) { + lwsl_debug("%s: lws_libuv_closehandle: wsi %p\n", __func__, wsi); + /* + * libuv has to do his own close handle processing asynchronously + * but once it starts we can do everything else synchronously, + * including trash wsi->desc.sockfd since it took a copy. + * + * When it completes it will call compatible_close() + */ + lws_libuv_closehandle_manually(wsi); + } else +#else + compatible_close(wsi->desc.sockfd); +#endif + + remove_wsi_socket_from_fds(wsi); + +#ifdef LWS_OPENSSL_SUPPORT + wsi->use_ssl = ssl; +#else + if (ssl) { + lwsl_err("%s: not configured for ssl\n", __func__); + return NULL; + } +#endif + + wsi->desc.sockfd = LWS_SOCK_INVALID; + wsi->state = LWSS_CLIENT_UNCONNECTED; + wsi->protocol = NULL; + wsi->pending_timeout = NO_PENDING_TIMEOUT; + wsi->c_port = port; + wsi->hdr_parsing_completed = 0; + _lws_header_table_reset(wsi->u.hdr.ah); + + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, address)) + return NULL; + + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, host)) + return NULL; + + if (origin[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN, + origin)) + return NULL; + if (protocol[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS, + protocol)) + return NULL; + if (method[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD, + method)) + return NULL; + + if (iface[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE, + iface)) + return NULL; + + origin[0] = '/'; + strncpy(&origin[1], path, sizeof(origin) - 2); + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, origin)) + return NULL; + + *pwsi = lws_client_connect_2(wsi); + + return *pwsi; +} + +#ifdef LWS_WITH_HTTP_PROXY +static hubbub_error +html_parser_cb(const hubbub_token *token, void *pw) +{ + struct lws_rewrite *r = (struct lws_rewrite *)pw; + char buf[1024], *start = buf + LWS_PRE, *p = start, + *end = &buf[sizeof(buf) - 1]; + size_t i; + + switch (token->type) { + case HUBBUB_TOKEN_DOCTYPE: + + p += lws_snprintf(p, end - p, "<!DOCTYPE %.*s %s ", + (int) token->data.doctype.name.len, + token->data.doctype.name.ptr, + token->data.doctype.force_quirks ? + "(force-quirks) " : ""); + + if (token->data.doctype.public_missing) + lwsl_debug("\tpublic: missing\n"); + else + p += lws_snprintf(p, end - p, "PUBLIC \"%.*s\"\n", + (int) token->data.doctype.public_id.len, + token->data.doctype.public_id.ptr); + + if (token->data.doctype.system_missing) + lwsl_debug("\tsystem: missing\n"); + else + p += lws_snprintf(p, end - p, " \"%.*s\">\n", + (int) token->data.doctype.system_id.len, + token->data.doctype.system_id.ptr); + + break; + case HUBBUB_TOKEN_START_TAG: + p += lws_snprintf(p, end - p, "<%.*s", (int)token->data.tag.name.len, + token->data.tag.name.ptr); + +/* (token->data.tag.self_closing) ? + "(self-closing) " : "", + (token->data.tag.n_attributes > 0) ? + "attributes:" : ""); +*/ + for (i = 0; i < token->data.tag.n_attributes; i++) { + if (!hstrcmp(&token->data.tag.attributes[i].name, "href", 4) || + !hstrcmp(&token->data.tag.attributes[i].name, "action", 6) || + !hstrcmp(&token->data.tag.attributes[i].name, "src", 3)) { + const char *pp = (const char *)token->data.tag.attributes[i].value.ptr; + int plen = (int) token->data.tag.attributes[i].value.len; + + if (strncmp(pp, "http:", 5) && strncmp(pp, "https:", 6)) { + + if (!hstrcmp(&token->data.tag.attributes[i].value, + r->from, r->from_len)) { + pp += r->from_len; + plen -= r->from_len; + } + p += lws_snprintf(p, end - p, " %.*s=\"%s/%.*s\"", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + r->to, plen, pp); + continue; + } + } + + p += lws_snprintf(p, end - p, " %.*s=\"%.*s\"", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + (int) token->data.tag.attributes[i].value.len, + token->data.tag.attributes[i].value.ptr); + } + p += lws_snprintf(p, end - p, ">"); + break; + case HUBBUB_TOKEN_END_TAG: + p += lws_snprintf(p, end - p, "</%.*s", (int) token->data.tag.name.len, + token->data.tag.name.ptr); +/* + (token->data.tag.self_closing) ? + "(self-closing) " : "", + (token->data.tag.n_attributes > 0) ? + "attributes:" : ""); +*/ + for (i = 0; i < token->data.tag.n_attributes; i++) { + p += lws_snprintf(p, end - p, " %.*s='%.*s'\n", + (int) token->data.tag.attributes[i].name.len, + token->data.tag.attributes[i].name.ptr, + (int) token->data.tag.attributes[i].value.len, + token->data.tag.attributes[i].value.ptr); + } + p += lws_snprintf(p, end - p, ">"); + break; + case HUBBUB_TOKEN_COMMENT: + p += lws_snprintf(p, end - p, "<!-- %.*s -->\n", + (int) token->data.comment.len, + token->data.comment.ptr); + break; + case HUBBUB_TOKEN_CHARACTER: + if (token->data.character.len == 1) { + if (*token->data.character.ptr == '<') { + p += lws_snprintf(p, end - p, "<"); + break; + } + if (*token->data.character.ptr == '>') { + p += lws_snprintf(p, end - p, ">"); + break; + } + if (*token->data.character.ptr == '&') { + p += lws_snprintf(p, end - p, "&"); + break; + } + } + + p += lws_snprintf(p, end - p, "%.*s", (int) token->data.character.len, + token->data.character.ptr); + break; + case HUBBUB_TOKEN_EOF: + p += lws_snprintf(p, end - p, "\n"); + break; + } + + if (user_callback_handle_rxflow(r->wsi->protocol->callback, + r->wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, + r->wsi->user_space, start, p - start)) + return -1; + + return HUBBUB_OK; +} +#endif + +LWS_VISIBLE struct lws * +lws_client_connect_via_info(struct lws_client_connect_info *i) +{ + struct lws *wsi; + int v = SPEC_LATEST_SUPPORTED; + const struct lws_protocols *p; + + if (i->context->requested_kill) + return NULL; + + if (!i->context->protocol_init_done) + lws_protocol_init(i->context); + + wsi = lws_zalloc(sizeof(struct lws), "client wsi"); + if (wsi == NULL) + goto bail; + + wsi->context = i->context; + /* assert the mode and union status (hdr) clearly */ + lws_union_transition(wsi, LWSCM_HTTP_CLIENT); + wsi->desc.sockfd = LWS_SOCK_INVALID; + + /* 1) fill up the wsi with stuff from the connect_info as far as it + * can go. It's because not only is our connection async, we might + * not even be able to get ahold of an ah at this point. + */ + + /* -1 means just use latest supported */ + if (i->ietf_version_or_minus_one != -1 && i->ietf_version_or_minus_one) + v = i->ietf_version_or_minus_one; + + wsi->ietf_spec_revision = v; + wsi->user_space = NULL; + wsi->state = LWSS_CLIENT_UNCONNECTED; + wsi->pending_timeout = NO_PENDING_TIMEOUT; + wsi->position_in_fds_table = -1; + wsi->c_port = i->port; + wsi->vhost = i->vhost; + if (!wsi->vhost) + wsi->vhost = i->context->vhost_list; + + wsi->protocol = &wsi->vhost->protocols[0]; + + /* for http[s] connection, allow protocol selection by name */ + + if (i->method && i->vhost && i->protocol) { + p = lws_vhost_name_to_protocol(i->vhost, i->protocol); + if (p) + wsi->protocol = p; + } + + if (wsi && !wsi->user_space && i->userdata) { + wsi->user_space_externally_allocated = 1; + wsi->user_space = i->userdata; + } else + /* if we stay in http, we can assign the user space now, + * otherwise do it after the protocol negotiated + */ + if (i->method) + if (lws_ensure_user_space(wsi)) + goto bail; + +#ifdef LWS_OPENSSL_SUPPORT + wsi->use_ssl = i->ssl_connection; +#else + if (i->ssl_connection) { + lwsl_err("libwebsockets not configured for ssl\n"); + goto bail; + } +#endif + + /* 2) stash the things from connect_info that we can't process without + * an ah. Because if no ah, we will go on the ah waiting list and + * process those things later (after the connect_info and maybe the + * things pointed to have gone out of scope. + */ + + wsi->u.hdr.stash = lws_malloc(sizeof(*wsi->u.hdr.stash), "client stash"); + if (!wsi->u.hdr.stash) { + lwsl_err("%s: OOM\n", __func__); + goto bail; + } + + wsi->u.hdr.stash->origin[0] = '\0'; + wsi->u.hdr.stash->protocol[0] = '\0'; + wsi->u.hdr.stash->method[0] = '\0'; + wsi->u.hdr.stash->iface[0] = '\0'; + + strncpy(wsi->u.hdr.stash->address, i->address, + sizeof(wsi->u.hdr.stash->address) - 1); + strncpy(wsi->u.hdr.stash->path, i->path, + sizeof(wsi->u.hdr.stash->path) - 1); + strncpy(wsi->u.hdr.stash->host, i->host, + sizeof(wsi->u.hdr.stash->host) - 1); + if (i->origin) + strncpy(wsi->u.hdr.stash->origin, i->origin, + sizeof(wsi->u.hdr.stash->origin) - 1); + if (i->protocol) + strncpy(wsi->u.hdr.stash->protocol, i->protocol, + sizeof(wsi->u.hdr.stash->protocol) - 1); + if (i->method) + strncpy(wsi->u.hdr.stash->method, i->method, + sizeof(wsi->u.hdr.stash->method) - 1); + if (i->iface) + strncpy(wsi->u.hdr.stash->iface, i->iface, + sizeof(wsi->u.hdr.stash->iface) - 1); + + wsi->u.hdr.stash->address[sizeof(wsi->u.hdr.stash->address) - 1] = '\0'; + wsi->u.hdr.stash->path[sizeof(wsi->u.hdr.stash->path) - 1] = '\0'; + wsi->u.hdr.stash->host[sizeof(wsi->u.hdr.stash->host) - 1] = '\0'; + wsi->u.hdr.stash->origin[sizeof(wsi->u.hdr.stash->origin) - 1] = '\0'; + wsi->u.hdr.stash->protocol[sizeof(wsi->u.hdr.stash->protocol) - 1] = '\0'; + wsi->u.hdr.stash->method[sizeof(wsi->u.hdr.stash->method) - 1] = '\0'; + wsi->u.hdr.stash->iface[sizeof(wsi->u.hdr.stash->iface) - 1] = '\0'; + + if (i->pwsi) + *i->pwsi = wsi; + + /* if we went on the waiting list, no probs just return the wsi + * when we get the ah, now or later, he will call + * lws_client_connect_via_info2() below. + */ + if (lws_header_table_attach(wsi, 0) < 0) { + /* + * if we failed here, the connection is already closed + * and freed. + */ + goto bail1; + } + + if (i->parent_wsi) { + lwsl_info("%s: created child %p of parent %p\n", __func__, + wsi, i->parent_wsi); + wsi->parent = i->parent_wsi; + wsi->sibling_list = i->parent_wsi->child_list; + i->parent_wsi->child_list = wsi; + } +#ifdef LWS_WITH_HTTP_PROXY + if (i->uri_replace_to) + wsi->rw = lws_rewrite_create(wsi, html_parser_cb, + i->uri_replace_from, + i->uri_replace_to); +#endif + + return wsi; + +bail: + lws_free(wsi); + +bail1: + if (i->pwsi) + *i->pwsi = NULL; + + return NULL; +} + +struct lws * +lws_client_connect_via_info2(struct lws *wsi) +{ + struct client_info_stash *stash = wsi->u.hdr.stash; + + if (!stash) + return wsi; + + /* + * we're not necessarily in a position to action these right away, + * stash them... we only need during connect phase so u.hdr is fine + */ + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, + stash->address)) + goto bail1; + + /* these only need u.hdr lifetime as well */ + + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_URI, stash->path)) + goto bail1; + + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_HOST, stash->host)) + goto bail1; + + if (stash->origin[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_ORIGIN, + stash->origin)) + goto bail1; + /* + * this is a list of protocols we tell the server we're okay with + * stash it for later when we compare server response with it + */ + if (stash->protocol[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS, + stash->protocol)) + goto bail1; + if (stash->method[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_METHOD, + stash->method)) + goto bail1; + if (stash->iface[0]) + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_IFACE, + stash->iface)) + goto bail1; + +#if defined(LWS_WITH_SOCKS5) + if (!wsi->vhost->socks_proxy_port) + lws_free_set_NULL(wsi->u.hdr.stash); +#endif + + /* + * Check with each extension if it is able to route and proxy this + * connection for us. For example, an extension like x-google-mux + * can handle this and then we don't need an actual socket for this + * connection. + */ + + if (lws_ext_cb_all_exts(wsi->context, wsi, + LWS_EXT_CB_CAN_PROXY_CLIENT_CONNECTION, + (void *)stash->address, + wsi->c_port) > 0) { + lwsl_client("lws_client_connect: ext handling conn\n"); + + lws_set_timeout(wsi, + PENDING_TIMEOUT_AWAITING_EXTENSION_CONNECT_RESPONSE, + AWAITING_TIMEOUT); + + wsi->mode = LWSCM_WSCL_WAITING_EXTENSION_CONNECT; + return wsi; + } + lwsl_client("lws_client_connect: direct conn\n"); + wsi->context->count_wsi_allocated++; + + return lws_client_connect_2(wsi); + +bail1: +#if defined(LWS_WITH_SOCKS5) + if (!wsi->vhost->socks_proxy_port) + lws_free_set_NULL(wsi->u.hdr.stash); +#endif + + return NULL; +} + +LWS_VISIBLE struct lws * +lws_client_connect_extended(struct lws_context *context, const char *address, + int port, int ssl_connection, const char *path, + const char *host, const char *origin, + const char *protocol, int ietf_version_or_minus_one, + void *userdata) +{ + struct lws_client_connect_info i; + + memset(&i, 0, sizeof(i)); + + i.context = context; + i.address = address; + i.port = port; + i.ssl_connection = ssl_connection; + i.path = path; + i.host = host; + i.origin = origin; + i.protocol = protocol; + i.ietf_version_or_minus_one = ietf_version_or_minus_one; + i.userdata = userdata; + + return lws_client_connect_via_info(&i); +} + +LWS_VISIBLE struct lws * +lws_client_connect(struct lws_context *context, const char *address, + int port, int ssl_connection, const char *path, + const char *host, const char *origin, + const char *protocol, int ietf_version_or_minus_one) +{ + struct lws_client_connect_info i; + + memset(&i, 0, sizeof(i)); + + i.context = context; + i.address = address; + i.port = port; + i.ssl_connection = ssl_connection; + i.path = path; + i.host = host; + i.origin = origin; + i.protocol = protocol; + i.ietf_version_or_minus_one = ietf_version_or_minus_one; + i.userdata = NULL; + + return lws_client_connect_via_info(&i); +} + +#if defined(LWS_WITH_SOCKS5) +void socks_generate_msg(struct lws *wsi, enum socks_msg_type type, + ssize_t *msg_len) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + ssize_t len = 0, n, passwd_len; + short net_num; + char *p; + + switch (type) { + case SOCKS_MSG_GREETING: + /* socks version, version 5 only */ + pt->serv_buf[len++] = SOCKS_VERSION_5; + /* number of methods */ + pt->serv_buf[len++] = 2; + /* username password method */ + pt->serv_buf[len++] = SOCKS_AUTH_USERNAME_PASSWORD; + /* no authentication method */ + pt->serv_buf[len++] = SOCKS_AUTH_NO_AUTH; + break; + + case SOCKS_MSG_USERNAME_PASSWORD: + n = strlen(wsi->vhost->socks_user); + passwd_len = strlen(wsi->vhost->socks_password); + + /* the subnegotiation version */ + pt->serv_buf[len++] = SOCKS_SUBNEGOTIATION_VERSION_1; + /* length of the user name */ + pt->serv_buf[len++] = n; + /* user name */ + strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_user, + context->pt_serv_buf_size - len); + len += n; + /* length of the password */ + pt->serv_buf[len++] = passwd_len; + /* password */ + strncpy((char *)&pt->serv_buf[len], wsi->vhost->socks_password, + context->pt_serv_buf_size - len); + len += passwd_len; + break; + + case SOCKS_MSG_CONNECT: + p = (char*)&net_num; + + /* socks version */ + pt->serv_buf[len++] = SOCKS_VERSION_5; + /* socks command */ + pt->serv_buf[len++] = SOCKS_COMMAND_CONNECT; + /* reserved */ + pt->serv_buf[len++] = 0; + /* address type */ + pt->serv_buf[len++] = SOCKS_ATYP_DOMAINNAME; + /* skip length, we fill it in at the end */ + n = len++; + + /* the address we tell SOCKS proxy to connect to */ + strncpy((char *)&(pt->serv_buf[len]), wsi->u.hdr.stash->address, + context->pt_serv_buf_size - len); + len += strlen(wsi->u.hdr.stash->address); + net_num = htons(wsi->c_port); + + /* the port we tell SOCKS proxy to connect to */ + pt->serv_buf[len++] = p[0]; + pt->serv_buf[len++] = p[1]; + + /* the length of the address, excluding port */ + pt->serv_buf[n] = strlen(wsi->u.hdr.stash->address); + break; + + default: + return; + } + + *msg_len = len; +} +#endif diff --git a/thirdparty/lws/client/client-parser.c b/thirdparty/lws/client/client-parser.c new file mode 100644 index 0000000000..0e42dac362 --- /dev/null +++ b/thirdparty/lws/client/client-parser.c @@ -0,0 +1,598 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +/* + * parsers.c: lws_rx_sm() needs to be roughly kept in + * sync with changes here, esp related to ext draining + */ + +int lws_client_rx_sm(struct lws *wsi, unsigned char c) +{ + int callback_action = LWS_CALLBACK_CLIENT_RECEIVE; + int handled, n, m, rx_draining_ext = 0; + unsigned short close_code; + struct lws_tokens eff_buf; + unsigned char *pp; + + if (wsi->u.ws.rx_draining_ext) { + assert(!c); + eff_buf.token = NULL; + eff_buf.token_len = 0; + lws_remove_wsi_from_draining_ext_list(wsi); + rx_draining_ext = 1; + lwsl_debug("%s: doing draining flow\n", __func__); + + goto drain_extension; + } + + if (wsi->socket_is_permanently_unusable) + return -1; + + switch (wsi->lws_rx_parse_state) { + case LWS_RXPS_NEW: + /* control frames (PING) may interrupt checkable sequences */ + wsi->u.ws.defeat_check_utf8 = 0; + + switch (wsi->ietf_spec_revision) { + case 13: + wsi->u.ws.opcode = c & 0xf; + /* revisit if an extension wants them... */ + switch (wsi->u.ws.opcode) { + case LWSWSOPC_TEXT_FRAME: + wsi->u.ws.rsv_first_msg = (c & 0x70); + wsi->u.ws.continuation_possible = 1; + wsi->u.ws.check_utf8 = lws_check_opt( + wsi->context->options, + LWS_SERVER_OPTION_VALIDATE_UTF8); + wsi->u.ws.utf8 = 0; + break; + case LWSWSOPC_BINARY_FRAME: + wsi->u.ws.rsv_first_msg = (c & 0x70); + wsi->u.ws.check_utf8 = 0; + wsi->u.ws.continuation_possible = 1; + break; + case LWSWSOPC_CONTINUATION: + if (!wsi->u.ws.continuation_possible) { + lwsl_info("disordered continuation\n"); + return -1; + } + break; + case LWSWSOPC_CLOSE: + wsi->u.ws.check_utf8 = 0; + wsi->u.ws.utf8 = 0; + break; + case 3: + case 4: + case 5: + case 6: + case 7: + case 0xb: + case 0xc: + case 0xd: + case 0xe: + case 0xf: + lwsl_info("illegal opcode\n"); + return -1; + default: + wsi->u.ws.defeat_check_utf8 = 1; + break; + } + wsi->u.ws.rsv = (c & 0x70); + /* revisit if an extension wants them... */ + if ( +#ifndef LWS_NO_EXTENSIONS + !wsi->count_act_ext && +#endif + wsi->u.ws.rsv) { + lwsl_info("illegal rsv bits set\n"); + return -1; + } + wsi->u.ws.final = !!((c >> 7) & 1); + lwsl_ext("%s: This RX frame Final %d\n", __func__, + wsi->u.ws.final); + + if (wsi->u.ws.owed_a_fin && + (wsi->u.ws.opcode == LWSWSOPC_TEXT_FRAME || + wsi->u.ws.opcode == LWSWSOPC_BINARY_FRAME)) { + lwsl_info("hey you owed us a FIN\n"); + return -1; + } + if ((!(wsi->u.ws.opcode & 8)) && wsi->u.ws.final) { + wsi->u.ws.continuation_possible = 0; + wsi->u.ws.owed_a_fin = 0; + } + + if ((wsi->u.ws.opcode & 8) && !wsi->u.ws.final) { + lwsl_info("control msg can't be fragmented\n"); + return -1; + } + if (!wsi->u.ws.final) + wsi->u.ws.owed_a_fin = 1; + + switch (wsi->u.ws.opcode) { + case LWSWSOPC_TEXT_FRAME: + case LWSWSOPC_BINARY_FRAME: + wsi->u.ws.frame_is_binary = wsi->u.ws.opcode == + LWSWSOPC_BINARY_FRAME; + break; + } + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; + break; + + default: + lwsl_err("unknown spec version %02d\n", + wsi->ietf_spec_revision); + break; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN: + + wsi->u.ws.this_frame_masked = !!(c & 0x80); + + switch (c & 0x7f) { + case 126: + /* control frames are not allowed to have big lengths */ + if (wsi->u.ws.opcode & 8) + goto illegal_ctl_length; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2; + break; + case 127: + /* control frames are not allowed to have big lengths */ + if (wsi->u.ws.opcode & 8) + goto illegal_ctl_length; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; + break; + default: + wsi->u.ws.rx_packet_length = c; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (c) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + } + break; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_2: + wsi->u.ws.rx_packet_length = c << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_1: + wsi->u.ws.rx_packet_length |= c; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (wsi->u.ws.rx_packet_length) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_8: + if (c & 0x80) { + lwsl_warn("b63 of length must be zero\n"); + /* kill the connection */ + return -1; + } +#if defined __LP64__ + wsi->u.ws.rx_packet_length = ((size_t)c) << 56; +#else + wsi->u.ws.rx_packet_length = 0; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_7: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 48; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_6: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 40; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_5: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 32; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_4: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 24; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_3: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 16; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_2: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_1: + wsi->u.ws.rx_packet_length |= (size_t)c; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else { + if (wsi->u.ws.rx_packet_length) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + } + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_1: + wsi->u.ws.mask[0] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_2: + wsi->u.ws.mask[1] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_3: + wsi->u.ws.mask[2] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_4: + wsi->u.ws.mask[3] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + + if (wsi->u.ws.rx_packet_length) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + break; + + case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: + + assert(wsi->u.ws.rx_ubuf); + + if (wsi->u.ws.rx_draining_ext) + goto drain_extension; + + if (wsi->u.ws.this_frame_masked && !wsi->u.ws.all_zero_nonce) + c ^= wsi->u.ws.mask[(wsi->u.ws.mask_idx++) & 3]; + + wsi->u.ws.rx_ubuf[LWS_PRE + (wsi->u.ws.rx_ubuf_head++)] = c; + + if (--wsi->u.ws.rx_packet_length == 0) { + /* spill because we have the whole frame */ + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + + /* + * if there's no protocol max frame size given, we are + * supposed to default to context->pt_serv_buf_size + */ + if (!wsi->protocol->rx_buffer_size && + wsi->u.ws.rx_ubuf_head != wsi->context->pt_serv_buf_size) + break; + + if (wsi->protocol->rx_buffer_size && + wsi->u.ws.rx_ubuf_head != wsi->protocol->rx_buffer_size) + break; + + /* spill because we filled our rx buffer */ +spill: + + handled = 0; + + /* + * is this frame a control packet we should take care of at this + * layer? If so service it and hide it from the user callback + */ + + switch (wsi->u.ws.opcode) { + case LWSWSOPC_CLOSE: + pp = (unsigned char *)&wsi->u.ws.rx_ubuf[LWS_PRE]; + if (lws_check_opt(wsi->context->options, + LWS_SERVER_OPTION_VALIDATE_UTF8) && + wsi->u.ws.rx_ubuf_head > 2 && + lws_check_utf8(&wsi->u.ws.utf8, pp + 2, + wsi->u.ws.rx_ubuf_head - 2)) + goto utf8_fail; + + /* is this an acknowledgement of our close? */ + if (wsi->state == LWSS_AWAITING_CLOSE_ACK) { + /* + * fine he has told us he is closing too, let's + * finish our close + */ + lwsl_parser("seen server's close ack\n"); + return -1; + } + + lwsl_parser("client sees server close len = %d\n", + wsi->u.ws.rx_ubuf_head); + if (wsi->u.ws.rx_ubuf_head >= 2) { + close_code = (pp[0] << 8) | pp[1]; + if (close_code < 1000 || + close_code == 1004 || + close_code == 1005 || + close_code == 1006 || + close_code == 1012 || + close_code == 1013 || + close_code == 1014 || + close_code == 1015 || + (close_code >= 1016 && close_code < 3000) + ) { + pp[0] = (LWS_CLOSE_STATUS_PROTOCOL_ERR >> 8) & 0xff; + pp[1] = LWS_CLOSE_STATUS_PROTOCOL_ERR & 0xff; + } + } + if (user_callback_handle_rxflow( + wsi->protocol->callback, wsi, + LWS_CALLBACK_WS_PEER_INITIATED_CLOSE, + wsi->user_space, pp, + wsi->u.ws.rx_ubuf_head)) + return -1; + + if (lws_partial_buffered(wsi)) + /* + * if we're in the middle of something, + * we can't do a normal close response and + * have to just close our end. + */ + wsi->socket_is_permanently_unusable = 1; + else + /* + * parrot the close packet payload back + * we do not care about how it went, we are closing + * immediately afterwards + */ + lws_write(wsi, (unsigned char *) + &wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head, + LWS_WRITE_CLOSE); + wsi->state = LWSS_RETURNED_CLOSE_ALREADY; + /* close the connection */ + return -1; + + case LWSWSOPC_PING: + lwsl_info("received %d byte ping, sending pong\n", + wsi->u.ws.rx_ubuf_head); + + /* he set a close reason on this guy, ignore PING */ + if (wsi->u.ws.close_in_ping_buffer_len) + goto ping_drop; + + if (wsi->u.ws.ping_pending_flag) { + /* + * there is already a pending ping payload + * we should just log and drop + */ + lwsl_parser("DROP PING since one pending\n"); + goto ping_drop; + } + + /* control packets can only be < 128 bytes long */ + if (wsi->u.ws.rx_ubuf_head > 128 - 3) { + lwsl_parser("DROP PING payload too large\n"); + goto ping_drop; + } + + /* stash the pong payload */ + memcpy(wsi->u.ws.ping_payload_buf + LWS_PRE, + &wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head); + + wsi->u.ws.ping_payload_len = wsi->u.ws.rx_ubuf_head; + wsi->u.ws.ping_pending_flag = 1; + + /* get it sent as soon as possible */ + lws_callback_on_writable(wsi); +ping_drop: + wsi->u.ws.rx_ubuf_head = 0; + handled = 1; + break; + + case LWSWSOPC_PONG: + lwsl_info("client receied pong\n"); + lwsl_hexdump(&wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head); + + if (wsi->pending_timeout == + PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) { + lwsl_info("%p: received expected PONG\n", wsi); + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + } + + /* issue it */ + callback_action = LWS_CALLBACK_CLIENT_RECEIVE_PONG; + break; + + case LWSWSOPC_CONTINUATION: + case LWSWSOPC_TEXT_FRAME: + case LWSWSOPC_BINARY_FRAME: + break; + + default: + + lwsl_parser("Reserved opc 0x%2X\n", wsi->u.ws.opcode); + + /* + * It's something special we can't understand here. + * Pass the payload up to the extension's parsing + * state machine. + */ + + eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; + eff_buf.token_len = wsi->u.ws.rx_ubuf_head; + + if (lws_ext_cb_active(wsi, + LWS_EXT_CB_EXTENDED_PAYLOAD_RX, + &eff_buf, 0) <= 0) { + /* not handled or failed */ + lwsl_ext("Unhandled ext opc 0x%x\n", + wsi->u.ws.opcode); + wsi->u.ws.rx_ubuf_head = 0; + + return 0; + } + handled = 1; + break; + } + + /* + * No it's real payload, pass it up to the user callback. + * It's nicely buffered with the pre-padding taken care of + * so it can be sent straight out again using lws_write + */ + if (handled) + goto already_done; + + eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; + eff_buf.token_len = wsi->u.ws.rx_ubuf_head; + + if (wsi->u.ws.opcode == LWSWSOPC_PONG && !eff_buf.token_len) + goto already_done; + +drain_extension: + lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len); + + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0); + lwsl_ext("Ext RX returned %d\n", n); + if (n < 0) { + wsi->socket_is_permanently_unusable = 1; + return -1; + } + + lwsl_ext("post inflate eff_buf len %d\n", eff_buf.token_len); + + if (rx_draining_ext && !eff_buf.token_len) { + lwsl_debug(" --- ending drain on 0 read result\n"); + goto already_done; + } + + if (wsi->u.ws.check_utf8 && !wsi->u.ws.defeat_check_utf8) { + if (lws_check_utf8(&wsi->u.ws.utf8, + (unsigned char *)eff_buf.token, + eff_buf.token_len)) + goto utf8_fail; + + /* we are ending partway through utf-8 character? */ + if (!wsi->u.ws.rx_packet_length && wsi->u.ws.final && + wsi->u.ws.utf8 && !n) { + lwsl_info("FINAL utf8 error\n"); +utf8_fail: + lwsl_info("utf8 error\n"); + return -1; + } + } + + if (eff_buf.token_len < 0 && + callback_action != LWS_CALLBACK_CLIENT_RECEIVE_PONG) + goto already_done; + + if (!eff_buf.token) + goto already_done; + + eff_buf.token[eff_buf.token_len] = '\0'; + + if (!wsi->protocol->callback) + goto already_done; + + if (callback_action == LWS_CALLBACK_CLIENT_RECEIVE_PONG) + lwsl_info("Client doing pong callback\n"); + + if (n && eff_buf.token_len) + /* extension had more... main loop will come back + * we want callback to be done with this set, if so, + * because lws_is_final() hides it was final until the + * last chunk + */ + lws_add_wsi_to_draining_ext_list(wsi); + else + lws_remove_wsi_from_draining_ext_list(wsi); + + if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY || + wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || + wsi->state == LWSS_AWAITING_CLOSE_ACK) + goto already_done; + + m = wsi->protocol->callback(wsi, + (enum lws_callback_reasons)callback_action, + wsi->user_space, eff_buf.token, eff_buf.token_len); + + /* if user code wants to close, let caller know */ + if (m) + return 1; + +already_done: + wsi->u.ws.rx_ubuf_head = 0; + break; + default: + lwsl_err("client rx illegal state\n"); + return 1; + } + + return 0; + +illegal_ctl_length: + lwsl_warn("Control frame asking for extended length is illegal\n"); + + /* kill the connection */ + return -1; +} + + diff --git a/thirdparty/lws/client/client.c b/thirdparty/lws/client/client.c new file mode 100644 index 0000000000..20450aa923 --- /dev/null +++ b/thirdparty/lws/client/client.c @@ -0,0 +1,1296 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2014 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +int +lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len) +{ + int m; + + switch (wsi->mode) { + case LWSCM_WSCL_WAITING_PROXY_REPLY: + case LWSCM_WSCL_ISSUE_HANDSHAKE: + case LWSCM_WSCL_WAITING_SERVER_REPLY: + case LWSCM_WSCL_WAITING_EXTENSION_CONNECT: + case LWSCM_WS_CLIENT: + while (len) { + /* + * we were accepting input but now we stopped doing so + */ + if (lws_is_flowcontrolled(wsi)) { + lwsl_debug("%s: caching %ld\n", __func__, (long)len); + lws_rxflow_cache(wsi, *buf, 0, len); + return 0; + } + if (wsi->u.ws.rx_draining_ext) { +#if !defined(LWS_NO_CLIENT) + if (wsi->mode == LWSCM_WS_CLIENT) + m = lws_client_rx_sm(wsi, 0); + else +#endif + m = lws_rx_sm(wsi, 0); + if (m < 0) + return -1; + continue; + } + /* account for what we're using in rxflow buffer */ + if (wsi->rxflow_buffer) + wsi->rxflow_pos++; + + if (lws_client_rx_sm(wsi, *(*buf)++)) { + lwsl_debug("client_rx_sm exited\n"); + return -1; + } + len--; + } + lwsl_debug("%s: finished with %ld\n", __func__, (long)len); + return 0; + default: + break; + } + + return 0; +} + +LWS_VISIBLE LWS_EXTERN void +lws_client_http_body_pending(struct lws *wsi, int something_left_to_send) +{ + wsi->client_http_body_pending = !!something_left_to_send; +} + +int +lws_client_socket_service(struct lws_context *context, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char *p = (char *)&pt->serv_buf[0]; + const char *cce = NULL; + unsigned char c; + char *sb = p; + int n = 0; + ssize_t len = 0; +#if defined(LWS_WITH_SOCKS5) + char conn_mode = 0, pending_timeout = 0; +#endif + + switch (wsi->mode) { + + case LWSCM_WSCL_WAITING_CONNECT: + + /* + * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE + * timeout protection set in client-handshake.c + */ + + if (!lws_client_connect_2(wsi)) { + /* closed */ + lwsl_client("closed\n"); + return -1; + } + + /* either still pending connection, or changed mode */ + return 0; + +#if defined(LWS_WITH_SOCKS5) + /* SOCKS Greeting Reply */ + case LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY: + case LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY: + case LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY: + + /* handle proxy hung up on us */ + + if (pollfd->revents & LWS_POLLHUP) { + lwsl_warn("SOCKS connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + goto bail3; + } + + n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); + if (n < 0) { + if (LWS_ERRNO == LWS_EAGAIN) { + lwsl_debug("SOCKS read EAGAIN, retrying\n"); + return 0; + } + lwsl_err("ERROR reading from SOCKS socket\n"); + goto bail3; + } + + switch (wsi->mode) { + + case LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY: + if (pt->serv_buf[0] != SOCKS_VERSION_5) + goto socks_reply_fail; + + if (pt->serv_buf[1] == SOCKS_AUTH_NO_AUTH) { + lwsl_client("SOCKS greeting reply: No Auth Method\n"); + socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); + conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY; + pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; + goto socks_send; + } + + if (pt->serv_buf[1] == SOCKS_AUTH_USERNAME_PASSWORD) { + lwsl_client("SOCKS greeting reply: User/Pw Method\n"); + socks_generate_msg(wsi, SOCKS_MSG_USERNAME_PASSWORD, &len); + conn_mode = LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY; + pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY; + goto socks_send; + } + goto socks_reply_fail; + + case LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY: + if (pt->serv_buf[0] != SOCKS_SUBNEGOTIATION_VERSION_1 || + pt->serv_buf[1] != SOCKS_SUBNEGOTIATION_STATUS_SUCCESS) + goto socks_reply_fail; + + lwsl_client("SOCKS password OK, sending connect\n"); + socks_generate_msg(wsi, SOCKS_MSG_CONNECT, &len); + conn_mode = LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY; + pending_timeout = PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY; +socks_send: + n = send(wsi->desc.sockfd, (char *)pt->serv_buf, len, + MSG_NOSIGNAL); + if (n < 0) { + lwsl_debug("ERROR writing to socks proxy\n"); + goto bail3; + } + + lws_set_timeout(wsi, pending_timeout, AWAITING_TIMEOUT); + wsi->mode = conn_mode; + break; + +socks_reply_fail: + lwsl_notice("socks reply: v%d, err %d\n", + pt->serv_buf[0], pt->serv_buf[1]); + goto bail3; + + case LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY: + if (pt->serv_buf[0] != SOCKS_VERSION_5 || + pt->serv_buf[1] != SOCKS_REQUEST_REPLY_SUCCESS) + goto socks_reply_fail; + + lwsl_client("socks connect OK\n"); + + /* free stash since we are done with it */ + lws_free_set_NULL(wsi->u.hdr.stash); + if (lws_hdr_simple_create(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS, + wsi->vhost->socks_proxy_address)) + goto bail3; + + wsi->c_port = wsi->vhost->socks_proxy_port; + + /* clear his proxy connection timeout */ + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + goto start_ws_handshake; + } + break; +#endif + + case LWSCM_WSCL_WAITING_PROXY_REPLY: + + /* handle proxy hung up on us */ + + if (pollfd->revents & LWS_POLLHUP) { + + lwsl_warn("Proxy connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + + goto bail3; + } + + n = recv(wsi->desc.sockfd, sb, context->pt_serv_buf_size, 0); + if (n < 0) { + if (LWS_ERRNO == LWS_EAGAIN) { + lwsl_debug("Proxy read returned EAGAIN... retrying\n"); + return 0; + } + lwsl_err("ERROR reading from proxy socket\n"); + goto bail3; + } + + pt->serv_buf[13] = '\0'; + if (strcmp(sb, "HTTP/1.0 200 ") && + strcmp(sb, "HTTP/1.1 200 ")) { + lwsl_err("ERROR proxy: %s\n", sb); + goto bail3; + } + + /* clear his proxy connection timeout */ + + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* fallthru */ + + case LWSCM_WSCL_ISSUE_HANDSHAKE: + + /* + * we are under PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE + * timeout protection set in client-handshake.c + * + * take care of our lws_callback_on_writable + * happening at a time when there's no real connection yet + */ +#if defined(LWS_WITH_SOCKS5) +start_ws_handshake: +#endif + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) + return -1; + +#ifdef LWS_OPENSSL_SUPPORT + /* we can retry this... just cook the SSL BIO the first time */ + + if (wsi->use_ssl && !wsi->ssl) { + if (lws_ssl_client_bio_create(wsi)) + return -1; + } + + if (wsi->use_ssl) { + n = lws_ssl_client_connect1(wsi); + if (!n) + return 0; + if (n < 0) { + cce = "lws_ssl_client_connect1 failed"; + goto bail3; + } + } else + wsi->ssl = NULL; + + /* fallthru */ + + case LWSCM_WSCL_WAITING_SSL: + + if (wsi->use_ssl) { + n = lws_ssl_client_connect2(wsi); + if (!n) + return 0; + if (n < 0) { + cce = "lws_ssl_client_connect2 failed"; + goto bail3; + } + } else + wsi->ssl = NULL; +#endif + + wsi->mode = LWSCM_WSCL_ISSUE_HANDSHAKE2; + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND, + context->timeout_secs); + + /* fallthru */ + + case LWSCM_WSCL_ISSUE_HANDSHAKE2: + p = lws_generate_client_handshake(wsi, p); + if (p == NULL) { + if (wsi->mode == LWSCM_RAW) + return 0; + + lwsl_err("Failed to generate handshake for client\n"); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + return 0; + } + + /* send our request to the server */ + lws_latency_pre(context, wsi); + + n = lws_ssl_capable_write(wsi, (unsigned char *)sb, p - sb); + lws_latency(context, wsi, "send lws_issue_raw", n, + n == p - sb); + switch (n) { + case LWS_SSL_CAPABLE_ERROR: + lwsl_debug("ERROR writing to client socket\n"); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + return 0; + case LWS_SSL_CAPABLE_MORE_SERVICE: + lws_callback_on_writable(wsi); + break; + } + + if (wsi->client_http_body_pending) { + wsi->mode = LWSCM_WSCL_ISSUE_HTTP_BODY; + lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, + context->timeout_secs); + /* user code must ask for writable callback */ + break; + } + + goto client_http_body_sent; + + case LWSCM_WSCL_ISSUE_HTTP_BODY: + if (wsi->client_http_body_pending) { + lws_set_timeout(wsi, PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD, + context->timeout_secs); + /* user code must ask for writable callback */ + break; + } +client_http_body_sent: + wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; + wsi->u.hdr.lextable_pos = 0; + wsi->mode = LWSCM_WSCL_WAITING_SERVER_REPLY; + lws_set_timeout(wsi, PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE, + context->timeout_secs); + break; + + case LWSCM_WSCL_WAITING_SERVER_REPLY: + /* + * handle server hanging up on us... + * but if there is POLLIN waiting, handle that first + */ + if ((pollfd->revents & (LWS_POLLIN | LWS_POLLHUP)) == + LWS_POLLHUP) { + + lwsl_debug("Server connection %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + cce = "Peer hung up"; + goto bail3; + } + + if (!(pollfd->revents & LWS_POLLIN)) + break; + + /* interpret the server response + * + * HTTP/1.1 101 Switching Protocols + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= + * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== + * Sec-WebSocket-Protocol: chat + * + * we have to take some care here to only take from the + * socket bytewise. The browser may (and has been seen to + * in the case that onopen() performs websocket traffic) + * coalesce both handshake response and websocket traffic + * in one packet, since at that point the connection is + * definitively ready from browser pov. + */ + len = 1; + while (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE && + len > 0) { + n = lws_ssl_capable_read(wsi, &c, 1); + lws_latency(context, wsi, "send lws_issue_raw", n, + n == 1); + switch (n) { + case 0: + case LWS_SSL_CAPABLE_ERROR: + cce = "read failed"; + goto bail3; + case LWS_SSL_CAPABLE_MORE_SERVICE: + return 0; + } + + if (lws_parse(wsi, c)) { + lwsl_warn("problems parsing header\n"); + goto bail3; + } + } + + /* + * hs may also be coming in multiple packets, there is a 5-sec + * libwebsocket timeout still active here too, so if parsing did + * not complete just wait for next packet coming in this state + */ + if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) + break; + + /* + * otherwise deal with the handshake. If there's any + * packet traffic already arrived we'll trigger poll() again + * right away and deal with it that way + */ + return lws_client_interpret_server_handshake(wsi); + +bail3: + lwsl_info("closing conn at LWS_CONNMODE...SERVER_REPLY\n"); + if (cce) + lwsl_info("reason: %s\n", cce); + wsi->protocol->callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, cce ? strlen(cce) : 0); + wsi->already_did_cce = 1; + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + return -1; + + case LWSCM_WSCL_WAITING_EXTENSION_CONNECT: + lwsl_ext("LWSCM_WSCL_WAITING_EXTENSION_CONNECT\n"); + break; + + case LWSCM_WSCL_PENDING_CANDIDATE_CHILD: + lwsl_ext("LWSCM_WSCL_PENDING_CANDIDATE_CHILD\n"); + break; + default: + break; + } + + return 0; +} + +/* + * In-place str to lower case + */ + +static void +strtolower(char *s) +{ + while (*s) { +#ifdef LWS_PLAT_OPTEE + int tolower_optee(int c); + *s = tolower_optee((int)*s); +#else + *s = tolower((int)*s); +#endif + s++; + } +} + +int LWS_WARN_UNUSED_RESULT +lws_http_transaction_completed_client(struct lws *wsi) +{ + lwsl_debug("%s: wsi %p\n", __func__, wsi); + /* if we can't go back to accept new headers, drop the connection */ + if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { + lwsl_info("%s: %p: close connection\n", __func__, wsi); + return 1; + } + + /* we don't support chained client connections yet */ + return 1; +#if 0 + /* otherwise set ourselves up ready to go again */ + wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED; + wsi->mode = LWSCM_HTTP_CLIENT_ACCEPTED; + wsi->u.http.rx_content_length = 0; + wsi->hdr_parsing_completed = 0; + + /* He asked for it to stay alive indefinitely */ + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* + * As client, nothing new is going to come until we ask for it + * we can drop the ah, if any + */ + if (wsi->u.hdr.ah) { + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 0); + } + + /* If we're (re)starting on headers, need other implied init */ + wsi->u.hdr.ues = URIES_IDLE; + + lwsl_info("%s: %p: keep-alive await new transaction\n", __func__, wsi); + + return 0; +#endif +} + +LWS_VISIBLE LWS_EXTERN unsigned int +lws_http_client_http_response(struct lws *wsi) +{ + if (!wsi->u.http.ah) + return 0; + + return wsi->u.http.ah->http_response; +} + +int +lws_client_interpret_server_handshake(struct lws *wsi) +{ + int n, len, okay = 0, port = 0, ssl = 0; + int close_reason = LWS_CLOSE_STATUS_PROTOCOL_ERR; + struct lws_context *context = wsi->context; + const char *pc, *prot, *ads = NULL, *path, *cce = NULL; + struct allocated_headers *ah = NULL; + char *p, *q; + char new_path[300]; +#ifndef LWS_NO_EXTENSIONS + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char *sb = (char *)&pt->serv_buf[0]; + const struct lws_ext_options *opts; + const struct lws_extension *ext; + char ext_name[128]; + const char *c, *a; + char ignore; + int more = 1; + void *v; +#endif + if (wsi->u.hdr.stash) + lws_free_set_NULL(wsi->u.hdr.stash); + + ah = wsi->u.hdr.ah; + if (!wsi->do_ws) { + /* we are being an http client... + */ + lws_union_transition(wsi, LWSCM_HTTP_CLIENT_ACCEPTED); + wsi->state = LWSS_CLIENT_HTTP_ESTABLISHED; + wsi->u.http.ah = ah; + ah->http_response = 0; + } + + /* + * well, what the server sent looked reasonable for syntax. + * Now let's confirm it sent all the necessary headers + * + * http (non-ws) client will expect something like this + * + * HTTP/1.0.200 + * server:.libwebsockets + * content-type:.text/html + * content-length:.17703 + * set-cookie:.test=LWS_1456736240_336776_COOKIE;Max-Age=360000 + * + * + * + */ + + wsi->u.http.connection_type = HTTP_CONNECTION_KEEP_ALIVE; + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP); + if (wsi->do_ws && !p) { + lwsl_info("no URI\n"); + cce = "HS: URI missing"; + goto bail3; + } + if (!p) { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP1_0); + wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE; + } + if (!p) { + cce = "HS: URI missing"; + lwsl_info("no URI\n"); + goto bail3; + } + n = atoi(p); + if (ah) + ah->http_response = n; + + if (n == 301 || n == 302 || n == 303 || n == 307 || n == 308) { + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_LOCATION); + if (!p) { + cce = "HS: Redirect code but no Location"; + goto bail3; + } + + /* Relative reference absolute path */ + if (p[0] == '/') + { +#ifdef LWS_OPENSSL_SUPPORT + ssl = wsi->use_ssl; +#endif + ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); + port = wsi->c_port; + path = p + 1; /* +1 as lws_client_reset expects leading / to be omitted */ + } + /* Absolute (Full) URI */ + else if (strchr(p, ':')) + { + if (lws_parse_uri(p, &prot, &ads, &port, &path)) { + cce = "HS: URI did not parse"; + goto bail3; + } + + if (!strcmp(prot, "wss") || !strcmp(prot, "https")) + ssl = 1; + } + /* Relative reference relative path */ + else + { + /* This doesn't try to calculate an absolute path, that will be left to the server */ +#ifdef LWS_OPENSSL_SUPPORT + ssl = wsi->use_ssl; +#endif + ads = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_PEER_ADDRESS); + port = wsi->c_port; + path = new_path + 1; /* +1 as lws_client_reset expects leading / to be omitted */ + strncpy(new_path, lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI), sizeof(new_path)); + new_path[sizeof(new_path) - 1] = '\0'; + q = strrchr(new_path, '/'); + if (q) + { + strncpy(q + 1, p, sizeof(new_path) - (q - new_path) - 1); + new_path[sizeof(new_path) - 1] = '\0'; + } + else + { + path = p; + } + } + +#ifdef LWS_OPENSSL_SUPPORT + if (wsi->use_ssl && !ssl) { + cce = "HS: Redirect attempted SSL downgrade"; + goto bail3; + } +#endif + + if (!lws_client_reset(&wsi, ssl, ads, port, path, ads)) { + /* there are two ways to fail out with NULL return... + * simple, early problem where the wsi is intact, or + * we went through with the reconnect attempt and the + * wsi is already closed. In the latter case, the wsi + * has beet set to NULL additionally. + */ + lwsl_err("Redirect failed\n"); + cce = "HS: Redirect failed"; + if (wsi) + goto bail3; + + return 1; + } + return 0; + } + + if (!wsi->do_ws) { + +#ifdef LWS_WITH_HTTP_PROXY + wsi->perform_rewrite = 0; + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE)) { + if (!strncmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE), + "text/html", 9)) + wsi->perform_rewrite = 1; + } +#endif + + /* allocate the per-connection user memory (if any) */ + if (lws_ensure_user_space(wsi)) { + lwsl_err("Problem allocating wsi user mem\n"); + cce = "HS: OOM"; + goto bail2; + } + + /* he may choose to send us stuff in chunked transfer-coding */ + wsi->chunked = 0; + wsi->chunk_remaining = 0; /* ie, next thing is chunk size */ + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_TRANSFER_ENCODING)) { + wsi->chunked = !strcmp(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING), + "chunked"); + /* first thing is hex, after payload there is crlf */ + wsi->chunk_parser = ELCP_HEX; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { + wsi->u.http.rx_content_length = + atoll(lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_CONTENT_LENGTH)); + lwsl_notice("%s: incoming content length %llu\n", __func__, + (unsigned long long)wsi->u.http.rx_content_length); + wsi->u.http.rx_content_remain = wsi->u.http.rx_content_length; + } else /* can't do 1.1 without a content length or chunked */ + if (!wsi->chunked) + wsi->u.http.connection_type = HTTP_CONNECTION_CLOSE; + + /* + * we seem to be good to go, give client last chance to check + * headers and OK it + */ + if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, + wsi->user_space, NULL, 0)) { + + cce = "HS: disallowed by client filter"; + goto bail2; + } + + /* clear his proxy connection timeout */ + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + + /* call him back to inform him he is up */ + if (wsi->protocol->callback(wsi, + LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP, + wsi->user_space, NULL, 0)) { + cce = "HS: disallowed at ESTABLISHED"; + goto bail3; + } + + /* free up his parsing allocations */ + lws_header_table_detach(wsi, 0); + + lwsl_notice("%s: client connection up\n", __func__); + + return 0; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_ACCEPT) == 0) { + lwsl_info("no ACCEPT\n"); + cce = "HS: ACCEPT missing"; + goto bail3; + } + + if (p && strncmp(p, "101", 3)) { + lwsl_warn( + "lws_client_handshake: got bad HTTP response '%s'\n", p); + cce = "HS: ws upgrade response not 101"; + goto bail3; + } + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE); + if (!p) { + lwsl_info("no UPGRADE\n"); + cce = "HS: UPGRADE missing"; + goto bail3; + } + strtolower(p); + if (strcmp(p, "websocket")) { + lwsl_warn( + "lws_client_handshake: got bad Upgrade header '%s'\n", p); + cce = "HS: Upgrade to something other than websocket"; + goto bail3; + } + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_CONNECTION); + if (!p) { + lwsl_info("no Connection hdr\n"); + cce = "HS: CONNECTION missing"; + goto bail3; + } + strtolower(p); + if (strcmp(p, "upgrade")) { + lwsl_warn("lws_client_int_s_hs: bad header %s\n", p); + cce = "HS: UPGRADE malformed"; + goto bail3; + } + + pc = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + if (!pc) { + lwsl_parser("lws_client_int_s_hs: no protocol list\n"); + } else + lwsl_parser("lws_client_int_s_hs: protocol list '%s'\n", pc); + + /* + * confirm the protocol the server wants to talk was in the list + * of protocols we offered + */ + + len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); + if (!len) { + lwsl_info("lws_client_int_s_hs: WSI_TOKEN_PROTOCOL is null\n"); + /* + * no protocol name to work from, + * default to first protocol + */ + n = 0; + wsi->protocol = &wsi->vhost->protocols[0]; + goto check_extensions; + } + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL); + len = strlen(p); + + while (pc && *pc && !okay) { + if (!strncmp(pc, p, len) && + (pc[len] == ',' || pc[len] == '\0')) { + okay = 1; + continue; + } + while (*pc && *pc++ != ',') + ; + while (*pc && *pc == ' ') + pc++; + } + + if (!okay) { + lwsl_err("lws_client_int_s_hs: got bad protocol %s\n", p); + cce = "HS: PROTOCOL malformed"; + goto bail2; + } + + /* + * identify the selected protocol struct and set it + */ + n = 0; + wsi->protocol = NULL; + while (wsi->vhost->protocols[n].callback && !wsi->protocol) { + if (strcmp(p, wsi->vhost->protocols[n].name) == 0) { + wsi->protocol = &wsi->vhost->protocols[n]; + break; + } + n++; + } + + if (wsi->protocol == NULL) { + lwsl_err("lws_client_int_s_hs: fail protocol %s\n", p); + cce = "HS: Cannot match protocol"; + goto bail2; + } + +check_extensions: + /* + * stitch protocol choice into the vh protocol linked list + * We always insert ourselves at the start of the list + * + * X <-> B + * X <-> pAn <-> pB + */ + //lwsl_err("%s: pre insert vhost start wsi %p, that wsi prev == %p\n", + // __func__, + // wsi->vhost->same_vh_protocol_list[n], + // wsi->same_vh_protocol_prev); + wsi->same_vh_protocol_prev = /* guy who points to us */ + &wsi->vhost->same_vh_protocol_list[n]; + wsi->same_vh_protocol_next = /* old first guy is our next */ + wsi->vhost->same_vh_protocol_list[n]; + /* we become the new first guy */ + wsi->vhost->same_vh_protocol_list[n] = wsi; + + if (wsi->same_vh_protocol_next) + /* old first guy points back to us now */ + wsi->same_vh_protocol_next->same_vh_protocol_prev = + &wsi->same_vh_protocol_next; + +#ifndef LWS_NO_EXTENSIONS + /* instantiate the accepted extensions */ + + if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) { + lwsl_ext("no client extensions allowed by server\n"); + goto check_accept; + } + + /* + * break down the list of server accepted extensions + * and go through matching them or identifying bogons + */ + + if (lws_hdr_copy(wsi, sb, context->pt_serv_buf_size, WSI_TOKEN_EXTENSIONS) < 0) { + lwsl_warn("ext list from server failed to copy\n"); + cce = "HS: EXT: list too big"; + goto bail2; + } + + c = sb; + n = 0; + ignore = 0; + a = NULL; + while (more) { + + if (*c && (*c != ',' && *c != '\t')) { + if (*c == ';') { + ignore = 1; + if (!a) + a = c + 1; + } + if (ignore || *c == ' ') { + c++; + continue; + } + + ext_name[n] = *c++; + if (n < sizeof(ext_name) - 1) + n++; + continue; + } + ext_name[n] = '\0'; + ignore = 0; + if (!*c) + more = 0; + else { + c++; + if (!n) + continue; + } + + /* check we actually support it */ + + lwsl_notice("checking client ext %s\n", ext_name); + + n = 0; + ext = wsi->vhost->extensions; + while (ext && ext->callback) { + if (strcmp(ext_name, ext->name)) { + ext++; + continue; + } + + n = 1; + lwsl_notice("instantiating client ext %s\n", ext_name); + + /* instantiate the extension on this conn */ + + wsi->active_extensions[wsi->count_act_ext] = ext; + + /* allow him to construct his ext instance */ + + if (ext->callback(lws_get_context(wsi), ext, wsi, + LWS_EXT_CB_CLIENT_CONSTRUCT, + (void *)&wsi->act_ext_user[wsi->count_act_ext], + (void *)&opts, 0)) { + lwsl_info(" ext %s failed construction\n", ext_name); + ext++; + continue; + } + + /* + * allow the user code to override ext defaults if it + * wants to + */ + ext_name[0] = '\0'; + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_WS_EXT_DEFAULTS, + (char *)ext->name, ext_name, + sizeof(ext_name))) { + cce = "HS: EXT: failed setting defaults"; + goto bail2; + } + + if (ext_name[0] && + lws_ext_parse_options(ext, wsi, wsi->act_ext_user[ + wsi->count_act_ext], opts, ext_name, + strlen(ext_name))) { + lwsl_err("%s: unable to parse user defaults '%s'", + __func__, ext_name); + cce = "HS: EXT: failed parsing defaults"; + goto bail2; + } + + /* + * give the extension the server options + */ + if (a && lws_ext_parse_options(ext, wsi, + wsi->act_ext_user[wsi->count_act_ext], + opts, a, c - a)) { + lwsl_err("%s: unable to parse remote def '%s'", + __func__, a); + cce = "HS: EXT: failed parsing options"; + goto bail2; + } + + if (ext->callback(lws_get_context(wsi), ext, wsi, + LWS_EXT_CB_OPTION_CONFIRM, + wsi->act_ext_user[wsi->count_act_ext], + NULL, 0)) { + lwsl_err("%s: ext %s rejects server options %s", + __func__, ext->name, a); + cce = "HS: EXT: Rejects server options"; + goto bail2; + } + + wsi->count_act_ext++; + + ext++; + } + + if (n == 0) { + lwsl_warn("Unknown ext '%s'!\n", ext_name); + cce = "HS: EXT: unknown ext"; + goto bail2; + } + + a = NULL; + n = 0; + } + +check_accept: +#endif + + /* + * Confirm his accept token is the one we precomputed + */ + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_ACCEPT); + if (strcmp(p, wsi->u.hdr.ah->initial_handshake_hash_base64)) { + lwsl_warn("lws_client_int_s_hs: accept '%s' wrong vs '%s'\n", p, + wsi->u.hdr.ah->initial_handshake_hash_base64); + cce = "HS: Accept hash wrong"; + goto bail2; + } + + /* allocate the per-connection user memory (if any) */ + if (lws_ensure_user_space(wsi)) { + lwsl_err("Problem allocating wsi user mem\n"); + cce = "HS: OOM"; + goto bail2; + } + + /* + * we seem to be good to go, give client last chance to check + * headers and OK it + */ + if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH, + wsi->user_space, NULL, 0)) { + cce = "HS: Rejected by filter cb"; + goto bail2; + } + + /* clear his proxy connection timeout */ + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* free up his parsing allocations */ + lws_header_table_detach(wsi, 0); + + lws_union_transition(wsi, LWSCM_WS_CLIENT); + wsi->state = LWSS_ESTABLISHED; + lws_restart_ws_ping_pong_timer(wsi); + + wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + + /* + * create the frame buffer for this connection according to the + * size mentioned in the protocol definition. If 0 there, then + * use a big default for compatibility + */ + n = wsi->protocol->rx_buffer_size; + if (!n) + n = context->pt_serv_buf_size; + n += LWS_PRE; + wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, "client frame buffer"); + if (!wsi->u.ws.rx_ubuf) { + lwsl_err("Out of Mem allocating rx buffer %d\n", n); + cce = "HS: OOM"; + goto bail2; + } + wsi->u.ws.rx_ubuf_alloc = n; + lwsl_info("Allocating client RX buffer %d\n", n); + +#if !defined(LWS_WITH_ESP32) + if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, (const char *)&n, + sizeof n)) { + lwsl_warn("Failed to set SNDBUF to %d", n); + cce = "HS: SO_SNDBUF failed"; + goto bail3; + } +#endif + + lwsl_debug("handshake OK for protocol %s\n", wsi->protocol->name); + + /* call him back to inform him he is up */ + + if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_ESTABLISHED, + wsi->user_space, NULL, 0)) { + cce = "HS: Rejected at CLIENT_ESTABLISHED"; + goto bail3; + } +#ifndef LWS_NO_EXTENSIONS + /* + * inform all extensions, not just active ones since they + * already know + */ + ext = wsi->vhost->extensions; + + while (ext && ext->callback) { + v = NULL; + for (n = 0; n < wsi->count_act_ext; n++) + if (wsi->active_extensions[n] == ext) + v = wsi->act_ext_user[n]; + + ext->callback(context, ext, wsi, + LWS_EXT_CB_ANY_WSI_ESTABLISHED, v, NULL, 0); + ext++; + } +#endif + + return 0; + +bail3: + close_reason = LWS_CLOSE_STATUS_NOSTATUS; + +bail2: + if (wsi->protocol) + wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, (void *)cce, + (unsigned int)strlen(cce)); + wsi->already_did_cce = 1; + + lwsl_info("closing connection due to bail2 connection error\n"); + + /* closing will free up his parsing allocations */ + lws_close_free_wsi(wsi, close_reason); + + return 1; +} + + +char * +lws_generate_client_handshake(struct lws *wsi, char *pkt) +{ + char buf[128], hash[20], key_b64[40], *p = pkt; + struct lws_context *context = wsi->context; + const char *meth; + int n; +#ifndef LWS_NO_EXTENSIONS + const struct lws_extension *ext; + int ext_count = 0; +#endif + const char *pp = lws_hdr_simple_ptr(wsi, + _WSI_TOKEN_CLIENT_SENT_PROTOCOLS); + + meth = lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_METHOD); + if (!meth) { + meth = "GET"; + wsi->do_ws = 1; + } else { + wsi->do_ws = 0; + } + + if (!strcmp(meth, "RAW")) { + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + lwsl_notice("client transition to raw\n"); + + if (pp) { + const struct lws_protocols *pr; + + pr = lws_vhost_name_to_protocol(wsi->vhost, pp); + + if (!pr) { + lwsl_err("protocol %s not enabled on vhost\n", + pp); + return NULL; + } + + lws_bind_protocol(wsi, pr); + } + + if ((wsi->protocol->callback)(wsi, + LWS_CALLBACK_RAW_ADOPT, + wsi->user_space, NULL, 0)) + return NULL; + + lws_header_table_force_to_detachable_state(wsi); + lws_union_transition(wsi, LWSCM_RAW); + lws_header_table_detach(wsi, 1); + + return NULL; + } + + if (wsi->do_ws) { + /* + * create the random key + */ + n = lws_get_random(context, hash, 16); + if (n != 16) { + lwsl_err("Unable to read from random dev %s\n", + SYSTEM_RANDOM_FILEPATH); + return NULL; + } + + lws_b64_encode_string(hash, 16, key_b64, sizeof(key_b64)); + } + + /* + * 04 example client handshake + * + * GET /chat HTTP/1.1 + * Host: server.example.com + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== + * Sec-WebSocket-Origin: http://example.com + * Sec-WebSocket-Protocol: chat, superchat + * Sec-WebSocket-Version: 4 + */ + + p += sprintf(p, "%s %s HTTP/1.1\x0d\x0a", meth, + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_URI)); + + p += sprintf(p, "Pragma: no-cache\x0d\x0a" + "Cache-Control: no-cache\x0d\x0a"); + + p += sprintf(p, "Host: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_HOST)); + + if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)) { + if (lws_check_opt(context->options, LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN)) + p += sprintf(p, "Origin: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); + else + p += sprintf(p, "Origin: http://%s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_ORIGIN)); + } + + if (wsi->do_ws) { + p += sprintf(p, "Upgrade: websocket\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Key: "); + strcpy(p, key_b64); + p += strlen(key_b64); + p += sprintf(p, "\x0d\x0a"); + if (lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)) + p += sprintf(p, "Sec-WebSocket-Protocol: %s\x0d\x0a", + lws_hdr_simple_ptr(wsi, _WSI_TOKEN_CLIENT_SENT_PROTOCOLS)); + + /* tell the server what extensions we could support */ + +#ifndef LWS_NO_EXTENSIONS + ext = wsi->vhost->extensions; + while (ext && ext->callback) { + n = lws_ext_cb_all_exts(context, wsi, + LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION, + (char *)ext->name, 0); + if (n) { /* an extension vetos us */ + lwsl_ext("ext %s vetoed\n", (char *)ext->name); + ext++; + continue; + } + n = wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED, + wsi->user_space, (char *)ext->name, 0); + + /* + * zero return from callback means + * go ahead and allow the extension, + * it's what we get if the callback is + * unhandled + */ + + if (n) { + ext++; + continue; + } + + /* apply it */ + + if (ext_count) + *p++ = ','; + else + p += sprintf(p, "Sec-WebSocket-Extensions: "); + p += sprintf(p, "%s", ext->client_offer); + ext_count++; + + ext++; + } + if (ext_count) + p += sprintf(p, "\x0d\x0a"); +#endif + + if (wsi->ietf_spec_revision) + p += sprintf(p, "Sec-WebSocket-Version: %d\x0d\x0a", + wsi->ietf_spec_revision); + + /* prepare the expected server accept response */ + + key_b64[39] = '\0'; /* enforce composed length below buf sizeof */ + n = sprintf(buf, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", key_b64); + + lws_SHA1((unsigned char *)buf, n, (unsigned char *)hash); + + lws_b64_encode_string(hash, 20, + wsi->u.hdr.ah->initial_handshake_hash_base64, + sizeof(wsi->u.hdr.ah->initial_handshake_hash_base64)); + } + + /* give userland a chance to append, eg, cookies */ + + if (wsi->protocol->callback(wsi, LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER, + wsi->user_space, &p, (pkt + context->pt_serv_buf_size) - p - 12)) + return NULL; + + p += sprintf(p, "\x0d\x0a"); + + return p; +} + diff --git a/thirdparty/lws/client/ssl-client.c b/thirdparty/lws/client/ssl-client.c new file mode 100644 index 0000000000..b69fd2da30 --- /dev/null +++ b/thirdparty/lws/client/ssl-client.c @@ -0,0 +1,625 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +extern int openssl_websocket_private_data_index, + openssl_SSL_CTX_private_data_index; + +extern void +lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info); + +extern int lws_ssl_get_error(struct lws *wsi, int n); + +#if defined(USE_WOLFSSL) +#else + +static int +OpenSSL_client_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ +#if defined(LWS_WITH_MBEDTLS) + lwsl_notice("%s\n", __func__); + + return 0; +#else + SSL *ssl; + int n; + struct lws *wsi; + + /* keep old behaviour accepting self-signed server certs */ + if (!preverify_ok) { + int err = X509_STORE_CTX_get_error(x509_ctx); + + if (err != X509_V_OK) { + ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index); + + if ((err == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || + err == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) && + wsi->use_ssl & LCCSCF_ALLOW_SELFSIGNED) { + lwsl_notice("accepting self-signed certificate (verify_callback)\n"); + X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); + return 1; // ok + } else if ((err == X509_V_ERR_CERT_NOT_YET_VALID || + err == X509_V_ERR_CERT_HAS_EXPIRED) && + wsi->use_ssl & LCCSCF_ALLOW_EXPIRED) { + if (err == X509_V_ERR_CERT_NOT_YET_VALID) + lwsl_notice("accepting not yet valid certificate (verify_callback)\n"); + else if (err == X509_V_ERR_CERT_HAS_EXPIRED) + lwsl_notice("accepting expired certificate (verify_callback)\n"); + X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); + return 1; // ok + } + } + } + + ssl = X509_STORE_CTX_get_ex_data(x509_ctx, SSL_get_ex_data_X509_STORE_CTX_idx()); + wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index); + + n = lws_get_context_protocol(wsi->context, 0).callback(wsi, + LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION, + x509_ctx, ssl, preverify_ok); + + /* keep old behaviour if something wrong with server certs */ + /* if ssl error is overruled in callback and cert is ok, + * X509_STORE_CTX_set_error(x509_ctx, X509_V_OK); must be set and + * return value is 0 from callback */ + if (!preverify_ok) { + int err = X509_STORE_CTX_get_error(x509_ctx); + + if (err != X509_V_OK) { /* cert validation error was not handled in callback */ + int depth = X509_STORE_CTX_get_error_depth(x509_ctx); + const char* msg = X509_verify_cert_error_string(err); + lwsl_err("SSL error: %s (preverify_ok=%d;err=%d;depth=%d)\n", msg, preverify_ok, err, depth); + return preverify_ok; // not ok + } + } + /* convert callback return code from 0 = OK to verify callback return value 1 = OK */ + return !n; +#endif +} +#endif + +int +lws_ssl_client_bio_create(struct lws *wsi) +{ + char hostname[128], *p; + + if (lws_hdr_copy(wsi, hostname, sizeof(hostname), + _WSI_TOKEN_CLIENT_HOST) <= 0) { + lwsl_err("%s: Unable to get hostname\n", __func__); + + return -1; + } + + /* + * remove any :port part on the hostname... necessary for network + * connection but typical certificates do not contain it + */ + p = hostname; + while (*p) { + if (*p == ':') { + *p = '\0'; + break; + } + p++; + } + + wsi->ssl = SSL_new(wsi->vhost->ssl_client_ctx); + if (!wsi->ssl) { + lwsl_err("SSL_new failed: %s\n", + ERR_error_string(lws_ssl_get_error(wsi, 0), NULL)); + lws_ssl_elaborate_error(); + return -1; + } + +#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK) + if (wsi->vhost->ssl_info_event_mask) + SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback); +#endif + +#if defined LWS_HAVE_X509_VERIFY_PARAM_set1_host + X509_VERIFY_PARAM *param; + (void)param; + + if (!(wsi->use_ssl & LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK)) { + param = SSL_get0_param(wsi->ssl); + /* Enable automatic hostname checks */ + X509_VERIFY_PARAM_set_hostflags(param, + X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); + X509_VERIFY_PARAM_set1_host(param, hostname, 0); + } + +#endif + +#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) +#ifndef USE_OLD_CYASSL + /* OpenSSL_client_verify_callback will be called @ SSL_connect() */ + SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback); +#endif +#endif + +#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) + SSL_set_mode(wsi->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); +#endif + /* + * use server name indication (SNI), if supported, + * when establishing connection + */ +#ifdef USE_WOLFSSL +#ifdef USE_OLD_CYASSL +#ifdef CYASSL_SNI_HOST_NAME + CyaSSL_UseSNI(wsi->ssl, CYASSL_SNI_HOST_NAME, hostname, strlen(hostname)); +#endif +#else +#ifdef WOLFSSL_SNI_HOST_NAME + wolfSSL_UseSNI(wsi->ssl, WOLFSSL_SNI_HOST_NAME, hostname, strlen(hostname)); +#endif +#endif +#else +#if defined(LWS_WITH_MBEDTLS) + if (wsi->vhost->x509_client_CA) + SSL_set_verify(wsi->ssl, SSL_VERIFY_PEER, OpenSSL_client_verify_callback); + else + SSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, OpenSSL_client_verify_callback); + +#else +#ifdef SSL_CTRL_SET_TLSEXT_HOSTNAME + SSL_set_tlsext_host_name(wsi->ssl, hostname); +#endif +#endif +#endif + +#ifdef USE_WOLFSSL + /* + * wolfSSL/CyaSSL does certificate verification differently + * from OpenSSL. + * If we should ignore the certificate, we need to set + * this before SSL_new and SSL_connect is called. + * Otherwise the connect will simply fail with error code -155 + */ +#ifdef USE_OLD_CYASSL + if (wsi->use_ssl == 2) + CyaSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL); +#else + if (wsi->use_ssl == 2) + wolfSSL_set_verify(wsi->ssl, SSL_VERIFY_NONE, NULL); +#endif +#endif /* USE_WOLFSSL */ + +#if !defined(LWS_WITH_MBEDTLS) + wsi->client_bio = BIO_new_socket(wsi->desc.sockfd, BIO_NOCLOSE); + SSL_set_bio(wsi->ssl, wsi->client_bio, wsi->client_bio); +#else + SSL_set_fd(wsi->ssl, wsi->desc.sockfd); +#endif + +#ifdef USE_WOLFSSL +#ifdef USE_OLD_CYASSL + CyaSSL_set_using_nonblock(wsi->ssl, 1); +#else + wolfSSL_set_using_nonblock(wsi->ssl, 1); +#endif +#else +#if !defined(LWS_WITH_MBEDTLS) + BIO_set_nbio(wsi->client_bio, 1); /* nonblocking */ +#endif +#endif + +#if !defined(LWS_WITH_MBEDTLS) + SSL_set_ex_data(wsi->ssl, openssl_websocket_private_data_index, + wsi); +#endif + + return 0; +} + +#if defined(LWS_WITH_MBEDTLS) +int ERR_get_error(void) +{ + return 0; +} +#endif + +int +lws_ssl_client_connect1(struct lws *wsi) +{ + struct lws_context *context = wsi->context; + int n = 0; + + lws_latency_pre(context, wsi); + + n = SSL_connect(wsi->ssl); + + lws_latency(context, wsi, + "SSL_connect LWSCM_WSCL_ISSUE_HANDSHAKE", n, n > 0); + + if (n < 0) { + n = lws_ssl_get_error(wsi, n); + + if (n == SSL_ERROR_WANT_READ) + goto some_wait; + + if (n == SSL_ERROR_WANT_WRITE) { + /* + * wants us to retry connect due to + * state of the underlying ssl layer... + * but since it may be stalled on + * blocked write, no incoming data may + * arrive to trigger the retry. + * Force (possibly many times if the SSL + * state persists in returning the + * condition code, but other sockets + * are getting serviced inbetweentimes) + * us to get called back when writable. + */ + lwsl_info("%s: WANT_WRITE... retrying\n", __func__); + lws_callback_on_writable(wsi); +some_wait: + wsi->mode = LWSCM_WSCL_WAITING_SSL; + + return 0; /* no error */ + } + + { + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char *p = (char *)&pt->serv_buf[0]; + char *sb = p; + + lwsl_err("ssl hs1 error, X509_V_ERR = %d: %s\n", + n, ERR_error_string(n, sb)); + lws_ssl_elaborate_error(); + } + + n = -1; + } + + if (n <= 0) { + /* + * retry if new data comes until we + * run into the connection timeout or win + */ + + unsigned long error = ERR_get_error(); + + if (error != SSL_ERROR_NONE) { + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char *p = (char *)&pt->serv_buf[0]; + char *sb = p; + lwsl_err("SSL connect error %lu: %s\n", + error, ERR_error_string(error, sb)); + return -1; + } + + return 0; + } + + return 1; +} + +int +lws_ssl_client_connect2(struct lws *wsi) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + char *p = (char *)&pt->serv_buf[0]; + char *sb = p; + int n = 0; + + if (wsi->mode == LWSCM_WSCL_WAITING_SSL) { + lws_latency_pre(context, wsi); + n = SSL_connect(wsi->ssl); + lwsl_debug("%s: SSL_connect says %d\n", __func__, n); + + lws_latency(context, wsi, + "SSL_connect LWSCM_WSCL_WAITING_SSL", n, n > 0); + + if (n < 0) { + n = lws_ssl_get_error(wsi, n); + + if (n == SSL_ERROR_WANT_READ) { + lwsl_info("SSL_connect WANT_READ... retrying\n"); + + wsi->mode = LWSCM_WSCL_WAITING_SSL; + + return 0; /* no error */ + } + + if (n == SSL_ERROR_WANT_WRITE) { + /* + * wants us to retry connect due to + * state of the underlying ssl layer... + * but since it may be stalled on + * blocked write, no incoming data may + * arrive to trigger the retry. + * Force (possibly many times if the SSL + * state persists in returning the + * condition code, but other sockets + * are getting serviced inbetweentimes) + * us to get called back when writable. + */ + lwsl_info("SSL_connect WANT_WRITE... retrying\n"); + lws_callback_on_writable(wsi); + + wsi->mode = LWSCM_WSCL_WAITING_SSL; + + return 0; /* no error */ + } + + n = -1; + } + + if (n <= 0) { + /* + * retry if new data comes until we + * run into the connection timeout or win + */ + unsigned long error = ERR_get_error(); + if (error != SSL_ERROR_NONE) { + lwsl_err("SSL connect error %lu: %s\n", + error, ERR_error_string(error, sb)); + return -1; + } + } + } + +#if defined(LWS_WITH_MBEDTLS) + { + X509 *peer = SSL_get_peer_certificate(wsi->ssl); + + if (!peer) { + lwsl_notice("peer did not provide cert\n"); + + return -1; + } + lwsl_notice("peer provided cert\n"); + } +#endif + +#ifndef USE_WOLFSSL + /* + * See comment above about wolfSSL certificate + * verification + */ + lws_latency_pre(context, wsi); + n = SSL_get_verify_result(wsi->ssl); + lws_latency(context, wsi, + "SSL_get_verify_result LWS_CONNMODE..HANDSHAKE", n, n > 0); + + lwsl_debug("get_verify says %d\n", n); + + if (n != X509_V_OK) { + if ((n == X509_V_ERR_DEPTH_ZERO_SELF_SIGNED_CERT || + n == X509_V_ERR_SELF_SIGNED_CERT_IN_CHAIN) && + (wsi->use_ssl & LCCSCF_ALLOW_SELFSIGNED)) { + lwsl_notice("accepting self-signed certificate\n"); + } else if ((n == X509_V_ERR_CERT_NOT_YET_VALID || + n == X509_V_ERR_CERT_HAS_EXPIRED) && + (wsi->use_ssl & LCCSCF_ALLOW_EXPIRED)) { + lwsl_notice("accepting expired certificate\n"); + } else if (n == X509_V_ERR_CERT_NOT_YET_VALID) { + lwsl_notice("Cert is from the future... " + "probably our clock... accepting...\n"); + } else { + lwsl_err("server's cert didn't look good, X509_V_ERR = %d: %s\n", + n, ERR_error_string(n, sb)); + lws_ssl_elaborate_error(); + return -1; + } + } + +#endif /* USE_WOLFSSL */ + + return 1; +} + + +int lws_context_init_client_ssl(struct lws_context_creation_info *info, + struct lws_vhost *vhost) +{ + SSL_METHOD *method = NULL; + struct lws wsi; + unsigned long error; + const char *ca_filepath = info->ssl_ca_filepath; +#if !defined(LWS_WITH_MBEDTLS) + const char *cipher_list = info->ssl_cipher_list; + const char *private_key_filepath = info->ssl_private_key_filepath; + const char *cert_filepath = info->ssl_cert_filepath; + int n; + + if (vhost->options & LWS_SERVER_OPTION_ONLY_RAW) + return 0; + + /* + * for backwards-compatibility default to using ssl_... members, but + * if the newer client-specific ones are given, use those + */ + if (info->client_ssl_cipher_list) + cipher_list = info->client_ssl_cipher_list; + if (info->client_ssl_cert_filepath) + cert_filepath = info->client_ssl_cert_filepath; + if (info->client_ssl_private_key_filepath) + private_key_filepath = info->client_ssl_private_key_filepath; +#endif + if (info->client_ssl_ca_filepath) + ca_filepath = info->client_ssl_ca_filepath; + + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) + return 0; + + if (vhost->ssl_client_ctx) + return 0; + + if (info->provided_client_ssl_ctx) { + /* use the provided OpenSSL context if given one */ + vhost->ssl_client_ctx = info->provided_client_ssl_ctx; + /* nothing for lib to delete */ + vhost->user_supplied_ssl_ctx = 1; + + return 0; + } + + /* basic openssl init already happened in context init */ + + /* choose the most recent spin of the api */ +#if defined(LWS_HAVE_TLS_CLIENT_METHOD) + method = (SSL_METHOD *)TLS_client_method(); +#elif defined(LWS_HAVE_TLSV1_2_CLIENT_METHOD) + method = (SSL_METHOD *)TLSv1_2_client_method(); +#else + method = (SSL_METHOD *)SSLv23_client_method(); +#endif + if (!method) { + error = ERR_get_error(); + lwsl_err("problem creating ssl method %lu: %s\n", + error, ERR_error_string(error, + (char *)vhost->context->pt[0].serv_buf)); + return 1; + } + /* create context */ + vhost->ssl_client_ctx = SSL_CTX_new(method); + if (!vhost->ssl_client_ctx) { + error = ERR_get_error(); + lwsl_err("problem creating ssl context %lu: %s\n", + error, ERR_error_string(error, + (char *)vhost->context->pt[0].serv_buf)); + return 1; + } + + lwsl_notice("created client ssl context for %s\n", vhost->name); + +#ifdef SSL_OP_NO_COMPRESSION + SSL_CTX_set_options(vhost->ssl_client_ctx, SSL_OP_NO_COMPRESSION); +#endif + +#if defined(LWS_WITH_MBEDTLS) + if (ca_filepath) { + lws_filepos_t len; + uint8_t *buf; + /* + * prototype this here, the shim does not export it in the + * header, and we need to use the shim unchanged for ESP32 case + */ + X509 *d2i_X509(X509 **cert, const unsigned char *buffer, long len); + + if (alloc_file(vhost->context, ca_filepath, &buf, &len)) { + lwsl_err("Load CA cert file %s failed\n", ca_filepath); + return 1; + } + + vhost->x509_client_CA = d2i_X509(NULL, buf, len); + free(buf); + if (!vhost->x509_client_CA) { + lwsl_err("client CA: x509 parse failed\n"); + return 1; + } + + SSL_CTX_add_client_CA(vhost->ssl_client_ctx, + vhost->x509_client_CA); + + lwsl_notice("client loaded CA for verification %s\n", ca_filepath); + } +#else + SSL_CTX_set_options(vhost->ssl_client_ctx, + SSL_OP_CIPHER_SERVER_PREFERENCE); + + if (cipher_list) + SSL_CTX_set_cipher_list(vhost->ssl_client_ctx, cipher_list); + +#ifdef LWS_SSL_CLIENT_USE_OS_CA_CERTS + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS)) + /* loads OS default CA certs */ + SSL_CTX_set_default_verify_paths(vhost->ssl_client_ctx); +#endif + + /* openssl init for cert verification (for client sockets) */ + if (!ca_filepath) { + if (!SSL_CTX_load_verify_locations( + vhost->ssl_client_ctx, NULL, LWS_OPENSSL_CLIENT_CERTS)) + lwsl_err("Unable to load SSL Client certs from %s " + "(set by LWS_OPENSSL_CLIENT_CERTS) -- " + "client ssl isn't going to work\n", + LWS_OPENSSL_CLIENT_CERTS); + } else + if (!SSL_CTX_load_verify_locations( + vhost->ssl_client_ctx, ca_filepath, NULL)) { + lwsl_err( + "Unable to load SSL Client certs " + "file from %s -- client ssl isn't " + "going to work\n", info->client_ssl_ca_filepath); + lws_ssl_elaborate_error(); + } + else + lwsl_info("loaded ssl_ca_filepath\n"); + + /* + * callback allowing user code to load extra verification certs + * helping the client to verify server identity + */ + + /* support for client-side certificate authentication */ + if (cert_filepath) { + lwsl_notice("%s: doing cert filepath\n", __func__); + n = SSL_CTX_use_certificate_chain_file(vhost->ssl_client_ctx, + cert_filepath); + if (n < 1) { + lwsl_err("problem %d getting cert '%s'\n", n, + cert_filepath); + lws_ssl_elaborate_error(); + return 1; + } + lwsl_notice("Loaded client cert %s\n", cert_filepath); + } + if (private_key_filepath) { + lwsl_notice("%s: doing private key filepath\n", __func__); + lws_ssl_bind_passphrase(vhost->ssl_client_ctx, info); + /* set the private key from KeyFile */ + if (SSL_CTX_use_PrivateKey_file(vhost->ssl_client_ctx, + private_key_filepath, SSL_FILETYPE_PEM) != 1) { + lwsl_err("use_PrivateKey_file '%s'\n", + private_key_filepath); + lws_ssl_elaborate_error(); + return 1; + } + lwsl_notice("Loaded client cert private key %s\n", + private_key_filepath); + + /* verify private key */ + if (!SSL_CTX_check_private_key(vhost->ssl_client_ctx)) { + lwsl_err("Private SSL key doesn't match cert\n"); + return 1; + } + } +#endif + /* + * give him a fake wsi with context set, so he can use + * lws_get_context() in the callback + */ + memset(&wsi, 0, sizeof(wsi)); + wsi.vhost = vhost; + wsi.context = vhost->context; + + vhost->protocols[0].callback(&wsi, + LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS, + vhost->ssl_client_ctx, NULL, 0); + + return 0; +} diff --git a/thirdparty/lws/context.c b/thirdparty/lws/context.c new file mode 100644 index 0000000000..f67476b1e3 --- /dev/null +++ b/thirdparty/lws/context.c @@ -0,0 +1,1628 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +#ifndef LWS_BUILD_HASH +#define LWS_BUILD_HASH "unknown-build-hash" +#endif + +static const char *library_version = LWS_LIBRARY_VERSION " " LWS_BUILD_HASH; + +/** + * lws_get_library_version: get version and git hash library built from + * + * returns a const char * to a string like "1.1 178d78c" + * representing the library version followed by the git head hash it + * was built from + */ +LWS_VISIBLE const char * +lws_get_library_version(void) +{ + return library_version; +} + +static const char * const mount_protocols[] = { + "http://", + "https://", + "file://", + "cgi://", + ">http://", + ">https://", + "callback://" +}; + +#if defined(LWS_WITH_HTTP2) +/* + * These are the standardized defaults. + * Override what actually goes in the vhost settings in platform or user code. + * Leave these alone because they are used to determine "what is different + * from the protocol defaults". + */ +const struct http2_settings lws_h2_defaults = { { + 1, + /* H2SET_HEADER_TABLE_SIZE */ 4096, + /* *** This controls how many entries in the dynamic table *** + * Allows the sender to inform the remote endpoint of the maximum + * size of the header compression table used to decode header + * blocks, in octets. The encoder can select any size equal to or + * less than this value by using signaling specific to the header + * compression format inside a header block (see [COMPRESSION]). + * The initial value is 4,096 octets. + */ + /* H2SET_ENABLE_PUSH */ 1, + /* H2SET_MAX_CONCURRENT_STREAMS */ 0x7fffffff, + /* H2SET_INITIAL_WINDOW_SIZE */ 65535, + /* H2SET_MAX_FRAME_SIZE */ 16384, + /* H2SET_MAX_HEADER_LIST_SIZE */ 0x7fffffff, + /*< This advisory setting informs a peer of the maximum size of + * header list that the sender is prepared to accept, in octets. + * The value is based on the uncompressed size of header fields, + * including the length of the name and value in octets plus an + * overhead of 32 octets for each header field. + */ + +}}; + +const struct http2_settings lws_h2_stock_settings = { { + 1, + /* H2SET_HEADER_TABLE_SIZE */ 4096, + /* *** This controls how many entries in the dynamic table *** + * Allows the sender to inform the remote endpoint of the maximum + * size of the header compression table used to decode header + * blocks, in octets. The encoder can select any size equal to or + * less than this value by using signaling specific to the header + * compression format inside a header block (see [COMPRESSION]). + * The initial value is 4,096 octets. + * + * Can't pass h2spec with less than 4096 here... + */ + /* H2SET_ENABLE_PUSH */ 1, + /* H2SET_MAX_CONCURRENT_STREAMS */ 24, + /* H2SET_INITIAL_WINDOW_SIZE */ 65535, + /* H2SET_MAX_FRAME_SIZE */ 16384, + /* H2SET_MAX_HEADER_LIST_SIZE */ 4096, + /*< This advisory setting informs a peer of the maximum size of + * header list that the sender is prepared to accept, in octets. + * The value is based on the uncompressed size of header fields, + * including the length of the name and value in octets plus an + * overhead of 32 octets for each header field. + */ + +}}; +#endif + +LWS_VISIBLE void * +lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, + const struct lws_protocols *prot, int size) +{ + int n = 0; + + /* allocate the vh priv array only on demand */ + if (!vhost->protocol_vh_privs) { + vhost->protocol_vh_privs = (void **)lws_zalloc( + vhost->count_protocols * sizeof(void *), "protocol_vh_privs"); + if (!vhost->protocol_vh_privs) + return NULL; + } + + while (n < vhost->count_protocols && &vhost->protocols[n] != prot) + n++; + + if (n == vhost->count_protocols) { + n = 0; + while (n < vhost->count_protocols && + strcmp(vhost->protocols[n].name, prot->name)) + n++; + + if (n == vhost->count_protocols) + return NULL; + } + + vhost->protocol_vh_privs[n] = lws_zalloc(size, "vh priv"); + return vhost->protocol_vh_privs[n]; +} + +LWS_VISIBLE void * +lws_protocol_vh_priv_get(struct lws_vhost *vhost, + const struct lws_protocols *prot) +{ + int n = 0; + + if (!vhost || !vhost->protocol_vh_privs) + return NULL; + + while (n < vhost->count_protocols && &vhost->protocols[n] != prot) + n++; + + if (n == vhost->count_protocols) { + n = 0; + while (n < vhost->count_protocols && + strcmp(vhost->protocols[n].name, prot->name)) + n++; + + if (n == vhost->count_protocols) { + lwsl_err("%s: unknown protocol %p\n", __func__, prot); + return NULL; + } + } + + return vhost->protocol_vh_privs[n]; +} + +static const struct lws_protocol_vhost_options * +lws_vhost_protocol_options(struct lws_vhost *vh, const char *name) +{ + const struct lws_protocol_vhost_options *pvo = vh->pvo; + + if (!name) + return NULL; + + while (pvo) { + if (!strcmp(pvo->name, name)) + return pvo; + pvo = pvo->next; + } + + return NULL; +} + +/* + * inform every vhost that hasn't already done it, that + * his protocols are initializing + */ +LWS_VISIBLE int +lws_protocol_init(struct lws_context *context) +{ + struct lws_vhost *vh = context->vhost_list; + const struct lws_protocol_vhost_options *pvo, *pvo1; + struct lws wsi; + int n; + + if (context->doing_protocol_init) + return 0; + + context->doing_protocol_init = 1; + + memset(&wsi, 0, sizeof(wsi)); + wsi.context = context; + + lwsl_info("%s\n", __func__); + + while (vh) { + wsi.vhost = vh; + + /* only do the protocol init once for a given vhost */ + if (vh->created_vhost_protocols) + goto next; + + /* initialize supported protocols on this vhost */ + + for (n = 0; n < vh->count_protocols; n++) { + wsi.protocol = &vh->protocols[n]; + if (!vh->protocols[n].name) + continue; + pvo = lws_vhost_protocol_options(vh, + vh->protocols[n].name); + if (pvo) { + /* + * linked list of options specific to + * vh + protocol + */ + pvo1 = pvo; + pvo = pvo1->options; + + while (pvo) { + lwsl_notice( + " vhost \"%s\", protocol \"%s\", option \"%s\"\n", + vh->name, + vh->protocols[n].name, + pvo->name); + + if (!strcmp(pvo->name, "default")) { + lwsl_notice("Setting default " + "protocol for vh %s to %s\n", + vh->name, + vh->protocols[n].name); + vh->default_protocol_index = n; + } + if (!strcmp(pvo->name, "raw")) { + lwsl_notice("Setting raw " + "protocol for vh %s to %s\n", + vh->name, + vh->protocols[n].name); + vh->raw_protocol_index = n; + } + pvo = pvo->next; + } + + pvo = pvo1->options; + } + + /* + * inform all the protocols that they are doing their + * one-time initialization if they want to. + * + * NOTE the wsi is all zeros except for the context, vh + * + protocol ptrs so lws_get_context(wsi) etc can work + */ + if (vh->protocols[n].callback(&wsi, + LWS_CALLBACK_PROTOCOL_INIT, NULL, + (void *)pvo, 0)) { + lwsl_err("%s: vhost %s failed init\n", __func__, + vh->protocols[n].name); + context->doing_protocol_init = 0; + return 1; + } + } + + vh->created_vhost_protocols = 1; +next: + vh = vh->vhost_next; + } + + context->doing_protocol_init = 0; + + if (!context->protocol_init_done) + lws_finalize_startup(context); + + context->protocol_init_done = 1; + + return 0; +} + +LWS_VISIBLE int +lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct lws_ssl_info *si; +#ifdef LWS_WITH_CGI + struct lws_cgi_args *args; +#endif +#if defined(LWS_WITH_CGI) || defined(LWS_WITH_HTTP_PROXY) + char buf[512]; + int n; +#endif + + switch (reason) { + case LWS_CALLBACK_HTTP: +#ifndef LWS_NO_SERVER + if (lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL)) + return -1; + + if (lws_http_transaction_completed(wsi)) +#endif + return -1; + break; +#if !defined(LWS_NO_SERVER) + case LWS_CALLBACK_HTTP_FILE_COMPLETION: + if (lws_http_transaction_completed(wsi)) + return -1; + break; +#endif + + case LWS_CALLBACK_HTTP_WRITEABLE: +#ifdef LWS_WITH_CGI + if (wsi->reason_bf & (LWS_CB_REASON_AUX_BF__CGI_HEADERS | + LWS_CB_REASON_AUX_BF__CGI)) { + n = lws_cgi_write_split_stdout_headers(wsi); + if (n < 0) { + lwsl_debug("LWS_CB_REASON_AUX_BF__CGI forcing close\n"); + return -1; + } + if (!n) + lws_rx_flow_control(wsi->cgi->stdwsi[LWS_STDOUT], 1); + + if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_HEADERS) + wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI_HEADERS; + else + wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__CGI; + break; + } + + if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__CGI_CHUNK_END) { + if (!wsi->http2_substream) { + memcpy(buf + LWS_PRE, "0\x0d\x0a\x0d\x0a", 5); + lwsl_debug("writing chunk terminator and exiting\n"); + n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, + 5, LWS_WRITE_HTTP); + } else + n = lws_write(wsi, (unsigned char *)buf + LWS_PRE, + 0, LWS_WRITE_HTTP_FINAL); + + /* always close after sending it */ + return -1; + } +#endif +#if defined(LWS_WITH_HTTP_PROXY) + if (wsi->reason_bf & LWS_CB_REASON_AUX_BF__PROXY) { + char *px = buf + LWS_PRE; + int lenx = sizeof(buf) - LWS_PRE; + + /* + * our sink is writeable and our source has something + * to read. So read a lump of source material of + * suitable size to send or what's available, whichever + * is the smaller. + */ + wsi->reason_bf &= ~LWS_CB_REASON_AUX_BF__PROXY; + if (!lws_get_child(wsi)) + break; + if (lws_http_client_read(lws_get_child(wsi), &px, &lenx) < 0) + return -1; + break; + } +#endif + break; + +#if defined(LWS_WITH_HTTP_PROXY) + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP: + assert(lws_get_parent(wsi)); + if (!lws_get_parent(wsi)) + break; + lws_get_parent(wsi)->reason_bf |= LWS_CB_REASON_AUX_BF__PROXY; + lws_callback_on_writable(lws_get_parent(wsi)); + break; + + case LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ: + assert(lws_get_parent(wsi)); + n = lws_write(lws_get_parent(wsi), (unsigned char *)in, + len, LWS_WRITE_HTTP); + if (n < 0) + return -1; + break; + + case LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP: { + unsigned char *p, *end; + char ctype[64], ctlen = 0; + + p = (unsigned char *)buf + LWS_PRE; + end = p + sizeof(buf) - LWS_PRE; + + if (lws_add_http_header_status(lws_get_parent(wsi), + HTTP_STATUS_OK, &p, end)) + return 1; + if (lws_add_http_header_by_token(lws_get_parent(wsi), + WSI_TOKEN_HTTP_SERVER, + (unsigned char *)"libwebsockets", + 13, &p, end)) + return 1; + + ctlen = lws_hdr_copy(wsi, ctype, sizeof(ctype), + WSI_TOKEN_HTTP_CONTENT_TYPE); + if (ctlen > 0) { + if (lws_add_http_header_by_token(lws_get_parent(wsi), + WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)ctype, ctlen, &p, end)) + return 1; + } + + if (lws_finalize_http_header(lws_get_parent(wsi), &p, end)) + return 1; + + *p = '\0'; + n = lws_write(lws_get_parent(wsi), + (unsigned char *)buf + LWS_PRE, + p - ((unsigned char *)buf + LWS_PRE), + LWS_WRITE_HTTP_HEADERS); + if (n < 0) + return -1; + + break; } + +#endif + +#ifdef LWS_WITH_CGI + /* CGI IO events (POLLIN/OUT) appear here, our default policy is: + * + * - POST data goes on subprocess stdin + * - subprocess stdout goes on http via writeable callback + * - subprocess stderr goes to the logs + */ + case LWS_CALLBACK_CGI: + args = (struct lws_cgi_args *)in; + switch (args->ch) { /* which of stdin/out/err ? */ + case LWS_STDIN: + /* TBD stdin rx flow control */ + break; + case LWS_STDOUT: + /* quench POLLIN on STDOUT until MASTER got writeable */ + lws_rx_flow_control(args->stdwsi[LWS_STDOUT], 0); + wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI; + /* when writing to MASTER would not block */ + lws_callback_on_writable(wsi); + break; + case LWS_STDERR: + n = lws_get_socket_fd(args->stdwsi[LWS_STDERR]); + if (n < 0) + break; + n = read(n, buf, sizeof(buf) - 2); + if (n > 0) { + if (buf[n - 1] != '\n') + buf[n++] = '\n'; + buf[n] = '\0'; + lwsl_notice("CGI-stderr: %s\n", buf); + } + break; + } + break; + + case LWS_CALLBACK_CGI_TERMINATED: + lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: %d %" PRIu64 "\n", + wsi->cgi->explicitly_chunked, + (uint64_t)wsi->cgi->content_length); + if (!wsi->cgi->explicitly_chunked && + !wsi->cgi->content_length) { + /* send terminating chunk */ + lwsl_debug("LWS_CALLBACK_CGI_TERMINATED: ending\n"); + wsi->reason_bf |= LWS_CB_REASON_AUX_BF__CGI_CHUNK_END; + lws_callback_on_writable(wsi); + lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, 3); + break; + } + return -1; + + case LWS_CALLBACK_CGI_STDIN_DATA: /* POST body for stdin */ + args = (struct lws_cgi_args *)in; + args->data[args->len] = '\0'; + n = lws_get_socket_fd(args->stdwsi[LWS_STDIN]); + if (n < 0) + return -1; + n = write(n, args->data, args->len); + if (n < args->len) + lwsl_notice("LWS_CALLBACK_CGI_STDIN_DATA: " + "sent %d only %d went", n, args->len); + return n; +#endif + + case LWS_CALLBACK_SSL_INFO: + si = in; + + (void)si; + lwsl_notice("LWS_CALLBACK_SSL_INFO: where: 0x%x, ret: 0x%x\n", + si->where, si->ret); + break; + + default: + break; + } + + return 0; +} + +/* list of supported protocols and callbacks */ + +static const struct lws_protocols protocols_dummy[] = { + /* first protocol must always be HTTP handler */ + + { + "http-only", /* name */ + lws_callback_http_dummy, /* callback */ + 0, /* per_session_data_size */ + 0, /* max frame size / rx buffer */ + 0, NULL, 0 + }, + /* + * the other protocols are provided by lws plugins + */ + { NULL, NULL, 0, 0, 0, NULL, 0} /* terminator */ +}; + +#ifdef LWS_PLAT_OPTEE +#undef LWS_HAVE_GETENV +#endif + +LWS_VISIBLE struct lws_vhost * +lws_create_vhost(struct lws_context *context, + struct lws_context_creation_info *info) +{ + struct lws_vhost *vh = lws_zalloc(sizeof(*vh), "create vhost"), + **vh1 = &context->vhost_list; + const struct lws_http_mount *mounts; + const struct lws_protocol_vhost_options *pvo; +#ifdef LWS_WITH_PLUGINS + struct lws_plugin *plugin = context->plugin_list; +#endif + struct lws_protocols *lwsp; + int m, f = !info->pvo; +#ifdef LWS_HAVE_GETENV + char *p; +#endif + int n; + + if (!vh) + return NULL; + + if (!info->protocols) + info->protocols = &protocols_dummy[0]; + + vh->context = context; + if (!info->vhost_name) + vh->name = "default"; + else + vh->name = info->vhost_name; + + if (info->options & LWS_SERVER_OPTION_ONLY_RAW) + lwsl_info("%s set to only support RAW\n", vh->name); + +#if defined(LWS_WITH_HTTP2) + vh->set = context->set; + if (info->http2_settings[0]) + for (n = 1; n < LWS_H2_SETTINGS_LEN; n++) + vh->set.s[n] = info->http2_settings[n]; +#endif + + vh->iface = info->iface; +#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32) + vh->bind_iface = info->bind_iface; +#endif + + for (vh->count_protocols = 0; + info->protocols[vh->count_protocols].callback; + vh->count_protocols++) + ; + + vh->options = info->options; + vh->pvo = info->pvo; + vh->headers = info->headers; + vh->user = info->user; + vh->ssl_info_event_mask = info->ssl_info_event_mask; + if (info->keepalive_timeout) + vh->keepalive_timeout = info->keepalive_timeout; + else + vh->keepalive_timeout = 5; + + if (info->timeout_secs_ah_idle) + vh->timeout_secs_ah_idle = info->timeout_secs_ah_idle; + else + vh->timeout_secs_ah_idle = 10; + + /* + * give the vhost a unified list of protocols including the + * ones that came from plugins + */ + lwsp = lws_zalloc(sizeof(struct lws_protocols) * + (vh->count_protocols + + context->plugin_protocol_count + 1), "vhost-specific plugin table"); + if (!lwsp) { + lwsl_err("OOM\n"); + return NULL; + } + + m = vh->count_protocols; + memcpy(lwsp, info->protocols, sizeof(struct lws_protocols) * m); + + /* for compatibility, all protocols enabled on vhost if only + * the default vhost exists. Otherwise only vhosts who ask + * for a protocol get it enabled. + */ + + if (context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS) + f = 0; + (void)f; +#ifdef LWS_WITH_PLUGINS + if (plugin) { + + while (plugin) { + for (n = 0; n < plugin->caps.count_protocols; n++) { + /* + * for compatibility's sake, no pvo implies + * allow all protocols + */ + if (f || lws_vhost_protocol_options(vh, + plugin->caps.protocols[n].name)) { + memcpy(&lwsp[m], + &plugin->caps.protocols[n], + sizeof(struct lws_protocols)); + m++; + vh->count_protocols++; + } + } + plugin = plugin->list; + } + } +#endif + + if ( +#ifdef LWS_WITH_PLUGINS + (context->plugin_list) || +#endif + context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS) + vh->protocols = lwsp; + else { + vh->protocols = info->protocols; + lws_free(lwsp); + } + + vh->same_vh_protocol_list = (struct lws **) + lws_zalloc(sizeof(struct lws *) * vh->count_protocols, "same vh list"); + + vh->mount_list = info->mounts; + +#ifdef LWS_WITH_UNIX_SOCK + if (LWS_UNIX_SOCK_ENABLED(context)) { + lwsl_notice("Creating Vhost '%s' path \"%s\", %d protocols\n", + vh->name, info->iface, vh->count_protocols); + } else +#endif + lwsl_notice("Creating Vhost '%s' port %d, %d protocols, IPv6 %s\n", + vh->name, info->port, vh->count_protocols, + LWS_IPV6_ENABLED(vh) ? "on" : "off"); + + mounts = info->mounts; + while (mounts) { + (void)mount_protocols[0]; + lwsl_notice(" mounting %s%s to %s\n", + mount_protocols[mounts->origin_protocol], + mounts->origin, mounts->mountpoint); + + /* convert interpreter protocol names to pointers */ + pvo = mounts->interpret; + while (pvo) { + for (n = 0; n < vh->count_protocols; n++) + if (!strcmp(pvo->value, vh->protocols[n].name)) { + ((struct lws_protocol_vhost_options *)pvo)->value = + (const char *)(lws_intptr_t)n; + break; + } + if (n == vh->count_protocols) + lwsl_err("ignoring unknown interpret protocol %s\n", + pvo->value); + pvo = pvo->next; + } + + mounts = mounts->mount_next; + } + +#ifndef LWS_NO_EXTENSIONS +#ifdef LWS_WITH_PLUGINS + if (context->plugin_extension_count) { + + m = 0; + while (info->extensions && info->extensions[m].callback) + m++; + + /* + * give the vhost a unified list of extensions including the + * ones that came from plugins + */ + vh->extensions = lws_zalloc(sizeof(struct lws_extension) * + (m + + context->plugin_extension_count + 1), "extensions"); + if (!vh->extensions) + return NULL; + + memcpy((struct lws_extension *)vh->extensions, info->extensions, + sizeof(struct lws_extension) * m); + plugin = context->plugin_list; + while (plugin) { + memcpy((struct lws_extension *)&vh->extensions[m], + plugin->caps.extensions, + sizeof(struct lws_extension) * + plugin->caps.count_extensions); + m += plugin->caps.count_extensions; + plugin = plugin->list; + } + } else +#endif + vh->extensions = info->extensions; +#endif + + vh->listen_port = info->port; +#if !defined(LWS_WITH_ESP8266) + vh->http_proxy_port = 0; + vh->http_proxy_address[0] = '\0'; +#if defined(LWS_WITH_SOCKS5) + vh->socks_proxy_port = 0; + vh->socks_proxy_address[0] = '\0'; +#endif + + /* either use proxy from info, or try get it from env var */ + + /* http proxy */ + if (info->http_proxy_address) { + /* override for backwards compatibility */ + if (info->http_proxy_port) + vh->http_proxy_port = info->http_proxy_port; + lws_set_proxy(vh, info->http_proxy_address); + } else { +#ifdef LWS_HAVE_GETENV + p = getenv("http_proxy"); + if (p) + lws_set_proxy(vh, p); +#endif + } +#if defined(LWS_WITH_SOCKS5) + /* socks proxy */ + if (info->socks_proxy_address) { + /* override for backwards compatibility */ + if (info->socks_proxy_port) + vh->socks_proxy_port = info->socks_proxy_port; + lws_set_socks(vh, info->socks_proxy_address); + } else { +#ifdef LWS_HAVE_GETENV + p = getenv("socks_proxy"); + if (p) + lws_set_socks(vh, p); +#endif + } +#endif +#endif + + vh->ka_time = info->ka_time; + vh->ka_interval = info->ka_interval; + vh->ka_probes = info->ka_probes; + + if (vh->options & LWS_SERVER_OPTION_STS) + lwsl_notice(" STS enabled\n"); + +#ifdef LWS_WITH_ACCESS_LOG + if (info->log_filepath) { + vh->log_fd = open(info->log_filepath, + O_CREAT | O_APPEND | O_RDWR, 0600); + if (vh->log_fd == (int)LWS_INVALID_FILE) { + lwsl_err("unable to open log filepath %s\n", + info->log_filepath); + goto bail; + } +#ifndef WIN32 + if (context->uid != -1) + if (chown(info->log_filepath, context->uid, + context->gid) == -1) + lwsl_err("unable to chown log file %s\n", + info->log_filepath); +#endif + } else + vh->log_fd = (int)LWS_INVALID_FILE; +#endif + if (lws_context_init_server_ssl(info, vh)) + goto bail; + if (lws_context_init_client_ssl(info, vh)) + goto bail; + if (lws_context_init_server(info, vh)) { + lwsl_err("init server failed\n"); + goto bail; + } + + while (1) { + if (!(*vh1)) { + *vh1 = vh; + break; + } + vh1 = &(*vh1)->vhost_next; + }; + /* for the case we are adding a vhost much later, after server init */ + + if (context->protocol_init_done) + lws_protocol_init(context); + + return vh; + +bail: + lws_free(vh); + + return NULL; +} + +LWS_VISIBLE int +lws_init_vhost_client_ssl(const struct lws_context_creation_info *info, + struct lws_vhost *vhost) +{ + struct lws_context_creation_info i; + + memcpy(&i, info, sizeof(i)); + i.port = CONTEXT_PORT_NO_LISTEN; + + return lws_context_init_client_ssl(&i, vhost); +} + +LWS_VISIBLE struct lws_context * +lws_create_context(struct lws_context_creation_info *info) +{ + struct lws_context *context = NULL; + struct lws_plat_file_ops *prev; +#ifndef LWS_NO_DAEMONIZE + int pid_daemon = get_daemonize_pid(); +#endif + int n; +#if defined(__ANDROID__) + struct rlimit rt; +#endif + + lwsl_info("Initial logging level %d\n", log_level); + lwsl_info("Libwebsockets version: %s\n", library_version); +#if defined(GCC_VER) + lwsl_info("Compiled with %s\n", GCC_VER); +#endif +#if LWS_POSIX +#ifdef LWS_WITH_IPV6 + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DISABLE_IPV6)) + lwsl_info("IPV6 compiled in and enabled\n"); + else + lwsl_info("IPV6 compiled in but disabled\n"); +#else + lwsl_info("IPV6 not compiled in\n"); +#endif +#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_PLAT_ESP32) + lws_feature_status_libev(info); + lws_feature_status_libuv(info); +#endif +#endif + lwsl_info(" LWS_DEF_HEADER_LEN : %u\n", LWS_DEF_HEADER_LEN); + lwsl_info(" LWS_MAX_PROTOCOLS : %u\n", LWS_MAX_PROTOCOLS); + lwsl_info(" LWS_MAX_SMP : %u\n", LWS_MAX_SMP); + lwsl_info(" sizeof (*info) : %ld\n", (long)sizeof(*info)); +#if defined(LWS_WITH_STATS) + lwsl_info(" LWS_WITH_STATS : on\n"); +#endif +#if LWS_POSIX + lwsl_info(" SYSTEM_RANDOM_FILEPATH: '%s'\n", SYSTEM_RANDOM_FILEPATH); +#endif +#if defined(LWS_WITH_HTTP2) + lwsl_info(" HTTP2 support : available\n"); +#else + lwsl_info(" HTTP2 support : not configured"); +#endif + if (lws_plat_context_early_init()) + return NULL; + + context = lws_zalloc(sizeof(struct lws_context), "context"); + if (!context) { + lwsl_err("No memory for websocket context\n"); + return NULL; + } + if (info->pt_serv_buf_size) + context->pt_serv_buf_size = info->pt_serv_buf_size; + else + context->pt_serv_buf_size = 4096; + +#if defined(LWS_WITH_HTTP2) + context->set = lws_h2_stock_settings; +#endif + +#if LWS_MAX_SMP > 1 + pthread_mutex_init(&context->lock, NULL); +#endif + +#if defined(LWS_WITH_ESP32) + context->last_free_heap = esp_get_free_heap_size(); +#endif + + /* default to just the platform fops implementation */ + + context->fops_platform.LWS_FOP_OPEN = _lws_plat_file_open; + context->fops_platform.LWS_FOP_CLOSE = _lws_plat_file_close; + context->fops_platform.LWS_FOP_SEEK_CUR = _lws_plat_file_seek_cur; + context->fops_platform.LWS_FOP_READ = _lws_plat_file_read; + context->fops_platform.LWS_FOP_WRITE = _lws_plat_file_write; + context->fops_platform.fi[0].sig = NULL; + + /* + * arrange a linear linked-list of fops starting from context->fops + * + * platform fops + * [ -> fops_zip (copied into context so .next settable) ] + * [ -> info->fops ] + */ + + context->fops = &context->fops_platform; + prev = (struct lws_plat_file_ops *)context->fops; + +#if defined(LWS_WITH_ZIP_FOPS) + /* make a soft copy so we can set .next */ + context->fops_zip = fops_zip; + prev->next = &context->fops_zip; + prev = (struct lws_plat_file_ops *)prev->next; +#endif + + /* if user provided fops, tack them on the end of the list */ + if (info->fops) + prev->next = info->fops; + + context->reject_service_keywords = info->reject_service_keywords; + if (info->external_baggage_free_on_destroy) + context->external_baggage_free_on_destroy = + info->external_baggage_free_on_destroy; + + context->time_up = time(NULL); + + context->simultaneous_ssl_restriction = info->simultaneous_ssl_restriction; + +#ifndef LWS_NO_DAEMONIZE + if (pid_daemon) { + context->started_with_parent = pid_daemon; + lwsl_info(" Started with daemon pid %d\n", pid_daemon); + } +#endif +#if defined(__ANDROID__) + n = getrlimit ( RLIMIT_NOFILE,&rt); + if (-1 == n) { + lwsl_err("Get RLIMIT_NOFILE failed!\n"); + return NULL; + } + context->max_fds = rt.rlim_cur; +#else + context->max_fds = getdtablesize(); +#endif + + if (info->count_threads) + context->count_threads = info->count_threads; + else + context->count_threads = 1; + + if (context->count_threads > LWS_MAX_SMP) + context->count_threads = LWS_MAX_SMP; + + context->token_limits = info->token_limits; + + context->options = info->options; + + if (info->timeout_secs) + context->timeout_secs = info->timeout_secs; + else + context->timeout_secs = AWAITING_TIMEOUT; + + context->ws_ping_pong_interval = info->ws_ping_pong_interval; + + lwsl_info(" default timeout (secs): %u\n", context->timeout_secs); + + if (info->max_http_header_data) + context->max_http_header_data = info->max_http_header_data; + else + if (info->max_http_header_data2) + context->max_http_header_data = + info->max_http_header_data2; + else + context->max_http_header_data = LWS_DEF_HEADER_LEN; + if (info->max_http_header_pool) + context->max_http_header_pool = info->max_http_header_pool; + else + context->max_http_header_pool = LWS_DEF_HEADER_POOL; + + /* + * Allocate the per-thread storage for scratchpad buffers, + * and header data pool + */ + for (n = 0; n < context->count_threads; n++) { + context->pt[n].serv_buf = lws_malloc(context->pt_serv_buf_size, + "pt_serv_buf"); + if (!context->pt[n].serv_buf) { + lwsl_err("OOM\n"); + return NULL; + } + +#ifdef LWS_WITH_LIBUV + context->pt[n].context = context; +#endif + context->pt[n].tid = n; + context->pt[n].ah_list = NULL; + context->pt[n].ah_pool_length = 0; + + lws_pt_mutex_init(&context->pt[n]); + } + + if (info->fd_limit_per_thread) + context->fd_limit_per_thread = info->fd_limit_per_thread; + else + context->fd_limit_per_thread = context->max_fds / + context->count_threads; + + lwsl_info(" Threads: %d each %d fds\n", context->count_threads, + context->fd_limit_per_thread); + + if (!info->ka_interval && info->ka_time > 0) { + lwsl_err("info->ka_interval can't be 0 if ka_time used\n"); + return NULL; + } + +#ifdef LWS_WITH_LIBEV + /* (Issue #264) In order to *avoid breaking backwards compatibility*, we + * enable libev mediated SIGINT handling with a default handler of + * lws_sigint_cb. The handler can be overridden or disabled + * by invoking lws_sigint_cfg after creating the context, but + * before invoking lws_initloop: + */ + context->use_ev_sigint = 1; + context->lws_ev_sigint_cb = &lws_ev_sigint_cb; +#endif /* LWS_WITH_LIBEV */ +#ifdef LWS_WITH_LIBUV + /* (Issue #264) In order to *avoid breaking backwards compatibility*, we + * enable libev mediated SIGINT handling with a default handler of + * lws_sigint_cb. The handler can be overridden or disabled + * by invoking lws_sigint_cfg after creating the context, but + * before invoking lws_initloop: + */ + context->use_ev_sigint = 1; + context->lws_uv_sigint_cb = &lws_uv_sigint_cb; +#endif +#ifdef LWS_WITH_LIBEVENT + /* (Issue #264) In order to *avoid breaking backwards compatibility*, we + * enable libev mediated SIGINT handling with a default handler of + * lws_sigint_cb. The handler can be overridden or disabled + * by invoking lws_sigint_cfg after creating the context, but + * before invoking lws_initloop: + */ + context->use_ev_sigint = 1; + context->lws_event_sigint_cb = &lws_event_sigint_cb; +#endif /* LWS_WITH_LIBEVENT */ + +#if defined(LWS_WITH_PEER_LIMITS) + /* scale the peer hash table according to the max fds for the process, + * so that the max list depth averages 16. Eg, 1024 fd -> 64, + * 102400 fd -> 6400 + */ + context->pl_hash_elements = + (context->count_threads * context->fd_limit_per_thread) / 16; + context->pl_hash_table = lws_zalloc(sizeof(struct lws_peer *) * + context->pl_hash_elements, "peer limits hash table"); + context->ip_limit_ah = info->ip_limit_ah; + context->ip_limit_wsi = info->ip_limit_wsi; +#endif + + lwsl_info(" mem: context: %5lu bytes (%ld ctx + (%ld thr x %d))\n", + (long)sizeof(struct lws_context) + + (context->count_threads * context->pt_serv_buf_size), + (long)sizeof(struct lws_context), + (long)context->count_threads, + context->pt_serv_buf_size); + + lwsl_info(" mem: http hdr rsvd: %5lu bytes (%u thr x (%u + %lu) x %u))\n", + (long)(context->max_http_header_data + + sizeof(struct allocated_headers)) * + context->max_http_header_pool * context->count_threads, + context->count_threads, + context->max_http_header_data, + (long)sizeof(struct allocated_headers), + context->max_http_header_pool); + n = sizeof(struct lws_pollfd) * context->count_threads * + context->fd_limit_per_thread; + context->pt[0].fds = lws_zalloc(n, "fds table"); + if (context->pt[0].fds == NULL) { + lwsl_err("OOM allocating %d fds\n", context->max_fds); + goto bail; + } + lwsl_info(" mem: pollfd map: %5u\n", n); + + if (info->server_string) { + context->server_string = info->server_string; + context->server_string_len = (short) + strlen(context->server_string); + } + +#if LWS_MAX_SMP > 1 + /* each thread serves his own chunk of fds */ + for (n = 1; n < (int)info->count_threads; n++) + context->pt[n].fds = context->pt[n - 1].fds + + context->fd_limit_per_thread; +#endif + + if (lws_plat_init(context, info)) + goto bail; + +#if defined(LWS_WITH_HTTP2) + /* + * let the user code see what the platform default SETTINGS were, he + * can modify them when he creates the vhosts. + */ + for (n = 1; n < LWS_H2_SETTINGS_LEN; n++) + info->http2_settings[n] = context->set.s[n]; +#endif + + lws_context_init_ssl_library(info); + + context->user_space = info->user; + + /* + * if he's not saying he'll make his own vhosts later then act + * compatibly and make a default vhost using the data in the info + */ + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) + if (!lws_create_vhost(context, info)) { + lwsl_err("Failed to create default vhost\n"); + return NULL; + } + + lws_context_init_extensions(info, context); + + lwsl_info(" mem: per-conn: %5lu bytes + protocol rx buf\n", + (unsigned long)sizeof(struct lws)); + + strcpy(context->canonical_hostname, "unknown"); + lws_server_get_canonical_hostname(context, info); + + context->uid = info->uid; + context->gid = info->gid; + +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + memcpy(context->caps, info->caps, sizeof(context->caps)); + context->count_caps = info->count_caps; +#endif + + /* + * drop any root privs for this process + * to listen on port < 1023 we would have needed root, but now we are + * listening, we don't want the power for anything else + */ + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) + lws_plat_drop_app_privileges(info); + + /* + * give all extensions a chance to create any per-context + * allocations they need + */ + if (info->port != CONTEXT_PORT_NO_LISTEN) { + if (lws_ext_cb_all_exts(context, NULL, + LWS_EXT_CB_SERVER_CONTEXT_CONSTRUCT, NULL, 0) < 0) + goto bail; + } else + if (lws_ext_cb_all_exts(context, NULL, + LWS_EXT_CB_CLIENT_CONTEXT_CONSTRUCT, NULL, 0) < 0) + goto bail; + + return context; + +bail: + lws_context_destroy(context); + return NULL; +} + +LWS_VISIBLE LWS_EXTERN void +lws_context_deprecate(struct lws_context *context, lws_reload_func cb) +{ + struct lws_vhost *vh = context->vhost_list, *vh1; + struct lws *wsi; + + /* + * "deprecation" means disable the context from accepting any new + * connections and free up listen sockets to be used by a replacement + * context. + * + * Otherwise the deprecated context remains operational, until its + * number of connected sockets falls to zero, when it is deleted. + */ + + /* for each vhost, close his listen socket */ + + while (vh) { + wsi = vh->lserv_wsi; + if (wsi) { + wsi->socket_is_permanently_unusable = 1; + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + wsi->context->deprecation_pending_listen_close_count++; + /* + * other vhosts can share the listen port, they + * point to the same wsi. So zap those too. + */ + vh1 = context->vhost_list; + while (vh1) { + if (vh1->lserv_wsi == wsi) + vh1->lserv_wsi = NULL; + vh1 = vh1->vhost_next; + } + } + vh = vh->vhost_next; + } + + context->deprecated = 1; + context->deprecation_cb = cb; +} + +LWS_VISIBLE LWS_EXTERN int +lws_context_is_deprecated(struct lws_context *context) +{ + return context->deprecated; +} + +LWS_VISIBLE void +lws_context_destroy2(struct lws_context *context); + + +static void +lws_vhost_destroy1(struct lws_vhost *vh) +{ + const struct lws_protocols *protocol = NULL; + struct lws_context_per_thread *pt; + int n, m = vh->context->count_threads; + struct lws_context *context = vh->context; + struct lws wsi; + + lwsl_info("%s\n", __func__); + + if (vh->being_destroyed) + return; + + vh->being_destroyed = 1; + + /* + * Are there other vhosts that are piggybacking on our listen socket? + * If so we need to hand the listen socket off to one of the others + * so it will remain open. If not, leave it attached to the closing + * vhost and it will get closed. + */ + + if (vh->lserv_wsi) + lws_start_foreach_ll(struct lws_vhost *, v, context->vhost_list) { + if (v != vh && + !v->being_destroyed && + v->listen_port == vh->listen_port && + ((!v->iface && !vh->iface) || + (v->iface && vh->iface && + !strcmp(v->iface, vh->iface)))) { + /* + * this can only be a listen wsi, which is + * restricted... it has no protocol or other + * bindings or states. So we can simply + * swap it to a vhost that has the same + * iface + port, but is not closing. + */ + assert(v->lserv_wsi == NULL); + v->lserv_wsi = vh->lserv_wsi; + vh->lserv_wsi = NULL; + if (v->lserv_wsi) + v->lserv_wsi->vhost = v; + + lwsl_notice("%s: listen skt from %s to %s\n", + __func__, vh->name, v->name); + break; + } + } lws_end_foreach_ll(v, vhost_next); + + /* + * Forcibly close every wsi assoicated with this vhost. That will + * include the listen socket if it is still associated with the closing + * vhost. + */ + + while (m--) { + pt = &context->pt[m]; + + for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) { + struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd); + if (!wsi) + continue; + if (wsi->vhost != vh) + continue; + + lws_close_free_wsi(wsi, + LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY + /* no protocol close */); + n--; + } + } + + /* + * let the protocols destroy the per-vhost protocol objects + */ + + memset(&wsi, 0, sizeof(wsi)); + wsi.context = vh->context; + wsi.vhost = vh; + protocol = vh->protocols; + if (protocol) { + n = 0; + while (n < vh->count_protocols) { + wsi.protocol = protocol; + protocol->callback(&wsi, LWS_CALLBACK_PROTOCOL_DESTROY, + NULL, NULL, 0); + protocol++; + n++; + } + } + + /* + * remove vhost from context list of vhosts + */ + + lws_start_foreach_llp(struct lws_vhost **, pv, context->vhost_list) { + if (*pv == vh) { + *pv = vh->vhost_next; + break; + } + } lws_end_foreach_llp(pv, vhost_next); + + /* add ourselves to the pending destruction list */ + + vh->vhost_next = vh->context->vhost_pending_destruction_list; + vh->context->vhost_pending_destruction_list = vh; +} + +static void +lws_vhost_destroy2(struct lws_vhost *vh) +{ + const struct lws_protocols *protocol = NULL; + struct lws_context *context = vh->context; + struct lws_deferred_free *df; + int n; + + lwsl_info("%s: %p\n", __func__, vh); + + /* if we are still on deferred free list, remove ourselves */ + + lws_start_foreach_llp(struct lws_deferred_free **, pdf, + context->deferred_free_list) { + if ((*pdf)->payload == vh) { + df = *pdf; + *pdf = df->next; + lws_free(df); + break; + } + } lws_end_foreach_llp(pdf, next); + + /* remove ourselves from the pending destruction list */ + + lws_start_foreach_llp(struct lws_vhost **, pv, + context->vhost_pending_destruction_list) { + if ((*pv) == vh) { + *pv = (*pv)->vhost_next; + break; + } + } lws_end_foreach_llp(pv, vhost_next); + + /* + * Free all the allocations associated with the vhost + */ + + protocol = vh->protocols; + if (protocol) { + n = 0; + while (n < vh->count_protocols) { + if (vh->protocol_vh_privs && + vh->protocol_vh_privs[n]) { + lws_free(vh->protocol_vh_privs[n]); + vh->protocol_vh_privs[n] = NULL; + } + protocol++; + n++; + } + } + if (vh->protocol_vh_privs) + lws_free(vh->protocol_vh_privs); + lws_ssl_SSL_CTX_destroy(vh); + lws_free(vh->same_vh_protocol_list); +#ifdef LWS_WITH_PLUGINS + if (LWS_LIBUV_ENABLED(context)) { + if (context->plugin_list) + lws_free((void *)vh->protocols); + } else +#endif + { + if (context->options & LWS_SERVER_OPTION_EXPLICIT_VHOSTS) + lws_free((void *)vh->protocols); + } + +#ifdef LWS_WITH_PLUGINS +#ifndef LWS_NO_EXTENSIONS + if (context->plugin_extension_count) + lws_free((void *)vh->extensions); +#endif +#endif +#ifdef LWS_WITH_ACCESS_LOG + if (vh->log_fd != (int)LWS_INVALID_FILE) + close(vh->log_fd); +#endif + + /* + * although async event callbacks may still come for wsi handles with + * pending close in the case of asycn event library like libuv, + * they do not refer to the vhost. So it's safe to free. + */ + + lwsl_info(" %s: Freeing vhost %p\n", __func__, vh); + + memset(vh, 0, sizeof(*vh)); + lws_free(vh); +} + +int +lws_check_deferred_free(struct lws_context *context, int force) +{ + struct lws_deferred_free *df; + time_t now = lws_now_secs(); + + lws_start_foreach_llp(struct lws_deferred_free **, pdf, + context->deferred_free_list) { + if (now > (*pdf)->deadline || force) { + df = *pdf; + *pdf = df->next; + /* finalize vh destruction */ + lwsl_notice("deferred vh %p destroy\n", df->payload); + lws_vhost_destroy2(df->payload); + lws_free(df); + continue; /* after deletion we already point to next */ + } + } lws_end_foreach_llp(pdf, next); + + return 0; +} + +LWS_VISIBLE void +lws_vhost_destroy(struct lws_vhost *vh) +{ + struct lws_deferred_free *df = lws_malloc(sizeof(*df), "deferred free"); + + if (!df) + return; + + lws_vhost_destroy1(vh); + + /* part 2 is deferred to allow all the handle closes to complete */ + + df->next = vh->context->deferred_free_list; + df->deadline = lws_now_secs() + 5; + df->payload = vh; + vh->context->deferred_free_list = df; +} + +LWS_VISIBLE void +lws_context_destroy(struct lws_context *context) +{ + struct lws_context_per_thread *pt; + struct lws_vhost *vh = NULL; + struct lws wsi; + int n, m; + + if (!context) { + lwsl_notice("%s: ctx %p\n", __func__, context); + return; + } + if (context->being_destroyed1) { + lwsl_notice("%s: ctx %p: already being destroyed\n", + __func__, context); + return; + } + + lwsl_info("%s: ctx %p\n", __func__, context); + + m = context->count_threads; + context->being_destroyed = 1; + context->being_destroyed1 = 1; + + memset(&wsi, 0, sizeof(wsi)); + wsi.context = context; + +#ifdef LWS_LATENCY + if (context->worst_latency_info[0]) + lwsl_notice("Worst latency: %s\n", context->worst_latency_info); +#endif + + while (m--) { + pt = &context->pt[m]; + + for (n = 0; (unsigned int)n < context->pt[m].fds_count; n++) { + struct lws *wsi = wsi_from_fd(context, pt->fds[n].fd); + if (!wsi) + continue; + + lws_close_free_wsi(wsi, + LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY + /* no protocol close */); + n--; + } + lws_pt_mutex_destroy(pt); + } + + /* + * give all extensions a chance to clean up any per-context + * allocations they might have made + */ + + n = lws_ext_cb_all_exts(context, NULL, + LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT, NULL, 0); + + n = lws_ext_cb_all_exts(context, NULL, + LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT, NULL, 0); + + /* + * inform all the protocols that they are done and will have no more + * callbacks. + * + * We can't free things until after the event loop shuts down. + */ + if (context->protocol_init_done) + vh = context->vhost_list; + while (vh) { + struct lws_vhost *vhn = vh->vhost_next; + lws_vhost_destroy1(vh); + vh = vhn; + } + + for (n = 0; n < context->count_threads; n++) { + pt = &context->pt[n]; + + lws_libev_destroyloop(context, n); + lws_libuv_destroyloop(context, n); + lws_libevent_destroyloop(context, n); + + lws_free_set_NULL(context->pt[n].serv_buf); + + while (pt->ah_list) + _lws_destroy_ah(pt, pt->ah_list); + } + lws_plat_context_early_destroy(context); + + if (context->pt[0].fds) + lws_free_set_NULL(context->pt[0].fds); + + if (!LWS_LIBUV_ENABLED(context)) + lws_context_destroy2(context); +} + +/* + * call the second one after the event loop has been shut down cleanly + */ + +LWS_VISIBLE void +lws_context_destroy2(struct lws_context *context) +{ + struct lws_vhost *vh = NULL, *vh1; +#if defined(LWS_WITH_PEER_LIMITS) + uint32_t n; +#endif + + lwsl_info("%s: ctx %p\n", __func__, context); + + /* + * free all the per-vhost allocations + */ + + vh = context->vhost_list; + while (vh) { + vh1 = vh->vhost_next; + lws_vhost_destroy2(vh); + vh = vh1; + } + + /* remove ourselves from the pending destruction list */ + + while (context->vhost_pending_destruction_list) + /* removes itself from list */ + lws_vhost_destroy2(context->vhost_pending_destruction_list); + + + lws_stats_log_dump(context); + + lws_ssl_context_destroy(context); + lws_plat_context_late_destroy(context); + +#if defined(LWS_WITH_PEER_LIMITS) + for (n = 0; n < context->pl_hash_elements; n++) { + lws_start_foreach_llp(struct lws_peer **, peer, + context->pl_hash_table[n]) { + struct lws_peer *df = *peer; + *peer = df->next; + lws_free(df); + continue; + } lws_end_foreach_llp(peer, next); + } + lws_free(context->pl_hash_table); +#endif + + if (context->external_baggage_free_on_destroy) + free(context->external_baggage_free_on_destroy); + + lws_check_deferred_free(context, 1); + +#if LWS_MAX_SMP > 1 + pthread_mutex_destroy(&context->lock, NULL); +#endif + + lws_free(context); +} diff --git a/thirdparty/lws/ext/extension-permessage-deflate.c b/thirdparty/lws/ext/extension-permessage-deflate.c new file mode 100644 index 0000000000..e2be2ae615 --- /dev/null +++ b/thirdparty/lws/ext/extension-permessage-deflate.c @@ -0,0 +1,473 @@ +/* + * ./lib/extension-permessage-deflate.c + * + * Copyright (C) 2016 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" +#include "extension-permessage-deflate.h" +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#define LWS_ZLIB_MEMLEVEL 8 + +const struct lws_ext_options lws_ext_pm_deflate_options[] = { + /* public RFC7692 settings */ + { "server_no_context_takeover", EXTARG_NONE }, + { "client_no_context_takeover", EXTARG_NONE }, + { "server_max_window_bits", EXTARG_OPT_DEC }, + { "client_max_window_bits", EXTARG_OPT_DEC }, + /* ones only user code can set */ + { "rx_buf_size", EXTARG_DEC }, + { "tx_buf_size", EXTARG_DEC }, + { "compression_level", EXTARG_DEC }, + { "mem_level", EXTARG_DEC }, + { NULL, 0 }, /* sentinel */ +}; + +static void +lws_extension_pmdeflate_restrict_args(struct lws *wsi, + struct lws_ext_pm_deflate_priv *priv) +{ + int n, extra; + + /* cap the RX buf at the nearest power of 2 to protocol rx buf */ + + n = wsi->context->pt_serv_buf_size; + if (wsi->protocol->rx_buffer_size) + n = wsi->protocol->rx_buffer_size; + + extra = 7; + while (n >= 1 << (extra + 1)) + extra++; + + if (extra < priv->args[PMD_RX_BUF_PWR2]) { + priv->args[PMD_RX_BUF_PWR2] = extra; + lwsl_info(" Capping pmd rx to %d\n", 1 << extra); + } +} + +LWS_VISIBLE int +lws_extension_callback_pm_deflate(struct lws_context *context, + const struct lws_extension *ext, + struct lws *wsi, + enum lws_extension_callback_reasons reason, + void *user, void *in, size_t len) +{ + struct lws_ext_pm_deflate_priv *priv = + (struct lws_ext_pm_deflate_priv *)user; + struct lws_tokens *eff_buf = (struct lws_tokens *)in; + static unsigned char trail[] = { 0, 0, 0xff, 0xff }; + int n, ret = 0, was_fin = 0, extra; + struct lws_ext_option_arg *oa; + + switch (reason) { + case LWS_EXT_CB_NAMED_OPTION_SET: + oa = in; + if (!oa->option_name) + break; + for (n = 0; n < ARRAY_SIZE(lws_ext_pm_deflate_options); n++) + if (!strcmp(lws_ext_pm_deflate_options[n].name, oa->option_name)) + break; + + if (n == ARRAY_SIZE(lws_ext_pm_deflate_options)) + break; + oa->option_index = n; + + /* fallthru */ + + case LWS_EXT_CB_OPTION_SET: + oa = in; + lwsl_notice("%s: option set: idx %d, %s, len %d\n", __func__, + oa->option_index, oa->start, oa->len); + if (oa->start) + priv->args[oa->option_index] = atoi(oa->start); + else + priv->args[oa->option_index] = 1; + + if (priv->args[PMD_CLIENT_MAX_WINDOW_BITS] == 8) + priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 9; + + lws_extension_pmdeflate_restrict_args(wsi, priv); + break; + + case LWS_EXT_CB_OPTION_CONFIRM: + if (priv->args[PMD_SERVER_MAX_WINDOW_BITS] < 8 || + priv->args[PMD_SERVER_MAX_WINDOW_BITS] > 15 || + priv->args[PMD_CLIENT_MAX_WINDOW_BITS] < 8 || + priv->args[PMD_CLIENT_MAX_WINDOW_BITS] > 15) + return -1; + break; + + case LWS_EXT_CB_CLIENT_CONSTRUCT: + case LWS_EXT_CB_CONSTRUCT: + + n = context->pt_serv_buf_size; + if (wsi->protocol->rx_buffer_size) + n = wsi->protocol->rx_buffer_size; + + if (n < 128) { + lwsl_info(" permessage-deflate requires the protocol (%s) to have an RX buffer >= 128\n", + wsi->protocol->name); + return -1; + } + + /* fill in **user */ + priv = lws_zalloc(sizeof(*priv), "pmd priv"); + *((void **)user) = priv; + lwsl_ext("%s: LWS_EXT_CB_*CONSTRUCT\n", __func__); + memset(priv, 0, sizeof(*priv)); + + /* fill in pointer to options list */ + if (in) + *((const struct lws_ext_options **)in) = + lws_ext_pm_deflate_options; + + /* fallthru */ + + case LWS_EXT_CB_OPTION_DEFAULT: + + /* set the public, RFC7692 defaults... */ + + priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER] = 0, + priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER] = 0; + priv->args[PMD_SERVER_MAX_WINDOW_BITS] = 15; + priv->args[PMD_CLIENT_MAX_WINDOW_BITS] = 15; + + /* ...and the ones the user code can override */ + + priv->args[PMD_RX_BUF_PWR2] = 10; /* ie, 1024 */ + priv->args[PMD_TX_BUF_PWR2] = 10; /* ie, 1024 */ + priv->args[PMD_COMP_LEVEL] = 1; + priv->args[PMD_MEM_LEVEL] = 8; + + lws_extension_pmdeflate_restrict_args(wsi, priv); + break; + + case LWS_EXT_CB_DESTROY: + lwsl_ext("%s: LWS_EXT_CB_DESTROY\n", __func__); + lws_free(priv->buf_rx_inflated); + lws_free(priv->buf_tx_deflated); + if (priv->rx_init) + (void)inflateEnd(&priv->rx); + if (priv->tx_init) + (void)deflateEnd(&priv->tx); + lws_free(priv); + return ret; + + case LWS_EXT_CB_PAYLOAD_RX: + lwsl_ext(" %s: LWS_EXT_CB_PAYLOAD_RX: in %d, existing in %d\n", + __func__, eff_buf->token_len, priv->rx.avail_in); + if (!(wsi->u.ws.rsv_first_msg & 0x40)) + return 0; + +#if 0 + for (n = 0; n < eff_buf->token_len; n++) { + printf("%02X ", (unsigned char)eff_buf->token[n]); + if ((n & 15) == 15) + printf("\n"); + } + printf("\n"); +#endif + if (!priv->rx_init) + if (inflateInit2(&priv->rx, -priv->args[PMD_SERVER_MAX_WINDOW_BITS]) != Z_OK) { + lwsl_err("%s: iniflateInit failed\n", __func__); + return -1; + } + priv->rx_init = 1; + if (!priv->buf_rx_inflated) + priv->buf_rx_inflated = lws_malloc(LWS_PRE + 7 + 5 + + (1 << priv->args[PMD_RX_BUF_PWR2]), "pmd rx inflate buf"); + if (!priv->buf_rx_inflated) { + lwsl_err("%s: OOM\n", __func__); + return -1; + } + + /* + * We have to leave the input stream alone if we didn't + * finish with it yet. The input stream is held in the wsi + * rx buffer by the caller, so this assumption is safe while + * we block new rx while draining the existing rx + */ + if (!priv->rx.avail_in && eff_buf->token && eff_buf->token_len) { + priv->rx.next_in = (unsigned char *)eff_buf->token; + priv->rx.avail_in = eff_buf->token_len; + } + priv->rx.next_out = priv->buf_rx_inflated + LWS_PRE; + eff_buf->token = (char *)priv->rx.next_out; + priv->rx.avail_out = 1 << priv->args[PMD_RX_BUF_PWR2]; + + if (priv->rx_held_valid) { + lwsl_ext("-- RX piling on held byte --\n"); + *(priv->rx.next_out++) = priv->rx_held; + priv->rx.avail_out--; + priv->rx_held_valid = 0; + } + + /* if... + * + * - he has no remaining input content for this message, and + * - and this is the final fragment, and + * - we used everything that could be drained on the input side + * + * ...then put back the 00 00 FF FF the sender stripped as our + * input to zlib + */ + if (!priv->rx.avail_in && wsi->u.ws.final && + !wsi->u.ws.rx_packet_length) { + lwsl_ext("RX APPEND_TRAILER-DO\n"); + was_fin = 1; + priv->rx.next_in = trail; + priv->rx.avail_in = sizeof(trail); + } + + n = inflate(&priv->rx, Z_NO_FLUSH); + lwsl_ext("inflate ret %d, avi %d, avo %d, wsifinal %d\n", n, + priv->rx.avail_in, priv->rx.avail_out, wsi->u.ws.final); + switch (n) { + case Z_NEED_DICT: + case Z_STREAM_ERROR: + case Z_DATA_ERROR: + case Z_MEM_ERROR: + lwsl_info("zlib error inflate %d: %s\n", + n, priv->rx.msg); + return -1; + } + /* + * If we did not already send in the 00 00 FF FF, and he's + * out of input, he did not EXACTLY fill the output buffer + * (which is ambiguous and we will force it to go around + * again by withholding a byte), and he's otherwise working on + * being a FIN fragment, then do the FIN message processing + * of faking up the 00 00 FF FF that the sender stripped. + */ + if (!priv->rx.avail_in && wsi->u.ws.final && + !wsi->u.ws.rx_packet_length && !was_fin && + priv->rx.avail_out /* ambiguous as to if it is the end */ + ) { + lwsl_ext("RX APPEND_TRAILER-DO\n"); + was_fin = 1; + priv->rx.next_in = trail; + priv->rx.avail_in = sizeof(trail); + n = inflate(&priv->rx, Z_SYNC_FLUSH); + lwsl_ext("RX trailer inf returned %d, avi %d, avo %d\n", n, + priv->rx.avail_in, priv->rx.avail_out); + switch (n) { + case Z_NEED_DICT: + case Z_STREAM_ERROR: + case Z_DATA_ERROR: + case Z_MEM_ERROR: + lwsl_info("zlib error inflate %d: %s\n", + n, priv->rx.msg); + return -1; + } + } + /* + * we must announce in our returncode now if there is more + * output to be expected from inflate, so we can decide to + * set the FIN bit on this bufferload or not. However zlib + * is ambiguous when we exactly filled the inflate buffer. It + * does not give us a clue as to whether we should understand + * that to mean he ended on a buffer boundary, or if there is + * more in the pipeline. + * + * So to work around that safely, if it used all output space + * exactly, we ALWAYS say there is more coming and we withhold + * the last byte of the buffer to guarantee that is true. + * + * That still leaves us at least one byte to finish with a FIN + * on, even if actually nothing more is coming from the next + * inflate action itself. + */ + if (!priv->rx.avail_out) { /* he used all available out buf */ + lwsl_ext("-- rx grabbing held --\n"); + /* snip the last byte and hold it for next time */ + priv->rx_held = *(--priv->rx.next_out); + priv->rx_held_valid = 1; + } + + eff_buf->token_len = (char *)priv->rx.next_out - eff_buf->token; + priv->count_rx_between_fin += eff_buf->token_len; + + lwsl_ext(" %s: RX leaving with new effbuff len %d, " + "ret %d, rx.avail_in=%d, TOTAL RX since FIN %lu\n", + __func__, eff_buf->token_len, priv->rx_held_valid, + priv->rx.avail_in, + (unsigned long)priv->count_rx_between_fin); + + if (was_fin) { + priv->count_rx_between_fin = 0; + if (priv->args[PMD_SERVER_NO_CONTEXT_TAKEOVER]) { + (void)inflateEnd(&priv->rx); + priv->rx_init = 0; + } + } +#if 0 + for (n = 0; n < eff_buf->token_len; n++) + putchar(eff_buf->token[n]); + puts("\n"); +#endif + + return priv->rx_held_valid; + + case LWS_EXT_CB_PAYLOAD_TX: + + if (!priv->tx_init) { + n = deflateInit2(&priv->tx, priv->args[PMD_COMP_LEVEL], + Z_DEFLATED, + -priv->args[PMD_SERVER_MAX_WINDOW_BITS + + (wsi->vhost->listen_port <= 0)], + priv->args[PMD_MEM_LEVEL], + Z_DEFAULT_STRATEGY); + if (n != Z_OK) { + lwsl_ext("inflateInit2 failed %d\n", n); + return 1; + } + } + priv->tx_init = 1; + if (!priv->buf_tx_deflated) + priv->buf_tx_deflated = lws_malloc(LWS_PRE + 7 + 5 + + (1 << priv->args[PMD_TX_BUF_PWR2]), "pmd tx deflate buf"); + if (!priv->buf_tx_deflated) { + lwsl_err("%s: OOM\n", __func__); + return -1; + } + + if (eff_buf->token) { + lwsl_ext("%s: TX: eff_buf length %d\n", __func__, + eff_buf->token_len); + priv->tx.next_in = (unsigned char *)eff_buf->token; + priv->tx.avail_in = eff_buf->token_len; + } + +#if 0 + for (n = 0; n < eff_buf->token_len; n++) { + printf("%02X ", (unsigned char)eff_buf->token[n]); + if ((n & 15) == 15) + printf("\n"); + } + printf("\n"); +#endif + + priv->tx.next_out = priv->buf_tx_deflated + LWS_PRE + 5; + eff_buf->token = (char *)priv->tx.next_out; + priv->tx.avail_out = 1 << priv->args[PMD_TX_BUF_PWR2]; + + n = deflate(&priv->tx, Z_SYNC_FLUSH); + if (n == Z_STREAM_ERROR) { + lwsl_ext("%s: Z_STREAM_ERROR\n", __func__); + return -1; + } + + if (priv->tx_held_valid) { + priv->tx_held_valid = 0; + if (priv->tx.avail_out == 1 << priv->args[PMD_TX_BUF_PWR2]) + /* + * we can get a situation he took something in + * but did not generate anything out, at the end + * of a message (eg, next thing he sends is 80 + * 00, a zero length FIN, like Authobahn can + * send). + * If we have come back as a FIN, we must not + * place the pending trailer 00 00 FF FF, just + * the 1 byte of live data + */ + *(--eff_buf->token) = priv->tx_held[0]; + else { + /* he generated data, prepend whole pending */ + eff_buf->token -= 5; + for (n = 0; n < 5; n++) + eff_buf->token[n] = priv->tx_held[n]; + + } + } + priv->compressed_out = 1; + eff_buf->token_len = (int)(priv->tx.next_out - + (unsigned char *)eff_buf->token); + + /* + * we must announce in our returncode now if there is more + * output to be expected from inflate, so we can decide to + * set the FIN bit on this bufferload or not. However zlib + * is ambiguous when we exactly filled the inflate buffer. It + * does not give us a clue as to whether we should understand + * that to mean he ended on a buffer boundary, or if there is + * more in the pipeline. + * + * Worse, the guy providing the stuff we are sending may not + * know until after that this was, actually, the last chunk, + * that can happen even if we did not fill the output buf, ie + * he may send after this a zero-length FIN fragment. + * + * This is super difficult because we must snip the last 4 + * bytes in the case this is the last compressed output of the + * message. The only way to deal with it is defer sending the + * last 5 bytes of each frame until the next one, when we will + * be in a position to understand if that has a FIN or not. + */ + + extra = !!(len & LWS_WRITE_NO_FIN) || !priv->tx.avail_out; + + if (eff_buf->token_len >= 4 + extra) { + lwsl_ext("tx held %d\n", 4 + extra); + priv->tx_held_valid = extra; + for (n = 3 + extra; n >= 0; n--) + priv->tx_held[n] = *(--priv->tx.next_out); + eff_buf->token_len -= 4 + extra; + } + lwsl_ext(" TX rewritten with new effbuff len %d, ret %d\n", + eff_buf->token_len, !priv->tx.avail_out); + + return !priv->tx.avail_out; /* 1 == have more tx pending */ + + case LWS_EXT_CB_PACKET_TX_PRESEND: + if (!priv->compressed_out) + break; + priv->compressed_out = 0; + + if ((*(eff_buf->token) & 0x80) && + priv->args[PMD_CLIENT_NO_CONTEXT_TAKEOVER]) { + lwsl_debug("PMD_CLIENT_NO_CONTEXT_TAKEOVER\n"); + (void)deflateEnd(&priv->tx); + priv->tx_init = 0; + } + + n = *(eff_buf->token) & 15; + /* set RSV1, but not on CONTINUATION */ + if (n == LWSWSOPC_TEXT_FRAME || n == LWSWSOPC_BINARY_FRAME) + *eff_buf->token |= 0x40; +#if 0 + for (n = 0; n < eff_buf->token_len; n++) { + printf("%02X ", (unsigned char)eff_buf->token[n]); + if ((n & 15) == 15) + puts("\n"); + } + puts("\n"); +#endif + lwsl_ext("%s: tx opcode 0x%02X\n", __func__, + (unsigned char)*eff_buf->token); + break; + + default: + break; + } + + return 0; +} + diff --git a/thirdparty/lws/ext/extension-permessage-deflate.h b/thirdparty/lws/ext/extension-permessage-deflate.h new file mode 100644 index 0000000000..8737736897 --- /dev/null +++ b/thirdparty/lws/ext/extension-permessage-deflate.h @@ -0,0 +1,41 @@ + +#include <zlib.h> + +#define DEFLATE_FRAME_COMPRESSION_LEVEL_SERVER 1 +#define DEFLATE_FRAME_COMPRESSION_LEVEL_CLIENT Z_DEFAULT_COMPRESSION + +enum arg_indexes { + PMD_SERVER_NO_CONTEXT_TAKEOVER, + PMD_CLIENT_NO_CONTEXT_TAKEOVER, + PMD_SERVER_MAX_WINDOW_BITS, + PMD_CLIENT_MAX_WINDOW_BITS, + PMD_RX_BUF_PWR2, + PMD_TX_BUF_PWR2, + PMD_COMP_LEVEL, + PMD_MEM_LEVEL, + + PMD_ARG_COUNT +}; + +struct lws_ext_pm_deflate_priv { + z_stream rx; + z_stream tx; + + unsigned char *buf_rx_inflated; /* RX inflated output buffer */ + unsigned char *buf_tx_deflated; /* TX deflated output buffer */ + + size_t count_rx_between_fin; + + unsigned char args[PMD_ARG_COUNT]; + unsigned char tx_held[5]; + unsigned char rx_held; + + unsigned char tx_init:1; + unsigned char rx_init:1; + unsigned char compressed_out:1; + unsigned char rx_held_valid:1; + unsigned char tx_held_valid:1; + unsigned char rx_append_trailer:1; + unsigned char pending_tx_trailer:1; +}; + diff --git a/thirdparty/lws/ext/extension.c b/thirdparty/lws/ext/extension.c new file mode 100644 index 0000000000..ac28204034 --- /dev/null +++ b/thirdparty/lws/ext/extension.c @@ -0,0 +1,344 @@ +#include "private-libwebsockets.h" + +#include "extension-permessage-deflate.h" + +LWS_VISIBLE void +lws_context_init_extensions(struct lws_context_creation_info *info, + struct lws_context *context) +{ + lwsl_info(" LWS_MAX_EXTENSIONS_ACTIVE: %u\n", LWS_MAX_EXTENSIONS_ACTIVE); +} + +enum lws_ext_option_parser_states { + LEAPS_SEEK_NAME, + LEAPS_EAT_NAME, + LEAPS_SEEK_VAL, + LEAPS_EAT_DEC, + LEAPS_SEEK_ARG_TERM +}; + +LWS_VISIBLE int +lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi, + void *ext_user, const struct lws_ext_options *opts, + const char *in, int len) +{ + enum lws_ext_option_parser_states leap = LEAPS_SEEK_NAME; + unsigned int match_map = 0, n, m, w = 0, count_options = 0, + pending_close_quote = 0; + struct lws_ext_option_arg oa; + + oa.option_name = NULL; + + while (opts[count_options].name) + count_options++; + while (len) { + lwsl_ext("'%c' %d", *in, leap); + switch (leap) { + case LEAPS_SEEK_NAME: + if (*in == ' ') + break; + if (*in == ',') { + len = 1; + break; + } + match_map = (1 << count_options) - 1; + leap = LEAPS_EAT_NAME; + w = 0; + + /* fallthru */ + + case LEAPS_EAT_NAME: + oa.start = NULL; + oa.len = 0; + m = match_map; + n = 0; + pending_close_quote = 0; + while (m) { + if (m & 1) { + lwsl_ext(" m=%d, n=%d, w=%d\n", m, n, w); + + if (*in == opts[n].name[w]) { + if (!opts[n].name[w + 1]) { + oa.option_index = n; + lwsl_ext("hit %d\n", oa.option_index); + leap = LEAPS_SEEK_VAL; + if (len == 1) + goto set_arg; + break; + } + } else { + match_map &= ~(1 << n); + if (!match_map) { + lwsl_ext("empty match map\n"); + return -1; + } + } + } + m >>= 1; + n++; + } + w++; + break; + case LEAPS_SEEK_VAL: + if (*in == ' ') + break; + if (*in == ',') { + len = 1; + break; + } + if (*in == ';' || len == 1) { /* ie,nonoptional */ + if (opts[oa.option_index].type == EXTARG_DEC) + return -1; + leap = LEAPS_SEEK_NAME; + goto set_arg; + } + if (*in == '=') { + w = 0; + pending_close_quote = 0; + if (opts[oa.option_index].type == EXTARG_NONE) + return -1; + + leap = LEAPS_EAT_DEC; + break; + } + return -1; + + case LEAPS_EAT_DEC: + if (*in >= '0' && *in <= '9') { + if (!w) + oa.start = in; + w++; + if (len != 1) + break; + } + if (!w && *in =='"') { + pending_close_quote = 1; + break; + } + if (!w) + return -1; + if (pending_close_quote && *in != '"' && len != 1) + return -1; + leap = LEAPS_SEEK_ARG_TERM; + if (oa.start) + oa.len = in - oa.start; + if (len == 1) + oa.len++; + +set_arg: + ext->callback(lws_get_context(wsi), + ext, wsi, LWS_EXT_CB_OPTION_SET, + ext_user, (char *)&oa, 0); + if (len == 1) + break; + if (pending_close_quote && *in == '"') + break; + + /* fallthru */ + + case LEAPS_SEEK_ARG_TERM: + if (*in == ' ') + break; + if (*in == ';') { + leap = LEAPS_SEEK_NAME; + break; + } + if (*in == ',') { + len = 1; + break; + } + return -1; + } + len--; + in++; + } + + return 0; +} + + +/* 0 = nobody had nonzero return, 1 = somebody had positive return, -1 = fail */ + +int lws_ext_cb_active(struct lws *wsi, int reason, void *arg, int len) +{ + int n, m, handled = 0; + + for (n = 0; n < wsi->count_act_ext; n++) { + m = wsi->active_extensions[n]->callback(lws_get_context(wsi), + wsi->active_extensions[n], wsi, reason, + wsi->act_ext_user[n], arg, len); + if (m < 0) { + lwsl_ext("Ext '%s' failed to handle callback %d!\n", + wsi->active_extensions[n]->name, reason); + return -1; + } + /* valgrind... */ + if (reason == LWS_EXT_CB_DESTROY) + wsi->act_ext_user[n] = NULL; + if (m > handled) + handled = m; + } + + return handled; +} + +int lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi, + int reason, void *arg, int len) +{ + int n = 0, m, handled = 0; + const struct lws_extension *ext; + + if (!wsi || !wsi->vhost) + return 0; + + ext = wsi->vhost->extensions; + + while (ext && ext->callback && !handled) { + m = ext->callback(context, ext, wsi, reason, + (void *)(lws_intptr_t)n, arg, len); + if (m < 0) { + lwsl_ext("Ext '%s' failed to handle callback %d!\n", + wsi->active_extensions[n]->name, reason); + return -1; + } + if (m) + handled = 1; + + ext++; + n++; + } + + return 0; +} + +int +lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len) +{ + struct lws_tokens eff_buf; + int ret, m, n = 0; + + eff_buf.token = (char *)buf; + eff_buf.token_len = len; + + /* + * while we have original buf to spill ourselves, or extensions report + * more in their pipeline + */ + + ret = 1; + while (ret == 1) { + + /* default to nobody has more to spill */ + + ret = 0; + + /* show every extension the new incoming data */ + m = lws_ext_cb_active(wsi, + LWS_EXT_CB_PACKET_TX_PRESEND, &eff_buf, 0); + if (m < 0) + return -1; + if (m) /* handled */ + ret = 1; + + if ((char *)buf != eff_buf.token) + /* + * extension recreated it: + * need to buffer this if not all sent + */ + wsi->u.ws.clean_buffer = 0; + + /* assuming they left us something to send, send it */ + + if (eff_buf.token_len) { + n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token, + eff_buf.token_len); + if (n < 0) { + lwsl_info("closing from ext access\n"); + return -1; + } + + /* always either sent it all or privately buffered */ + if (wsi->u.ws.clean_buffer) + len = n; + } + + lwsl_parser("written %d bytes to client\n", n); + + /* no extension has more to spill? Then we can go */ + + if (!ret) + break; + + /* we used up what we had */ + + eff_buf.token = NULL; + eff_buf.token_len = 0; + + /* + * Did that leave the pipe choked? + * Or we had to hold on to some of it? + */ + + if (!lws_send_pipe_choked(wsi) && !wsi->trunc_len) + /* no we could add more, lets's do that */ + continue; + + lwsl_debug("choked\n"); + + /* + * Yes, he's choked. Don't spill the rest now get a callback + * when he is ready to send and take care of it there + */ + lws_callback_on_writable(wsi); + wsi->extension_data_pending = 1; + ret = 0; + } + + return len; +} + +int +lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r, + void *v, size_t len) +{ + struct lws_context *context = wsi->context; + int n, handled = 0; + + /* maybe an extension will take care of it for us */ + + for (n = 0; n < wsi->count_act_ext && !handled; n++) { + if (!wsi->active_extensions[n]->callback) + continue; + + handled |= wsi->active_extensions[n]->callback(context, + wsi->active_extensions[n], wsi, + r, wsi->act_ext_user[n], v, len); + } + + return handled; +} + +int +lws_set_extension_option(struct lws *wsi, const char *ext_name, + const char *opt_name, const char *opt_val) +{ + struct lws_ext_option_arg oa; + int idx = 0; + + /* first identify if the ext is active on this wsi */ + while (idx < wsi->count_act_ext && + strcmp(wsi->active_extensions[idx]->name, ext_name)) + idx++; + + if (idx == wsi->count_act_ext) + return -1; /* request ext not active on this wsi */ + + oa.option_name = opt_name; + oa.option_index = 0; + oa.start = opt_val; + oa.len = 0; + + return wsi->active_extensions[idx]->callback( + wsi->context, wsi->active_extensions[idx], wsi, + LWS_EXT_CB_NAMED_OPTION_SET, wsi->act_ext_user[idx], &oa, 0); +} diff --git a/thirdparty/lws/handshake.c b/thirdparty/lws/handshake.c new file mode 100644 index 0000000000..bc7609d920 --- /dev/null +++ b/thirdparty/lws/handshake.c @@ -0,0 +1,280 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +/* + * -04 of the protocol (actually the 80th version) has a radically different + * handshake. The 04 spec gives the following idea + * + * The handshake from the client looks as follows: + * + * GET /chat HTTP/1.1 + * Host: server.example.com + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== + * Sec-WebSocket-Origin: http://example.com + * Sec-WebSocket-Protocol: chat, superchat + * Sec-WebSocket-Version: 4 + * + * The handshake from the server looks as follows: + * + * HTTP/1.1 101 Switching Protocols + * Upgrade: websocket + * Connection: Upgrade + * Sec-WebSocket-Accept: me89jWimTRKTWwrS3aRrL53YZSo= + * Sec-WebSocket-Nonce: AQIDBAUGBwgJCgsMDQ4PEC== + * Sec-WebSocket-Protocol: chat + */ + +#ifndef min +#define min(a, b) ((a) < (b) ? (a) : (b)) +#endif + +/* + * We have to take care about parsing because the headers may be split + * into multiple fragments. They may contain unknown headers with arbitrary + * argument lengths. So, we parse using a single-character at a time state + * machine that is completely independent of packet size. + * + * Returns <0 for error or length of chars consumed from buf (up to len) + */ + +LWS_VISIBLE int +lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len) +{ + unsigned char *last_char, *oldbuf = buf; + lws_filepos_t body_chunk_len; + size_t n; + + switch (wsi->state) { +#ifdef LWS_WITH_HTTP2 + case LWSS_HTTP2_AWAIT_CLIENT_PREFACE: + case LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS: + case LWSS_HTTP2_ESTABLISHED: + n = 0; + //lwsl_debug("%s: starting new block of %d\n", __func__, (int)len); + /* + * wsi here is always the network connection wsi, not a stream + * wsi. + */ + while (n < len) { + /* + * we were accepting input but now we stopped doing so + */ + if (lws_is_flowcontrolled(wsi)) { + lws_rxflow_cache(wsi, buf, n, len); + + return 1; + } + + /* account for what we're using in rxflow buffer */ + if (wsi->rxflow_buffer) { + wsi->rxflow_pos++; + assert(wsi->rxflow_pos <= wsi->rxflow_len); + } + + if (lws_h2_parser(wsi, buf[n++])) { + lwsl_debug("%s: http2_parser bailed\n", __func__); + goto bail; + } + } + lwsl_debug("%s: used up block of %d\n", __func__, (int)len); + break; +#endif + + case LWSS_HTTP_ISSUING_FILE: + return 0; + + case LWSS_CLIENT_HTTP_ESTABLISHED: + break; + + case LWSS_HTTP: + wsi->hdr_parsing_completed = 0; + + /* fallthru */ + + case LWSS_HTTP_HEADERS: + if (!wsi->u.hdr.ah) { + lwsl_err("%s: LWSS_HTTP_HEADERS: NULL ah\n", __func__); + assert(0); + } + lwsl_parser("issuing %d bytes to parser\n", (int)len); + + lwsl_hexdump(buf, (size_t)len); + + if (lws_handshake_client(wsi, &buf, (size_t)len)) + goto bail; + + last_char = buf; + if (lws_handshake_server(wsi, &buf, (size_t)len)) + /* Handshake indicates this session is done. */ + goto bail; + + /* we might have transitioned to RAW */ + if (wsi->mode == LWSCM_RAW) + /* we gave the read buffer to RAW handler already */ + goto read_ok; + + /* + * It's possible that we've exhausted our data already, or + * rx flow control has stopped us dealing with this early, + * but lws_handshake_server doesn't update len for us. + * Figure out how much was read, so that we can proceed + * appropriately: + */ + len -= (buf - last_char); + lwsl_debug("%s: thinks we have used %ld\n", __func__, (long)len); + + if (!wsi->hdr_parsing_completed) + /* More header content on the way */ + goto read_ok; + + switch (wsi->state) { + case LWSS_HTTP: + case LWSS_HTTP_HEADERS: + goto read_ok; + case LWSS_HTTP_ISSUING_FILE: + goto read_ok; + case LWSS_HTTP_BODY: + wsi->u.http.rx_content_remain = + wsi->u.http.rx_content_length; + if (wsi->u.http.rx_content_remain) + goto http_postbody; + + /* there is no POST content */ + goto postbody_completion; + default: + break; + } + break; + + case LWSS_HTTP_BODY: +http_postbody: + //lwsl_notice("http post body\n"); + while (len && wsi->u.http.rx_content_remain) { + /* Copy as much as possible, up to the limit of: + * what we have in the read buffer (len) + * remaining portion of the POST body (content_remain) + */ + body_chunk_len = min(wsi->u.http.rx_content_remain, len); + wsi->u.http.rx_content_remain -= body_chunk_len; + len -= body_chunk_len; +#ifdef LWS_WITH_CGI + if (wsi->cgi) { + struct lws_cgi_args args; + + args.ch = LWS_STDIN; + args.stdwsi = &wsi->cgi->stdwsi[0]; + args.data = buf; + args.len = body_chunk_len; + + /* returns how much used */ + n = user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, LWS_CALLBACK_CGI_STDIN_DATA, + wsi->user_space, + (void *)&args, 0); + if ((int)n < 0) + goto bail; + } else { +#endif + n = wsi->protocol->callback(wsi, + LWS_CALLBACK_HTTP_BODY, wsi->user_space, + buf, (size_t)body_chunk_len); + if (n) + goto bail; + n = (size_t)body_chunk_len; +#ifdef LWS_WITH_CGI + } +#endif + buf += n; + + if (wsi->u.http.rx_content_remain) { + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, + wsi->context->timeout_secs); + break; + } + /* he sent all the content in time */ +postbody_completion: +#ifdef LWS_WITH_CGI + /* + * If we're running a cgi, we can't let him off the + * hook just because he sent his POST data + */ + if (wsi->cgi) + lws_set_timeout(wsi, PENDING_TIMEOUT_CGI, + wsi->context->timeout_secs); + else +#endif + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); +#ifdef LWS_WITH_CGI + if (!wsi->cgi) +#endif + { + lwsl_notice("LWS_CALLBACK_HTTP_BODY_COMPLETION\n"); + n = wsi->protocol->callback(wsi, + LWS_CALLBACK_HTTP_BODY_COMPLETION, + wsi->user_space, NULL, 0); + if (n) + goto bail; + + if (wsi->http2_substream) + wsi->state = LWSS_HTTP2_ESTABLISHED; + } + + break; + } + break; + + case LWSS_ESTABLISHED: + case LWSS_AWAITING_CLOSE_ACK: + case LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION: + case LWSS_SHUTDOWN: + if (lws_handshake_client(wsi, &buf, (size_t)len)) + goto bail; + switch (wsi->mode) { + case LWSCM_WS_SERVING: + + if (lws_interpret_incoming_packet(wsi, &buf, (size_t)len) < 0) { + lwsl_info("interpret_incoming_packet has bailed\n"); + goto bail; + } + break; + } + break; + default: + lwsl_err("%s: Unhandled state %d\n", __func__, wsi->state); + break; + } + +read_ok: + /* Nothing more to do for now */ + lwsl_info("%s: read_ok, used %ld\n", __func__, (long)(buf - oldbuf)); + + return buf - oldbuf; + +bail: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + + return -1; +} diff --git a/thirdparty/lws/header.c b/thirdparty/lws/header.c new file mode 100644 index 0000000000..e2562cd6ea --- /dev/null +++ b/thirdparty/lws/header.c @@ -0,0 +1,355 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +#include "lextable-strings.h" + + +const unsigned char *lws_token_to_string(enum lws_token_indexes token) +{ + if ((unsigned int)token >= ARRAY_SIZE(set)) + return NULL; + + return (unsigned char *)set[token]; +} + +int +lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name, + const unsigned char *value, int length, + unsigned char **p, unsigned char *end) +{ +#ifdef LWS_WITH_HTTP2 + if (wsi->mode == LWSCM_HTTP2_SERVING) + return lws_add_http2_header_by_name(wsi, name, + value, length, p, end); +#else + (void)wsi; +#endif + if (name) { + while (*p < end && *name) + *((*p)++) = *name++; + if (*p == end) + return 1; + *((*p)++) = ' '; + } + if (*p + length + 3 >= end) + return 1; + + memcpy(*p, value, length); + *p += length; + *((*p)++) = '\x0d'; + *((*p)++) = '\x0a'; + + return 0; +} + +int lws_finalize_http_header(struct lws *wsi, unsigned char **p, + unsigned char *end) +{ +#ifdef LWS_WITH_HTTP2 + if (wsi->mode == LWSCM_HTTP2_SERVING) + return 0; +#else + (void)wsi; +#endif + if ((lws_intptr_t)(end - *p) < 3) + return 1; + *((*p)++) = '\x0d'; + *((*p)++) = '\x0a'; + + return 0; +} + +int +lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token, + const unsigned char *value, int length, + unsigned char **p, unsigned char *end) +{ + const unsigned char *name; +#ifdef LWS_WITH_HTTP2 + if (wsi->mode == LWSCM_HTTP2_SERVING) + return lws_add_http2_header_by_token(wsi, token, value, + length, p, end); +#endif + name = lws_token_to_string(token); + if (!name) + return 1; + return lws_add_http_header_by_name(wsi, name, value, length, p, end); +} + +int lws_add_http_header_content_length(struct lws *wsi, + lws_filepos_t content_length, + unsigned char **p, unsigned char *end) +{ + char b[24]; + int n; + + n = sprintf(b, "%llu", (unsigned long long)content_length); + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, + (unsigned char *)b, n, p, end)) + return 1; + wsi->u.http.tx_content_length = content_length; + wsi->u.http.tx_content_remain = content_length; + + return 0; +} + +STORE_IN_ROM static const char * const err400[] = { + "Bad Request", + "Unauthorized", + "Payment Required", + "Forbidden", + "Not Found", + "Method Not Allowed", + "Not Acceptable", + "Proxy Auth Required", + "Request Timeout", + "Conflict", + "Gone", + "Length Required", + "Precondition Failed", + "Request Entity Too Large", + "Request URI too Long", + "Unsupported Media Type", + "Requested Range Not Satisfiable", + "Expectation Failed" +}; + +STORE_IN_ROM static const char * const err500[] = { + "Internal Server Error", + "Not Implemented", + "Bad Gateway", + "Service Unavailable", + "Gateway Timeout", + "HTTP Version Not Supported" +}; + +int +lws_add_http_header_status(struct lws *wsi, unsigned int _code, + unsigned char **p, unsigned char *end) +{ + STORE_IN_ROM static const char * const hver[] = { + "HTTP/1.0", "HTTP/1.1", "HTTP/2" + }; + const struct lws_protocol_vhost_options *headers; + unsigned int code = _code & LWSAHH_CODE_MASK; + const char *description = "", *p1; + unsigned char code_and_desc[60]; + int n; + +#ifdef LWS_WITH_ACCESS_LOG + wsi->access_log.response = code; +#endif + +#ifdef LWS_WITH_HTTP2 + if (wsi->mode == LWSCM_HTTP2_SERVING) + return lws_add_http2_header_status(wsi, code, p, end); +#endif + if (code >= 400 && code < (400 + ARRAY_SIZE(err400))) + description = err400[code - 400]; + if (code >= 500 && code < (500 + ARRAY_SIZE(err500))) + description = err500[code - 500]; + + if (code == 100) + description = "Continue"; + + if (code == 200) + description = "OK"; + + if (code == 304) + description = "Not Modified"; + else + if (code >= 300 && code < 400) + description = "Redirect"; + + if (wsi->u.http.request_version < ARRAY_SIZE(hver)) + p1 = hver[wsi->u.http.request_version]; + else + p1 = hver[0]; + + n = sprintf((char *)code_and_desc, "%s %u %s", p1, code, description); + + if (lws_add_http_header_by_name(wsi, NULL, code_and_desc, n, p, end)) + return 1; + + headers = wsi->vhost->headers; + while (headers) { + if (lws_add_http_header_by_name(wsi, + (const unsigned char *)headers->name, + (unsigned char *)headers->value, + strlen(headers->value), p, end)) + return 1; + + headers = headers->next; + } + + if (wsi->context->server_string && + !(_code & LWSAHH_FLAG_NO_SERVER_NAME)) + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_SERVER, + (unsigned char *)wsi->context->server_string, + wsi->context->server_string_len, p, end)) + return 1; + + if (wsi->vhost->options & LWS_SERVER_OPTION_STS) + if (lws_add_http_header_by_name(wsi, (unsigned char *) + "Strict-Transport-Security:", + (unsigned char *)"max-age=15768000 ; " + "includeSubDomains", 36, p, end)) + return 1; + + return 0; +} + +LWS_VISIBLE int +lws_return_http_status(struct lws *wsi, unsigned int code, + const char *html_body) +{ + struct lws_context *context = lws_get_context(wsi); + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + unsigned char *p = pt->serv_buf + LWS_PRE; + unsigned char *start = p; + unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE; + int n = 0, m = 0, len; + char slen[20]; + + if (!html_body) + html_body = ""; + + if (lws_add_http_header_status(wsi, code, &p, end)) + return 1; + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)"text/html", 9, + &p, end)) + return 1; + + len = 35 + strlen(html_body) + sprintf(slen, "%d", code); + n = sprintf(slen, "%d", len); + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH, + (unsigned char *)slen, n, + &p, end)) + return 1; + + if (lws_finalize_http_header(wsi, &p, end)) + return 1; + +#if defined(LWS_WITH_HTTP2) + if (wsi->http2_substream) { + unsigned char *body = p + 512; + + /* + * for HTTP/2, the headers must be sent separately, since they + * go out in their own frame. That puts us in a bind that + * we won't always be able to get away with two lws_write()s in + * sequence, since the first may use up the writability due to + * the pipe being choked or SSL_WANT_. + * + * However we do need to send the human-readable body, and the + * END_STREAM. + * + * Solve it by writing the headers now... + */ + m = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS); + if (m != (int)(p - start)) + return 1; + + /* + * ... but stash the body and send it as a priority next + * handle_POLLOUT + */ + + len = sprintf((char *)body, + "<html><body><h1>%u</h1>%s</body></html>", + code, html_body); + wsi->u.http.tx_content_length = len; + wsi->u.http.tx_content_remain = len; + + wsi->u.h2.pending_status_body = lws_malloc(len + LWS_PRE + 1, + "pending status body"); + if (!wsi->u.h2.pending_status_body) + return -1; + + strcpy(wsi->u.h2.pending_status_body + LWS_PRE, + (const char *)body); + lws_callback_on_writable(wsi); + + return 0; + } else +#endif + { + /* + * for http/1, we can just append the body after the finalized + * headers and send it all in one go. + */ + p += lws_snprintf((char *)p, end - p - 1, + "<html><body><h1>%u</h1>%s</body></html>", + code, html_body); + + n = (int)(p - start); + + m = lws_write(wsi, start, n, LWS_WRITE_HTTP); + if (m != n) + return 1; + } + + lwsl_notice("%s: return\n", __func__); + + return m != n; +} + +LWS_VISIBLE int +lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len, + unsigned char **p, unsigned char *end) +{ + unsigned char *start = *p; + int n; + + if (lws_add_http_header_status(wsi, code, p, end)) + return -1; + + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_LOCATION, + loc, len, p, end)) + return -1; + /* + * if we're going with http/1.1 and keepalive, we have to give fake + * content metadata so the client knows we completed the transaction and + * it can do the redirect... + */ + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)"text/html", 9, + p, end)) + return -1; + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_LENGTH, + (unsigned char *)"0", 1, p, end)) + return -1; + + if (lws_finalize_http_header(wsi, p, end)) + return -1; + + n = lws_write(wsi, start, *p - start, LWS_WRITE_HTTP_HEADERS | LWS_WRITE_H2_STREAM_END); + + return n; +} diff --git a/thirdparty/lws/lextable-strings.h b/thirdparty/lws/lextable-strings.h new file mode 100644 index 0000000000..ab42c3e476 --- /dev/null +++ b/thirdparty/lws/lextable-strings.h @@ -0,0 +1,104 @@ +/* set of parsable strings -- ALL LOWER CASE */ + +#if !defined(STORE_IN_ROM) +#define STORE_IN_ROM +#endif + +STORE_IN_ROM static const char * const set[] = { + "get ", + "post ", + "options ", + "host:", + "connection:", + "upgrade:", + "origin:", + "sec-websocket-draft:", + "\x0d\x0a", + + "sec-websocket-extensions:", + "sec-websocket-key1:", + "sec-websocket-key2:", + "sec-websocket-protocol:", + + "sec-websocket-accept:", + "sec-websocket-nonce:", + "http/1.1 ", + "http2-settings:", + + "accept:", + "access-control-request-headers:", + "if-modified-since:", + "if-none-match:", + "accept-encoding:", + "accept-language:", + "pragma:", + "cache-control:", + "authorization:", + "cookie:", + "content-length:", + "content-type:", + "date:", + "range:", + "referer:", + "sec-websocket-key:", + "sec-websocket-version:", + "sec-websocket-origin:", + + ":authority", + ":method", + ":path", + ":scheme", + ":status", + + "accept-charset:", + "accept-ranges:", + "access-control-allow-origin:", + "age:", + "allow:", + "content-disposition:", + "content-encoding:", + "content-language:", + "content-location:", + "content-range:", + "etag:", + "expect:", + "expires:", + "from:", + "if-match:", + "if-range:", + "if-unmodified-since:", + "last-modified:", + "link:", + "location:", + "max-forwards:", + "proxy-authenticate:", + "proxy-authorization:", + "refresh:", + "retry-after:", + "server:", + "set-cookie:", + "strict-transport-security:", + "transfer-encoding:", + "user-agent:", + "vary:", + "via:", + "www-authenticate:", + + "patch", + "put", + "delete", + + "uri-args", /* fake header used for uri-only storage */ + + "proxy ", + "x-real-ip:", + "http/1.0 ", + + "x-forwarded-for", + "connect ", + "head ", + "te:", /* http/2 wants it to reject it */ + + "", /* not matchable */ + +}; diff --git a/thirdparty/lws/lextable.h b/thirdparty/lws/lextable.h new file mode 100644 index 0000000000..f940afd25b --- /dev/null +++ b/thirdparty/lws/lextable.h @@ -0,0 +1,805 @@ +/* pos 0000: 0 */ 0x67 /* 'g' */, 0x40, 0x00 /* (to 0x0040 state 1) */, + 0x70 /* 'p' */, 0x42, 0x00 /* (to 0x0045 state 5) */, + 0x6F /* 'o' */, 0x51, 0x00 /* (to 0x0057 state 10) */, + 0x68 /* 'h' */, 0x5D, 0x00 /* (to 0x0066 state 18) */, + 0x63 /* 'c' */, 0x69, 0x00 /* (to 0x0075 state 23) */, + 0x75 /* 'u' */, 0x8A, 0x00 /* (to 0x0099 state 34) */, + 0x73 /* 's' */, 0xA0, 0x00 /* (to 0x00B2 state 48) */, + 0x0D /* '.' */, 0xD9, 0x00 /* (to 0x00EE state 68) */, + 0x61 /* 'a' */, 0x31, 0x01 /* (to 0x0149 state 129) */, + 0x69 /* 'i' */, 0x70, 0x01 /* (to 0x018B state 163) */, + 0x64 /* 'd' */, 0x19, 0x02 /* (to 0x0237 state 265) */, + 0x72 /* 'r' */, 0x22, 0x02 /* (to 0x0243 state 270) */, + 0x3A /* ':' */, 0x53, 0x02 /* (to 0x0277 state 299) */, + 0x65 /* 'e' */, 0xDF, 0x02 /* (to 0x0306 state 409) */, + 0x66 /* 'f' */, 0xFB, 0x02 /* (to 0x0325 state 425) */, + 0x6C /* 'l' */, 0x1D, 0x03 /* (to 0x034A state 458) */, + 0x6D /* 'm' */, 0x40, 0x03 /* (to 0x0370 state 484) */, + 0x74 /* 't' */, 0xAF, 0x03 /* (to 0x03E2 state 578) */, + 0x76 /* 'v' */, 0xD0, 0x03 /* (to 0x0406 state 606) */, + 0x77 /* 'w' */, 0xDD, 0x03 /* (to 0x0416 state 614) */, + 0x78 /* 'x' */, 0x04, 0x04 /* (to 0x0440 state 650) */, + 0x08, /* fail */ +/* pos 0040: 1 */ 0xE5 /* 'e' -> */, +/* pos 0041: 2 */ 0xF4 /* 't' -> */, +/* pos 0042: 3 */ 0xA0 /* ' ' -> */, +/* pos 0043: 4 */ 0x00, 0x00 /* - terminal marker 0 - */, +/* pos 0045: 5 */ 0x6F /* 'o' */, 0x0D, 0x00 /* (to 0x0052 state 6) */, + 0x72 /* 'r' */, 0x95, 0x01 /* (to 0x01DD state 211) */, + 0x61 /* 'a' */, 0xDD, 0x03 /* (to 0x0428 state 631) */, + 0x75 /* 'u' */, 0xDF, 0x03 /* (to 0x042D state 635) */, + 0x08, /* fail */ +/* pos 0052: 6 */ 0xF3 /* 's' -> */, +/* pos 0053: 7 */ 0xF4 /* 't' -> */, +/* pos 0054: 8 */ 0xA0 /* ' ' -> */, +/* pos 0055: 9 */ 0x00, 0x01 /* - terminal marker 1 - */, +/* pos 0057: 10 */ 0x70 /* 'p' */, 0x07, 0x00 /* (to 0x005E state 11) */, + 0x72 /* 'r' */, 0x51, 0x00 /* (to 0x00AB state 42) */, + 0x08, /* fail */ +/* pos 005e: 11 */ 0xF4 /* 't' -> */, +/* pos 005f: 12 */ 0xE9 /* 'i' -> */, +/* pos 0060: 13 */ 0xEF /* 'o' -> */, +/* pos 0061: 14 */ 0xEE /* 'n' -> */, +/* pos 0062: 15 */ 0xF3 /* 's' -> */, +/* pos 0063: 16 */ 0xA0 /* ' ' -> */, +/* pos 0064: 17 */ 0x00, 0x02 /* - terminal marker 2 - */, +/* pos 0066: 18 */ 0x6F /* 'o' */, 0x0A, 0x00 /* (to 0x0070 state 19) */, + 0x74 /* 't' */, 0xBF, 0x00 /* (to 0x0128 state 110) */, + 0x65 /* 'e' */, 0xF8, 0x03 /* (to 0x0464 state 676) */, + 0x08, /* fail */ +/* pos 0070: 19 */ 0xF3 /* 's' -> */, +/* pos 0071: 20 */ 0xF4 /* 't' -> */, +/* pos 0072: 21 */ 0xBA /* ':' -> */, +/* pos 0073: 22 */ 0x00, 0x03 /* - terminal marker 3 - */, +/* pos 0075: 23 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x007C state 24) */, + 0x61 /* 'a' */, 0x72, 0x01 /* (to 0x01EA state 217) */, + 0x08, /* fail */ +/* pos 007c: 24 */ 0x6E /* 'n' */, 0x07, 0x00 /* (to 0x0083 state 25) */, + 0x6F /* 'o' */, 0x87, 0x01 /* (to 0x0206 state 243) */, + 0x08, /* fail */ +/* pos 0083: 25 */ 0x6E /* 'n' */, 0x07, 0x00 /* (to 0x008A state 26) */, + 0x74 /* 't' */, 0x86, 0x01 /* (to 0x020C state 248) */, + 0x08, /* fail */ +/* pos 008a: 26 */ 0xE5 /* 'e' -> */, +/* pos 008b: 27 */ 0xE3 /* 'c' -> */, +/* pos 008c: 28 */ 0xF4 /* 't' -> */, +/* pos 008d: 29 */ 0x69 /* 'i' */, 0x07, 0x00 /* (to 0x0094 state 30) */, + 0x20 /* ' ' */, 0xD2, 0x03 /* (to 0x0462 state 675) */, + 0x08, /* fail */ +/* pos 0094: 30 */ 0xEF /* 'o' -> */, +/* pos 0095: 31 */ 0xEE /* 'n' -> */, +/* pos 0096: 32 */ 0xBA /* ':' -> */, +/* pos 0097: 33 */ 0x00, 0x04 /* - terminal marker 4 - */, +/* pos 0099: 34 */ 0x70 /* 'p' */, 0x0A, 0x00 /* (to 0x00A3 state 35) */, + 0x73 /* 's' */, 0x5F, 0x03 /* (to 0x03FB state 596) */, + 0x72 /* 'r' */, 0x97, 0x03 /* (to 0x0436 state 642) */, + 0x08, /* fail */ +/* pos 00a3: 35 */ 0xE7 /* 'g' -> */, +/* pos 00a4: 36 */ 0xF2 /* 'r' -> */, +/* pos 00a5: 37 */ 0xE1 /* 'a' -> */, +/* pos 00a6: 38 */ 0xE4 /* 'd' -> */, +/* pos 00a7: 39 */ 0xE5 /* 'e' -> */, +/* pos 00a8: 40 */ 0xBA /* ':' -> */, +/* pos 00a9: 41 */ 0x00, 0x05 /* - terminal marker 5 - */, +/* pos 00ab: 42 */ 0xE9 /* 'i' -> */, +/* pos 00ac: 43 */ 0xE7 /* 'g' -> */, +/* pos 00ad: 44 */ 0xE9 /* 'i' -> */, +/* pos 00ae: 45 */ 0xEE /* 'n' -> */, +/* pos 00af: 46 */ 0xBA /* ':' -> */, +/* pos 00b0: 47 */ 0x00, 0x06 /* - terminal marker 6 - */, +/* pos 00b2: 48 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x00B9 state 49) */, + 0x74 /* 't' */, 0x13, 0x03 /* (to 0x03C8 state 553) */, + 0x08, /* fail */ +/* pos 00b9: 49 */ 0x63 /* 'c' */, 0x0A, 0x00 /* (to 0x00C3 state 50) */, + 0x72 /* 'r' */, 0xFC, 0x02 /* (to 0x03B8 state 539) */, + 0x74 /* 't' */, 0xFF, 0x02 /* (to 0x03BE state 544) */, + 0x08, /* fail */ +/* pos 00c3: 50 */ 0xAD /* '-' -> */, +/* pos 00c4: 51 */ 0xF7 /* 'w' -> */, +/* pos 00c5: 52 */ 0xE5 /* 'e' -> */, +/* pos 00c6: 53 */ 0xE2 /* 'b' -> */, +/* pos 00c7: 54 */ 0xF3 /* 's' -> */, +/* pos 00c8: 55 */ 0xEF /* 'o' -> */, +/* pos 00c9: 56 */ 0xE3 /* 'c' -> */, +/* pos 00ca: 57 */ 0xEB /* 'k' -> */, +/* pos 00cb: 58 */ 0xE5 /* 'e' -> */, +/* pos 00cc: 59 */ 0xF4 /* 't' -> */, +/* pos 00cd: 60 */ 0xAD /* '-' -> */, +/* pos 00ce: 61 */ 0x64 /* 'd' */, 0x19, 0x00 /* (to 0x00E7 state 62) */, + 0x65 /* 'e' */, 0x20, 0x00 /* (to 0x00F1 state 70) */, + 0x6B /* 'k' */, 0x29, 0x00 /* (to 0x00FD state 81) */, + 0x70 /* 'p' */, 0x38, 0x00 /* (to 0x010F state 88) */, + 0x61 /* 'a' */, 0x3F, 0x00 /* (to 0x0119 state 97) */, + 0x6E /* 'n' */, 0x44, 0x00 /* (to 0x0121 state 104) */, + 0x76 /* 'v' */, 0x86, 0x01 /* (to 0x0266 state 284) */, + 0x6F /* 'o' */, 0x8C, 0x01 /* (to 0x026F state 292) */, + 0x08, /* fail */ +/* pos 00e7: 62 */ 0xF2 /* 'r' -> */, +/* pos 00e8: 63 */ 0xE1 /* 'a' -> */, +/* pos 00e9: 64 */ 0xE6 /* 'f' -> */, +/* pos 00ea: 65 */ 0xF4 /* 't' -> */, +/* pos 00eb: 66 */ 0xBA /* ':' -> */, +/* pos 00ec: 67 */ 0x00, 0x07 /* - terminal marker 7 - */, +/* pos 00ee: 68 */ 0x8A /* '.' -> */, +/* pos 00ef: 69 */ 0x00, 0x08 /* - terminal marker 8 - */, +/* pos 00f1: 70 */ 0xF8 /* 'x' -> */, +/* pos 00f2: 71 */ 0xF4 /* 't' -> */, +/* pos 00f3: 72 */ 0xE5 /* 'e' -> */, +/* pos 00f4: 73 */ 0xEE /* 'n' -> */, +/* pos 00f5: 74 */ 0xF3 /* 's' -> */, +/* pos 00f6: 75 */ 0xE9 /* 'i' -> */, +/* pos 00f7: 76 */ 0xEF /* 'o' -> */, +/* pos 00f8: 77 */ 0xEE /* 'n' -> */, +/* pos 00f9: 78 */ 0xF3 /* 's' -> */, +/* pos 00fa: 79 */ 0xBA /* ':' -> */, +/* pos 00fb: 80 */ 0x00, 0x09 /* - terminal marker 9 - */, +/* pos 00fd: 81 */ 0xE5 /* 'e' -> */, +/* pos 00fe: 82 */ 0xF9 /* 'y' -> */, +/* pos 00ff: 83 */ 0x31 /* '1' */, 0x0A, 0x00 /* (to 0x0109 state 84) */, + 0x32 /* '2' */, 0x0A, 0x00 /* (to 0x010C state 86) */, + 0x3A /* ':' */, 0x5F, 0x01 /* (to 0x0264 state 283) */, + 0x08, /* fail */ +/* pos 0109: 84 */ 0xBA /* ':' -> */, +/* pos 010a: 85 */ 0x00, 0x0A /* - terminal marker 10 - */, +/* pos 010c: 86 */ 0xBA /* ':' -> */, +/* pos 010d: 87 */ 0x00, 0x0B /* - terminal marker 11 - */, +/* pos 010f: 88 */ 0xF2 /* 'r' -> */, +/* pos 0110: 89 */ 0xEF /* 'o' -> */, +/* pos 0111: 90 */ 0xF4 /* 't' -> */, +/* pos 0112: 91 */ 0xEF /* 'o' -> */, +/* pos 0113: 92 */ 0xE3 /* 'c' -> */, +/* pos 0114: 93 */ 0xEF /* 'o' -> */, +/* pos 0115: 94 */ 0xEC /* 'l' -> */, +/* pos 0116: 95 */ 0xBA /* ':' -> */, +/* pos 0117: 96 */ 0x00, 0x0C /* - terminal marker 12 - */, +/* pos 0119: 97 */ 0xE3 /* 'c' -> */, +/* pos 011a: 98 */ 0xE3 /* 'c' -> */, +/* pos 011b: 99 */ 0xE5 /* 'e' -> */, +/* pos 011c: 100 */ 0xF0 /* 'p' -> */, +/* pos 011d: 101 */ 0xF4 /* 't' -> */, +/* pos 011e: 102 */ 0xBA /* ':' -> */, +/* pos 011f: 103 */ 0x00, 0x0D /* - terminal marker 13 - */, +/* pos 0121: 104 */ 0xEF /* 'o' -> */, +/* pos 0122: 105 */ 0xEE /* 'n' -> */, +/* pos 0123: 106 */ 0xE3 /* 'c' -> */, +/* pos 0124: 107 */ 0xE5 /* 'e' -> */, +/* pos 0125: 108 */ 0xBA /* ':' -> */, +/* pos 0126: 109 */ 0x00, 0x0E /* - terminal marker 14 - */, +/* pos 0128: 110 */ 0xF4 /* 't' -> */, +/* pos 0129: 111 */ 0xF0 /* 'p' -> */, +/* pos 012a: 112 */ 0x2F /* '/' */, 0x07, 0x00 /* (to 0x0131 state 113) */, + 0x32 /* '2' */, 0x10, 0x00 /* (to 0x013D state 118) */, + 0x08, /* fail */ +/* pos 0131: 113 */ 0xB1 /* '1' -> */, +/* pos 0132: 114 */ 0xAE /* '.' -> */, +/* pos 0133: 115 */ 0x31 /* '1' */, 0x07, 0x00 /* (to 0x013A state 116) */, + 0x30 /* '0' */, 0x1B, 0x03 /* (to 0x0451 state 660) */, + 0x08, /* fail */ +/* pos 013a: 116 */ 0xA0 /* ' ' -> */, +/* pos 013b: 117 */ 0x00, 0x0F /* - terminal marker 15 - */, +/* pos 013d: 118 */ 0xAD /* '-' -> */, +/* pos 013e: 119 */ 0xF3 /* 's' -> */, +/* pos 013f: 120 */ 0xE5 /* 'e' -> */, +/* pos 0140: 121 */ 0xF4 /* 't' -> */, +/* pos 0141: 122 */ 0xF4 /* 't' -> */, +/* pos 0142: 123 */ 0xE9 /* 'i' -> */, +/* pos 0143: 124 */ 0xEE /* 'n' -> */, +/* pos 0144: 125 */ 0xE7 /* 'g' -> */, +/* pos 0145: 126 */ 0xF3 /* 's' -> */, +/* pos 0146: 127 */ 0xBA /* ':' -> */, +/* pos 0147: 128 */ 0x00, 0x10 /* - terminal marker 16 - */, +/* pos 0149: 129 */ 0x63 /* 'c' */, 0x0D, 0x00 /* (to 0x0156 state 130) */, + 0x75 /* 'u' */, 0xAC, 0x00 /* (to 0x01F8 state 230) */, + 0x67 /* 'g' */, 0x7D, 0x01 /* (to 0x02CC state 358) */, + 0x6C /* 'l' */, 0x7E, 0x01 /* (to 0x02D0 state 361) */, + 0x08, /* fail */ +/* pos 0156: 130 */ 0xE3 /* 'c' -> */, +/* pos 0157: 131 */ 0xE5 /* 'e' -> */, +/* pos 0158: 132 */ 0x70 /* 'p' */, 0x07, 0x00 /* (to 0x015F state 133) */, + 0x73 /* 's' */, 0x0E, 0x00 /* (to 0x0169 state 136) */, + 0x08, /* fail */ +/* pos 015f: 133 */ 0xF4 /* 't' -> */, +/* pos 0160: 134 */ 0x3A /* ':' */, 0x07, 0x00 /* (to 0x0167 state 135) */, + 0x2D /* '-' */, 0x59, 0x00 /* (to 0x01BC state 192) */, + 0x08, /* fail */ +/* pos 0167: 135 */ 0x00, 0x11 /* - terminal marker 17 - */, +/* pos 0169: 136 */ 0xF3 /* 's' -> */, +/* pos 016a: 137 */ 0xAD /* '-' -> */, +/* pos 016b: 138 */ 0xE3 /* 'c' -> */, +/* pos 016c: 139 */ 0xEF /* 'o' -> */, +/* pos 016d: 140 */ 0xEE /* 'n' -> */, +/* pos 016e: 141 */ 0xF4 /* 't' -> */, +/* pos 016f: 142 */ 0xF2 /* 'r' -> */, +/* pos 0170: 143 */ 0xEF /* 'o' -> */, +/* pos 0171: 144 */ 0xEC /* 'l' -> */, +/* pos 0172: 145 */ 0xAD /* '-' -> */, +/* pos 0173: 146 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x017A state 147) */, + 0x61 /* 'a' */, 0x48, 0x01 /* (to 0x02BE state 345) */, + 0x08, /* fail */ +/* pos 017a: 147 */ 0xE5 /* 'e' -> */, +/* pos 017b: 148 */ 0xF1 /* 'q' -> */, +/* pos 017c: 149 */ 0xF5 /* 'u' -> */, +/* pos 017d: 150 */ 0xE5 /* 'e' -> */, +/* pos 017e: 151 */ 0xF3 /* 's' -> */, +/* pos 017f: 152 */ 0xF4 /* 't' -> */, +/* pos 0180: 153 */ 0xAD /* '-' -> */, +/* pos 0181: 154 */ 0xE8 /* 'h' -> */, +/* pos 0182: 155 */ 0xE5 /* 'e' -> */, +/* pos 0183: 156 */ 0xE1 /* 'a' -> */, +/* pos 0184: 157 */ 0xE4 /* 'd' -> */, +/* pos 0185: 158 */ 0xE5 /* 'e' -> */, +/* pos 0186: 159 */ 0xF2 /* 'r' -> */, +/* pos 0187: 160 */ 0xF3 /* 's' -> */, +/* pos 0188: 161 */ 0xBA /* ':' -> */, +/* pos 0189: 162 */ 0x00, 0x12 /* - terminal marker 18 - */, +/* pos 018b: 163 */ 0xE6 /* 'f' -> */, +/* pos 018c: 164 */ 0xAD /* '-' -> */, +/* pos 018d: 165 */ 0x6D /* 'm' */, 0x0D, 0x00 /* (to 0x019A state 166) */, + 0x6E /* 'n' */, 0x20, 0x00 /* (to 0x01B0 state 181) */, + 0x72 /* 'r' */, 0x9E, 0x01 /* (to 0x0331 state 435) */, + 0x75 /* 'u' */, 0xA2, 0x01 /* (to 0x0338 state 441) */, + 0x08, /* fail */ +/* pos 019a: 166 */ 0x6F /* 'o' */, 0x07, 0x00 /* (to 0x01A1 state 167) */, + 0x61 /* 'a' */, 0x8E, 0x01 /* (to 0x032B state 430) */, + 0x08, /* fail */ +/* pos 01a1: 167 */ 0xE4 /* 'd' -> */, +/* pos 01a2: 168 */ 0xE9 /* 'i' -> */, +/* pos 01a3: 169 */ 0xE6 /* 'f' -> */, +/* pos 01a4: 170 */ 0xE9 /* 'i' -> */, +/* pos 01a5: 171 */ 0xE5 /* 'e' -> */, +/* pos 01a6: 172 */ 0xE4 /* 'd' -> */, +/* pos 01a7: 173 */ 0xAD /* '-' -> */, +/* pos 01a8: 174 */ 0xF3 /* 's' -> */, +/* pos 01a9: 175 */ 0xE9 /* 'i' -> */, +/* pos 01aa: 176 */ 0xEE /* 'n' -> */, +/* pos 01ab: 177 */ 0xE3 /* 'c' -> */, +/* pos 01ac: 178 */ 0xE5 /* 'e' -> */, +/* pos 01ad: 179 */ 0xBA /* ':' -> */, +/* pos 01ae: 180 */ 0x00, 0x13 /* - terminal marker 19 - */, +/* pos 01b0: 181 */ 0xEF /* 'o' -> */, +/* pos 01b1: 182 */ 0xEE /* 'n' -> */, +/* pos 01b2: 183 */ 0xE5 /* 'e' -> */, +/* pos 01b3: 184 */ 0xAD /* '-' -> */, +/* pos 01b4: 185 */ 0xED /* 'm' -> */, +/* pos 01b5: 186 */ 0xE1 /* 'a' -> */, +/* pos 01b6: 187 */ 0xF4 /* 't' -> */, +/* pos 01b7: 188 */ 0xE3 /* 'c' -> */, +/* pos 01b8: 189 */ 0xE8 /* 'h' -> */, +/* pos 01b9: 190 */ 0xBA /* ':' -> */, +/* pos 01ba: 191 */ 0x00, 0x14 /* - terminal marker 20 - */, +/* pos 01bc: 192 */ 0x65 /* 'e' */, 0x0D, 0x00 /* (to 0x01C9 state 193) */, + 0x6C /* 'l' */, 0x14, 0x00 /* (to 0x01D3 state 202) */, + 0x63 /* 'c' */, 0xEB, 0x00 /* (to 0x02AD state 330) */, + 0x72 /* 'r' */, 0xF1, 0x00 /* (to 0x02B6 state 338) */, + 0x08, /* fail */ +/* pos 01c9: 193 */ 0xEE /* 'n' -> */, +/* pos 01ca: 194 */ 0xE3 /* 'c' -> */, +/* pos 01cb: 195 */ 0xEF /* 'o' -> */, +/* pos 01cc: 196 */ 0xE4 /* 'd' -> */, +/* pos 01cd: 197 */ 0xE9 /* 'i' -> */, +/* pos 01ce: 198 */ 0xEE /* 'n' -> */, +/* pos 01cf: 199 */ 0xE7 /* 'g' -> */, +/* pos 01d0: 200 */ 0xBA /* ':' -> */, +/* pos 01d1: 201 */ 0x00, 0x15 /* - terminal marker 21 - */, +/* pos 01d3: 202 */ 0xE1 /* 'a' -> */, +/* pos 01d4: 203 */ 0xEE /* 'n' -> */, +/* pos 01d5: 204 */ 0xE7 /* 'g' -> */, +/* pos 01d6: 205 */ 0xF5 /* 'u' -> */, +/* pos 01d7: 206 */ 0xE1 /* 'a' -> */, +/* pos 01d8: 207 */ 0xE7 /* 'g' -> */, +/* pos 01d9: 208 */ 0xE5 /* 'e' -> */, +/* pos 01da: 209 */ 0xBA /* ':' -> */, +/* pos 01db: 210 */ 0x00, 0x16 /* - terminal marker 22 - */, +/* pos 01dd: 211 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x01E4 state 212) */, + 0x6F /* 'o' */, 0x9E, 0x01 /* (to 0x037E state 497) */, + 0x08, /* fail */ +/* pos 01e4: 212 */ 0xE7 /* 'g' -> */, +/* pos 01e5: 213 */ 0xED /* 'm' -> */, +/* pos 01e6: 214 */ 0xE1 /* 'a' -> */, +/* pos 01e7: 215 */ 0xBA /* ':' -> */, +/* pos 01e8: 216 */ 0x00, 0x17 /* - terminal marker 23 - */, +/* pos 01ea: 217 */ 0xE3 /* 'c' -> */, +/* pos 01eb: 218 */ 0xE8 /* 'h' -> */, +/* pos 01ec: 219 */ 0xE5 /* 'e' -> */, +/* pos 01ed: 220 */ 0xAD /* '-' -> */, +/* pos 01ee: 221 */ 0xE3 /* 'c' -> */, +/* pos 01ef: 222 */ 0xEF /* 'o' -> */, +/* pos 01f0: 223 */ 0xEE /* 'n' -> */, +/* pos 01f1: 224 */ 0xF4 /* 't' -> */, +/* pos 01f2: 225 */ 0xF2 /* 'r' -> */, +/* pos 01f3: 226 */ 0xEF /* 'o' -> */, +/* pos 01f4: 227 */ 0xEC /* 'l' -> */, +/* pos 01f5: 228 */ 0xBA /* ':' -> */, +/* pos 01f6: 229 */ 0x00, 0x18 /* - terminal marker 24 - */, +/* pos 01f8: 230 */ 0xF4 /* 't' -> */, +/* pos 01f9: 231 */ 0xE8 /* 'h' -> */, +/* pos 01fa: 232 */ 0xEF /* 'o' -> */, +/* pos 01fb: 233 */ 0xF2 /* 'r' -> */, +/* pos 01fc: 234 */ 0xE9 /* 'i' -> */, +/* pos 01fd: 235 */ 0xFA /* 'z' -> */, +/* pos 01fe: 236 */ 0xE1 /* 'a' -> */, +/* pos 01ff: 237 */ 0xF4 /* 't' -> */, +/* pos 0200: 238 */ 0xE9 /* 'i' -> */, +/* pos 0201: 239 */ 0xEF /* 'o' -> */, +/* pos 0202: 240 */ 0xEE /* 'n' -> */, +/* pos 0203: 241 */ 0xBA /* ':' -> */, +/* pos 0204: 242 */ 0x00, 0x19 /* - terminal marker 25 - */, +/* pos 0206: 243 */ 0xEB /* 'k' -> */, +/* pos 0207: 244 */ 0xE9 /* 'i' -> */, +/* pos 0208: 245 */ 0xE5 /* 'e' -> */, +/* pos 0209: 246 */ 0xBA /* ':' -> */, +/* pos 020a: 247 */ 0x00, 0x1A /* - terminal marker 26 - */, +/* pos 020c: 248 */ 0xE5 /* 'e' -> */, +/* pos 020d: 249 */ 0xEE /* 'n' -> */, +/* pos 020e: 250 */ 0xF4 /* 't' -> */, +/* pos 020f: 251 */ 0xAD /* '-' -> */, +/* pos 0210: 252 */ 0x6C /* 'l' */, 0x10, 0x00 /* (to 0x0220 state 253) */, + 0x74 /* 't' */, 0x1E, 0x00 /* (to 0x0231 state 260) */, + 0x64 /* 'd' */, 0xC0, 0x00 /* (to 0x02D6 state 366) */, + 0x65 /* 'e' */, 0xCA, 0x00 /* (to 0x02E3 state 378) */, + 0x72 /* 'r' */, 0xE3, 0x00 /* (to 0x02FF state 403) */, + 0x08, /* fail */ +/* pos 0220: 253 */ 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x022A state 254) */, + 0x61 /* 'a' */, 0xCA, 0x00 /* (to 0x02ED state 387) */, + 0x6F /* 'o' */, 0xD0, 0x00 /* (to 0x02F6 state 395) */, + 0x08, /* fail */ +/* pos 022a: 254 */ 0xEE /* 'n' -> */, +/* pos 022b: 255 */ 0xE7 /* 'g' -> */, +/* pos 022c: 256 */ 0xF4 /* 't' -> */, +/* pos 022d: 257 */ 0xE8 /* 'h' -> */, +/* pos 022e: 258 */ 0xBA /* ':' -> */, +/* pos 022f: 259 */ 0x00, 0x1B /* - terminal marker 27 - */, +/* pos 0231: 260 */ 0xF9 /* 'y' -> */, +/* pos 0232: 261 */ 0xF0 /* 'p' -> */, +/* pos 0233: 262 */ 0xE5 /* 'e' -> */, +/* pos 0234: 263 */ 0xBA /* ':' -> */, +/* pos 0235: 264 */ 0x00, 0x1C /* - terminal marker 28 - */, +/* pos 0237: 265 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x023E state 266) */, + 0x65 /* 'e' */, 0xF6, 0x01 /* (to 0x0430 state 637) */, + 0x08, /* fail */ +/* pos 023e: 266 */ 0xF4 /* 't' -> */, +/* pos 023f: 267 */ 0xE5 /* 'e' -> */, +/* pos 0240: 268 */ 0xBA /* ':' -> */, +/* pos 0241: 269 */ 0x00, 0x1D /* - terminal marker 29 - */, +/* pos 0243: 270 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x024A state 271) */, + 0x65 /* 'e' */, 0x0A, 0x00 /* (to 0x0250 state 276) */, + 0x08, /* fail */ +/* pos 024a: 271 */ 0xEE /* 'n' -> */, +/* pos 024b: 272 */ 0xE7 /* 'g' -> */, +/* pos 024c: 273 */ 0xE5 /* 'e' -> */, +/* pos 024d: 274 */ 0xBA /* ':' -> */, +/* pos 024e: 275 */ 0x00, 0x1E /* - terminal marker 30 - */, +/* pos 0250: 276 */ 0x66 /* 'f' */, 0x07, 0x00 /* (to 0x0257 state 277) */, + 0x74 /* 't' */, 0x5A, 0x01 /* (to 0x03AD state 529) */, + 0x08, /* fail */ +/* pos 0257: 277 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x025E state 278) */, + 0x72 /* 'r' */, 0x4D, 0x01 /* (to 0x03A7 state 524) */, + 0x08, /* fail */ +/* pos 025e: 278 */ 0xF2 /* 'r' -> */, +/* pos 025f: 279 */ 0xE5 /* 'e' -> */, +/* pos 0260: 280 */ 0xF2 /* 'r' -> */, +/* pos 0261: 281 */ 0xBA /* ':' -> */, +/* pos 0262: 282 */ 0x00, 0x1F /* - terminal marker 31 - */, +/* pos 0264: 283 */ 0x00, 0x20 /* - terminal marker 32 - */, +/* pos 0266: 284 */ 0xE5 /* 'e' -> */, +/* pos 0267: 285 */ 0xF2 /* 'r' -> */, +/* pos 0268: 286 */ 0xF3 /* 's' -> */, +/* pos 0269: 287 */ 0xE9 /* 'i' -> */, +/* pos 026a: 288 */ 0xEF /* 'o' -> */, +/* pos 026b: 289 */ 0xEE /* 'n' -> */, +/* pos 026c: 290 */ 0xBA /* ':' -> */, +/* pos 026d: 291 */ 0x00, 0x21 /* - terminal marker 33 - */, +/* pos 026f: 292 */ 0xF2 /* 'r' -> */, +/* pos 0270: 293 */ 0xE9 /* 'i' -> */, +/* pos 0271: 294 */ 0xE7 /* 'g' -> */, +/* pos 0272: 295 */ 0xE9 /* 'i' -> */, +/* pos 0273: 296 */ 0xEE /* 'n' -> */, +/* pos 0274: 297 */ 0xBA /* ':' -> */, +/* pos 0275: 298 */ 0x00, 0x22 /* - terminal marker 34 - */, +/* pos 0277: 299 */ 0x61 /* 'a' */, 0x0D, 0x00 /* (to 0x0284 state 300) */, + 0x6D /* 'm' */, 0x14, 0x00 /* (to 0x028E state 309) */, + 0x70 /* 'p' */, 0x18, 0x00 /* (to 0x0295 state 315) */, + 0x73 /* 's' */, 0x1A, 0x00 /* (to 0x029A state 319) */, + 0x08, /* fail */ +/* pos 0284: 300 */ 0xF5 /* 'u' -> */, +/* pos 0285: 301 */ 0xF4 /* 't' -> */, +/* pos 0286: 302 */ 0xE8 /* 'h' -> */, +/* pos 0287: 303 */ 0xEF /* 'o' -> */, +/* pos 0288: 304 */ 0xF2 /* 'r' -> */, +/* pos 0289: 305 */ 0xE9 /* 'i' -> */, +/* pos 028a: 306 */ 0xF4 /* 't' -> */, +/* pos 028b: 307 */ 0xF9 /* 'y' -> */, +/* pos 028c: 308 */ 0x00, 0x23 /* - terminal marker 35 - */, +/* pos 028e: 309 */ 0xE5 /* 'e' -> */, +/* pos 028f: 310 */ 0xF4 /* 't' -> */, +/* pos 0290: 311 */ 0xE8 /* 'h' -> */, +/* pos 0291: 312 */ 0xEF /* 'o' -> */, +/* pos 0292: 313 */ 0xE4 /* 'd' -> */, +/* pos 0293: 314 */ 0x00, 0x24 /* - terminal marker 36 - */, +/* pos 0295: 315 */ 0xE1 /* 'a' -> */, +/* pos 0296: 316 */ 0xF4 /* 't' -> */, +/* pos 0297: 317 */ 0xE8 /* 'h' -> */, +/* pos 0298: 318 */ 0x00, 0x25 /* - terminal marker 37 - */, +/* pos 029a: 319 */ 0x63 /* 'c' */, 0x07, 0x00 /* (to 0x02A1 state 320) */, + 0x74 /* 't' */, 0x0A, 0x00 /* (to 0x02A7 state 325) */, + 0x08, /* fail */ +/* pos 02a1: 320 */ 0xE8 /* 'h' -> */, +/* pos 02a2: 321 */ 0xE5 /* 'e' -> */, +/* pos 02a3: 322 */ 0xED /* 'm' -> */, +/* pos 02a4: 323 */ 0xE5 /* 'e' -> */, +/* pos 02a5: 324 */ 0x00, 0x26 /* - terminal marker 38 - */, +/* pos 02a7: 325 */ 0xE1 /* 'a' -> */, +/* pos 02a8: 326 */ 0xF4 /* 't' -> */, +/* pos 02a9: 327 */ 0xF5 /* 'u' -> */, +/* pos 02aa: 328 */ 0xF3 /* 's' -> */, +/* pos 02ab: 329 */ 0x00, 0x27 /* - terminal marker 39 - */, +/* pos 02ad: 330 */ 0xE8 /* 'h' -> */, +/* pos 02ae: 331 */ 0xE1 /* 'a' -> */, +/* pos 02af: 332 */ 0xF2 /* 'r' -> */, +/* pos 02b0: 333 */ 0xF3 /* 's' -> */, +/* pos 02b1: 334 */ 0xE5 /* 'e' -> */, +/* pos 02b2: 335 */ 0xF4 /* 't' -> */, +/* pos 02b3: 336 */ 0xBA /* ':' -> */, +/* pos 02b4: 337 */ 0x00, 0x28 /* - terminal marker 40 - */, +/* pos 02b6: 338 */ 0xE1 /* 'a' -> */, +/* pos 02b7: 339 */ 0xEE /* 'n' -> */, +/* pos 02b8: 340 */ 0xE7 /* 'g' -> */, +/* pos 02b9: 341 */ 0xE5 /* 'e' -> */, +/* pos 02ba: 342 */ 0xF3 /* 's' -> */, +/* pos 02bb: 343 */ 0xBA /* ':' -> */, +/* pos 02bc: 344 */ 0x00, 0x29 /* - terminal marker 41 - */, +/* pos 02be: 345 */ 0xEC /* 'l' -> */, +/* pos 02bf: 346 */ 0xEC /* 'l' -> */, +/* pos 02c0: 347 */ 0xEF /* 'o' -> */, +/* pos 02c1: 348 */ 0xF7 /* 'w' -> */, +/* pos 02c2: 349 */ 0xAD /* '-' -> */, +/* pos 02c3: 350 */ 0xEF /* 'o' -> */, +/* pos 02c4: 351 */ 0xF2 /* 'r' -> */, +/* pos 02c5: 352 */ 0xE9 /* 'i' -> */, +/* pos 02c6: 353 */ 0xE7 /* 'g' -> */, +/* pos 02c7: 354 */ 0xE9 /* 'i' -> */, +/* pos 02c8: 355 */ 0xEE /* 'n' -> */, +/* pos 02c9: 356 */ 0xBA /* ':' -> */, +/* pos 02ca: 357 */ 0x00, 0x2A /* - terminal marker 42 - */, +/* pos 02cc: 358 */ 0xE5 /* 'e' -> */, +/* pos 02cd: 359 */ 0xBA /* ':' -> */, +/* pos 02ce: 360 */ 0x00, 0x2B /* - terminal marker 43 - */, +/* pos 02d0: 361 */ 0xEC /* 'l' -> */, +/* pos 02d1: 362 */ 0xEF /* 'o' -> */, +/* pos 02d2: 363 */ 0xF7 /* 'w' -> */, +/* pos 02d3: 364 */ 0xBA /* ':' -> */, +/* pos 02d4: 365 */ 0x00, 0x2C /* - terminal marker 44 - */, +/* pos 02d6: 366 */ 0xE9 /* 'i' -> */, +/* pos 02d7: 367 */ 0xF3 /* 's' -> */, +/* pos 02d8: 368 */ 0xF0 /* 'p' -> */, +/* pos 02d9: 369 */ 0xEF /* 'o' -> */, +/* pos 02da: 370 */ 0xF3 /* 's' -> */, +/* pos 02db: 371 */ 0xE9 /* 'i' -> */, +/* pos 02dc: 372 */ 0xF4 /* 't' -> */, +/* pos 02dd: 373 */ 0xE9 /* 'i' -> */, +/* pos 02de: 374 */ 0xEF /* 'o' -> */, +/* pos 02df: 375 */ 0xEE /* 'n' -> */, +/* pos 02e0: 376 */ 0xBA /* ':' -> */, +/* pos 02e1: 377 */ 0x00, 0x2D /* - terminal marker 45 - */, +/* pos 02e3: 378 */ 0xEE /* 'n' -> */, +/* pos 02e4: 379 */ 0xE3 /* 'c' -> */, +/* pos 02e5: 380 */ 0xEF /* 'o' -> */, +/* pos 02e6: 381 */ 0xE4 /* 'd' -> */, +/* pos 02e7: 382 */ 0xE9 /* 'i' -> */, +/* pos 02e8: 383 */ 0xEE /* 'n' -> */, +/* pos 02e9: 384 */ 0xE7 /* 'g' -> */, +/* pos 02ea: 385 */ 0xBA /* ':' -> */, +/* pos 02eb: 386 */ 0x00, 0x2E /* - terminal marker 46 - */, +/* pos 02ed: 387 */ 0xEE /* 'n' -> */, +/* pos 02ee: 388 */ 0xE7 /* 'g' -> */, +/* pos 02ef: 389 */ 0xF5 /* 'u' -> */, +/* pos 02f0: 390 */ 0xE1 /* 'a' -> */, +/* pos 02f1: 391 */ 0xE7 /* 'g' -> */, +/* pos 02f2: 392 */ 0xE5 /* 'e' -> */, +/* pos 02f3: 393 */ 0xBA /* ':' -> */, +/* pos 02f4: 394 */ 0x00, 0x2F /* - terminal marker 47 - */, +/* pos 02f6: 395 */ 0xE3 /* 'c' -> */, +/* pos 02f7: 396 */ 0xE1 /* 'a' -> */, +/* pos 02f8: 397 */ 0xF4 /* 't' -> */, +/* pos 02f9: 398 */ 0xE9 /* 'i' -> */, +/* pos 02fa: 399 */ 0xEF /* 'o' -> */, +/* pos 02fb: 400 */ 0xEE /* 'n' -> */, +/* pos 02fc: 401 */ 0xBA /* ':' -> */, +/* pos 02fd: 402 */ 0x00, 0x30 /* - terminal marker 48 - */, +/* pos 02ff: 403 */ 0xE1 /* 'a' -> */, +/* pos 0300: 404 */ 0xEE /* 'n' -> */, +/* pos 0301: 405 */ 0xE7 /* 'g' -> */, +/* pos 0302: 406 */ 0xE5 /* 'e' -> */, +/* pos 0303: 407 */ 0xBA /* ':' -> */, +/* pos 0304: 408 */ 0x00, 0x31 /* - terminal marker 49 - */, +/* pos 0306: 409 */ 0x74 /* 't' */, 0x07, 0x00 /* (to 0x030D state 410) */, + 0x78 /* 'x' */, 0x09, 0x00 /* (to 0x0312 state 414) */, + 0x08, /* fail */ +/* pos 030d: 410 */ 0xE1 /* 'a' -> */, +/* pos 030e: 411 */ 0xE7 /* 'g' -> */, +/* pos 030f: 412 */ 0xBA /* ':' -> */, +/* pos 0310: 413 */ 0x00, 0x32 /* - terminal marker 50 - */, +/* pos 0312: 414 */ 0xF0 /* 'p' -> */, +/* pos 0313: 415 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x031A state 416) */, + 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x031F state 420) */, + 0x08, /* fail */ +/* pos 031a: 416 */ 0xE3 /* 'c' -> */, +/* pos 031b: 417 */ 0xF4 /* 't' -> */, +/* pos 031c: 418 */ 0xBA /* ':' -> */, +/* pos 031d: 419 */ 0x00, 0x33 /* - terminal marker 51 - */, +/* pos 031f: 420 */ 0xF2 /* 'r' -> */, +/* pos 0320: 421 */ 0xE5 /* 'e' -> */, +/* pos 0321: 422 */ 0xF3 /* 's' -> */, +/* pos 0322: 423 */ 0xBA /* ':' -> */, +/* pos 0323: 424 */ 0x00, 0x34 /* - terminal marker 52 - */, +/* pos 0325: 425 */ 0xF2 /* 'r' -> */, +/* pos 0326: 426 */ 0xEF /* 'o' -> */, +/* pos 0327: 427 */ 0xED /* 'm' -> */, +/* pos 0328: 428 */ 0xBA /* ':' -> */, +/* pos 0329: 429 */ 0x00, 0x35 /* - terminal marker 53 - */, +/* pos 032b: 430 */ 0xF4 /* 't' -> */, +/* pos 032c: 431 */ 0xE3 /* 'c' -> */, +/* pos 032d: 432 */ 0xE8 /* 'h' -> */, +/* pos 032e: 433 */ 0xBA /* ':' -> */, +/* pos 032f: 434 */ 0x00, 0x36 /* - terminal marker 54 - */, +/* pos 0331: 435 */ 0xE1 /* 'a' -> */, +/* pos 0332: 436 */ 0xEE /* 'n' -> */, +/* pos 0333: 437 */ 0xE7 /* 'g' -> */, +/* pos 0334: 438 */ 0xE5 /* 'e' -> */, +/* pos 0335: 439 */ 0xBA /* ':' -> */, +/* pos 0336: 440 */ 0x00, 0x37 /* - terminal marker 55 - */, +/* pos 0338: 441 */ 0xEE /* 'n' -> */, +/* pos 0339: 442 */ 0xED /* 'm' -> */, +/* pos 033a: 443 */ 0xEF /* 'o' -> */, +/* pos 033b: 444 */ 0xE4 /* 'd' -> */, +/* pos 033c: 445 */ 0xE9 /* 'i' -> */, +/* pos 033d: 446 */ 0xE6 /* 'f' -> */, +/* pos 033e: 447 */ 0xE9 /* 'i' -> */, +/* pos 033f: 448 */ 0xE5 /* 'e' -> */, +/* pos 0340: 449 */ 0xE4 /* 'd' -> */, +/* pos 0341: 450 */ 0xAD /* '-' -> */, +/* pos 0342: 451 */ 0xF3 /* 's' -> */, +/* pos 0343: 452 */ 0xE9 /* 'i' -> */, +/* pos 0344: 453 */ 0xEE /* 'n' -> */, +/* pos 0345: 454 */ 0xE3 /* 'c' -> */, +/* pos 0346: 455 */ 0xE5 /* 'e' -> */, +/* pos 0347: 456 */ 0xBA /* ':' -> */, +/* pos 0348: 457 */ 0x00, 0x38 /* - terminal marker 56 - */, +/* pos 034a: 458 */ 0x61 /* 'a' */, 0x0A, 0x00 /* (to 0x0354 state 459) */, + 0x69 /* 'i' */, 0x15, 0x00 /* (to 0x0362 state 472) */, + 0x6F /* 'o' */, 0x17, 0x00 /* (to 0x0367 state 476) */, + 0x08, /* fail */ +/* pos 0354: 459 */ 0xF3 /* 's' -> */, +/* pos 0355: 460 */ 0xF4 /* 't' -> */, +/* pos 0356: 461 */ 0xAD /* '-' -> */, +/* pos 0357: 462 */ 0xED /* 'm' -> */, +/* pos 0358: 463 */ 0xEF /* 'o' -> */, +/* pos 0359: 464 */ 0xE4 /* 'd' -> */, +/* pos 035a: 465 */ 0xE9 /* 'i' -> */, +/* pos 035b: 466 */ 0xE6 /* 'f' -> */, +/* pos 035c: 467 */ 0xE9 /* 'i' -> */, +/* pos 035d: 468 */ 0xE5 /* 'e' -> */, +/* pos 035e: 469 */ 0xE4 /* 'd' -> */, +/* pos 035f: 470 */ 0xBA /* ':' -> */, +/* pos 0360: 471 */ 0x00, 0x39 /* - terminal marker 57 - */, +/* pos 0362: 472 */ 0xEE /* 'n' -> */, +/* pos 0363: 473 */ 0xEB /* 'k' -> */, +/* pos 0364: 474 */ 0xBA /* ':' -> */, +/* pos 0365: 475 */ 0x00, 0x3A /* - terminal marker 58 - */, +/* pos 0367: 476 */ 0xE3 /* 'c' -> */, +/* pos 0368: 477 */ 0xE1 /* 'a' -> */, +/* pos 0369: 478 */ 0xF4 /* 't' -> */, +/* pos 036a: 479 */ 0xE9 /* 'i' -> */, +/* pos 036b: 480 */ 0xEF /* 'o' -> */, +/* pos 036c: 481 */ 0xEE /* 'n' -> */, +/* pos 036d: 482 */ 0xBA /* ':' -> */, +/* pos 036e: 483 */ 0x00, 0x3B /* - terminal marker 59 - */, +/* pos 0370: 484 */ 0xE1 /* 'a' -> */, +/* pos 0371: 485 */ 0xF8 /* 'x' -> */, +/* pos 0372: 486 */ 0xAD /* '-' -> */, +/* pos 0373: 487 */ 0xE6 /* 'f' -> */, +/* pos 0374: 488 */ 0xEF /* 'o' -> */, +/* pos 0375: 489 */ 0xF2 /* 'r' -> */, +/* pos 0376: 490 */ 0xF7 /* 'w' -> */, +/* pos 0377: 491 */ 0xE1 /* 'a' -> */, +/* pos 0378: 492 */ 0xF2 /* 'r' -> */, +/* pos 0379: 493 */ 0xE4 /* 'd' -> */, +/* pos 037a: 494 */ 0xF3 /* 's' -> */, +/* pos 037b: 495 */ 0xBA /* ':' -> */, +/* pos 037c: 496 */ 0x00, 0x3C /* - terminal marker 60 - */, +/* pos 037e: 497 */ 0xF8 /* 'x' -> */, +/* pos 037f: 498 */ 0xF9 /* 'y' -> */, +/* pos 0380: 499 */ 0x2D /* '-' */, 0x07, 0x00 /* (to 0x0387 state 500) */, + 0x20 /* ' ' */, 0xBB, 0x00 /* (to 0x043E state 649) */, + 0x08, /* fail */ +/* pos 0387: 500 */ 0xE1 /* 'a' -> */, +/* pos 0388: 501 */ 0xF5 /* 'u' -> */, +/* pos 0389: 502 */ 0xF4 /* 't' -> */, +/* pos 038a: 503 */ 0xE8 /* 'h' -> */, +/* pos 038b: 504 */ 0x65 /* 'e' */, 0x07, 0x00 /* (to 0x0392 state 505) */, + 0x6F /* 'o' */, 0x0E, 0x00 /* (to 0x039C state 514) */, + 0x08, /* fail */ +/* pos 0392: 505 */ 0xEE /* 'n' -> */, +/* pos 0393: 506 */ 0xF4 /* 't' -> */, +/* pos 0394: 507 */ 0xE9 /* 'i' -> */, +/* pos 0395: 508 */ 0xE3 /* 'c' -> */, +/* pos 0396: 509 */ 0xE1 /* 'a' -> */, +/* pos 0397: 510 */ 0xF4 /* 't' -> */, +/* pos 0398: 511 */ 0xE5 /* 'e' -> */, +/* pos 0399: 512 */ 0xBA /* ':' -> */, +/* pos 039a: 513 */ 0x00, 0x3D /* - terminal marker 61 - */, +/* pos 039c: 514 */ 0xF2 /* 'r' -> */, +/* pos 039d: 515 */ 0xE9 /* 'i' -> */, +/* pos 039e: 516 */ 0xFA /* 'z' -> */, +/* pos 039f: 517 */ 0xE1 /* 'a' -> */, +/* pos 03a0: 518 */ 0xF4 /* 't' -> */, +/* pos 03a1: 519 */ 0xE9 /* 'i' -> */, +/* pos 03a2: 520 */ 0xEF /* 'o' -> */, +/* pos 03a3: 521 */ 0xEE /* 'n' -> */, +/* pos 03a4: 522 */ 0xBA /* ':' -> */, +/* pos 03a5: 523 */ 0x00, 0x3E /* - terminal marker 62 - */, +/* pos 03a7: 524 */ 0xE5 /* 'e' -> */, +/* pos 03a8: 525 */ 0xF3 /* 's' -> */, +/* pos 03a9: 526 */ 0xE8 /* 'h' -> */, +/* pos 03aa: 527 */ 0xBA /* ':' -> */, +/* pos 03ab: 528 */ 0x00, 0x3F /* - terminal marker 63 - */, +/* pos 03ad: 529 */ 0xF2 /* 'r' -> */, +/* pos 03ae: 530 */ 0xF9 /* 'y' -> */, +/* pos 03af: 531 */ 0xAD /* '-' -> */, +/* pos 03b0: 532 */ 0xE1 /* 'a' -> */, +/* pos 03b1: 533 */ 0xE6 /* 'f' -> */, +/* pos 03b2: 534 */ 0xF4 /* 't' -> */, +/* pos 03b3: 535 */ 0xE5 /* 'e' -> */, +/* pos 03b4: 536 */ 0xF2 /* 'r' -> */, +/* pos 03b5: 537 */ 0xBA /* ':' -> */, +/* pos 03b6: 538 */ 0x00, 0x40 /* - terminal marker 64 - */, +/* pos 03b8: 539 */ 0xF6 /* 'v' -> */, +/* pos 03b9: 540 */ 0xE5 /* 'e' -> */, +/* pos 03ba: 541 */ 0xF2 /* 'r' -> */, +/* pos 03bb: 542 */ 0xBA /* ':' -> */, +/* pos 03bc: 543 */ 0x00, 0x41 /* - terminal marker 65 - */, +/* pos 03be: 544 */ 0xAD /* '-' -> */, +/* pos 03bf: 545 */ 0xE3 /* 'c' -> */, +/* pos 03c0: 546 */ 0xEF /* 'o' -> */, +/* pos 03c1: 547 */ 0xEF /* 'o' -> */, +/* pos 03c2: 548 */ 0xEB /* 'k' -> */, +/* pos 03c3: 549 */ 0xE9 /* 'i' -> */, +/* pos 03c4: 550 */ 0xE5 /* 'e' -> */, +/* pos 03c5: 551 */ 0xBA /* ':' -> */, +/* pos 03c6: 552 */ 0x00, 0x42 /* - terminal marker 66 - */, +/* pos 03c8: 553 */ 0xF2 /* 'r' -> */, +/* pos 03c9: 554 */ 0xE9 /* 'i' -> */, +/* pos 03ca: 555 */ 0xE3 /* 'c' -> */, +/* pos 03cb: 556 */ 0xF4 /* 't' -> */, +/* pos 03cc: 557 */ 0xAD /* '-' -> */, +/* pos 03cd: 558 */ 0xF4 /* 't' -> */, +/* pos 03ce: 559 */ 0xF2 /* 'r' -> */, +/* pos 03cf: 560 */ 0xE1 /* 'a' -> */, +/* pos 03d0: 561 */ 0xEE /* 'n' -> */, +/* pos 03d1: 562 */ 0xF3 /* 's' -> */, +/* pos 03d2: 563 */ 0xF0 /* 'p' -> */, +/* pos 03d3: 564 */ 0xEF /* 'o' -> */, +/* pos 03d4: 565 */ 0xF2 /* 'r' -> */, +/* pos 03d5: 566 */ 0xF4 /* 't' -> */, +/* pos 03d6: 567 */ 0xAD /* '-' -> */, +/* pos 03d7: 568 */ 0xF3 /* 's' -> */, +/* pos 03d8: 569 */ 0xE5 /* 'e' -> */, +/* pos 03d9: 570 */ 0xE3 /* 'c' -> */, +/* pos 03da: 571 */ 0xF5 /* 'u' -> */, +/* pos 03db: 572 */ 0xF2 /* 'r' -> */, +/* pos 03dc: 573 */ 0xE9 /* 'i' -> */, +/* pos 03dd: 574 */ 0xF4 /* 't' -> */, +/* pos 03de: 575 */ 0xF9 /* 'y' -> */, +/* pos 03df: 576 */ 0xBA /* ':' -> */, +/* pos 03e0: 577 */ 0x00, 0x43 /* - terminal marker 67 - */, +/* pos 03e2: 578 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x03E9 state 579) */, + 0x65 /* 'e' */, 0x84, 0x00 /* (to 0x0469 state 680) */, + 0x08, /* fail */ +/* pos 03e9: 579 */ 0xE1 /* 'a' -> */, +/* pos 03ea: 580 */ 0xEE /* 'n' -> */, +/* pos 03eb: 581 */ 0xF3 /* 's' -> */, +/* pos 03ec: 582 */ 0xE6 /* 'f' -> */, +/* pos 03ed: 583 */ 0xE5 /* 'e' -> */, +/* pos 03ee: 584 */ 0xF2 /* 'r' -> */, +/* pos 03ef: 585 */ 0xAD /* '-' -> */, +/* pos 03f0: 586 */ 0xE5 /* 'e' -> */, +/* pos 03f1: 587 */ 0xEE /* 'n' -> */, +/* pos 03f2: 588 */ 0xE3 /* 'c' -> */, +/* pos 03f3: 589 */ 0xEF /* 'o' -> */, +/* pos 03f4: 590 */ 0xE4 /* 'd' -> */, +/* pos 03f5: 591 */ 0xE9 /* 'i' -> */, +/* pos 03f6: 592 */ 0xEE /* 'n' -> */, +/* pos 03f7: 593 */ 0xE7 /* 'g' -> */, +/* pos 03f8: 594 */ 0xBA /* ':' -> */, +/* pos 03f9: 595 */ 0x00, 0x44 /* - terminal marker 68 - */, +/* pos 03fb: 596 */ 0xE5 /* 'e' -> */, +/* pos 03fc: 597 */ 0xF2 /* 'r' -> */, +/* pos 03fd: 598 */ 0xAD /* '-' -> */, +/* pos 03fe: 599 */ 0xE1 /* 'a' -> */, +/* pos 03ff: 600 */ 0xE7 /* 'g' -> */, +/* pos 0400: 601 */ 0xE5 /* 'e' -> */, +/* pos 0401: 602 */ 0xEE /* 'n' -> */, +/* pos 0402: 603 */ 0xF4 /* 't' -> */, +/* pos 0403: 604 */ 0xBA /* ':' -> */, +/* pos 0404: 605 */ 0x00, 0x45 /* - terminal marker 69 - */, +/* pos 0406: 606 */ 0x61 /* 'a' */, 0x07, 0x00 /* (to 0x040D state 607) */, + 0x69 /* 'i' */, 0x09, 0x00 /* (to 0x0412 state 611) */, + 0x08, /* fail */ +/* pos 040d: 607 */ 0xF2 /* 'r' -> */, +/* pos 040e: 608 */ 0xF9 /* 'y' -> */, +/* pos 040f: 609 */ 0xBA /* ':' -> */, +/* pos 0410: 610 */ 0x00, 0x46 /* - terminal marker 70 - */, +/* pos 0412: 611 */ 0xE1 /* 'a' -> */, +/* pos 0413: 612 */ 0xBA /* ':' -> */, +/* pos 0414: 613 */ 0x00, 0x47 /* - terminal marker 71 - */, +/* pos 0416: 614 */ 0xF7 /* 'w' -> */, +/* pos 0417: 615 */ 0xF7 /* 'w' -> */, +/* pos 0418: 616 */ 0xAD /* '-' -> */, +/* pos 0419: 617 */ 0xE1 /* 'a' -> */, +/* pos 041a: 618 */ 0xF5 /* 'u' -> */, +/* pos 041b: 619 */ 0xF4 /* 't' -> */, +/* pos 041c: 620 */ 0xE8 /* 'h' -> */, +/* pos 041d: 621 */ 0xE5 /* 'e' -> */, +/* pos 041e: 622 */ 0xEE /* 'n' -> */, +/* pos 041f: 623 */ 0xF4 /* 't' -> */, +/* pos 0420: 624 */ 0xE9 /* 'i' -> */, +/* pos 0421: 625 */ 0xE3 /* 'c' -> */, +/* pos 0422: 626 */ 0xE1 /* 'a' -> */, +/* pos 0423: 627 */ 0xF4 /* 't' -> */, +/* pos 0424: 628 */ 0xE5 /* 'e' -> */, +/* pos 0425: 629 */ 0xBA /* ':' -> */, +/* pos 0426: 630 */ 0x00, 0x48 /* - terminal marker 72 - */, +/* pos 0428: 631 */ 0xF4 /* 't' -> */, +/* pos 0429: 632 */ 0xE3 /* 'c' -> */, +/* pos 042a: 633 */ 0xE8 /* 'h' -> */, +/* pos 042b: 634 */ 0x00, 0x49 /* - terminal marker 73 - */, +/* pos 042d: 635 */ 0xF4 /* 't' -> */, +/* pos 042e: 636 */ 0x00, 0x4A /* - terminal marker 74 - */, +/* pos 0430: 637 */ 0xEC /* 'l' -> */, +/* pos 0431: 638 */ 0xE5 /* 'e' -> */, +/* pos 0432: 639 */ 0xF4 /* 't' -> */, +/* pos 0433: 640 */ 0xE5 /* 'e' -> */, +/* pos 0434: 641 */ 0x00, 0x4B /* - terminal marker 75 - */, +/* pos 0436: 642 */ 0xE9 /* 'i' -> */, +/* pos 0437: 643 */ 0xAD /* '-' -> */, +/* pos 0438: 644 */ 0xE1 /* 'a' -> */, +/* pos 0439: 645 */ 0xF2 /* 'r' -> */, +/* pos 043a: 646 */ 0xE7 /* 'g' -> */, +/* pos 043b: 647 */ 0xF3 /* 's' -> */, +/* pos 043c: 648 */ 0x00, 0x4C /* - terminal marker 76 - */, +/* pos 043e: 649 */ 0x00, 0x4D /* - terminal marker 77 - */, +/* pos 0440: 650 */ 0xAD /* '-' -> */, +/* pos 0441: 651 */ 0x72 /* 'r' */, 0x07, 0x00 /* (to 0x0448 state 652) */, + 0x66 /* 'f' */, 0x10, 0x00 /* (to 0x0454 state 662) */, + 0x08, /* fail */ +/* pos 0448: 652 */ 0xE5 /* 'e' -> */, +/* pos 0449: 653 */ 0xE1 /* 'a' -> */, +/* pos 044a: 654 */ 0xEC /* 'l' -> */, +/* pos 044b: 655 */ 0xAD /* '-' -> */, +/* pos 044c: 656 */ 0xE9 /* 'i' -> */, +/* pos 044d: 657 */ 0xF0 /* 'p' -> */, +/* pos 044e: 658 */ 0xBA /* ':' -> */, +/* pos 044f: 659 */ 0x00, 0x4E /* - terminal marker 78 - */, +/* pos 0451: 660 */ 0xA0 /* ' ' -> */, +/* pos 0452: 661 */ 0x00, 0x4F /* - terminal marker 79 - */, +/* pos 0454: 662 */ 0xEF /* 'o' -> */, +/* pos 0455: 663 */ 0xF2 /* 'r' -> */, +/* pos 0456: 664 */ 0xF7 /* 'w' -> */, +/* pos 0457: 665 */ 0xE1 /* 'a' -> */, +/* pos 0458: 666 */ 0xF2 /* 'r' -> */, +/* pos 0459: 667 */ 0xE4 /* 'd' -> */, +/* pos 045a: 668 */ 0xE5 /* 'e' -> */, +/* pos 045b: 669 */ 0xE4 /* 'd' -> */, +/* pos 045c: 670 */ 0xAD /* '-' -> */, +/* pos 045d: 671 */ 0xE6 /* 'f' -> */, +/* pos 045e: 672 */ 0xEF /* 'o' -> */, +/* pos 045f: 673 */ 0xF2 /* 'r' -> */, +/* pos 0460: 674 */ 0x00, 0x50 /* - terminal marker 80 - */, +/* pos 0462: 675 */ 0x00, 0x51 /* - terminal marker 81 - */, +/* pos 0464: 676 */ 0xE1 /* 'a' -> */, +/* pos 0465: 677 */ 0xE4 /* 'd' -> */, +/* pos 0466: 678 */ 0xA0 /* ' ' -> */, +/* pos 0467: 679 */ 0x00, 0x52 /* - terminal marker 82 - */, +/* pos 0469: 680 */ 0xBA /* ':' -> */, +/* pos 046a: 681 */ 0x00, 0x53 /* - terminal marker 83 - */, +/* total size 1132 bytes */ diff --git a/thirdparty/lws/libwebsockets.c b/thirdparty/lws/libwebsockets.c new file mode 100644 index 0000000000..50f975d21e --- /dev/null +++ b/thirdparty/lws/libwebsockets.c @@ -0,0 +1,3048 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +#ifdef LWS_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#ifdef LWS_WITH_IPV6 +#if defined(WIN32) || defined(_WIN32) +#include <Iphlpapi.h> +#else +#include <net/if.h> +#endif +#endif + +int log_level = LLL_ERR | LLL_WARN | LLL_NOTICE; +static void (*lwsl_emit)(int level, const char *line) +#ifndef LWS_PLAT_OPTEE + = lwsl_emit_stderr +#endif + ; +#ifndef LWS_PLAT_OPTEE +static const char * const log_level_names[] = { + "ERR", + "WARN", + "NOTICE", + "INFO", + "DEBUG", + "PARSER", + "HEADER", + "EXTENSION", + "CLIENT", + "LATENCY", + "USER", + "?", + "?" +}; +#endif + +void +lws_free_wsi(struct lws *wsi) +{ + struct lws_context_per_thread *pt; + struct allocated_headers *ah; + + if (!wsi) + return; + + pt = &wsi->context->pt[(int)wsi->tsi]; + + /* + * Protocol user data may be allocated either internally by lws + * or by specified the user. We should only free what we allocated. + */ + if (wsi->protocol && wsi->protocol->per_session_data_size && + wsi->user_space && !wsi->user_space_externally_allocated) + lws_free(wsi->user_space); + + lws_free_set_NULL(wsi->rxflow_buffer); + lws_free_set_NULL(wsi->trunc_alloc); + + /* we may not have an ah, but may be on the waiting list... */ + lwsl_info("ah det due to close\n"); + /* we're closing, losing some rx is OK */ + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 0); + + if (wsi->vhost->lserv_wsi == wsi) + wsi->vhost->lserv_wsi = NULL; + + lws_pt_lock(pt); + ah = pt->ah_list; + while (ah) { + if (ah->in_use && ah->wsi == wsi) { + lwsl_err("%s: ah leak: wsi %p\n", __func__, wsi); + ah->in_use = 0; + ah->wsi = NULL; + pt->ah_count_in_use--; + break; + } + ah = ah->next; + } + +#if defined(LWS_WITH_PEER_LIMITS) + lws_peer_track_wsi_close(wsi->context, wsi->peer); + wsi->peer = NULL; +#endif + +#if defined(LWS_WITH_HTTP2) + if (wsi->upgraded_to_http2 || wsi->http2_substream) { + lws_hpack_destroy_dynamic_header(wsi); + + if (wsi->u.h2.h2n) + lws_free_set_NULL(wsi->u.h2.h2n); + } +#endif + + lws_pt_unlock(pt); + + /* since we will destroy the wsi, make absolutely sure now */ + + lws_ssl_remove_wsi_from_buffered_list(wsi); + lws_remove_from_timeout_list(wsi); + + wsi->context->count_wsi_allocated--; + lwsl_debug("%s: %p, remaining wsi %d\n", __func__, wsi, + wsi->context->count_wsi_allocated); + + lws_free(wsi); +} + +void +lws_remove_from_timeout_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + if (!wsi->timeout_list_prev) /* ie, not part of the list */ + return; + + lws_pt_lock(pt); + /* if we have a next guy, set his prev to our prev */ + if (wsi->timeout_list) + wsi->timeout_list->timeout_list_prev = wsi->timeout_list_prev; + /* set our prev guy to our next guy instead of us */ + *wsi->timeout_list_prev = wsi->timeout_list; + + /* we're out of the list, we should not point anywhere any more */ + wsi->timeout_list_prev = NULL; + wsi->timeout_list = NULL; + lws_pt_unlock(pt); +} + +LWS_VISIBLE void +lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + time_t now; + + if (secs == LWS_TO_KILL_SYNC) { + lws_remove_from_timeout_list(wsi); + lwsl_debug("synchronously killing %p\n", wsi); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + return; + } + + lws_pt_lock(pt); + + time(&now); + + if (reason && !wsi->timeout_list_prev) { + /* our next guy is current first guy */ + wsi->timeout_list = pt->timeout_list; + /* if there is a next guy, set his prev ptr to our next ptr */ + if (wsi->timeout_list) + wsi->timeout_list->timeout_list_prev = &wsi->timeout_list; + /* our prev ptr is first ptr */ + wsi->timeout_list_prev = &pt->timeout_list; + /* set the first guy to be us */ + *wsi->timeout_list_prev = wsi; + } + + lwsl_debug("%s: %p: %d secs\n", __func__, wsi, secs); + wsi->pending_timeout_limit = now + secs; + wsi->pending_timeout = reason; + + lws_pt_unlock(pt); + + if (!reason) + lws_remove_from_timeout_list(wsi); +} + +static void +lws_remove_child_from_any_parent(struct lws *wsi) +{ + struct lws **pwsi; + int seen = 0; + + if (!wsi->parent) + return; + + /* detach ourselves from parent's child list */ + pwsi = &wsi->parent->child_list; + while (*pwsi) { + if (*pwsi == wsi) { + lwsl_info("%s: detach %p from parent %p\n", __func__, + wsi, wsi->parent); + + if (wsi->parent->protocol) + wsi->parent->protocol->callback(wsi, + LWS_CALLBACK_CHILD_CLOSING, + wsi->parent->user_space, wsi, 0); + + *pwsi = wsi->sibling_list; + seen = 1; + break; + } + pwsi = &(*pwsi)->sibling_list; + } + if (!seen) + lwsl_err("%s: failed to detach from parent\n", __func__); + + wsi->parent = NULL; +} + +int +lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p) +{ +// if (wsi->protocol == p) +// return 0; + const struct lws_protocols *vp = wsi->vhost->protocols, *vpo; + + if (wsi->protocol) + wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_DROP_PROTOCOL, + wsi->user_space, NULL, 0); + if (!wsi->user_space_externally_allocated) + lws_free_set_NULL(wsi->user_space); + + lws_same_vh_protocol_remove(wsi); + + wsi->protocol = p; + if (!p) + return 0; + + if (lws_ensure_user_space(wsi)) + return 1; + + if (p > vp && p < &vp[wsi->vhost->count_protocols]) + lws_same_vh_protocol_insert(wsi, p - vp); + else { + int n = wsi->vhost->count_protocols; + int hit = 0; + + vpo = vp; + + while (n--) { + if (p->name && vp->name && !strcmp(p->name, vp->name)) { + hit = 1; + lws_same_vh_protocol_insert(wsi, vp - vpo); + break; + } + vp++; + } + if (!hit) + lwsl_err("%s: %p is not in vhost '%s' protocols list\n", + __func__, p, wsi->vhost->name); + } + + if (wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_BIND_PROTOCOL, + wsi->user_space, NULL, 0)) + return 1; + + return 0; +} + +void +lws_close_free_wsi(struct lws *wsi, enum lws_close_status reason) +{ + struct lws_context_per_thread *pt; + struct lws *wsi1, *wsi2; + struct lws_context *context; + struct lws_tokens eff_buf; + int n, m, ret; + + lwsl_debug("%s: %p\n", __func__, wsi); + + if (!wsi) + return; + + lws_access_log(wsi); +#if defined(LWS_WITH_ESP8266) + if (wsi->premature_rx) + lws_free(wsi->premature_rx); + + if (wsi->pending_send_completion && !wsi->close_is_pending_send_completion) { + lwsl_notice("delaying close\n"); + wsi->close_is_pending_send_completion = 1; + return; + } +#endif + + /* we're closing, losing some rx is OK */ + lws_header_table_force_to_detachable_state(wsi); + + context = wsi->context; + pt = &context->pt[(int)wsi->tsi]; + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_CLOSE, 1); + + /* if we have children, close them first */ + if (wsi->child_list) { + wsi2 = wsi->child_list; + while (wsi2) { + wsi1 = wsi2->sibling_list; + wsi2->parent = NULL; + /* stop it doing shutdown processing */ + wsi2->socket_is_permanently_unusable = 1; + lws_close_free_wsi(wsi2, reason); + wsi2 = wsi1; + } + wsi->child_list = NULL; + } + +#if defined(LWS_WITH_HTTP2) + + if (wsi->u.h2.parent_wsi) { + lwsl_info(" wsi: %p, his parent %p: siblings:\n", wsi, wsi->u.h2.parent_wsi); + lws_start_foreach_llp(struct lws **, w, wsi->u.h2.parent_wsi->u.h2.child_list) { + lwsl_info(" \\---- child %p\n", *w); + } lws_end_foreach_llp(w, u.h2.sibling_list); + } + + if (wsi->upgraded_to_http2 || wsi->http2_substream) { + lwsl_info("closing %p: parent %p\n", wsi, wsi->u.h2.parent_wsi); + + if (wsi->u.h2.child_list) { + lwsl_info(" parent %p: closing children: list:\n", wsi); + lws_start_foreach_llp(struct lws **, w, wsi->u.h2.child_list) { + lwsl_info(" \\---- child %p\n", *w); + } lws_end_foreach_llp(w, u.h2.sibling_list); + /* trigger closing of all of our http2 children first */ + lws_start_foreach_llp(struct lws **, w, wsi->u.h2.child_list) { + lwsl_info(" closing child %p\n", *w); + /* disconnect from siblings */ + wsi2 = (*w)->u.h2.sibling_list; + (*w)->u.h2.sibling_list = NULL; + (*w)->socket_is_permanently_unusable = 1; + lws_close_free_wsi(*w, reason); + *w = wsi2; + continue; + } lws_end_foreach_llp(w, u.h2.sibling_list); + } + } + + if (wsi->upgraded_to_http2) { + /* remove pps */ + struct lws_h2_protocol_send *w = wsi->u.h2.h2n->pps, *w1; + while (w) { + w1 = wsi->u.h2.h2n->pps->next; + free(w); + w = w1; + } + wsi->u.h2.h2n->pps = NULL; + } + + if (wsi->http2_substream && wsi->u.h2.parent_wsi) { + lwsl_info(" %p: disentangling from siblings\n", wsi); + lws_start_foreach_llp(struct lws **, w, + wsi->u.h2.parent_wsi->u.h2.child_list) { + /* disconnect from siblings */ + if (*w == wsi) { + wsi2 = (*w)->u.h2.sibling_list; + (*w)->u.h2.sibling_list = NULL; + *w = wsi2; + lwsl_info(" %p disentangled from sibling %p\n", wsi, wsi2); + break; + } + } lws_end_foreach_llp(w, u.h2.sibling_list); + wsi->u.h2.parent_wsi->u.h2.child_count--; + wsi->u.h2.parent_wsi = NULL; + if (wsi->u.h2.pending_status_body) + lws_free_set_NULL(wsi->u.h2.pending_status_body); + } + + if (wsi->upgraded_to_http2 && wsi->u.h2.h2n && + wsi->u.h2.h2n->rx_scratch) + lws_free_set_NULL(wsi->u.h2.h2n->rx_scratch); +#endif + + if (wsi->mode == LWSCM_RAW_FILEDESC) { + lws_remove_child_from_any_parent(wsi); + remove_wsi_socket_from_fds(wsi); + wsi->protocol->callback(wsi, + LWS_CALLBACK_RAW_CLOSE_FILE, + wsi->user_space, NULL, 0); + goto async_close; + } + +#ifdef LWS_WITH_CGI + if (wsi->mode == LWSCM_CGI) { + /* we are not a network connection, but a handler for CGI io */ + if (wsi->parent && wsi->parent->cgi) { + + if (wsi->cgi_channel == LWS_STDOUT) + lws_cgi_remove_and_kill(wsi->parent); + + /* end the binding between us and master */ + wsi->parent->cgi->stdwsi[(int)wsi->cgi_channel] = NULL; + } + wsi->socket_is_permanently_unusable = 1; + + goto just_kill_connection; + } + + if (wsi->cgi) + lws_cgi_remove_and_kill(wsi); +#endif + +#if !defined(LWS_NO_CLIENT) + if (wsi->mode == LWSCM_HTTP_CLIENT || + wsi->mode == LWSCM_WSCL_WAITING_CONNECT || + wsi->mode == LWSCM_WSCL_WAITING_PROXY_REPLY || + wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE || + wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE2 || + wsi->mode == LWSCM_WSCL_WAITING_SSL || + wsi->mode == LWSCM_WSCL_WAITING_SERVER_REPLY || + wsi->mode == LWSCM_WSCL_WAITING_EXTENSION_CONNECT || + wsi->mode == LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY || + wsi->mode == LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY || + wsi->mode == LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY) + if (wsi->u.hdr.stash) + lws_free_set_NULL(wsi->u.hdr.stash); +#endif + + if (wsi->mode == LWSCM_RAW) { + wsi->protocol->callback(wsi, + LWS_CALLBACK_RAW_CLOSE, wsi->user_space, NULL, 0); + wsi->socket_is_permanently_unusable = 1; + goto just_kill_connection; + } + + if ((wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED || + wsi->mode == LWSCM_HTTP2_SERVING) && + wsi->u.http.fop_fd != NULL) { + lws_vfs_file_close(&wsi->u.http.fop_fd); + wsi->vhost->protocols->callback(wsi, + LWS_CALLBACK_CLOSED_HTTP, wsi->user_space, NULL, 0); + wsi->told_user_closed = 1; + } + if (wsi->socket_is_permanently_unusable || + reason == LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY || + wsi->state == LWSS_SHUTDOWN) + goto just_kill_connection; + + wsi->state_pre_close = wsi->state; + + switch (wsi->state_pre_close) { + case LWSS_DEAD_SOCKET: + return; + + /* we tried the polite way... */ + case LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION: + case LWSS_AWAITING_CLOSE_ACK: + goto just_kill_connection; + + case LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE: + if (wsi->trunc_len) { + lws_callback_on_writable(wsi); + return; + } + lwsl_info("%p: end FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi); + goto just_kill_connection; + default: + if (wsi->trunc_len) { + lwsl_info("%p: start FLUSHING_STORED_SEND_BEFORE_CLOSE\n", wsi); + wsi->state = LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE; + lws_set_timeout(wsi, PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE, 5); + return; + } + break; + } + + if (wsi->mode == LWSCM_WSCL_WAITING_CONNECT || + wsi->mode == LWSCM_WSCL_ISSUE_HANDSHAKE) + goto just_kill_connection; + + if (wsi->mode == LWSCM_HTTP_SERVING || + wsi->mode == LWSCM_HTTP2_SERVING) { + if (wsi->user_space) + wsi->vhost->protocols->callback(wsi, + LWS_CALLBACK_HTTP_DROP_PROTOCOL, + wsi->user_space, NULL, 0); + wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP, + wsi->user_space, NULL, 0); + wsi->told_user_closed = 1; + } + + /* + * are his extensions okay with him closing? Eg he might be a mux + * parent and just his ch1 aspect is closing? + */ + + if (lws_ext_cb_active(wsi, LWS_EXT_CB_CHECK_OK_TO_REALLY_CLOSE, NULL, 0) > 0) { + lwsl_ext("extension vetoed close\n"); + return; + } + + /* + * flush any tx pending from extensions, since we may send close packet + * if there are problems with send, just nuke the connection + */ + do { + ret = 0; + eff_buf.token = NULL; + eff_buf.token_len = 0; + + /* show every extension the new incoming data */ + + m = lws_ext_cb_active(wsi, + LWS_EXT_CB_FLUSH_PENDING_TX, &eff_buf, 0); + if (m < 0) { + lwsl_ext("Extension reports fatal error\n"); + goto just_kill_connection; + } + if (m) + /* + * at least one extension told us he has more + * to spill, so we will go around again after + */ + ret = 1; + + /* assuming they left us something to send, send it */ + + if (eff_buf.token_len) + if (lws_issue_raw(wsi, (unsigned char *)eff_buf.token, + eff_buf.token_len) != + eff_buf.token_len) { + lwsl_debug("close: ext spill failed\n"); + goto just_kill_connection; + } + } while (ret); + + /* + * signal we are closing, lws_write will + * add any necessary version-specific stuff. If the write fails, + * no worries we are closing anyway. If we didn't initiate this + * close, then our state has been changed to + * LWSS_RETURNED_CLOSE_ALREADY and we will skip this. + * + * Likewise if it's a second call to close this connection after we + * sent the close indication to the peer already, we are in state + * LWSS_AWAITING_CLOSE_ACK and will skip doing this a second time. + */ + + if (wsi->state_pre_close == LWSS_ESTABLISHED && + (wsi->u.ws.close_in_ping_buffer_len || /* already a reason */ + (reason != LWS_CLOSE_STATUS_NOSTATUS && + (reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY)))) { + lwsl_debug("sending close indication...\n"); + + /* if no prepared close reason, use 1000 and no aux data */ + if (!wsi->u.ws.close_in_ping_buffer_len) { + wsi->u.ws.close_in_ping_buffer_len = 2; + wsi->u.ws.ping_payload_buf[LWS_PRE] = + (reason >> 8) & 0xff; + wsi->u.ws.ping_payload_buf[LWS_PRE + 1] = + reason & 0xff; + } + +#if defined (LWS_WITH_ESP8266) + wsi->close_is_pending_send_completion = 1; +#endif + + lwsl_debug("waiting for chance to send close\n"); + wsi->waiting_to_send_close_frame = 1; + wsi->state = LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION; + lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_SEND, 2); + lws_callback_on_writable(wsi); + + return; + } + +just_kill_connection: + + lws_remove_child_from_any_parent(wsi); + n = 0; + + if (wsi->user_space) { + lwsl_debug("%s: %p: DROP_PROTOCOL %s\n", __func__, wsi, + wsi->protocol->name); + wsi->protocol->callback(wsi, + LWS_CALLBACK_HTTP_DROP_PROTOCOL, + wsi->user_space, NULL, 0); + } + + if ((wsi->mode == LWSCM_WSCL_WAITING_SERVER_REPLY || + wsi->mode == LWSCM_WSCL_WAITING_CONNECT) && + !wsi->already_did_cce) { + wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, NULL, 0); + } + + if (wsi->mode & LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP) { + wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CLOSED_CLIENT_HTTP, + wsi->user_space, NULL, 0); + wsi->told_user_closed = 1; + } + + +#if LWS_POSIX + /* + * Testing with ab shows that we have to stage the socket close when + * the system is under stress... shutdown any further TX, change the + * state to one that won't emit anything more, and wait with a timeout + * for the POLLIN to show a zero-size rx before coming back and doing + * the actual close. + */ + if (wsi->mode != LWSCM_RAW && + !(wsi->mode & LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP) && + wsi->state != LWSS_SHUTDOWN && + wsi->state != LWSS_CLIENT_UNCONNECTED && + reason != LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY && + !wsi->socket_is_permanently_unusable) { +#ifdef LWS_OPENSSL_SUPPORT + if (lws_is_ssl(wsi) && wsi->ssl) { + n = SSL_shutdown(wsi->ssl); + /* + * If finished the SSL shutdown, then do socket + * shutdown, else need to retry SSL shutdown + */ + switch (n) { + case 0: + lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); + break; + case 1: + n = shutdown(wsi->desc.sockfd, SHUT_WR); + break; + default: + if (SSL_want_read(wsi->ssl)) { + lws_change_pollfd(wsi, 0, LWS_POLLIN); + n = 0; + break; + } + if (SSL_want_write(wsi->ssl)) { + lws_change_pollfd(wsi, 0, LWS_POLLOUT); + n = 0; + break; + } + n = shutdown(wsi->desc.sockfd, SHUT_WR); + break; + } + } else +#endif + { + lwsl_info("%s: shutdown conn: %p (sock %d, state %d)\n", + __func__, wsi, (int)(long)wsi->desc.sockfd, + wsi->state); + if (!wsi->socket_is_permanently_unusable && + lws_sockfd_valid(wsi->desc.sockfd)) + n = shutdown(wsi->desc.sockfd, SHUT_WR); + } + if (n) + lwsl_debug("closing: shutdown (state %d) ret %d\n", + wsi->state, LWS_ERRNO); + + /* + * This causes problems on WINCE / ESP32 with disconnection + * when the events are half closing connection + */ +#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP32) + /* libuv: no event available to guarantee completion */ + if (!wsi->socket_is_permanently_unusable && + lws_sockfd_valid(wsi->desc.sockfd) && + !LWS_LIBUV_ENABLED(context)) { + lws_change_pollfd(wsi, LWS_POLLOUT, LWS_POLLIN); + wsi->state = LWSS_SHUTDOWN; + lws_set_timeout(wsi, PENDING_TIMEOUT_SHUTDOWN_FLUSH, + context->timeout_secs); + + return; + } +#endif + } +#endif + + lwsl_debug("%s: real just_kill_connection: %p (sockfd %d)\n", __func__, + wsi, wsi->desc.sockfd); + +#ifdef LWS_WITH_HTTP_PROXY + if (wsi->rw) { + lws_rewrite_destroy(wsi->rw); + wsi->rw = NULL; + } +#endif + /* + * we won't be servicing or receiving anything further from this guy + * delete socket from the internal poll list if still present + */ + lws_ssl_remove_wsi_from_buffered_list(wsi); + lws_remove_from_timeout_list(wsi); + + /* checking return redundant since we anyway close */ + if (wsi->desc.sockfd != LWS_SOCK_INVALID) + remove_wsi_socket_from_fds(wsi); + else + lws_same_vh_protocol_remove(wsi); + +#if defined(LWS_WITH_ESP8266) + espconn_disconnect(wsi->desc.sockfd); +#endif + + wsi->state = LWSS_DEAD_SOCKET; + + lws_free_set_NULL(wsi->rxflow_buffer); + if (wsi->state_pre_close == LWSS_ESTABLISHED || + wsi->mode == LWSCM_WS_SERVING || + wsi->mode == LWSCM_WS_CLIENT) { + + if (wsi->u.ws.rx_draining_ext) { + struct lws **w = &pt->rx_draining_ext_list; + + wsi->u.ws.rx_draining_ext = 0; + /* remove us from context draining ext list */ + while (*w) { + if (*w == wsi) { + *w = wsi->u.ws.rx_draining_ext_list; + break; + } + w = &((*w)->u.ws.rx_draining_ext_list); + } + wsi->u.ws.rx_draining_ext_list = NULL; + } + + if (wsi->u.ws.tx_draining_ext) { + struct lws **w = &pt->tx_draining_ext_list; + + wsi->u.ws.tx_draining_ext = 0; + /* remove us from context draining ext list */ + while (*w) { + if (*w == wsi) { + *w = wsi->u.ws.tx_draining_ext_list; + break; + } + w = &((*w)->u.ws.tx_draining_ext_list); + } + wsi->u.ws.tx_draining_ext_list = NULL; + } + lws_free_set_NULL(wsi->u.ws.rx_ubuf); + + if (wsi->trunc_alloc) + /* not going to be completed... nuke it */ + lws_free_set_NULL(wsi->trunc_alloc); + + wsi->u.ws.ping_payload_len = 0; + wsi->u.ws.ping_pending_flag = 0; + } + + /* tell the user it's all over for this guy */ + + if (!wsi->told_user_closed && + wsi->mode != LWSCM_RAW && wsi->protocol && + wsi->protocol->callback && + (wsi->state_pre_close == LWSS_ESTABLISHED || + wsi->state_pre_close == LWSS_HTTP2_ESTABLISHED || + wsi->state_pre_close == LWSS_HTTP_BODY || + wsi->state_pre_close == LWSS_HTTP || + wsi->state_pre_close == LWSS_RETURNED_CLOSE_ALREADY || + wsi->state_pre_close == LWSS_AWAITING_CLOSE_ACK || + wsi->state_pre_close == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || + wsi->state_pre_close == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE || + (wsi->mode == LWSCM_WS_CLIENT && wsi->state_pre_close == LWSS_HTTP) || + (wsi->mode == LWSCM_WS_SERVING && wsi->state_pre_close == LWSS_HTTP))) { + lwsl_debug("calling back CLOSED %d %d\n", wsi->mode, wsi->state); + wsi->protocol->callback(wsi, LWS_CALLBACK_CLOSED, + wsi->user_space, NULL, 0); + } else if (wsi->mode == LWSCM_HTTP_SERVING_ACCEPTED) { + lwsl_debug("calling back CLOSED_HTTP\n"); + wsi->vhost->protocols->callback(wsi, LWS_CALLBACK_CLOSED_HTTP, + wsi->user_space, NULL, 0 ); + } else + lwsl_debug("not calling back closed mode=%d state=%d\n", + wsi->mode, wsi->state_pre_close); + + /* deallocate any active extension contexts */ + + if (lws_ext_cb_active(wsi, LWS_EXT_CB_DESTROY, NULL, 0) < 0) + lwsl_warn("extension destruction failed\n"); + /* + * inform all extensions in case they tracked this guy out of band + * even though not active on him specifically + */ + if (lws_ext_cb_all_exts(context, wsi, + LWS_EXT_CB_DESTROY_ANY_WSI_CLOSING, NULL, 0) < 0) + lwsl_warn("ext destroy wsi failed\n"); + +async_close: + wsi->socket_is_permanently_unusable = 1; + +#ifdef LWS_WITH_LIBUV + if (!wsi->parent_carries_io && + lws_sockfd_valid(wsi->desc.sockfd)) + if (LWS_LIBUV_ENABLED(context)) { + if (wsi->listener) { + lwsl_debug("%s: stop listener poll\n", __func__); + uv_poll_stop(&wsi->w_read.uv_watcher); + } + lwsl_debug("%s: lws_libuv_closehandle: wsi %p\n", + __func__, wsi); + /* + * libuv has to do his own close handle processing + * asynchronously + */ + lws_libuv_closehandle(wsi); + + return; + } +#endif + + lws_close_free_wsi_final(wsi); +} + +void +lws_close_free_wsi_final(struct lws *wsi) +{ + int n; + + if (lws_socket_is_valid(wsi->desc.sockfd) && !lws_ssl_close(wsi)) { +#if LWS_POSIX + n = compatible_close(wsi->desc.sockfd); + if (n) + lwsl_debug("closing: close ret %d\n", LWS_ERRNO); + +#else + compatible_close(wsi->desc.sockfd); + (void)n; +#endif + wsi->desc.sockfd = LWS_SOCK_INVALID; + } + + /* outermost destroy notification for wsi (user_space still intact) */ + wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_WSI_DESTROY, + wsi->user_space, NULL, 0); + +#ifdef LWS_WITH_CGI + if (wsi->cgi) { + + for (n = 0; n < 3; n++) { + if (wsi->cgi->pipe_fds[n][!!(n == 0)] == 0) + lwsl_err("ZERO FD IN CGI CLOSE"); + + if (wsi->cgi->pipe_fds[n][!!(n == 0)] >= 0) + close(wsi->cgi->pipe_fds[n][!!(n == 0)]); + } + + lws_free(wsi->cgi); + } +#endif + + lws_free_wsi(wsi); +} + +LWS_VISIBLE LWS_EXTERN const char * +lws_get_urlarg_by_name(struct lws *wsi, const char *name, char *buf, int len) +{ + int n = 0, sl = strlen(name); + + while (lws_hdr_copy_fragment(wsi, buf, len, + WSI_TOKEN_HTTP_URI_ARGS, n) >= 0) { + + if (!strncmp(buf, name, sl)) + return buf + sl; + + n++; + } + + return NULL; +} + +#if LWS_POSIX && !defined(LWS_WITH_ESP32) +LWS_VISIBLE int +interface_to_sa(struct lws_vhost *vh, const char *ifname, + struct sockaddr_in *addr, size_t addrlen) +{ + int ipv6 = 0; +#ifdef LWS_WITH_IPV6 + ipv6 = LWS_IPV6_ENABLED(vh); +#endif + (void)vh; + + return lws_interface_to_sa(ipv6, ifname, addr, addrlen); +} +#endif + +#ifndef LWS_PLAT_OPTEE +#if LWS_POSIX +static int +lws_get_addresses(struct lws_vhost *vh, void *ads, char *name, + int name_len, char *rip, int rip_len) +{ +#if LWS_POSIX + struct addrinfo ai, *res; + struct sockaddr_in addr4; + + rip[0] = '\0'; + name[0] = '\0'; + addr4.sin_family = AF_UNSPEC; + +#ifdef LWS_WITH_IPV6 + if (LWS_IPV6_ENABLED(vh)) { + if (!lws_plat_inet_ntop(AF_INET6, + &((struct sockaddr_in6 *)ads)->sin6_addr, + rip, rip_len)) { + lwsl_err("inet_ntop: %s", strerror(LWS_ERRNO)); + return -1; + } + + // Strip off the IPv4 to IPv6 header if one exists + if (strncmp(rip, "::ffff:", 7) == 0) + memmove(rip, rip + 7, strlen(rip) - 6); + + getnameinfo((struct sockaddr *)ads, + sizeof(struct sockaddr_in6), name, name_len, NULL, 0, 0); + + return 0; + } else +#endif + { + struct addrinfo *result; + + memset(&ai, 0, sizeof ai); + ai.ai_family = PF_UNSPEC; + ai.ai_socktype = SOCK_STREAM; + ai.ai_flags = AI_CANONNAME; +#if !defined(LWS_WITH_ESP32) + if (getnameinfo((struct sockaddr *)ads, + sizeof(struct sockaddr_in), + name, name_len, NULL, 0, 0)) + return -1; +#endif + + if (getaddrinfo(name, NULL, &ai, &result)) + return -1; + + res = result; + while (addr4.sin_family == AF_UNSPEC && res) { + switch (res->ai_family) { + case AF_INET: + addr4.sin_addr = + ((struct sockaddr_in *)res->ai_addr)->sin_addr; + addr4.sin_family = AF_INET; + break; + } + + res = res->ai_next; + } + freeaddrinfo(result); + } + + if (addr4.sin_family == AF_UNSPEC) + return -1; + + if (lws_plat_inet_ntop(AF_INET, &addr4.sin_addr, rip, rip_len) == NULL) + return -1; + + return 0; +#else + (void)vh; + (void)ads; + (void)name; + (void)name_len; + (void)rip; + (void)rip_len; + + return -1; +#endif +} +#endif + + +LWS_VISIBLE const char * +lws_get_peer_simple(struct lws *wsi, char *name, int namelen) +{ +#if LWS_POSIX + socklen_t len, olen; +#ifdef LWS_WITH_IPV6 + struct sockaddr_in6 sin6; +#endif + struct sockaddr_in sin4; + int af = AF_INET; + void *p, *q; + +#if defined(LWS_WITH_HTTP2) + if (wsi->http2_substream) + wsi = wsi->u.h2.parent_wsi; +#endif + + if (wsi->parent_carries_io) + wsi = wsi->parent; + +#ifdef LWS_WITH_IPV6 + if (LWS_IPV6_ENABLED(wsi->vhost)) { + len = sizeof(sin6); + p = &sin6; + af = AF_INET6; + q = &sin6.sin6_addr; + } else +#endif + { + len = sizeof(sin4); + p = &sin4; + q = &sin4.sin_addr; + } + + olen = len; + if (getpeername(wsi->desc.sockfd, p, &len) < 0 || len > olen) { + lwsl_warn("getpeername: %s\n", strerror(LWS_ERRNO)); + return NULL; + } + + return lws_plat_inet_ntop(af, q, name, namelen); +#else +#if defined(LWS_WITH_ESP8266) + return lws_plat_get_peer_simple(wsi, name, namelen); +#else + return NULL; +#endif +#endif +} +#endif + +LWS_VISIBLE void +lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name, + int name_len, char *rip, int rip_len) +{ +#ifndef LWS_PLAT_OPTEE +#if LWS_POSIX + socklen_t len; +#ifdef LWS_WITH_IPV6 + struct sockaddr_in6 sin6; +#endif + struct sockaddr_in sin4; + struct lws_context *context = wsi->context; + int ret = -1; + void *p; + + rip[0] = '\0'; + name[0] = '\0'; + + lws_latency_pre(context, wsi); + +#ifdef LWS_WITH_IPV6 + if (LWS_IPV6_ENABLED(wsi->vhost)) { + len = sizeof(sin6); + p = &sin6; + } else +#endif + { + len = sizeof(sin4); + p = &sin4; + } + + if (getpeername(fd, p, &len) < 0) { + lwsl_warn("getpeername: %s\n", strerror(LWS_ERRNO)); + goto bail; + } + + ret = lws_get_addresses(wsi->vhost, p, name, name_len, rip, rip_len); + +bail: + lws_latency(context, wsi, "lws_get_peer_addresses", ret, 1); +#endif +#endif + (void)wsi; + (void)fd; + (void)name; + (void)name_len; + (void)rip; + (void)rip_len; + +} + +LWS_EXTERN void * +lws_vhost_user(struct lws_vhost *vhost) +{ + return vhost->user; +} + +LWS_EXTERN void * +lws_context_user(struct lws_context *context) +{ + return context->user_space; +} + +LWS_VISIBLE struct lws_vhost * +lws_vhost_get(struct lws *wsi) +{ + return wsi->vhost; +} + +LWS_VISIBLE struct lws_vhost * +lws_get_vhost(struct lws *wsi) +{ + return wsi->vhost; +} + +LWS_VISIBLE const struct lws_protocols * +lws_protocol_get(struct lws *wsi) +{ + return wsi->protocol; +} + +LWS_VISIBLE struct lws * +lws_get_network_wsi(struct lws *wsi) +{ + if (!wsi) + return NULL; + +#if defined(LWS_WITH_HTTP2) + if (!wsi->http2_substream) + return wsi; + + while (wsi->u.h2.parent_wsi) + wsi = wsi->u.h2.parent_wsi; +#endif + + return wsi; +} + +LWS_VISIBLE LWS_EXTERN const struct lws_protocols * +lws_vhost_name_to_protocol(struct lws_vhost *vh, const char *name) +{ + int n; + + for (n = 0; n < vh->count_protocols; n++) + if (!strcmp(name, vh->protocols[n].name)) + return &vh->protocols[n]; + + return NULL; +} + +LWS_VISIBLE int +lws_callback_all_protocol(struct lws_context *context, + const struct lws_protocols *protocol, int reason) +{ + struct lws_context_per_thread *pt = &context->pt[0]; + unsigned int n, m = context->count_threads; + struct lws *wsi; + + while (m--) { + for (n = 0; n < pt->fds_count; n++) { + wsi = wsi_from_fd(context, pt->fds[n].fd); + if (!wsi) + continue; + if (wsi->protocol == protocol) + protocol->callback(wsi, reason, wsi->user_space, + NULL, 0); + } + pt++; + } + + return 0; +} + +LWS_VISIBLE int +lws_callback_all_protocol_vhost_args(struct lws_vhost *vh, + const struct lws_protocols *protocol, int reason, + void *argp, size_t len) +{ + struct lws_context *context = vh->context; + struct lws_context_per_thread *pt = &context->pt[0]; + unsigned int n, m = context->count_threads; + struct lws *wsi; + + while (m--) { + for (n = 0; n < pt->fds_count; n++) { + wsi = wsi_from_fd(context, pt->fds[n].fd); + if (!wsi) + continue; + if (wsi->vhost == vh && (wsi->protocol == protocol || + !protocol)) + wsi->protocol->callback(wsi, reason, + wsi->user_space, argp, len); + } + pt++; + } + + return 0; +} + +LWS_VISIBLE int +lws_callback_all_protocol_vhost(struct lws_vhost *vh, + const struct lws_protocols *protocol, int reason) +{ + return lws_callback_all_protocol_vhost_args(vh, protocol, reason, NULL, 0); +} + +LWS_VISIBLE LWS_EXTERN int +lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len) +{ + int n; + + for (n = 0; n < wsi->vhost->count_protocols; n++) + if (wsi->vhost->protocols[n].callback(wsi, reason, NULL, in, len)) + return 1; + + return 0; +} + +LWS_VISIBLE LWS_EXTERN void +lws_set_fops(struct lws_context *context, const struct lws_plat_file_ops *fops) +{ + context->fops = fops; +} + +LWS_VISIBLE LWS_EXTERN lws_filepos_t +lws_vfs_tell(lws_fop_fd_t fop_fd) +{ + return fop_fd->pos; +} + +LWS_VISIBLE LWS_EXTERN lws_filepos_t +lws_vfs_get_length(lws_fop_fd_t fop_fd) +{ + return fop_fd->len; +} + +LWS_VISIBLE LWS_EXTERN uint32_t +lws_vfs_get_mod_time(lws_fop_fd_t fop_fd) +{ + return fop_fd->mod_time; +} + +LWS_VISIBLE lws_fileofs_t +lws_vfs_file_seek_set(lws_fop_fd_t fop_fd, lws_fileofs_t offset) +{ + lws_fileofs_t ofs; + + ofs = fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, offset - fop_fd->pos); + + return ofs; +} + + +LWS_VISIBLE lws_fileofs_t +lws_vfs_file_seek_end(lws_fop_fd_t fop_fd, lws_fileofs_t offset) +{ + return fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, fop_fd->len + + fop_fd->pos + offset); +} + + +const struct lws_plat_file_ops * +lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path, + const char **vpath) +{ + const struct lws_plat_file_ops *pf; + const char *p = vfs_path; + int n; + + *vpath = NULL; + + /* no non-platform fops, just use that */ + + if (!fops->next) + return fops; + + /* + * scan the vfs path looking for indications we are to be + * handled by a specific fops + */ + + while (p && *p) { + if (*p != '/') { + p++; + continue; + } + /* the first one is always platform fops, so skip */ + pf = fops->next; + while (pf) { + n = 0; + while (n < ARRAY_SIZE(pf->fi) && pf->fi[n].sig) { + if (p >= vfs_path + pf->fi[n].len) + if (!strncmp(p - (pf->fi[n].len - 1), + pf->fi[n].sig, + pf->fi[n].len - 1)) { + *vpath = p + 1; + return pf; + } + + n++; + } + pf = pf->next; + } + p++; + } + + return fops; +} + +LWS_VISIBLE LWS_EXTERN lws_fop_fd_t LWS_WARN_UNUSED_RESULT +lws_vfs_file_open(const struct lws_plat_file_ops *fops, const char *vfs_path, + lws_fop_flags_t *flags) +{ + const char *vpath = ""; + const struct lws_plat_file_ops *selected; + + selected = lws_vfs_select_fops(fops, vfs_path, &vpath); + + return selected->LWS_FOP_OPEN(fops, vfs_path, vpath, flags); +} + + +/** + * lws_now_secs() - seconds since 1970-1-1 + * + */ +LWS_VISIBLE LWS_EXTERN unsigned long +lws_now_secs(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + + return tv.tv_sec; +} + + +#if LWS_POSIX + +LWS_VISIBLE int +lws_get_socket_fd(struct lws *wsi) +{ + if (!wsi) + return -1; + return wsi->desc.sockfd; +} + +#endif + +#ifdef LWS_LATENCY +void +lws_latency(struct lws_context *context, struct lws *wsi, const char *action, + int ret, int completed) +{ + unsigned long long u; + char buf[256]; + + u = time_in_microseconds(); + + if (!action) { + wsi->latency_start = u; + if (!wsi->action_start) + wsi->action_start = u; + return; + } + if (completed) { + if (wsi->action_start == wsi->latency_start) + sprintf(buf, + "Completion first try lat %lluus: %p: ret %d: %s\n", + u - wsi->latency_start, + (void *)wsi, ret, action); + else + sprintf(buf, + "Completion %lluus: lat %lluus: %p: ret %d: %s\n", + u - wsi->action_start, + u - wsi->latency_start, + (void *)wsi, ret, action); + wsi->action_start = 0; + } else + sprintf(buf, "lat %lluus: %p: ret %d: %s\n", + u - wsi->latency_start, (void *)wsi, ret, action); + + if (u - wsi->latency_start > context->worst_latency) { + context->worst_latency = u - wsi->latency_start; + strcpy(context->worst_latency_info, buf); + } + lwsl_latency("%s", buf); +} +#endif + +LWS_VISIBLE int +lws_rx_flow_control(struct lws *wsi, int _enable) +{ + int en = _enable; + + lwsl_info("%s: %p 0x%x\n", __func__, wsi, _enable); + + if (!(_enable & LWS_RXFLOW_REASON_APPLIES)) { + /* + * convert user bool style to bitmap style... in user simple + * bool style _enable = 0 = flow control it, = 1 = allow rx + */ + en = LWS_RXFLOW_REASON_APPLIES | LWS_RXFLOW_REASON_USER_BOOL; + if (_enable & 1) + en |= LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT; + } + + /* any bit set in rxflow_bitmap DISABLEs rxflow control */ + if (en & LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT) + wsi->rxflow_bitmap &= ~(en & 0xff); + else + wsi->rxflow_bitmap |= en & 0xff; + + if ((LWS_RXFLOW_PENDING_CHANGE | (!wsi->rxflow_bitmap)) == + wsi->rxflow_change_to) + return 0; + + wsi->rxflow_change_to = LWS_RXFLOW_PENDING_CHANGE | !wsi->rxflow_bitmap; + + lwsl_info("%s: 0x%p: bitmap 0x%x: en 0x%x, ch 0x%x\n", __func__, wsi, + wsi->rxflow_bitmap, en, wsi->rxflow_change_to); + + if (_enable & LWS_RXFLOW_REASON_FLAG_PROCESS_NOW || + !wsi->rxflow_will_be_applied) + return _lws_rx_flow_control(wsi); + + return 0; +} + +LWS_VISIBLE void +lws_rx_flow_allow_all_protocol(const struct lws_context *context, + const struct lws_protocols *protocol) +{ + const struct lws_context_per_thread *pt = &context->pt[0]; + struct lws *wsi; + unsigned int n, m = context->count_threads; + + while (m--) { + for (n = 0; n < pt->fds_count; n++) { + wsi = wsi_from_fd(context, pt->fds[n].fd); + if (!wsi) + continue; + if (wsi->protocol == protocol) + lws_rx_flow_control(wsi, LWS_RXFLOW_ALLOW); + } + pt++; + } +} + +LWS_VISIBLE extern const char * +lws_canonical_hostname(struct lws_context *context) +{ + return (const char *)context->canonical_hostname; +} + +int user_callback_handle_rxflow(lws_callback_function callback_function, + struct lws *wsi, + enum lws_callback_reasons reason, void *user, + void *in, size_t len) +{ + int n; + + wsi->rxflow_will_be_applied = 1; + n = callback_function(wsi, reason, user, in, len); + wsi->rxflow_will_be_applied = 0; + if (!n) + n = _lws_rx_flow_control(wsi); + + return n; +} + +#if defined(LWS_WITH_ESP8266) +#undef strchr +#define strchr ets_strchr +#endif + +LWS_VISIBLE int +lws_set_proxy(struct lws_vhost *vhost, const char *proxy) +{ +#if !defined(LWS_WITH_ESP8266) + char *p; + char authstring[96]; + + if (!proxy) + return -1; + + /* we have to deal with a possible redundant leading http:// */ + if (!strncmp(proxy, "http://", 7)) + proxy += 7; + + p = strchr(proxy, '@'); + if (p) { /* auth is around */ + + if ((unsigned int)(p - proxy) > sizeof(authstring) - 1) + goto auth_too_long; + + strncpy(authstring, proxy, p - proxy); + // null termination not needed on input + if (lws_b64_encode_string(authstring, (p - proxy), + vhost->proxy_basic_auth_token, + sizeof vhost->proxy_basic_auth_token) < 0) + goto auth_too_long; + + lwsl_info(" Proxy auth in use\n"); + + proxy = p + 1; + } else + vhost->proxy_basic_auth_token[0] = '\0'; + + strncpy(vhost->http_proxy_address, proxy, + sizeof(vhost->http_proxy_address) - 1); + vhost->http_proxy_address[ + sizeof(vhost->http_proxy_address) - 1] = '\0'; + + p = strchr(vhost->http_proxy_address, ':'); + if (!p && !vhost->http_proxy_port) { + lwsl_err("http_proxy needs to be ads:port\n"); + + return -1; + } else { + if (p) { + *p = '\0'; + vhost->http_proxy_port = atoi(p + 1); + } + } + + lwsl_info(" Proxy %s:%u\n", vhost->http_proxy_address, + vhost->http_proxy_port); + + return 0; + +auth_too_long: + lwsl_err("proxy auth too long\n"); +#endif + return -1; +} + +#if defined(LWS_WITH_SOCKS5) +LWS_VISIBLE int +lws_set_socks(struct lws_vhost *vhost, const char *socks) +{ +#if !defined(LWS_WITH_ESP8266) + char *p_at, *p_colon; + char user[96]; + char password[96]; + + if (!socks) + return -1; + + vhost->socks_user[0] = '\0'; + vhost->socks_password[0] = '\0'; + + p_at = strchr(socks, '@'); + if (p_at) { /* auth is around */ + if ((unsigned int)(p_at - socks) > (sizeof(user) + + sizeof(password) - 2)) { + lwsl_err("Socks auth too long\n"); + goto bail; + } + + p_colon = strchr(socks, ':'); + if (p_colon) { + if ((unsigned int)(p_colon - socks) > (sizeof(user) + - 1) ) { + lwsl_err("Socks user too long\n"); + goto bail; + } + if ((unsigned int)(p_at - p_colon) > (sizeof(password) + - 1) ) { + lwsl_err("Socks password too long\n"); + goto bail; + } + + strncpy(vhost->socks_user, socks, p_colon - socks); + strncpy(vhost->socks_password, p_colon + 1, + p_at - (p_colon + 1)); + } + + lwsl_info(" Socks auth, user: %s, password: %s\n", + vhost->socks_user, vhost->socks_password ); + + socks = p_at + 1; + } + + strncpy(vhost->socks_proxy_address, socks, + sizeof(vhost->socks_proxy_address) - 1); + vhost->socks_proxy_address[sizeof(vhost->socks_proxy_address) - 1] + = '\0'; + + p_colon = strchr(vhost->socks_proxy_address, ':'); + if (!p_colon && !vhost->socks_proxy_port) { + lwsl_err("socks_proxy needs to be address:port\n"); + return -1; + } else { + if (p_colon) { + *p_colon = '\0'; + vhost->socks_proxy_port = atoi(p_colon + 1); + } + } + + lwsl_info(" Socks %s:%u\n", vhost->socks_proxy_address, + vhost->socks_proxy_port); + + return 0; + +bail: +#endif + return -1; +} +#endif + +LWS_VISIBLE const struct lws_protocols * +lws_get_protocol(struct lws *wsi) +{ + return wsi->protocol; +} + +LWS_VISIBLE int +lws_is_final_fragment(struct lws *wsi) +{ + lwsl_info("%s: final %d, rx pk length %ld, draining %ld\n", __func__, + wsi->u.ws.final, (long)wsi->u.ws.rx_packet_length, + (long)wsi->u.ws.rx_draining_ext); + return wsi->u.ws.final && !wsi->u.ws.rx_packet_length && + !wsi->u.ws.rx_draining_ext; +} + +LWS_VISIBLE int +lws_is_first_fragment(struct lws *wsi) +{ + return wsi->u.ws.first_fragment; +} + +LWS_VISIBLE unsigned char +lws_get_reserved_bits(struct lws *wsi) +{ + return wsi->u.ws.rsv; +} + +int +lws_ensure_user_space(struct lws *wsi) +{ + if (!wsi->protocol) + return 0; + + /* allocate the per-connection user memory (if any) */ + + if (wsi->protocol->per_session_data_size && !wsi->user_space) { + wsi->user_space = lws_zalloc(wsi->protocol->per_session_data_size, "user space"); + if (wsi->user_space == NULL) { + lwsl_err("%s: OOM\n", __func__); + return 1; + } + } else + lwsl_debug("%s: %p protocol pss %lu, user_space=%p\n", __func__, + wsi, (long)wsi->protocol->per_session_data_size, + wsi->user_space); + return 0; +} + +LWS_VISIBLE void * +lws_adjust_protocol_psds(struct lws *wsi, size_t new_size) +{ + ((struct lws_protocols *)lws_get_protocol(wsi))->per_session_data_size = + new_size; + + if (lws_ensure_user_space(wsi)) + return NULL; + + return wsi->user_space; +} + +LWS_VISIBLE int +lwsl_timestamp(int level, char *p, int len) +{ +#ifndef LWS_PLAT_OPTEE + time_t o_now = time(NULL); + unsigned long long now; + struct tm *ptm = NULL; +#ifndef WIN32 + struct tm tm; +#endif + int n; + +#ifndef _WIN32_WCE +#ifdef WIN32 + ptm = localtime(&o_now); +#else + if (localtime_r(&o_now, &tm)) + ptm = &tm; +#endif +#endif + p[0] = '\0'; + for (n = 0; n < LLL_COUNT; n++) { + if (level != (1 << n)) + continue; + now = time_in_microseconds() / 100; + if (ptm) + n = lws_snprintf(p, len, + "[%04d/%02d/%02d %02d:%02d:%02d:%04d] %s: ", + ptm->tm_year + 1900, + ptm->tm_mon + 1, + ptm->tm_mday, + ptm->tm_hour, + ptm->tm_min, + ptm->tm_sec, + (int)(now % 10000), log_level_names[n]); + else + n = lws_snprintf(p, len, "[%llu:%04d] %s: ", + (unsigned long long) now / 10000, + (int)(now % 10000), log_level_names[n]); + return n; + } +#endif + return 0; +} + +static const char * const colours[] = { + "[31;1m", /* LLL_ERR */ + "[36;1m", /* LLL_WARN */ + "[35;1m", /* LLL_NOTICE */ + "[32;1m", /* LLL_INFO */ + "[34;1m", /* LLL_DEBUG */ + "[33;1m", /* LLL_PARSER */ + "[33;1m", /* LLL_HEADER */ + "[33;1m", /* LLL_EXT */ + "[33;1m", /* LLL_CLIENT */ + "[33;1m", /* LLL_LATENCY */ + "[30;1m", /* LLL_USER */ +}; + +#ifndef LWS_PLAT_OPTEE +LWS_VISIBLE void lwsl_emit_stderr(int level, const char *line) +{ +#if !defined(LWS_WITH_ESP8266) + char buf[50]; + static char tty; + int n, m = ARRAY_SIZE(colours) - 1; + + if (!tty) + tty = isatty(2) | 2; + + lwsl_timestamp(level, buf, sizeof(buf)); + + if (tty == 3) { + n = 1 << (ARRAY_SIZE(colours) - 1); + while (n) { + if (level & n) + break; + m--; + n >>= 1; + } + fprintf(stderr, "%c%s%s%s%c[0m", 27, colours[m], buf, line, 27); + } else + fprintf(stderr, "%s%s", buf, line); +#endif +} +#endif + +LWS_VISIBLE void _lws_logv(int filter, const char *format, va_list vl) +{ +#if defined(LWS_WITH_ESP8266) + char buf[128]; +#else + char buf[256]; +#endif + int n; + + if (!(log_level & filter)) + return; + + n = vsnprintf(buf, sizeof(buf) - 1, format, vl); + (void)n; +#if defined(LWS_WITH_ESP8266) + buf[sizeof(buf) - 1] = '\0'; +#else + /* vnsprintf returns what it would have written, even if truncated */ + if (n > sizeof(buf) - 1) + n = sizeof(buf) - 1; + if (n > 0) + buf[n] = '\0'; +#endif + + lwsl_emit(filter, buf); +} + +LWS_VISIBLE void _lws_log(int filter, const char *format, ...) +{ + va_list ap; + + va_start(ap, format); + _lws_logv(filter, format, ap); + va_end(ap); +} + +LWS_VISIBLE void lws_set_log_level(int level, + void (*func)(int level, const char *line)) +{ + log_level = level; + if (func) + lwsl_emit = func; +} + +LWS_VISIBLE int lwsl_visible(int level) +{ + return log_level & level; +} + +LWS_VISIBLE void +lwsl_hexdump_level(int hexdump_level, const void *vbuf, size_t len) +{ + unsigned char *buf = (unsigned char *)vbuf; + unsigned int n, m, start; + char line[80]; + char *p; + + if (!lwsl_visible(hexdump_level)) + return; + + _lws_log(hexdump_level, "\n"); + + for (n = 0; n < len;) { + start = n; + p = line; + + p += sprintf(p, "%04X: ", start); + + for (m = 0; m < 16 && n < len; m++) + p += sprintf(p, "%02X ", buf[n++]); + while (m++ < 16) + p += sprintf(p, " "); + + p += sprintf(p, " "); + + for (m = 0; m < 16 && (start + m) < len; m++) { + if (buf[start + m] >= ' ' && buf[start + m] < 127) + *p++ = buf[start + m]; + else + *p++ = '.'; + } + while (m++ < 16) + *p++ = ' '; + + *p++ = '\n'; + *p = '\0'; + _lws_log(hexdump_level, "%s", line); + (void)line; + } + + _lws_log(hexdump_level, "\n"); +} + +LWS_VISIBLE void +lwsl_hexdump(const void *vbuf, size_t len) +{ + lwsl_hexdump_level(LLL_DEBUG, vbuf, len); +} + +LWS_VISIBLE int +lws_is_ssl(struct lws *wsi) +{ +#ifdef LWS_OPENSSL_SUPPORT + return wsi->use_ssl; +#else + (void)wsi; + return 0; +#endif +} + +#ifdef LWS_OPENSSL_SUPPORT +LWS_VISIBLE SSL* +lws_get_ssl(struct lws *wsi) +{ + return wsi->ssl; +} +#endif + +LWS_VISIBLE int +lws_partial_buffered(struct lws *wsi) +{ + return !!wsi->trunc_len; +} + +LWS_VISIBLE size_t +lws_get_peer_write_allowance(struct lws *wsi) +{ +#ifdef LWS_WITH_HTTP2 + /* only if we are using HTTP2 on this connection */ + if (wsi->mode != LWSCM_HTTP2_SERVING) + return -1; + + return lws_h2_tx_cr_get(wsi); +#else + (void)wsi; + return -1; +#endif +} + +LWS_VISIBLE void +lws_union_transition(struct lws *wsi, enum connection_mode mode) +{ + lwsl_debug("%s: %p: mode %d\n", __func__, wsi, mode); + memset(&wsi->u, 0, sizeof(wsi->u)); + wsi->mode = mode; +} + +LWS_VISIBLE struct lws_plat_file_ops * +lws_get_fops(struct lws_context *context) +{ + return (struct lws_plat_file_ops *)context->fops; +} + +LWS_VISIBLE LWS_EXTERN struct lws_context * +lws_get_context(const struct lws *wsi) +{ + return wsi->context; +} + +LWS_VISIBLE LWS_EXTERN int +lws_get_count_threads(struct lws_context *context) +{ + return context->count_threads; +} + +LWS_VISIBLE LWS_EXTERN void * +lws_wsi_user(struct lws *wsi) +{ + return wsi->user_space; +} + +LWS_VISIBLE LWS_EXTERN void +lws_set_wsi_user(struct lws *wsi, void *data) +{ + if (wsi->user_space_externally_allocated) + wsi->user_space = data; + else + lwsl_err("%s: Cannot set internally-allocated user_space\n", + __func__); +} + +LWS_VISIBLE LWS_EXTERN struct lws * +lws_get_parent(const struct lws *wsi) +{ + return wsi->parent; +} + +LWS_VISIBLE LWS_EXTERN struct lws * +lws_get_child(const struct lws *wsi) +{ + return wsi->child_list; +} + +LWS_VISIBLE LWS_EXTERN void +lws_set_parent_carries_io(struct lws *wsi) +{ + wsi->parent_carries_io = 1; +} + +LWS_VISIBLE LWS_EXTERN void * +lws_get_opaque_parent_data(const struct lws *wsi) +{ + return wsi->opaque_parent_data; +} + +LWS_VISIBLE LWS_EXTERN void +lws_set_opaque_parent_data(struct lws *wsi, void *data) +{ + wsi->opaque_parent_data = data; +} + +LWS_VISIBLE LWS_EXTERN int +lws_get_child_pending_on_writable(const struct lws *wsi) +{ + return wsi->parent_pending_cb_on_writable; +} + +LWS_VISIBLE LWS_EXTERN void +lws_clear_child_pending_on_writable(struct lws *wsi) +{ + wsi->parent_pending_cb_on_writable = 0; +} + +LWS_VISIBLE LWS_EXTERN int +lws_get_close_length(struct lws *wsi) +{ + return wsi->u.ws.close_in_ping_buffer_len; +} + +LWS_VISIBLE LWS_EXTERN unsigned char * +lws_get_close_payload(struct lws *wsi) +{ + return &wsi->u.ws.ping_payload_buf[LWS_PRE]; +} + +LWS_VISIBLE LWS_EXTERN void +lws_close_reason(struct lws *wsi, enum lws_close_status status, + unsigned char *buf, size_t len) +{ + unsigned char *p, *start; + int budget = sizeof(wsi->u.ws.ping_payload_buf) - LWS_PRE; + + assert(wsi->mode == LWSCM_WS_SERVING || wsi->mode == LWSCM_WS_CLIENT); + + start = p = &wsi->u.ws.ping_payload_buf[LWS_PRE]; + + *p++ = (((int)status) >> 8) & 0xff; + *p++ = ((int)status) & 0xff; + + if (buf) + while (len-- && p < start + budget) + *p++ = *buf++; + + wsi->u.ws.close_in_ping_buffer_len = p - start; +} + +LWS_EXTERN int +_lws_rx_flow_control(struct lws *wsi) +{ + struct lws *wsic = wsi->child_list; + + /* if he has children, do those if they were changed */ + while (wsic) { + if (wsic->rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE) + _lws_rx_flow_control(wsic); + + wsic = wsic->sibling_list; + } + + /* there is no pending change */ + if (!(wsi->rxflow_change_to & LWS_RXFLOW_PENDING_CHANGE)) + return 0; + + /* stuff is still buffered, not ready to really accept new input */ + if (wsi->rxflow_buffer) { + /* get ourselves called back to deal with stashed buffer */ + lws_callback_on_writable(wsi); + return 0; + } + + /* pending is cleared, we can change rxflow state */ + + wsi->rxflow_change_to &= ~LWS_RXFLOW_PENDING_CHANGE; + + lwsl_info("rxflow: wsi %p change_to %d\n", wsi, + wsi->rxflow_change_to & LWS_RXFLOW_ALLOW); + + /* adjust the pollfd for this wsi */ + + if (wsi->rxflow_change_to & LWS_RXFLOW_ALLOW) { + if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { + lwsl_info("%s: fail\n", __func__); + return -1; + } + } else + if (lws_change_pollfd(wsi, LWS_POLLIN, 0)) + return -1; + + return 0; +} + +LWS_EXTERN int +lws_check_utf8(unsigned char *state, unsigned char *buf, size_t len) +{ + static const unsigned char e0f4[] = { + 0xa0 | ((2 - 1) << 2) | 1, /* e0 */ + 0x80 | ((4 - 1) << 2) | 1, /* e1 */ + 0x80 | ((4 - 1) << 2) | 1, /* e2 */ + 0x80 | ((4 - 1) << 2) | 1, /* e3 */ + 0x80 | ((4 - 1) << 2) | 1, /* e4 */ + 0x80 | ((4 - 1) << 2) | 1, /* e5 */ + 0x80 | ((4 - 1) << 2) | 1, /* e6 */ + 0x80 | ((4 - 1) << 2) | 1, /* e7 */ + 0x80 | ((4 - 1) << 2) | 1, /* e8 */ + 0x80 | ((4 - 1) << 2) | 1, /* e9 */ + 0x80 | ((4 - 1) << 2) | 1, /* ea */ + 0x80 | ((4 - 1) << 2) | 1, /* eb */ + 0x80 | ((4 - 1) << 2) | 1, /* ec */ + 0x80 | ((2 - 1) << 2) | 1, /* ed */ + 0x80 | ((4 - 1) << 2) | 1, /* ee */ + 0x80 | ((4 - 1) << 2) | 1, /* ef */ + 0x90 | ((3 - 1) << 2) | 2, /* f0 */ + 0x80 | ((4 - 1) << 2) | 2, /* f1 */ + 0x80 | ((4 - 1) << 2) | 2, /* f2 */ + 0x80 | ((4 - 1) << 2) | 2, /* f3 */ + 0x80 | ((1 - 1) << 2) | 2, /* f4 */ + + 0, /* s0 */ + 0x80 | ((4 - 1) << 2) | 0, /* s2 */ + 0x80 | ((4 - 1) << 2) | 1, /* s3 */ + }; + unsigned char s = *state; + + while (len--) { + unsigned char c = *buf++; + + if (!s) { + if (c >= 0x80) { + if (c < 0xc2 || c > 0xf4) + return 1; + if (c < 0xe0) + s = 0x80 | ((4 - 1) << 2); + else + s = e0f4[c - 0xe0]; + } + } else { + if (c < (s & 0xf0) || + c >= (s & 0xf0) + 0x10 + ((s << 2) & 0x30)) + return 1; + s = e0f4[21 + (s & 3)]; + } + } + + *state = s; + + return 0; +} + +LWS_VISIBLE LWS_EXTERN int +lws_parse_uri(char *p, const char **prot, const char **ads, int *port, + const char **path) +{ + const char *end; + static const char *slash = "/"; + + /* cut up the location into address, port and path */ + *prot = p; + while (*p && (*p != ':' || p[1] != '/' || p[2] != '/')) + p++; + if (!*p) { + end = p; + p = (char *)*prot; + *prot = end; + } else { + *p = '\0'; + p += 3; + } + *ads = p; + if (!strcmp(*prot, "http") || !strcmp(*prot, "ws")) + *port = 80; + else if (!strcmp(*prot, "https") || !strcmp(*prot, "wss")) + *port = 443; + + if (*p == '[') + { + ++(*ads); + while (*p && *p != ']') + p++; + if (*p) + *p++ = '\0'; + } + else + { + while (*p && *p != ':' && *p != '/') + p++; + } + if (*p == ':') { + *p++ = '\0'; + *port = atoi(p); + while (*p && *p != '/') + p++; + } + *path = slash; + if (*p) { + *p++ = '\0'; + if (*p) + *path = p; + } + + return 0; +} + +#ifdef LWS_NO_EXTENSIONS + +/* we need to provide dummy callbacks for internal exts + * so user code runs when faced with a lib compiled with + * extensions disabled. + */ + +int +lws_extension_callback_pm_deflate(struct lws_context *context, + const struct lws_extension *ext, + struct lws *wsi, + enum lws_extension_callback_reasons reason, + void *user, void *in, size_t len) +{ + (void)context; + (void)ext; + (void)wsi; + (void)reason; + (void)user; + (void)in; + (void)len; + + return 0; +} +#endif + +LWS_EXTERN int +lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, + const char *iface) +{ +#if LWS_POSIX +#ifdef LWS_WITH_UNIX_SOCK + struct sockaddr_un serv_unix; +#endif +#ifdef LWS_WITH_IPV6 + struct sockaddr_in6 serv_addr6; +#endif + struct sockaddr_in serv_addr4; +#ifndef LWS_PLAT_OPTEE + socklen_t len = sizeof(struct sockaddr_storage); +#endif + int n; + struct sockaddr_storage sin; + struct sockaddr *v; + +#ifdef LWS_WITH_UNIX_SOCK + if (LWS_UNIX_SOCK_ENABLED(vhost)) { + v = (struct sockaddr *)&serv_unix; + n = sizeof(struct sockaddr_un); + bzero((char *) &serv_unix, sizeof(serv_unix)); + serv_unix.sun_family = AF_UNIX; + if (sizeof(serv_unix.sun_path) <= strlen(iface)) { + lwsl_err("\"%s\" too long for UNIX domain socket\n", + iface); + return -1; + } + strcpy(serv_unix.sun_path, iface); + if (serv_unix.sun_path[0] == '@') + serv_unix.sun_path[0] = '\0'; + + } else +#endif +#if defined(LWS_WITH_IPV6) && !defined(LWS_WITH_ESP32) + if (LWS_IPV6_ENABLED(vhost)) { + v = (struct sockaddr *)&serv_addr6; + n = sizeof(struct sockaddr_in6); + bzero((char *) &serv_addr6, sizeof(serv_addr6)); + if (iface) { + if (interface_to_sa(vhost, iface, + (struct sockaddr_in *)v, n) < 0) { + lwsl_err("Unable to find if %s\n", iface); + return -1; + } + serv_addr6.sin6_scope_id = lws_get_addr_scope(iface); + } + + serv_addr6.sin6_family = AF_INET6; + serv_addr6.sin6_port = htons(port); + } else +#endif + { + v = (struct sockaddr *)&serv_addr4; + n = sizeof(serv_addr4); + bzero((char *) &serv_addr4, sizeof(serv_addr4)); + serv_addr4.sin_addr.s_addr = INADDR_ANY; + serv_addr4.sin_family = AF_INET; +#if !defined(LWS_WITH_ESP32) + + if (iface && + interface_to_sa(vhost, iface, + (struct sockaddr_in *)v, n) < 0) { + lwsl_err("Unable to find interface %s\n", iface); + return -1; + } +#endif + serv_addr4.sin_port = htons(port); + } /* ipv4 */ + + n = bind(sockfd, v, n); +#ifdef LWS_WITH_UNIX_SOCK + if (n < 0 && LWS_UNIX_SOCK_ENABLED(vhost)) { + lwsl_err("ERROR on binding fd %d to \"%s\" (%d %d)\n", + sockfd, iface, n, LWS_ERRNO); + return -1; + } else +#endif + if (n < 0) { + lwsl_err("ERROR on binding fd %d to port %d (%d %d)\n", + sockfd, port, n, LWS_ERRNO); + return -1; + } + +#ifndef LWS_PLAT_OPTEE + if (getsockname(sockfd, (struct sockaddr *)&sin, &len) == -1) + lwsl_warn("getsockname: %s\n", strerror(LWS_ERRNO)); + else +#endif +#if defined(LWS_WITH_IPV6) + port = (sin.ss_family == AF_INET6) ? + ntohs(((struct sockaddr_in6 *) &sin)->sin6_port) : + ntohs(((struct sockaddr_in *) &sin)->sin_port); +#else + { + struct sockaddr_in sain; + memcpy(&sain, &sin, sizeof(sain)); + port = ntohs(sain.sin_port); + } +#endif +#endif + + return port; +} + +#if defined(LWS_WITH_IPV6) +LWS_EXTERN unsigned long +lws_get_addr_scope(const char *ipaddr) +{ + unsigned long scope = 0; + +#ifndef WIN32 + struct ifaddrs *addrs, *addr; + char ip[NI_MAXHOST]; + unsigned int i; + + getifaddrs(&addrs); + for (addr = addrs; addr; addr = addr->ifa_next) { + if (!addr->ifa_addr || + addr->ifa_addr->sa_family != AF_INET6) + continue; + + getnameinfo(addr->ifa_addr, + sizeof(struct sockaddr_in6), + ip, sizeof(ip), + NULL, 0, NI_NUMERICHOST); + + i = 0; + while (ip[i]) + if (ip[i++] == '%') { + ip[i - 1] = '\0'; + break; + } + + if (!strcmp(ip, ipaddr)) { + scope = if_nametoindex(addr->ifa_name); + break; + } + } + freeifaddrs(addrs); +#else + PIP_ADAPTER_ADDRESSES adapter, addrs = NULL; + PIP_ADAPTER_UNICAST_ADDRESS addr; + ULONG size = 0; + DWORD ret; + struct sockaddr_in6 *sockaddr; + char ip[NI_MAXHOST]; + unsigned int i; + int found = 0; + + for (i = 0; i < 5; i++) + { + ret = GetAdaptersAddresses(AF_INET6, GAA_FLAG_INCLUDE_PREFIX, + NULL, addrs, &size); + if ((ret == NO_ERROR) || (ret == ERROR_NO_DATA)) { + break; + } else if (ret == ERROR_BUFFER_OVERFLOW) + { + if (addrs) + free(addrs); + addrs = (IP_ADAPTER_ADDRESSES *)malloc(size); + } else + { + if (addrs) + { + free(addrs); + addrs = NULL; + } + lwsl_err("Failed to get IPv6 address table (%d)", ret); + break; + } + } + + if ((ret == NO_ERROR) && (addrs)) { + adapter = addrs; + while (adapter && !found) { + addr = adapter->FirstUnicastAddress; + while (addr && !found) { + if (addr->Address.lpSockaddr->sa_family == AF_INET6) { + sockaddr = (struct sockaddr_in6 *) + (addr->Address.lpSockaddr); + + lws_plat_inet_ntop(sockaddr->sin6_family, + &sockaddr->sin6_addr, + ip, sizeof(ip)); + + if (!strcmp(ip, ipaddr)) { + scope = sockaddr->sin6_scope_id; + found = 1; + break; + } + } + addr = addr->Next; + } + adapter = adapter->Next; + } + } + if (addrs) + free(addrs); +#endif + + return scope; +} +#endif + +LWS_EXTERN void +lws_restart_ws_ping_pong_timer(struct lws *wsi) +{ + if (!wsi->context->ws_ping_pong_interval) + return; + if (wsi->state != LWSS_ESTABLISHED) + return; + + wsi->u.ws.time_next_ping_check = (time_t)lws_now_secs() + + wsi->context->ws_ping_pong_interval; +} + +static const char *hex = "0123456789ABCDEF"; + +LWS_VISIBLE LWS_EXTERN const char * +lws_sql_purify(char *escaped, const char *string, int len) +{ + const char *p = string; + char *q = escaped; + + while (*p && len-- > 2) { + if (*p == '\'') { + *q++ = '\''; + *q++ = '\''; + len --; + p++; + } else + *q++ = *p++; + } + *q = '\0'; + + return escaped; +} + +LWS_VISIBLE LWS_EXTERN const char * +lws_json_purify(char *escaped, const char *string, int len) +{ + const char *p = string; + char *q = escaped; + + if (!p) { + escaped[0] = '\0'; + return escaped; + } + + while (*p && len-- > 6) { + if (*p == '\"' || *p == '\\' || *p < 0x20) { + *q++ = '\\'; + *q++ = 'u'; + *q++ = '0'; + *q++ = '0'; + *q++ = hex[((*p) >> 4) & 15]; + *q++ = hex[(*p) & 15]; + len -= 5; + p++; + } else + *q++ = *p++; + } + *q = '\0'; + + return escaped; +} + +LWS_VISIBLE LWS_EXTERN const char * +lws_urlencode(char *escaped, const char *string, int len) +{ + const char *p = string; + char *q = escaped; + + while (*p && len-- > 3) { + if (*p == ' ') { + *q++ = '+'; + p++; + continue; + } + if ((*p >= '0' && *p <= '9') || + (*p >= 'A' && *p <= 'Z') || + (*p >= 'a' && *p <= 'z')) { + *q++ = *p++; + continue; + } + *q++ = '%'; + *q++ = hex[(*p >> 4) & 0xf]; + *q++ = hex[*p & 0xf]; + + len -= 2; + p++; + } + *q = '\0'; + + return escaped; +} + +LWS_VISIBLE LWS_EXTERN int +lws_urldecode(char *string, const char *escaped, int len) +{ + int state = 0, n; + char sum = 0; + + while (*escaped && len) { + switch (state) { + case 0: + if (*escaped == '%') { + state++; + escaped++; + continue; + } + if (*escaped == '+') { + escaped++; + *string++ = ' '; + len--; + continue; + } + *string++ = *escaped++; + len--; + break; + case 1: + n = char_to_hex(*escaped); + if (n < 0) + return -1; + escaped++; + sum = n << 4; + state++; + break; + + case 2: + n = char_to_hex(*escaped); + if (n < 0) + return -1; + escaped++; + *string++ = sum | n; + len--; + state = 0; + break; + } + + } + *string = '\0'; + + return 0; +} + +LWS_VISIBLE LWS_EXTERN int +lws_finalize_startup(struct lws_context *context) +{ + struct lws_context_creation_info info; + + info.uid = context->uid; + info.gid = context->gid; + +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + memcpy(info.caps, context->caps, sizeof(info.caps)); + info.count_caps = context->count_caps; +#endif + + if (lws_check_opt(context->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) + lws_plat_drop_app_privileges(&info); + + return 0; +} + +int +lws_snprintf(char *str, size_t size, const char *format, ...) +{ + va_list ap; + int n; + + if (!size) + return 0; + + va_start(ap, format); + n = vsnprintf(str, size, format, ap); + va_end(ap); + + if (n >= (int)size) + return size; + + return n; +} + + +LWS_VISIBLE LWS_EXTERN int +lws_is_cgi(struct lws *wsi) { +#ifdef LWS_WITH_CGI + return !!wsi->cgi; +#else + return 0; +#endif +} + + + +#ifdef LWS_NO_EXTENSIONS +LWS_EXTERN int +lws_set_extension_option(struct lws *wsi, const char *ext_name, + const char *opt_name, const char *opt_val) +{ + return -1; +} +#endif + + +void +lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs) +{ + const struct lws_vhost *vh = ctx->vhost_list; + + while (vh) { + + cs->rx += vh->conn_stats.rx; + cs->tx += vh->conn_stats.tx; + cs->h1_conn += vh->conn_stats.h1_conn; + cs->h1_trans += vh->conn_stats.h1_trans; + cs->h2_trans += vh->conn_stats.h2_trans; + cs->ws_upg += vh->conn_stats.ws_upg; + cs->h2_upg += vh->conn_stats.h2_upg; + cs->h2_alpn += vh->conn_stats.h2_alpn; + cs->h2_subs += vh->conn_stats.h2_subs; + cs->rejected += vh->conn_stats.rejected; + + vh = vh->vhost_next; + } +} + +#ifdef LWS_WITH_SERVER_STATUS + +LWS_EXTERN int +lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len) +{ + static const char * const prots[] = { + "http://", + "https://", + "file://", + "cgi://", + ">http://", + ">https://", + "callback://" + }; + char *orig = buf, *end = buf + len - 1, first = 1; + int n = 0; + + if (len < 100) + return 0; + + buf += lws_snprintf(buf, end - buf, + "{\n \"name\":\"%s\",\n" + " \"port\":\"%d\",\n" + " \"use_ssl\":\"%d\",\n" + " \"sts\":\"%d\",\n" + " \"rx\":\"%llu\",\n" + " \"tx\":\"%llu\",\n" + " \"h1_conn\":\"%lu\",\n" + " \"h1_trans\":\"%lu\",\n" + " \"h2_trans\":\"%lu\",\n" + " \"ws_upg\":\"%lu\",\n" + " \"rejected\":\"%lu\",\n" + " \"h2_upg\":\"%lu\",\n" + " \"h2_alpn\":\"%lu\",\n" + " \"h2_subs\":\"%lu\"" + , + vh->name, vh->listen_port, +#ifdef LWS_OPENSSL_SUPPORT + vh->use_ssl, +#else + 0, +#endif + !!(vh->options & LWS_SERVER_OPTION_STS), + vh->conn_stats.rx, vh->conn_stats.tx, + vh->conn_stats.h1_conn, + vh->conn_stats.h1_trans, + vh->conn_stats.h2_trans, + vh->conn_stats.ws_upg, + vh->conn_stats.rejected, + vh->conn_stats.h2_upg, + vh->conn_stats.h2_alpn, + vh->conn_stats.h2_subs + ); + + if (vh->mount_list) { + const struct lws_http_mount *m = vh->mount_list; + + buf += lws_snprintf(buf, end - buf, ",\n \"mounts\":["); + while (m) { + if (!first) + buf += lws_snprintf(buf, end - buf, ","); + buf += lws_snprintf(buf, end - buf, + "\n {\n \"mountpoint\":\"%s\",\n" + " \"origin\":\"%s%s\",\n" + " \"cache_max_age\":\"%d\",\n" + " \"cache_reuse\":\"%d\",\n" + " \"cache_revalidate\":\"%d\",\n" + " \"cache_intermediaries\":\"%d\"\n" + , + m->mountpoint, + prots[m->origin_protocol], + m->origin, + m->cache_max_age, + m->cache_reusable, + m->cache_revalidate, + m->cache_intermediaries); + if (m->def) + buf += lws_snprintf(buf, end - buf, + ",\n \"default\":\"%s\"", + m->def); + buf += lws_snprintf(buf, end - buf, "\n }"); + first = 0; + m = m->mount_next; + } + buf += lws_snprintf(buf, end - buf, "\n ]"); + } + + if (vh->protocols) { + n = 0; + first = 1; + + buf += lws_snprintf(buf, end - buf, ",\n \"ws-protocols\":["); + while (n < vh->count_protocols) { + if (!first) + buf += lws_snprintf(buf, end - buf, ","); + buf += lws_snprintf(buf, end - buf, + "\n {\n \"%s\":{\n" + " \"status\":\"ok\"\n }\n }" + , + vh->protocols[n].name); + first = 0; + n++; + } + buf += lws_snprintf(buf, end - buf, "\n ]"); + } + + buf += lws_snprintf(buf, end - buf, "\n}"); + + return buf - orig; +} + + +LWS_EXTERN LWS_VISIBLE int +lws_json_dump_context(const struct lws_context *context, char *buf, int len, + int hide_vhosts) +{ + char *orig = buf, *end = buf + len - 1, first = 1; + const struct lws_vhost *vh = context->vhost_list; + const struct lws_context_per_thread *pt; + time_t t = time(NULL); + int n, listening = 0, cgi_count = 0; + struct lws_conn_stats cs; + double d = 0; +#ifdef LWS_WITH_CGI + struct lws_cgi * const *pcgi; +#endif + +#ifdef LWS_WITH_LIBUV + uv_uptime(&d); +#endif + + buf += lws_snprintf(buf, end - buf, "{ " + "\"version\":\"%s\",\n" + "\"uptime\":\"%ld\",\n", + lws_get_library_version(), + (long)d); + +#ifdef LWS_HAVE_GETLOADAVG + { + double d[3]; + int m; + + m = getloadavg(d, 3); + for (n = 0; n < m; n++) { + buf += lws_snprintf(buf, end - buf, + "\"l%d\":\"%.2f\",\n", + n + 1, d[n]); + } + } +#endif + + buf += lws_snprintf(buf, end - buf, "\"contexts\":[\n"); + + buf += lws_snprintf(buf, end - buf, "{ " + "\"context_uptime\":\"%ld\",\n" + "\"cgi_spawned\":\"%d\",\n" + "\"pt_fd_max\":\"%d\",\n" + "\"ah_pool_max\":\"%d\",\n" + "\"deprecated\":\"%d\",\n" + "\"wsi_alive\":\"%d\",\n", + (unsigned long)(t - context->time_up), + context->count_cgi_spawned, + context->fd_limit_per_thread, + context->max_http_header_pool, + context->deprecated, + context->count_wsi_allocated); + + buf += lws_snprintf(buf, end - buf, "\"pt\":[\n "); + for (n = 0; n < context->count_threads; n++) { + pt = &context->pt[n]; + if (n) + buf += lws_snprintf(buf, end - buf, ","); + buf += lws_snprintf(buf, end - buf, + "\n {\n" + " \"fds_count\":\"%d\",\n" + " \"ah_pool_inuse\":\"%d\",\n" + " \"ah_wait_list\":\"%d\"\n" + " }", + pt->fds_count, + pt->ah_count_in_use, + pt->ah_wait_list_length); + } + + buf += lws_snprintf(buf, end - buf, "]"); + + buf += lws_snprintf(buf, end - buf, ", \"vhosts\":[\n "); + + first = 1; + vh = context->vhost_list; + listening = 0; + cs = context->conn_stats; + lws_sum_stats(context, &cs); + while (vh) { + + if (!hide_vhosts) { + if (!first) + if(buf != end) + *buf++ = ','; + buf += lws_json_dump_vhost(vh, buf, end - buf); + first = 0; + } + if (vh->lserv_wsi) + listening++; + vh = vh->vhost_next; + } + + buf += lws_snprintf(buf, end - buf, + "],\n\"listen_wsi\":\"%d\",\n" + " \"rx\":\"%llu\",\n" + " \"tx\":\"%llu\",\n" + " \"h1_conn\":\"%lu\",\n" + " \"h1_trans\":\"%lu\",\n" + " \"h2_trans\":\"%lu\",\n" + " \"ws_upg\":\"%lu\",\n" + " \"rejected\":\"%lu\",\n" + " \"h2_alpn\":\"%lu\",\n" + " \"h2_subs\":\"%lu\",\n" + " \"h2_upg\":\"%lu\"", + listening, cs.rx, cs.tx, + cs.h1_conn, + cs.h1_trans, + cs.h2_trans, + cs.ws_upg, + cs.rejected, + cs.h2_alpn, + cs.h2_subs, + cs.h2_upg); + +#ifdef LWS_WITH_CGI + for (n = 0; n < context->count_threads; n++) { + pt = &context->pt[n]; + pcgi = &pt->cgi_list; + + while (*pcgi) { + pcgi = &(*pcgi)->cgi_list; + + cgi_count++; + } + } +#endif + buf += lws_snprintf(buf, end - buf, ",\n \"cgi_alive\":\"%d\"\n ", + cgi_count); + + buf += lws_snprintf(buf, end - buf, "}"); + + + buf += lws_snprintf(buf, end - buf, "]}\n "); + + return buf - orig; +} + +#endif + +#if defined(LWS_WITH_STATS) + +LWS_VISIBLE LWS_EXTERN uint64_t +lws_stats_get(struct lws_context *context, int index) +{ + if (index >= LWSSTATS_SIZE) + return 0; + + return context->lws_stats[index]; +} + +LWS_VISIBLE LWS_EXTERN void +lws_stats_log_dump(struct lws_context *context) +{ + struct lws_vhost *v = context->vhost_list; + int n, m; + + (void)m; + + if (!context->updated) + return; + + context->updated = 0; + + lwsl_notice("\n"); + lwsl_notice("LWS internal statistics dump ----->\n"); + lwsl_notice("LWSSTATS_C_CONNECTIONS: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_CONNECTIONS)); + lwsl_notice("LWSSTATS_C_API_CLOSE: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_CLOSE)); + lwsl_notice("LWSSTATS_C_API_READ: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_READ)); + lwsl_notice("LWSSTATS_C_API_LWS_WRITE: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_LWS_WRITE)); + lwsl_notice("LWSSTATS_C_API_WRITE: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_API_WRITE)); + lwsl_notice("LWSSTATS_C_WRITE_PARTIALS: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITE_PARTIALS)); + lwsl_notice("LWSSTATS_C_WRITEABLE_CB_REQ: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB_REQ)); + lwsl_notice("LWSSTATS_C_WRITEABLE_CB_EFF_REQ: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB_EFF_REQ)); + lwsl_notice("LWSSTATS_C_WRITEABLE_CB: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB)); + lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN)); + lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_FAILED: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_FAILED)); + lwsl_notice("LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)); + lwsl_notice("LWSSTATS_C_SSL_CONNS_HAD_RX: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX)); + lwsl_notice("LWSSTATS_C_PEER_LIMIT_AH_DENIED: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_PEER_LIMIT_AH_DENIED)); + lwsl_notice("LWSSTATS_C_PEER_LIMIT_WSI_DENIED: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_PEER_LIMIT_WSI_DENIED)); + + lwsl_notice("LWSSTATS_C_TIMEOUTS: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_TIMEOUTS)); + lwsl_notice("LWSSTATS_C_SERVICE_ENTRY: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_C_SERVICE_ENTRY)); + lwsl_notice("LWSSTATS_B_READ: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_READ)); + lwsl_notice("LWSSTATS_B_WRITE: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_WRITE)); + lwsl_notice("LWSSTATS_B_PARTIALS_ACCEPTED_PARTS: %8llu\n", (unsigned long long)lws_stats_get(context, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS)); + lwsl_notice("LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY: %8llums\n", (unsigned long long)lws_stats_get(context, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) / 1000); + if (lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)) + lwsl_notice(" Avg accept delay: %8llums\n", + (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY) / + lws_stats_get(context, LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED)) / 1000); + lwsl_notice("LWSSTATS_MS_SSL_RX_DELAY: %8llums\n", (unsigned long long)lws_stats_get(context, LWSSTATS_MS_SSL_RX_DELAY) / 1000); + if (lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX)) + lwsl_notice(" Avg accept-rx delay: %8llums\n", + (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_SSL_RX_DELAY) / + lws_stats_get(context, LWSSTATS_C_SSL_CONNS_HAD_RX)) / 1000); + + lwsl_notice("LWSSTATS_MS_WRITABLE_DELAY: %8lluus\n", + (unsigned long long)lws_stats_get(context, LWSSTATS_MS_WRITABLE_DELAY)); + lwsl_notice("LWSSTATS_MS_WORST_WRITABLE_DELAY: %8lluus\n", + (unsigned long long)lws_stats_get(context, LWSSTATS_MS_WORST_WRITABLE_DELAY)); + if (lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB)) + lwsl_notice(" Avg writable delay: %8lluus\n", + (unsigned long long)(lws_stats_get(context, LWSSTATS_MS_WRITABLE_DELAY) / + lws_stats_get(context, LWSSTATS_C_WRITEABLE_CB))); + lwsl_notice("Simultaneous SSL restriction: %8d/%d/%d\n", context->simultaneous_ssl, + context->simultaneous_ssl_restriction, context->ssl_gate_accepts); + + lwsl_notice("Live wsi: %8d\n", context->count_wsi_allocated); + + context->updated = 1; + + while (v) { + if (v->lserv_wsi) { + + struct lws_context_per_thread *pt = &context->pt[(int)v->lserv_wsi->tsi]; + struct lws_pollfd *pfd; + + pfd = &pt->fds[v->lserv_wsi->position_in_fds_table]; + + lwsl_notice(" Listen port %d actual POLLIN: %d\n", + v->listen_port, (int)pfd->events & LWS_POLLIN); + } + + v = v->vhost_next; + } + + for (n = 0; n < context->count_threads; n++) { + struct lws_context_per_thread *pt = &context->pt[n]; + struct lws *wl; + int m = 0; + + lwsl_notice("PT %d\n", n + 1); + + lws_pt_lock(pt); + + lwsl_notice(" AH in use / max: %d / %d\n", + pt->ah_count_in_use, + context->max_http_header_pool); + + wl = pt->ah_wait_list; + while (wl) { + m++; + wl = wl->u.hdr.ah_wait_list; + } + + lwsl_notice(" AH wait list count / actual: %d / %d\n", + pt->ah_wait_list_length, m); + + lws_pt_unlock(pt); + } + +#if defined(LWS_WITH_PEER_LIMITS) + m = 0; + for (n = 0; n < (int)context->pl_hash_elements; n++) { + lws_start_foreach_llp(struct lws_peer **, peer, + context->pl_hash_table[n]) { + m++; + } lws_end_foreach_llp(peer, next); + } + + lwsl_notice(" Peers: total active %d\n", m); + if (m > 10) { + m = 10; + lwsl_notice(" (showing 10 peers only)\n"); + } + + if (m) { + for (n = 0; n < (int)context->pl_hash_elements; n++) { + char buf[72]; + + lws_start_foreach_llp(struct lws_peer **, peer, context->pl_hash_table[n]) { + struct lws_peer *df = *peer; + + if (!lws_plat_inet_ntop(df->af, df->addr, buf, + sizeof(buf) - 1)) + strcpy(buf, "unknown"); + + lwsl_notice(" peer %s: count wsi: %d, count ah: %d\n", + buf, df->count_wsi, df->count_ah); + + if (!--m) + break; + } lws_end_foreach_llp(peer, next); + } + } +#endif + + lwsl_notice("\n"); +} + +void +lws_stats_atomic_bump(struct lws_context * context, + struct lws_context_per_thread *pt, int index, uint64_t bump) +{ + lws_pt_lock(pt); + context->lws_stats[index] += bump; + if (index != LWSSTATS_C_SERVICE_ENTRY) + context->updated = 1; + lws_pt_unlock(pt); +} + +void +lws_stats_atomic_max(struct lws_context * context, + struct lws_context_per_thread *pt, int index, uint64_t val) +{ + lws_pt_lock(pt); + if (val > context->lws_stats[index]) { + context->lws_stats[index] = val; + context->updated = 1; + } + lws_pt_unlock(pt); +} + +#endif + diff --git a/thirdparty/lws/libwebsockets.h b/thirdparty/lws/libwebsockets.h new file mode 100644 index 0000000000..ef996c5d78 --- /dev/null +++ b/thirdparty/lws/libwebsockets.h @@ -0,0 +1,5790 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2016 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +/** @file */ + +#ifndef LIBWEBSOCKET_H_3060898B846849FF9F88F5DB59B5950C +#define LIBWEBSOCKET_H_3060898B846849FF9F88F5DB59B5950C + +#ifdef __cplusplus +#include <cstddef> +#include <cstdarg> +# +extern "C" { +#else +#include <stdarg.h> +#endif + +#include "lws_config.h" + +/* + * CARE: everything using cmake defines needs to be below here + */ + +#if defined(LWS_WITH_ESP8266) +struct sockaddr_in; +#define LWS_POSIX 0 +#else +#define LWS_POSIX 1 +#endif + +#if defined(LWS_HAS_INTPTR_T) +#include <stdint.h> +#define lws_intptr_t intptr_t +#else +typedef unsigned long long lws_intptr_t; +#endif + +#if defined(WIN32) || defined(_WIN32) +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#include <winsock2.h> +#include <ws2tcpip.h> +#include <stddef.h> +#include <basetsd.h> +#ifndef _WIN32_WCE +#include <fcntl.h> +#else +#define _O_RDONLY 0x0000 +#define O_RDONLY _O_RDONLY +#endif + +// Visual studio older than 2015 and WIN_CE has only _stricmp +#if (defined(_MSC_VER) && _MSC_VER < 1900) || defined(_WIN32_WCE) +#define strcasecmp _stricmp +#elif !defined(__MINGW32__) +#define strcasecmp stricmp +#endif +#define getdtablesize() 30000 + +#define LWS_INLINE __inline +#define LWS_VISIBLE +#define LWS_WARN_UNUSED_RESULT +#define LWS_WARN_DEPRECATED +#define LWS_FORMAT(string_index) + +#ifdef LWS_DLL +#ifdef LWS_INTERNAL +#define LWS_EXTERN extern __declspec(dllexport) +#else +#define LWS_EXTERN extern __declspec(dllimport) +#endif +#else +#define LWS_EXTERN +#endif + +#define LWS_INVALID_FILE INVALID_HANDLE_VALUE +#define LWS_O_RDONLY _O_RDONLY +#define LWS_O_WRONLY _O_WRONLY +#define LWS_O_CREAT _O_CREAT +#define LWS_O_TRUNC _O_TRUNC + +#if !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER < 1900) /* Visual Studio 2015 already defines this in <stdio.h> */ +#define lws_snprintf _snprintf +#endif + +#ifndef __func__ +#define __func__ __FUNCTION__ +#endif + +#if !defined(__MINGW32__) &&(!defined(_MSC_VER) || _MSC_VER < 1900) && !defined(snprintf) +#define snprintf(buf,len, format,...) _snprintf_s(buf, len,len, format, __VA_ARGS__) +#endif + +#else /* NOT WIN32 */ +#include <unistd.h> +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) +#include <sys/capability.h> +#endif + +#if defined(__NetBSD__) || defined(__FreeBSD__) +#include <netinet/in.h> +#endif + +#define LWS_INLINE inline +#define LWS_O_RDONLY O_RDONLY +#define LWS_O_WRONLY O_WRONLY +#define LWS_O_CREAT O_CREAT +#define LWS_O_TRUNC O_TRUNC + +#if !defined(LWS_WITH_ESP8266) && !defined(OPTEE_TA) && !defined(LWS_WITH_ESP32) +#include <poll.h> +#include <netdb.h> +#define LWS_INVALID_FILE -1 +#else +#define getdtablesize() (30) +#if defined(LWS_WITH_ESP32) +#define LWS_INVALID_FILE NULL +#else +#define LWS_INVALID_FILE NULL +#endif +#endif + +#if defined(__GNUC__) + +/* warn_unused_result attribute only supported by GCC 3.4 or later */ +#if __GNUC__ >= 4 || (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) +#define LWS_WARN_UNUSED_RESULT __attribute__((warn_unused_result)) +#else +#define LWS_WARN_UNUSED_RESULT +#endif + +#define LWS_VISIBLE __attribute__((visibility("default"))) +#define LWS_WARN_DEPRECATED __attribute__ ((deprecated)) +#define LWS_FORMAT(string_index) __attribute__ ((format(printf, string_index, string_index+1))) +#else +#define LWS_VISIBLE +#define LWS_WARN_UNUSED_RESULT +#define LWS_WARN_DEPRECATED +#define LWS_FORMAT(string_index) +#endif + +#if defined(__ANDROID__) +#include <unistd.h> +#define getdtablesize() sysconf(_SC_OPEN_MAX) +#endif + +#endif + +#ifdef LWS_WITH_LIBEV +#include <ev.h> +#endif /* LWS_WITH_LIBEV */ +#ifdef LWS_WITH_LIBUV +#include <uv.h> +#ifdef LWS_HAVE_UV_VERSION_H +#include <uv-version.h> +#endif +#endif /* LWS_WITH_LIBUV */ +#ifdef LWS_WITH_LIBEVENT +#include <event2/event.h> +#endif /* LWS_WITH_LIBEVENT */ + +#ifndef LWS_EXTERN +#define LWS_EXTERN extern +#endif + +#ifdef _WIN32 +#define random rand +#else +#if !defined(OPTEE_TA) +#include <sys/time.h> +#include <unistd.h> +#endif +#endif + +#ifdef LWS_OPENSSL_SUPPORT + +#ifdef USE_WOLFSSL +#ifdef USE_OLD_CYASSL +#include <cyassl/openssl/ssl.h> +#include <cyassl/error-ssl.h> +#else +#include <wolfssl/openssl/ssl.h> +#include <wolfssl/error-ssl.h> +#endif /* not USE_OLD_CYASSL */ +#else +#if defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_ESP32) +/* this filepath is passed to us but without quotes or <> */ +#undef MBEDTLS_CONFIG_FILE +#define MBEDTLS_CONFIG_FILE <mbedtls/esp_config.h> +#endif +#include <mbedtls/ssl.h> +#endif +#include <openssl/ssl.h> +#if !defined(LWS_WITH_MBEDTLS) +#include <openssl/err.h> +#endif +#endif /* not USE_WOLFSSL */ +#endif + + +#define CONTEXT_PORT_NO_LISTEN -1 +#define CONTEXT_PORT_NO_LISTEN_SERVER -2 + +/** \defgroup log Logging + * + * ##Logging + * + * Lws provides flexible and filterable logging facilities, which can be + * used inside lws and in user code. + * + * Log categories may be individually filtered bitwise, and directed to built-in + * sinks for syslog-compatible logging, or a user-defined function. + */ +///@{ + +enum lws_log_levels { + LLL_ERR = 1 << 0, + LLL_WARN = 1 << 1, + LLL_NOTICE = 1 << 2, + LLL_INFO = 1 << 3, + LLL_DEBUG = 1 << 4, + LLL_PARSER = 1 << 5, + LLL_HEADER = 1 << 6, + LLL_EXT = 1 << 7, + LLL_CLIENT = 1 << 8, + LLL_LATENCY = 1 << 9, + LLL_USER = 1 << 10, + + LLL_COUNT = 11 /* set to count of valid flags */ +}; + +LWS_VISIBLE LWS_EXTERN void _lws_log(int filter, const char *format, ...) LWS_FORMAT(2); +LWS_VISIBLE LWS_EXTERN void _lws_logv(int filter, const char *format, va_list vl); +/** + * lwsl_timestamp: generate logging timestamp string + * + * \param level: logging level + * \param p: char * buffer to take timestamp + * \param len: length of p + * + * returns length written in p + */ +LWS_VISIBLE LWS_EXTERN int +lwsl_timestamp(int level, char *p, int len); + +/* these guys are unconditionally included */ + +#define lwsl_err(...) _lws_log(LLL_ERR, __VA_ARGS__) +#define lwsl_user(...) _lws_log(LLL_USER, __VA_ARGS__) + +#if !defined(LWS_WITH_NO_LOGS) +/* notice and warn are usually included by being compiled in */ +#define lwsl_warn(...) _lws_log(LLL_WARN, __VA_ARGS__) +#define lwsl_notice(...) _lws_log(LLL_NOTICE, __VA_ARGS__) +#endif +/* + * weaker logging can be deselected by telling CMake to build in RELEASE mode + * that gets rid of the overhead of checking while keeping _warn and _err + * active + */ + +#if defined(LWS_WITH_ESP8266) +#undef _DEBUG +#endif + +#ifdef _DEBUG +#if defined(LWS_WITH_NO_LOGS) +/* notice, warn and log are always compiled in */ +#define lwsl_warn(...) _lws_log(LLL_WARN, __VA_ARGS__) +#define lwsl_notice(...) _lws_log(LLL_NOTICE, __VA_ARGS__) +#endif +#define lwsl_info(...) _lws_log(LLL_INFO, __VA_ARGS__) +#define lwsl_debug(...) _lws_log(LLL_DEBUG, __VA_ARGS__) +#define lwsl_parser(...) _lws_log(LLL_PARSER, __VA_ARGS__) +#define lwsl_header(...) _lws_log(LLL_HEADER, __VA_ARGS__) +#define lwsl_ext(...) _lws_log(LLL_EXT, __VA_ARGS__) +#define lwsl_client(...) _lws_log(LLL_CLIENT, __VA_ARGS__) +#define lwsl_latency(...) _lws_log(LLL_LATENCY, __VA_ARGS__) + +#else /* no debug */ +#if defined(LWS_WITH_NO_LOGS) +#define lwsl_warn(...) do {} while(0) +#define lwsl_notice(...) do {} while(0) +#endif +#define lwsl_info(...) do {} while(0) +#define lwsl_debug(...) do {} while(0) +#define lwsl_parser(...) do {} while(0) +#define lwsl_header(...) do {} while(0) +#define lwsl_ext(...) do {} while(0) +#define lwsl_client(...) do {} while(0) +#define lwsl_latency(...) do {} while(0) + +#endif + +/** + * lwsl_hexdump() - helper to hexdump a buffer + * + * \param level: one of LLL_ constants + * \param buf: buffer start to dump + * \param len: length of buffer to dump + * + * If \p level is visible, does a nice hexdump -C style dump of \p buf for + * \p len bytes. This can be extremely convenient while debugging. + */ +LWS_VISIBLE LWS_EXTERN void +lwsl_hexdump_level(int level, const void *vbuf, size_t len); + +/** + * lwsl_hexdump() - helper to hexdump a buffer (DEBUG builds only) + * + * \param buf: buffer start to dump + * \param len: length of buffer to dump + * + * Calls through to lwsl_hexdump_level(LLL_DEBUG, ... for compatability. + * It's better to use lwsl_hexdump_level(level, ... directly so you can control + * the visibility. + */ +LWS_VISIBLE LWS_EXTERN void +lwsl_hexdump(const void *buf, size_t len); + +/** + * lws_is_be() - returns nonzero if the platform is Big Endian + */ +static LWS_INLINE int lws_is_be(void) { + const int probe = ~0xff; + + return *(const char *)&probe; +} + +/** + * lws_set_log_level() - Set the logging bitfield + * \param level: OR together the LLL_ debug contexts you want output from + * \param log_emit_function: NULL to leave it as it is, or a user-supplied + * function to perform log string emission instead of + * the default stderr one. + * + * log level defaults to "err", "warn" and "notice" contexts enabled and + * emission on stderr. If stderr is a tty (according to isatty()) then + * the output is coloured according to the log level using ANSI escapes. + */ +LWS_VISIBLE LWS_EXTERN void +lws_set_log_level(int level, + void (*log_emit_function)(int level, const char *line)); + +/** + * lwsl_emit_syslog() - helper log emit function writes to system log + * + * \param level: one of LLL_ log level indexes + * \param line: log string + * + * You use this by passing the function pointer to lws_set_log_level(), to set + * it as the log emit function, it is not called directly. + */ +LWS_VISIBLE LWS_EXTERN void +lwsl_emit_syslog(int level, const char *line); + +/** + * lwsl_visible() - returns true if the log level should be printed + * + * \param level: one of LLL_ log level indexes + * + * This is useful if you have to do work to generate the log content, you + * can skip the work if the log level used to print it is not actually + * enabled at runtime. + */ +LWS_VISIBLE LWS_EXTERN int +lwsl_visible(int level); + +///@} + + +#include <stddef.h> + +#ifndef lws_container_of +#define lws_container_of(P,T,M) ((T *)((char *)(P) - offsetof(T, M))) +#endif + + +struct lws; +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(x[0])) +#endif + +/* api change list for user code to test against */ + +#define LWS_FEATURE_SERVE_HTTP_FILE_HAS_OTHER_HEADERS_ARG + +/* the struct lws_protocols has the id field present */ +#define LWS_FEATURE_PROTOCOLS_HAS_ID_FIELD + +/* you can call lws_get_peer_write_allowance */ +#define LWS_FEATURE_PROTOCOLS_HAS_PEER_WRITE_ALLOWANCE + +/* extra parameter introduced in 917f43ab821 */ +#define LWS_FEATURE_SERVE_HTTP_FILE_HAS_OTHER_HEADERS_LEN + +/* File operations stuff exists */ +#define LWS_FEATURE_FOPS + + +#if defined(_WIN32) +typedef SOCKET lws_sockfd_type; +typedef HANDLE lws_filefd_type; +#define lws_sockfd_valid(sfd) (!!sfd) +struct lws_pollfd { + lws_sockfd_type fd; /**< file descriptor */ + SHORT events; /**< which events to respond to */ + SHORT revents; /**< which events happened */ +}; +#define LWS_POLLHUP (FD_CLOSE) +#define LWS_POLLIN (FD_READ | FD_ACCEPT) +#define LWS_POLLOUT (FD_WRITE) +#else + + +#if defined(LWS_WITH_ESP8266) + +#include <user_interface.h> +#include <espconn.h> + +typedef struct espconn * lws_sockfd_type; +typedef void * lws_filefd_type; +#define lws_sockfd_valid(sfd) (!!sfd) +struct pollfd { + lws_sockfd_type fd; /**< fd related to */ + short events; /**< which POLL... events to respond to */ + short revents; /**< which POLL... events occurred */ +}; +#define POLLIN 0x0001 +#define POLLPRI 0x0002 +#define POLLOUT 0x0004 +#define POLLERR 0x0008 +#define POLLHUP 0x0010 +#define POLLNVAL 0x0020 + +struct lws_vhost; + +lws_sockfd_type esp8266_create_tcp_listen_socket(struct lws_vhost *vh); +void esp8266_tcp_stream_accept(lws_sockfd_type fd, struct lws *wsi); + +#include <os_type.h> +#include <osapi.h> +#include "ets_sys.h" + +int ets_snprintf(char *str, size_t size, const char *format, ...) LWS_FORMAT(3); +#define snprintf ets_snprintf + +typedef os_timer_t uv_timer_t; +typedef void uv_cb_t(uv_timer_t *); + +void os_timer_disarm(void *); +void os_timer_setfn(os_timer_t *, os_timer_func_t *, void *); + +void ets_timer_arm_new(os_timer_t *, int, int, int); + +//void os_timer_arm(os_timer_t *, int, int); + +#define UV_VERSION_MAJOR 1 + +#define lws_uv_getloop(a, b) (NULL) + +static inline void uv_timer_init(void *l, uv_timer_t *t) +{ + (void)l; + memset(t, 0, sizeof(*t)); + os_timer_disarm(t); +} + +static inline void uv_timer_start(uv_timer_t *t, uv_cb_t *cb, int first, int rep) +{ + os_timer_setfn(t, (os_timer_func_t *)cb, t); + /* ms, repeat */ + os_timer_arm(t, first, !!rep); +} + +static inline void uv_timer_stop(uv_timer_t *t) +{ + os_timer_disarm(t); +} + +#else +#if defined(LWS_WITH_ESP32) + +typedef int lws_sockfd_type; +typedef int lws_filefd_type; +#define lws_sockfd_valid(sfd) (sfd >= 0) +struct pollfd { + lws_sockfd_type fd; /**< fd related to */ + short events; /**< which POLL... events to respond to */ + short revents; /**< which POLL... events occurred */ +}; +#define POLLIN 0x0001 +#define POLLPRI 0x0002 +#define POLLOUT 0x0004 +#define POLLERR 0x0008 +#define POLLHUP 0x0010 +#define POLLNVAL 0x0020 + +#include <freertos/FreeRTOS.h> +#include <freertos/event_groups.h> +#include <string.h> +#include "esp_wifi.h" +#include "esp_system.h" +#include "esp_event.h" +#include "esp_event_loop.h" +#include "nvs.h" +#include "driver/gpio.h" +#include "esp_spi_flash.h" +#include "freertos/timers.h" + +#if !defined(CONFIG_FREERTOS_HZ) +#define CONFIG_FREERTOS_HZ 100 +#endif + +typedef TimerHandle_t uv_timer_t; +typedef void uv_cb_t(uv_timer_t *); +typedef void * uv_handle_t; + +struct timer_mapping { + uv_cb_t *cb; + uv_timer_t *t; +}; + +#define UV_VERSION_MAJOR 1 + +#define lws_uv_getloop(a, b) (NULL) + +static inline void uv_timer_init(void *l, uv_timer_t *t) +{ + (void)l; + *t = NULL; +} + +extern void esp32_uvtimer_cb(TimerHandle_t t); + +static inline void uv_timer_start(uv_timer_t *t, uv_cb_t *cb, int first, int rep) +{ + struct timer_mapping *tm = (struct timer_mapping *)malloc(sizeof(*tm)); + + if (!tm) + return; + + tm->t = t; + tm->cb = cb; + + *t = xTimerCreate("x", pdMS_TO_TICKS(first), !!rep, tm, + (TimerCallbackFunction_t)esp32_uvtimer_cb); + xTimerStart(*t, 0); +} + +static inline void uv_timer_stop(uv_timer_t *t) +{ + xTimerStop(*t, 0); +} + +static inline void uv_close(uv_handle_t *h, void *v) +{ + free(pvTimerGetTimerID((uv_timer_t)h)); + xTimerDelete(*(uv_timer_t *)h, 0); +} + +/* ESP32 helper declarations */ + +#include <mdns.h> +#include <esp_partition.h> + +#define LWS_PLUGIN_STATIC +#define LWS_MAGIC_REBOOT_TYPE_ADS 0x50001ffc +#define LWS_MAGIC_REBOOT_TYPE_REQ_FACTORY 0xb00bcafe +#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY 0xfaceb00b +#define LWS_MAGIC_REBOOT_TYPE_FORCED_FACTORY_BUTTON 0xf0cedfac + + +/* user code provides these */ + +extern void +lws_esp32_identify_physical_device(void); + +/* lws-plat-esp32 provides these */ + +typedef void (*lws_cb_scan_done)(uint16_t count, wifi_ap_record_t *recs, void *arg); + +enum genled_state { + LWSESP32_GENLED__INIT, + LWSESP32_GENLED__LOST_NETWORK, + LWSESP32_GENLED__NO_NETWORK, + LWSESP32_GENLED__CONN_AP, + LWSESP32_GENLED__GOT_IP, + LWSESP32_GENLED__OK, +}; + +struct lws_group_member { + struct lws_group_member *next; + uint64_t last_seen; + char model[16]; + char role[16]; + char host[32]; + char mac[20]; + int width, height; + struct ip4_addr addr; + struct ip6_addr addrv6; + uint8_t flags; +}; + +#define LWS_SYSTEM_GROUP_MEMBER_ADD 1 +#define LWS_SYSTEM_GROUP_MEMBER_CHANGE 2 +#define LWS_SYSTEM_GROUP_MEMBER_REMOVE 3 + +#define LWS_GROUP_FLAG_SELF 1 + +struct lws_esp32 { + char sta_ip[16]; + char sta_mask[16]; + char sta_gw[16]; + char serial[16]; + char opts[16]; + char model[16]; + char group[16]; + char role[16]; + char ssid[4][16]; + char password[4][32]; + char active_ssid[32]; + char access_pw[16]; + char hostname[32]; + char mac[20]; + mdns_server_t *mdns; + char region; + char inet; + char conn_ap; + + enum genled_state genled; + uint64_t genled_t; + + lws_cb_scan_done scan_consumer; + void *scan_consumer_arg; + struct lws_group_member *first; + int extant_group_members; +}; + +struct lws_esp32_image { + uint32_t romfs; + uint32_t romfs_len; + uint32_t json; + uint32_t json_len; +}; + +extern struct lws_esp32 lws_esp32; +struct lws_vhost; + +extern esp_err_t +lws_esp32_event_passthru(void *ctx, system_event_t *event); +extern void +lws_esp32_wlan_config(void); +extern void +lws_esp32_wlan_start_ap(void); +extern void +lws_esp32_wlan_start_station(void); +struct lws_context_creation_info; +extern void +lws_esp32_set_creation_defaults(struct lws_context_creation_info *info); +extern struct lws_context * +lws_esp32_init(struct lws_context_creation_info *, struct lws_vhost **pvh); +extern int +lws_esp32_wlan_nvs_get(int retry); +extern esp_err_t +lws_nvs_set_str(nvs_handle handle, const char* key, const char* value); +extern void +lws_esp32_restart_guided(uint32_t type); +extern const esp_partition_t * +lws_esp_ota_get_boot_partition(void); +extern int +lws_esp32_get_image_info(const esp_partition_t *part, struct lws_esp32_image *i, char *json, int json_len); +extern int +lws_esp32_leds_network_indication(void); + +extern uint32_t lws_esp32_get_reboot_type(void); +extern uint16_t lws_esp32_sine_interp(int n); + +/* required in external code by esp32 plat (may just return if no leds) */ +extern void lws_esp32_leds_timer_cb(TimerHandle_t th); +#else +typedef int lws_sockfd_type; +typedef int lws_filefd_type; +#define lws_sockfd_valid(sfd) (sfd >= 0) +#endif +#endif + +#define lws_pollfd pollfd +#define LWS_POLLHUP (POLLHUP|POLLERR) +#define LWS_POLLIN (POLLIN) +#define LWS_POLLOUT (POLLOUT) +#endif + + +#if (defined(WIN32) || defined(_WIN32)) && !defined(__MINGW32__) +/* ... */ +#define ssize_t SSIZE_T +#endif + +#if defined(WIN32) && defined(LWS_HAVE__STAT32I64) +#include <sys/types.h> +#include <sys/stat.h> +#endif + +#if defined(LWS_HAVE_STDINT_H) +#include <stdint.h> +#else +#if defined(WIN32) || defined(_WIN32) +/* !!! >:-[ */ +typedef unsigned __int32 uint32_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int8 uint8_t; +#else +typedef unsigned int uint32_t; +typedef unsigned short uint16_t; +typedef unsigned char uint8_t; +#endif +#endif + +typedef unsigned long long lws_filepos_t; +typedef long long lws_fileofs_t; +typedef uint32_t lws_fop_flags_t; + +/** struct lws_pollargs - argument structure for all external poll related calls + * passed in via 'in' */ +struct lws_pollargs { + lws_sockfd_type fd; /**< applicable socket descriptor */ + int events; /**< the new event mask */ + int prev_events; /**< the previous event mask */ +}; + +struct lws_tokens; +struct lws_token_limits; + +/*! \defgroup wsclose Websocket Close + * + * ##Websocket close frame control + * + * When we close a ws connection, we can send a reason code and a short + * UTF-8 description back with the close packet. + */ +///@{ + +/* + * NOTE: These public enums are part of the abi. If you want to add one, + * add it at where specified so existing users are unaffected. + */ +/** enum lws_close_status - RFC6455 close status codes */ +enum lws_close_status { + LWS_CLOSE_STATUS_NOSTATUS = 0, + LWS_CLOSE_STATUS_NORMAL = 1000, + /**< 1000 indicates a normal closure, meaning that the purpose for + which the connection was established has been fulfilled. */ + LWS_CLOSE_STATUS_GOINGAWAY = 1001, + /**< 1001 indicates that an endpoint is "going away", such as a server + going down or a browser having navigated away from a page. */ + LWS_CLOSE_STATUS_PROTOCOL_ERR = 1002, + /**< 1002 indicates that an endpoint is terminating the connection due + to a protocol error. */ + LWS_CLOSE_STATUS_UNACCEPTABLE_OPCODE = 1003, + /**< 1003 indicates that an endpoint is terminating the connection + because it has received a type of data it cannot accept (e.g., an + endpoint that understands only text data MAY send this if it + receives a binary message). */ + LWS_CLOSE_STATUS_RESERVED = 1004, + /**< Reserved. The specific meaning might be defined in the future. */ + LWS_CLOSE_STATUS_NO_STATUS = 1005, + /**< 1005 is a reserved value and MUST NOT be set as a status code in a + Close control frame by an endpoint. It is designated for use in + applications expecting a status code to indicate that no status + code was actually present. */ + LWS_CLOSE_STATUS_ABNORMAL_CLOSE = 1006, + /**< 1006 is a reserved value and MUST NOT be set as a status code in a + Close control frame by an endpoint. It is designated for use in + applications expecting a status code to indicate that the + connection was closed abnormally, e.g., without sending or + receiving a Close control frame. */ + LWS_CLOSE_STATUS_INVALID_PAYLOAD = 1007, + /**< 1007 indicates that an endpoint is terminating the connection + because it has received data within a message that was not + consistent with the type of the message (e.g., non-UTF-8 [RFC3629] + data within a text message). */ + LWS_CLOSE_STATUS_POLICY_VIOLATION = 1008, + /**< 1008 indicates that an endpoint is terminating the connection + because it has received a message that violates its policy. This + is a generic status code that can be returned when there is no + other more suitable status code (e.g., 1003 or 1009) or if there + is a need to hide specific details about the policy. */ + LWS_CLOSE_STATUS_MESSAGE_TOO_LARGE = 1009, + /**< 1009 indicates that an endpoint is terminating the connection + because it has received a message that is too big for it to + process. */ + LWS_CLOSE_STATUS_EXTENSION_REQUIRED = 1010, + /**< 1010 indicates that an endpoint (client) is terminating the + connection because it has expected the server to negotiate one or + more extension, but the server didn't return them in the response + message of the WebSocket handshake. The list of extensions that + are needed SHOULD appear in the /reason/ part of the Close frame. + Note that this status code is not used by the server, because it + can fail the WebSocket handshake instead */ + LWS_CLOSE_STATUS_UNEXPECTED_CONDITION = 1011, + /**< 1011 indicates that a server is terminating the connection because + it encountered an unexpected condition that prevented it from + fulfilling the request. */ + LWS_CLOSE_STATUS_TLS_FAILURE = 1015, + /**< 1015 is a reserved value and MUST NOT be set as a status code in a + Close control frame by an endpoint. It is designated for use in + applications expecting a status code to indicate that the + connection was closed due to a failure to perform a TLS handshake + (e.g., the server certificate can't be verified). */ + + /****** add new things just above ---^ ******/ + + LWS_CLOSE_STATUS_NOSTATUS_CONTEXT_DESTROY = 9999, +}; + +/** + * lws_close_reason - Set reason and aux data to send with Close packet + * If you are going to return nonzero from the callback + * requesting the connection to close, you can optionally + * call this to set the reason the peer will be told if + * possible. + * + * \param wsi: The websocket connection to set the close reason on + * \param status: A valid close status from websocket standard + * \param buf: NULL or buffer containing up to 124 bytes of auxiliary data + * \param len: Length of data in \param buf to send + */ +LWS_VISIBLE LWS_EXTERN void +lws_close_reason(struct lws *wsi, enum lws_close_status status, + unsigned char *buf, size_t len); + +///@} + +struct lws; +struct lws_context; +/* needed even with extensions disabled for create context */ +struct lws_extension; + +/*! \defgroup lwsmeta lws-meta + * + * ##lws-meta protocol + * + * The protocol wraps other muxed connections inside one tcp connection. + * + * Commands are assigned from 0x41 up (so they are valid unicode) + */ +///@{ + +enum lws_meta_commands { + LWS_META_CMD_OPEN_SUBCHANNEL = 'A', + /**< Client requests to open new subchannel + */ + LWS_META_CMD_OPEN_RESULT, + /**< Result of client request to open new subchannel */ + LWS_META_CMD_CLOSE_NOTIFY, + /**< Notification of subchannel closure */ + LWS_META_CMD_CLOSE_RQ, + /**< client requests to close a subchannel */ + LWS_META_CMD_WRITE, + /**< connection writes something to specific channel index */ + + /****** add new things just above ---^ ******/ +}; + +/* channel numbers are transported offset by 0x20 so they are valid unicode */ + +#define LWS_META_TRANSPORT_OFFSET 0x20 + +///@} + +/*! \defgroup usercb User Callback + * + * ##User protocol callback + * + * The protocol callback is the primary way lws interacts with + * user code. For one of a list of a few dozen reasons the callback gets + * called at some event to be handled. + * + * All of the events can be ignored, returning 0 is taken as "OK" and returning + * nonzero in most cases indicates that the connection should be closed. + */ +///@{ + +struct lws_ssl_info { + int where; + int ret; +}; + +/* + * NOTE: These public enums are part of the abi. If you want to add one, + * add it at where specified so existing users are unaffected. + */ +/** enum lws_callback_reasons - reason you're getting a protocol callback */ +enum lws_callback_reasons { + LWS_CALLBACK_ESTABLISHED = 0, + /**< (VH) after the server completes a handshake with an incoming + * client. If you built the library with ssl support, in is a + * pointer to the ssl struct associated with the connection or NULL.*/ + LWS_CALLBACK_CLIENT_CONNECTION_ERROR = 1, + /**< the request client connection has been unable to complete a + * handshake with the remote server. If in is non-NULL, you can + * find an error string of length len where it points to + * + * Diagnostic strings that may be returned include + * + * "getaddrinfo (ipv6) failed" + * "unknown address family" + * "getaddrinfo (ipv4) failed" + * "set socket opts failed" + * "insert wsi failed" + * "lws_ssl_client_connect1 failed" + * "lws_ssl_client_connect2 failed" + * "Peer hung up" + * "read failed" + * "HS: URI missing" + * "HS: Redirect code but no Location" + * "HS: URI did not parse" + * "HS: Redirect failed" + * "HS: Server did not return 200" + * "HS: OOM" + * "HS: disallowed by client filter" + * "HS: disallowed at ESTABLISHED" + * "HS: ACCEPT missing" + * "HS: ws upgrade response not 101" + * "HS: UPGRADE missing" + * "HS: Upgrade to something other than websocket" + * "HS: CONNECTION missing" + * "HS: UPGRADE malformed" + * "HS: PROTOCOL malformed" + * "HS: Cannot match protocol" + * "HS: EXT: list too big" + * "HS: EXT: failed setting defaults" + * "HS: EXT: failed parsing defaults" + * "HS: EXT: failed parsing options" + * "HS: EXT: Rejects server options" + * "HS: EXT: unknown ext" + * "HS: Accept hash wrong" + * "HS: Rejected by filter cb" + * "HS: OOM" + * "HS: SO_SNDBUF failed" + * "HS: Rejected at CLIENT_ESTABLISHED" + */ + LWS_CALLBACK_CLIENT_FILTER_PRE_ESTABLISH = 2, + /**< this is the last chance for the client user code to examine the + * http headers and decide to reject the connection. If the + * content in the headers is interesting to the + * client (url, etc) it needs to copy it out at + * this point since it will be destroyed before + * the CLIENT_ESTABLISHED call */ + LWS_CALLBACK_CLIENT_ESTABLISHED = 3, + /**< after your client connection completed + * a handshake with the remote server */ + LWS_CALLBACK_CLOSED = 4, + /**< when the websocket session ends */ + LWS_CALLBACK_CLOSED_HTTP = 5, + /**< when a HTTP (non-websocket) session ends */ + LWS_CALLBACK_RECEIVE = 6, + /**< data has appeared for this server endpoint from a + * remote client, it can be found at *in and is + * len bytes long */ + LWS_CALLBACK_RECEIVE_PONG = 7, + /**< servers receive PONG packets with this callback reason */ + LWS_CALLBACK_CLIENT_RECEIVE = 8, + /**< data has appeared from the server for the client connection, it + * can be found at *in and is len bytes long */ + LWS_CALLBACK_CLIENT_RECEIVE_PONG = 9, + /**< clients receive PONG packets with this callback reason */ + LWS_CALLBACK_CLIENT_WRITEABLE = 10, + /**< If you call lws_callback_on_writable() on a connection, you will + * get one of these callbacks coming when the connection socket + * is able to accept another write packet without blocking. + * If it already was able to take another packet without blocking, + * you'll get this callback at the next call to the service loop + * function. Notice that CLIENTs get LWS_CALLBACK_CLIENT_WRITEABLE + * and servers get LWS_CALLBACK_SERVER_WRITEABLE. */ + LWS_CALLBACK_SERVER_WRITEABLE = 11, + /**< See LWS_CALLBACK_CLIENT_WRITEABLE */ + LWS_CALLBACK_HTTP = 12, + /**< an http request has come from a client that is not + * asking to upgrade the connection to a websocket + * one. This is a chance to serve http content, + * for example, to send a script to the client + * which will then open the websockets connection. + * in points to the URI path requested and + * lws_serve_http_file() makes it very + * simple to send back a file to the client. + * Normally after sending the file you are done + * with the http connection, since the rest of the + * activity will come by websockets from the script + * that was delivered by http, so you will want to + * return 1; to close and free up the connection. */ + LWS_CALLBACK_HTTP_BODY = 13, + /**< the next len bytes data from the http + * request body HTTP connection is now available in in. */ + LWS_CALLBACK_HTTP_BODY_COMPLETION = 14, + /**< the expected amount of http request body has been delivered */ + LWS_CALLBACK_HTTP_FILE_COMPLETION = 15, + /**< a file requested to be sent down http link has completed. */ + LWS_CALLBACK_HTTP_WRITEABLE = 16, + /**< you can write more down the http protocol link now. */ + LWS_CALLBACK_FILTER_NETWORK_CONNECTION = 17, + /**< called when a client connects to + * the server at network level; the connection is accepted but then + * passed to this callback to decide whether to hang up immediately + * or not, based on the client IP. in contains the connection + * socket's descriptor. Since the client connection information is + * not available yet, wsi still pointing to the main server socket. + * Return non-zero to terminate the connection before sending or + * receiving anything. Because this happens immediately after the + * network connection from the client, there's no websocket protocol + * selected yet so this callback is issued only to protocol 0. */ + LWS_CALLBACK_FILTER_HTTP_CONNECTION = 18, + /**< called when the request has + * been received and parsed from the client, but the response is + * not sent yet. Return non-zero to disallow the connection. + * user is a pointer to the connection user space allocation, + * in is the URI, eg, "/" + * In your handler you can use the public APIs + * lws_hdr_total_length() / lws_hdr_copy() to access all of the + * headers using the header enums lws_token_indexes from + * libwebsockets.h to check for and read the supported header + * presence and content before deciding to allow the http + * connection to proceed or to kill the connection. */ + LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED = 19, + /**< A new client just had + * been connected, accepted, and instantiated into the pool. This + * callback allows setting any relevant property to it. Because this + * happens immediately after the instantiation of a new client, + * there's no websocket protocol selected yet so this callback is + * issued only to protocol 0. Only wsi is defined, pointing to the + * new client, and the return value is ignored. */ + LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION = 20, + /**< called when the handshake has + * been received and parsed from the client, but the response is + * not sent yet. Return non-zero to disallow the connection. + * user is a pointer to the connection user space allocation, + * in is the requested protocol name + * In your handler you can use the public APIs + * lws_hdr_total_length() / lws_hdr_copy() to access all of the + * headers using the header enums lws_token_indexes from + * libwebsockets.h to check for and read the supported header + * presence and content before deciding to allow the handshake + * to proceed or to kill the connection. */ + LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS = 21, + /**< if configured for + * including OpenSSL support, this callback allows your user code + * to perform extra SSL_CTX_load_verify_locations() or similar + * calls to direct OpenSSL where to find certificates the client + * can use to confirm the remote server identity. user is the + * OpenSSL SSL_CTX* */ + LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS = 22, + /**< if configured for + * including OpenSSL support, this callback allows your user code + * to load extra certifcates into the server which allow it to + * verify the validity of certificates returned by clients. user + * is the server's OpenSSL SSL_CTX* */ + LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION = 23, + /**< if the libwebsockets vhost was created with the option + * LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT, then this + * callback is generated during OpenSSL verification of the cert + * sent from the client. It is sent to protocol[0] callback as + * no protocol has been negotiated on the connection yet. + * Notice that the libwebsockets context and wsi are both NULL + * during this callback. See + * http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html + * to understand more detail about the OpenSSL callback that + * generates this libwebsockets callback and the meanings of the + * arguments passed. In this callback, user is the x509_ctx, + * in is the ssl pointer and len is preverify_ok + * Notice that this callback maintains libwebsocket return + * conventions, return 0 to mean the cert is OK or 1 to fail it. + * This also means that if you don't handle this callback then + * the default callback action of returning 0 allows the client + * certificates. */ + LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER = 24, + /**< this callback happens + * when a client handshake is being compiled. user is NULL, + * in is a char **, it's pointing to a char * which holds the + * next location in the header buffer where you can add + * headers, and len is the remaining space in the header buffer, + * which is typically some hundreds of bytes. So, to add a canned + * cookie, your handler code might look similar to: + * + * char **p = (char **)in; + * + * if (len < 100) + * return 1; + * + * *p += sprintf(*p, "Cookie: a=b\x0d\x0a"); + * + * return 0; + * + * Notice if you add anything, you just have to take care about + * the CRLF on the line you added. Obviously this callback is + * optional, if you don't handle it everything is fine. + * + * Notice the callback is coming to protocols[0] all the time, + * because there is no specific protocol negotiated yet. */ + LWS_CALLBACK_CONFIRM_EXTENSION_OKAY = 25, + /**< When the server handshake code + * sees that it does support a requested extension, before + * accepting the extension by additing to the list sent back to + * the client it gives this callback just to check that it's okay + * to use that extension. It calls back to the requested protocol + * and with in being the extension name, len is 0 and user is + * valid. Note though at this time the ESTABLISHED callback hasn't + * happened yet so if you initialize user content there, user + * content during this callback might not be useful for anything. */ + LWS_CALLBACK_CLIENT_CONFIRM_EXTENSION_SUPPORTED = 26, + /**< When a client + * connection is being prepared to start a handshake to a server, + * each supported extension is checked with protocols[0] callback + * with this reason, giving the user code a chance to suppress the + * claim to support that extension by returning non-zero. If + * unhandled, by default 0 will be returned and the extension + * support included in the header to the server. Notice this + * callback comes to protocols[0]. */ + LWS_CALLBACK_PROTOCOL_INIT = 27, + /**< One-time call per protocol, per-vhost using it, so it can + * do initial setup / allocations etc */ + LWS_CALLBACK_PROTOCOL_DESTROY = 28, + /**< One-time call per protocol, per-vhost using it, indicating + * this protocol won't get used at all after this callback, the + * vhost is getting destroyed. Take the opportunity to + * deallocate everything that was allocated by the protocol. */ + LWS_CALLBACK_WSI_CREATE = 29, + /**< outermost (earliest) wsi create notification to protocols[0] */ + LWS_CALLBACK_WSI_DESTROY = 30, + /**< outermost (latest) wsi destroy notification to protocols[0] */ + LWS_CALLBACK_GET_THREAD_ID = 31, + /**< lws can accept callback when writable requests from other + * threads, if you implement this callback and return an opaque + * current thread ID integer. */ + + /* external poll() management support */ + LWS_CALLBACK_ADD_POLL_FD = 32, + /**< lws normally deals with its poll() or other event loop + * internally, but in the case you are integrating with another + * server you will need to have lws sockets share a + * polling array with the other server. This and the other + * POLL_FD related callbacks let you put your specialized + * poll array interface code in the callback for protocol 0, the + * first protocol you support, usually the HTTP protocol in the + * serving case. + * This callback happens when a socket needs to be + * added to the polling loop: in points to a struct + * lws_pollargs; the fd member of the struct is the file + * descriptor, and events contains the active events + * + * If you are using the internal lws polling / event loop + * you can just ignore these callbacks. */ + LWS_CALLBACK_DEL_POLL_FD = 33, + /**< This callback happens when a socket descriptor + * needs to be removed from an external polling array. in is + * again the struct lws_pollargs containing the fd member + * to be removed. If you are using the internal polling + * loop, you can just ignore it. */ + LWS_CALLBACK_CHANGE_MODE_POLL_FD = 34, + /**< This callback happens when lws wants to modify the events for + * a connection. + * in is the struct lws_pollargs with the fd to change. + * The new event mask is in events member and the old mask is in + * the prev_events member. + * If you are using the internal polling loop, you can just ignore + * it. */ + LWS_CALLBACK_LOCK_POLL = 35, + /**< These allow the external poll changes driven + * by lws to participate in an external thread locking + * scheme around the changes, so the whole thing is threadsafe. + * These are called around three activities in the library, + * - inserting a new wsi in the wsi / fd table (len=1) + * - deleting a wsi from the wsi / fd table (len=1) + * - changing a wsi's POLLIN/OUT state (len=0) + * Locking and unlocking external synchronization objects when + * len == 1 allows external threads to be synchronized against + * wsi lifecycle changes if it acquires the same lock for the + * duration of wsi dereference from the other thread context. */ + LWS_CALLBACK_UNLOCK_POLL = 36, + /**< See LWS_CALLBACK_LOCK_POLL, ignore if using lws internal poll */ + + LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY = 37, + /**< if configured for including OpenSSL support but no private key + * file has been specified (ssl_private_key_filepath is NULL), this is + * called to allow the user to set the private key directly via + * libopenssl and perform further operations if required; this might be + * useful in situations where the private key is not directly accessible + * by the OS, for example if it is stored on a smartcard. + * user is the server's OpenSSL SSL_CTX* */ + LWS_CALLBACK_WS_PEER_INITIATED_CLOSE = 38, + /**< The peer has sent an unsolicited Close WS packet. in and + * len are the optional close code (first 2 bytes, network + * order) and the optional additional information which is not + * defined in the standard, and may be a string or non-human- readable data. + * If you return 0 lws will echo the close and then close the + * connection. If you return nonzero lws will just close the + * connection. */ + + LWS_CALLBACK_WS_EXT_DEFAULTS = 39, + /**< Gives client connections an opportunity to adjust negotiated + * extension defaults. `user` is the extension name that was + * negotiated (eg, "permessage-deflate"). `in` points to a + * buffer and `len` is the buffer size. The user callback can + * set the buffer to a string describing options the extension + * should parse. Or just ignore for defaults. */ + + LWS_CALLBACK_CGI = 40, + /**< CGI: CGI IO events on stdin / out / err are sent here on + * protocols[0]. The provided `lws_callback_http_dummy()` + * handles this and the callback should be directed there if + * you use CGI. */ + LWS_CALLBACK_CGI_TERMINATED = 41, + /**< CGI: The related CGI process ended, this is called before + * the wsi is closed. Used to, eg, terminate chunking. + * The provided `lws_callback_http_dummy()` + * handles this and the callback should be directed there if + * you use CGI. The child PID that terminated is in len. */ + LWS_CALLBACK_CGI_STDIN_DATA = 42, + /**< CGI: Data is, to be sent to the CGI process stdin, eg from + * a POST body. The provided `lws_callback_http_dummy()` + * handles this and the callback should be directed there if + * you use CGI. */ + LWS_CALLBACK_CGI_STDIN_COMPLETED = 43, + /**< CGI: no more stdin is coming. The provided + * `lws_callback_http_dummy()` handles this and the callback + * should be directed there if you use CGI. */ + LWS_CALLBACK_ESTABLISHED_CLIENT_HTTP = 44, + /**< The HTTP client connection has succeeded, and is now + * connected to the server */ + LWS_CALLBACK_CLOSED_CLIENT_HTTP = 45, + /**< The HTTP client connection is closing */ + LWS_CALLBACK_RECEIVE_CLIENT_HTTP = 46, + /**< This simply indicates data was received on the HTTP client + * connection. It does NOT drain or provide the data. + * This exists to neatly allow a proxying type situation, + * where this incoming data will go out on another connection. + * If the outgoing connection stalls, we should stall processing + * the incoming data. So a handler for this in that case should + * simply set a flag to indicate there is incoming data ready + * and ask for a writeable callback on the outgoing connection. + * In the writable callback he can check the flag and then get + * and drain the waiting incoming data using lws_http_client_read(). + * This will use callbacks to LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ + * to get and drain the incoming data, where it should be sent + * back out on the outgoing connection. */ + LWS_CALLBACK_COMPLETED_CLIENT_HTTP = 47, + /**< The client transaction completed... at the moment this + * is the same as closing since transaction pipelining on + * client side is not yet supported. */ + LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ = 48, + /**< This is generated by lws_http_client_read() used to drain + * incoming data. In the case the incoming data was chunked, + * it will be split into multiple smaller callbacks for each + * chunk block, removing the chunk headers. If not chunked, + * it will appear all in one callback. */ + LWS_CALLBACK_HTTP_BIND_PROTOCOL = 49, + /**< By default, all HTTP handling is done in protocols[0]. + * However you can bind different protocols (by name) to + * different parts of the URL space using callback mounts. This + * callback occurs in the new protocol when a wsi is bound + * to that protocol. Any protocol allocation related to the + * http transaction processing should be created then. + * These specific callbacks are necessary because with HTTP/1.1, + * a single connection may perform at series of different + * transactions at different URLs, thus the lifetime of the + * protocol bind is just for one transaction, not connection. */ + LWS_CALLBACK_HTTP_DROP_PROTOCOL = 50, + /**< This is called when a transaction is unbound from a protocol. + * It indicates the connection completed its transaction and may + * do something different now. Any protocol allocation related + * to the http transaction processing should be destroyed. */ + LWS_CALLBACK_CHECK_ACCESS_RIGHTS = 51, + /**< This gives the user code a chance to forbid an http access. + * `in` points to a `struct lws_process_html_args`, which + * describes the URL, and a bit mask describing the type of + * authentication required. If the callback returns nonzero, + * the transaction ends with HTTP_STATUS_UNAUTHORIZED. */ + LWS_CALLBACK_PROCESS_HTML = 52, + /**< This gives your user code a chance to mangle outgoing + * HTML. `in` points to a `struct lws_process_html_args` + * which describes the buffer containing outgoing HTML. + * The buffer may grow up to `.max_len` (currently +128 + * bytes per buffer). + * */ + LWS_CALLBACK_ADD_HEADERS = 53, + /**< This gives your user code a chance to add headers to a + * transaction bound to your protocol. `in` points to a + * `struct lws_process_html_args` describing a buffer and length + * you can add headers into using the normal lws apis. + * + * Only `args->p` and `args->len` are valid, and `args->p` should + * be moved on by the amount of bytes written, if any. Eg + * + * case LWS_CALLBACK_ADD_HEADERS: + * + * struct lws_process_html_args *args = + * (struct lws_process_html_args *)in; + * + * if (lws_add_http_header_by_name(wsi, + * (unsigned char *)"set-cookie:", + * (unsigned char *)cookie, cookie_len, + * (unsigned char **)&args->p, + * (unsigned char *)args->p + args->max_len)) + * return 1; + * + * break; + */ + LWS_CALLBACK_SESSION_INFO = 54, + /**< This is only generated by user code using generic sessions. + * It's used to get a `struct lws_session_info` filled in by + * generic sessions with information about the logged-in user. + * See the messageboard sample for an example of how to use. */ + + LWS_CALLBACK_GS_EVENT = 55, + /**< Indicates an event happened to the Generic Sessions session. + * `in` contains a `struct lws_gs_event_args` describing the event. */ + LWS_CALLBACK_HTTP_PMO = 56, + /**< per-mount options for this connection, called before + * the normal LWS_CALLBACK_HTTP when the mount has per-mount + * options. + */ + LWS_CALLBACK_CLIENT_HTTP_WRITEABLE = 57, + /**< when doing an HTTP type client connection, you can call + * lws_client_http_body_pending(wsi, 1) from + * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER to get these callbacks + * sending the HTTP headers. + * + * From this callback, when you have sent everything, you should let + * lws know by calling lws_client_http_body_pending(wsi, 0) + */ + LWS_CALLBACK_OPENSSL_PERFORM_SERVER_CERT_VERIFICATION = 58, + /**< Similar to LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION + * this callback is called during OpenSSL verification of the cert + * sent from the server to the client. It is sent to protocol[0] + * callback as no protocol has been negotiated on the connection yet. + * Notice that the wsi is set because lws_client_connect_via_info was + * successful. + * + * See http://www.openssl.org/docs/ssl/SSL_CTX_set_verify.html + * to understand more detail about the OpenSSL callback that + * generates this libwebsockets callback and the meanings of the + * arguments passed. In this callback, user is the x509_ctx, + * in is the ssl pointer and len is preverify_ok. + * + * THIS IS NOT RECOMMENDED BUT if a cert validation error shall be + * overruled and cert shall be accepted as ok, + * X509_STORE_CTX_set_error((X509_STORE_CTX*)user, X509_V_OK); must be + * called and return value must be 0 to mean the cert is OK; + * returning 1 will fail the cert in any case. + * + * This also means that if you don't handle this callback then + * the default callback action of returning 0 will not accept the + * certificate in case of a validation error decided by the SSL lib. + * + * This is expected and secure behaviour when validating certificates. + * + * Note: LCCSCF_ALLOW_SELFSIGNED and + * LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK still work without this + * callback being implemented. + */ + LWS_CALLBACK_RAW_RX = 59, + /**< RAW mode connection RX */ + LWS_CALLBACK_RAW_CLOSE = 60, + /**< RAW mode connection is closing */ + LWS_CALLBACK_RAW_WRITEABLE = 61, + /**< RAW mode connection may be written */ + LWS_CALLBACK_RAW_ADOPT = 62, + /**< RAW mode connection was adopted (equivalent to 'wsi created') */ + LWS_CALLBACK_RAW_ADOPT_FILE = 63, + /**< RAW mode file was adopted (equivalent to 'wsi created') */ + LWS_CALLBACK_RAW_RX_FILE = 64, + /**< RAW mode file has something to read */ + LWS_CALLBACK_RAW_WRITEABLE_FILE = 65, + /**< RAW mode file is writeable */ + LWS_CALLBACK_RAW_CLOSE_FILE = 66, + /**< RAW mode wsi that adopted a file is closing */ + LWS_CALLBACK_SSL_INFO = 67, + /**< SSL connections only. An event you registered an + * interest in at the vhost has occurred on a connection + * using the vhost. in is a pointer to a + * struct lws_ssl_info containing information about the + * event*/ + LWS_CALLBACK_CHILD_WRITE_VIA_PARENT = 68, + /**< Child has been marked with parent_carries_io attribute, so + * lws_write directs the to this callback at the parent, + * in is a struct lws_write_passthru containing the args + * the lws_write() was called with. + */ + LWS_CALLBACK_CHILD_CLOSING = 69, + /**< Sent to parent to notify them a child is closing / being + * destroyed. in is the child wsi. + */ + LWS_CALLBACK_CGI_PROCESS_ATTACH = 70, + /**< CGI: Sent when the CGI process is spawned for the wsi. The + * len parameter is the PID of the child process */ + + /****** add new things just above ---^ ******/ + + LWS_CALLBACK_USER = 1000, + /**< user code can use any including above without fear of clashes */ +}; + + + +/** + * typedef lws_callback_function() - User server actions + * \param wsi: Opaque websocket instance pointer + * \param reason: The reason for the call + * \param user: Pointer to per-session user data allocated by library + * \param in: Pointer used for some callback reasons + * \param len: Length set for some callback reasons + * + * This callback is the way the user controls what is served. All the + * protocol detail is hidden and handled by the library. + * + * For each connection / session there is user data allocated that is + * pointed to by "user". You set the size of this user data area when + * the library is initialized with lws_create_server. + */ +typedef int +lws_callback_function(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len); + +#define LWS_CB_REASON_AUX_BF__CGI 1 +#define LWS_CB_REASON_AUX_BF__PROXY 2 +#define LWS_CB_REASON_AUX_BF__CGI_CHUNK_END 4 +#define LWS_CB_REASON_AUX_BF__CGI_HEADERS 8 +///@} + +/*! \defgroup generic hash + * ## Generic Hash related functions + * + * Lws provides generic hash / digest accessors that abstract the ones + * provided by whatever OpenSSL library you are linking against. + * + * It lets you use the same code if you build against mbedtls or OpenSSL + * for example. + */ +///@{ + +#ifdef LWS_OPENSSL_SUPPORT + +#if defined(LWS_WITH_MBEDTLS) +#include <mbedtls/sha1.h> +#include <mbedtls/sha256.h> +#include <mbedtls/sha512.h> +#endif + +#define LWS_GENHASH_TYPE_SHA1 0 +#define LWS_GENHASH_TYPE_SHA256 1 +#define LWS_GENHASH_TYPE_SHA512 2 + +struct lws_genhash_ctx { + uint8_t type; +#if defined(LWS_WITH_MBEDTLS) + union { + mbedtls_sha1_context sha1; + mbedtls_sha256_context sha256; + mbedtls_sha512_context sha512; + } u; +#else + const EVP_MD *evp_type; + EVP_MD_CTX *mdctx; +#endif +}; + +/** lws_genhash_size() - get hash size in bytes + * + * \param type: one of LWS_GENHASH_TYPE_... + * + * Returns number of bytes in this type of hash + */ +LWS_VISIBLE LWS_EXTERN size_t LWS_WARN_UNUSED_RESULT +lws_genhash_size(int type); + +/** lws_genhash_init() - prepare your struct lws_genhash_ctx for use + * + * \param ctx: your struct lws_genhash_ctx + * \param type: one of LWS_GENHASH_TYPE_... + * + * Initializes the hash context for the type you requested + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_genhash_init(struct lws_genhash_ctx *ctx, int type); + +/** lws_genhash_update() - digest len bytes of the buffer starting at in + * + * \param ctx: your struct lws_genhash_ctx + * \param in: start of the bytes to digest + * \param len: count of bytes to digest + * + * Updates the state of your hash context to reflect digesting len bytes from in + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_genhash_update(struct lws_genhash_ctx *ctx, const void *in, size_t len); + +/** lws_genhash_destroy() - copy out the result digest and destroy the ctx + * + * \param ctx: your struct lws_genhash_ctx + * \param result: NULL, or where to copy the result hash + * + * Finalizes the hash and copies out the digest. Destroys any allocations such + * that ctx can safely go out of scope after calling this. + * + * NULL result is supported so that you can destroy the ctx cleanly on error + * conditions, where there is no valid result. + */ +LWS_VISIBLE LWS_EXTERN int +lws_genhash_destroy(struct lws_genhash_ctx *ctx, void *result); + +#endif + +///@} + +/*! \defgroup extensions Extension related functions + * ##Extension releated functions + * + * Ws defines optional extensions, lws provides the ability to implement these + * in user code if so desired. + * + * We provide one extensions permessage-deflate. + */ +///@{ + +/* + * NOTE: These public enums are part of the abi. If you want to add one, + * add it at where specified so existing users are unaffected. + */ +enum lws_extension_callback_reasons { + LWS_EXT_CB_SERVER_CONTEXT_CONSTRUCT = 0, + LWS_EXT_CB_CLIENT_CONTEXT_CONSTRUCT = 1, + LWS_EXT_CB_SERVER_CONTEXT_DESTRUCT = 2, + LWS_EXT_CB_CLIENT_CONTEXT_DESTRUCT = 3, + LWS_EXT_CB_CONSTRUCT = 4, + LWS_EXT_CB_CLIENT_CONSTRUCT = 5, + LWS_EXT_CB_CHECK_OK_TO_REALLY_CLOSE = 6, + LWS_EXT_CB_CHECK_OK_TO_PROPOSE_EXTENSION = 7, + LWS_EXT_CB_DESTROY = 8, + LWS_EXT_CB_DESTROY_ANY_WSI_CLOSING = 9, + LWS_EXT_CB_ANY_WSI_ESTABLISHED = 10, + LWS_EXT_CB_PACKET_RX_PREPARSE = 11, + LWS_EXT_CB_PACKET_TX_PRESEND = 12, + LWS_EXT_CB_PACKET_TX_DO_SEND = 13, + LWS_EXT_CB_HANDSHAKE_REPLY_TX = 14, + LWS_EXT_CB_FLUSH_PENDING_TX = 15, + LWS_EXT_CB_EXTENDED_PAYLOAD_RX = 16, + LWS_EXT_CB_CAN_PROXY_CLIENT_CONNECTION = 17, + LWS_EXT_CB_1HZ = 18, + LWS_EXT_CB_REQUEST_ON_WRITEABLE = 19, + LWS_EXT_CB_IS_WRITEABLE = 20, + LWS_EXT_CB_PAYLOAD_TX = 21, + LWS_EXT_CB_PAYLOAD_RX = 22, + LWS_EXT_CB_OPTION_DEFAULT = 23, + LWS_EXT_CB_OPTION_SET = 24, + LWS_EXT_CB_OPTION_CONFIRM = 25, + LWS_EXT_CB_NAMED_OPTION_SET = 26, + + /****** add new things just above ---^ ******/ +}; + +/** enum lws_ext_options_types */ +enum lws_ext_options_types { + EXTARG_NONE, /**< does not take an argument */ + EXTARG_DEC, /**< requires a decimal argument */ + EXTARG_OPT_DEC /**< may have an optional decimal argument */ + + /* Add new things just above here ---^ + * This is part of the ABI, don't needlessly break compatibility */ +}; + +/** struct lws_ext_options - Option arguments to the extension. These are + * used in the negotiation at ws upgrade time. + * The helper function lws_ext_parse_options() + * uses these to generate callbacks */ +struct lws_ext_options { + const char *name; /**< Option name, eg, "server_no_context_takeover" */ + enum lws_ext_options_types type; /**< What kind of args the option can take */ + + /* Add new things just above here ---^ + * This is part of the ABI, don't needlessly break compatibility */ +}; + +/** struct lws_ext_option_arg */ +struct lws_ext_option_arg { + const char *option_name; /**< may be NULL, option_index used then */ + int option_index; /**< argument ordinal to use if option_name missing */ + const char *start; /**< value */ + int len; /**< length of value */ +}; + +/** + * typedef lws_extension_callback_function() - Hooks to allow extensions to operate + * \param context: Websockets context + * \param ext: This extension + * \param wsi: Opaque websocket instance pointer + * \param reason: The reason for the call + * \param user: Pointer to ptr to per-session user data allocated by library + * \param in: Pointer used for some callback reasons + * \param len: Length set for some callback reasons + * + * Each extension that is active on a particular connection receives + * callbacks during the connection lifetime to allow the extension to + * operate on websocket data and manage itself. + * + * Libwebsockets takes care of allocating and freeing "user" memory for + * each active extension on each connection. That is what is pointed to + * by the user parameter. + * + * LWS_EXT_CB_CONSTRUCT: called when the server has decided to + * select this extension from the list provided by the client, + * just before the server will send back the handshake accepting + * the connection with this extension active. This gives the + * extension a chance to initialize its connection context found + * in user. + * + * LWS_EXT_CB_CLIENT_CONSTRUCT: same as LWS_EXT_CB_CONSTRUCT + * but called when client is instantiating this extension. Some + * extensions will work the same on client and server side and then + * you can just merge handlers for both CONSTRUCTS. + * + * LWS_EXT_CB_DESTROY: called when the connection the extension was + * being used on is about to be closed and deallocated. It's the + * last chance for the extension to deallocate anything it has + * allocated in the user data (pointed to by user) before the + * user data is deleted. This same callback is used whether you + * are in client or server instantiation context. + * + * LWS_EXT_CB_PACKET_RX_PREPARSE: when this extension was active on + * a connection, and a packet of data arrived at the connection, + * it is passed to this callback to give the extension a chance to + * change the data, eg, decompress it. user is pointing to the + * extension's private connection context data, in is pointing + * to an lws_tokens struct, it consists of a char * pointer called + * token, and an int called token_len. At entry, these are + * set to point to the received buffer and set to the content + * length. If the extension will grow the content, it should use + * a new buffer allocated in its private user context data and + * set the pointed-to lws_tokens members to point to its buffer. + * + * LWS_EXT_CB_PACKET_TX_PRESEND: this works the same way as + * LWS_EXT_CB_PACKET_RX_PREPARSE above, except it gives the + * extension a chance to change websocket data just before it will + * be sent out. Using the same lws_token pointer scheme in in, + * the extension can change the buffer and the length to be + * transmitted how it likes. Again if it wants to grow the + * buffer safely, it should copy the data into its own buffer and + * set the lws_tokens token pointer to it. + * + * LWS_EXT_CB_ARGS_VALIDATE: + */ +typedef int +lws_extension_callback_function(struct lws_context *context, + const struct lws_extension *ext, struct lws *wsi, + enum lws_extension_callback_reasons reason, + void *user, void *in, size_t len); + +/** struct lws_extension - An extension we support */ +struct lws_extension { + const char *name; /**< Formal extension name, eg, "permessage-deflate" */ + lws_extension_callback_function *callback; /**< Service callback */ + const char *client_offer; /**< String containing exts and options client offers */ + + /* Add new things just above here ---^ + * This is part of the ABI, don't needlessly break compatibility */ +}; + +/** + * lws_set_extension_option(): set extension option if possible + * + * \param wsi: websocket connection + * \param ext_name: name of ext, like "permessage-deflate" + * \param opt_name: name of option, like "rx_buf_size" + * \param opt_val: value to set option to + */ +LWS_VISIBLE LWS_EXTERN int +lws_set_extension_option(struct lws *wsi, const char *ext_name, + const char *opt_name, const char *opt_val); + +#ifndef LWS_NO_EXTENSIONS +/* lws_get_internal_extensions() - DEPRECATED + * + * \Deprecated There is no longer a set internal extensions table. The table is provided + * by user code along with application-specific settings. See the test + * client and server for how to do. + */ +static LWS_INLINE LWS_WARN_DEPRECATED const struct lws_extension * +lws_get_internal_extensions(void) { return NULL; } + +/** + * lws_ext_parse_options() - deal with parsing negotiated extension options + * + * \param ext: related extension struct + * \param wsi: websocket connection + * \param ext_user: per-connection extension private data + * \param opts: list of supported options + * \param o: option string to parse + * \param len: length + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ext_parse_options(const struct lws_extension *ext, struct lws *wsi, + void *ext_user, const struct lws_ext_options *opts, + const char *o, int len); +#endif + +/** lws_extension_callback_pm_deflate() - extension for RFC7692 + * + * \param context: lws context + * \param ext: related lws_extension struct + * \param wsi: websocket connection + * \param reason: incoming callback reason + * \param user: per-connection extension private data + * \param in: pointer parameter + * \param len: length parameter + * + * Built-in callback implementing RFC7692 permessage-deflate + */ +LWS_EXTERN +int lws_extension_callback_pm_deflate( + struct lws_context *context, const struct lws_extension *ext, + struct lws *wsi, enum lws_extension_callback_reasons reason, + void *user, void *in, size_t len); + +/* + * The internal exts are part of the public abi + * If we add more extensions, publish the callback here ------v + */ +///@} + +/*! \defgroup Protocols-and-Plugins Protocols and Plugins + * \ingroup lwsapi + * + * ##Protocol and protocol plugin -related apis + * + * Protocols bind ws protocol names to a custom callback specific to that + * protocol implementaion. + * + * A list of protocols can be passed in at context creation time, but it is + * also legal to leave that NULL and add the protocols and their callback code + * using plugins. + * + * Plugins are much preferable compared to cut and pasting code into an + * application each time, since they can be used standalone. + */ +///@{ +/** struct lws_protocols - List of protocols and handlers client or server + * supports. */ + +struct lws_protocols { + const char *name; + /**< Protocol name that must match the one given in the client + * Javascript new WebSocket(url, 'protocol') name. */ + lws_callback_function *callback; + /**< The service callback used for this protocol. It allows the + * service action for an entire protocol to be encapsulated in + * the protocol-specific callback */ + size_t per_session_data_size; + /**< Each new connection using this protocol gets + * this much memory allocated on connection establishment and + * freed on connection takedown. A pointer to this per-connection + * allocation is passed into the callback in the 'user' parameter */ + size_t rx_buffer_size; + /**< lws allocates this much space for rx data and informs callback + * when something came. Due to rx flow control, the callback may not + * be able to consume it all without having to return to the event + * loop. That is supported in lws. + * + * If .tx_packet_size is 0, this also controls how much may be sent at once + * for backwards compatibility. + */ + unsigned int id; + /**< ignored by lws, but useful to contain user information bound + * to the selected protocol. For example if this protocol was + * called "myprotocol-v2", you might set id to 2, and the user + * code that acts differently according to the version can do so by + * switch (wsi->protocol->id), user code might use some bits as + * capability flags based on selected protocol version, etc. */ + void *user; /**< ignored by lws, but user code can pass a pointer + here it can later access from the protocol callback */ + size_t tx_packet_size; + /**< 0 indicates restrict send() size to .rx_buffer_size for backwards- + * compatibility. + * If greater than zero, a single send() is restricted to this amount + * and any remainder is buffered by lws and sent afterwards also in + * these size chunks. Since that is expensive, it's preferable + * to restrict one fragment you are trying to send to match this + * size. + */ + + /* Add new things just above here ---^ + * This is part of the ABI, don't needlessly break compatibility */ +}; + +struct lws_vhost; + +/** + * lws_vhost_name_to_protocol() - get vhost's protocol object from its name + * + * \param vh: vhost to search + * \param name: protocol name + * + * Returns NULL or a pointer to the vhost's protocol of the requested name + */ +LWS_VISIBLE LWS_EXTERN const struct lws_protocols * +lws_vhost_name_to_protocol(struct lws_vhost *vh, const char *name); + +/** + * lws_get_protocol() - Returns a protocol pointer from a websocket + * connection. + * \param wsi: pointer to struct websocket you want to know the protocol of + * + * + * Some apis can act on all live connections of a given protocol, + * this is how you can get a pointer to the active protocol if needed. + */ +LWS_VISIBLE LWS_EXTERN const struct lws_protocols * +lws_get_protocol(struct lws *wsi); + +/** lws_protocol_get() - deprecated: use lws_get_protocol */ +LWS_VISIBLE LWS_EXTERN const struct lws_protocols * +lws_protocol_get(struct lws *wsi) LWS_WARN_DEPRECATED; + +/** + * lws_protocol_vh_priv_zalloc() - Allocate and zero down a protocol's per-vhost + * storage + * \param vhost: vhost the instance is related to + * \param prot: protocol the instance is related to + * \param size: bytes to allocate + * + * Protocols often find it useful to allocate a per-vhost struct, this is a + * helper to be called in the per-vhost init LWS_CALLBACK_PROTOCOL_INIT + */ +LWS_VISIBLE LWS_EXTERN void * +lws_protocol_vh_priv_zalloc(struct lws_vhost *vhost, const struct lws_protocols *prot, + int size); + +/** + * lws_protocol_vh_priv_get() - retreive a protocol's per-vhost storage + * + * \param vhost: vhost the instance is related to + * \param prot: protocol the instance is related to + * + * Recover a pointer to the allocated per-vhost storage for the protocol created + * by lws_protocol_vh_priv_zalloc() earlier + */ +LWS_VISIBLE LWS_EXTERN void * +lws_protocol_vh_priv_get(struct lws_vhost *vhost, const struct lws_protocols *prot); + +/** + * lws_adjust_protocol_psds - change a vhost protocol's per session data size + * + * \param wsi: a connection with the protocol to change + * \param new_size: the new size of the per session data size for the protocol + * + * Returns user_space for the wsi, after allocating + * + * This should not be used except to initalize a vhost protocol's per session + * data size one time, before any connections are accepted. + * + * Sometimes the protocol wraps another protocol and needs to discover and set + * its per session data size at runtime. + */ +LWS_VISIBLE LWS_EXTERN void * +lws_adjust_protocol_psds(struct lws *wsi, size_t new_size); + +/** + * lws_finalize_startup() - drop initial process privileges + * + * \param context: lws context + * + * This is called after the end of the vhost protocol initializations, but + * you may choose to call it earlier + */ +LWS_VISIBLE LWS_EXTERN int +lws_finalize_startup(struct lws_context *context); + +LWS_VISIBLE LWS_EXTERN int +lws_protocol_init(struct lws_context *context); + +#ifdef LWS_WITH_PLUGINS + +/* PLUGINS implies LIBUV */ + +#define LWS_PLUGIN_API_MAGIC 180 + +/** struct lws_plugin_capability - how a plugin introduces itself to lws */ +struct lws_plugin_capability { + unsigned int api_magic; /**< caller fills this in, plugin fills rest */ + const struct lws_protocols *protocols; /**< array of supported protocols provided by plugin */ + int count_protocols; /**< how many protocols */ + const struct lws_extension *extensions; /**< array of extensions provided by plugin */ + int count_extensions; /**< how many extensions */ +}; + +typedef int (*lws_plugin_init_func)(struct lws_context *, + struct lws_plugin_capability *); +typedef int (*lws_plugin_destroy_func)(struct lws_context *); + +/** struct lws_plugin */ +struct lws_plugin { + struct lws_plugin *list; /**< linked list */ +#if (UV_VERSION_MAJOR > 0) + uv_lib_t lib; /**< shared library pointer */ +#else + void *l; /**< so we can compile on ancient libuv */ +#endif + char name[64]; /**< name of the plugin */ + struct lws_plugin_capability caps; /**< plugin capabilities */ +}; + +#endif + +///@} + + +/*! \defgroup generic-sessions plugin: generic-sessions + * \ingroup Protocols-and-Plugins + * + * ##Plugin Generic-sessions related + * + * generic-sessions plugin provides a reusable, generic session and login / + * register / forgot password framework including email verification. + */ +///@{ + +#define LWSGS_EMAIL_CONTENT_SIZE 16384 +/**< Maximum size of email we might send */ + +/* SHA-1 binary and hexified versions */ +/** typedef struct lwsgw_hash_bin */ +typedef struct { unsigned char bin[20]; /**< binary representation of hash */} lwsgw_hash_bin; +/** typedef struct lwsgw_hash */ +typedef struct { char id[41]; /**< ascii hex representation of hash */ } lwsgw_hash; + +/** enum lwsgs_auth_bits */ +enum lwsgs_auth_bits { + LWSGS_AUTH_LOGGED_IN = 1, /**< user is logged in as somebody */ + LWSGS_AUTH_ADMIN = 2, /**< logged in as the admin user */ + LWSGS_AUTH_VERIFIED = 4, /**< user has verified his email */ + LWSGS_AUTH_FORGOT_FLOW = 8, /**< he just completed "forgot password" flow */ +}; + +/** struct lws_session_info - information about user session status */ +struct lws_session_info { + char username[32]; /**< username logged in as, or empty string */ + char email[100]; /**< email address associated with login, or empty string */ + char ip[72]; /**< ip address session was started from */ + unsigned int mask; /**< access rights mask associated with session + * see enum lwsgs_auth_bits */ + char session[42]; /**< session id string, usable as opaque uid when not logged in */ +}; + +/** enum lws_gs_event */ +enum lws_gs_event { + LWSGSE_CREATED, /**< a new user was created */ + LWSGSE_DELETED /**< an existing user was deleted */ +}; + +/** struct lws_gs_event_args */ +struct lws_gs_event_args { + enum lws_gs_event event; /**< which event happened */ + const char *username; /**< which username the event happened to */ + const char *email; /**< the email address of that user */ +}; + +///@} + + +/*! \defgroup context-and-vhost context and vhost related functions + * ##Context and Vhost releated functions + * \ingroup lwsapi + * + * + * LWS requires that there is one context, in which you may define multiple + * vhosts. Each vhost is a virtual host, with either its own listen port + * or sharing an existing one. Each vhost has its own SSL context that can + * be set up individually or left disabled. + * + * If you don't care about multiple "site" support, you can ignore it and + * lws will create a single default vhost at context creation time. + */ +///@{ + +/* + * NOTE: These public enums are part of the abi. If you want to add one, + * add it at where specified so existing users are unaffected. + */ + +/** enum lws_context_options - context and vhost options */ +enum lws_context_options { + LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT = (1 << 1) | + (1 << 12), + /**< (VH) Don't allow the connection unless the client has a + * client cert that we recognize; provides + * LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT */ + LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME = (1 << 2), + /**< (CTX) Don't try to get the server's hostname */ + LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT = (1 << 3) | + (1 << 12), + /**< (VH) Allow non-SSL (plaintext) connections on the same + * port as SSL is listening... undermines the security of SSL; + * provides LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT */ + LWS_SERVER_OPTION_LIBEV = (1 << 4), + /**< (CTX) Use libev event loop */ + LWS_SERVER_OPTION_DISABLE_IPV6 = (1 << 5), + /**< (VH) Disable IPV6 support */ + LWS_SERVER_OPTION_DISABLE_OS_CA_CERTS = (1 << 6), + /**< (VH) Don't load OS CA certs, you will need to load your + * own CA cert(s) */ + LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED = (1 << 7), + /**< (VH) Accept connections with no valid Cert (eg, selfsigned) */ + LWS_SERVER_OPTION_VALIDATE_UTF8 = (1 << 8), + /**< (VH) Check UT-8 correctness */ + LWS_SERVER_OPTION_SSL_ECDH = (1 << 9) | + (1 << 12), + /**< (VH) initialize ECDH ciphers */ + LWS_SERVER_OPTION_LIBUV = (1 << 10), + /**< (CTX) Use libuv event loop */ + LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS = (1 << 11) | + (1 << 12), + /**< (VH) Use http redirect to force http to https + * (deprecated: use mount redirection) */ + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT = (1 << 12), + /**< (CTX) Initialize the SSL library at all */ + LWS_SERVER_OPTION_EXPLICIT_VHOSTS = (1 << 13), + /**< (CTX) Only create the context when calling context + * create api, implies user code will create its own vhosts */ + LWS_SERVER_OPTION_UNIX_SOCK = (1 << 14), + /**< (VH) Use Unix socket */ + LWS_SERVER_OPTION_STS = (1 << 15), + /**< (VH) Send Strict Transport Security header, making + * clients subsequently go to https even if user asked for http */ + LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY = (1 << 16), + /**< (VH) Enable LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE to take effect */ + LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE = (1 << 17), + /**< (VH) if set, only ipv6 allowed on the vhost */ + LWS_SERVER_OPTION_UV_NO_SIGSEGV_SIGFPE_SPIN = (1 << 18), + /**< (CTX) Libuv only: Do not spin on SIGSEGV / SIGFPE. A segfault + * normally makes the lib spin so you can attach a debugger to it + * even if it happened without a debugger in place. You can disable + * that by giving this option. + */ + LWS_SERVER_OPTION_JUST_USE_RAW_ORIGIN = (1 << 19), + /**< For backwards-compatibility reasons, by default + * lws prepends "http://" to the origin you give in the client + * connection info struct. If you give this flag when you create + * the context, only the string you give in the client connect + * info for .origin (if any) will be used directly. + */ + LWS_SERVER_OPTION_FALLBACK_TO_RAW = (1 << 20), + /**< (VH) if invalid http is coming in the first line, */ + LWS_SERVER_OPTION_LIBEVENT = (1 << 21), + /**< (CTX) Use libevent event loop */ + LWS_SERVER_OPTION_ONLY_RAW = (1 << 22), + /**< (VH) All connections to this vhost / port are RAW as soon as + * the connection is accepted, no HTTP is going to be coming. + */ + LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE = (1 << 23), + /**< (VH) Set to allow multiple listen sockets on one interface + + * address + port. The default is to strictly allow only one + * listen socket at a time. This is automatically selected if you + * have multiple service threads. + */ + LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX = (1 << 24), + /**< (VH) Force setting up the vhost SSL_CTX, even though the user + * code doesn't explicitly provide a cert in the info struct. It + * implies the user code is going to provide a cert at the + * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS callback, which + * provides the vhost SSL_CTX * in the user parameter. + */ + + /****** add new things just above ---^ ******/ +}; + +#define lws_check_opt(c, f) (((c) & (f)) == (f)) + +struct lws_plat_file_ops; + +/** struct lws_context_creation_info - parameters to create context and /or vhost with + * + * This is also used to create vhosts.... if LWS_SERVER_OPTION_EXPLICIT_VHOSTS + * is not given, then for backwards compatibility one vhost is created at + * context-creation time using the info from this struct. + * + * If LWS_SERVER_OPTION_EXPLICIT_VHOSTS is given, then no vhosts are created + * at the same time as the context, they are expected to be created afterwards. + */ +struct lws_context_creation_info { + int port; + /**< VHOST: Port to listen on. Use CONTEXT_PORT_NO_LISTEN to suppress + * listening for a client. Use CONTEXT_PORT_NO_LISTEN_SERVER if you are + * writing a server but you are using \ref sock-adopt instead of the + * built-in listener */ + const char *iface; + /**< VHOST: NULL to bind the listen socket to all interfaces, or the + * interface name, eg, "eth2" + * If options specifies LWS_SERVER_OPTION_UNIX_SOCK, this member is + * the pathname of a UNIX domain socket. you can use the UNIX domain + * sockets in abstract namespace, by prepending an at symbol to the + * socket name. */ + const struct lws_protocols *protocols; + /**< VHOST: Array of structures listing supported protocols and a protocol- + * specific callback for each one. The list is ended with an + * entry that has a NULL callback pointer. */ + const struct lws_extension *extensions; + /**< VHOST: NULL or array of lws_extension structs listing the + * extensions this context supports. */ + const struct lws_token_limits *token_limits; + /**< CONTEXT: NULL or struct lws_token_limits pointer which is initialized + * with a token length limit for each possible WSI_TOKEN_ */ + const char *ssl_private_key_password; + /**< VHOST: NULL or the passphrase needed for the private key. (For + * backwards compatibility, this can also be used to pass the client + * cert passphrase when setting up a vhost client SSL context, but it is + * preferred to use .client_ssl_private_key_password for that.) */ + const char *ssl_cert_filepath; + /**< VHOST: If libwebsockets was compiled to use ssl, and you want + * to listen using SSL, set to the filepath to fetch the + * server cert from, otherwise NULL for unencrypted. (For backwards + * compatibility, this can also be used to pass the client certificate + * when setting up a vhost client SSL context, but it is preferred to + * use .client_ssl_cert_filepath for that.) */ + const char *ssl_private_key_filepath; + /**< VHOST: filepath to private key if wanting SSL mode; + * if this is set to NULL but ssl_cert_filepath is set, the + * OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY callback is called + * to allow setting of the private key directly via openSSL + * library calls. (For backwards compatibility, this can also be used + * to pass the client cert private key filepath when setting up a + * vhost client SSL context, but it is preferred to use + * .client_ssl_private_key_filepath for that.) */ + const char *ssl_ca_filepath; + /**< VHOST: CA certificate filepath or NULL. (For backwards + * compatibility, this can also be used to pass the client CA + * filepath when setting up a vhost client SSL context, + * but it is preferred to use .client_ssl_ca_filepath for that.) */ + const char *ssl_cipher_list; + /**< VHOST: List of valid ciphers to use (eg, + * "RC4-MD5:RC4-SHA:AES128-SHA:AES256-SHA:HIGH:!DSS:!aNULL" + * or you can leave it as NULL to get "DEFAULT" (For backwards + * compatibility, this can also be used to pass the client cipher + * list when setting up a vhost client SSL context, + * but it is preferred to use .client_ssl_cipher_list for that.)*/ + const char *http_proxy_address; + /**< VHOST: If non-NULL, attempts to proxy via the given address. + * If proxy auth is required, use format "username:password\@server:port" */ + unsigned int http_proxy_port; + /**< VHOST: If http_proxy_address was non-NULL, uses this port */ + int gid; + /**< CONTEXT: group id to change to after setting listen socket, or -1. */ + int uid; + /**< CONTEXT: user id to change to after setting listen socket, or -1. */ + unsigned int options; + /**< VHOST + CONTEXT: 0, or LWS_SERVER_OPTION_... bitfields */ + void *user; + /**< VHOST + CONTEXT: optional user pointer that will be associated + * with the context when creating the context (and can be retrieved by + * lws_context_user(context), or with the vhost when creating the vhost + * (and can be retrieved by lws_vhost_user(vhost)). You will need to + * use LWS_SERVER_OPTION_EXPLICIT_VHOSTS and create the vhost separately + * if you care about giving the context and vhost different user pointer + * values. + */ + int ka_time; + /**< CONTEXT: 0 for no TCP keepalive, otherwise apply this keepalive + * timeout to all libwebsocket sockets, client or server */ + int ka_probes; + /**< CONTEXT: if ka_time was nonzero, after the timeout expires how many + * times to try to get a response from the peer before giving up + * and killing the connection */ + int ka_interval; + /**< CONTEXT: if ka_time was nonzero, how long to wait before each ka_probes + * attempt */ +#ifdef LWS_OPENSSL_SUPPORT + SSL_CTX *provided_client_ssl_ctx; + /**< CONTEXT: If non-null, swap out libwebsockets ssl + * implementation for the one provided by provided_ssl_ctx. + * Libwebsockets no longer is responsible for freeing the context + * if this option is selected. */ +#else /* maintain structure layout either way */ + void *provided_client_ssl_ctx; /**< dummy if ssl disabled */ +#endif + + short max_http_header_data; + /**< CONTEXT: The max amount of header payload that can be handled + * in an http request (unrecognized header payload is dropped) */ + short max_http_header_pool; + /**< CONTEXT: The max number of connections with http headers that + * can be processed simultaneously (the corresponding memory is + * allocated for the lifetime of the context). If the pool is + * busy new incoming connections must wait for accept until one + * becomes free. */ + + unsigned int count_threads; + /**< CONTEXT: how many contexts to create in an array, 0 = 1 */ + unsigned int fd_limit_per_thread; + /**< CONTEXT: nonzero means restrict each service thread to this + * many fds, 0 means the default which is divide the process fd + * limit by the number of threads. */ + unsigned int timeout_secs; + /**< VHOST: various processes involving network roundtrips in the + * library are protected from hanging forever by timeouts. If + * nonzero, this member lets you set the timeout used in seconds. + * Otherwise a default timeout is used. */ + const char *ecdh_curve; + /**< VHOST: if NULL, defaults to initializing server with "prime256v1" */ + const char *vhost_name; + /**< VHOST: name of vhost, must match external DNS name used to + * access the site, like "warmcat.com" as it's used to match + * Host: header and / or SNI name for SSL. */ + const char * const *plugin_dirs; + /**< CONTEXT: NULL, or NULL-terminated array of directories to + * scan for lws protocol plugins at context creation time */ + const struct lws_protocol_vhost_options *pvo; + /**< VHOST: pointer to optional linked list of per-vhost + * options made accessible to protocols */ + int keepalive_timeout; + /**< VHOST: (default = 0 = 60s) seconds to allow remote + * client to hold on to an idle HTTP/1.1 connection */ + const char *log_filepath; + /**< VHOST: filepath to append logs to... this is opened before + * any dropping of initial privileges */ + const struct lws_http_mount *mounts; + /**< VHOST: optional linked list of mounts for this vhost */ + const char *server_string; + /**< CONTEXT: string used in HTTP headers to identify server + * software, if NULL, "libwebsockets". */ + unsigned int pt_serv_buf_size; + /**< CONTEXT: 0 = default of 4096. This buffer is used by + * various service related features including file serving, it + * defines the max chunk of file that can be sent at once. + * At the risk of lws having to buffer failed large sends, it + * can be increased to, eg, 128KiB to improve throughput. */ + unsigned int max_http_header_data2; + /**< CONTEXT: if max_http_header_data is 0 and this + * is nonzero, this will be used in place of the default. It's + * like this for compatibility with the original short version, + * this is unsigned int length. */ + long ssl_options_set; + /**< VHOST: Any bits set here will be set as SSL options */ + long ssl_options_clear; + /**< VHOST: Any bits set here will be cleared as SSL options */ + unsigned short ws_ping_pong_interval; + /**< CONTEXT: 0 for none, else interval in seconds between sending + * PINGs on idle websocket connections. When the PING is sent, + * the PONG must come within the normal timeout_secs timeout period + * or the connection will be dropped. + * Any RX or TX traffic on the connection restarts the interval timer, + * so a connection which always sends or receives something at intervals + * less than the interval given here will never send PINGs / expect + * PONGs. Conversely as soon as the ws connection is established, an + * idle connection will do the PING / PONG roundtrip as soon as + * ws_ping_pong_interval seconds has passed without traffic + */ + const struct lws_protocol_vhost_options *headers; + /**< VHOST: pointer to optional linked list of per-vhost + * canned headers that are added to server responses */ + + const struct lws_protocol_vhost_options *reject_service_keywords; + /**< CONTEXT: Optional list of keywords and rejection codes + text. + * + * The keywords are checked for existing in the user agent string. + * + * Eg, "badrobot" "404 Not Found" + */ + void *external_baggage_free_on_destroy; + /**< CONTEXT: NULL, or pointer to something externally malloc'd, that + * should be freed when the context is destroyed. This allows you to + * automatically sync the freeing action to the context destruction + * action, so there is no need for an external free() if the context + * succeeded to create. + */ + + const char *client_ssl_private_key_password; + /**< VHOST: Client SSL context init: NULL or the passphrase needed + * for the private key */ + const char *client_ssl_cert_filepath; + /**< VHOST: Client SSL context init:T he certificate the client + * should present to the peer on connection */ + const char *client_ssl_private_key_filepath; + /**< VHOST: Client SSL context init: filepath to client private key + * if this is set to NULL but client_ssl_cert_filepath is set, you + * can handle the LWS_CALLBACK_OPENSSL_LOAD_EXTRA_CLIENT_VERIFY_CERTS + * callback of protocols[0] to allow setting of the private key directly + * via openSSL library calls */ + const char *client_ssl_ca_filepath; + /**< VHOST: Client SSL context init: CA certificate filepath or NULL */ + const char *client_ssl_cipher_list; + /**< VHOST: Client SSL context init: List of valid ciphers to use (eg, + * "RC4-MD5:RC4-SHA:AES128-SHA:AES256-SHA:HIGH:!DSS:!aNULL" + * or you can leave it as NULL to get "DEFAULT" */ + + const struct lws_plat_file_ops *fops; + /**< CONTEXT: NULL, or pointer to an array of fops structs, terminated + * by a sentinel with NULL .open. + * + * If NULL, lws provides just the platform file operations struct for + * backwards compatibility. + */ + int simultaneous_ssl_restriction; + /**< CONTEXT: 0 (no limit) or limit of simultaneous SSL sessions possible.*/ + const char *socks_proxy_address; + /**< VHOST: If non-NULL, attempts to proxy via the given address. + * If proxy auth is required, use format "username:password\@server:port" */ + unsigned int socks_proxy_port; + /**< VHOST: If socks_proxy_address was non-NULL, uses this port */ +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + cap_value_t caps[4]; + /**< CONTEXT: array holding Linux capabilities you want to + * continue to be available to the server after it transitions + * to a noprivileged user. Usually none are needed but for, eg, + * .bind_iface, CAP_NET_RAW is required. This gives you a way + * to still have the capability but drop root. + */ + char count_caps; + /**< CONTEXT: count of Linux capabilities in .caps[]. 0 means + * no capabilities will be inherited from root (the default) */ +#endif + int bind_iface; + /**< VHOST: nonzero to strictly bind sockets to the interface name in + * .iface (eg, "eth2"), using SO_BIND_TO_DEVICE. + * + * Requires SO_BINDTODEVICE support from your OS and CAP_NET_RAW + * capability. + * + * Notice that common things like access network interface IP from + * your local machine use your lo / loopback interface and will be + * disallowed by this. + */ + int ssl_info_event_mask; + /**< VHOST: mask of ssl events to be reported on LWS_CALLBACK_SSL_INFO + * callback for connections on this vhost. The mask values are of + * the form SSL_CB_ALERT, defined in openssl/ssl.h. The default of + * 0 means no info events will be reported. + */ + unsigned int timeout_secs_ah_idle; + /**< VHOST: seconds to allow a client to hold an ah without using it. + * 0 defaults to 10s. */ + unsigned short ip_limit_ah; + /**< CONTEXT: max number of ah a single IP may use simultaneously + * 0 is no limit. This is a soft limit: if the limit is + * reached, connections from that IP will wait in the ah + * waiting list and not be able to acquire an ah until + * a connection belonging to the IP relinquishes one it + * already has. + */ + unsigned short ip_limit_wsi; + /**< CONTEXT: max number of wsi a single IP may use simultaneously. + * 0 is no limit. This is a hard limit, connections from + * the same IP will simply be dropped once it acquires the + * amount of simultaneous wsi / accepted connections + * given here. + */ + uint32_t http2_settings[7]; + /**< CONTEXT: after context creation http2_settings[1] thru [6] have + * been set to the lws platform default values. + * VHOST: if http2_settings[0] is nonzero, the values given in + * http2_settings[1]..[6] are used instead of the lws + * platform default values. + * Just leave all at 0 if you don't care. + */ + + /* Add new things just above here ---^ + * This is part of the ABI, don't needlessly break compatibility + * + * The below is to ensure later library versions with new + * members added above will see 0 (default) even if the app + * was not built against the newer headers. + */ + + void *_unused[8]; /**< dummy */ +}; + +/** + * lws_create_context() - Create the websocket handler + * \param info: pointer to struct with parameters + * + * This function creates the listening socket (if serving) and takes care + * of all initialization in one step. + * + * If option LWS_SERVER_OPTION_EXPLICIT_VHOSTS is given, no vhost is + * created; you're expected to create your own vhosts afterwards using + * lws_create_vhost(). Otherwise a vhost named "default" is also created + * using the information in the vhost-related members, for compatibility. + * + * After initialization, it returns a struct lws_context * that + * represents this server. After calling, user code needs to take care + * of calling lws_service() with the context pointer to get the + * server's sockets serviced. This must be done in the same process + * context as the initialization call. + * + * The protocol callback functions are called for a handful of events + * including http requests coming in, websocket connections becoming + * established, and data arriving; it's also called periodically to allow + * async transmission. + * + * HTTP requests are sent always to the FIRST protocol in protocol, since + * at that time websocket protocol has not been negotiated. Other + * protocols after the first one never see any HTTP callback activity. + * + * The server created is a simple http server by default; part of the + * websocket standard is upgrading this http connection to a websocket one. + * + * This allows the same server to provide files like scripts and favicon / + * images or whatever over http and dynamic data over websockets all in + * one place; they're all handled in the user callback. + */ +LWS_VISIBLE LWS_EXTERN struct lws_context * +lws_create_context(struct lws_context_creation_info *info); + +/** + * lws_context_destroy() - Destroy the websocket context + * \param context: Websocket context + * + * This function closes any active connections and then frees the + * context. After calling this, any further use of the context is + * undefined. + */ +LWS_VISIBLE LWS_EXTERN void +lws_context_destroy(struct lws_context *context); + +LWS_VISIBLE LWS_EXTERN void +lws_context_destroy2(struct lws_context *context); + +typedef int (*lws_reload_func)(void); + +/** + * lws_context_deprecate() - Deprecate the websocket context + * + * \param context: Websocket context + * \param cb: Callback notified when old context listen sockets are closed + * + * This function is used on an existing context before superceding it + * with a new context. + * + * It closes any listen sockets in the context, so new connections are + * not possible. + * + * And it marks the context to be deleted when the number of active + * connections into it falls to zero. + * + * Otherwise if you attach the deprecated context to the replacement + * context when it has been created using lws_context_attach_deprecated() + * both any deprecated and the new context will service their connections. + * + * This is aimed at allowing seamless configuration reloads. + * + * The callback cb will be called after the listen sockets are actually + * closed and may be reopened. In the callback the new context should be + * configured and created. (With libuv, socket close happens async after + * more loop events). + */ +LWS_VISIBLE LWS_EXTERN void +lws_context_deprecate(struct lws_context *context, lws_reload_func cb); + +LWS_VISIBLE LWS_EXTERN int +lws_context_is_deprecated(struct lws_context *context); + +/** + * lws_set_proxy() - Setups proxy to lws_context. + * \param vhost: pointer to struct lws_vhost you want set proxy for + * \param proxy: pointer to c string containing proxy in format address:port + * + * Returns 0 if proxy string was parsed and proxy was setup. + * Returns -1 if proxy is NULL or has incorrect format. + * + * This is only required if your OS does not provide the http_proxy + * environment variable (eg, OSX) + * + * IMPORTANT! You should call this function right after creation of the + * lws_context and before call to connect. If you call this + * function after connect behavior is undefined. + * This function will override proxy settings made on lws_context + * creation with genenv() call. + */ +LWS_VISIBLE LWS_EXTERN int +lws_set_proxy(struct lws_vhost *vhost, const char *proxy); + +/** + * lws_set_socks() - Setup socks to lws_context. + * \param vhost: pointer to struct lws_vhost you want set socks for + * \param socks: pointer to c string containing socks in format address:port + * + * Returns 0 if socks string was parsed and socks was setup. + * Returns -1 if socks is NULL or has incorrect format. + * + * This is only required if your OS does not provide the socks_proxy + * environment variable (eg, OSX) + * + * IMPORTANT! You should call this function right after creation of the + * lws_context and before call to connect. If you call this + * function after connect behavior is undefined. + * This function will override proxy settings made on lws_context + * creation with genenv() call. + */ +LWS_VISIBLE LWS_EXTERN int +lws_set_socks(struct lws_vhost *vhost, const char *socks); + +struct lws_vhost; + +/** + * lws_create_vhost() - Create a vhost (virtual server context) + * \param context: pointer to result of lws_create_context() + * \param info: pointer to struct with parameters + * + * This function creates a virtual server (vhost) using the vhost-related + * members of the info struct. You can create many vhosts inside one context + * if you created the context with the option LWS_SERVER_OPTION_EXPLICIT_VHOSTS + */ +LWS_VISIBLE LWS_EXTERN struct lws_vhost * +lws_create_vhost(struct lws_context *context, + struct lws_context_creation_info *info); + +/** + * lws_vhost_destroy() - Destroy a vhost (virtual server context) + * + * \param vh: pointer to result of lws_create_vhost() + * + * This function destroys a vhost. Normally, if you just want to exit, + * then lws_destroy_context() will take care of everything. If you want + * to destroy an individual vhost and all connections and allocations, you + * can do it with this. + * + * If the vhost has a listen sockets shared by other vhosts, it will be given + * to one of the vhosts sharing it rather than closed. + */ +LWS_VISIBLE LWS_EXTERN void +lws_vhost_destroy(struct lws_vhost *vh); + +/** + * lwsws_get_config_globals() - Parse a JSON server config file + * \param info: pointer to struct with parameters + * \param d: filepath of the config file + * \param config_strings: storage for the config strings extracted from JSON, + * the pointer is incremented as strings are stored + * \param len: pointer to the remaining length left in config_strings + * the value is decremented as strings are stored + * + * This function prepares a n lws_context_creation_info struct with global + * settings from a file d. + * + * Requires CMake option LWS_WITH_LEJP_CONF to have been enabled + */ +LWS_VISIBLE LWS_EXTERN int +lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d, + char **config_strings, int *len); + +/** + * lwsws_get_config_vhosts() - Create vhosts from a JSON server config file + * \param context: pointer to result of lws_create_context() + * \param info: pointer to struct with parameters + * \param d: filepath of the config file + * \param config_strings: storage for the config strings extracted from JSON, + * the pointer is incremented as strings are stored + * \param len: pointer to the remaining length left in config_strings + * the value is decremented as strings are stored + * + * This function creates vhosts into a context according to the settings in + *JSON files found in directory d. + * + * Requires CMake option LWS_WITH_LEJP_CONF to have been enabled + */ +LWS_VISIBLE LWS_EXTERN int +lwsws_get_config_vhosts(struct lws_context *context, + struct lws_context_creation_info *info, const char *d, + char **config_strings, int *len); + +/** lws_vhost_get() - \deprecated deprecated: use lws_get_vhost() */ +LWS_VISIBLE LWS_EXTERN struct lws_vhost * +lws_vhost_get(struct lws *wsi) LWS_WARN_DEPRECATED; + +/** + * lws_get_vhost() - return the vhost a wsi belongs to + * + * \param wsi: which connection + */ +LWS_VISIBLE LWS_EXTERN struct lws_vhost * +lws_get_vhost(struct lws *wsi); + +/** + * lws_json_dump_vhost() - describe vhost state and stats in JSON + * + * \param vh: the vhost + * \param buf: buffer to fill with JSON + * \param len: max length of buf + */ +LWS_VISIBLE LWS_EXTERN int +lws_json_dump_vhost(const struct lws_vhost *vh, char *buf, int len); + +/** + * lws_json_dump_context() - describe context state and stats in JSON + * + * \param context: the context + * \param buf: buffer to fill with JSON + * \param len: max length of buf + * \param hide_vhosts: nonzero to not provide per-vhost mount etc information + * + * Generates a JSON description of vhost state into buf + */ +LWS_VISIBLE LWS_EXTERN int +lws_json_dump_context(const struct lws_context *context, char *buf, int len, + int hide_vhosts); + +/** + * lws_vhost_user() - get the user data associated with the vhost + * \param vhost: Websocket vhost + * + * This returns the optional user pointer that can be attached to + * a vhost when it was created. Lws never dereferences this pointer, it only + * sets it when the vhost is created, and returns it using this api. + */ +LWS_VISIBLE LWS_EXTERN void * +lws_vhost_user(struct lws_vhost *vhost); + +/** + * lws_context_user() - get the user data associated with the context + * \param context: Websocket context + * + * This returns the optional user allocation that can be attached to + * the context the sockets live in at context_create time. It's a way + * to let all sockets serviced in the same context share data without + * using globals statics in the user code. + */ +LWS_VISIBLE LWS_EXTERN void * +lws_context_user(struct lws_context *context); + +/*! \defgroup vhost-mounts Vhost mounts and options + * \ingroup context-and-vhost-creation + * + * ##Vhost mounts and options + */ +///@{ +/** struct lws_protocol_vhost_options - linked list of per-vhost protocol + * name=value options + * + * This provides a general way to attach a linked-list of name=value pairs, + * which can also have an optional child link-list using the options member. + */ +struct lws_protocol_vhost_options { + const struct lws_protocol_vhost_options *next; /**< linked list */ + const struct lws_protocol_vhost_options *options; /**< child linked-list of more options for this node */ + const char *name; /**< name of name=value pair */ + const char *value; /**< value of name=value pair */ +}; + +/** enum lws_mount_protocols + * This specifies the mount protocol for a mountpoint, whether it is to be + * served from a filesystem, or it is a cgi etc. + */ +enum lws_mount_protocols { + LWSMPRO_HTTP = 0, /**< http reverse proxy */ + LWSMPRO_HTTPS = 1, /**< https reverse proxy */ + LWSMPRO_FILE = 2, /**< serve from filesystem directory */ + LWSMPRO_CGI = 3, /**< pass to CGI to handle */ + LWSMPRO_REDIR_HTTP = 4, /**< redirect to http:// url */ + LWSMPRO_REDIR_HTTPS = 5, /**< redirect to https:// url */ + LWSMPRO_CALLBACK = 6, /**< hand by named protocol's callback */ +}; + +/** struct lws_http_mount + * + * arguments for mounting something in a vhost's url namespace + */ +struct lws_http_mount { + const struct lws_http_mount *mount_next; + /**< pointer to next struct lws_http_mount */ + const char *mountpoint; + /**< mountpoint in http pathspace, eg, "/" */ + const char *origin; + /**< path to be mounted, eg, "/var/www/warmcat.com" */ + const char *def; + /**< default target, eg, "index.html" */ + const char *protocol; + /**<"protocol-name" to handle mount */ + + const struct lws_protocol_vhost_options *cgienv; + /**< optional linked-list of cgi options. These are created + * as environment variables for the cgi process + */ + const struct lws_protocol_vhost_options *extra_mimetypes; + /**< optional linked-list of mimetype mappings */ + const struct lws_protocol_vhost_options *interpret; + /**< optional linked-list of files to be interpreted */ + + int cgi_timeout; + /**< seconds cgi is allowed to live, if cgi://mount type */ + int cache_max_age; + /**< max-age for reuse of client cache of files, seconds */ + unsigned int auth_mask; + /**< bits set here must be set for authorized client session */ + + unsigned int cache_reusable:1; /**< set if client cache may reuse this */ + unsigned int cache_revalidate:1; /**< set if client cache should revalidate on use */ + unsigned int cache_intermediaries:1; /**< set if intermediaries are allowed to cache */ + + unsigned char origin_protocol; /**< one of enum lws_mount_protocols */ + unsigned char mountpoint_len; /**< length of mountpoint string */ + + const char *basic_auth_login_file; + /**<NULL, or filepath to use to check basic auth logins against */ + + /* Add new things just above here ---^ + * This is part of the ABI, don't needlessly break compatibility + * + * The below is to ensure later library versions with new + * members added above will see 0 (default) even if the app + * was not built against the newer headers. + */ + + void *_unused[2]; /**< dummy */ +}; +///@} +///@} + +/*! \defgroup client Client related functions + * ##Client releated functions + * \ingroup lwsapi + * + * */ +///@{ + +/** enum lws_client_connect_ssl_connection_flags - flags that may be used + * with struct lws_client_connect_info ssl_connection member to control if + * and how SSL checks apply to the client connection being created + */ + +enum lws_client_connect_ssl_connection_flags { + LCCSCF_USE_SSL = (1 << 0), + LCCSCF_ALLOW_SELFSIGNED = (1 << 1), + LCCSCF_SKIP_SERVER_CERT_HOSTNAME_CHECK = (1 << 2), + LCCSCF_ALLOW_EXPIRED = (1 << 3) +}; + +/** struct lws_client_connect_info - parameters to connect with when using + * lws_client_connect_via_info() */ + +struct lws_client_connect_info { + struct lws_context *context; + /**< lws context to create connection in */ + const char *address; + /**< remote address to connect to */ + int port; + /**< remote port to connect to */ + int ssl_connection; + /**< nonzero for ssl */ + const char *path; + /**< uri path */ + const char *host; + /**< content of host header */ + const char *origin; + /**< content of origin header */ + const char *protocol; + /**< list of ws protocols we could accept */ + int ietf_version_or_minus_one; + /**< deprecated: currently leave at 0 or -1 */ + void *userdata; + /**< if non-NULL, use this as wsi user_data instead of malloc it */ + const void *client_exts; + /**< UNUSED... provide in info.extensions at context creation time */ + const char *method; + /**< if non-NULL, do this http method instead of ws[s] upgrade. + * use "GET" to be a simple http client connection */ + struct lws *parent_wsi; + /**< if another wsi is responsible for this connection, give it here. + * this is used to make sure if the parent closes so do any + * child connections first. */ + const char *uri_replace_from; + /**< if non-NULL, when this string is found in URIs in + * text/html content-encoding, it's replaced with uri_replace_to */ + const char *uri_replace_to; + /**< see uri_replace_from */ + struct lws_vhost *vhost; + /**< vhost to bind to (used to determine related SSL_CTX) */ + struct lws **pwsi; + /**< if not NULL, store the new wsi here early in the connection + * process. Although we return the new wsi, the call to create the + * client connection does progress the connection somewhat and may + * meet an error that will result in the connection being scrubbed and + * NULL returned. While the wsi exists though, he may process a + * callback like CLIENT_CONNECTION_ERROR with his wsi: this gives the + * user callback a way to identify which wsi it is that faced the error + * even before the new wsi is returned and even if ultimately no wsi + * is returned. + */ + const char *iface; + /**< NULL to allow routing on any interface, or interface name or IP + * to bind the socket to */ + + /* Add new things just above here ---^ + * This is part of the ABI, don't needlessly break compatibility + * + * The below is to ensure later library versions with new + * members added above will see 0 (default) even if the app + * was not built against the newer headers. + */ + + void *_unused[4]; /**< dummy */ +}; + +/** + * lws_client_connect_via_info() - Connect to another websocket server + * \param ccinfo: pointer to lws_client_connect_info struct + * + * This function creates a connection to a remote server using the + * information provided in ccinfo. + */ +LWS_VISIBLE LWS_EXTERN struct lws * +lws_client_connect_via_info(struct lws_client_connect_info * ccinfo); + +/** + * lws_client_connect() - Connect to another websocket server + * \deprecated DEPRECATED use lws_client_connect_via_info + * \param clients: Websocket context + * \param address: Remote server address, eg, "myserver.com" + * \param port: Port to connect to on the remote server, eg, 80 + * \param ssl_connection: 0 = ws://, 1 = wss:// encrypted, 2 = wss:// allow self + * signed certs + * \param path: Websocket path on server + * \param host: Hostname on server + * \param origin: Socket origin name + * \param protocol: Comma-separated list of protocols being asked for from + * the server, or just one. The server will pick the one it + * likes best. If you don't want to specify a protocol, which is + * legal, use NULL here. + * \param ietf_version_or_minus_one: -1 to ask to connect using the default, latest + * protocol supported, or the specific protocol ordinal + * + * This function creates a connection to a remote server + */ +/* deprecated, use lws_client_connect_via_info() */ +LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +lws_client_connect(struct lws_context *clients, const char *address, + int port, int ssl_connection, const char *path, + const char *host, const char *origin, const char *protocol, + int ietf_version_or_minus_one) LWS_WARN_DEPRECATED; +/* deprecated, use lws_client_connect_via_info() */ +/** + * lws_client_connect_extended() - Connect to another websocket server + * \deprecated DEPRECATED use lws_client_connect_via_info + * \param clients: Websocket context + * \param address: Remote server address, eg, "myserver.com" + * \param port: Port to connect to on the remote server, eg, 80 + * \param ssl_connection: 0 = ws://, 1 = wss:// encrypted, 2 = wss:// allow self + * signed certs + * \param path: Websocket path on server + * \param host: Hostname on server + * \param origin: Socket origin name + * \param protocol: Comma-separated list of protocols being asked for from + * the server, or just one. The server will pick the one it + * likes best. + * \param ietf_version_or_minus_one: -1 to ask to connect using the default, latest + * protocol supported, or the specific protocol ordinal + * \param userdata: Pre-allocated user data + * + * This function creates a connection to a remote server + */ +LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +lws_client_connect_extended(struct lws_context *clients, const char *address, + int port, int ssl_connection, const char *path, + const char *host, const char *origin, + const char *protocol, int ietf_version_or_minus_one, + void *userdata) LWS_WARN_DEPRECATED; + +/** + * lws_init_vhost_client_ssl() - also enable client SSL on an existing vhost + * + * \param info: client ssl related info + * \param vhost: which vhost to initialize client ssl operations on + * + * You only need to call this if you plan on using SSL client connections on + * the vhost. For non-SSL client connections, it's not necessary to call this. + * + * The following members of info are used during the call + * + * - options must have LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT set, + * otherwise the call does nothing + * - provided_client_ssl_ctx must be NULL to get a generated client + * ssl context, otherwise you can pass a prepared one in by setting it + * - ssl_cipher_list may be NULL or set to the client valid cipher list + * - ssl_ca_filepath may be NULL or client cert filepath + * - ssl_cert_filepath may be NULL or client cert filepath + * - ssl_private_key_filepath may be NULL or client cert private key + * + * You must create your vhost explicitly if you want to use this, so you have + * a pointer to the vhost. Create the context first with the option flag + * LWS_SERVER_OPTION_EXPLICIT_VHOSTS and then call lws_create_vhost() with + * the same info struct. + */ +LWS_VISIBLE LWS_EXTERN int +lws_init_vhost_client_ssl(const struct lws_context_creation_info *info, + struct lws_vhost *vhost); +/** + * lws_http_client_read() - consume waiting received http client data + * + * \param wsi: client connection + * \param buf: pointer to buffer pointer - fill with pointer to your buffer + * \param len: pointer to chunk length - fill with max length of buffer + * + * This is called when the user code is notified client http data has arrived. + * The user code may choose to delay calling it to consume the data, for example + * waiting until an onward connection is writeable. + * + * For non-chunked connections, up to len bytes of buf are filled with the + * received content. len is set to the actual amount filled before return. + * + * For chunked connections, the linear buffer content contains the chunking + * headers and it cannot be passed in one lump. Instead, this function will + * call back LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ with in pointing to the + * chunk start and len set to the chunk length. There will be as many calls + * as there are chunks or partial chunks in the buffer. + */ +LWS_VISIBLE LWS_EXTERN int +lws_http_client_read(struct lws *wsi, char **buf, int *len); + +/** + * lws_http_client_http_response() - get last HTTP response code + * + * \param wsi: client connection + * + * Returns the last server response code, eg, 200 for client http connections. + */ +LWS_VISIBLE LWS_EXTERN unsigned int +lws_http_client_http_response(struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN void +lws_client_http_body_pending(struct lws *wsi, int something_left_to_send); + +/** + * lws_client_http_body_pending() - control if client connection neeeds to send body + * + * \param wsi: client connection + * \param something_left_to_send: nonzero if need to send more body, 0 (default) + * if nothing more to send + * + * If you will send payload data with your HTTP client connection, eg, for POST, + * when you set the related http headers in + * LWS_CALLBACK_CLIENT_APPEND_HANDSHAKE_HEADER callback you should also call + * this API with something_left_to_send nonzero, and call + * lws_callback_on_writable(wsi); + * + * After sending the headers, lws will call your callback with + * LWS_CALLBACK_CLIENT_HTTP_WRITEABLE reason when writable. You can send the + * next part of the http body payload, calling lws_callback_on_writable(wsi); + * if there is more to come, or lws_client_http_body_pending(wsi, 0); to + * let lws know the last part is sent and the connection can move on. + */ + +///@} + +/** \defgroup service Built-in service loop entry + * + * ##Built-in service loop entry + * + * If you're not using libev / libuv, these apis are needed to enter the poll() + * wait in lws and service any connections with pending events. + */ +///@{ + +/** + * lws_service() - Service any pending websocket activity + * \param context: Websocket context + * \param timeout_ms: Timeout for poll; 0 means return immediately if nothing needed + * service otherwise block and service immediately, returning + * after the timeout if nothing needed service. + * + * This function deals with any pending websocket traffic, for three + * kinds of event. It handles these events on both server and client + * types of connection the same. + * + * 1) Accept new connections to our context's server + * + * 2) Call the receive callback for incoming frame data received by + * server or client connections. + * + * You need to call this service function periodically to all the above + * functions to happen; if your application is single-threaded you can + * just call it in your main event loop. + * + * Alternatively you can fork a new process that asynchronously handles + * calling this service in a loop. In that case you are happy if this + * call blocks your thread until it needs to take care of something and + * would call it with a large nonzero timeout. Your loop then takes no + * CPU while there is nothing happening. + * + * If you are calling it in a single-threaded app, you don't want it to + * wait around blocking other things in your loop from happening, so you + * would call it with a timeout_ms of 0, so it returns immediately if + * nothing is pending, or as soon as it services whatever was pending. + */ +LWS_VISIBLE LWS_EXTERN int +lws_service(struct lws_context *context, int timeout_ms); + +/** + * lws_service_tsi() - Service any pending websocket activity + * + * \param context: Websocket context + * \param timeout_ms: Timeout for poll; 0 means return immediately if nothing needed + * service otherwise block and service immediately, returning + * after the timeout if nothing needed service. + * \param tsi: Thread service index, starting at 0 + * + * Same as lws_service(), but for a specific thread service index. Only needed + * if you are spawning multiple service threads. + */ +LWS_VISIBLE LWS_EXTERN int +lws_service_tsi(struct lws_context *context, int timeout_ms, int tsi); + +/** + * lws_cancel_service_pt() - Cancel servicing of pending socket activity + * on one thread + * \param wsi: Cancel service on the thread this wsi is serviced by + * + * This function lets a call to lws_service() waiting for a timeout + * immediately return. + * + * It works by creating a phony event and then swallowing it silently. + * + * The reason it may be needed is when waiting in poll(), changes to + * the event masks are ignored by the OS until poll() is reentered. This + * lets you halt the poll() wait and make the reentry happen immediately + * instead of having the wait out the rest of the poll timeout. + */ +LWS_VISIBLE LWS_EXTERN void +lws_cancel_service_pt(struct lws *wsi); + +/** + * lws_cancel_service() - Cancel wait for new pending socket activity + * \param context: Websocket context + * + * This function let a call to lws_service() waiting for a timeout + * immediately return. + * + * What it basically does is provide a fake event that will be swallowed, + * so the wait in poll() is ended. That's useful because poll() doesn't + * attend to changes in POLLIN/OUT/ERR until it re-enters the wait. + */ +LWS_VISIBLE LWS_EXTERN void +lws_cancel_service(struct lws_context *context); + +/** + * lws_service_fd() - Service polled socket with something waiting + * \param context: Websocket context + * \param pollfd: The pollfd entry describing the socket fd and which events + * happened, or NULL to tell lws to do only timeout servicing. + * + * This function takes a pollfd that has POLLIN or POLLOUT activity and + * services it according to the state of the associated + * struct lws. + * + * The one call deals with all "service" that might happen on a socket + * including listen accepts, http files as well as websocket protocol. + * + * If a pollfd says it has something, you can just pass it to + * lws_service_fd() whether it is a socket handled by lws or not. + * If it sees it is a lws socket, the traffic will be handled and + * pollfd->revents will be zeroed now. + * + * If the socket is foreign to lws, it leaves revents alone. So you can + * see if you should service yourself by checking the pollfd revents + * after letting lws try to service it. + * + * You should also call this with pollfd = NULL to just allow the + * once-per-second global timeout checks; if less than a second since the last + * check it returns immediately then. + */ +LWS_VISIBLE LWS_EXTERN int +lws_service_fd(struct lws_context *context, struct lws_pollfd *pollfd); + +/** + * lws_service_fd_tsi() - Service polled socket in specific service thread + * \param context: Websocket context + * \param pollfd: The pollfd entry describing the socket fd and which events + * happened. + * \param tsi: thread service index + * + * Same as lws_service_fd() but used with multiple service threads + */ +LWS_VISIBLE LWS_EXTERN int +lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, + int tsi); + +/** + * lws_service_adjust_timeout() - Check for any connection needing forced service + * \param context: Websocket context + * \param timeout_ms: The original poll timeout value. You can just set this + * to 1 if you don't really have a poll timeout. + * \param tsi: thread service index + * + * Under some conditions connections may need service even though there is no + * pending network action on them, this is "forced service". For default + * poll() and libuv / libev, the library takes care of calling this and + * dealing with it for you. But for external poll() integration, you need + * access to the apis. + * + * If anybody needs "forced service", returned timeout is zero. In that case, + * you can call lws_service_tsi() with a timeout of -1 to only service + * guys who need forced service. + */ +LWS_VISIBLE LWS_EXTERN int +lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi); + +/* Backwards compatibility */ +#define lws_plat_service_tsi lws_service_tsi + +LWS_VISIBLE LWS_EXTERN int +lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd); + +///@} + +/*! \defgroup http HTTP + + Modules related to handling HTTP +*/ +//@{ + +/*! \defgroup httpft HTTP File transfer + * \ingroup http + + APIs for sending local files in response to HTTP requests +*/ +//@{ + +/** + * lws_get_mimetype() - Determine mimetype to use from filename + * + * \param file: filename + * \param m: NULL, or mount context + * + * This uses a canned list of known filetypes first, if no match and m is + * non-NULL, then tries a list of per-mount file suffix to mimtype mappings. + * + * Returns either NULL or a pointer to the mimetype matching the file. + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_get_mimetype(const char *file, const struct lws_http_mount *m); + +/** + * lws_serve_http_file() - Send a file back to the client using http + * \param wsi: Websocket instance (available from user callback) + * \param file: The file to issue over http + * \param content_type: The http content type, eg, text/html + * \param other_headers: NULL or pointer to header string + * \param other_headers_len: length of the other headers if non-NULL + * + * This function is intended to be called from the callback in response + * to http requests from the client. It allows the callback to issue + * local files down the http link in a single step. + * + * Returning <0 indicates error and the wsi should be closed. Returning + * >0 indicates the file was completely sent and + * lws_http_transaction_completed() called on the wsi (and close if != 0) + * ==0 indicates the file transfer is started and needs more service later, + * the wsi should be left alone. + */ +LWS_VISIBLE LWS_EXTERN int +lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, + const char *other_headers, int other_headers_len); + +LWS_VISIBLE LWS_EXTERN int +lws_serve_http_file_fragment(struct lws *wsi); +//@} + + +enum http_status { + HTTP_STATUS_CONTINUE = 100, + + HTTP_STATUS_OK = 200, + HTTP_STATUS_NO_CONTENT = 204, + HTTP_STATUS_PARTIAL_CONTENT = 206, + + HTTP_STATUS_MOVED_PERMANENTLY = 301, + HTTP_STATUS_FOUND = 302, + HTTP_STATUS_SEE_OTHER = 303, + HTTP_STATUS_NOT_MODIFIED = 304, + + HTTP_STATUS_BAD_REQUEST = 400, + HTTP_STATUS_UNAUTHORIZED, + HTTP_STATUS_PAYMENT_REQUIRED, + HTTP_STATUS_FORBIDDEN, + HTTP_STATUS_NOT_FOUND, + HTTP_STATUS_METHOD_NOT_ALLOWED, + HTTP_STATUS_NOT_ACCEPTABLE, + HTTP_STATUS_PROXY_AUTH_REQUIRED, + HTTP_STATUS_REQUEST_TIMEOUT, + HTTP_STATUS_CONFLICT, + HTTP_STATUS_GONE, + HTTP_STATUS_LENGTH_REQUIRED, + HTTP_STATUS_PRECONDITION_FAILED, + HTTP_STATUS_REQ_ENTITY_TOO_LARGE, + HTTP_STATUS_REQ_URI_TOO_LONG, + HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE, + HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE, + HTTP_STATUS_EXPECTATION_FAILED, + + HTTP_STATUS_INTERNAL_SERVER_ERROR = 500, + HTTP_STATUS_NOT_IMPLEMENTED, + HTTP_STATUS_BAD_GATEWAY, + HTTP_STATUS_SERVICE_UNAVAILABLE, + HTTP_STATUS_GATEWAY_TIMEOUT, + HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED, +}; +/*! \defgroup html-chunked-substitution HTML Chunked Substitution + * \ingroup http + * + * ##HTML chunked Substitution + * + * APIs for receiving chunks of text, replacing a set of variable names via + * a callback, and then prepending and appending HTML chunked encoding + * headers. + */ +//@{ + +struct lws_process_html_args { + char *p; /**< pointer to the buffer containing the data */ + int len; /**< length of the original data at p */ + int max_len; /**< maximum length we can grow the data to */ + int final; /**< set if this is the last chunk of the file */ +}; + +typedef const char *(*lws_process_html_state_cb)(void *data, int index); + +struct lws_process_html_state { + char *start; /**< pointer to start of match */ + char swallow[16]; /**< matched character buffer */ + int pos; /**< position in match */ + void *data; /**< opaque pointer */ + const char * const *vars; /**< list of variable names */ + int count_vars; /**< count of variable names */ + + lws_process_html_state_cb replace; /**< called on match to perform substitution */ +}; + +/*! lws_chunked_html_process() - generic chunked substitution + * \param args: buffer to process using chunked encoding + * \param s: current processing state + */ +LWS_VISIBLE LWS_EXTERN int +lws_chunked_html_process(struct lws_process_html_args *args, + struct lws_process_html_state *s); +//@} + +/** \defgroup HTTP-headers-read HTTP headers: read + * \ingroup http + * + * ##HTTP header releated functions + * + * In lws the client http headers are temporarily stored in a pool, only for the + * duration of the http part of the handshake. It's because in most cases, + * the header content is ignored for the whole rest of the connection lifetime + * and would then just be taking up space needlessly. + * + * During LWS_CALLBACK_HTTP when the URI path is delivered is the last time + * the http headers are still allocated, you can use these apis then to + * look at and copy out interesting header content (cookies, etc) + * + * Notice that the header total length reported does not include a terminating + * '\0', however you must allocate for it when using the _copy apis. So the + * length reported for a header containing "123" is 3, but you must provide + * a buffer of length 4 so that "123\0" may be copied into it, or the copy + * will fail with a nonzero return code. + * + * In the special case of URL arguments, like ?x=1&y=2, the arguments are + * stored in a token named for the method, eg, WSI_TOKEN_GET_URI if it + * was a GET or WSI_TOKEN_POST_URI if POST. You can check the total + * length to confirm the method. + * + * For URL arguments, each argument is stored urldecoded in a "fragment", so + * you can use the fragment-aware api lws_hdr_copy_fragment() to access each + * argument in turn: the fragments contain urldecoded strings like x=1 or y=2. + * + * As a convenience, lws has an api that will find the fragment with a + * given name= part, lws_get_urlarg_by_name(). + */ +///@{ + +/** struct lws_tokens + * you need these to look at headers that have been parsed if using the + * LWS_CALLBACK_FILTER_CONNECTION callback. If a header from the enum + * list below is absent, .token = NULL and token_len = 0. Otherwise .token + * points to .token_len chars containing that header content. + */ +struct lws_tokens { + char *token; /**< pointer to start of the token */ + int token_len; /**< length of the token's value */ +}; + +/* enum lws_token_indexes + * these have to be kept in sync with lextable.h / minilex.c + * + * NOTE: These public enums are part of the abi. If you want to add one, + * add it at where specified so existing users are unaffected. + */ +enum lws_token_indexes { + WSI_TOKEN_GET_URI = 0, + WSI_TOKEN_POST_URI = 1, + WSI_TOKEN_OPTIONS_URI = 2, + WSI_TOKEN_HOST = 3, + WSI_TOKEN_CONNECTION = 4, + WSI_TOKEN_UPGRADE = 5, + WSI_TOKEN_ORIGIN = 6, + WSI_TOKEN_DRAFT = 7, + WSI_TOKEN_CHALLENGE = 8, + WSI_TOKEN_EXTENSIONS = 9, + WSI_TOKEN_KEY1 = 10, + WSI_TOKEN_KEY2 = 11, + WSI_TOKEN_PROTOCOL = 12, + WSI_TOKEN_ACCEPT = 13, + WSI_TOKEN_NONCE = 14, + WSI_TOKEN_HTTP = 15, + WSI_TOKEN_HTTP2_SETTINGS = 16, + WSI_TOKEN_HTTP_ACCEPT = 17, + WSI_TOKEN_HTTP_AC_REQUEST_HEADERS = 18, + WSI_TOKEN_HTTP_IF_MODIFIED_SINCE = 19, + WSI_TOKEN_HTTP_IF_NONE_MATCH = 20, + WSI_TOKEN_HTTP_ACCEPT_ENCODING = 21, + WSI_TOKEN_HTTP_ACCEPT_LANGUAGE = 22, + WSI_TOKEN_HTTP_PRAGMA = 23, + WSI_TOKEN_HTTP_CACHE_CONTROL = 24, + WSI_TOKEN_HTTP_AUTHORIZATION = 25, + WSI_TOKEN_HTTP_COOKIE = 26, + WSI_TOKEN_HTTP_CONTENT_LENGTH = 27, + WSI_TOKEN_HTTP_CONTENT_TYPE = 28, + WSI_TOKEN_HTTP_DATE = 29, + WSI_TOKEN_HTTP_RANGE = 30, + WSI_TOKEN_HTTP_REFERER = 31, + WSI_TOKEN_KEY = 32, + WSI_TOKEN_VERSION = 33, + WSI_TOKEN_SWORIGIN = 34, + + WSI_TOKEN_HTTP_COLON_AUTHORITY = 35, + WSI_TOKEN_HTTP_COLON_METHOD = 36, + WSI_TOKEN_HTTP_COLON_PATH = 37, + WSI_TOKEN_HTTP_COLON_SCHEME = 38, + WSI_TOKEN_HTTP_COLON_STATUS = 39, + + WSI_TOKEN_HTTP_ACCEPT_CHARSET = 40, + WSI_TOKEN_HTTP_ACCEPT_RANGES = 41, + WSI_TOKEN_HTTP_ACCESS_CONTROL_ALLOW_ORIGIN = 42, + WSI_TOKEN_HTTP_AGE = 43, + WSI_TOKEN_HTTP_ALLOW = 44, + WSI_TOKEN_HTTP_CONTENT_DISPOSITION = 45, + WSI_TOKEN_HTTP_CONTENT_ENCODING = 46, + WSI_TOKEN_HTTP_CONTENT_LANGUAGE = 47, + WSI_TOKEN_HTTP_CONTENT_LOCATION = 48, + WSI_TOKEN_HTTP_CONTENT_RANGE = 49, + WSI_TOKEN_HTTP_ETAG = 50, + WSI_TOKEN_HTTP_EXPECT = 51, + WSI_TOKEN_HTTP_EXPIRES = 52, + WSI_TOKEN_HTTP_FROM = 53, + WSI_TOKEN_HTTP_IF_MATCH = 54, + WSI_TOKEN_HTTP_IF_RANGE = 55, + WSI_TOKEN_HTTP_IF_UNMODIFIED_SINCE = 56, + WSI_TOKEN_HTTP_LAST_MODIFIED = 57, + WSI_TOKEN_HTTP_LINK = 58, + WSI_TOKEN_HTTP_LOCATION = 59, + WSI_TOKEN_HTTP_MAX_FORWARDS = 60, + WSI_TOKEN_HTTP_PROXY_AUTHENTICATE = 61, + WSI_TOKEN_HTTP_PROXY_AUTHORIZATION = 62, + WSI_TOKEN_HTTP_REFRESH = 63, + WSI_TOKEN_HTTP_RETRY_AFTER = 64, + WSI_TOKEN_HTTP_SERVER = 65, + WSI_TOKEN_HTTP_SET_COOKIE = 66, + WSI_TOKEN_HTTP_STRICT_TRANSPORT_SECURITY = 67, + WSI_TOKEN_HTTP_TRANSFER_ENCODING = 68, + WSI_TOKEN_HTTP_USER_AGENT = 69, + WSI_TOKEN_HTTP_VARY = 70, + WSI_TOKEN_HTTP_VIA = 71, + WSI_TOKEN_HTTP_WWW_AUTHENTICATE = 72, + + WSI_TOKEN_PATCH_URI = 73, + WSI_TOKEN_PUT_URI = 74, + WSI_TOKEN_DELETE_URI = 75, + + WSI_TOKEN_HTTP_URI_ARGS = 76, + WSI_TOKEN_PROXY = 77, + WSI_TOKEN_HTTP_X_REAL_IP = 78, + WSI_TOKEN_HTTP1_0 = 79, + WSI_TOKEN_X_FORWARDED_FOR = 80, + WSI_TOKEN_CONNECT = 81, + WSI_TOKEN_HEAD_URI = 82, + WSI_TOKEN_TE = 83, + /****** add new things just above ---^ ******/ + + /* use token storage to stash these internally, not for + * user use */ + + _WSI_TOKEN_CLIENT_SENT_PROTOCOLS, + _WSI_TOKEN_CLIENT_PEER_ADDRESS, + _WSI_TOKEN_CLIENT_URI, + _WSI_TOKEN_CLIENT_HOST, + _WSI_TOKEN_CLIENT_ORIGIN, + _WSI_TOKEN_CLIENT_METHOD, + _WSI_TOKEN_CLIENT_IFACE, + + /* always last real token index*/ + WSI_TOKEN_COUNT, + + /* parser state additions, no storage associated */ + WSI_TOKEN_NAME_PART, + WSI_TOKEN_SKIPPING, + WSI_TOKEN_SKIPPING_SAW_CR, + WSI_PARSING_COMPLETE, + WSI_INIT_TOKEN_MUXURL, +}; + +struct lws_token_limits { + unsigned short token_limit[WSI_TOKEN_COUNT]; /**< max chars for this token */ +}; + +/** + * lws_token_to_string() - returns a textual representation of a hdr token index + * + * \param token: token index + */ +LWS_VISIBLE LWS_EXTERN const unsigned char * +lws_token_to_string(enum lws_token_indexes token); + +/** + * lws_hdr_total_length: report length of all fragments of a header totalled up + * The returned length does not include the space for a + * terminating '\0' + * + * \param wsi: websocket connection + * \param h: which header index we are interested in + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_hdr_total_length(struct lws *wsi, enum lws_token_indexes h); + +/** + * lws_hdr_fragment_length: report length of a single fragment of a header + * The returned length does not include the space for a + * terminating '\0' + * + * \param wsi: websocket connection + * \param h: which header index we are interested in + * \param frag_idx: which fragment of h we want to get the length of + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h, int frag_idx); + +/** + * lws_hdr_copy() - copy a single fragment of the given header to a buffer + * The buffer length len must include space for an additional + * terminating '\0', or it will fail returning -1. + * + * \param wsi: websocket connection + * \param dest: destination buffer + * \param len: length of destination buffer + * \param h: which header index we are interested in + * + * copies the whole, aggregated header, even if it was delivered in + * several actual headers piece by piece + */ +LWS_VISIBLE LWS_EXTERN int +lws_hdr_copy(struct lws *wsi, char *dest, int len, enum lws_token_indexes h); + +/** + * lws_hdr_copy_fragment() - copy a single fragment of the given header to a buffer + * The buffer length len must include space for an additional + * terminating '\0', or it will fail returning -1. + * If the requested fragment index is not present, it fails + * returning -1. + * + * \param wsi: websocket connection + * \param dest: destination buffer + * \param len: length of destination buffer + * \param h: which header index we are interested in + * \param frag_idx: which fragment of h we want to copy + * + * Normally this is only useful + * to parse URI arguments like ?x=1&y=2, token index WSI_TOKEN_HTTP_URI_ARGS + * fragment 0 will contain "x=1" and fragment 1 "y=2" + */ +LWS_VISIBLE LWS_EXTERN int +lws_hdr_copy_fragment(struct lws *wsi, char *dest, int len, + enum lws_token_indexes h, int frag_idx); + +/** + * lws_get_urlarg_by_name() - return pointer to arg value if present + * \param wsi: the connection to check + * \param name: the arg name, like "token=" + * \param buf: the buffer to receive the urlarg (including the name= part) + * \param len: the length of the buffer to receive the urlarg + * + * Returns NULL if not found or a pointer inside buf to just after the + * name= part. + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_get_urlarg_by_name(struct lws *wsi, const char *name, char *buf, int len); +///@} + +/*! \defgroup HTTP-headers-create HTTP headers: create + * + * ## HTTP headers: Create + * + * These apis allow you to create HTTP response headers in a way compatible with + * both HTTP/1.x and HTTP/2. + * + * They each append to a buffer taking care about the buffer end, which is + * passed in as a pointer. When data is written to the buffer, the current + * position p is updated accordingly. + * + * All of these apis are LWS_WARN_UNUSED_RESULT as they can run out of space + * and fail with nonzero return. + */ +///@{ + +#define LWSAHH_CODE_MASK ((1 << 16) - 1) +#define LWSAHH_FLAG_NO_SERVER_NAME (1 << 30) + +/** + * lws_add_http_header_status() - add the HTTP response status code + * + * \param wsi: the connection to check + * \param code: an HTTP code like 200, 404 etc (see enum http_status) + * \param p: pointer to current position in buffer pointer + * \param end: pointer to end of buffer + * + * Adds the initial response code, so should be called first. + * + * Code may additionally take OR'd flags: + * + * LWSAHH_FLAG_NO_SERVER_NAME: don't apply server name header this time + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_add_http_header_status(struct lws *wsi, + unsigned int code, unsigned char **p, + unsigned char *end); +/** + * lws_add_http_header_by_name() - append named header and value + * + * \param wsi: the connection to check + * \param name: the hdr name, like "my-header" + * \param value: the value after the = for this header + * \param length: the length of the value + * \param p: pointer to current position in buffer pointer + * \param end: pointer to end of buffer + * + * Appends name: value to the headers + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_add_http_header_by_name(struct lws *wsi, const unsigned char *name, + const unsigned char *value, int length, + unsigned char **p, unsigned char *end); +/** + * lws_add_http_header_by_token() - append given header and value + * + * \param wsi: the connection to check + * \param token: the token index for the hdr + * \param value: the value after the = for this header + * \param length: the length of the value + * \param p: pointer to current position in buffer pointer + * \param end: pointer to end of buffer + * + * Appends name=value to the headers, but is able to take advantage of better + * HTTP/2 coding mechanisms where possible. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_add_http_header_by_token(struct lws *wsi, enum lws_token_indexes token, + const unsigned char *value, int length, + unsigned char **p, unsigned char *end); +/** + * lws_add_http_header_content_length() - append content-length helper + * + * \param wsi: the connection to check + * \param content_length: the content length to use + * \param p: pointer to current position in buffer pointer + * \param end: pointer to end of buffer + * + * Appends content-length: content_length to the headers + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_add_http_header_content_length(struct lws *wsi, + lws_filepos_t content_length, + unsigned char **p, unsigned char *end); +/** + * lws_finalize_http_header() - terminate header block + * + * \param wsi: the connection to check + * \param p: pointer to current position in buffer pointer + * \param end: pointer to end of buffer + * + * Indicates no more headers will be added + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_finalize_http_header(struct lws *wsi, unsigned char **p, + unsigned char *end); +///@} + +/** \defgroup form-parsing Form Parsing + * \ingroup http + * ##POSTed form parsing functions + * + * These lws_spa (stateful post arguments) apis let you parse and urldecode + * POSTed form arguments, both using simple urlencoded and multipart transfer + * encoding. + * + * It's capable of handling file uploads as well a named input parsing, + * and the apis are the same for both form upload styles. + * + * You feed it a list of parameter names and it creates pointers to the + * urldecoded arguments: file upload parameters pass the file data in chunks to + * a user-supplied callback as they come. + * + * Since it's stateful, it handles the incoming data needing more than one + * POST_BODY callback and has no limit on uploaded file size. + */ +///@{ + +/** enum lws_spa_fileupload_states */ +enum lws_spa_fileupload_states { + LWS_UFS_CONTENT, + /**< a chunk of file content has arrived */ + LWS_UFS_FINAL_CONTENT, + /**< the last chunk (possibly zero length) of file content has arrived */ + LWS_UFS_OPEN + /**< a new file is starting to arrive */ +}; + +/** + * lws_spa_fileupload_cb() - callback to receive file upload data + * + * \param data: opt_data pointer set in lws_spa_create + * \param name: name of the form field being uploaded + * \param filename: original filename from client + * \param buf: start of data to receive + * \param len: length of data to receive + * \param state: information about how this call relates to file + * + * Notice name and filename shouldn't be trusted, as they are passed from + * HTTP provided by the client. + */ +typedef int (*lws_spa_fileupload_cb)(void *data, const char *name, + const char *filename, char *buf, int len, + enum lws_spa_fileupload_states state); + +/** struct lws_spa - opaque urldecode parser capable of handling multipart + * and file uploads */ +struct lws_spa; + +/** + * lws_spa_create() - create urldecode parser + * + * \param wsi: lws connection (used to find Content Type) + * \param param_names: array of form parameter names, like "username" + * \param count_params: count of param_names + * \param max_storage: total amount of form parameter values we can store + * \param opt_cb: NULL, or callback to receive file upload data. + * \param opt_data: NULL, or user pointer provided to opt_cb. + * + * Creates a urldecode parser and initializes it. + * + * opt_cb can be NULL if you just want normal name=value parsing, however + * if one or more entries in your form are bulk data (file transfer), you + * can provide this callback and filter on the name callback parameter to + * treat that urldecoded data separately. The callback should return -1 + * in case of fatal error, and 0 if OK. + */ +LWS_VISIBLE LWS_EXTERN struct lws_spa * +lws_spa_create(struct lws *wsi, const char * const *param_names, + int count_params, int max_storage, lws_spa_fileupload_cb opt_cb, + void *opt_data); + +/** + * lws_spa_process() - parses a chunk of input data + * + * \param spa: the parser object previously created + * \param in: incoming, urlencoded data + * \param len: count of bytes valid at \param in + */ +LWS_VISIBLE LWS_EXTERN int +lws_spa_process(struct lws_spa *spa, const char *in, int len); + +/** + * lws_spa_finalize() - indicate incoming data completed + * + * \param spa: the parser object previously created + */ +LWS_VISIBLE LWS_EXTERN int +lws_spa_finalize(struct lws_spa *spa); + +/** + * lws_spa_get_length() - return length of parameter value + * + * \param spa: the parser object previously created + * \param n: parameter ordinal to return length of value for + */ +LWS_VISIBLE LWS_EXTERN int +lws_spa_get_length(struct lws_spa *spa, int n); + +/** + * lws_spa_get_string() - return pointer to parameter value + * \param spa: the parser object previously created + * \param n: parameter ordinal to return pointer to value for + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_spa_get_string(struct lws_spa *spa, int n); + +/** + * lws_spa_destroy() - destroy parser object + * + * \param spa: the parser object previously created + */ +LWS_VISIBLE LWS_EXTERN int +lws_spa_destroy(struct lws_spa *spa); +///@} + +/*! \defgroup urlendec Urlencode and Urldecode + * \ingroup http + * + * ##HTML chunked Substitution + * + * APIs for receiving chunks of text, replacing a set of variable names via + * a callback, and then prepending and appending HTML chunked encoding + * headers. + */ +//@{ + +/** + * lws_urlencode() - like strncpy but with urlencoding + * + * \param escaped: output buffer + * \param string: input buffer ('/0' terminated) + * \param len: output buffer max length + * + * Because urlencoding expands the output string, it's not + * possible to do it in-place, ie, with escaped == string + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_urlencode(char *escaped, const char *string, int len); + +/* + * URLDECODE 1 / 2 + * + * This simple urldecode only operates until the first '\0' and requires the + * data to exist all at once + */ +/** + * lws_urldecode() - like strncpy but with urldecoding + * + * \param string: output buffer + * \param escaped: input buffer ('\0' terminated) + * \param len: output buffer max length + * + * This is only useful for '\0' terminated strings + * + * Since urldecoding only shrinks the output string, it is possible to + * do it in-place, ie, string == escaped + * + * Returns 0 if completed OK or nonzero for urldecode violation (non-hex chars + * where hex required, etc) + */ +LWS_VISIBLE LWS_EXTERN int +lws_urldecode(char *string, const char *escaped, int len); +///@} +/** + * lws_return_http_status() - Return simple http status + * \param wsi: Websocket instance (available from user callback) + * \param code: Status index, eg, 404 + * \param html_body: User-readable HTML description < 1KB, or NULL + * + * Helper to report HTTP errors back to the client cleanly and + * consistently + */ +LWS_VISIBLE LWS_EXTERN int +lws_return_http_status(struct lws *wsi, unsigned int code, + const char *html_body); + +/** + * lws_http_redirect() - write http redirect into buffer + * + * \param wsi: websocket connection + * \param code: HTTP response code (eg, 301) + * \param loc: where to redirect to + * \param len: length of loc + * \param p: pointer current position in buffer (updated as we write) + * \param end: pointer to end of buffer + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_http_redirect(struct lws *wsi, int code, const unsigned char *loc, int len, + unsigned char **p, unsigned char *end); + +/** + * lws_http_transaction_completed() - wait for new http transaction or close + * \param wsi: websocket connection + * + * Returns 1 if the HTTP connection must close now + * Returns 0 and resets connection to wait for new HTTP header / + * transaction if possible + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_http_transaction_completed(struct lws *wsi); +///@} + +/*! \defgroup pur Sanitize / purify SQL and JSON helpers + * + * ##Sanitize / purify SQL and JSON helpers + * + * APIs for escaping untrusted JSON and SQL safely before use + */ +//@{ + +/** + * lws_sql_purify() - like strncpy but with escaping for sql quotes + * + * \param escaped: output buffer + * \param string: input buffer ('/0' terminated) + * \param len: output buffer max length + * + * Because escaping expands the output string, it's not + * possible to do it in-place, ie, with escaped == string + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_sql_purify(char *escaped, const char *string, int len); + +/** + * lws_json_purify() - like strncpy but with escaping for json chars + * + * \param escaped: output buffer + * \param string: input buffer ('/0' terminated) + * \param len: output buffer max length + * + * Because escaping expands the output string, it's not + * possible to do it in-place, ie, with escaped == string + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_json_purify(char *escaped, const char *string, int len); +///@} + +/*! \defgroup ev libev helpers + * + * ##libev helpers + * + * APIs specific to libev event loop itegration + */ +///@{ + +#ifdef LWS_WITH_LIBEV +typedef void (lws_ev_signal_cb_t)(EV_P_ struct ev_signal *w, int revents); + +LWS_VISIBLE LWS_EXTERN int +lws_ev_sigint_cfg(struct lws_context *context, int use_ev_sigint, + lws_ev_signal_cb_t *cb); + +LWS_VISIBLE LWS_EXTERN int +lws_ev_initloop(struct lws_context *context, struct ev_loop *loop, int tsi); + +LWS_VISIBLE LWS_EXTERN void +lws_ev_sigint_cb(struct ev_loop *loop, struct ev_signal *watcher, int revents); +#endif /* LWS_WITH_LIBEV */ + +///@} + +/*! \defgroup uv libuv helpers + * + * ##libuv helpers + * + * APIs specific to libuv event loop itegration + */ +///@{ +#ifdef LWS_WITH_LIBUV +LWS_VISIBLE LWS_EXTERN int +lws_uv_sigint_cfg(struct lws_context *context, int use_uv_sigint, + uv_signal_cb cb); + +LWS_VISIBLE LWS_EXTERN void +lws_libuv_run(const struct lws_context *context, int tsi); + +LWS_VISIBLE LWS_EXTERN void +lws_libuv_stop(struct lws_context *context); + +LWS_VISIBLE LWS_EXTERN void +lws_libuv_stop_without_kill(const struct lws_context *context, int tsi); + +LWS_VISIBLE LWS_EXTERN int +lws_uv_initloop(struct lws_context *context, uv_loop_t *loop, int tsi); + +LWS_VISIBLE LWS_EXTERN uv_loop_t * +lws_uv_getloop(struct lws_context *context, int tsi); + +LWS_VISIBLE LWS_EXTERN void +lws_uv_sigint_cb(uv_signal_t *watcher, int signum); + +LWS_VISIBLE LWS_EXTERN void +lws_close_all_handles_in_loop(uv_loop_t *loop); +#endif /* LWS_WITH_LIBUV */ +///@} + +/*! \defgroup event libevent helpers + * + * ##libevent helpers + * + * APIs specific to libevent event loop itegration + */ +///@{ + +#ifdef LWS_WITH_LIBEVENT +typedef void (lws_event_signal_cb_t) (evutil_socket_t sock_fd, short revents, + void *ctx); + +LWS_VISIBLE LWS_EXTERN int +lws_event_sigint_cfg(struct lws_context *context, int use_event_sigint, + lws_event_signal_cb_t cb); + +LWS_VISIBLE LWS_EXTERN int +lws_event_initloop(struct lws_context *context, struct event_base *loop, + int tsi); + +LWS_VISIBLE LWS_EXTERN void +lws_event_sigint_cb(evutil_socket_t sock_fd, short revents, + void *ctx); +#endif /* LWS_WITH_LIBEVENT */ + +///@} + +/*! \defgroup timeout Connection timeouts + + APIs related to setting connection timeouts +*/ +//@{ + +/* + * NOTE: These public enums are part of the abi. If you want to add one, + * add it at where specified so existing users are unaffected. + */ +enum pending_timeout { + NO_PENDING_TIMEOUT = 0, + PENDING_TIMEOUT_AWAITING_PROXY_RESPONSE = 1, + PENDING_TIMEOUT_AWAITING_CONNECT_RESPONSE = 2, + PENDING_TIMEOUT_ESTABLISH_WITH_SERVER = 3, + PENDING_TIMEOUT_AWAITING_SERVER_RESPONSE = 4, + PENDING_TIMEOUT_AWAITING_PING = 5, + PENDING_TIMEOUT_CLOSE_ACK = 6, + PENDING_TIMEOUT_AWAITING_EXTENSION_CONNECT_RESPONSE = 7, + PENDING_TIMEOUT_SENT_CLIENT_HANDSHAKE = 8, + PENDING_TIMEOUT_SSL_ACCEPT = 9, + PENDING_TIMEOUT_HTTP_CONTENT = 10, + PENDING_TIMEOUT_AWAITING_CLIENT_HS_SEND = 11, + PENDING_FLUSH_STORED_SEND_BEFORE_CLOSE = 12, + PENDING_TIMEOUT_SHUTDOWN_FLUSH = 13, + PENDING_TIMEOUT_CGI = 14, + PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE = 15, + PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING = 16, + PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG = 17, + PENDING_TIMEOUT_CLIENT_ISSUE_PAYLOAD = 18, + PENDING_TIMEOUT_AWAITING_SOCKS_GREETING_REPLY = 19, + PENDING_TIMEOUT_AWAITING_SOCKS_CONNECT_REPLY = 20, + PENDING_TIMEOUT_AWAITING_SOCKS_AUTH_REPLY = 21, + PENDING_TIMEOUT_KILLED_BY_SSL_INFO = 22, + PENDING_TIMEOUT_KILLED_BY_PARENT = 23, + PENDING_TIMEOUT_CLOSE_SEND = 24, + PENDING_TIMEOUT_HOLDING_AH = 25, + + /****** add new things just above ---^ ******/ + + PENDING_TIMEOUT_USER_REASON_BASE = 1000 +}; + +#define LWS_TO_KILL_ASYNC -1 +/**< If LWS_TO_KILL_ASYNC is given as the timeout sec in a lws_set_timeout() + * call, then the connection is marked to be killed at the next timeout + * check. This is how you should force-close the wsi being serviced if + * you are doing it outside the callback (where you should close by nonzero + * return). + */ +#define LWS_TO_KILL_SYNC -2 +/**< If LWS_TO_KILL_SYNC is given as the timeout sec in a lws_set_timeout() + * call, then the connection is closed before returning (which may delete + * the wsi). This should only be used where the wsi being closed is not the + * wsi currently being serviced. + */ +/** + * lws_set_timeout() - marks the wsi as subject to a timeout + * + * You will not need this unless you are doing something special + * + * \param wsi: Websocket connection instance + * \param reason: timeout reason + * \param secs: how many seconds. You may set to LWS_TO_KILL_ASYNC to + * force the connection to timeout at the next opportunity, or + * LWS_TO_KILL_SYNC to close it synchronously if you know the + * wsi is not the one currently being serviced. + */ +LWS_VISIBLE LWS_EXTERN void +lws_set_timeout(struct lws *wsi, enum pending_timeout reason, int secs); +///@} + +/*! \defgroup sending-data Sending data + + APIs related to writing data on a connection +*/ +//@{ +#if !defined(LWS_SIZEOFPTR) +#define LWS_SIZEOFPTR (sizeof (void *)) +#endif +#if !defined(u_int64_t) +#define u_int64_t unsigned long long +#endif + +#if defined(__x86_64__) +#define _LWS_PAD_SIZE 16 /* Intel recommended for best performance */ +#else +#define _LWS_PAD_SIZE LWS_SIZEOFPTR /* Size of a pointer on the target arch */ +#endif +#define _LWS_PAD(n) (((n) % _LWS_PAD_SIZE) ? \ + ((n) + (_LWS_PAD_SIZE - ((n) % _LWS_PAD_SIZE))) : (n)) +/* last 2 is for lws-meta */ +#define LWS_PRE _LWS_PAD(4 + 10 + 2) +/* used prior to 1.7 and retained for backward compatibility */ +#define LWS_SEND_BUFFER_PRE_PADDING LWS_PRE +#define LWS_SEND_BUFFER_POST_PADDING 0 + +/* + * NOTE: These public enums are part of the abi. If you want to add one, + * add it at where specified so existing users are unaffected. + */ +enum lws_write_protocol { + LWS_WRITE_TEXT = 0, + /**< Send a ws TEXT message,the pointer must have LWS_PRE valid + * memory behind it. The receiver expects only valid utf-8 in the + * payload */ + LWS_WRITE_BINARY = 1, + /**< Send a ws BINARY message, the pointer must have LWS_PRE valid + * memory behind it. Any sequence of bytes is valid */ + LWS_WRITE_CONTINUATION = 2, + /**< Continue a previous ws message, the pointer must have LWS_PRE valid + * memory behind it */ + LWS_WRITE_HTTP = 3, + /**< Send HTTP content */ + + /* LWS_WRITE_CLOSE is handled by lws_close_reason() */ + LWS_WRITE_PING = 5, + LWS_WRITE_PONG = 6, + + /* Same as write_http but we know this write ends the transaction */ + LWS_WRITE_HTTP_FINAL = 7, + + /* HTTP2 */ + + LWS_WRITE_HTTP_HEADERS = 8, + /**< Send http headers (http2 encodes this payload and LWS_WRITE_HTTP + * payload differently, http 1.x links also handle this correctly. so + * to be compatible with both in the future,header response part should + * be sent using this regardless of http version expected) + */ + LWS_WRITE_HTTP_HEADERS_CONTINUATION = 9, + /**< Continuation of http/2 headers + */ + + /****** add new things just above ---^ ******/ + + /* flags */ + + LWS_WRITE_NO_FIN = 0x40, + /**< This part of the message is not the end of the message */ + + LWS_WRITE_H2_STREAM_END = 0x80, + /**< Flag indicates this packet should go out with STREAM_END if h2 + * STREAM_END is allowed on DATA or HEADERS. + */ + + LWS_WRITE_CLIENT_IGNORE_XOR_MASK = 0x80 + /**< client packet payload goes out on wire unmunged + * only useful for security tests since normal servers cannot + * decode the content if used */ +}; + +/* used with LWS_CALLBACK_CHILD_WRITE_VIA_PARENT */ + +struct lws_write_passthru { + struct lws *wsi; + unsigned char *buf; + size_t len; + enum lws_write_protocol wp; +}; + + +/** + * lws_write() - Apply protocol then write data to client + * \param wsi: Websocket instance (available from user callback) + * \param buf: The data to send. For data being sent on a websocket + * connection (ie, not default http), this buffer MUST have + * LWS_PRE bytes valid BEFORE the pointer. + * This is so the protocol header data can be added in-situ. + * \param len: Count of the data bytes in the payload starting from buf + * \param protocol: Use LWS_WRITE_HTTP to reply to an http connection, and one + * of LWS_WRITE_BINARY or LWS_WRITE_TEXT to send appropriate + * data on a websockets connection. Remember to allow the extra + * bytes before and after buf if LWS_WRITE_BINARY or LWS_WRITE_TEXT + * are used. + * + * This function provides the way to issue data back to the client + * for both http and websocket protocols. + * + * IMPORTANT NOTICE! + * + * When sending with websocket protocol + * + * LWS_WRITE_TEXT, + * LWS_WRITE_BINARY, + * LWS_WRITE_CONTINUATION, + * LWS_WRITE_PING, + * LWS_WRITE_PONG + * + * the send buffer has to have LWS_PRE bytes valid BEFORE + * the buffer pointer you pass to lws_write(). + * + * This allows us to add protocol info before and after the data, and send as + * one packet on the network without payload copying, for maximum efficiency. + * + * So for example you need this kind of code to use lws_write with a + * 128-byte payload + * + * char buf[LWS_PRE + 128]; + * + * // fill your part of the buffer... for example here it's all zeros + * memset(&buf[LWS_PRE], 0, 128); + * + * lws_write(wsi, &buf[LWS_PRE], 128, LWS_WRITE_TEXT); + * + * When sending HTTP, with + * + * LWS_WRITE_HTTP, + * LWS_WRITE_HTTP_HEADERS + * LWS_WRITE_HTTP_FINAL + * + * there is no protocol data prepended, and don't need to take care about the + * LWS_PRE bytes valid before the buffer pointer. + * + * LWS_PRE is at least the frame nonce + 2 header + 8 length + * LWS_SEND_BUFFER_POST_PADDING is deprecated, it's now 0 and can be left off. + * The example apps no longer use it. + * + * Pad LWS_PRE to the CPU word size, so that word references + * to the address immediately after the padding won't cause an unaligned access + * error. Sometimes for performance reasons the recommended padding is even + * larger than sizeof(void *). + * + * In the case of sending using websocket protocol, be sure to allocate + * valid storage before and after buf as explained above. This scheme + * allows maximum efficiency of sending data and protocol in a single + * packet while not burdening the user code with any protocol knowledge. + * + * Return may be -1 for a fatal error needing connection close, or the + * number of bytes sent. + * + * Truncated Writes + * ================ + * + * The OS may not accept everything you asked to write on the connection. + * + * Posix defines POLLOUT indication from poll() to show that the connection + * will accept more write data, but it doesn't specifiy how much. It may just + * accept one byte of whatever you wanted to send. + * + * LWS will buffer the remainder automatically, and send it out autonomously. + * + * During that time, WRITABLE callbacks will be suppressed. + * + * This is to handle corner cases where unexpectedly the OS refuses what we + * usually expect it to accept. You should try to send in chunks that are + * almost always accepted in order to avoid the inefficiency of the buffering. + */ +LWS_VISIBLE LWS_EXTERN int +lws_write(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol protocol); + +/* helper for case where buffer may be const */ +#define lws_write_http(wsi, buf, len) \ + lws_write(wsi, (unsigned char *)(buf), len, LWS_WRITE_HTTP) +///@} + +/** \defgroup callback-when-writeable Callback when writeable + * + * ##Callback When Writeable + * + * lws can only write data on a connection when it is able to accept more + * data without blocking. + * + * So a basic requirement is we should only use the lws_write() apis when the + * connection we want to write on says that he can accept more data. + * + * When lws cannot complete your send at the time, it will buffer the data + * and send it in the background, suppressing any further WRITEABLE callbacks + * on that connection until it completes. So it is important to write new + * things in a new writeable callback. + * + * These apis reflect the various ways we can indicate we would like to be + * called back when one or more connections is writeable. + */ +///@{ + +/** + * lws_callback_on_writable() - Request a callback when this socket + * becomes able to be written to without + * blocking + * + * \param wsi: Websocket connection instance to get callback for + * + * - Which: only this wsi + * - When: when the individual connection becomes writeable + * - What: LWS_CALLBACK_*_WRITEABLE + */ +LWS_VISIBLE LWS_EXTERN int +lws_callback_on_writable(struct lws *wsi); + +/** + * lws_callback_on_writable_all_protocol() - Request a callback for all + * connections using the given protocol when it + * becomes possible to write to each socket without + * blocking in turn. + * + * \param context: lws_context + * \param protocol: Protocol whose connections will get callbacks + * + * - Which: connections using this protocol on ANY VHOST + * - When: when the individual connection becomes writeable + * - What: LWS_CALLBACK_*_WRITEABLE + */ +LWS_VISIBLE LWS_EXTERN int +lws_callback_on_writable_all_protocol(const struct lws_context *context, + const struct lws_protocols *protocol); + +/** + * lws_callback_on_writable_all_protocol_vhost() - Request a callback for + * all connections on same vhost using the given protocol + * when it becomes possible to write to each socket without + * blocking in turn. + * + * \param vhost: Only consider connections on this lws_vhost + * \param protocol: Protocol whose connections will get callbacks + * + * - Which: connections using this protocol on GIVEN VHOST ONLY + * - When: when the individual connection becomes writeable + * - What: LWS_CALLBACK_*_WRITEABLE + */ +LWS_VISIBLE LWS_EXTERN int +lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost, + const struct lws_protocols *protocol); + +/** + * lws_callback_all_protocol() - Callback all connections using + * the given protocol with the given reason + * + * \param context: lws_context + * \param protocol: Protocol whose connections will get callbacks + * \param reason: Callback reason index + * + * - Which: connections using this protocol on ALL VHOSTS + * - When: before returning + * - What: reason + * + * This isn't normally what you want... normally any update of connection- + * specific information can wait until a network-related callback like rx, + * writable, or close. + */ +LWS_VISIBLE LWS_EXTERN int +lws_callback_all_protocol(struct lws_context *context, + const struct lws_protocols *protocol, int reason); + +/** + * lws_callback_all_protocol_vhost() - Callback all connections using + * the given protocol with the given reason. This is + * deprecated since v2.4: use lws_callback_all_protocol_vhost_args + * + * \param vh: Vhost whose connections will get callbacks + * \param protocol: Which protocol to match. NULL means all. + * \param reason: Callback reason index + * + * - Which: connections using this protocol on GIVEN VHOST ONLY + * - When: now + * - What: reason + */ +LWS_VISIBLE LWS_EXTERN int +lws_callback_all_protocol_vhost(struct lws_vhost *vh, + const struct lws_protocols *protocol, int reason) +LWS_WARN_DEPRECATED; + +/** + * lws_callback_all_protocol_vhost_args() - Callback all connections using + * the given protocol with the given reason and args + * + * \param vh: Vhost whose connections will get callbacks + * \param protocol: Which protocol to match. NULL means all. + * \param reason: Callback reason index + * \param argp: Callback "in" parameter + * \param len: Callback "len" parameter + * + * - Which: connections using this protocol on GIVEN VHOST ONLY + * - When: now + * - What: reason + */ +LWS_VISIBLE int +lws_callback_all_protocol_vhost_args(struct lws_vhost *vh, + const struct lws_protocols *protocol, int reason, + void *argp, size_t len); + +/** + * lws_callback_vhost_protocols() - Callback all protocols enabled on a vhost + * with the given reason + * + * \param wsi: wsi whose vhost will get callbacks + * \param reason: Callback reason index + * \param in: in argument to callback + * \param len: len argument to callback + * + * - Which: connections using this protocol on same VHOST as wsi ONLY + * - When: now + * - What: reason + */ +LWS_VISIBLE LWS_EXTERN int +lws_callback_vhost_protocols(struct lws *wsi, int reason, void *in, int len); + +LWS_VISIBLE LWS_EXTERN int +lws_callback_http_dummy(struct lws *wsi, enum lws_callback_reasons reason, + void *user, void *in, size_t len); + +/** + * lws_get_socket_fd() - returns the socket file descriptor + * + * You will not need this unless you are doing something special + * + * \param wsi: Websocket connection instance + */ +LWS_VISIBLE LWS_EXTERN int +lws_get_socket_fd(struct lws *wsi); + +/** + * lws_get_peer_write_allowance() - get the amount of data writeable to peer + * if known + * + * \param wsi: Websocket connection instance + * + * if the protocol does not have any guidance, returns -1. Currently only + * http2 connections get send window information from this API. But your code + * should use it so it can work properly with any protocol. + * + * If nonzero return is the amount of payload data the peer or intermediary has + * reported it has buffer space for. That has NO relationship with the amount + * of buffer space your OS can accept on this connection for a write action. + * + * This number represents the maximum you could send to the peer or intermediary + * on this connection right now without the protocol complaining. + * + * lws manages accounting for send window updates and payload writes + * automatically, so this number reflects the situation at the peer or + * intermediary dynamically. + */ +LWS_VISIBLE LWS_EXTERN size_t +lws_get_peer_write_allowance(struct lws *wsi); +///@} + +enum { + /* + * Flags for enable and disable rxflow with reason bitmap and with + * backwards-compatible single bool + */ + LWS_RXFLOW_REASON_USER_BOOL = (1 << 0), + LWS_RXFLOW_REASON_HTTP_RXBUFFER = (1 << 6), + LWS_RXFLOW_REASON_H2_PPS_PENDING = (1 << 7), + + LWS_RXFLOW_REASON_APPLIES = (1 << 14), + LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT = (1 << 13), + LWS_RXFLOW_REASON_APPLIES_ENABLE = LWS_RXFLOW_REASON_APPLIES | + LWS_RXFLOW_REASON_APPLIES_ENABLE_BIT, + LWS_RXFLOW_REASON_APPLIES_DISABLE = LWS_RXFLOW_REASON_APPLIES, + LWS_RXFLOW_REASON_FLAG_PROCESS_NOW = (1 << 12), + +}; + +/** + * lws_rx_flow_control() - Enable and disable socket servicing for + * received packets. + * + * If the output side of a server process becomes choked, this allows flow + * control for the input side. + * + * \param wsi: Websocket connection instance to get callback for + * \param enable: 0 = disable read servicing for this connection, 1 = enable + * + * If you need more than one additive reason for rxflow control, you can give + * iLWS_RXFLOW_REASON_APPLIES_ENABLE or _DISABLE together with one or more of + * b5..b0 set to idicate which bits to enable or disable. If any bits are + * enabled, rx on the connection is suppressed. + * + * LWS_RXFLOW_REASON_FLAG_PROCESS_NOW flag may also be given to force any change + * in rxflowbstatus to benapplied immediately, this should be used when you are + * changing a wsi flow control state from outside a callback on that wsi. + */ +LWS_VISIBLE LWS_EXTERN int +lws_rx_flow_control(struct lws *wsi, int enable); + +/** + * lws_rx_flow_allow_all_protocol() - Allow all connections with this protocol to receive + * + * When the user server code realizes it can accept more input, it can + * call this to have the RX flow restriction removed from all connections using + * the given protocol. + * \param context: lws_context + * \param protocol: all connections using this protocol will be allowed to receive + */ +LWS_VISIBLE LWS_EXTERN void +lws_rx_flow_allow_all_protocol(const struct lws_context *context, + const struct lws_protocols *protocol); + +/** + * lws_remaining_packet_payload() - Bytes to come before "overall" + * rx packet is complete + * \param wsi: Websocket instance (available from user callback) + * + * This function is intended to be called from the callback if the + * user code is interested in "complete packets" from the client. + * libwebsockets just passes through payload as it comes and issues a buffer + * additionally when it hits a built-in limit. The LWS_CALLBACK_RECEIVE + * callback handler can use this API to find out if the buffer it has just + * been given is the last piece of a "complete packet" from the client -- + * when that is the case lws_remaining_packet_payload() will return + * 0. + * + * Many protocols won't care becuse their packets are always small. + */ +LWS_VISIBLE LWS_EXTERN size_t +lws_remaining_packet_payload(struct lws *wsi); + + +/** \defgroup sock-adopt Socket adoption helpers + * ##Socket adoption helpers + * + * When integrating with an external app with its own event loop, these can + * be used to accept connections from someone else's listening socket. + * + * When using lws own event loop, these are not needed. + */ +///@{ + +/** + * lws_adopt_socket() - adopt foreign socket as if listen socket accepted it + * for the default vhost of context. + * + * \param context: lws context + * \param accept_fd: fd of already-accepted socket to adopt + * + * Either returns new wsi bound to accept_fd, or closes accept_fd and + * returns NULL, having cleaned up any new wsi pieces. + * + * LWS adopts the socket in http serving mode, it's ready to accept an upgrade + * to ws or just serve http. + */ +LWS_VISIBLE LWS_EXTERN struct lws * +lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd); +/** + * lws_adopt_socket_vhost() - adopt foreign socket as if listen socket accepted it + * for vhost + * + * \param vh: lws vhost + * \param accept_fd: fd of already-accepted socket to adopt + * + * Either returns new wsi bound to accept_fd, or closes accept_fd and + * returns NULL, having cleaned up any new wsi pieces. + * + * LWS adopts the socket in http serving mode, it's ready to accept an upgrade + * to ws or just serve http. + */ +LWS_VISIBLE LWS_EXTERN struct lws * +lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd); + +typedef enum { + LWS_ADOPT_RAW_FILE_DESC = 0, /* convenience constant */ + LWS_ADOPT_HTTP = 1, /* flag: absent implies RAW */ + LWS_ADOPT_SOCKET = 2, /* flag: absent implies file descr */ + LWS_ADOPT_ALLOW_SSL = 4, /* flag: if set requires LWS_ADOPT_SOCKET */ + LWS_ADOPT_WS_PARENTIO = 8, /* flag: ws mode parent handles IO + * if given must be only flag + * wsi put directly into ws mode + */ +} lws_adoption_type; + +typedef union { + lws_sockfd_type sockfd; + lws_filefd_type filefd; +} lws_sock_file_fd_type; + +/* +* lws_adopt_descriptor_vhost() - adopt foreign socket or file descriptor +* if socket descriptor, should already have been accepted from listen socket +* +* \param vhost: lws vhost +* \param type: OR-ed combinations of lws_adoption_type flags +* \param fd: union with either .sockfd or .filefd set +* \param vh_prot_name: NULL or vh protocol name to bind raw connection to +* \param parent: NULL or struct lws to attach new_wsi to as a child +* +* Either returns new wsi bound to accept_fd, or closes accept_fd and +* returns NULL, having cleaned up any new wsi pieces. +* +* If LWS_ADOPT_SOCKET is set, LWS adopts the socket in http serving mode, it's +* ready to accept an upgrade to ws or just serve http. +* +* parent may be NULL, if given it should be an existing wsi that will become the +* parent of the new wsi created by this call. +*/ +LWS_VISIBLE LWS_EXTERN struct lws * +lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, + lws_sock_file_fd_type fd, const char *vh_prot_name, + struct lws *parent); + +/** + * lws_adopt_socket_readbuf() - adopt foreign socket and first rx as if listen socket accepted it + * for the default vhost of context. + * \param context: lws context + * \param accept_fd: fd of already-accepted socket to adopt + * \param readbuf: NULL or pointer to data that must be drained before reading from + * accept_fd + * \param len: The length of the data held at \param readbuf + * + * Either returns new wsi bound to accept_fd, or closes accept_fd and + * returns NULL, having cleaned up any new wsi pieces. + * + * LWS adopts the socket in http serving mode, it's ready to accept an upgrade + * to ws or just serve http. + * + * If your external code did not already read from the socket, you can use + * lws_adopt_socket() instead. + * + * This api is guaranteed to use the data at \param readbuf first, before reading from + * the socket. + * + * readbuf is limited to the size of the ah rx buf, currently 2048 bytes. + */ +LWS_VISIBLE LWS_EXTERN struct lws * +lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd, + const char *readbuf, size_t len); +/** + * lws_adopt_socket_vhost_readbuf() - adopt foreign socket and first rx as if listen socket + * accepted it for vhost. + * \param vhost: lws vhost + * \param accept_fd: fd of already-accepted socket to adopt + * \param readbuf: NULL or pointer to data that must be drained before reading from + * accept_fd + * \param len: The length of the data held at \param readbuf + * + * Either returns new wsi bound to accept_fd, or closes accept_fd and + * returns NULL, having cleaned up any new wsi pieces. + * + * LWS adopts the socket in http serving mode, it's ready to accept an upgrade + * to ws or just serve http. + * + * If your external code did not already read from the socket, you can use + * lws_adopt_socket() instead. + * + * This api is guaranteed to use the data at \param readbuf first, before reading from + * the socket. + * + * readbuf is limited to the size of the ah rx buf, currently 2048 bytes. + */ +LWS_VISIBLE LWS_EXTERN struct lws * +lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, lws_sockfd_type accept_fd, + const char *readbuf, size_t len); +///@} + +/** \defgroup net Network related helper APIs + * ##Network related helper APIs + * + * These wrap miscellaneous useful network-related functions + */ +///@{ + +/** + * lws_canonical_hostname() - returns this host's hostname + * + * This is typically used by client code to fill in the host parameter + * when making a client connection. You can only call it after the context + * has been created. + * + * \param context: Websocket context + */ +LWS_VISIBLE LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT +lws_canonical_hostname(struct lws_context *context); + +/** + * lws_get_peer_addresses() - Get client address information + * \param wsi: Local struct lws associated with + * \param fd: Connection socket descriptor + * \param name: Buffer to take client address name + * \param name_len: Length of client address name buffer + * \param rip: Buffer to take client address IP dotted quad + * \param rip_len: Length of client address IP buffer + * + * This function fills in name and rip with the name and IP of + * the client connected with socket descriptor fd. Names may be + * truncated if there is not enough room. If either cannot be + * determined, they will be returned as valid zero-length strings. + */ +LWS_VISIBLE LWS_EXTERN void +lws_get_peer_addresses(struct lws *wsi, lws_sockfd_type fd, char *name, + int name_len, char *rip, int rip_len); + +/** + * lws_get_peer_simple() - Get client address information without RDNS + * + * \param wsi: Local struct lws associated with + * \param name: Buffer to take client address name + * \param namelen: Length of client address name buffer + * + * This provides a 123.123.123.123 type IP address in name from the + * peer that has connected to wsi + */ +LWS_VISIBLE LWS_EXTERN const char * +lws_get_peer_simple(struct lws *wsi, char *name, int namelen); +#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) +/** + * lws_interface_to_sa() - Convert interface name or IP to sockaddr struct + * + * \param ipv6: Allow IPV6 addresses + * \param ifname: Interface name or IP + * \param addr: struct sockaddr_in * to be written + * \param addrlen: Length of addr + * + * This converts a textual network interface name to a sockaddr usable by + * other network functions + */ +LWS_VISIBLE LWS_EXTERN int +lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, + size_t addrlen); +///@} +#endif + +/** \defgroup misc Miscellaneous APIs +* ##Miscellaneous APIs +* +* Various APIs outside of other categories +*/ +///@{ + +/** + * lws_start_foreach_ll(): linkedlist iterator helper start + * + * \param type: type of iteration, eg, struct xyz * + * \param it: iterator var name to create + * \param start: start of list + * + * This helper creates an iterator and starts a while (it) { + * loop. The iterator runs through the linked list starting at start and + * ends when it gets a NULL. + * The while loop should be terminated using lws_start_foreach_ll(). + */ +#define lws_start_foreach_ll(type, it, start)\ +{ \ + type it = start; \ + while (it) { + +/** + * lws_end_foreach_ll(): linkedlist iterator helper end + * + * \param it: same iterator var name given when starting + * \param nxt: member name in the iterator pointing to next list element + * + * This helper is the partner for lws_start_foreach_ll() that ends the + * while loop. + */ + +#define lws_end_foreach_ll(it, nxt) \ + it = it->nxt; \ + } \ +} + +/** + * lws_start_foreach_llp(): linkedlist pointer iterator helper start + * + * \param type: type of iteration, eg, struct xyz ** + * \param it: iterator var name to create + * \param start: start of list + * + * This helper creates an iterator and starts a while (it) { + * loop. The iterator runs through the linked list starting at the + * address of start and ends when it gets a NULL. + * The while loop should be terminated using lws_start_foreach_llp(). + * + * This helper variant iterates using a pointer to the previous linked-list + * element. That allows you to easily delete list members by rewriting the + * previous pointer to the element's next pointer. + */ +#define lws_start_foreach_llp(type, it, start)\ +{ \ + type it = &(start); \ + while (*(it)) { + +/** + * lws_end_foreach_llp(): linkedlist pointer iterator helper end + * + * \param it: same iterator var name given when starting + * \param nxt: member name in the iterator pointing to next list element + * + * This helper is the partner for lws_start_foreach_llp() that ends the + * while loop. + */ + +#define lws_end_foreach_llp(it, nxt) \ + it = &(*(it))->nxt; \ + } \ +} + +/** + * lws_snprintf(): snprintf that truncates the returned length too + * + * \param str: destination buffer + * \param size: bytes left in destination buffer + * \param format: format string + * \param ...: args for format + * + * This lets you correctly truncate buffers by concatenating lengths, if you + * reach the limit the reported length doesn't exceed the limit. + */ +LWS_VISIBLE LWS_EXTERN int +lws_snprintf(char *str, size_t size, const char *format, ...) LWS_FORMAT(3); + +/** + * lws_get_random(): fill a buffer with platform random data + * + * \param context: the lws context + * \param buf: buffer to fill + * \param len: how much to fill + * + * This is intended to be called from the LWS_CALLBACK_RECEIVE callback if + * it's interested to see if the frame it's dealing with was sent in binary + * mode. + */ +LWS_VISIBLE LWS_EXTERN int +lws_get_random(struct lws_context *context, void *buf, int len); +/** + * lws_daemonize(): make current process run in the background + * + * \param _lock_path: the filepath to write the lock file + * + * Spawn lws as a background process, taking care of various things + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_daemonize(const char *_lock_path); +/** + * lws_get_library_version(): return string describing the version of lws + * + * On unix, also includes the git describe + */ +LWS_VISIBLE LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT +lws_get_library_version(void); + +/** + * lws_wsi_user() - get the user data associated with the connection + * \param wsi: lws connection + * + * Not normally needed since it's passed into the callback + */ +LWS_VISIBLE LWS_EXTERN void * +lws_wsi_user(struct lws *wsi); + +/** + * lws_wsi_set_user() - set the user data associated with the client connection + * \param wsi: lws connection + * \param user: user data + * + * By default lws allocates this and it's not legal to externally set it + * yourself. However client connections may have it set externally when the + * connection is created... if so, this api can be used to modify it at + * runtime additionally. + */ +LWS_VISIBLE LWS_EXTERN void +lws_set_wsi_user(struct lws *wsi, void *user); + +/** + * lws_parse_uri: cut up prot:/ads:port/path into pieces + * Notice it does so by dropping '\0' into input string + * and the leading / on the path is consequently lost + * + * \param p: incoming uri string.. will get written to + * \param prot: result pointer for protocol part (https://) + * \param ads: result pointer for address part + * \param port: result pointer for port part + * \param path: result pointer for path part + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_parse_uri(char *p, const char **prot, const char **ads, int *port, + const char **path); + +/** + * lws_now_secs(): return seconds since 1970-1-1 + */ +LWS_VISIBLE LWS_EXTERN unsigned long +lws_now_secs(void); + +/** + * lws_get_context - Allow geting lws_context from a Websocket connection + * instance + * + * With this function, users can access context in the callback function. + * Otherwise users may have to declare context as a global variable. + * + * \param wsi: Websocket connection instance + */ +LWS_VISIBLE LWS_EXTERN struct lws_context * LWS_WARN_UNUSED_RESULT +lws_get_context(const struct lws *wsi); + +/** + * lws_get_count_threads(): how many service threads the context uses + * + * \param context: the lws context + * + * By default this is always 1, if you asked for more than lws can handle it + * will clip the number of threads. So you can use this to find out how many + * threads are actually in use. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_get_count_threads(struct lws_context *context); + +/** + * lws_get_parent() - get parent wsi or NULL + * \param wsi: lws connection + * + * Specialized wsi like cgi stdin/out/err are associated to a parent wsi, + * this allows you to get their parent. + */ +LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +lws_get_parent(const struct lws *wsi); + +/** + * lws_get_child() - get child wsi or NULL + * \param wsi: lws connection + * + * Allows you to find a related wsi from the parent wsi. + */ +LWS_VISIBLE LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +lws_get_child(const struct lws *wsi); + +/** + * lws_parent_carries_io() - mark wsi as needing to send messages via parent + * + * \param wsi: child lws connection + */ + +LWS_VISIBLE LWS_EXTERN void +lws_set_parent_carries_io(struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN void * +lws_get_opaque_parent_data(const struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN void +lws_set_opaque_parent_data(struct lws *wsi, void *data); + +LWS_VISIBLE LWS_EXTERN int +lws_get_child_pending_on_writable(const struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN void +lws_clear_child_pending_on_writable(struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN int +lws_get_close_length(struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN unsigned char * +lws_get_close_payload(struct lws *wsi); + +/** + * lws_get_network_wsi() - Returns wsi that has the tcp connection for this wsi + * + * \param wsi: wsi you have + * + * Returns wsi that has the tcp connection (which may be the incoming wsi) + * + * HTTP/1 connections will always return the incoming wsi + * HTTP/2 connections may return a different wsi that has the tcp connection + */ +LWS_VISIBLE LWS_EXTERN +struct lws *lws_get_network_wsi(struct lws *wsi); + +/* + * \deprecated DEPRECATED Note: this is not normally needed as a user api. + * It's provided in case it is + * useful when integrating with other app poll loop service code. + */ +LWS_VISIBLE LWS_EXTERN int +lws_read(struct lws *wsi, unsigned char *buf, lws_filepos_t len); + +/** + * lws_set_allocator() - custom allocator support + * + * \param realloc + * + * Allows you to replace the allocator (and deallocator) used by lws + */ +LWS_VISIBLE LWS_EXTERN void +lws_set_allocator(void *(*realloc)(void *ptr, size_t size, const char *reason)); +///@} + +/** \defgroup wsstatus Websocket status APIs + * ##Websocket connection status APIs + * + * These provide information about ws connection or message status + */ +///@{ +/** + * lws_send_pipe_choked() - tests if socket is writable or not + * \param wsi: lws connection + * + * Allows you to check if you can write more on the socket + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_send_pipe_choked(struct lws *wsi); + +/** + * lws_is_final_fragment() - tests if last part of ws message + * + * \param wsi: lws connection + */ +LWS_VISIBLE LWS_EXTERN int +lws_is_final_fragment(struct lws *wsi); + +/** + * lws_is_first_fragment() - tests if first part of ws message + * + * \param wsi: lws connection + */ +LWS_VISIBLE LWS_EXTERN int +lws_is_first_fragment(struct lws *wsi); + +/** + * lws_get_reserved_bits() - access reserved bits of ws frame + * \param wsi: lws connection + */ +LWS_VISIBLE LWS_EXTERN unsigned char +lws_get_reserved_bits(struct lws *wsi); + +/** + * lws_partial_buffered() - find out if lws buffered the last write + * \param wsi: websocket connection to check + * + * Returns 1 if you cannot use lws_write because the last + * write on this connection is still buffered, and can't be cleared without + * returning to the service loop and waiting for the connection to be + * writeable again. + * + * If you will try to do >1 lws_write call inside a single + * WRITEABLE callback, you must check this after every write and bail if + * set, ask for a new writeable callback and continue writing from there. + * + * This is never set at the start of a writeable callback, but any write + * may set it. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_partial_buffered(struct lws *wsi); + +/** + * lws_frame_is_binary(): true if the current frame was sent in binary mode + * + * \param wsi: the connection we are inquiring about + * + * This is intended to be called from the LWS_CALLBACK_RECEIVE callback if + * it's interested to see if the frame it's dealing with was sent in binary + * mode. + */ +LWS_VISIBLE LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_frame_is_binary(struct lws *wsi); + +/** + * lws_is_ssl() - Find out if connection is using SSL + * \param wsi: websocket connection to check + * + * Returns 0 if the connection is not using SSL, 1 if using SSL and + * using verified cert, and 2 if using SSL but the cert was not + * checked (appears for client wsi told to skip check on connection) + */ +LWS_VISIBLE LWS_EXTERN int +lws_is_ssl(struct lws *wsi); +/** + * lws_is_cgi() - find out if this wsi is running a cgi process + * \param wsi: lws connection + */ +LWS_VISIBLE LWS_EXTERN int +lws_is_cgi(struct lws *wsi); + +#ifdef LWS_OPENSSL_SUPPORT +/** + * lws_get_ssl() - Return wsi's SSL context structure + * \param wsi: websocket connection + * + * Returns pointer to the SSL library's context structure + */ +LWS_VISIBLE LWS_EXTERN SSL* +lws_get_ssl(struct lws *wsi); +#endif +///@} + +/** \defgroup lws_ring LWS Ringbuffer APIs + * ##lws_ring: generic ringbuffer struct + * + * Provides an abstract ringbuffer api supporting one head and one or an + * unlimited number of tails. + * + * All of the members are opaque and manipulated by lws_ring_...() apis. + * + * The lws_ring and its buffer is allocated at runtime on the heap, using + * + * - lws_ring_create() + * - lws_ring_destroy() + * + * It may contain any type, the size of the "element" stored in the ring + * buffer and the number of elements is given at creation time. + * + * When you create the ringbuffer, you can optionally provide an element + * destroy callback that frees any allocations inside the element. This is then + * automatically called for elements with no tail behind them, ie, elements + * which don't have any pending consumer are auto-freed. + * + * Whole elements may be inserted into the ringbuffer and removed from it, using + * + * - lws_ring_insert() + * - lws_ring_consume() + * + * You can find out how many whole elements are free or waiting using + * + * - lws_ring_get_count_free_elements() + * - lws_ring_get_count_waiting_elements() + * + * In addition there are special purpose optional byte-centric apis + * + * - lws_ring_next_linear_insert_range() + * - lws_ring_bump_head() + * + * which let you, eg, read() directly into the ringbuffer without needing + * an intermediate bounce buffer. + * + * The accessors understand that the ring wraps, and optimizes insertion and + * consumption into one or two memcpy()s depending on if the head or tail + * wraps. + * + * lws_ring only supports a single head, but optionally multiple tails with + * an API to inform it when the "oldest" tail has moved on. You can give + * NULL where-ever an api asks for a tail pointer, and it will use an internal + * single tail pointer for convenience. + * + * The "oldest tail", which is the only tail if you give it NULL instead of + * some other tail, is used to track which elements in the ringbuffer are + * still unread by anyone. + * + * - lws_ring_update_oldest_tail() + */ +///@{ +struct lws_ring; + +/** + * lws_ring_create(): create a new ringbuffer + * + * \param element_len: the size in bytes of one element in the ringbuffer + * \param count: the number of elements the ringbuffer can contain + * \param destroy_element: NULL, or callback to be called for each element + * that is removed from the ringbuffer due to the + * oldest tail moving beyond it + * + * Creates the ringbuffer and allocates the storage. Returns the new + * lws_ring *, or NULL if the allocation failed. + * + * If non-NULL, destroy_element will get called back for every element that is + * retired from the ringbuffer after the oldest tail has gone past it, and for + * any element still left in the ringbuffer when it is destroyed. It replaces + * all other element destruction code in your user code. + */ +LWS_VISIBLE LWS_EXTERN struct lws_ring * +lws_ring_create(size_t element_len, size_t count, + void (*destroy_element)(void *element)); + +/** + * lws_ring_destroy(): destroy a previously created ringbuffer + * + * \param ring: the struct lws_ring to destroy + * + * Destroys the ringbuffer allocation and the struct lws_ring itself. + */ +LWS_VISIBLE LWS_EXTERN void +lws_ring_destroy(struct lws_ring *ring); + +/** + * lws_ring_get_count_free_elements(): return how many elements can fit + * in the free space + * + * \param ring: the struct lws_ring to report on + * + * Returns how much room is left in the ringbuffer for whole element insertion. + */ +LWS_VISIBLE LWS_EXTERN size_t +lws_ring_get_count_free_elements(struct lws_ring *ring); + +/** + * lws_ring_get_count_waiting_elements(): return how many elements can be consumed + * + * \param ring: the struct lws_ring to report on + * \param tail: a pointer to the tail struct to use, or NULL for single tail + * + * Returns how many elements are waiting to be consumed from the perspective + * of the tail pointer given. + */ +LWS_VISIBLE LWS_EXTERN size_t +lws_ring_get_count_waiting_elements(struct lws_ring *ring, uint32_t *tail); + +/** + * lws_ring_insert(): attempt to insert up to max_count elements from src + * + * \param ring: the struct lws_ring to report on + * \param src: the array of elements to be inserted + * \param max_count: the number of available elements at src + * + * Attempts to insert as many of the elements at src as possible, up to the + * maximum max_count. Returns the number of elements actually inserted. + */ +LWS_VISIBLE LWS_EXTERN size_t +lws_ring_insert(struct lws_ring *ring, const void *src, size_t max_count); + +/** + * lws_ring_consume(): attempt to copy out and remove up to max_count elements + * to src + * + * \param ring: the struct lws_ring to report on + * \param tail: a pointer to the tail struct to use, or NULL for single tail + * \param dest: the array of elements to be inserted. or NULL for no copy + * \param max_count: the number of available elements at src + * + * Attempts to copy out as many waiting elements as possible into dest, from + * the perspective of the given tail, up to max_count. If dest is NULL, the + * copying out is not done but the elements are logically consumed as usual. + * NULL dest is useful in combination with lws_ring_get_element(), where you + * can use the element direct from the ringbuffer and then call this with NULL + * dest to logically consume it. + * + * Increments the tail position according to how many elements could be + * consumed. + * + * Returns the number of elements consumed. + */ +LWS_VISIBLE LWS_EXTERN size_t +lws_ring_consume(struct lws_ring *ring, uint32_t *tail, void *dest, + size_t max_count); + +/** + * lws_ring_get_element(): get a pointer to the next waiting element for tail + * + * \param ring: the struct lws_ring to report on + * \param tail: a pointer to the tail struct to use, or NULL for single tail + * + * Points to the next element that tail would consume, directly in the + * ringbuffer. This lets you write() or otherwise use the element without + * having to copy it out somewhere first. + * + * After calling this, you must call lws_ring_consume(ring, &tail, NULL, 1) + * which will logically consume the element you used up and increment your + * tail (tail may also be NULL there if you use a single tail). + * + * Returns NULL if no waiting element, or a const void * pointing to it. + */ +LWS_VISIBLE LWS_EXTERN const void * +lws_ring_get_element(struct lws_ring *ring, uint32_t *tail); + +/** + * lws_ring_update_oldest_tail(): free up elements older than tail for reuse + * + * \param ring: the struct lws_ring to report on + * \param tail: a pointer to the tail struct to use, or NULL for single tail + * + * If you are using multiple tails, you must use this API to inform the + * lws_ring when none of the tails still need elements in the fifo any more, + * by updating it when the "oldest" tail has moved on. + */ +LWS_VISIBLE LWS_EXTERN void +lws_ring_update_oldest_tail(struct lws_ring *ring, uint32_t tail); + +/** + * lws_ring_get_oldest_tail(): get current oldest available data index + * + * \param ring: the struct lws_ring to report on + * + * If you are initializing a new ringbuffer consumer, you can set its tail to + * this to start it from the oldest ringbuffer entry still available. + */ +LWS_VISIBLE LWS_EXTERN uint32_t +lws_ring_get_oldest_tail(struct lws_ring *ring); + +/** + * lws_ring_next_linear_insert_range(): used to write directly into the ring + * + * \param ring: the struct lws_ring to report on + * \param start: pointer to a void * set to the start of the next ringbuffer area + * \param bytes: pointer to a size_t set to the max length you may use from *start + * + * This provides a low-level, bytewise access directly into the ringbuffer + * allowing direct insertion of data without having to use a bounce buffer. + * + * The api reports the position and length of the next linear range that can + * be written in the ringbuffer, ie, up to the point it would wrap, and sets + * *start and *bytes accordingly. You can then, eg, directly read() into + * *start for up to *bytes, and use lws_ring_bump_head() to update the lws_ring + * with what you have done. + * + * Returns nonzero if no insertion is currently possible. + */ +LWS_VISIBLE LWS_EXTERN int +lws_ring_next_linear_insert_range(struct lws_ring *ring, void **start, + size_t *bytes); + +/** + * lws_ring_bump_head(): used to write directly into the ring + * + * \param ring: the struct lws_ring to operate on + * \param bytes: the number of bytes you inserted at the current head + */ +LWS_VISIBLE LWS_EXTERN void +lws_ring_bump_head(struct lws_ring *ring, size_t bytes); +///@} + +/** \defgroup sha SHA and B64 helpers + * ##SHA and B64 helpers + * + * These provide SHA-1 and B64 helper apis + */ +///@{ +#ifdef LWS_SHA1_USE_OPENSSL_NAME +#define lws_SHA1 SHA1 +#else +/** + * lws_SHA1(): make a SHA-1 digest of a buffer + * + * \param d: incoming buffer + * \param n: length of incoming buffer + * \param md: buffer for message digest (must be >= 20 bytes) + * + * Reduces any size buffer into a 20-byte SHA-1 hash. + */ +LWS_VISIBLE LWS_EXTERN unsigned char * +lws_SHA1(const unsigned char *d, size_t n, unsigned char *md); +#endif +/** + * lws_b64_encode_string(): encode a string into base 64 + * + * \param in: incoming buffer + * \param in_len: length of incoming buffer + * \param out: result buffer + * \param out_size: length of result buffer + * + * Encodes a string using b64 + */ +LWS_VISIBLE LWS_EXTERN int +lws_b64_encode_string(const char *in, int in_len, char *out, int out_size); +/** + * lws_b64_decode_string(): decode a string from base 64 + * + * \param in: incoming buffer + * \param out: result buffer + * \param out_size: length of result buffer + * + * Decodes a string using b64 + */ +LWS_VISIBLE LWS_EXTERN int +lws_b64_decode_string(const char *in, char *out, int out_size); +///@} + + +/*! \defgroup cgi cgi handling + * + * ##CGI handling + * + * These functions allow low-level control over stdin/out/err of the cgi. + * + * However for most cases, binding the cgi to http in and out, the default + * lws implementation already does the right thing. + */ + +enum lws_enum_stdinouterr { + LWS_STDIN = 0, + LWS_STDOUT = 1, + LWS_STDERR = 2, +}; + +enum lws_cgi_hdr_state { + LCHS_HEADER, + LCHS_CR1, + LCHS_LF1, + LCHS_CR2, + LCHS_LF2, + LHCS_RESPONSE, + LHCS_DUMP_HEADERS, + LHCS_PAYLOAD, + LCHS_SINGLE_0A, +}; + +struct lws_cgi_args { + struct lws **stdwsi; /**< get fd with lws_get_socket_fd() */ + enum lws_enum_stdinouterr ch; /**< channel index */ + unsigned char *data; /**< for messages with payload */ + enum lws_cgi_hdr_state hdr_state; /**< track where we are in cgi headers */ + int len; /**< length */ +}; + +#ifdef LWS_WITH_CGI +/** + * lws_cgi: spawn network-connected cgi process + * + * \param wsi: connection to own the process + * \param exec_array: array of "exec-name" "arg1" ... "argn" NULL + * \param script_uri_path_len: how many chars on the left of the uri are the + * path to the cgi, or -1 to spawn without URL-related env vars + * \param timeout_secs: seconds script should be allowed to run + * \param mp_cgienv: pvo list with per-vhost cgi options to put in env + */ +LWS_VISIBLE LWS_EXTERN int +lws_cgi(struct lws *wsi, const char * const *exec_array, + int script_uri_path_len, int timeout_secs, + const struct lws_protocol_vhost_options *mp_cgienv); + +/** + * lws_cgi_write_split_stdout_headers: write cgi output accounting for header part + * + * \param wsi: connection to own the process + */ +LWS_VISIBLE LWS_EXTERN int +lws_cgi_write_split_stdout_headers(struct lws *wsi); + +/** + * lws_cgi_kill: terminate cgi process associated with wsi + * + * \param wsi: connection to own the process + */ +LWS_VISIBLE LWS_EXTERN int +lws_cgi_kill(struct lws *wsi); + +/** + * lws_cgi_get_stdwsi: get wsi for stdin, stdout, or stderr + * + * \param wsi: parent wsi that has cgi + * \param ch: which of LWS_STDIN, LWS_STDOUT or LWS_STDERR + */ +LWS_VISIBLE LWS_EXTERN struct lws * +lws_cgi_get_stdwsi(struct lws *wsi, enum lws_enum_stdinouterr ch); + +#endif +///@} + + +/*! \defgroup fops file operation wrapping + * + * ##File operation wrapping + * + * Use these helper functions if you want to access a file from the perspective + * of a specific wsi, which is usually the case. If you just want contextless + * file access, use the fops callbacks directly with NULL wsi instead of these + * helpers. + * + * If so, then it calls the platform handler or user overrides where present + * (as defined in info->fops) + * + * The advantage from all this is user code can be portable for file operations + * without having to deal with differences between platforms. + */ +//@{ + +/** struct lws_plat_file_ops - Platform-specific file operations + * + * These provide platform-agnostic ways to deal with filesystem access in the + * library and in the user code. + */ + +#if defined(LWS_WITH_ESP32) +/* sdk preprocessor defs? compiler issue? gets confused with member names */ +#define LWS_FOP_OPEN _open +#define LWS_FOP_CLOSE _close +#define LWS_FOP_SEEK_CUR _seek_cur +#define LWS_FOP_READ _read +#define LWS_FOP_WRITE _write +#else +#define LWS_FOP_OPEN open +#define LWS_FOP_CLOSE close +#define LWS_FOP_SEEK_CUR seek_cur +#define LWS_FOP_READ read +#define LWS_FOP_WRITE write +#endif + +#define LWS_FOP_FLAGS_MASK ((1 << 23) - 1) +#define LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP (1 << 24) +#define LWS_FOP_FLAG_COMPR_IS_GZIP (1 << 25) +#define LWS_FOP_FLAG_MOD_TIME_VALID (1 << 26) +#define LWS_FOP_FLAG_VIRTUAL (1 << 27) + +struct lws_plat_file_ops; + +struct lws_fop_fd { + lws_filefd_type fd; + /**< real file descriptor related to the file... */ + const struct lws_plat_file_ops *fops; + /**< fops that apply to this fop_fd */ + void *filesystem_priv; + /**< ignored by lws; owned by the fops handlers */ + lws_filepos_t pos; + /**< generic "position in file" */ + lws_filepos_t len; + /**< generic "length of file" */ + lws_fop_flags_t flags; + /**< copy of the returned flags */ + uint32_t mod_time; + /**< optional "modification time of file", only valid if .open() + * set the LWS_FOP_FLAG_MOD_TIME_VALID flag */ +}; +typedef struct lws_fop_fd *lws_fop_fd_t; + +struct lws_fops_index { + const char *sig; /* NULL or vfs signature, eg, ".zip/" */ + uint8_t len; /* length of above string */ +}; + +struct lws_plat_file_ops { + lws_fop_fd_t (*LWS_FOP_OPEN)(const struct lws_plat_file_ops *fops, + const char *filename, const char *vpath, + lws_fop_flags_t *flags); + /**< Open file (always binary access if plat supports it) + * vpath may be NULL, or if the fops understands it, the point at which + * the filename's virtual part starts. + * *flags & LWS_FOP_FLAGS_MASK should be set to O_RDONLY or O_RDWR. + * If the file may be gzip-compressed, + * LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP is set. If it actually is + * gzip-compressed, then the open handler should OR + * LWS_FOP_FLAG_COMPR_IS_GZIP on to *flags before returning. + */ + int (*LWS_FOP_CLOSE)(lws_fop_fd_t *fop_fd); + /**< close file AND set the pointer to NULL */ + lws_fileofs_t (*LWS_FOP_SEEK_CUR)(lws_fop_fd_t fop_fd, + lws_fileofs_t offset_from_cur_pos); + /**< seek from current position */ + int (*LWS_FOP_READ)(lws_fop_fd_t fop_fd, lws_filepos_t *amount, + uint8_t *buf, lws_filepos_t len); + /**< Read from file, on exit *amount is set to amount actually read */ + int (*LWS_FOP_WRITE)(lws_fop_fd_t fop_fd, lws_filepos_t *amount, + uint8_t *buf, lws_filepos_t len); + /**< Write to file, on exit *amount is set to amount actually written */ + + struct lws_fops_index fi[3]; + /**< vfs path signatures implying use of this fops */ + + const struct lws_plat_file_ops *next; + /**< NULL or next fops in list */ + + /* Add new things just above here ---^ + * This is part of the ABI, don't needlessly break compatibility */ +}; + +/** + * lws_get_fops() - get current file ops + * + * \param context: context + */ +LWS_VISIBLE LWS_EXTERN struct lws_plat_file_ops * LWS_WARN_UNUSED_RESULT +lws_get_fops(struct lws_context *context); +LWS_VISIBLE LWS_EXTERN void +lws_set_fops(struct lws_context *context, const struct lws_plat_file_ops *fops); +/** + * lws_vfs_tell() - get current file position + * + * \param fop_fd: fop_fd we are asking about + */ +LWS_VISIBLE LWS_EXTERN lws_filepos_t LWS_WARN_UNUSED_RESULT +lws_vfs_tell(lws_fop_fd_t fop_fd); +/** + * lws_vfs_get_length() - get current file total length in bytes + * + * \param fop_fd: fop_fd we are asking about + */ +LWS_VISIBLE LWS_EXTERN lws_filepos_t LWS_WARN_UNUSED_RESULT +lws_vfs_get_length(lws_fop_fd_t fop_fd); +/** + * lws_vfs_get_mod_time() - get time file last modified + * + * \param fop_fd: fop_fd we are asking about + */ +LWS_VISIBLE LWS_EXTERN uint32_t LWS_WARN_UNUSED_RESULT +lws_vfs_get_mod_time(lws_fop_fd_t fop_fd); +/** + * lws_vfs_file_seek_set() - seek relative to start of file + * + * \param fop_fd: fop_fd we are seeking in + * \param offset: offset from start of file + */ +LWS_VISIBLE LWS_EXTERN lws_fileofs_t +lws_vfs_file_seek_set(lws_fop_fd_t fop_fd, lws_fileofs_t offset); +/** + * lws_vfs_file_seek_end() - seek relative to end of file + * + * \param fop_fd: fop_fd we are seeking in + * \param offset: offset from start of file + */ +LWS_VISIBLE LWS_EXTERN lws_fileofs_t +lws_vfs_file_seek_end(lws_fop_fd_t fop_fd, lws_fileofs_t offset); + +extern struct lws_plat_file_ops fops_zip; + +/** + * lws_plat_file_open() - open vfs filepath + * + * \param fops: file ops struct that applies to this descriptor + * \param vfs_path: filename to open + * \param flags: pointer to open flags + * + * The vfs_path is scanned for known fops signatures, and the open directed + * to any matching fops open. + * + * User code should use this api to perform vfs opens. + * + * returns semi-opaque handle + */ +LWS_VISIBLE LWS_EXTERN lws_fop_fd_t LWS_WARN_UNUSED_RESULT +lws_vfs_file_open(const struct lws_plat_file_ops *fops, const char *vfs_path, + lws_fop_flags_t *flags); + +/** + * lws_plat_file_close() - close file + * + * \param fop_fd: file handle to close + */ +static LWS_INLINE int +lws_vfs_file_close(lws_fop_fd_t *fop_fd) +{ + return (*fop_fd)->fops->LWS_FOP_CLOSE(fop_fd); +} + +/** + * lws_plat_file_seek_cur() - close file + * + * + * \param fop_fd: file handle + * \param offset: position to seek to + */ +static LWS_INLINE lws_fileofs_t +lws_vfs_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset) +{ + return fop_fd->fops->LWS_FOP_SEEK_CUR(fop_fd, offset); +} +/** + * lws_plat_file_read() - read from file + * + * \param fop_fd: file handle + * \param amount: how much to read (rewritten by call) + * \param buf: buffer to write to + * \param len: max length + */ +static LWS_INLINE int LWS_WARN_UNUSED_RESULT +lws_vfs_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount, + uint8_t *buf, lws_filepos_t len) +{ + return fop_fd->fops->LWS_FOP_READ(fop_fd, amount, buf, len); +} +/** + * lws_plat_file_write() - write from file + * + * \param fop_fd: file handle + * \param amount: how much to write (rewritten by call) + * \param buf: buffer to read from + * \param len: max length + */ +static LWS_INLINE int LWS_WARN_UNUSED_RESULT +lws_vfs_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount, + uint8_t *buf, lws_filepos_t len) +{ + return fop_fd->fops->LWS_FOP_WRITE(fop_fd, amount, buf, len); +} + +/* these are the platform file operations implementations... they can + * be called directly and used in fops arrays + */ + +LWS_VISIBLE LWS_EXTERN lws_fop_fd_t +_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename, + const char *vpath, lws_fop_flags_t *flags); +LWS_VISIBLE LWS_EXTERN int +_lws_plat_file_close(lws_fop_fd_t *fop_fd); +LWS_VISIBLE LWS_EXTERN lws_fileofs_t +_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset); +LWS_VISIBLE LWS_EXTERN int +_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount, + uint8_t *buf, lws_filepos_t len); +LWS_VISIBLE LWS_EXTERN int +_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount, + uint8_t *buf, lws_filepos_t len); + +LWS_VISIBLE LWS_EXTERN int +lws_alloc_vfs_file(struct lws_context *context, const char *filename, + uint8_t **buf, lws_filepos_t *amount); +//@} + +/** \defgroup smtp SMTP related functions + * ##SMTP related functions + * \ingroup lwsapi + * + * These apis let you communicate with a local SMTP server to send email from + * lws. It handles all the SMTP sequencing and protocol actions. + * + * Your system should have postfix, sendmail or another MTA listening on port + * 25 and able to send email using the "mail" commandline app. Usually distro + * MTAs are configured for this by default. + * + * It runs via its own libuv events if initialized (which requires giving it + * a libuv loop to attach to). + * + * It operates using three callbacks, on_next() queries if there is a new email + * to send, on_get_body() asks for the body of the email, and on_sent() is + * called after the email is successfully sent. + * + * To use it + * + * - create an lws_email struct + * + * - initialize data, loop, the email_* strings, max_content_size and + * the callbacks + * + * - call lws_email_init() + * + * When you have at least one email to send, call lws_email_check() to + * schedule starting to send it. + */ +//@{ +#ifdef LWS_WITH_SMTP + +/** enum lwsgs_smtp_states - where we are in SMTP protocol sequence */ +enum lwsgs_smtp_states { + LGSSMTP_IDLE, /**< awaiting new email */ + LGSSMTP_CONNECTING, /**< opening tcp connection to MTA */ + LGSSMTP_CONNECTED, /**< tcp connection to MTA is connected */ + LGSSMTP_SENT_HELO, /**< sent the HELO */ + LGSSMTP_SENT_FROM, /**< sent FROM */ + LGSSMTP_SENT_TO, /**< sent TO */ + LGSSMTP_SENT_DATA, /**< sent DATA request */ + LGSSMTP_SENT_BODY, /**< sent the email body */ + LGSSMTP_SENT_QUIT, /**< sent the session quit */ +}; + +/** struct lws_email - abstract context for performing SMTP operations */ +struct lws_email { + void *data; + /**< opaque pointer set by user code and available to the callbacks */ + uv_loop_t *loop; + /**< the libuv loop we will work on */ + + char email_smtp_ip[32]; /**< Fill before init, eg, "127.0.0.1" */ + char email_helo[32]; /**< Fill before init, eg, "myserver.com" */ + char email_from[100]; /**< Fill before init or on_next */ + char email_to[100]; /**< Fill before init or on_next */ + + unsigned int max_content_size; + /**< largest possible email body size */ + + /* Fill all the callbacks before init */ + + int (*on_next)(struct lws_email *email); + /**< (Fill in before calling lws_email_init) + * called when idle, 0 = another email to send, nonzero is idle. + * If you return 0, all of the email_* char arrays must be set + * to something useful. */ + int (*on_sent)(struct lws_email *email); + /**< (Fill in before calling lws_email_init) + * called when transfer of the email to the SMTP server was + * successful, your callback would remove the current email + * from its queue */ + int (*on_get_body)(struct lws_email *email, char *buf, int len); + /**< (Fill in before calling lws_email_init) + * called when the body part of the queued email is about to be + * sent to the SMTP server. */ + + + /* private things */ + uv_timer_t timeout_email; /**< private */ + enum lwsgs_smtp_states estate; /**< private */ + uv_connect_t email_connect_req; /**< private */ + uv_tcp_t email_client; /**< private */ + time_t email_connect_started; /**< private */ + char email_buf[256]; /**< private */ + char *content; /**< private */ +}; + +/** + * lws_email_init() - Initialize a struct lws_email + * + * \param email: struct lws_email to init + * \param loop: libuv loop to use + * \param max_content: max email content size + * + * Prepares a struct lws_email for use ending SMTP + */ +LWS_VISIBLE LWS_EXTERN int +lws_email_init(struct lws_email *email, uv_loop_t *loop, int max_content); + +/** + * lws_email_check() - Request check for new email + * + * \param email: struct lws_email context to check + * + * Schedules a check for new emails in 1s... call this when you have queued an + * email for send. + */ +LWS_VISIBLE LWS_EXTERN void +lws_email_check(struct lws_email *email); +/** + * lws_email_destroy() - stop using the struct lws_email + * + * \param email: the struct lws_email context + * + * Stop sending email using email and free allocations + */ +LWS_VISIBLE LWS_EXTERN void +lws_email_destroy(struct lws_email *email); + +#endif +//@} + +/* + * Stats are all uint64_t numbers that start at 0. + * Index names here have the convention + * + * _C_ counter + * _B_ byte count + * _MS_ millisecond count + */ + +enum { + LWSSTATS_C_CONNECTIONS, /**< count incoming connections */ + LWSSTATS_C_API_CLOSE, /**< count calls to close api */ + LWSSTATS_C_API_READ, /**< count calls to read from socket api */ + LWSSTATS_C_API_LWS_WRITE, /**< count calls to lws_write API */ + LWSSTATS_C_API_WRITE, /**< count calls to write API */ + LWSSTATS_C_WRITE_PARTIALS, /**< count of partial writes */ + LWSSTATS_C_WRITEABLE_CB_REQ, /**< count of writable callback requests */ + LWSSTATS_C_WRITEABLE_CB_EFF_REQ, /**< count of effective writable callback requests */ + LWSSTATS_C_WRITEABLE_CB, /**< count of writable callbacks */ + LWSSTATS_C_SSL_CONNECTIONS_FAILED, /**< count of failed SSL connections */ + LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, /**< count of accepted SSL connections */ + LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN, /**< count of SSL_accept() attempts */ + LWSSTATS_C_SSL_CONNS_HAD_RX, /**< count of accepted SSL conns that have had some RX */ + LWSSTATS_C_TIMEOUTS, /**< count of timed-out connections */ + LWSSTATS_C_SERVICE_ENTRY, /**< count of entries to lws service loop */ + LWSSTATS_B_READ, /**< aggregate bytes read */ + LWSSTATS_B_WRITE, /**< aggregate bytes written */ + LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, /**< aggreate of size of accepted write data from new partials */ + LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, /**< aggregate delay in accepting connection */ + LWSSTATS_MS_WRITABLE_DELAY, /**< aggregate delay between asking for writable and getting cb */ + LWSSTATS_MS_WORST_WRITABLE_DELAY, /**< single worst delay between asking for writable and getting cb */ + LWSSTATS_MS_SSL_RX_DELAY, /**< aggregate delay between ssl accept complete and first RX */ + LWSSTATS_C_PEER_LIMIT_AH_DENIED, /**< number of times we would have given an ah but for the peer limit */ + LWSSTATS_C_PEER_LIMIT_WSI_DENIED, /**< number of times we would have given a wsi but for the peer limit */ + + /* Add new things just above here ---^ + * This is part of the ABI, don't needlessly break compatibility */ + LWSSTATS_SIZE +}; + +#if defined(LWS_WITH_STATS) + +LWS_VISIBLE LWS_EXTERN uint64_t +lws_stats_get(struct lws_context *context, int index); +LWS_VISIBLE LWS_EXTERN void +lws_stats_log_dump(struct lws_context *context); +#else +static LWS_INLINE uint64_t +lws_stats_get(struct lws_context *context, int index) { return 0; } +static LWS_INLINE void +lws_stats_log_dump(struct lws_context *context) { } +#endif + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/thirdparty/lws/lws_config.h b/thirdparty/lws/lws_config.h new file mode 100644 index 0000000000..6d2a3c93ac --- /dev/null +++ b/thirdparty/lws/lws_config.h @@ -0,0 +1,166 @@ +/* lws_config.h Generated from lws_config.h.in */ +#include "lws_config_private.h" + +#ifndef NDEBUG + #ifndef _DEBUG + #define _DEBUG + #endif +#endif + +#define LWS_INSTALL_DATADIR "/usr/local/share" + +/* Define to 1 to use wolfSSL/CyaSSL as a replacement for OpenSSL. + * LWS_OPENSSL_SUPPORT needs to be set also for this to work. */ +/* #undef USE_WOLFSSL */ + +/* Also define to 1 (in addition to USE_WOLFSSL) when using the + (older) CyaSSL library */ +/* #undef USE_OLD_CYASSL */ +/* #undef LWS_WITH_BORINGSSL */ + +/* #undef LWS_WITH_MBEDTLS */ +/* #undef LWS_WITH_POLARSSL */ +/* #undef LWS_WITH_ESP8266 */ +/* #undef LWS_WITH_ESP32 */ + +/* #undef LWS_WITH_PLUGINS */ +/* #undef LWS_WITH_NO_LOGS */ +#ifndef DEBUG_ENABLED +#define LWS_WITH_NO_LOGS +#endif + +/* The Libwebsocket version */ +#define LWS_LIBRARY_VERSION "2.4.1" + +#define LWS_LIBRARY_VERSION_MAJOR 2 +#define LWS_LIBRARY_VERSION_MINOR 4 +#define LWS_LIBRARY_VERSION_PATCH 1 +/* LWS_LIBRARY_VERSION_NUMBER looks like 1005001 for e.g. version 1.5.1 */ +#define LWS_LIBRARY_VERSION_NUMBER (LWS_LIBRARY_VERSION_MAJOR*1000000)+(LWS_LIBRARY_VERSION_MINOR*1000)+LWS_LIBRARY_VERSION_PATCH + +/* The current git commit hash that we're building from */ +#define LWS_BUILD_HASH "55f97b7806e07db2d4c8a158172cd309d0faf450" + +/* Build with OpenSSL support */ +#define LWS_OPENSSL_SUPPORT + +/* The client should load and trust CA root certs it finds in the OS */ +#define LWS_SSL_CLIENT_USE_OS_CA_CERTS + +/* Sets the path where the client certs should be installed. */ +#define LWS_OPENSSL_CLIENT_CERTS "../share" + +/* Turn off websocket extensions */ +/* #undef LWS_NO_EXTENSIONS */ + +/* Enable libev io loop */ +/* #undef LWS_WITH_LIBEV */ +#undef LWS_WITH_LIBEV + +/* Enable libuv io loop */ +/* #undef LWS_WITH_LIBUV */ +#undef LWS_WITH_LIBUV + +/* Enable libevent io loop */ +/* #undef LWS_WITH_LIBEVENT */ +#undef LWS_WITH_LIBEVENT + +/* Build with support for ipv6 */ +/* #undef LWS_WITH_IPV6 */ + +/* Build with support for UNIX domain socket */ +/* #undef LWS_WITH_UNIX_SOCK */ +#ifdef WINDOWS_ENABLED +#undef LWS_USE_UNIX_SOCK +#endif + +/* Build with support for HTTP2 */ +/* #undef LWS_WITH_HTTP2 */ + +/* Turn on latency measuring code */ +/* #undef LWS_LATENCY */ + +/* Don't build the daemonizeation api */ +#define LWS_NO_DAEMONIZE + +/* Build without server support */ +/* #undef LWS_NO_SERVER */ + +/* Build without client support */ +/* #undef LWS_NO_CLIENT */ + +/* If we should compile with MinGW support */ +/* #undef LWS_MINGW_SUPPORT */ + +/* Use the BSD getifaddrs that comes with libwebsocket, for uclibc support */ +/* #undef LWS_BUILTIN_GETIFADDRS */ + +/* use SHA1() not internal libwebsockets_SHA1 */ +/* #undef LWS_SHA1_USE_OPENSSL_NAME */ + +/* SSL server using ECDH certificate */ +/* #undef LWS_SSL_SERVER_WITH_ECDH_CERT */ +#define LWS_HAVE_SSL_CTX_set1_param +#define LWS_HAVE_X509_VERIFY_PARAM_set1_host +/* #undef LWS_HAVE_RSA_SET0_KEY */ + +/* #undef LWS_HAVE_UV_VERSION_H */ + +/* CGI apis */ +/* #undef LWS_WITH_CGI */ + +/* whether the Openssl is recent enough, and / or built with, ecdh */ +#define LWS_HAVE_OPENSSL_ECDH_H + +/* HTTP Proxy support */ +/* #undef LWS_WITH_HTTP_PROXY */ + +/* HTTP Ranges support */ +#define LWS_WITH_RANGES + +/* Http access log support */ +/* #undef LWS_WITH_ACCESS_LOG */ +/* #undef LWS_WITH_SERVER_STATUS */ + +/* #undef LWS_WITH_STATEFUL_URLDECODE */ +/* #undef LWS_WITH_PEER_LIMITS */ + +/* Maximum supported service threads */ +#define LWS_MAX_SMP 1 + +/* Lightweight JSON Parser */ +/* #undef LWS_WITH_LEJP */ + +/* SMTP */ +/* #undef LWS_WITH_SMTP */ + +/* OPTEE */ +/* #undef LWS_PLAT_OPTEE */ + +/* ZIP FOPS */ +#define LWS_WITH_ZIP_FOPS +#define LWS_HAVE_STDINT_H + +/* #undef LWS_AVOID_SIGPIPE_IGN */ + +/* #undef LWS_FALLBACK_GETHOSTBYNAME */ + +/* #undef LWS_WITH_STATS */ +/* #undef LWS_WITH_SOCKS5 */ + +/* #undef LWS_HAVE_SYS_CAPABILITY_H */ +/* #undef LWS_HAVE_LIBCAP */ + +#define LWS_HAVE_ATOLL +/* #undef LWS_HAVE__ATOI64 */ +/* #undef LWS_HAVE__STAT32I64 */ + +/* OpenSSL various APIs */ + +/* #undef LWS_HAVE_TLS_CLIENT_METHOD */ +#define LWS_HAVE_TLSV1_2_CLIENT_METHOD +#define LWS_HAVE_SSL_SET_INFO_CALLBACK + +#define LWS_HAS_INTPTR_T + + diff --git a/thirdparty/lws/lws_config_private.h b/thirdparty/lws/lws_config_private.h new file mode 100644 index 0000000000..235f2ba26d --- /dev/null +++ b/thirdparty/lws/lws_config_private.h @@ -0,0 +1,143 @@ +/* lws_config_private.h.in. Private compilation options. */ +#ifndef DEBUG_ENABLED +#define NDEBUG +#endif + +#ifndef NDEBUG + #ifndef _DEBUG + #define _DEBUG + #endif +#endif + +/* Define to 1 to use CyaSSL as a replacement for OpenSSL. + * LWS_OPENSSL_SUPPORT needs to be set also for this to work. */ +/* #undef USE_CYASSL */ + +/* Define to 1 if you have the `bzero' function. */ +#define LWS_HAVE_BZERO +/* Windows has no bzero function */ +#ifdef WINDOWS_ENABLED +#undef LWS_HAVE_BZERO +#endif + +/* Define to 1 if you have the <dlfcn.h> header file. */ +#define LWS_HAVE_DLFCN_H + +/* Define to 1 if you have the <fcntl.h> header file. */ +#define LWS_HAVE_FCNTL_H +#ifdef NO_FCNTL +#undef LWS_HAVE_FCNTL_H +#endif + +/* Define to 1 if you have the `fork' function. */ +#define LWS_HAVE_FORK + +/* Define to 1 if you have the `getenv’ function. */ +#define LWS_HAVE_GETENV + +/* Define to 1 if you have the <in6addr.h> header file. */ +/* #undef LWS_HAVE_IN6ADDR_H */ + +/* Define to 1 if you have the <inttypes.h> header file. */ +#define LWS_HAVE_INTTYPES_H + +/* Define to 1 if you have the `ssl' library (-lssl). */ +/* #undef LWS_HAVE_LIBSSL */ + +/* Define to 1 if your system has a GNU libc compatible `malloc' function, and + to 0 otherwise. */ +#define LWS_HAVE_MALLOC + +/* Define to 1 if you have the <memory.h> header file. */ +#define LWS_HAVE_MEMORY_H + +/* Define to 1 if you have the `memset' function. */ +#define LWS_HAVE_MEMSET + +/* Define to 1 if you have the <netinet/in.h> header file. */ +#define LWS_HAVE_NETINET_IN_H + +/* Define to 1 if your system has a GNU libc compatible `realloc' function, + and to 0 otherwise. */ +#define LWS_HAVE_REALLOC + +/* Define to 1 if you have the `socket' function. */ +#define LWS_HAVE_SOCKET + +/* Define to 1 if you have the <stdint.h> header file. */ +#define LWS_HAVE_STDINT_H + +/* Define to 1 if you have the <stdlib.h> header file. */ +#define LWS_HAVE_STDLIB_H + +/* Define to 1 if you have the `strerror' function. */ +#define LWS_HAVE_STRERROR + +/* Define to 1 if you have the <strings.h> header file. */ +#define LWS_HAVE_STRINGS_H + +/* Define to 1 if you have the <string.h> header file. */ +#define LWS_HAVE_STRING_H + +/* Define to 1 if you have the <sys/prctl.h> header file. */ +#define LWS_HAVE_SYS_PRCTL_H +#if defined(OSX_ENABLED) || defined(IPHONE_ENABLED) +#undef LWS_HAVE_SYS_PRCTL_H +#endif + +/* Define to 1 if you have the <sys/socket.h> header file. */ +#define LWS_HAVE_SYS_SOCKET_H + +/* Define to 1 if you have the <sys/sockio.h> header file. */ +/* #undef LWS_HAVE_SYS_SOCKIO_H */ + +/* Define to 1 if you have the <sys/stat.h> header file. */ +#define LWS_HAVE_SYS_STAT_H + +/* Define to 1 if you have the <sys/types.h> header file. */ +#define LWS_HAVE_SYS_TYPES_H + +/* Define to 1 if you have the <unistd.h> header file. */ +#define LWS_HAVE_UNISTD_H + +/* Define to 1 if you have the `vfork' function. */ +#define LWS_HAVE_VFORK + +/* Define to 1 if you have the <vfork.h> header file. */ +/* #undef LWS_HAVE_VFORK_H */ + +/* Define to 1 if `fork' works. */ +#define LWS_HAVE_WORKING_FORK + +/* Define to 1 if `vfork' works. */ +#define LWS_HAVE_WORKING_VFORK + +/* Define to 1 if execvpe() exists */ +#define LWS_HAVE_EXECVPE + +/* Define to 1 if you have the <zlib.h> header file. */ +#define LWS_HAVE_ZLIB_H + +#define LWS_HAVE_GETLOADAVG + +/* Define to the sub-directory in which libtool stores uninstalled libraries. + */ +#undef LT_OBJDIR // We're not using libtool + +/* Define to rpl_malloc if the replacement function should be used. */ +/* #undef malloc */ + +/* Define to rpl_realloc if the replacement function should be used. */ +/* #undef realloc */ + +/* Define to 1 if we have getifaddrs */ +#define LWS_HAVE_GETIFADDRS +#if defined(ANDROID_ENABLED) +#undef LWS_HAVE_GETIFADDRS +#define LWS_BUILTIN_GETIFADDRS +#endif + +/* Define if the inline keyword doesn't exist. */ +/* #undef inline */ + + diff --git a/thirdparty/lws/minilex.c b/thirdparty/lws/minilex.c new file mode 100644 index 0000000000..3cb1e33696 --- /dev/null +++ b/thirdparty/lws/minilex.c @@ -0,0 +1,272 @@ +/* + * minilex.c + * + * High efficiency lexical state parser + * + * Copyright (C)2011-2014 Andy Green <andy@warmcat.com> + * + * Licensed under LGPL2 + * + * Usage: gcc minilex.c -o minilex && ./minilex > lextable.h + * + * Run it twice to test parsing on the generated table on stderr + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "lextable-strings.h" + +/* + * b7 = 0 = 1-byte seq + * 0x08 = fail + * 2-byte seq + * 0x00 - 0x07, then terminal as given in 2nd byte + 3-byte seq + * no match: go fwd 3 byte, match: jump fwd by amt in +1/+2 bytes + * = 1 = 1-byte seq + * no match: die, match go fwd 1 byte + */ + +unsigned char lextable[] = { + #include "lextable.h" +}; + +#define PARALLEL 30 + +struct state { + char c[PARALLEL]; + int state[PARALLEL]; + int count; + int bytepos; + + int real_pos; +}; + +struct state state[1000]; +int next = 1; + +#define FAIL_CHAR 0x08 + +int lextable_decode(int pos, char c) +{ + while (1) { + if (lextable[pos] & (1 << 7)) { /* 1-byte, fail on mismatch */ + if ((lextable[pos] & 0x7f) != c) + return -1; + /* fall thru */ + pos++; + if (lextable[pos] == FAIL_CHAR) + return -1; + return pos; + } else { /* b7 = 0, end or 3-byte */ + if (lextable[pos] < FAIL_CHAR) /* terminal marker */ + return pos; + + if (lextable[pos] == c) /* goto */ + return pos + (lextable[pos + 1]) + + (lextable[pos + 2] << 8); + /* fall thru goto */ + pos += 3; + /* continue */ + } + } +} + +int main(void) +{ + int n = 0; + int m = 0; + int prev; + char c; + int walk; + int saw; + int y; + int j; + int pos = 0; + + while (n < sizeof(set) / sizeof(set[0])) { + + m = 0; + walk = 0; + prev = 0; + + if (set[n][0] == '\0') { + n++; + continue; + } + + while (set[n][m]) { + + saw = 0; + for (y = 0; y < state[walk].count; y++) + if (state[walk].c[y] == set[n][m]) { + /* exists -- go forward */ + walk = state[walk].state[y]; + saw = 1; + break; + } + + if (saw) + goto again; + + /* something we didn't see before */ + + state[walk].c[state[walk].count] = set[n][m]; + + state[walk].state[state[walk].count] = next; + state[walk].count++; + walk = next++; +again: + m++; + } + + state[walk].c[0] = n++; + state[walk].state[0] = 0; /* terminal marker */ + state[walk].count = 1; + } + + walk = 0; + for (n = 0; n < next; n++) { + state[n].bytepos = walk; + walk += (2 * state[n].count); + } + + /* compute everyone's position first */ + + pos = 0; + walk = 0; + for (n = 0; n < next; n++) { + + state[n].real_pos = pos; + + for (m = 0; m < state[n].count; m++) { + + if (state[n].state[m] == 0) + pos += 2; /* terminal marker */ + else { /* c is a character */ + if ((state[state[n].state[m]].bytepos - + walk) == 2) + pos++; + else { + pos += 3; + if (m == state[n].count - 1) + pos++; /* fail */ + } + } + walk += 2; + } + } + + walk = 0; + pos = 0; + for (n = 0; n < next; n++) { + for (m = 0; m < state[n].count; m++) { + + if (!m) + fprintf(stdout, "/* pos %04x: %3d */ ", + state[n].real_pos, n); + else + fprintf(stdout, " "); + + y = state[n].c[m]; + saw = state[n].state[m]; + + if (saw == 0) { // c is a terminal then + + if (y > 0x7ff) { + fprintf(stderr, "terminal too big\n"); + return 2; + } + + fprintf(stdout, " 0x%02X, 0x%02X " + " " + "/* - terminal marker %2d - */,\n", + y >> 8, y & 0xff, y & 0x7f); + pos += 2; + walk += 2; + continue; + } + + /* c is a character */ + + prev = y &0x7f; + if (prev < 32 || prev > 126) + prev = '.'; + + + if ((state[saw].bytepos - walk) == 2) { + fprintf(stdout, " 0x%02X /* '%c' -> */,\n", + y | 0x80, prev); + pos++; + walk += 2; + continue; + } + + j = state[saw].real_pos - pos; + + if (j > 0xffff) { + fprintf(stderr, + "Jump > 64K bytes ahead (%d to %d)\n", + state[n].real_pos, state[saw].real_pos); + return 1; + } + fprintf(stdout, " 0x%02X /* '%c' */, 0x%02X, 0x%02X " + "/* (to 0x%04X state %3d) */,\n", + y, prev, + j & 0xff, j >> 8, + state[saw].real_pos, saw); + pos += 3; + + if (m == state[n].count - 1) { + fprintf(stdout, + " 0x%02X, /* fail */\n", + FAIL_CHAR); + pos++; /* fail */ + } + + walk += 2; + } + } + + fprintf(stdout, "/* total size %d bytes */\n", pos); + + /* + * Try to parse every legal input string + */ + + for (n = 0; n < sizeof(set) / sizeof(set[0]); n++) { + walk = 0; + m = 0; + y = -1; + + if (set[n][0] == '\0') + continue; + + fprintf(stderr, " trying '%s'\n", set[n]); + + while (set[n][m]) { + walk = lextable_decode(walk, set[n][m]); + if (walk < 0) { + fprintf(stderr, "failed\n"); + return 3; + } + + if (lextable[walk] < FAIL_CHAR) { + y = (lextable[walk] << 8) + lextable[walk + 1]; + break; + } + m++; + } + + if (y != n) { + fprintf(stderr, "decode failed %d\n", y); + return 4; + } + } + + fprintf(stderr, "All decode OK\n"); + + return 0; +} diff --git a/thirdparty/lws/misc/base64-decode.c b/thirdparty/lws/misc/base64-decode.c new file mode 100644 index 0000000000..c8f11d21b8 --- /dev/null +++ b/thirdparty/lws/misc/base64-decode.c @@ -0,0 +1,206 @@ +/* + * This code originally came from here + * + * http://base64.sourceforge.net/b64.c + * + * with the following license: + * + * LICENCE: Copyright (c) 2001 Bob Trower, Trantor Standard Systems Inc. + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated + * documentation files (the "Software"), to deal in the + * Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, + * sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall + * be included in all copies or substantial portions of the + * Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY + * KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE + * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR + * PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS + * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR + * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR + * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + * + * VERSION HISTORY: + * Bob Trower 08/04/01 -- Create Version 0.00.00B + * + * I cleaned it up quite a bit to match the (linux kernel) style of the rest + * of libwebsockets; this version is under LGPL2.1 + SLE like the rest of lws + * since he explicitly allows sublicensing, but I give the URL above so you can + * get the original with Bob's super-liberal terms directly if you prefer. + */ + +#include <stdio.h> +#include <string.h> +#include "private-libwebsockets.h" + +static const char encode[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz0123456789+/"; +static const char decode[] = "|$$$}rstuvwxyz{$$$$$$$>?@ABCDEFGHIJKLMNOPQRSTUVW" + "$$$$$$XYZ[\\]^_`abcdefghijklmnopq"; + +LWS_VISIBLE int +lws_b64_encode_string(const char *in, int in_len, char *out, int out_size) +{ + unsigned char triple[3]; + int i; + int len; + int line = 0; + int done = 0; + + while (in_len) { + len = 0; + for (i = 0; i < 3; i++) { + if (in_len) { + triple[i] = *in++; + len++; + in_len--; + } else + triple[i] = 0; + } + + if (done + 4 >= out_size) + return -1; + + *out++ = encode[triple[0] >> 2]; + *out++ = encode[((triple[0] & 0x03) << 4) | + ((triple[1] & 0xf0) >> 4)]; + *out++ = (len > 1 ? encode[((triple[1] & 0x0f) << 2) | + ((triple[2] & 0xc0) >> 6)] : '='); + *out++ = (len > 2 ? encode[triple[2] & 0x3f] : '='); + + done += 4; + line += 4; + } + + if (done + 1 >= out_size) + return -1; + + *out++ = '\0'; + + return done; +} + +/* + * returns length of decoded string in out, or -1 if out was too small + * according to out_size + */ + +LWS_VISIBLE int +lws_b64_decode_string(const char *in, char *out, int out_size) +{ + int len, i, c = 0, done = 0; + unsigned char v, quad[4]; + + while (*in) { + + len = 0; + for (i = 0; i < 4 && *in; i++) { + + v = 0; + c = 0; + while (*in && !v) { + c = v = *in++; + v = (v < 43 || v > 122) ? 0 : decode[v - 43]; + if (v) + v = (v == '$') ? 0 : v - 61; + } + if (c) { + len++; + if (v) + quad[i] = v - 1; + } else + quad[i] = 0; + } + + if (out_size < (done + len - 1)) + /* out buffer is too small */ + return -1; + + /* + * "The '==' sequence indicates that the last group contained + * only one byte, and '=' indicates that it contained two + * bytes." (wikipedia) + */ + + if (!*in && c == '=') + len--; + + if (len >= 2) + *out++ = quad[0] << 2 | quad[1] >> 4; + if (len >= 3) + *out++ = quad[1] << 4 | quad[2] >> 2; + if (len >= 4) + *out++ = ((quad[2] << 6) & 0xc0) | quad[3]; + + done += len - 1; + } + + if (done + 1 >= out_size) + return -1; + + *out = '\0'; + + return done; +} + +#if 0 +int +lws_b64_selftest(void) +{ + char buf[64]; + unsigned int n, r = 0; + unsigned int test; + /* examples from https://en.wikipedia.org/wiki/Base64 */ + static const char * const plaintext[] = { + "any carnal pleasure.", + "any carnal pleasure", + "any carnal pleasur", + "any carnal pleasu", + "any carnal pleas", + "Admin:kloikloi" + }; + static const char * const coded[] = { + "YW55IGNhcm5hbCBwbGVhc3VyZS4=", + "YW55IGNhcm5hbCBwbGVhc3VyZQ==", + "YW55IGNhcm5hbCBwbGVhc3Vy", + "YW55IGNhcm5hbCBwbGVhc3U=", + "YW55IGNhcm5hbCBwbGVhcw==", + "QWRtaW46a2xvaWtsb2k=" + }; + + for (test = 0; test < sizeof plaintext / sizeof(plaintext[0]); test++) { + + buf[sizeof(buf) - 1] = '\0'; + n = lws_b64_encode_string(plaintext[test], + strlen(plaintext[test]), buf, sizeof buf); + if (n != strlen(coded[test]) || strcmp(buf, coded[test])) { + lwsl_err("Failed lws_b64 encode selftest " + "%d result '%s' %d\n", test, buf, n); + r = -1; + } + + buf[sizeof(buf) - 1] = '\0'; + n = lws_b64_decode_string(coded[test], buf, sizeof buf); + if (n != strlen(plaintext[test]) || + strcmp(buf, plaintext[test])) { + lwsl_err("Failed lws_b64 decode selftest " + "%d result '%s' / '%s', %d / %d\n", + test, buf, plaintext[test], n, strlen(plaintext[test])); + r = -1; + } + } + + lwsl_notice("Base 64 selftests passed\n"); + + return r; +} +#endif diff --git a/thirdparty/lws/misc/getifaddrs.c b/thirdparty/lws/misc/getifaddrs.c new file mode 100644 index 0000000000..4f42ab4595 --- /dev/null +++ b/thirdparty/lws/misc/getifaddrs.c @@ -0,0 +1,270 @@ +/* + * Copyright (c) 2000 - 2001 Kungliga Tekniska H�gskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * + * originally downloaded from + * + * http://ftp.uninett.no/pub/OpenBSD/src/kerberosV/src/lib/roken/getifaddrs.c + */ + +#include <errno.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <stdlib.h> +#include <string.h> +#include <sys/ioctl.h> +#include <unistd.h> +#include "private-libwebsockets.h" + +#ifdef LWS_HAVE_SYS_SOCKIO_H +#include <sys/sockio.h> +#endif + +#ifdef LWS_HAVE_NETINET_IN6_VAR_H +#include <netinet/in6_var.h> +#endif + +#ifndef max +#define max(a, b) ((a) > (b) ? (a) : (b)) +#endif + +#include "getifaddrs.h" + +static int +getifaddrs2(struct ifaddrs **ifap, int af, int siocgifconf, int siocgifflags, + size_t ifreq_sz) +{ + int ret; + int fd; + size_t buf_size; + char *buf; + struct ifconf ifconf; + char *p; + size_t sz; + struct sockaddr sa_zero; + struct ifreq *ifr; + struct ifaddrs *start, **end = &start; + + buf = NULL; + + memset(&sa_zero, 0, sizeof(sa_zero)); + fd = socket(af, SOCK_DGRAM, 0); + if (fd < 0) + return -1; + + buf_size = 8192; + for (;;) { + buf = lws_zalloc(buf_size, "getifaddrs2"); + if (buf == NULL) { + ret = ENOMEM; + goto error_out; + } + ifconf.ifc_len = buf_size; + ifconf.ifc_buf = buf; + + /* + * Solaris returns EINVAL when the buffer is too small. + */ + if (ioctl(fd, siocgifconf, &ifconf) < 0 && errno != EINVAL) { + ret = errno; + goto error_out; + } + /* + * Can the difference between a full and a overfull buf + * be determined? + */ + + if (ifconf.ifc_len < (int)buf_size) + break; + lws_free(buf); + buf_size *= 2; + } + + for (p = ifconf.ifc_buf; p < ifconf.ifc_buf + ifconf.ifc_len; p += sz) { + struct ifreq ifreq; + struct sockaddr *sa; + size_t salen; + + ifr = (struct ifreq *)p; + sa = &ifr->ifr_addr; + + sz = ifreq_sz; + salen = sizeof(struct sockaddr); +#ifdef LWS_HAVE_STRUCT_SOCKADDR_SA_LEN + salen = sa->sa_len; + sz = max(sz, sizeof(ifr->ifr_name) + sa->sa_len); +#endif +#ifdef SA_LEN + salen = SA_LEN(sa); + sz = max(sz, sizeof(ifr->ifr_name) + SA_LEN(sa)); +#endif + memset(&ifreq, 0, sizeof(ifreq)); + memcpy(ifreq.ifr_name, ifr->ifr_name, sizeof(ifr->ifr_name)); + + if (ioctl(fd, siocgifflags, &ifreq) < 0) { + ret = errno; + goto error_out; + } + + *end = lws_malloc(sizeof(**end), "getifaddrs"); + + (*end)->ifa_next = NULL; + (*end)->ifa_name = strdup(ifr->ifr_name); + (*end)->ifa_flags = ifreq.ifr_flags; + (*end)->ifa_addr = lws_malloc(salen, "getifaddrs"); + memcpy((*end)->ifa_addr, sa, salen); + (*end)->ifa_netmask = NULL; + +#if 0 + /* fix these when we actually need them */ + if (ifreq.ifr_flags & IFF_BROADCAST) { + (*end)->ifa_broadaddr = + lws_malloc(sizeof(ifr->ifr_broadaddr), "getifaddrs"); + memcpy((*end)->ifa_broadaddr, &ifr->ifr_broadaddr, + sizeof(ifr->ifr_broadaddr)); + } else if (ifreq.ifr_flags & IFF_POINTOPOINT) { + (*end)->ifa_dstaddr = + lws_malloc(sizeof(ifr->ifr_dstaddr), "getifaddrs"); + memcpy((*end)->ifa_dstaddr, &ifr->ifr_dstaddr, + sizeof(ifr->ifr_dstaddr)); + } else + (*end)->ifa_dstaddr = NULL; +#else + (*end)->ifa_dstaddr = NULL; +#endif + (*end)->ifa_data = NULL; + + end = &(*end)->ifa_next; + + } + *ifap = start; + close(fd); + lws_free(buf); + return 0; + +error_out: + close(fd); + lws_free(buf); + errno = ret; + + return -1; +} + +int +getifaddrs(struct ifaddrs **ifap) +{ + int ret = -1; + errno = ENXIO; +#if defined(AF_INET6) && defined(SIOCGIF6CONF) && defined(SIOCGIF6FLAGS) + if (ret) + ret = getifaddrs2(ifap, AF_INET6, SIOCGIF6CONF, SIOCGIF6FLAGS, + sizeof(struct in6_ifreq)); +#endif +#if defined(LWS_HAVE_IPV6) && defined(SIOCGIFCONF) + if (ret) + ret = getifaddrs2(ifap, AF_INET6, SIOCGIFCONF, SIOCGIFFLAGS, + sizeof(struct ifreq)); +#endif +#if defined(AF_INET) && defined(SIOCGIFCONF) && defined(SIOCGIFFLAGS) + if (ret) + ret = getifaddrs2(ifap, AF_INET, SIOCGIFCONF, SIOCGIFFLAGS, + sizeof(struct ifreq)); +#endif + return ret; +} + +void +freeifaddrs(struct ifaddrs *ifp) +{ + struct ifaddrs *p, *q; + + for (p = ifp; p; ) { + lws_free(p->ifa_name); + lws_free(p->ifa_addr); + lws_free(p->ifa_dstaddr); + lws_free(p->ifa_netmask); + lws_free(p->ifa_data); + q = p; + p = p->ifa_next; + lws_free(q); + } +} + +#ifdef TEST + +void +print_addr(const char *s, struct sockaddr *sa) +{ + int i; + printf(" %s=%d/", s, sa->sa_family); +#ifdef LWS_HAVE_STRUCT_SOCKADDR_SA_LEN + for (i = 0; + i < sa->sa_len - ((lws_intptr_t)sa->sa_data - (lws_intptr_t)&sa->sa_family); i++) + printf("%02x", ((unsigned char *)sa->sa_data)[i]); +#else + for (i = 0; i < sizeof(sa->sa_data); i++) + printf("%02x", ((unsigned char *)sa->sa_data)[i]); +#endif + printf("\n"); +} + +void +print_ifaddrs(struct ifaddrs *x) +{ + struct ifaddrs *p; + + for (p = x; p; p = p->ifa_next) { + printf("%s\n", p->ifa_name); + printf(" flags=%x\n", p->ifa_flags); + if (p->ifa_addr) + print_addr("addr", p->ifa_addr); + if (p->ifa_dstaddr) + print_addr("dstaddr", p->ifa_dstaddr); + if (p->ifa_netmask) + print_addr("netmask", p->ifa_netmask); + printf(" %p\n", p->ifa_data); + } +} + +int +main() +{ + struct ifaddrs *a = NULL, *b; + getifaddrs2(&a, AF_INET, SIOCGIFCONF, SIOCGIFFLAGS, + sizeof(struct ifreq)); + print_ifaddrs(a); + printf("---\n"); + getifaddrs(&b); + print_ifaddrs(b); + return 0; +} +#endif diff --git a/thirdparty/lws/misc/getifaddrs.h b/thirdparty/lws/misc/getifaddrs.h new file mode 100644 index 0000000000..d26670c082 --- /dev/null +++ b/thirdparty/lws/misc/getifaddrs.h @@ -0,0 +1,80 @@ +#ifndef LWS_HAVE_GETIFADDRS +#define LWS_HAVE_GETIFADDRS 0 +#endif + +#if LWS_HAVE_GETIFADDRS +#include <sys/types.h> +#include <ifaddrs.h> +#else +#ifdef __cplusplus +extern "C" { +#endif +/* + * Copyright (c) 2000 Kungliga Tekniska H�gskolan + * (Royal Institute of Technology, Stockholm, Sweden). + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * 3. Neither the name of the Institute nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +/* $KTH: ifaddrs.hin,v 1.3 2000/12/11 00:01:13 assar Exp $ */ + +#ifndef ifaddrs_h_7467027A95AD4B5C8DDD40FE7D973791 +#define ifaddrs_h_7467027A95AD4B5C8DDD40FE7D973791 + +/* + * the interface is defined in terms of the fields below, and this is + * sometimes #define'd, so there seems to be no simple way of solving + * this and this seemed the best. */ + +#undef ifa_dstaddr + +struct ifaddrs { + struct ifaddrs *ifa_next; + char *ifa_name; + unsigned int ifa_flags; + struct sockaddr *ifa_addr; + struct sockaddr *ifa_netmask; + struct sockaddr *ifa_dstaddr; + void *ifa_data; +}; + +#ifndef ifa_broadaddr +#define ifa_broadaddr ifa_dstaddr +#endif + +int getifaddrs(struct ifaddrs **); + +void freeifaddrs(struct ifaddrs *); + +#endif /* __ifaddrs_h__ */ + +#ifdef __cplusplus +} +#endif +#endif diff --git a/thirdparty/lws/misc/lejp.c b/thirdparty/lws/misc/lejp.c new file mode 100644 index 0000000000..5407c90f97 --- /dev/null +++ b/thirdparty/lws/misc/lejp.c @@ -0,0 +1,709 @@ +/* + * Lightweight Embedded JSON Parser + * + * Copyright (C) 2013 Andy Green <andy@warmcat.com> + * This code is licensed under LGPL 2.1 + * http://www.gnu.org/licenses/lgpl-2.1.html + */ + +#include <string.h> +#include "lejp.h" + +#include <stdio.h> + +/** + * lejp_construct - prepare a struct lejp_ctx for use + * + * \param ctx: pointer to your struct lejp_ctx + * \param callback: your user callback which will received parsed tokens + * \param user: optional user data pointer untouched by lejp + * \param paths: your array of name elements you are interested in + * \param count_paths: ARRAY_SIZE() of @paths + * + * Prepares your context struct for use with lejp + */ + +void +lejp_construct(struct lejp_ctx *ctx, + signed char (*callback)(struct lejp_ctx *ctx, char reason), void *user, + const char * const *paths, unsigned char count_paths) +{ + ctx->st[0].s = 0; + ctx->st[0].p = 0; + ctx->st[0].i = 0; + ctx->st[0].b = 0; + ctx->sp = 0; + ctx->ipos = 0; + ctx->ppos = 0; + ctx->path_match = 0; + ctx->path[0] = '\0'; + ctx->callback = callback; + ctx->user = user; + ctx->paths = paths; + ctx->count_paths = count_paths; + ctx->line = 1; + ctx->callback(ctx, LEJPCB_CONSTRUCTED); +} + +/** + * lejp_destruct - retire a previously constructed struct lejp_ctx + * + * \param ctx: pointer to your struct lejp_ctx + * + * lejp does not perform any allocations, but since your user code might, this + * provides a one-time LEJPCB_DESTRUCTED callback at destruction time where + * you can clean up in your callback. + */ + +void +lejp_destruct(struct lejp_ctx *ctx) +{ + /* no allocations... just let callback know what it happening */ + ctx->callback(ctx, LEJPCB_DESTRUCTED); +} + +/** + * lejp_change_callback - switch to a different callback from now on + * + * \param ctx: pointer to your struct lejp_ctx + * \param callback: your user callback which will received parsed tokens + * + * This tells the old callback it was destroyed, in case you want to take any + * action because that callback "lost focus", then changes to the new + * callback and tells it first that it was constructed, and then started. + * + * Changing callback is a cheap and powerful trick to split out handlers + * according to information earlier in the parse. For example you may have + * a JSON pair "schema" whose value defines what can be expected for the rest + * of the JSON. Rather than having one huge callback for all cases, you can + * have an initial one looking for "schema" which then calls + * lejp_change_callback() to a handler specific for the schema. + * + * Notice that afterwards, you need to construct the context again anyway to + * parse another JSON object, and the callback is reset then to the main, + * schema-interpreting one. The construction action is very lightweight. + */ + +void +lejp_change_callback(struct lejp_ctx *ctx, + signed char (*callback)(struct lejp_ctx *ctx, char reason)) +{ + ctx->callback(ctx, LEJPCB_DESTRUCTED); + ctx->callback = callback; + ctx->callback(ctx, LEJPCB_CONSTRUCTED); + ctx->callback(ctx, LEJPCB_START); +} + +static void +lejp_check_path_match(struct lejp_ctx *ctx) +{ + const char *p, *q; + int n; + + /* we only need to check if a match is not active */ + for (n = 0; !ctx->path_match && n < ctx->count_paths; n++) { + ctx->wildcount = 0; + p = ctx->path; + q = ctx->paths[n]; + while (*p && *q) { + if (*q != '*') { + if (*p != *q) + break; + p++; + q++; + continue; + } + ctx->wild[ctx->wildcount++] = p - ctx->path; + q++; + /* + * if * has something after it, match to . + * if ends with *, eat everything. + * This implies match sequences must be ordered like + * x.*.* + * x.* + * if both options are possible + */ + while (*p && (*p != '.' || !*q)) + p++; + } + if (*p || *q) + continue; + + ctx->path_match = n + 1; + ctx->path_match_len = ctx->ppos; + return; + } + + if (!ctx->path_match) + ctx->wildcount = 0; +} + +int +lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len) +{ + int n; + + if (wildcard >= ctx->wildcount || !len) + return 0; + + n = ctx->wild[wildcard]; + + while (--len && n < ctx->ppos && (n == ctx->wild[wildcard] || ctx->path[n] != '.')) + *dest++ = ctx->path[n++]; + + *dest = '\0'; + n++; + + return n - ctx->wild[wildcard]; +} + +/** + * lejp_parse - interpret some more incoming data incrementally + * + * \param ctx: previously constructed parsing context + * \param json: char buffer with the new data to interpret + * \param len: amount of data in the buffer + * + * Because lejp is a stream parser, it incrementally parses as new data + * becomes available, maintaining all state in the context struct. So an + * incomplete JSON is a normal situation, getting you a LEJP_CONTINUE + * return, signalling there's no error but to call again with more data when + * it comes to complete the parsing. Successful parsing completes with a + * 0 or positive integer indicating how much of the last input buffer was + * unused. + */ + +int +lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len) +{ + unsigned char c, n, s, ret = LEJP_REJECT_UNKNOWN; + static const char esc_char[] = "\"\\/bfnrt"; + static const char esc_tran[] = "\"\\/\b\f\n\r\t"; + static const char tokens[] = "rue alse ull "; + + if (!ctx->sp && !ctx->ppos) + ctx->callback(ctx, LEJPCB_START); + + while (len--) { + c = *json++; + + s = ctx->st[ctx->sp].s; + + /* skip whitespace unless we should care */ + if (c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '#') { + if (c == '\n') { + ctx->line++; + ctx->st[ctx->sp].s &= ~LEJP_FLAG_WS_COMMENTLINE; + } + if (!(s & LEJP_FLAG_WS_KEEP)) { + if (c == '#') + ctx->st[ctx->sp].s |= + LEJP_FLAG_WS_COMMENTLINE; + continue; + } + } + + if (ctx->st[ctx->sp].s & LEJP_FLAG_WS_COMMENTLINE) + continue; + + switch (s) { + case LEJP_IDLE: + if (c != '{') { + ret = LEJP_REJECT_IDLE_NO_BRACE; + goto reject; + } + if (ctx->callback(ctx, LEJPCB_OBJECT_START)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + ctx->st[ctx->sp].s = LEJP_MEMBERS; + break; + case LEJP_MEMBERS: + if (c == '}') { + ctx->st[ctx->sp].s = LEJP_IDLE; + ret = LEJP_REJECT_MEMBERS_NO_CLOSE; + goto reject; + } + ctx->st[ctx->sp].s = LEJP_M_P; + goto redo_character; + case LEJP_M_P: + if (c != '\"') { + ret = LEJP_REJECT_MP_NO_OPEN_QUOTE; + goto reject; + } + /* push */ + ctx->st[ctx->sp].s = LEJP_MP_DELIM; + c = LEJP_MP_STRING; + goto add_stack_level; + + case LEJP_MP_STRING: + if (c == '\"') { + if (!ctx->sp) { + ret = LEJP_REJECT_MP_STRING_UNDERRUN; + goto reject; + } + if (ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) { + ctx->buf[ctx->npos] = '\0'; + if (ctx->callback(ctx, + LEJPCB_VAL_STR_END) < 0) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + } + /* pop */ + ctx->sp--; + break; + } + if (c == '\\') { + ctx->st[ctx->sp].s = LEJP_MP_STRING_ESC; + break; + } + if (c < ' ') {/* "control characters" not allowed */ + ret = LEJP_REJECT_MP_ILLEGAL_CTRL; + goto reject; + } + goto emit_string_char; + + case LEJP_MP_STRING_ESC: + if (c == 'u') { + ctx->st[ctx->sp].s = LEJP_MP_STRING_ESC_U1; + ctx->uni = 0; + break; + } + for (n = 0; n < sizeof(esc_char); n++) { + if (c != esc_char[n]) + continue; + /* found it */ + c = esc_tran[n]; + ctx->st[ctx->sp].s = LEJP_MP_STRING; + goto emit_string_char; + } + ret = LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC; + /* illegal escape char */ + goto reject; + + case LEJP_MP_STRING_ESC_U1: + case LEJP_MP_STRING_ESC_U2: + case LEJP_MP_STRING_ESC_U3: + case LEJP_MP_STRING_ESC_U4: + ctx->uni <<= 4; + if (c >= '0' && c <= '9') + ctx->uni |= c - '0'; + else + if (c >= 'a' && c <= 'f') + ctx->uni = c - 'a' + 10; + else + if (c >= 'A' && c <= 'F') + ctx->uni = c - 'A' + 10; + else { + ret = LEJP_REJECT_ILLEGAL_HEX; + goto reject; + } + ctx->st[ctx->sp].s++; + switch (s) { + case LEJP_MP_STRING_ESC_U2: + if (ctx->uni < 0x08) + break; + /* + * 0x08-0xff (0x0800 - 0xffff) + * emit 3-byte UTF-8 + */ + c = 0xe0 | ((ctx->uni >> 4) & 0xf); + goto emit_string_char; + + case LEJP_MP_STRING_ESC_U3: + if (ctx->uni >= 0x080) { + /* + * 0x080 - 0xfff (0x0800 - 0xffff) + * middle 3-byte seq + * send ....XXXXXX.. + */ + c = 0x80 | ((ctx->uni >> 2) & 0x3f); + goto emit_string_char; + } + if (ctx->uni < 0x008) + break; + /* + * 0x008 - 0x7f (0x0080 - 0x07ff) + * start 2-byte seq + */ + c = 0xc0 | (ctx->uni >> 2); + goto emit_string_char; + + case LEJP_MP_STRING_ESC_U4: + if (ctx->uni >= 0x0080) + /* end of 2 or 3-byte seq */ + c = 0x80 | (ctx->uni & 0x3f); + else + /* literal */ + c = (unsigned char)ctx->uni; + + ctx->st[ctx->sp].s = LEJP_MP_STRING; + goto emit_string_char; + default: + break; + } + break; + + case LEJP_MP_DELIM: + if (c != ':') { + ret = LEJP_REJECT_MP_DELIM_MISSING_COLON; + goto reject; + } + ctx->st[ctx->sp].s = LEJP_MP_VALUE; + ctx->path[ctx->ppos] = '\0'; + + lejp_check_path_match(ctx); + if (ctx->callback(ctx, LEJPCB_PAIR_NAME)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + break; + + case LEJP_MP_VALUE: + if (c >= '0' && c <= '9') { + ctx->npos = 0; + ctx->dcount = 0; + ctx->f = 0; + ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_INT; + goto redo_character; + } + switch (c) { + case'\"': + /* push */ + ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; + c = LEJP_MP_STRING; + ctx->npos = 0; + ctx->buf[0] = '\0'; + if (ctx->callback(ctx, LEJPCB_VAL_STR_START)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + goto add_stack_level; + + case '{': + /* push */ + ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; + c = LEJP_MEMBERS; + lejp_check_path_match(ctx); + if (ctx->callback(ctx, LEJPCB_OBJECT_START)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + ctx->path_match = 0; + goto add_stack_level; + + case '[': + /* push */ + ctx->st[ctx->sp].s = LEJP_MP_ARRAY_END; + c = LEJP_MP_VALUE; + ctx->path[ctx->ppos++] = '['; + ctx->path[ctx->ppos++] = ']'; + ctx->path[ctx->ppos] = '\0'; + if (ctx->callback(ctx, LEJPCB_ARRAY_START)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + ctx->i[ctx->ipos++] = 0; + if (ctx->ipos > ARRAY_SIZE(ctx->i)) { + ret = LEJP_REJECT_MP_DELIM_ISTACK; + goto reject; + } + goto add_stack_level; + + case 't': /* true */ + ctx->uni = 0; + ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK; + break; + + case 'f': + ctx->uni = 4; + ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK; + break; + + case 'n': + ctx->uni = 4 + 5; + ctx->st[ctx->sp].s = LEJP_MP_VALUE_TOK; + break; + default: + ret = LEJP_REJECT_MP_DELIM_BAD_VALUE_START; + goto reject; + } + break; + + case LEJP_MP_VALUE_NUM_INT: + if (!ctx->npos && c == '-') { + ctx->f |= LEJP_SEEN_MINUS; + goto append_npos; + } + + if (ctx->dcount < 10 && c >= '0' && c <= '9') { + if (ctx->f & LEJP_SEEN_POINT) + ctx->f |= LEJP_SEEN_POST_POINT; + ctx->dcount++; + goto append_npos; + } + if (c == '.') { + if (ctx->dcount || (ctx->f & LEJP_SEEN_POINT)) { + ret = LEJP_REJECT_MP_VAL_NUM_FORMAT; + goto reject; + } + ctx->f |= LEJP_SEEN_POINT; + goto append_npos; + } + /* + * before exponent, if we had . we must have had at + * least one more digit + */ + if ((ctx->f & + (LEJP_SEEN_POINT | LEJP_SEEN_POST_POINT)) == + LEJP_SEEN_POINT) { + ret = LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC; + goto reject; + } + if (c == 'e' || c == 'E') { + if (ctx->f & LEJP_SEEN_EXP) { + ret = LEJP_REJECT_MP_VAL_NUM_FORMAT; + goto reject; + } + ctx->f |= LEJP_SEEN_EXP; + ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_EXP; + goto append_npos; + } + /* if none of the above, did we even have a number? */ + if (!ctx->dcount) { + ret = LEJP_REJECT_MP_VAL_NUM_FORMAT; + goto reject; + } + + ctx->buf[ctx->npos] = '\0'; + if (ctx->f & LEJP_SEEN_POINT) { + if (ctx->callback(ctx, LEJPCB_VAL_NUM_FLOAT)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + } else { + if (ctx->callback(ctx, LEJPCB_VAL_NUM_INT)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + } + + /* then this is the post-number character, loop */ + ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; + goto redo_character; + + case LEJP_MP_VALUE_NUM_EXP: + ctx->st[ctx->sp].s = LEJP_MP_VALUE_NUM_INT; + if (c >= '0' && c <= '9') + goto redo_character; + if (c == '+' || c == '-') + goto append_npos; + ret = LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP; + goto reject; + + case LEJP_MP_VALUE_TOK: /* true, false, null */ + if (c != tokens[ctx->uni]) { + ret = LEJP_REJECT_MP_VAL_TOK_UNKNOWN; + goto reject; + } + ctx->uni++; + if (tokens[ctx->uni] != ' ') + break; + switch (ctx->uni) { + case 3: + ctx->buf[0] = '1'; + ctx->buf[1] = '\0'; + if (ctx->callback(ctx, LEJPCB_VAL_TRUE)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + break; + case 8: + ctx->buf[0] = '0'; + ctx->buf[1] = '\0'; + if (ctx->callback(ctx, LEJPCB_VAL_FALSE)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + break; + case 12: + ctx->buf[0] = '\0'; + if (ctx->callback(ctx, LEJPCB_VAL_NULL)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + break; + } + ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; + break; + + case LEJP_MP_COMMA_OR_END: + ctx->path[ctx->ppos] = '\0'; + if (c == ',') { + /* increment this stack level's index */ + ctx->st[ctx->sp].s = LEJP_M_P; + if (!ctx->sp) { + ctx->ppos = 0; + /* + * since we came back to root level, + * no path can still match + */ + ctx->path_match = 0; + break; + } + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->path[ctx->ppos] = '\0'; + if (ctx->path_match && + ctx->ppos <= ctx->path_match_len) + /* + * we shrank the path to be + * smaller than the matching point + */ + ctx->path_match = 0; + + if (ctx->st[ctx->sp - 1].s != LEJP_MP_ARRAY_END) + break; + /* top level is definitely an array... */ + if (ctx->ipos) + ctx->i[ctx->ipos - 1]++; + ctx->st[ctx->sp].s = LEJP_MP_VALUE; + break; + } + if (c == ']') { + if (!ctx->sp) { + ret = LEJP_REJECT_MP_C_OR_E_UNDERF; + goto reject; + } + /* pop */ + ctx->sp--; + if (ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) { + ret = LEJP_REJECT_MP_C_OR_E_NOTARRAY; + goto reject; + } + /* drop the path [n] bit */ + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->ipos = ctx->st[ctx->sp - 1].i; + ctx->path[ctx->ppos] = '\0'; + if (ctx->path_match && + ctx->ppos <= ctx->path_match_len) + /* + * we shrank the path to be + * smaller than the matching point + */ + ctx->path_match = 0; + + /* do LEJP_MP_ARRAY_END processing */ + goto redo_character; + } + if (c == '}') { + if (ctx->sp == 0) { + lejp_check_path_match(ctx); + if (ctx->callback(ctx, LEJPCB_OBJECT_END)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + ctx->callback(ctx, LEJPCB_COMPLETE); + /* done, return unused amount */ + return len; + } + /* pop */ + ctx->sp--; + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->ipos = ctx->st[ctx->sp - 1].i; + ctx->path[ctx->ppos] = '\0'; + if (ctx->path_match && + ctx->ppos <= ctx->path_match_len) + /* + * we shrank the path to be + * smaller than the matching point + */ + ctx->path_match = 0; + lejp_check_path_match(ctx); + if (ctx->callback(ctx, LEJPCB_OBJECT_END)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + break; + } + + ret = LEJP_REJECT_MP_C_OR_E_NEITHER; + goto reject; + + case LEJP_MP_ARRAY_END: + ctx->path[ctx->ppos] = '\0'; + if (c == ',') { + /* increment this stack level's index */ + if (ctx->ipos) + ctx->i[ctx->ipos - 1]++; + ctx->st[ctx->sp].s = LEJP_MP_VALUE; + if (ctx->sp) + ctx->ppos = ctx->st[ctx->sp - 1].p; + ctx->path[ctx->ppos] = '\0'; + break; + } + if (c != ']') { + ret = LEJP_REJECT_MP_ARRAY_END_MISSING; + goto reject; + } + + ctx->st[ctx->sp].s = LEJP_MP_COMMA_OR_END; + ctx->callback(ctx, LEJPCB_ARRAY_END); + break; + } + + continue; + +emit_string_char: + if (!ctx->sp || ctx->st[ctx->sp - 1].s != LEJP_MP_DELIM) { + /* assemble the string value into chunks */ + ctx->buf[ctx->npos++] = c; + if (ctx->npos == sizeof(ctx->buf) - 1) { + if (ctx->callback(ctx, LEJPCB_VAL_STR_CHUNK)) { + ret = LEJP_REJECT_CALLBACK; + goto reject; + } + ctx->npos = 0; + } + continue; + } + /* name part of name:value pair */ + ctx->path[ctx->ppos++] = c; + continue; + +add_stack_level: + /* push on to the object stack */ + if (ctx->ppos && ctx->st[ctx->sp].s != LEJP_MP_COMMA_OR_END && + ctx->st[ctx->sp].s != LEJP_MP_ARRAY_END) + ctx->path[ctx->ppos++] = '.'; + + ctx->st[ctx->sp].p = ctx->ppos; + ctx->st[ctx->sp].i = ctx->ipos; + if (++ctx->sp == ARRAY_SIZE(ctx->st)) { + ret = LEJP_REJECT_STACK_OVERFLOW; + goto reject; + } + ctx->path[ctx->ppos] = '\0'; + ctx->st[ctx->sp].s = c; + ctx->st[ctx->sp].b = 0; + continue; + +append_npos: + if (ctx->npos >= sizeof(ctx->buf)) { + ret = LEJP_REJECT_NUM_TOO_LONG; + goto reject; + } + ctx->buf[ctx->npos++] = c; + continue; + +redo_character: + json--; + len++; + } + + return LEJP_CONTINUE; + +reject: + ctx->callback(ctx, LEJPCB_FAILED); + return ret; +} diff --git a/thirdparty/lws/misc/lejp.h b/thirdparty/lws/misc/lejp.h new file mode 100644 index 0000000000..0b37bb3e42 --- /dev/null +++ b/thirdparty/lws/misc/lejp.h @@ -0,0 +1,232 @@ +#include "libwebsockets.h" +struct lejp_ctx; + +#ifndef ARRAY_SIZE +#define ARRAY_SIZE(_x) (sizeof(_x) / sizeof(_x[0])) +#endif +#define LEJP_FLAG_WS_KEEP 64 +#define LEJP_FLAG_WS_COMMENTLINE 32 + +enum lejp_states { + LEJP_IDLE = 0, + LEJP_MEMBERS = 1, + LEJP_M_P = 2, + LEJP_MP_STRING = LEJP_FLAG_WS_KEEP | 3, + LEJP_MP_STRING_ESC = LEJP_FLAG_WS_KEEP | 4, + LEJP_MP_STRING_ESC_U1 = LEJP_FLAG_WS_KEEP | 5, + LEJP_MP_STRING_ESC_U2 = LEJP_FLAG_WS_KEEP | 6, + LEJP_MP_STRING_ESC_U3 = LEJP_FLAG_WS_KEEP | 7, + LEJP_MP_STRING_ESC_U4 = LEJP_FLAG_WS_KEEP | 8, + LEJP_MP_DELIM = 9, + LEJP_MP_VALUE = 10, + LEJP_MP_VALUE_NUM_INT = LEJP_FLAG_WS_KEEP | 11, + LEJP_MP_VALUE_NUM_EXP = LEJP_FLAG_WS_KEEP | 12, + LEJP_MP_VALUE_TOK = LEJP_FLAG_WS_KEEP | 13, + LEJP_MP_COMMA_OR_END = 14, + LEJP_MP_ARRAY_END = 15, +}; + +enum lejp_reasons { + LEJP_CONTINUE = -1, + LEJP_REJECT_IDLE_NO_BRACE = -2, + LEJP_REJECT_MEMBERS_NO_CLOSE = -3, + LEJP_REJECT_MP_NO_OPEN_QUOTE = -4, + LEJP_REJECT_MP_STRING_UNDERRUN = -5, + LEJP_REJECT_MP_ILLEGAL_CTRL = -6, + LEJP_REJECT_MP_STRING_ESC_ILLEGAL_ESC = -7, + LEJP_REJECT_ILLEGAL_HEX = -8, + LEJP_REJECT_MP_DELIM_MISSING_COLON = -9, + LEJP_REJECT_MP_DELIM_BAD_VALUE_START = -10, + LEJP_REJECT_MP_VAL_NUM_INT_NO_FRAC = -11, + LEJP_REJECT_MP_VAL_NUM_FORMAT = -12, + LEJP_REJECT_MP_VAL_NUM_EXP_BAD_EXP = -13, + LEJP_REJECT_MP_VAL_TOK_UNKNOWN = -14, + LEJP_REJECT_MP_C_OR_E_UNDERF = -15, + LEJP_REJECT_MP_C_OR_E_NOTARRAY = -16, + LEJP_REJECT_MP_ARRAY_END_MISSING = -17, + LEJP_REJECT_STACK_OVERFLOW = -18, + LEJP_REJECT_MP_DELIM_ISTACK = -19, + LEJP_REJECT_NUM_TOO_LONG = -20, + LEJP_REJECT_MP_C_OR_E_NEITHER = -21, + LEJP_REJECT_UNKNOWN = -22, + LEJP_REJECT_CALLBACK = -23 +}; + +#define LEJP_FLAG_CB_IS_VALUE 64 + +enum lejp_callbacks { + LEJPCB_CONSTRUCTED = 0, + LEJPCB_DESTRUCTED = 1, + + LEJPCB_START = 2, + LEJPCB_COMPLETE = 3, + LEJPCB_FAILED = 4, + + LEJPCB_PAIR_NAME = 5, + + LEJPCB_VAL_TRUE = LEJP_FLAG_CB_IS_VALUE | 6, + LEJPCB_VAL_FALSE = LEJP_FLAG_CB_IS_VALUE | 7, + LEJPCB_VAL_NULL = LEJP_FLAG_CB_IS_VALUE | 8, + LEJPCB_VAL_NUM_INT = LEJP_FLAG_CB_IS_VALUE | 9, + LEJPCB_VAL_NUM_FLOAT = LEJP_FLAG_CB_IS_VALUE | 10, + LEJPCB_VAL_STR_START = 11, /* notice handle separately */ + LEJPCB_VAL_STR_CHUNK = LEJP_FLAG_CB_IS_VALUE | 12, + LEJPCB_VAL_STR_END = LEJP_FLAG_CB_IS_VALUE | 13, + + LEJPCB_ARRAY_START = 14, + LEJPCB_ARRAY_END = 15, + + LEJPCB_OBJECT_START = 16, + LEJPCB_OBJECT_END = 17 +}; + +/** + * _lejp_callback() - User parser actions + * \param ctx: LEJP context + * \param reason: Callback reason + * + * Your user callback is associated with the context at construction time, + * and receives calls as the parsing progresses. + * + * All of the callbacks may be ignored and just return 0. + * + * The reasons it might get called, found in @reason, are: + * + * LEJPCB_CONSTRUCTED: The context was just constructed... you might want to + * perform one-time allocation for the life of the context. + * + * LEJPCB_DESTRUCTED: The context is being destructed... if you made any + * allocations at construction-time, you can free them now + * + * LEJPCB_START: Parsing is beginning at the first byte of input + * + * LEJPCB_COMPLETE: Parsing has completed successfully. You'll get a 0 or + * positive return code from lejp_parse indicating the + * amount of unused bytes left in the input buffer + * + * LEJPCB_FAILED: Parsing failed. You'll get a negative error code + * returned from lejp_parse + * + * LEJPCB_PAIR_NAME: When a "name":"value" pair has had the name parsed, + * this callback occurs. You can find the new name at + * the end of ctx->path[] + * + * LEJPCB_VAL_TRUE: The "true" value appeared + * + * LEJPCB_VAL_FALSE: The "false" value appeared + * + * LEJPCB_VAL_NULL: The "null" value appeared + * + * LEJPCB_VAL_NUM_INT: A string representing an integer is in ctx->buf + * + * LEJPCB_VAL_NUM_FLOAT: A string representing a float is in ctx->buf + * + * LEJPCB_VAL_STR_START: We are starting to parse a string, no data yet + * + * LEJPCB_VAL_STR_CHUNK: We parsed LEJP_STRING_CHUNK -1 bytes of string data in + * ctx->buf, which is as much as we can buffer, so we are + * spilling it. If all your strings are less than + * LEJP_STRING_CHUNK - 1 bytes, you will never see this + * callback. + * + * LEJPCB_VAL_STR_END: String parsing has completed, the last chunk of the + * string is in ctx->buf. + * + * LEJPCB_ARRAY_START: An array started + * + * LEJPCB_ARRAY_END: An array ended + * + * LEJPCB_OBJECT_START: An object started + * + * LEJPCB_OBJECT_END: An object ended + */ +LWS_EXTERN signed char _lejp_callback(struct lejp_ctx *ctx, char reason); + +typedef signed char (*lejp_callback)(struct lejp_ctx *ctx, char reason); + +#ifndef LEJP_MAX_DEPTH +#define LEJP_MAX_DEPTH 12 +#endif +#ifndef LEJP_MAX_INDEX_DEPTH +#define LEJP_MAX_INDEX_DEPTH 5 +#endif +#ifndef LEJP_MAX_PATH +#define LEJP_MAX_PATH 128 +#endif +#ifndef LEJP_STRING_CHUNK +/* must be >= 30 to assemble floats */ +#define LEJP_STRING_CHUNK 255 +#endif + +enum num_flags { + LEJP_SEEN_MINUS = (1 << 0), + LEJP_SEEN_POINT = (1 << 1), + LEJP_SEEN_POST_POINT = (1 << 2), + LEJP_SEEN_EXP = (1 << 3) +}; + +struct _lejp_stack { + char s; /* lejp_state stack*/ + char p; /* path length */ + char i; /* index array length */ + char b; /* user bitfield */ +}; + +struct lejp_ctx { + + /* sorted by type for most compact alignment + * + * pointers + */ + + signed char (*callback)(struct lejp_ctx *ctx, char reason); + void *user; + const char * const *paths; + + /* arrays */ + + struct _lejp_stack st[LEJP_MAX_DEPTH]; + unsigned short i[LEJP_MAX_INDEX_DEPTH]; /* index array */ + unsigned short wild[LEJP_MAX_INDEX_DEPTH]; /* index array */ + char path[LEJP_MAX_PATH]; + char buf[LEJP_STRING_CHUNK]; + + /* int */ + + unsigned int line; + + /* short */ + + unsigned short uni; + + /* char */ + + unsigned char npos; + unsigned char dcount; + unsigned char f; + unsigned char sp; /* stack head */ + unsigned char ipos; /* index stack depth */ + unsigned char ppos; + unsigned char count_paths; + unsigned char path_match; + unsigned char path_match_len; + unsigned char wildcount; +}; + +LWS_VISIBLE LWS_EXTERN void +lejp_construct(struct lejp_ctx *ctx, + signed char (*callback)(struct lejp_ctx *ctx, char reason), + void *user, const char * const *paths, unsigned char paths_count); + +LWS_VISIBLE LWS_EXTERN void +lejp_destruct(struct lejp_ctx *ctx); + +LWS_VISIBLE LWS_EXTERN int +lejp_parse(struct lejp_ctx *ctx, const unsigned char *json, int len); + +LWS_VISIBLE LWS_EXTERN void +lejp_change_callback(struct lejp_ctx *ctx, + signed char (*callback)(struct lejp_ctx *ctx, char reason)); + +LWS_VISIBLE LWS_EXTERN int +lejp_get_wildcard(struct lejp_ctx *ctx, int wildcard, char *dest, int len); diff --git a/thirdparty/lws/misc/sha-1.c b/thirdparty/lws/misc/sha-1.c new file mode 100644 index 0000000000..9353fbefe4 --- /dev/null +++ b/thirdparty/lws/misc/sha-1.c @@ -0,0 +1,300 @@ +/* + * Copyright (C) 1995, 1996, 1997, and 1998 WIDE Project. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. Neither the name of the project nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE PROJECT AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE PROJECT OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +/* + * FIPS pub 180-1: Secure Hash Algorithm (SHA-1) + * based on: http://csrc.nist.gov/fips/fip180-1.txt + * implemented by Jun-ichiro itojun Itoh <itojun@itojun.org> + */ + +#include "private-libwebsockets.h" + +#ifdef LWS_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +struct sha1_ctxt { + union { + unsigned char b8[20]; + unsigned int b32[5]; + } h; + union { + unsigned char b8[8]; + u_int64_t b64[1]; + } c; + union { + unsigned char b8[64]; + unsigned int b32[16]; + } m; + unsigned char count; +}; + +/* sanity check */ +#if !defined(BYTE_ORDER) || !defined(LITTLE_ENDIAN) || !defined(BIG_ENDIAN) +# define unsupported 1 +#elif BYTE_ORDER != BIG_ENDIAN +# if BYTE_ORDER != LITTLE_ENDIAN +# define unsupported 1 +# endif +#endif + +#ifndef unsupported + +/* constant table */ +static const unsigned int _K[] = + { 0x5a827999, 0x6ed9eba1, 0x8f1bbcdc, 0xca62c1d6 }; +#define K(t) _K[(t) / 20] + +#define F0(b, c, d) (((b) & (c)) | ((~(b)) & (d))) +#define F1(b, c, d) (((b) ^ (c)) ^ (d)) +#define F2(b, c, d) (((b) & (c)) | ((b) & (d)) | ((c) & (d))) +#define F3(b, c, d) (((b) ^ (c)) ^ (d)) + +#define S(n, x) (((x) << (n)) | ((x) >> (32 - n))) + +#define H(n) (ctxt->h.b32[(n)]) +#define COUNT (ctxt->count) +#define BCOUNT (ctxt->c.b64[0] / 8) +#define W(n) (ctxt->m.b32[(n)]) + +#define PUTBYTE(x) { \ + ctxt->m.b8[(COUNT % 64)] = (x); \ + COUNT++; \ + COUNT %= 64; \ + ctxt->c.b64[0] += 8; \ + if (COUNT % 64 == 0) \ + sha1_step(ctxt); \ + } + +#define PUTPAD(x) { \ + ctxt->m.b8[(COUNT % 64)] = (x); \ + COUNT++; \ + COUNT %= 64; \ + if (COUNT % 64 == 0) \ + sha1_step(ctxt); \ + } + + +static void +sha1_step(struct sha1_ctxt *ctxt) +{ + unsigned int a, b, c, d, e, tmp; + size_t t, s; + +#if BYTE_ORDER == LITTLE_ENDIAN + struct sha1_ctxt tctxt; + + memcpy(&tctxt.m.b8[0], &ctxt->m.b8[0], 64); + ctxt->m.b8[0] = tctxt.m.b8[3]; ctxt->m.b8[1] = tctxt.m.b8[2]; + ctxt->m.b8[2] = tctxt.m.b8[1]; ctxt->m.b8[3] = tctxt.m.b8[0]; + ctxt->m.b8[4] = tctxt.m.b8[7]; ctxt->m.b8[5] = tctxt.m.b8[6]; + ctxt->m.b8[6] = tctxt.m.b8[5]; ctxt->m.b8[7] = tctxt.m.b8[4]; + ctxt->m.b8[8] = tctxt.m.b8[11]; ctxt->m.b8[9] = tctxt.m.b8[10]; + ctxt->m.b8[10] = tctxt.m.b8[9]; ctxt->m.b8[11] = tctxt.m.b8[8]; + ctxt->m.b8[12] = tctxt.m.b8[15]; ctxt->m.b8[13] = tctxt.m.b8[14]; + ctxt->m.b8[14] = tctxt.m.b8[13]; ctxt->m.b8[15] = tctxt.m.b8[12]; + ctxt->m.b8[16] = tctxt.m.b8[19]; ctxt->m.b8[17] = tctxt.m.b8[18]; + ctxt->m.b8[18] = tctxt.m.b8[17]; ctxt->m.b8[19] = tctxt.m.b8[16]; + ctxt->m.b8[20] = tctxt.m.b8[23]; ctxt->m.b8[21] = tctxt.m.b8[22]; + ctxt->m.b8[22] = tctxt.m.b8[21]; ctxt->m.b8[23] = tctxt.m.b8[20]; + ctxt->m.b8[24] = tctxt.m.b8[27]; ctxt->m.b8[25] = tctxt.m.b8[26]; + ctxt->m.b8[26] = tctxt.m.b8[25]; ctxt->m.b8[27] = tctxt.m.b8[24]; + ctxt->m.b8[28] = tctxt.m.b8[31]; ctxt->m.b8[29] = tctxt.m.b8[30]; + ctxt->m.b8[30] = tctxt.m.b8[29]; ctxt->m.b8[31] = tctxt.m.b8[28]; + ctxt->m.b8[32] = tctxt.m.b8[35]; ctxt->m.b8[33] = tctxt.m.b8[34]; + ctxt->m.b8[34] = tctxt.m.b8[33]; ctxt->m.b8[35] = tctxt.m.b8[32]; + ctxt->m.b8[36] = tctxt.m.b8[39]; ctxt->m.b8[37] = tctxt.m.b8[38]; + ctxt->m.b8[38] = tctxt.m.b8[37]; ctxt->m.b8[39] = tctxt.m.b8[36]; + ctxt->m.b8[40] = tctxt.m.b8[43]; ctxt->m.b8[41] = tctxt.m.b8[42]; + ctxt->m.b8[42] = tctxt.m.b8[41]; ctxt->m.b8[43] = tctxt.m.b8[40]; + ctxt->m.b8[44] = tctxt.m.b8[47]; ctxt->m.b8[45] = tctxt.m.b8[46]; + ctxt->m.b8[46] = tctxt.m.b8[45]; ctxt->m.b8[47] = tctxt.m.b8[44]; + ctxt->m.b8[48] = tctxt.m.b8[51]; ctxt->m.b8[49] = tctxt.m.b8[50]; + ctxt->m.b8[50] = tctxt.m.b8[49]; ctxt->m.b8[51] = tctxt.m.b8[48]; + ctxt->m.b8[52] = tctxt.m.b8[55]; ctxt->m.b8[53] = tctxt.m.b8[54]; + ctxt->m.b8[54] = tctxt.m.b8[53]; ctxt->m.b8[55] = tctxt.m.b8[52]; + ctxt->m.b8[56] = tctxt.m.b8[59]; ctxt->m.b8[57] = tctxt.m.b8[58]; + ctxt->m.b8[58] = tctxt.m.b8[57]; ctxt->m.b8[59] = tctxt.m.b8[56]; + ctxt->m.b8[60] = tctxt.m.b8[63]; ctxt->m.b8[61] = tctxt.m.b8[62]; + ctxt->m.b8[62] = tctxt.m.b8[61]; ctxt->m.b8[63] = tctxt.m.b8[60]; +#endif + + a = H(0); b = H(1); c = H(2); d = H(3); e = H(4); + + for (t = 0; t < 20; t++) { + s = t & 0x0f; + if (t >= 16) + W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ + W((s+2) & 0x0f) ^ W(s)); + + tmp = S(5, a) + F0(b, c, d) + e + W(s) + K(t); + e = d; d = c; c = S(30, b); b = a; a = tmp; + } + for (t = 20; t < 40; t++) { + s = t & 0x0f; + W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ + W((s+2) & 0x0f) ^ W(s)); + tmp = S(5, a) + F1(b, c, d) + e + W(s) + K(t); + e = d; d = c; c = S(30, b); b = a; a = tmp; + } + for (t = 40; t < 60; t++) { + s = t & 0x0f; + W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ + W((s+2) & 0x0f) ^ W(s)); + tmp = S(5, a) + F2(b, c, d) + e + W(s) + K(t); + e = d; d = c; c = S(30, b); b = a; a = tmp; + } + for (t = 60; t < 80; t++) { + s = t & 0x0f; + W(s) = S(1, W((s+13) & 0x0f) ^ W((s+8) & 0x0f) ^ + W((s+2) & 0x0f) ^ W(s)); + tmp = S(5, a) + F3(b, c, d) + e + W(s) + K(t); + e = d; d = c; c = S(30, b); b = a; a = tmp; + } + + H(0) = H(0) + a; + H(1) = H(1) + b; + H(2) = H(2) + c; + H(3) = H(3) + d; + H(4) = H(4) + e; + + bzero(&ctxt->m.b8[0], 64); +} + +/*------------------------------------------------------------*/ + +static void +_sha1_init(struct sha1_ctxt *ctxt) +{ + bzero(ctxt, sizeof(struct sha1_ctxt)); + H(0) = 0x67452301; + H(1) = 0xefcdab89; + H(2) = 0x98badcfe; + H(3) = 0x10325476; + H(4) = 0xc3d2e1f0; +} + +void +sha1_pad(struct sha1_ctxt *ctxt) +{ + size_t padlen; /*pad length in bytes*/ + size_t padstart; + + PUTPAD(0x80); + + padstart = COUNT % 64; + padlen = 64 - padstart; + if (padlen < 8) { + bzero(&ctxt->m.b8[padstart], padlen); + COUNT += (unsigned char)padlen; + COUNT %= 64; + sha1_step(ctxt); + padstart = COUNT % 64; /* should be 0 */ + padlen = 64 - padstart; /* should be 64 */ + } + bzero(&ctxt->m.b8[padstart], padlen - 8); + COUNT += ((unsigned char)padlen - 8); + COUNT %= 64; +#if BYTE_ORDER == BIG_ENDIAN + PUTPAD(ctxt->c.b8[0]); PUTPAD(ctxt->c.b8[1]); + PUTPAD(ctxt->c.b8[2]); PUTPAD(ctxt->c.b8[3]); + PUTPAD(ctxt->c.b8[4]); PUTPAD(ctxt->c.b8[5]); + PUTPAD(ctxt->c.b8[6]); PUTPAD(ctxt->c.b8[7]); +#else + PUTPAD(ctxt->c.b8[7]); PUTPAD(ctxt->c.b8[6]); + PUTPAD(ctxt->c.b8[5]); PUTPAD(ctxt->c.b8[4]); + PUTPAD(ctxt->c.b8[3]); PUTPAD(ctxt->c.b8[2]); + PUTPAD(ctxt->c.b8[1]); PUTPAD(ctxt->c.b8[0]); +#endif +} + +void +sha1_loop(struct sha1_ctxt *ctxt, const unsigned char *input, size_t len) +{ + size_t gaplen; + size_t gapstart; + size_t off; + size_t copysiz; + + off = 0; + + while (off < len) { + gapstart = COUNT % 64; + gaplen = 64 - gapstart; + + copysiz = (gaplen < len - off) ? gaplen : len - off; + memcpy(&ctxt->m.b8[gapstart], &input[off], copysiz); + COUNT += (unsigned char)copysiz; + COUNT %= 64; + ctxt->c.b64[0] += copysiz * 8; + if (COUNT % 64 == 0) + sha1_step(ctxt); + off += copysiz; + } +} + +void +sha1_result(struct sha1_ctxt *ctxt, void *digest0) +{ + unsigned char *digest; + + digest = (unsigned char *)digest0; + sha1_pad(ctxt); +#if BYTE_ORDER == BIG_ENDIAN + memcpy(digest, &ctxt->h.b8[0], 20); +#else + digest[0] = ctxt->h.b8[3]; digest[1] = ctxt->h.b8[2]; + digest[2] = ctxt->h.b8[1]; digest[3] = ctxt->h.b8[0]; + digest[4] = ctxt->h.b8[7]; digest[5] = ctxt->h.b8[6]; + digest[6] = ctxt->h.b8[5]; digest[7] = ctxt->h.b8[4]; + digest[8] = ctxt->h.b8[11]; digest[9] = ctxt->h.b8[10]; + digest[10] = ctxt->h.b8[9]; digest[11] = ctxt->h.b8[8]; + digest[12] = ctxt->h.b8[15]; digest[13] = ctxt->h.b8[14]; + digest[14] = ctxt->h.b8[13]; digest[15] = ctxt->h.b8[12]; + digest[16] = ctxt->h.b8[19]; digest[17] = ctxt->h.b8[18]; + digest[18] = ctxt->h.b8[17]; digest[19] = ctxt->h.b8[16]; +#endif +} + +/* + * This should look and work like the libcrypto implementation + */ + +LWS_VISIBLE unsigned char * +lws_SHA1(const unsigned char *d, size_t n, unsigned char *md) +{ + struct sha1_ctxt ctx; + + _sha1_init(&ctx); + sha1_loop(&ctx, d, n); + sha1_result(&ctx, (void *)md); + + return md; +} + +#endif /*unsupported*/ diff --git a/thirdparty/lws/output.c b/thirdparty/lws/output.c new file mode 100644 index 0000000000..ed4752490e --- /dev/null +++ b/thirdparty/lws/output.c @@ -0,0 +1,877 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +static int +lws_0405_frame_mask_generate(struct lws *wsi) +{ +#if 0 + wsi->u.ws.mask[0] = 0; + wsi->u.ws.mask[1] = 0; + wsi->u.ws.mask[2] = 0; + wsi->u.ws.mask[3] = 0; +#else + int n; + /* fetch the per-frame nonce */ + + n = lws_get_random(lws_get_context(wsi), wsi->u.ws.mask, 4); + if (n != 4) { + lwsl_parser("Unable to read from random device %s %d\n", + SYSTEM_RANDOM_FILEPATH, n); + return 1; + } +#endif + /* start masking from first byte of masking key buffer */ + wsi->u.ws.mask_idx = 0; + + return 0; +} + +/* + * notice this returns number of bytes consumed, or -1 + */ +int lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len) +{ + struct lws_context *context = lws_get_context(wsi); + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + size_t real_len = len; + unsigned int n; + int m; + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_WRITE, 1); + + if (!len) + return 0; + /* just ignore sends after we cleared the truncation buffer */ + if (wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE && + !wsi->trunc_len) + return len; + + if (wsi->trunc_len && (buf < wsi->trunc_alloc || + buf > (wsi->trunc_alloc + wsi->trunc_len + wsi->trunc_offset))) { + char dump[20]; + strncpy(dump, (char *)buf, sizeof(dump) - 1); + dump[sizeof(dump) - 1] = '\0'; +#if defined(LWS_WITH_ESP8266) + lwsl_err("****** %p: Sending new %lu (%s), pending truncated ...\n", + wsi, (unsigned long)len, dump); +#else + lwsl_err("****** %p: Sending new %lu (%s), pending truncated ...\n" + " It's illegal to do an lws_write outside of\n" + " the writable callback: fix your code\n", + wsi, (unsigned long)len, dump); +#endif + assert(0); + + return -1; + } + + m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_DO_SEND, &buf, len); + if (m < 0) + return -1; + if (m) /* handled */ { + n = m; + goto handle_truncated_send; + } + + if (!wsi->http2_substream && !lws_socket_is_valid(wsi->desc.sockfd)) + lwsl_warn("** error invalid sock but expected to send\n"); + + /* limit sending */ + if (wsi->protocol->tx_packet_size) + n = wsi->protocol->tx_packet_size; + else { + n = wsi->protocol->rx_buffer_size; + if (!n) + n = context->pt_serv_buf_size; + } + n += LWS_PRE + 4; + if (n > len) + n = len; +#if defined(LWS_WITH_ESP8266) + if (wsi->pending_send_completion) { + n = 0; + goto handle_truncated_send; + } +#endif + + /* nope, send it on the socket directly */ + lws_latency_pre(context, wsi); + n = lws_ssl_capable_write(wsi, buf, n); + lws_latency(context, wsi, "send lws_issue_raw", n, n == len); + + switch (n) { + case LWS_SSL_CAPABLE_ERROR: + /* we're going to close, let close know sends aren't possible */ + wsi->socket_is_permanently_unusable = 1; + return -1; + case LWS_SSL_CAPABLE_MORE_SERVICE: + /* nothing got sent, not fatal, retry the whole thing later */ + n = 0; + break; + } + +handle_truncated_send: + /* + * we were already handling a truncated send? + */ + if (wsi->trunc_len) { + lwsl_info("%p partial adv %d (vs %ld)\n", wsi, n, (long)real_len); + wsi->trunc_offset += n; + wsi->trunc_len -= n; + + if (!wsi->trunc_len) { + lwsl_info("***** %p partial send completed\n", wsi); + /* done with it, but don't free it */ + n = real_len; + if (wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) { + lwsl_info("***** %p signalling to close now\n", wsi); + return -1; /* retry closing now */ + } + } + /* always callback on writeable */ + lws_callback_on_writable(wsi); + + return n; + } + + if ((unsigned int)n == real_len) + /* what we just sent went out cleanly */ + return n; + + /* + * Newly truncated send. Buffer the remainder (it will get + * first priority next time the socket is writable) + */ + lwsl_debug("%p new partial sent %d from %lu total\n", wsi, n, + (unsigned long)real_len); + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITE_PARTIALS, 1); + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_PARTIALS_ACCEPTED_PARTS, n); + + /* + * - if we still have a suitable malloc lying around, use it + * - or, if too small, reallocate it + * - or, if no buffer, create it + */ + if (!wsi->trunc_alloc || real_len - n > wsi->trunc_alloc_len) { + lws_free(wsi->trunc_alloc); + + wsi->trunc_alloc_len = real_len - n; + wsi->trunc_alloc = lws_malloc(real_len - n, "truncated send alloc"); + if (!wsi->trunc_alloc) { + lwsl_err("truncated send: unable to malloc %lu\n", + (unsigned long)(real_len - n)); + return -1; + } + } + wsi->trunc_offset = 0; + wsi->trunc_len = real_len - n; + memcpy(wsi->trunc_alloc, buf + n, real_len - n); + + /* since something buffered, force it to get another chance to send */ + lws_callback_on_writable(wsi); + + return real_len; +} + +LWS_VISIBLE int lws_write(struct lws *wsi, unsigned char *buf, size_t len, + enum lws_write_protocol wp) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + int masked7 = (wsi->mode == LWSCM_WS_CLIENT); + unsigned char is_masked_bit = 0; + unsigned char *dropmask = NULL; + struct lws_tokens eff_buf; + size_t orig_len = len; + int pre = 0, n; + + if (wsi->parent_carries_io) { + struct lws_write_passthru pas; + + pas.buf = buf; + pas.len = len; + pas.wp = wp; + pas.wsi = wsi; + + if (wsi->parent->protocol->callback(wsi->parent, + LWS_CALLBACK_CHILD_WRITE_VIA_PARENT, + wsi->parent->user_space, + (void *)&pas, 0)) + return 1; + + return len; + } + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_API_LWS_WRITE, 1); + + if ((int)len < 0) { + lwsl_err("%s: suspicious len int %d, ulong %lu\n", __func__, + (int)len, (unsigned long)len); + return -1; + } + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_B_WRITE, len); + +#ifdef LWS_WITH_ACCESS_LOG + wsi->access_log.sent += len; +#endif + if (wsi->vhost) + wsi->vhost->conn_stats.tx += len; + + if (wsi->state == LWSS_ESTABLISHED && wsi->u.ws.tx_draining_ext) { + /* remove us from the list */ + struct lws **w = &pt->tx_draining_ext_list; + + wsi->u.ws.tx_draining_ext = 0; + /* remove us from context draining ext list */ + while (*w) { + if (*w == wsi) { + *w = wsi->u.ws.tx_draining_ext_list; + break; + } + w = &((*w)->u.ws.tx_draining_ext_list); + } + wsi->u.ws.tx_draining_ext_list = NULL; + wp = (wsi->u.ws.tx_draining_stashed_wp & 0xc0) | + LWS_WRITE_CONTINUATION; + + lwsl_ext("FORCED draining wp to 0x%02X\n", wp); + } + + lws_restart_ws_ping_pong_timer(wsi); + + if ((wp & 0x1f) == LWS_WRITE_HTTP || + (wp & 0x1f) == LWS_WRITE_HTTP_FINAL || + (wp & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION || + (wp & 0x1f) == LWS_WRITE_HTTP_HEADERS) + goto send_raw; + + /* if not in a state to send stuff, then just send nothing */ + + if (wsi->state != LWSS_ESTABLISHED && + ((wsi->state != LWSS_RETURNED_CLOSE_ALREADY && + wsi->state != LWSS_AWAITING_CLOSE_ACK) || + wp != LWS_WRITE_CLOSE)) + return 0; + + /* if we are continuing a frame that already had its header done */ + + if (wsi->u.ws.inside_frame) { + lwsl_debug("INSIDE FRAME\n"); + goto do_more_inside_frame; + } + + wsi->u.ws.clean_buffer = 1; + + /* + * give a chance to the extensions to modify payload + * the extension may decide to produce unlimited payload erratically + * (eg, compression extension), so we require only that if he produces + * something, it will be a complete fragment of the length known at + * the time (just the fragment length known), and if he has + * more we will come back next time he is writeable and allow him to + * produce more fragments until he's drained. + * + * This allows what is sent each time it is writeable to be limited to + * a size that can be sent without partial sends or blocking, allows + * interleaving of control frames and other connection service. + */ + eff_buf.token = (char *)buf; + eff_buf.token_len = len; + + switch ((int)wp) { + case LWS_WRITE_PING: + case LWS_WRITE_PONG: + case LWS_WRITE_CLOSE: + break; + default: + lwsl_debug("LWS_EXT_CB_PAYLOAD_TX\n"); + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_TX, &eff_buf, wp); + if (n < 0) + return -1; + + if (n && eff_buf.token_len) { + lwsl_debug("drain len %d\n", (int)eff_buf.token_len); + /* extension requires further draining */ + wsi->u.ws.tx_draining_ext = 1; + wsi->u.ws.tx_draining_ext_list = pt->tx_draining_ext_list; + pt->tx_draining_ext_list = wsi; + /* we must come back to do more */ + lws_callback_on_writable(wsi); + /* + * keep a copy of the write type for the overall + * action that has provoked generation of these + * fragments, so the last guy can use its FIN state. + */ + wsi->u.ws.tx_draining_stashed_wp = wp; + /* this is definitely not actually the last fragment + * because the extension asserted he has more coming + * So make sure this intermediate one doesn't go out + * with a FIN. + */ + wp |= LWS_WRITE_NO_FIN; + } + + if (eff_buf.token_len && wsi->u.ws.stashed_write_pending) { + wsi->u.ws.stashed_write_pending = 0; + wp = (wp &0xc0) | (int)wsi->u.ws.stashed_write_type; + } + } + + /* + * an extension did something we need to keep... for example, if + * compression extension, it has already updated its state according + * to this being issued + */ + if ((char *)buf != eff_buf.token) { + /* + * ext might eat it, but not have anything to issue yet. + * In that case we have to follow his lead, but stash and + * replace the write type that was lost here the first time. + */ + if (len && !eff_buf.token_len) { + if (!wsi->u.ws.stashed_write_pending) + wsi->u.ws.stashed_write_type = (char)wp & 0x3f; + wsi->u.ws.stashed_write_pending = 1; + return len; + } + /* + * extension recreated it: + * need to buffer this if not all sent + */ + wsi->u.ws.clean_buffer = 0; + } + + buf = (unsigned char *)eff_buf.token; + len = eff_buf.token_len; + + if (!buf) { + lwsl_err("null buf (%d)\n", (int)len); + return -1; + } + + switch (wsi->ietf_spec_revision) { + case 13: + if (masked7) { + pre += 4; + dropmask = &buf[0 - pre]; + is_masked_bit = 0x80; + } + + switch (wp & 0xf) { + case LWS_WRITE_TEXT: + n = LWSWSOPC_TEXT_FRAME; + break; + case LWS_WRITE_BINARY: + n = LWSWSOPC_BINARY_FRAME; + break; + case LWS_WRITE_CONTINUATION: + n = LWSWSOPC_CONTINUATION; + break; + + case LWS_WRITE_CLOSE: + n = LWSWSOPC_CLOSE; + break; + case LWS_WRITE_PING: + n = LWSWSOPC_PING; + break; + case LWS_WRITE_PONG: + n = LWSWSOPC_PONG; + break; + default: + lwsl_warn("lws_write: unknown write opc / wp\n"); + return -1; + } + + if (!(wp & LWS_WRITE_NO_FIN)) + n |= 1 << 7; + + if (len < 126) { + pre += 2; + buf[-pre] = n; + buf[-pre + 1] = (unsigned char)(len | is_masked_bit); + } else { + if (len < 65536) { + pre += 4; + buf[-pre] = n; + buf[-pre + 1] = 126 | is_masked_bit; + buf[-pre + 2] = (unsigned char)(len >> 8); + buf[-pre + 3] = (unsigned char)len; + } else { + pre += 10; + buf[-pre] = n; + buf[-pre + 1] = 127 | is_masked_bit; +#if defined __LP64__ + buf[-pre + 2] = (len >> 56) & 0x7f; + buf[-pre + 3] = len >> 48; + buf[-pre + 4] = len >> 40; + buf[-pre + 5] = len >> 32; +#else + buf[-pre + 2] = 0; + buf[-pre + 3] = 0; + buf[-pre + 4] = 0; + buf[-pre + 5] = 0; +#endif + buf[-pre + 6] = (unsigned char)(len >> 24); + buf[-pre + 7] = (unsigned char)(len >> 16); + buf[-pre + 8] = (unsigned char)(len >> 8); + buf[-pre + 9] = (unsigned char)len; + } + } + break; + } + +do_more_inside_frame: + + /* + * Deal with masking if we are in client -> server direction and + * the wp demands it + */ + + if (masked7) { + if (!wsi->u.ws.inside_frame) + if (lws_0405_frame_mask_generate(wsi)) { + lwsl_err("frame mask generation failed\n"); + return -1; + } + + /* + * in v7, just mask the payload + */ + if (dropmask) { /* never set if already inside frame */ + for (n = 4; n < (int)len + 4; n++) + dropmask[n] = dropmask[n] ^ wsi->u.ws.mask[ + (wsi->u.ws.mask_idx++) & 3]; + + /* copy the frame nonce into place */ + memcpy(dropmask, wsi->u.ws.mask, 4); + } + } + +send_raw: + switch ((int)(wp & 0x1f)) { + case LWS_WRITE_CLOSE: +/* lwsl_hexdump(&buf[-pre], len); */ + case LWS_WRITE_HTTP: + case LWS_WRITE_HTTP_FINAL: + case LWS_WRITE_HTTP_HEADERS: + case LWS_WRITE_HTTP_HEADERS_CONTINUATION: + case LWS_WRITE_PONG: + case LWS_WRITE_PING: +#ifdef LWS_WITH_HTTP2 + if (wsi->mode == LWSCM_HTTP2_SERVING) { + unsigned char flags = 0; + + n = LWS_H2_FRAME_TYPE_DATA; + if ((wp & 0x1f) == LWS_WRITE_HTTP_HEADERS) { + n = LWS_H2_FRAME_TYPE_HEADERS; + if (!(wp & LWS_WRITE_NO_FIN)) + flags = LWS_H2_FLAG_END_HEADERS; + if (wsi->u.h2.send_END_STREAM || (wp & LWS_WRITE_H2_STREAM_END)) { + flags |= LWS_H2_FLAG_END_STREAM; + wsi->u.h2.send_END_STREAM = 1; + } + } + + if ((wp & 0x1f) == LWS_WRITE_HTTP_HEADERS_CONTINUATION) { + n = LWS_H2_FRAME_TYPE_CONTINUATION; + if (!(wp & LWS_WRITE_NO_FIN)) + flags = LWS_H2_FLAG_END_HEADERS; + if (wsi->u.h2.send_END_STREAM || (wp & LWS_WRITE_H2_STREAM_END)) { + flags |= LWS_H2_FLAG_END_STREAM; + wsi->u.h2.send_END_STREAM = 1; + } + } + + if (((wp & 0x1f) == LWS_WRITE_HTTP || + (wp & 0x1f) == LWS_WRITE_HTTP_FINAL) && + wsi->u.http.tx_content_length) { + wsi->u.http.tx_content_remain -= len; + lwsl_info("%s: content_remain = %llu\n", __func__, + (unsigned long long)wsi->u.http.tx_content_remain); + if (!wsi->u.http.tx_content_remain) { + lwsl_info("%s: selecting final write mode\n", __func__); + wp = LWS_WRITE_HTTP_FINAL; + } + } + + if ((wp & 0x1f) == LWS_WRITE_HTTP_FINAL || (wp & LWS_WRITE_H2_STREAM_END)) { + //lws_get_network_wsi(wsi)->u.h2.END_STREAM) { + lwsl_info("%s: setting END_STREAM\n", __func__); + flags |= LWS_H2_FLAG_END_STREAM; + wsi->u.h2.send_END_STREAM = 1; + } + + return lws_h2_frame_write(wsi, n, flags, + wsi->u.h2.my_sid, len, buf); + } +#endif + return lws_issue_raw(wsi, (unsigned char *)buf - pre, len + pre); + default: + break; + } + + /* + * give any active extensions a chance to munge the buffer + * before send. We pass in a pointer to an lws_tokens struct + * prepared with the default buffer and content length that's in + * there. Rather than rewrite the default buffer, extensions + * that expect to grow the buffer can adapt .token to + * point to their own per-connection buffer in the extension + * user allocation. By default with no extensions or no + * extension callback handling, just the normal input buffer is + * used then so it is efficient. + * + * callback returns 1 in case it wants to spill more buffers + * + * This takes care of holding the buffer if send is incomplete, ie, + * if wsi->u.ws.clean_buffer is 0 (meaning an extension meddled with + * the buffer). If wsi->u.ws.clean_buffer is 1, it will instead + * return to the user code how much OF THE USER BUFFER was consumed. + */ + + n = lws_issue_raw_ext_access(wsi, buf - pre, len + pre); + wsi->u.ws.inside_frame = 1; + if (n <= 0) + return n; + + if (n == (int)len + pre) { + /* everything in the buffer was handled (or rebuffered...) */ + wsi->u.ws.inside_frame = 0; + return orig_len; + } + + /* + * it is how many bytes of user buffer got sent... may be < orig_len + * in which case callback when writable has already been arranged + * and user code can call lws_write() again with the rest + * later. + */ + + return n - pre; +} + +LWS_VISIBLE int lws_serve_http_file_fragment(struct lws *wsi) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws_process_html_args args; + lws_filepos_t amount, poss; + unsigned char *p, *pstart; +#if defined(LWS_WITH_RANGES) + unsigned char finished = 0; +#endif + int n, m; + + lwsl_debug("wsi->http2_substream %d\n", wsi->http2_substream); + + while (!lws_send_pipe_choked(wsi)) { + + if (wsi->trunc_len) { + if (lws_issue_raw(wsi, wsi->trunc_alloc + + wsi->trunc_offset, + wsi->trunc_len) < 0) { + lwsl_info("%s: closing\n", __func__); + goto file_had_it; + } + continue; + } + + if (wsi->u.http.filepos == wsi->u.http.filelen) + goto all_sent; + + n = 0; + + pstart = pt->serv_buf + LWS_H2_FRAME_HEADER_LENGTH; + + p = pstart; + +#if defined(LWS_WITH_RANGES) + if (wsi->u.http.range.count_ranges && !wsi->u.http.range.inside) { + + lwsl_notice("%s: doing range start %llu\n", __func__, wsi->u.http.range.start); + + if ((long long)lws_vfs_file_seek_cur(wsi->u.http.fop_fd, + wsi->u.http.range.start - + wsi->u.http.filepos) < 0) + goto file_had_it; + + wsi->u.http.filepos = wsi->u.http.range.start; + + if (wsi->u.http.range.count_ranges > 1) { + n = lws_snprintf((char *)p, context->pt_serv_buf_size - LWS_H2_FRAME_HEADER_LENGTH, + "_lws\x0d\x0a" + "Content-Type: %s\x0d\x0a" + "Content-Range: bytes %llu-%llu/%llu\x0d\x0a" + "\x0d\x0a", + wsi->u.http.multipart_content_type, + wsi->u.http.range.start, + wsi->u.http.range.end, + wsi->u.http.range.extent); + p += n; + } + + wsi->u.http.range.budget = wsi->u.http.range.end - + wsi->u.http.range.start + 1; + wsi->u.http.range.inside = 1; + } +#endif + + poss = context->pt_serv_buf_size - n - LWS_H2_FRAME_HEADER_LENGTH; + + /* + * if there is a hint about how much we will do well to send at one time, + * restrict ourselves to only trying to send that. + */ + if (wsi->protocol->tx_packet_size && + poss > wsi->protocol->tx_packet_size) + poss = wsi->protocol->tx_packet_size; + +#if defined(LWS_WITH_HTTP2) + m = lws_h2_tx_cr_get(wsi); + if (!m) { + lwsl_info("%s: came here with no tx credit", __func__); + return 0; + } + if (m < poss) + poss = m; + /* + * consumption of the actual payload amount sent will be handled + * when the http2 data frame is sent + */ +#endif + +#if defined(LWS_WITH_RANGES) + if (wsi->u.http.range.count_ranges) { + if (wsi->u.http.range.count_ranges > 1) + poss -= 7; /* allow for final boundary */ + if (poss > wsi->u.http.range.budget) + poss = wsi->u.http.range.budget; + } +#endif + if (wsi->sending_chunked) { + /* we need to drop the chunk size in here */ + p += 10; + /* allow for the chunk to grow by 128 in translation */ + poss -= 10 + 128; + } + + if (lws_vfs_file_read(wsi->u.http.fop_fd, &amount, p, poss) < 0) + goto file_had_it; /* caller will close */ + + if (wsi->sending_chunked) + n = (int)amount; + else + n = (p - pstart) + (int)amount; + + lwsl_debug("%s: sending %d\n", __func__, n); + + if (n) { + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, + context->timeout_secs); + + if (wsi->sending_chunked) { + args.p = (char *)p; + args.len = n; + args.max_len = (unsigned int)poss + 128; + args.final = wsi->u.http.filepos + n == + wsi->u.http.filelen; + if (user_callback_handle_rxflow( + wsi->vhost->protocols[(int)wsi->protocol_interpret_idx].callback, wsi, + LWS_CALLBACK_PROCESS_HTML, + wsi->user_space, &args, 0) < 0) + goto file_had_it; + n = args.len; + p = (unsigned char *)args.p; + } else + p = pstart; + +#if defined(LWS_WITH_RANGES) + if (wsi->u.http.range.send_ctr + 1 == + wsi->u.http.range.count_ranges && // last range + wsi->u.http.range.count_ranges > 1 && // was 2+ ranges (ie, multipart) + wsi->u.http.range.budget - amount == 0) {// final part + n += lws_snprintf((char *)pstart + n, 6, + "_lws\x0d\x0a"); // append trailing boundary + lwsl_debug("added trailing boundary\n"); + } +#endif + m = lws_write(wsi, p, n, + wsi->u.http.filepos == wsi->u.http.filelen ? + LWS_WRITE_HTTP_FINAL : + LWS_WRITE_HTTP + ); + if (m < 0) + goto file_had_it; + + wsi->u.http.filepos += amount; + +#if defined(LWS_WITH_RANGES) + if (wsi->u.http.range.count_ranges >= 1) { + wsi->u.http.range.budget -= amount; + if (wsi->u.http.range.budget == 0) { + lwsl_notice("range budget exhausted\n"); + wsi->u.http.range.inside = 0; + wsi->u.http.range.send_ctr++; + + if (lws_ranges_next(&wsi->u.http.range) < 1) { + finished = 1; + goto all_sent; + } + } + } +#endif + + if (m != n) { + /* adjust for what was not sent */ + if (lws_vfs_file_seek_cur(wsi->u.http.fop_fd, + m - n) == + (unsigned long)-1) + goto file_had_it; + } + } + +all_sent: + if ((!wsi->trunc_len && wsi->u.http.filepos >= wsi->u.http.filelen) +#if defined(LWS_WITH_RANGES) + || finished) +#else + ) +#endif + { + wsi->state = LWSS_HTTP; + /* we might be in keepalive, so close it off here */ + lws_vfs_file_close(&wsi->u.http.fop_fd); + + lwsl_debug("file completed\n"); + + if (wsi->protocol->callback && + user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_HTTP_FILE_COMPLETION, + wsi->user_space, NULL, + 0) < 0) { + /* + * For http/1.x, the choices from + * transaction_completed are either + * 0 to use the connection for pipelined + * or nonzero to hang it up. + * + * However for http/2. while we are + * still interested in hanging up the + * nwsi if there was a network-level + * fatal error, simply completing the + * transaction is a matter of the stream + * state, not the root connection at the + * network level + */ + if (wsi->http2_substream) + return 1; + else + return -1; + } + + return 1; /* >0 indicates completed */ + } + } + + lws_callback_on_writable(wsi); + + return 0; /* indicates further processing must be done */ + +file_had_it: + lws_vfs_file_close(&wsi->u.http.fop_fd); + + return -1; +} + +#if LWS_POSIX +LWS_VISIBLE int +lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + int n; + + lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1); + + n = recv(wsi->desc.sockfd, (char *)buf, len, 0); + if (n >= 0) { + if (wsi->vhost) + wsi->vhost->conn_stats.rx += n; + lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n); + lws_restart_ws_ping_pong_timer(wsi); + return n; + } +#if LWS_POSIX + if (LWS_ERRNO == LWS_EAGAIN || + LWS_ERRNO == LWS_EWOULDBLOCK || + LWS_ERRNO == LWS_EINTR) + return LWS_SSL_CAPABLE_MORE_SERVICE; +#endif + lwsl_notice("error on reading from skt : %d\n", LWS_ERRNO); + return LWS_SSL_CAPABLE_ERROR; +} + +LWS_VISIBLE int +lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len) +{ + int n = 0; + +#if LWS_POSIX + n = send(wsi->desc.sockfd, (char *)buf, len, MSG_NOSIGNAL); +// lwsl_info("%s: sent len %d result %d", __func__, len, n); + if (n >= 0) + return n; + + if (LWS_ERRNO == LWS_EAGAIN || + LWS_ERRNO == LWS_EWOULDBLOCK || + LWS_ERRNO == LWS_EINTR) { + if (LWS_ERRNO == LWS_EWOULDBLOCK) { + lws_set_blocking_send(wsi); + } + + return LWS_SSL_CAPABLE_MORE_SERVICE; + } +#else + (void)n; + (void)wsi; + (void)buf; + (void)len; + // !!! +#endif + + lwsl_debug("ERROR writing len %d to skt fd %d err %d / errno %d\n", + len, wsi->desc.sockfd, n, LWS_ERRNO); + return LWS_SSL_CAPABLE_ERROR; +} +#endif +LWS_VISIBLE int +lws_ssl_pending_no_ssl(struct lws *wsi) +{ + (void)wsi; +#if defined(LWS_WITH_ESP32) + return 100; +#else + return 0; +#endif +} diff --git a/thirdparty/lws/plat/lws-plat-unix.c b/thirdparty/lws/plat/lws-plat-unix.c new file mode 100644 index 0000000000..a51e67bb81 --- /dev/null +++ b/thirdparty/lws/plat/lws-plat-unix.c @@ -0,0 +1,850 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +#include <pwd.h> +#include <grp.h> + +#ifdef LWS_WITH_PLUGINS +#include <dlfcn.h> +#endif +#include <dirent.h> + +unsigned long long time_in_microseconds(void) +{ + struct timeval tv; + + gettimeofday(&tv, NULL); + return ((unsigned long long)tv.tv_sec * 1000000LL) + tv.tv_usec; +} + +LWS_VISIBLE int +lws_get_random(struct lws_context *context, void *buf, int len) +{ + return read(context->fd_random, (char *)buf, len); +} + +LWS_VISIBLE int +lws_send_pipe_choked(struct lws *wsi) +{ + struct lws_pollfd fds; + struct lws *wsi_eff = wsi; + +#if defined(LWS_WITH_HTTP2) + wsi_eff = lws_get_network_wsi(wsi); +#endif + /* treat the fact we got a truncated send pending as if we're choked */ + if (wsi_eff->trunc_len) + return 1; + + fds.fd = wsi_eff->desc.sockfd; + fds.events = POLLOUT; + fds.revents = 0; + + if (poll(&fds, 1, 0) != 1) + return 1; + + if ((fds.revents & POLLOUT) == 0) + return 1; + + /* okay to send another packet without blocking */ + + return 0; +} + +LWS_VISIBLE int +lws_poll_listen_fd(struct lws_pollfd *fd) +{ + return poll(fd, 1, 0); +} + +LWS_VISIBLE void +lws_cancel_service_pt(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + char buf = 0; + + if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1) + lwsl_err("Cannot write to dummy pipe"); +} + +LWS_VISIBLE void +lws_cancel_service(struct lws_context *context) +{ + struct lws_context_per_thread *pt = &context->pt[0]; + char buf = 0, m = context->count_threads; + + while (m--) { + if (write(pt->dummy_pipe_fds[1], &buf, sizeof(buf)) != 1) + lwsl_err("Cannot write to dummy pipe"); + pt++; + } +} + +LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line) +{ + int syslog_level = LOG_DEBUG; + + switch (level) { + case LLL_ERR: + syslog_level = LOG_ERR; + break; + case LLL_WARN: + syslog_level = LOG_WARNING; + break; + case LLL_NOTICE: + syslog_level = LOG_NOTICE; + break; + case LLL_INFO: + syslog_level = LOG_INFO; + break; + } + syslog(syslog_level, "%s", line); +} + +LWS_VISIBLE LWS_EXTERN int +_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) +{ + struct lws_context_per_thread *pt; + int n = -1, m, c; + char buf; + + /* stay dead once we are dead */ + + if (!context || !context->vhost_list) + return 1; + + pt = &context->pt[tsi]; + + lws_stats_atomic_bump(context, pt, LWSSTATS_C_SERVICE_ENTRY, 1); + + if (timeout_ms < 0) + goto faked_service; + + lws_libev_run(context, tsi); + lws_libuv_run(context, tsi); + lws_libevent_run(context, tsi); + + if (!context->service_tid_detected) { + struct lws _lws; + + memset(&_lws, 0, sizeof(_lws)); + _lws.context = context; + + context->service_tid_detected = + context->vhost_list->protocols[0].callback( + &_lws, LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0); + context->service_tid = context->service_tid_detected; + context->service_tid_detected = 1; + } + + /* + * is there anybody with pending stuff that needs service forcing? + */ + if (!lws_service_adjust_timeout(context, 1, tsi)) { + /* -1 timeout means just do forced service */ + _lws_plat_service_tsi(context, -1, pt->tid); + /* still somebody left who wants forced service? */ + if (!lws_service_adjust_timeout(context, 1, pt->tid)) + /* yes... come back again quickly */ + timeout_ms = 0; + } + + n = poll(pt->fds, pt->fds_count, timeout_ms); + +#ifdef LWS_OPENSSL_SUPPORT + if (!n && !pt->rx_draining_ext_list && + !lws_ssl_anybody_has_buffered_read_tsi(context, tsi)) { +#else + if (!pt->rx_draining_ext_list && !n) /* poll timeout */ { +#endif + lws_service_fd_tsi(context, NULL, tsi); + return 0; + } + +faked_service: + m = lws_service_flag_pending(context, tsi); + if (m) + c = -1; /* unknown limit */ + else + if (n < 0) { + if (LWS_ERRNO != LWS_EINTR) + return -1; + return 0; + } else + c = n; + + /* any socket with events to service? */ + for (n = 0; n < pt->fds_count && c; n++) { + if (!pt->fds[n].revents) + continue; + + c--; + + if (pt->fds[n].fd == pt->dummy_pipe_fds[0]) { + if (read(pt->fds[n].fd, &buf, 1) != 1) + lwsl_err("Cannot read from dummy pipe."); + continue; + } + + m = lws_service_fd_tsi(context, &pt->fds[n], tsi); + if (m < 0) + return -1; + /* if something closed, retry this slot */ + if (m) + n--; + } + + return 0; +} + +LWS_VISIBLE int +lws_plat_check_connection_error(struct lws *wsi) +{ + return 0; +} + +LWS_VISIBLE int +lws_plat_service(struct lws_context *context, int timeout_ms) +{ + return _lws_plat_service_tsi(context, timeout_ms, 0); +} + +LWS_VISIBLE int +lws_plat_set_socket_options(struct lws_vhost *vhost, int fd) +{ + int optval = 1; + socklen_t optlen = sizeof(optval); + +#if defined(__APPLE__) || \ + defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ + defined(__NetBSD__) || \ + defined(__OpenBSD__) || \ + defined(__HAIKU__) + struct protoent *tcp_proto; +#endif + + if (vhost->ka_time) { + /* enable keepalive on this socket */ + optval = 1; + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, + (const void *)&optval, optlen) < 0) + return 1; + +#if defined(__APPLE__) || \ + defined(__FreeBSD__) || defined(__FreeBSD_kernel__) || \ + defined(__NetBSD__) || \ + defined(__CYGWIN__) || defined(__OpenBSD__) || defined (__sun) || \ + defined(__HAIKU__) + + /* + * didn't find a way to set these per-socket, need to + * tune kernel systemwide values + */ +#else + /* set the keepalive conditions we want on it too */ + optval = vhost->ka_time; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPIDLE, + (const void *)&optval, optlen) < 0) + return 1; + + optval = vhost->ka_interval; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPINTVL, + (const void *)&optval, optlen) < 0) + return 1; + + optval = vhost->ka_probes; + if (setsockopt(fd, IPPROTO_TCP, TCP_KEEPCNT, + (const void *)&optval, optlen) < 0) + return 1; +#endif + } + +#if defined(SO_BINDTODEVICE) + if (vhost->bind_iface && vhost->iface) { + lwsl_info("binding listen skt to %s using SO_BINDTODEVICE\n", vhost->iface); + if (setsockopt(fd, SOL_SOCKET, SO_BINDTODEVICE, vhost->iface, + strlen(vhost->iface)) < 0) { + lwsl_warn("Failed to bind to device %s\n", vhost->iface); + return 1; + } + } +#endif + + /* Disable Nagle */ + optval = 1; +#if defined (__sun) + if (setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0) + return 1; +#elif !defined(__APPLE__) && \ + !defined(__FreeBSD__) && !defined(__FreeBSD_kernel__) && \ + !defined(__NetBSD__) && \ + !defined(__OpenBSD__) && \ + !defined(__HAIKU__) + if (setsockopt(fd, SOL_TCP, TCP_NODELAY, (const void *)&optval, optlen) < 0) + return 1; +#else + tcp_proto = getprotobyname("TCP"); + if (setsockopt(fd, tcp_proto->p_proto, TCP_NODELAY, &optval, optlen) < 0) + return 1; +#endif + + /* We are nonblocking... */ + if (fcntl(fd, F_SETFL, O_NONBLOCK) < 0) + return 1; + + return 0; +} + +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) +static void +_lws_plat_apply_caps(int mode, cap_value_t *cv, int count) +{ + cap_t caps; + + if (!count) + return; + + caps = cap_get_proc(); + + cap_set_flag(caps, mode, count, cv, CAP_SET); + cap_set_proc(caps); + prctl(PR_SET_KEEPCAPS, 1, 0, 0, 0); + cap_free(caps); +} +#endif + +LWS_VISIBLE void +lws_plat_drop_app_privileges(struct lws_context_creation_info *info) +{ +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + int n; +#endif + + if (info->gid && info->gid != -1) + if (setgid(info->gid)) + lwsl_warn("setgid: %s\n", strerror(LWS_ERRNO)); + + if (info->uid && info->uid != -1) { + struct passwd *p = getpwuid(info->uid); + + if (p) { + +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + _lws_plat_apply_caps(CAP_PERMITTED, info->caps, info->count_caps); +#endif + + initgroups(p->pw_name, info->gid); + if (setuid(info->uid)) + lwsl_warn("setuid: %s\n", strerror(LWS_ERRNO)); + else + lwsl_notice("Set privs to user '%s'\n", p->pw_name); + +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + _lws_plat_apply_caps(CAP_EFFECTIVE, info->caps, info->count_caps); + + if (info->count_caps) + for (n = 0; n < info->count_caps; n++) + lwsl_notice(" RETAINING CAPABILITY %d\n", (int)info->caps[n]); +#endif + + } else + lwsl_warn("getpwuid: unable to find uid %d", info->uid); + } +} + +#ifdef LWS_WITH_PLUGINS + +#if defined(LWS_WITH_LIBUV) && UV_VERSION_MAJOR > 0 + +/* libuv.c implements these in a cross-platform way */ + +#else + +static int filter(const struct dirent *ent) +{ + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) + return 0; + + return 1; +} + +LWS_VISIBLE int +lws_plat_plugins_init(struct lws_context * context, const char * const *d) +{ + struct lws_plugin_capability lcaps; + struct lws_plugin *plugin; + lws_plugin_init_func initfunc; + struct dirent **namelist; + int n, i, m, ret = 0; + char path[256]; + void *l; + + lwsl_notice(" Plugins:\n"); + + while (d && *d) { + n = scandir(*d, &namelist, filter, alphasort); + if (n < 0) { + lwsl_err("Scandir on %s failed\n", *d); + return 1; + } + + for (i = 0; i < n; i++) { + if (strlen(namelist[i]->d_name) < 7) + goto inval; + + lwsl_notice(" %s\n", namelist[i]->d_name); + + lws_snprintf(path, sizeof(path) - 1, "%s/%s", *d, + namelist[i]->d_name); + l = dlopen(path, RTLD_NOW); + if (!l) { + lwsl_err("Error loading DSO: %s\n", dlerror()); + while (i++ < n) + free(namelist[i]); + goto bail; + } + /* we could open it, can we get his init function? */ + m = lws_snprintf(path, sizeof(path) - 1, "init_%s", + namelist[i]->d_name + 3 /* snip lib... */); + path[m - 3] = '\0'; /* snip the .so */ + initfunc = dlsym(l, path); + if (!initfunc) { + lwsl_err("Failed to get init on %s: %s", + namelist[i]->d_name, dlerror()); + dlclose(l); + } + lcaps.api_magic = LWS_PLUGIN_API_MAGIC; + m = initfunc(context, &lcaps); + if (m) { + lwsl_err("Initializing %s failed %d\n", + namelist[i]->d_name, m); + dlclose(l); + goto skip; + } + + plugin = lws_malloc(sizeof(*plugin), "plugin"); + if (!plugin) { + lwsl_err("OOM\n"); + goto bail; + } + plugin->list = context->plugin_list; + context->plugin_list = plugin; + strncpy(plugin->name, namelist[i]->d_name, sizeof(plugin->name) - 1); + plugin->name[sizeof(plugin->name) - 1] = '\0'; + plugin->l = l; + plugin->caps = lcaps; + context->plugin_protocol_count += lcaps.count_protocols; + context->plugin_extension_count += lcaps.count_extensions; + + free(namelist[i]); + continue; + + skip: + dlclose(l); + inval: + free(namelist[i]); + } + free(namelist); + d++; + } + +bail: + free(namelist); + + return ret; +} + +LWS_VISIBLE int +lws_plat_plugins_destroy(struct lws_context * context) +{ + struct lws_plugin *plugin = context->plugin_list, *p; + lws_plugin_destroy_func func; + char path[256]; + int m; + + if (!plugin) + return 0; + + lwsl_notice("%s\n", __func__); + + while (plugin) { + p = plugin; + m = lws_snprintf(path, sizeof(path) - 1, "destroy_%s", plugin->name + 3); + path[m - 3] = '\0'; + func = dlsym(plugin->l, path); + if (!func) { + lwsl_err("Failed to get destroy on %s: %s", + plugin->name, dlerror()); + goto next; + } + m = func(context); + if (m) + lwsl_err("Initializing %s failed %d\n", + plugin->name, m); +next: + dlclose(p->l); + plugin = p->list; + p->list = NULL; + free(p); + } + + context->plugin_list = NULL; + + return 0; +} + +#endif +#endif + + +#if 0 +static void +sigabrt_handler(int x) +{ + printf("%s\n", __func__); +} +#endif + +LWS_VISIBLE int +lws_plat_context_early_init(void) +{ +#if !defined(LWS_AVOID_SIGPIPE_IGN) + signal(SIGPIPE, SIG_IGN); +#endif + + return 0; +} + +LWS_VISIBLE void +lws_plat_context_early_destroy(struct lws_context *context) +{ +} + +LWS_VISIBLE void +lws_plat_context_late_destroy(struct lws_context *context) +{ + struct lws_context_per_thread *pt = &context->pt[0]; + int m = context->count_threads; + +#ifdef LWS_WITH_PLUGINS + if (context->plugin_list) + lws_plat_plugins_destroy(context); +#endif + + if (context->lws_lookup) + lws_free(context->lws_lookup); + + while (m--) { + if (pt->dummy_pipe_fds[0]) + close(pt->dummy_pipe_fds[0]); + if (pt->dummy_pipe_fds[1]) + close(pt->dummy_pipe_fds[1]); + pt++; + } + if (!context->fd_random) + lwsl_err("ZERO RANDOM FD\n"); + if (context->fd_random != LWS_INVALID_FILE) + close(context->fd_random); +} + +/* cast a struct sockaddr_in6 * into addr for ipv6 */ + +LWS_VISIBLE int +lws_interface_to_sa(int ipv6, const char *ifname, struct sockaddr_in *addr, + size_t addrlen) +{ + int rc = -1; + + struct ifaddrs *ifr; + struct ifaddrs *ifc; +#ifdef LWS_WITH_IPV6 + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; +#endif + + getifaddrs(&ifr); + for (ifc = ifr; ifc != NULL && rc; ifc = ifc->ifa_next) { + if (!ifc->ifa_addr) + continue; + + lwsl_info(" interface %s vs %s\n", ifc->ifa_name, ifname); + + if (strcmp(ifc->ifa_name, ifname)) + continue; + + switch (ifc->ifa_addr->sa_family) { + case AF_INET: +#ifdef LWS_WITH_IPV6 + if (ipv6) { + /* map IPv4 to IPv6 */ + bzero((char *)&addr6->sin6_addr, + sizeof(struct in6_addr)); + addr6->sin6_addr.s6_addr[10] = 0xff; + addr6->sin6_addr.s6_addr[11] = 0xff; + memcpy(&addr6->sin6_addr.s6_addr[12], + &((struct sockaddr_in *)ifc->ifa_addr)->sin_addr, + sizeof(struct in_addr)); + } else +#endif + memcpy(addr, + (struct sockaddr_in *)ifc->ifa_addr, + sizeof(struct sockaddr_in)); + break; +#ifdef LWS_WITH_IPV6 + case AF_INET6: + memcpy(&addr6->sin6_addr, + &((struct sockaddr_in6 *)ifc->ifa_addr)->sin6_addr, + sizeof(struct in6_addr)); + break; +#endif + default: + continue; + } + rc = 0; + } + + freeifaddrs(ifr); + + if (rc == -1) { + /* check if bind to IP address */ +#ifdef LWS_WITH_IPV6 + if (inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1) + rc = 0; + else +#endif + if (inet_pton(AF_INET, ifname, &addr->sin_addr) == 1) + rc = 0; + } + + return rc; +} + +LWS_VISIBLE void +lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi) +{ + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + + lws_libev_io(wsi, LWS_EV_START | LWS_EV_READ); + lws_libuv_io(wsi, LWS_EV_START | LWS_EV_READ); + lws_libevent_io(wsi, LWS_EV_START | LWS_EV_READ); + + pt->fds[pt->fds_count++].revents = 0; +} + +LWS_VISIBLE void +lws_plat_delete_socket_from_fds(struct lws_context *context, + struct lws *wsi, int m) +{ + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + + lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE); + lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE); + lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE); + + pt->fds_count--; +} + +LWS_VISIBLE void +lws_plat_service_periodic(struct lws_context *context) +{ + /* if our parent went down, don't linger around */ + if (context->started_with_parent && + kill(context->started_with_parent, 0) < 0) + kill(getpid(), SIGTERM); +} + +LWS_VISIBLE int +lws_plat_change_pollfd(struct lws_context *context, + struct lws *wsi, struct lws_pollfd *pfd) +{ + return 0; +} + +LWS_VISIBLE const char * +lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt) +{ + return inet_ntop(af, src, dst, cnt); +} + +LWS_VISIBLE int +lws_plat_inet_pton(int af, const char *src, void *dst) +{ + return inet_pton(af, src, dst); +} + +LWS_VISIBLE lws_fop_fd_t +_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename, + const char *vpath, lws_fop_flags_t *flags) +{ + struct stat stat_buf; + int ret = open(filename, (*flags) & LWS_FOP_FLAGS_MASK, 0664); + lws_fop_fd_t fop_fd; + + if (ret < 0) + return NULL; + + if (fstat(ret, &stat_buf) < 0) + goto bail; + + fop_fd = malloc(sizeof(*fop_fd)); + if (!fop_fd) + goto bail; + + fop_fd->fops = fops; + fop_fd->flags = *flags; + fop_fd->fd = ret; + fop_fd->filesystem_priv = NULL; /* we don't use it */ + fop_fd->len = stat_buf.st_size; + fop_fd->pos = 0; + + return fop_fd; + +bail: + close(ret); + return NULL; +} + +LWS_VISIBLE int +_lws_plat_file_close(lws_fop_fd_t *fop_fd) +{ + int fd = (*fop_fd)->fd; + + free(*fop_fd); + *fop_fd = NULL; + + return close(fd); +} + +LWS_VISIBLE lws_fileofs_t +_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset) +{ + lws_fileofs_t r; + + if (offset > 0 && offset > fop_fd->len - fop_fd->pos) + offset = fop_fd->len - fop_fd->pos; + + if ((lws_fileofs_t)fop_fd->pos + offset < 0) + offset = -fop_fd->pos; + + r = lseek(fop_fd->fd, offset, SEEK_CUR); + + if (r >= 0) + fop_fd->pos = r; + else + lwsl_err("error seeking from cur %ld, offset %ld\n", + (long)fop_fd->pos, (long)offset); + + return r; +} + +LWS_VISIBLE int +_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount, + uint8_t *buf, lws_filepos_t len) +{ + long n; + + n = read((int)fop_fd->fd, buf, len); + if (n == -1) { + *amount = 0; + return -1; + } + fop_fd->pos += n; + lwsl_debug("%s: read %ld of req %ld, pos %ld, len %ld\n", __func__, n, + (long)len, (long)fop_fd->pos, (long)fop_fd->len); + *amount = n; + + return 0; +} + +LWS_VISIBLE int +_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount, + uint8_t *buf, lws_filepos_t len) +{ + long n; + + n = write((int)fop_fd->fd, buf, len); + if (n == -1) { + *amount = 0; + return -1; + } + + fop_fd->pos += n; + *amount = n; + + return 0; +} + + +LWS_VISIBLE int +lws_plat_init(struct lws_context *context, + struct lws_context_creation_info *info) +{ + struct lws_context_per_thread *pt = &context->pt[0]; + int n = context->count_threads, fd; + + /* master context has the global fd lookup array */ + context->lws_lookup = lws_zalloc(sizeof(struct lws *) * + context->max_fds, "lws_lookup"); + if (context->lws_lookup == NULL) { + lwsl_err("OOM on lws_lookup array for %d connections\n", + context->max_fds); + return 1; + } + + lwsl_info(" mem: platform fd map: %5lu bytes\n", + (unsigned long)(sizeof(struct lws *) * context->max_fds)); + fd = open(SYSTEM_RANDOM_FILEPATH, O_RDONLY); + + context->fd_random = fd; + if (context->fd_random < 0) { + lwsl_err("Unable to open random device %s %d\n", + SYSTEM_RANDOM_FILEPATH, context->fd_random); + return 1; + } + + if (!lws_libev_init_fd_table(context) && + !lws_libuv_init_fd_table(context) && + !lws_libevent_init_fd_table(context)) { + /* otherwise libev/uv/event handled it instead */ + + while (n--) { + if (pipe(pt->dummy_pipe_fds)) { + lwsl_err("Unable to create pipe\n"); + return 1; + } + + /* use the read end of pipe as first item */ + pt->fds[0].fd = pt->dummy_pipe_fds[0]; + pt->fds[0].events = LWS_POLLIN; + pt->fds[0].revents = 0; + pt->fds_count = 1; + pt++; + } + } + +#ifdef LWS_WITH_PLUGINS + if (info->plugin_dirs) + lws_plat_plugins_init(context, info->plugin_dirs); +#endif + + return 0; +} diff --git a/thirdparty/lws/plat/lws-plat-win.c b/thirdparty/lws/plat/lws-plat-win.c new file mode 100644 index 0000000000..f5b178ce85 --- /dev/null +++ b/thirdparty/lws/plat/lws-plat-win.c @@ -0,0 +1,745 @@ +#ifndef _WINSOCK_DEPRECATED_NO_WARNINGS +#define _WINSOCK_DEPRECATED_NO_WARNINGS +#endif +#include "private-libwebsockets.h" + +unsigned long long +time_in_microseconds() +{ +#ifndef DELTA_EPOCH_IN_MICROSECS +#define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL +#endif + FILETIME filetime; + ULARGE_INTEGER datetime; + +#ifdef _WIN32_WCE + GetCurrentFT(&filetime); +#else + GetSystemTimeAsFileTime(&filetime); +#endif + + /* + * As per Windows documentation for FILETIME, copy the resulting FILETIME structure to a + * ULARGE_INTEGER structure using memcpy (using memcpy instead of direct assignment can + * prevent alignment faults on 64-bit Windows). + */ + memcpy(&datetime, &filetime, sizeof(datetime)); + + /* Windows file times are in 100s of nanoseconds. */ + return (datetime.QuadPart - DELTA_EPOCH_IN_MICROSECS) / 10; +} + +#ifdef _WIN32_WCE +time_t time(time_t *t) +{ + time_t ret = time_in_microseconds() / 1000000; + + if(t != NULL) + *t = ret; + + return ret; +} +#endif + +/* file descriptor hash management */ + +struct lws * +wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd) +{ + int h = LWS_FD_HASH(fd); + int n = 0; + + for (n = 0; n < context->fd_hashtable[h].length; n++) + if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd) + return context->fd_hashtable[h].wsi[n]; + + return NULL; +} + +int +insert_wsi(struct lws_context *context, struct lws *wsi) +{ + int h = LWS_FD_HASH(wsi->desc.sockfd); + + if (context->fd_hashtable[h].length == (getdtablesize() - 1)) { + lwsl_err("hash table overflow\n"); + return 1; + } + + context->fd_hashtable[h].wsi[context->fd_hashtable[h].length++] = wsi; + + return 0; +} + +int +delete_from_fd(struct lws_context *context, lws_sockfd_type fd) +{ + int h = LWS_FD_HASH(fd); + int n = 0; + + for (n = 0; n < context->fd_hashtable[h].length; n++) + if (context->fd_hashtable[h].wsi[n]->desc.sockfd == fd) { + while (n < context->fd_hashtable[h].length) { + context->fd_hashtable[h].wsi[n] = + context->fd_hashtable[h].wsi[n + 1]; + n++; + } + context->fd_hashtable[h].length--; + + return 0; + } + + lwsl_err("Failed to find fd %d requested for " + "delete in hashtable\n", fd); + return 1; +} + +LWS_VISIBLE int lws_get_random(struct lws_context *context, + void *buf, int len) +{ + int n; + char *p = (char *)buf; + + for (n = 0; n < len; n++) + p[n] = (unsigned char)rand(); + + return n; +} + +LWS_VISIBLE int lws_send_pipe_choked(struct lws *wsi) +{ + /* treat the fact we got a truncated send pending as if we're choked */ + if (wsi->trunc_len) + return 1; + + return (int)wsi->sock_send_blocking; +} + +LWS_VISIBLE int lws_poll_listen_fd(struct lws_pollfd *fd) +{ + fd_set readfds; + struct timeval tv = { 0, 0 }; + + assert((fd->events & LWS_POLLIN) == LWS_POLLIN); + + FD_ZERO(&readfds); + FD_SET(fd->fd, &readfds); + + return select(fd->fd + 1, &readfds, NULL, NULL, &tv); +} + +LWS_VISIBLE void +lws_cancel_service(struct lws_context *context) +{ + struct lws_context_per_thread *pt = &context->pt[0]; + int n = context->count_threads; + + while (n--) { + WSASetEvent(pt->events[0]); + pt++; + } +} + +LWS_VISIBLE void +lws_cancel_service_pt(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + WSASetEvent(pt->events[0]); +} + +LWS_VISIBLE void lwsl_emit_syslog(int level, const char *line) +{ + lwsl_emit_stderr(level, line); +} + +LWS_VISIBLE LWS_EXTERN int +_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi) +{ + struct lws_context_per_thread *pt; + WSANETWORKEVENTS networkevents; + struct lws_pollfd *pfd; + struct lws *wsi; + unsigned int i; + DWORD ev; + int n, m; + + /* stay dead once we are dead */ + if (context == NULL || !context->vhost_list) + return 1; + + pt = &context->pt[tsi]; + + if (!context->service_tid_detected) { + struct lws _lws; + + memset(&_lws, 0, sizeof(_lws)); + _lws.context = context; + + context->service_tid_detected = context->vhost_list-> + protocols[0].callback(&_lws, LWS_CALLBACK_GET_THREAD_ID, + NULL, NULL, 0); + context->service_tid = context->service_tid_detected; + context->service_tid_detected = 1; + } + + if (timeout_ms < 0) + { + if (lws_service_flag_pending(context, tsi)) { + /* any socket with events to service? */ + for (n = 0; n < (int)pt->fds_count; n++) { + if (!pt->fds[n].revents) + continue; + + m = lws_service_fd_tsi(context, &pt->fds[n], tsi); + if (m < 0) + return -1; + /* if something closed, retry this slot */ + if (m) + n--; + } + } + return 0; + } + + for (i = 0; i < pt->fds_count; ++i) { + pfd = &pt->fds[i]; + + if (!(pfd->events & LWS_POLLOUT)) + continue; + + wsi = wsi_from_fd(context, pfd->fd); + if (wsi->listener) + continue; + if (!wsi || wsi->sock_send_blocking) + continue; + pfd->revents = LWS_POLLOUT; + n = lws_service_fd(context, pfd); + if (n < 0) + return -1; + /* if something closed, retry this slot */ + if (n) + i--; + + if (wsi->trunc_len) + WSASetEvent(pt->events[0]); + } + + /* + * is there anybody with pending stuff that needs service forcing? + */ + if (!lws_service_adjust_timeout(context, 1, tsi)) { + /* -1 timeout means just do forced service */ + _lws_plat_service_tsi(context, -1, pt->tid); + /* still somebody left who wants forced service? */ + if (!lws_service_adjust_timeout(context, 1, pt->tid)) + /* yes... come back again quickly */ + timeout_ms = 0; + } + + ev = WSAWaitForMultipleEvents( 1, pt->events , FALSE, timeout_ms, FALSE); + if (ev == WSA_WAIT_EVENT_0) { + unsigned int eIdx; + + WSAResetEvent(pt->events[0]); + + for (eIdx = 0; eIdx < pt->fds_count; ++eIdx) { + if (WSAEnumNetworkEvents(pt->fds[eIdx].fd, 0, &networkevents) == SOCKET_ERROR) { + lwsl_err("WSAEnumNetworkEvents() failed with error %d\n", LWS_ERRNO); + return -1; + } + + pfd = &pt->fds[eIdx]; + pfd->revents = (short)networkevents.lNetworkEvents; + + if ((networkevents.lNetworkEvents & FD_CONNECT) && + networkevents.iErrorCode[FD_CONNECT_BIT] && + networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EALREADY && + networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EINPROGRESS && + networkevents.iErrorCode[FD_CONNECT_BIT] != LWS_EWOULDBLOCK && + networkevents.iErrorCode[FD_CONNECT_BIT] != WSAEINVAL) { + lwsl_debug("Unable to connect errno=%d\n", + networkevents.iErrorCode[FD_CONNECT_BIT]); + pfd->revents |= LWS_POLLHUP; + } + + if (pfd->revents & LWS_POLLOUT) { + wsi = wsi_from_fd(context, pfd->fd); + if (wsi) + wsi->sock_send_blocking = 0; + } + /* if something closed, retry this slot */ + if (pfd->revents & LWS_POLLHUP) + --eIdx; + + if( pfd->revents != 0 ) { + lws_service_fd_tsi(context, pfd, tsi); + + } + } + } + + context->service_tid = 0; + + if (ev == WSA_WAIT_TIMEOUT) { + lws_service_fd(context, NULL); + } + return 0;; +} + +LWS_VISIBLE int +lws_plat_service(struct lws_context *context, int timeout_ms) +{ + return _lws_plat_service_tsi(context, timeout_ms, 0); +} + +LWS_VISIBLE int +lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd) +{ + int optval = 1; + int optlen = sizeof(optval); + u_long optl = 1; + DWORD dwBytesRet; + struct tcp_keepalive alive; + int protonbr; +#ifndef _WIN32_WCE + struct protoent *tcp_proto; +#endif + + if (vhost->ka_time) { + /* enable keepalive on this socket */ + optval = 1; + if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, + (const char *)&optval, optlen) < 0) + return 1; + + alive.onoff = TRUE; + alive.keepalivetime = vhost->ka_time; + alive.keepaliveinterval = vhost->ka_interval; + + if (WSAIoctl(fd, SIO_KEEPALIVE_VALS, &alive, sizeof(alive), + NULL, 0, &dwBytesRet, NULL, NULL)) + return 1; + } + + /* Disable Nagle */ + optval = 1; +#ifndef _WIN32_WCE + tcp_proto = getprotobyname("TCP"); + if (!tcp_proto) { + lwsl_err("getprotobyname() failed with error %d\n", LWS_ERRNO); + return 1; + } + protonbr = tcp_proto->p_proto; +#else + protonbr = 6; +#endif + + setsockopt(fd, protonbr, TCP_NODELAY, (const char *)&optval, optlen); + + /* We are nonblocking... */ + ioctlsocket(fd, FIONBIO, &optl); + + return 0; +} + +LWS_VISIBLE void +lws_plat_drop_app_privileges(struct lws_context_creation_info *info) +{ +} + +LWS_VISIBLE int +lws_plat_context_early_init(void) +{ + WORD wVersionRequested; + WSADATA wsaData; + int err; + + /* Use the MAKEWORD(lowbyte, highbyte) macro from Windef.h */ + wVersionRequested = MAKEWORD(2, 2); + + err = WSAStartup(wVersionRequested, &wsaData); + if (!err) + return 0; + /* + * Tell the user that we could not find a usable + * Winsock DLL + */ + lwsl_err("WSAStartup failed with error: %d\n", err); + + return 1; +} + +LWS_VISIBLE void +lws_plat_context_early_destroy(struct lws_context *context) +{ + struct lws_context_per_thread *pt = &context->pt[0]; + int n = context->count_threads; + + while (n--) { + if (pt->events) { + WSACloseEvent(pt->events[0]); + lws_free(pt->events); + } + pt++; + } +} + +LWS_VISIBLE void +lws_plat_context_late_destroy(struct lws_context *context) +{ + int n; + + for (n = 0; n < FD_HASHTABLE_MODULUS; n++) { + if (context->fd_hashtable[n].wsi) + lws_free(context->fd_hashtable[n].wsi); + } + + WSACleanup(); +} + +LWS_VISIBLE LWS_EXTERN int +lws_interface_to_sa(int ipv6, + const char *ifname, struct sockaddr_in *addr, size_t addrlen) +{ +#ifdef LWS_WITH_IPV6 + struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)addr; + + if (ipv6) { + if (lws_plat_inet_pton(AF_INET6, ifname, &addr6->sin6_addr) == 1) { + return 0; + } + } +#endif + + long long address = inet_addr(ifname); + + if (address == INADDR_NONE) { + struct hostent *entry = gethostbyname(ifname); + if (entry) + address = ((struct in_addr *)entry->h_addr_list[0])->s_addr; + } + + if (address == INADDR_NONE) + return -1; + + addr->sin_addr.s_addr = (lws_intptr_t)address; + + return 0; +} + +LWS_VISIBLE void +lws_plat_insert_socket_into_fds(struct lws_context *context, struct lws *wsi) +{ + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + + pt->fds[pt->fds_count++].revents = 0; + pt->events[pt->fds_count] = pt->events[0]; + WSAEventSelect(wsi->desc.sockfd, pt->events[0], + LWS_POLLIN | LWS_POLLHUP | FD_CONNECT); +} + +LWS_VISIBLE void +lws_plat_delete_socket_from_fds(struct lws_context *context, + struct lws *wsi, int m) +{ + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + + pt->events[m + 1] = pt->events[pt->fds_count--]; +} + +LWS_VISIBLE void +lws_plat_service_periodic(struct lws_context *context) +{ +} + +LWS_VISIBLE int +lws_plat_check_connection_error(struct lws *wsi) +{ + int optVal; + int optLen = sizeof(int); + + if (getsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_ERROR, + (char*)&optVal, &optLen) != SOCKET_ERROR && optVal && + optVal != LWS_EALREADY && optVal != LWS_EINPROGRESS && + optVal != LWS_EWOULDBLOCK && optVal != WSAEINVAL) { + lwsl_debug("Connect failed SO_ERROR=%d\n", optVal); + return 1; + } + + return 0; +} + +LWS_VISIBLE int +lws_plat_change_pollfd(struct lws_context *context, + struct lws *wsi, struct lws_pollfd *pfd) +{ + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + long networkevents = LWS_POLLHUP | FD_CONNECT; + + if ((pfd->events & LWS_POLLIN)) + networkevents |= LWS_POLLIN; + + if ((pfd->events & LWS_POLLOUT)) + networkevents |= LWS_POLLOUT; + + if (WSAEventSelect(wsi->desc.sockfd, + pt->events[0], + networkevents) != SOCKET_ERROR) + return 0; + + lwsl_err("WSAEventSelect() failed with error %d\n", LWS_ERRNO); + + return 1; +} + +LWS_VISIBLE const char * +lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt) +{ + WCHAR *buffer; + DWORD bufferlen = cnt; + BOOL ok = FALSE; + + buffer = lws_malloc(bufferlen * 2, "inet_ntop"); + if (!buffer) { + lwsl_err("Out of memory\n"); + return NULL; + } + + if (af == AF_INET) { + struct sockaddr_in srcaddr; + bzero(&srcaddr, sizeof(srcaddr)); + srcaddr.sin_family = AF_INET; + memcpy(&(srcaddr.sin_addr), src, sizeof(srcaddr.sin_addr)); + + if (!WSAAddressToStringW((struct sockaddr*)&srcaddr, sizeof(srcaddr), 0, buffer, &bufferlen)) + ok = TRUE; +#ifdef LWS_WITH_IPV6 + } else if (af == AF_INET6) { + struct sockaddr_in6 srcaddr; + bzero(&srcaddr, sizeof(srcaddr)); + srcaddr.sin6_family = AF_INET6; + memcpy(&(srcaddr.sin6_addr), src, sizeof(srcaddr.sin6_addr)); + + if (!WSAAddressToStringW((struct sockaddr*)&srcaddr, sizeof(srcaddr), 0, buffer, &bufferlen)) + ok = TRUE; +#endif + } else + lwsl_err("Unsupported type\n"); + + if (!ok) { + int rv = WSAGetLastError(); + lwsl_err("WSAAddressToString() : %d\n", rv); + } else { + if (WideCharToMultiByte(CP_ACP, 0, buffer, bufferlen, dst, cnt, 0, NULL) <= 0) + ok = FALSE; + } + + lws_free(buffer); + return ok ? dst : NULL; +} + +LWS_VISIBLE int +lws_plat_inet_pton(int af, const char *src, void *dst) +{ + WCHAR *buffer; + DWORD bufferlen = strlen(src) + 1; + BOOL ok = FALSE; + + buffer = lws_malloc(bufferlen * 2, "inet_pton"); + if (!buffer) { + lwsl_err("Out of memory\n"); + return -1; + } + + if (MultiByteToWideChar(CP_ACP, 0, src, bufferlen, buffer, bufferlen) <= 0) { + lwsl_err("Failed to convert multi byte to wide char\n"); + lws_free(buffer); + return -1; + } + + if (af == AF_INET) { + struct sockaddr_in dstaddr; + int dstaddrlen = sizeof(dstaddr); + bzero(&dstaddr, sizeof(dstaddr)); + dstaddr.sin_family = AF_INET; + + if (!WSAStringToAddressW(buffer, af, 0, (struct sockaddr *) &dstaddr, &dstaddrlen)) { + ok = TRUE; + memcpy(dst, &dstaddr.sin_addr, sizeof(dstaddr.sin_addr)); + } +#ifdef LWS_WITH_IPV6 + } else if (af == AF_INET6) { + struct sockaddr_in6 dstaddr; + int dstaddrlen = sizeof(dstaddr); + bzero(&dstaddr, sizeof(dstaddr)); + dstaddr.sin6_family = AF_INET6; + + if (!WSAStringToAddressW(buffer, af, 0, (struct sockaddr *) &dstaddr, &dstaddrlen)) { + ok = TRUE; + memcpy(dst, &dstaddr.sin6_addr, sizeof(dstaddr.sin6_addr)); + } +#endif + } else + lwsl_err("Unsupported type\n"); + + if (!ok) { + int rv = WSAGetLastError(); + lwsl_err("WSAAddressToString() : %d\n", rv); + } + + lws_free(buffer); + return ok ? 1 : -1; +} + +LWS_VISIBLE lws_fop_fd_t +_lws_plat_file_open(const struct lws_plat_file_ops *fops, const char *filename, + const char *vpath, lws_fop_flags_t *flags) +{ + HANDLE ret; + WCHAR buf[MAX_PATH]; + lws_fop_fd_t fop_fd; + LARGE_INTEGER llFileSize = {0}; + + MultiByteToWideChar(CP_UTF8, 0, filename, -1, buf, ARRAY_SIZE(buf)); + if (((*flags) & 7) == _O_RDONLY) { + ret = CreateFileW(buf, GENERIC_READ, FILE_SHARE_READ, + NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); + } else { + ret = CreateFileW(buf, GENERIC_WRITE, 0, NULL, + CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL); + } + + if (ret == LWS_INVALID_FILE) + goto bail; + + fop_fd = malloc(sizeof(*fop_fd)); + if (!fop_fd) + goto bail; + + fop_fd->fops = fops; + fop_fd->fd = ret; + fop_fd->filesystem_priv = NULL; /* we don't use it */ + fop_fd->flags = *flags; + fop_fd->len = GetFileSize(ret, NULL); + if(GetFileSizeEx(ret, &llFileSize)) + fop_fd->len = llFileSize.QuadPart; + + fop_fd->pos = 0; + + return fop_fd; + +bail: + return NULL; +} + +LWS_VISIBLE int +_lws_plat_file_close(lws_fop_fd_t *fop_fd) +{ + HANDLE fd = (*fop_fd)->fd; + + free(*fop_fd); + *fop_fd = NULL; + + CloseHandle((HANDLE)fd); + + return 0; +} + +LWS_VISIBLE lws_fileofs_t +_lws_plat_file_seek_cur(lws_fop_fd_t fop_fd, lws_fileofs_t offset) +{ + LARGE_INTEGER l; + + l.QuadPart = offset; + return SetFilePointerEx((HANDLE)fop_fd->fd, l, NULL, FILE_CURRENT); +} + +LWS_VISIBLE int +_lws_plat_file_read(lws_fop_fd_t fop_fd, lws_filepos_t *amount, + uint8_t *buf, lws_filepos_t len) +{ + DWORD _amount; + + if (!ReadFile((HANDLE)fop_fd->fd, buf, (DWORD)len, &_amount, NULL)) { + *amount = 0; + + return 1; + } + + fop_fd->pos += _amount; + *amount = (unsigned long)_amount; + + return 0; +} + +LWS_VISIBLE int +_lws_plat_file_write(lws_fop_fd_t fop_fd, lws_filepos_t *amount, + uint8_t* buf, lws_filepos_t len) +{ + DWORD _amount; + + if (!WriteFile((HANDLE)fop_fd->fd, buf, (DWORD)len, &_amount, NULL)) { + *amount = 0; + + return 1; + } + + fop_fd->pos += _amount; + *amount = (unsigned long)_amount; + + return 0; +} + +LWS_VISIBLE int +lws_plat_init(struct lws_context *context, + struct lws_context_creation_info *info) +{ + struct lws_context_per_thread *pt = &context->pt[0]; + int i, n = context->count_threads; + + for (i = 0; i < FD_HASHTABLE_MODULUS; i++) { + context->fd_hashtable[i].wsi = + lws_zalloc(sizeof(struct lws*) * context->max_fds, "win hashtable"); + + if (!context->fd_hashtable[i].wsi) + return -1; + } + + while (n--) { + pt->events = lws_malloc(sizeof(WSAEVENT) * + (context->fd_limit_per_thread + 1), "event table"); + if (pt->events == NULL) { + lwsl_err("Unable to allocate events array for %d connections\n", + context->fd_limit_per_thread + 1); + return 1; + } + + pt->fds_count = 0; + pt->events[0] = WSACreateEvent(); + + pt++; + } + + context->fd_random = 0; + +#ifdef LWS_WITH_PLUGINS + if (info->plugin_dirs) + lws_plat_plugins_init(context, info->plugin_dirs); +#endif + + return 0; +} + + +int kill(int pid, int sig) +{ + lwsl_err("Sorry Windows doesn't support kill()."); + exit(0); +} + +int fork(void) +{ + lwsl_err("Sorry Windows doesn't support fork()."); + exit(0); +} + diff --git a/thirdparty/lws/pollfd.c b/thirdparty/lws/pollfd.c new file mode 100644 index 0000000000..4d6704d41c --- /dev/null +++ b/thirdparty/lws/pollfd.c @@ -0,0 +1,557 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +int +_lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa) +{ + struct lws_context_per_thread *pt; + struct lws_context *context; + int ret = 0, pa_events = 1; + struct lws_pollfd *pfd; + int sampled_tid, tid; + + if (!wsi || wsi->position_in_fds_table < 0) + return 0; + + if (wsi->handling_pollout && !_and && _or == LWS_POLLOUT) { + /* + * Happening alongside service thread handling POLLOUT. + * The danger is when he is finished, he will disable POLLOUT, + * countermanding what we changed here. + * + * Instead of changing the fds, inform the service thread + * what happened, and ask it to leave POLLOUT active on exit + */ + wsi->leave_pollout_active = 1; + /* + * by definition service thread is not in poll wait, so no need + * to cancel service + */ + + lwsl_debug("%s: using leave_pollout_active\n", __func__); + + return 0; + } + + context = wsi->context; + pt = &context->pt[(int)wsi->tsi]; + assert(wsi->position_in_fds_table >= 0 && + wsi->position_in_fds_table < pt->fds_count); + + pfd = &pt->fds[wsi->position_in_fds_table]; + pa->fd = wsi->desc.sockfd; + pa->prev_events = pfd->events; + pa->events = pfd->events = (pfd->events & ~_and) | _or; + + if (wsi->http2_substream) + return 0; + + if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_CHANGE_MODE_POLL_FD, + wsi->user_space, (void *)pa, 0)) { + ret = -1; + goto bail; + } + + if (_and & LWS_POLLIN) { + lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ); + lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ); + lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_READ); + } + if (_or & LWS_POLLIN) { + lws_libev_io(wsi, LWS_EV_START | LWS_EV_READ); + lws_libuv_io(wsi, LWS_EV_START | LWS_EV_READ); + lws_libevent_io(wsi, LWS_EV_START | LWS_EV_READ); + } + if (_and & LWS_POLLOUT) { + lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_WRITE); + lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_WRITE); + lws_libevent_io(wsi, LWS_EV_STOP | LWS_EV_WRITE); + } + if (_or & LWS_POLLOUT) { + lws_libev_io(wsi, LWS_EV_START | LWS_EV_WRITE); + lws_libuv_io(wsi, LWS_EV_START | LWS_EV_WRITE); + lws_libevent_io(wsi, LWS_EV_START | LWS_EV_WRITE); + } + + /* + * if we changed something in this pollfd... + * ... and we're running in a different thread context + * than the service thread... + * ... and the service thread is waiting ... + * then cancel it to force a restart with our changed events + */ +#if LWS_POSIX + pa_events = pa->prev_events != pa->events; +#endif + + if (pa_events) { + + if (lws_plat_change_pollfd(context, wsi, pfd)) { + lwsl_info("%s failed\n", __func__); + ret = -1; + goto bail; + } + + sampled_tid = context->service_tid; + if (sampled_tid) { + tid = wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_GET_THREAD_ID, NULL, NULL, 0); + if (tid == -1) { + ret = -1; + goto bail; + } + if (tid != sampled_tid) + lws_cancel_service_pt(wsi); + } + } +bail: + return ret; +} + +#ifndef LWS_NO_SERVER +static void +lws_accept_modulation(struct lws_context_per_thread *pt, int allow) +{ +// multithread listen seems broken +#if 0 + struct lws_vhost *vh = context->vhost_list; + struct lws_pollargs pa1; + + while (vh) { + if (allow) + _lws_change_pollfd(pt->wsi_listening, + 0, LWS_POLLIN, &pa1); + else + _lws_change_pollfd(pt->wsi_listening, + LWS_POLLIN, 0, &pa1); + vh = vh->vhost_next; + } +#endif +} +#endif + +int +insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi) +{ + struct lws_pollargs pa = { wsi->desc.sockfd, LWS_POLLIN, 0 }; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + int ret = 0; + + + lwsl_debug("%s: %p: tsi=%d, sock=%d, pos-in-fds=%d\n", + __func__, wsi, wsi->tsi, wsi->desc.sockfd, pt->fds_count); + + if ((unsigned int)pt->fds_count >= context->fd_limit_per_thread) { + lwsl_err("Too many fds (%d vs %d)\n", context->max_fds, + context->fd_limit_per_thread ); + return 1; + } + +#if !defined(_WIN32) && !defined(LWS_WITH_ESP8266) + if (wsi->desc.sockfd >= context->max_fds) { + lwsl_err("Socket fd %d is too high (%d)\n", + wsi->desc.sockfd, context->max_fds); + return 1; + } +#endif + + assert(wsi); + assert(wsi->vhost); + assert(lws_socket_is_valid(wsi->desc.sockfd)); + + if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, + wsi->user_space, (void *) &pa, 1)) + return -1; + + lws_pt_lock(pt); + pt->count_conns++; + insert_wsi(context, wsi); +#if defined(LWS_WITH_ESP8266) + if (wsi->position_in_fds_table == -1) +#endif + wsi->position_in_fds_table = pt->fds_count; + + pt->fds[wsi->position_in_fds_table].fd = wsi->desc.sockfd; +#if LWS_POSIX + pt->fds[wsi->position_in_fds_table].events = LWS_POLLIN; +#else + pt->fds[wsi->position_in_fds_table].events = 0; +#endif + pa.events = pt->fds[pt->fds_count].events; + + lws_plat_insert_socket_into_fds(context, wsi); + + /* external POLL support via protocol 0 */ + if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_ADD_POLL_FD, + wsi->user_space, (void *) &pa, 0)) + ret = -1; +#ifndef LWS_NO_SERVER + /* if no more room, defeat accepts on this thread */ + if ((unsigned int)pt->fds_count == context->fd_limit_per_thread - 1) + lws_accept_modulation(pt, 0); +#endif + lws_pt_unlock(pt); + + if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, + wsi->user_space, (void *)&pa, 1)) + ret = -1; + + return ret; +} + +int +remove_wsi_socket_from_fds(struct lws *wsi) +{ + struct lws_context *context = wsi->context; + struct lws_pollargs pa = { wsi->desc.sockfd, 0, 0 }; +#if !defined(LWS_WITH_ESP8266) + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws *end_wsi; + int v; +#endif + int m, ret = 0; + + if (wsi->parent_carries_io) { + lws_same_vh_protocol_remove(wsi); + return 0; + } + +#if !defined(_WIN32) && !defined(LWS_WITH_ESP8266) + if (wsi->desc.sockfd > context->max_fds) { + lwsl_err("fd %d too high (%d)\n", wsi->desc.sockfd, + context->max_fds); + return 1; + } +#endif + + if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, + wsi->user_space, (void *)&pa, 1)) + return -1; + + lws_same_vh_protocol_remove(wsi); + + /* the guy who is to be deleted's slot index in pt->fds */ + m = wsi->position_in_fds_table; + +#if !defined(LWS_WITH_ESP8266) + lws_libev_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE | + LWS_EV_PREPARE_DELETION); + lws_libuv_io(wsi, LWS_EV_STOP | LWS_EV_READ | LWS_EV_WRITE | + LWS_EV_PREPARE_DELETION); + + lws_pt_lock(pt); + + lwsl_debug("%s: wsi=%p, sock=%d, fds pos=%d, end guy pos=%d, endfd=%d\n", + __func__, wsi, wsi->desc.sockfd, wsi->position_in_fds_table, + pt->fds_count, pt->fds[pt->fds_count].fd); + + /* have the last guy take up the now vacant slot */ + pt->fds[m] = pt->fds[pt->fds_count - 1]; +#endif + /* this decrements pt->fds_count */ + lws_plat_delete_socket_from_fds(context, wsi, m); +#if !defined(LWS_WITH_ESP8266) + v = (int) pt->fds[m].fd; + /* end guy's "position in fds table" is now the deletion guy's old one */ + end_wsi = wsi_from_fd(context, v); + if (!end_wsi) { + lwsl_err("no wsi found for sock fd %d at pos %d, pt->fds_count=%d\n", + (int)pt->fds[m].fd, m, pt->fds_count); + assert(0); + } else + end_wsi->position_in_fds_table = m; + + /* deletion guy's lws_lookup entry needs nuking */ + delete_from_fd(context, wsi->desc.sockfd); + /* removed wsi has no position any more */ + wsi->position_in_fds_table = -1; + + /* remove also from external POLL support via protocol 0 */ + if (lws_socket_is_valid(wsi->desc.sockfd)) + if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_DEL_POLL_FD, + wsi->user_space, (void *) &pa, 0)) + ret = -1; +#ifndef LWS_NO_SERVER + if (!context->being_destroyed) + /* if this made some room, accept connects on this thread */ + if ((unsigned int)pt->fds_count < context->fd_limit_per_thread - 1) + lws_accept_modulation(pt, 1); +#endif + lws_pt_unlock(pt); + + if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, + wsi->user_space, (void *) &pa, 1)) + ret = -1; +#endif + return ret; +} + +int +lws_change_pollfd(struct lws *wsi, int _and, int _or) +{ + struct lws_context_per_thread *pt; + struct lws_context *context; + struct lws_pollargs pa; + int ret = 0; + + if (!wsi || !wsi->protocol || wsi->position_in_fds_table < 0) + return 1; + + context = lws_get_context(wsi); + if (!context) + return 1; + + if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_LOCK_POLL, + wsi->user_space, (void *) &pa, 0)) + return -1; + + pt = &context->pt[(int)wsi->tsi]; + + lws_pt_lock(pt); + ret = _lws_change_pollfd(wsi, _and, _or, &pa); + lws_pt_unlock(pt); + if (wsi->vhost->protocols[0].callback(wsi, LWS_CALLBACK_UNLOCK_POLL, + wsi->user_space, (void *) &pa, 0)) + ret = -1; + + return ret; +} + +LWS_VISIBLE int +lws_callback_on_writable(struct lws *wsi) +{ + struct lws_context_per_thread *pt; +#ifdef LWS_WITH_HTTP2 + struct lws *network_wsi, *wsi2; + int already; +#endif + int n; + + if (wsi->state == LWSS_SHUTDOWN) + return 0; + + if (wsi->socket_is_permanently_unusable) + return 0; + + pt = &wsi->context->pt[(int)wsi->tsi]; + + if (wsi->parent_carries_io) { +#if defined(LWS_WITH_STATS) + if (!wsi->active_writable_req_us) { + wsi->active_writable_req_us = time_in_microseconds(); + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_WRITEABLE_CB_EFF_REQ, 1); + } +#endif + n = lws_callback_on_writable(wsi->parent); + if (n < 0) + return n; + + wsi->parent_pending_cb_on_writable = 1; + return 1; + } + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB_REQ, 1); +#if defined(LWS_WITH_STATS) + if (!wsi->active_writable_req_us) { + wsi->active_writable_req_us = time_in_microseconds(); + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB_EFF_REQ, 1); + } +#endif + +#ifdef LWS_WITH_HTTP2 + lwsl_info("%s: %p\n", __func__, wsi); + + if (wsi->mode != LWSCM_HTTP2_SERVING) + goto network_sock; + + if (wsi->u.h2.requested_POLLOUT) { + lwsl_info("already pending writable\n"); + return 1; + } + + /* is this for DATA or for control messages? */ + if (wsi->upgraded_to_http2 && !wsi->u.h2.h2n->pps && + !lws_h2_tx_cr_get(wsi)) { + /* + * other side is not able to cope with us sending DATA + * anything so no matter if we have POLLOUT on our side if it's + * DATA we want to send. + * + * Delay waiting for our POLLOUT until peer indicates he has + * space for more using tx window command in http2 layer + */ + lwsl_notice("%s: %p: skint (%d)\n", __func__, wsi, wsi->u.h2.tx_cr); + wsi->u.h2.skint = 1; + return 0; + } + + wsi->u.h2.skint = 0; + network_wsi = lws_get_network_wsi(wsi); + already = network_wsi->u.h2.requested_POLLOUT; + + /* mark everybody above him as requesting pollout */ + + wsi2 = wsi; + while (wsi2) { + wsi2->u.h2.requested_POLLOUT = 1; + lwsl_info("mark %p pending writable\n", wsi2); + wsi2 = wsi2->u.h2.parent_wsi; + } + + /* for network action, act only on the network wsi */ + + wsi = network_wsi; + if (already) + return 1; +network_sock: +#endif + + if (lws_ext_cb_active(wsi, LWS_EXT_CB_REQUEST_ON_WRITEABLE, NULL, 0)) + return 1; + + if (wsi->position_in_fds_table < 0) { + lwsl_debug("%s: failed to find socket %d\n", __func__, wsi->desc.sockfd); + return -1; + } + + if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) + return -1; + + return 1; +} + +/* + * stitch protocol choice into the vh protocol linked list + * We always insert ourselves at the start of the list + * + * X <-> B + * X <-> pAn <-> pB + * + * Illegal to attach more than once without detach inbetween + */ +void +lws_same_vh_protocol_insert(struct lws *wsi, int n) +{ + if (wsi->same_vh_protocol_prev || wsi->same_vh_protocol_next) { + lws_same_vh_protocol_remove(wsi); + lwsl_notice("Attempted to attach wsi twice to same vh prot\n"); + } + + wsi->same_vh_protocol_prev = &wsi->vhost->same_vh_protocol_list[n]; + /* old first guy is our next */ + wsi->same_vh_protocol_next = wsi->vhost->same_vh_protocol_list[n]; + /* we become the new first guy */ + wsi->vhost->same_vh_protocol_list[n] = wsi; + + if (wsi->same_vh_protocol_next) + /* old first guy points back to us now */ + wsi->same_vh_protocol_next->same_vh_protocol_prev = + &wsi->same_vh_protocol_next; +} + +void +lws_same_vh_protocol_remove(struct lws *wsi) +{ + /* + * detach ourselves from vh protocol list if we're on one + * A -> B -> C + * A -> C , or, B -> C, or A -> B + * + * OK to call on already-detached wsi + */ + lwsl_info("%s: removing same prot wsi %p\n", __func__, wsi); + + if (wsi->same_vh_protocol_prev) { + assert (*(wsi->same_vh_protocol_prev) == wsi); + lwsl_info("have prev %p, setting him to our next %p\n", + wsi->same_vh_protocol_prev, + wsi->same_vh_protocol_next); + + /* guy who pointed to us should point to our next */ + *(wsi->same_vh_protocol_prev) = wsi->same_vh_protocol_next; + } + + /* our next should point back to our prev */ + if (wsi->same_vh_protocol_next) { + wsi->same_vh_protocol_next->same_vh_protocol_prev = + wsi->same_vh_protocol_prev; + } + + wsi->same_vh_protocol_prev = NULL; + wsi->same_vh_protocol_next = NULL; +} + + +LWS_VISIBLE int +lws_callback_on_writable_all_protocol_vhost(const struct lws_vhost *vhost, + const struct lws_protocols *protocol) +{ + struct lws *wsi; + + if (protocol < vhost->protocols || + protocol >= (vhost->protocols + vhost->count_protocols)) { + lwsl_err("%s: protocol %p is not from vhost %p (%p - %p)\n", + __func__, protocol, vhost->protocols, vhost, + (vhost->protocols + vhost->count_protocols)); + + return -1; + } + + wsi = vhost->same_vh_protocol_list[protocol - vhost->protocols]; + while (wsi) { + assert(wsi->protocol == protocol); + assert(*wsi->same_vh_protocol_prev == wsi); + if (wsi->same_vh_protocol_next) + assert(wsi->same_vh_protocol_next->same_vh_protocol_prev == + &wsi->same_vh_protocol_next); + + lws_callback_on_writable(wsi); + wsi = wsi->same_vh_protocol_next; + } + + return 0; +} + +LWS_VISIBLE int +lws_callback_on_writable_all_protocol(const struct lws_context *context, + const struct lws_protocols *protocol) +{ + struct lws_vhost *vhost = context->vhost_list; + int n; + + while (vhost) { + for (n = 0; n < vhost->count_protocols; n++) + if (protocol->callback == + vhost->protocols[n].callback && + !strcmp(protocol->name, vhost->protocols[n].name)) + break; + if (n != vhost->count_protocols) + lws_callback_on_writable_all_protocol_vhost( + vhost, &vhost->protocols[n]); + + vhost = vhost->vhost_next; + } + + return 0; +} diff --git a/thirdparty/lws/private-libwebsockets.h b/thirdparty/lws/private-libwebsockets.h new file mode 100644 index 0000000000..4f0b374332 --- /dev/null +++ b/thirdparty/lws/private-libwebsockets.h @@ -0,0 +1,2618 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010 - 2016 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "lws_config.h" +#include "lws_config_private.h" + + +#if defined(LWS_WITH_CGI) && defined(LWS_HAVE_VFORK) +#define _GNU_SOURCE +#endif + +#if defined(__COVERITY__) +typedef struct { long double x, y; } _Float128; +#endif + +#ifdef LWS_HAVE_SYS_TYPES_H +#include <sys/types.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <time.h> +#include <ctype.h> +#include <limits.h> +#include <stdarg.h> +#include <inttypes.h> + +#if defined(LWS_WITH_ESP32) +#define MSG_NOSIGNAL 0 +#define SOMAXCONN 3 +#endif + +#if defined(LWS_WITH_ESP8266) +#include <user_interface.h> +#define assert(n) + +/* rom-provided stdc functions for free, ensure use these instead of libc ones */ + +int ets_vsprintf(char *str, const char *format, va_list argptr); +int ets_vsnprintf(char *buffer, size_t sizeOfBuffer, const char *format, va_list argptr); +int ets_snprintf(char *str, size_t size, const char *format, ...); +int ets_sprintf(char *str, const char *format, ...); +int os_printf_plus(const char *format, ...); +#undef malloc +#undef realloc +#undef free +void *pvPortMalloc(size_t s, const char *f, int line); +#define malloc(s) pvPortMalloc(s, "", 0) +void *pvPortRealloc(void *p, size_t s, const char *f, int line); +#define realloc(p, s) pvPortRealloc(p, s, "", 0) +void vPortFree(void *p, const char *f, int line); +#define free(p) vPortFree(p, "", 0) +#undef memcpy +void *ets_memcpy(void *dest, const void *src, size_t n); +#define memcpy ets_memcpy +void *ets_memset(void *dest, int v, size_t n); +#define memset ets_memset +char *ets_strcpy(char *dest, const char *src); +#define strcpy ets_strcpy +char *ets_strncpy(char *dest, const char *src, size_t n); +#define strncpy ets_strncpy +char *ets_strstr(const char *haystack, const char *needle); +#define strstr ets_strstr +int ets_strcmp(const char *s1, const char *s2); +int ets_strncmp(const char *s1, const char *s2, size_t n); +#define strcmp ets_strcmp +#define strncmp ets_strncmp +size_t ets_strlen(const char *s); +#define strlen ets_strlen +void *ets_memmove(void *dest, const void *src, size_t n); +#define memmove ets_memmove +char *ets_strchr(const char *s, int c); +#define strchr_ets_strchr +#undef _DEBUG +#include <osapi.h> + +#else +#define STORE_IN_ROM +#include <assert.h> +#endif +#if LWS_MAX_SMP > 1 +#include <pthread.h> +#endif + +#ifdef LWS_HAVE_SYS_STAT_H +#include <sys/stat.h> +#endif + +#if defined(WIN32) || defined(_WIN32) + +#ifndef WIN32_LEAN_AND_MEAN +#define WIN32_LEAN_AND_MEAN +#endif + +#if (WINVER < 0x0501) +#undef WINVER +#undef _WIN32_WINNT +#define WINVER 0x0501 +#define _WIN32_WINNT WINVER +#endif +#define LWS_NO_DAEMONIZE +#define LWS_ERRNO WSAGetLastError() +#define LWS_EAGAIN WSAEWOULDBLOCK +#define LWS_EALREADY WSAEALREADY +#define LWS_EINPROGRESS WSAEINPROGRESS +#define LWS_EINTR WSAEINTR +#define LWS_EISCONN WSAEISCONN +#define LWS_EWOULDBLOCK WSAEWOULDBLOCK +#define MSG_NOSIGNAL 0 +#define SHUT_RDWR SD_BOTH +#define SOL_TCP IPPROTO_TCP +#define SHUT_WR SD_SEND + +#define compatible_close(fd) closesocket(fd) +#define lws_set_blocking_send(wsi) wsi->sock_send_blocking = 1 +#define lws_socket_is_valid(x) (!!x) +#define LWS_SOCK_INVALID 0 +#include <winsock2.h> +#include <ws2tcpip.h> +#include <windows.h> +#include <tchar.h> +#ifdef LWS_HAVE_IN6ADDR_H +#include <in6addr.h> +#endif +#include <mstcpip.h> +#include <io.h> + +#if !defined(LWS_HAVE_ATOLL) +#if defined(LWS_HAVE__ATOI64) +#define atoll _atoi64 +#else +#warning No atoll or _atoi64 available, using atoi +#define atoll atoi +#endif +#endif + +#ifndef __func__ +#define __func__ __FUNCTION__ +#endif + +#ifdef LWS_HAVE__VSNPRINTF +#define vsnprintf _vsnprintf +#endif + +/* we don't have an implementation for this on windows... */ +int kill(int pid, int sig); +int fork(void); +#ifndef SIGINT +#define SIGINT 2 +#endif + +#else /* not windows --> */ + +#include <fcntl.h> +#include <strings.h> +#include <unistd.h> +#include <sys/types.h> + +#ifndef __cplusplus +#include <errno.h> +#endif +#include <netdb.h> +#include <signal.h> +#ifdef LWS_WITH_ESP8266 +#include <sockets.h> +#define vsnprintf ets_vsnprintf +#define snprintf ets_snprintf +#define sprintf ets_sprintf + +int kill(int pid, int sig); + +#else +#include <sys/socket.h> +#endif +#ifdef LWS_WITH_HTTP_PROXY +#include <hubbub/hubbub.h> +#include <hubbub/parser.h> +#endif +#if defined(LWS_BUILTIN_GETIFADDRS) + #include "./misc/getifaddrs.h" +#else + #if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) + #if defined(__HAIKU__) + #define _BSD_SOURCE + #endif + #include <ifaddrs.h> + #endif +#endif +#if defined (__ANDROID__) +#include <syslog.h> +#include <sys/resource.h> +#elif defined (__sun) || defined(__HAIKU__) +#include <syslog.h> +#else +#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) +#include <sys/syslog.h> +#endif +#endif +#include <netdb.h> +#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) +#include <sys/mman.h> +#include <sys/un.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <poll.h> +#endif +#ifdef LWS_WITH_LIBEV +#include <ev.h> +#endif +#ifdef LWS_WITH_LIBUV +#include <uv.h> +#endif +#ifdef LWS_WITH_LIBEVENT +#include <event2/event.h> +#endif + +#ifndef LWS_NO_FORK +#ifdef LWS_HAVE_SYS_PRCTL_H +#include <sys/prctl.h> +#endif +#endif + +#include <sys/time.h> + +#define LWS_ERRNO errno +#define LWS_EAGAIN EAGAIN +#define LWS_EALREADY EALREADY +#define LWS_EINPROGRESS EINPROGRESS +#define LWS_EINTR EINTR +#define LWS_EISCONN EISCONN +#define LWS_EWOULDBLOCK EWOULDBLOCK + +#define lws_set_blocking_send(wsi) + +#if defined(LWS_WITH_ESP8266) +#define lws_socket_is_valid(x) ((x) != NULL) +#define LWS_SOCK_INVALID (NULL) +struct lws; +const char * +lws_plat_get_peer_simple(struct lws *wsi, char *name, int namelen); +#else +#define lws_socket_is_valid(x) (x >= 0) +#define LWS_SOCK_INVALID (-1) +#endif +#endif + +#ifndef LWS_HAVE_BZERO +#ifndef bzero +#define bzero(b, len) (memset((b), '\0', (len)), (void) 0) +#endif +#endif + +#ifndef LWS_HAVE_STRERROR +#define strerror(x) "" +#endif + +#ifdef LWS_OPENSSL_SUPPORT + +#ifdef USE_WOLFSSL +#ifdef USE_OLD_CYASSL +#include <cyassl/openssl/ssl.h> +#include <cyassl/error-ssl.h> +#else +#include <wolfssl/openssl/ssl.h> +#include <wolfssl/error-ssl.h> +#define OPENSSL_NO_TLSEXT +#endif /* not USE_OLD_CYASSL */ +#else +#if defined(LWS_WITH_ESP32) +#define OPENSSL_NO_TLSEXT +#else +#if defined(LWS_WITH_MBEDTLS) +#include <mbedtls/ssl.h> +#include <mbedtls/x509_crt.h> +#else +#include <openssl/ssl.h> +#include <openssl/evp.h> +#include <openssl/err.h> +#include <openssl/md5.h> +#include <openssl/sha.h> +#ifdef LWS_HAVE_OPENSSL_ECDH_H +#include <openssl/ecdh.h> +#endif +#include <openssl/x509v3.h> +#endif +#if defined(OPENSSL_VERSION_NUMBER) +#if (OPENSSL_VERSION_NUMBER < 0x0009080afL) +/* later openssl defines this to negate the presence of tlsext... but it was only + * introduced at 0.9.8j. Earlier versions don't know it exists so don't + * define it... making it look like the feature exists... + */ +#define OPENSSL_NO_TLSEXT +#endif +#endif +#endif /* not ESP32 */ +#endif /* not USE_WOLFSSL */ +#endif + +#include "libwebsockets.h" +#if defined(WIN32) || defined(_WIN32) +#else +static inline int compatible_close(int fd) { return close(fd); } +#endif + +#if defined(WIN32) || defined(_WIN32) +#include <gettimeofday.h> +#endif + +#if defined(LWS_WITH_ESP8266) +#undef compatible_close +#define compatible_close(fd) { fd->state=ESPCONN_CLOSE; espconn_delete(fd); } +lws_sockfd_type +esp8266_create_tcp_stream_socket(void); +void +esp8266_tcp_stream_bind(lws_sockfd_type fd, int port, struct lws *wsi); +#ifndef BIG_ENDIAN +#define BIG_ENDIAN 4321 /* to show byte order (taken from gcc) */ +#endif +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN 1234 +#endif +#ifndef BYTE_ORDER +#define BYTE_ORDER LITTLE_ENDIAN +#endif +#endif + + +#if defined(WIN32) || defined(_WIN32) + +#ifndef BIG_ENDIAN +#define BIG_ENDIAN 4321 /* to show byte order (taken from gcc) */ +#endif +#ifndef LITTLE_ENDIAN +#define LITTLE_ENDIAN 1234 +#endif +#ifndef BYTE_ORDER +#define BYTE_ORDER LITTLE_ENDIAN +#endif +#ifndef u_int64_t +typedef unsigned __int64 u_int64_t; +#endif + +#undef __P +#ifndef __P +#if __STDC__ +#define __P(protos) protos +#else +#define __P(protos) () +#endif +#endif + +#else + +#include <sys/stat.h> +#include <sys/time.h> + +#if defined(__APPLE__) +#include <machine/endian.h> +#elif defined(__FreeBSD__) +#include <sys/endian.h> +#elif defined(__linux__) +#include <endian.h> +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if defined(__QNX__) + #include <gulliver.h> + #if defined(__LITTLEENDIAN__) + #define BYTE_ORDER __LITTLEENDIAN__ + #define LITTLE_ENDIAN __LITTLEENDIAN__ + #define BIG_ENDIAN 4321 /* to show byte order (taken from gcc); for suppres warning that BIG_ENDIAN is not defined. */ + #endif + #if defined(__BIGENDIAN__) + #define BYTE_ORDER __BIGENDIAN__ + #define LITTLE_ENDIAN 1234 /* to show byte order (taken from gcc); for suppres warning that LITTLE_ENDIAN is not defined. */ + #define BIG_ENDIAN __BIGENDIAN__ + #endif +#endif + +#if defined(__sun) && defined(__GNUC__) + +#include <arpa/nameser_compat.h> + +#if !defined (BYTE_ORDER) +# define BYTE_ORDER __BYTE_ORDER__ +#endif + +#if !defined(LITTLE_ENDIAN) +# define LITTLE_ENDIAN __ORDER_LITTLE_ENDIAN__ +#endif + +#if !defined(BIG_ENDIAN) +# define BIG_ENDIAN __ORDER_BIG_ENDIAN__ +#endif + +#endif /* sun + GNUC */ + +#if !defined(BYTE_ORDER) +# define BYTE_ORDER __BYTE_ORDER +#endif +#if !defined(LITTLE_ENDIAN) +# define LITTLE_ENDIAN __LITTLE_ENDIAN +#endif +#if !defined(BIG_ENDIAN) +# define BIG_ENDIAN __BIG_ENDIAN +#endif + +#endif + +/* + * Mac OSX as well as iOS do not define the MSG_NOSIGNAL flag, + * but happily have something equivalent in the SO_NOSIGPIPE flag. + */ +#ifdef __APPLE__ +#define MSG_NOSIGNAL SO_NOSIGPIPE +#endif + +/* + * Solaris 11.X only supports POSIX 2001, MSG_NOSIGNAL appears in + * POSIX 2008. + */ +#ifdef __sun +#define MSG_NOSIGNAL 0 +#endif + +#ifdef _WIN32 +#ifndef FD_HASHTABLE_MODULUS +#define FD_HASHTABLE_MODULUS 32 +#endif +#endif + +#ifndef LWS_DEF_HEADER_LEN +#define LWS_DEF_HEADER_LEN 4096 +#endif +#ifndef LWS_DEF_HEADER_POOL +#define LWS_DEF_HEADER_POOL 4 +#endif +#ifndef LWS_MAX_PROTOCOLS +#define LWS_MAX_PROTOCOLS 5 +#endif +#ifndef LWS_MAX_EXTENSIONS_ACTIVE +#define LWS_MAX_EXTENSIONS_ACTIVE 2 +#endif +#ifndef LWS_MAX_EXT_OFFERS +#define LWS_MAX_EXT_OFFERS 8 +#endif +#ifndef SPEC_LATEST_SUPPORTED +#define SPEC_LATEST_SUPPORTED 13 +#endif +#ifndef AWAITING_TIMEOUT +#define AWAITING_TIMEOUT 20 +#endif +#ifndef CIPHERS_LIST_STRING +#define CIPHERS_LIST_STRING "DEFAULT" +#endif +#ifndef LWS_SOMAXCONN +#define LWS_SOMAXCONN SOMAXCONN +#endif + +#define MAX_WEBSOCKET_04_KEY_LEN 128 + +#ifndef SYSTEM_RANDOM_FILEPATH +#define SYSTEM_RANDOM_FILEPATH "/dev/urandom" +#endif + +enum lws_websocket_opcodes_07 { + LWSWSOPC_CONTINUATION = 0, + LWSWSOPC_TEXT_FRAME = 1, + LWSWSOPC_BINARY_FRAME = 2, + + LWSWSOPC_NOSPEC__MUX = 7, + + /* control extensions 8+ */ + + LWSWSOPC_CLOSE = 8, + LWSWSOPC_PING = 9, + LWSWSOPC_PONG = 0xa, +}; + + +enum lws_connection_states { + LWSS_HTTP, + LWSS_HTTP_ISSUING_FILE, + LWSS_HTTP_HEADERS, + LWSS_HTTP_BODY, + LWSS_DEAD_SOCKET, + LWSS_ESTABLISHED, + LWSS_CLIENT_HTTP_ESTABLISHED, + LWSS_CLIENT_UNCONNECTED, + LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION, + LWSS_RETURNED_CLOSE_ALREADY, + LWSS_AWAITING_CLOSE_ACK, + LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE, + LWSS_SHUTDOWN, + + LWSS_HTTP2_AWAIT_CLIENT_PREFACE, + LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS, + LWSS_HTTP2_ESTABLISHED, + + LWSS_CGI, +}; + +enum http_version { + HTTP_VERSION_1_0, + HTTP_VERSION_1_1, + HTTP_VERSION_2 +}; + +enum http_connection_type { + HTTP_CONNECTION_CLOSE, + HTTP_CONNECTION_KEEP_ALIVE +}; + +enum lws_rx_parse_state { + LWS_RXPS_NEW, + + LWS_RXPS_04_mask_1, + LWS_RXPS_04_mask_2, + LWS_RXPS_04_mask_3, + + LWS_RXPS_04_FRAME_HDR_1, + LWS_RXPS_04_FRAME_HDR_LEN, + LWS_RXPS_04_FRAME_HDR_LEN16_2, + LWS_RXPS_04_FRAME_HDR_LEN16_1, + LWS_RXPS_04_FRAME_HDR_LEN64_8, + LWS_RXPS_04_FRAME_HDR_LEN64_7, + LWS_RXPS_04_FRAME_HDR_LEN64_6, + LWS_RXPS_04_FRAME_HDR_LEN64_5, + LWS_RXPS_04_FRAME_HDR_LEN64_4, + LWS_RXPS_04_FRAME_HDR_LEN64_3, + LWS_RXPS_04_FRAME_HDR_LEN64_2, + LWS_RXPS_04_FRAME_HDR_LEN64_1, + + LWS_RXPS_07_COLLECT_FRAME_KEY_1, + LWS_RXPS_07_COLLECT_FRAME_KEY_2, + LWS_RXPS_07_COLLECT_FRAME_KEY_3, + LWS_RXPS_07_COLLECT_FRAME_KEY_4, + + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED +}; + +#define LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP 32 + +enum connection_mode { + LWSCM_HTTP_SERVING, + LWSCM_HTTP_SERVING_ACCEPTED, /* actual HTTP service going on */ + LWSCM_PRE_WS_SERVING_ACCEPT, + + LWSCM_WS_SERVING, + LWSCM_WS_CLIENT, + + LWSCM_HTTP2_SERVING, + + /* transient, ssl delay hiding */ + LWSCM_SSL_ACK_PENDING, + LWSCM_SSL_INIT, + /* as above, but complete into LWSCM_RAW */ + LWSCM_SSL_ACK_PENDING_RAW, + LWSCM_SSL_INIT_RAW, + + /* special internal types */ + LWSCM_SERVER_LISTENER, + LWSCM_CGI, /* stdin, stdout, stderr for another cgi master wsi */ + LWSCM_RAW, /* raw with bulk handling */ + LWSCM_RAW_FILEDESC, /* raw without bulk handling */ + + /* HTTP Client related */ + LWSCM_HTTP_CLIENT = LWSCM_FLAG_IMPLIES_CALLBACK_CLOSED_CLIENT_HTTP, + LWSCM_HTTP_CLIENT_ACCEPTED, /* actual HTTP service going on */ + LWSCM_WSCL_WAITING_CONNECT, + LWSCM_WSCL_WAITING_PROXY_REPLY, + LWSCM_WSCL_ISSUE_HANDSHAKE, + LWSCM_WSCL_ISSUE_HANDSHAKE2, + LWSCM_WSCL_ISSUE_HTTP_BODY, + LWSCM_WSCL_WAITING_SSL, + LWSCM_WSCL_WAITING_SERVER_REPLY, + LWSCM_WSCL_WAITING_EXTENSION_CONNECT, + LWSCM_WSCL_PENDING_CANDIDATE_CHILD, + LWSCM_WSCL_WAITING_SOCKS_GREETING_REPLY, + LWSCM_WSCL_WAITING_SOCKS_CONNECT_REPLY, + LWSCM_WSCL_WAITING_SOCKS_AUTH_REPLY, + + /****** add new things just above ---^ ******/ + + +}; + +/* enums of socks version */ +enum socks_version { + SOCKS_VERSION_4 = 4, + SOCKS_VERSION_5 = 5 +}; + +/* enums of subnegotiation version */ +enum socks_subnegotiation_version { + SOCKS_SUBNEGOTIATION_VERSION_1 = 1, +}; + +/* enums of socks commands */ +enum socks_command { + SOCKS_COMMAND_CONNECT = 1, + SOCKS_COMMAND_BIND = 2, + SOCKS_COMMAND_UDP_ASSOCIATE = 3 +}; + +/* enums of socks address type */ +enum socks_atyp { + SOCKS_ATYP_IPV4 = 1, + SOCKS_ATYP_DOMAINNAME = 3, + SOCKS_ATYP_IPV6 = 4 +}; + +/* enums of socks authentication methods */ +enum socks_auth_method { + SOCKS_AUTH_NO_AUTH = 0, + SOCKS_AUTH_GSSAPI = 1, + SOCKS_AUTH_USERNAME_PASSWORD = 2 +}; + +/* enums of subnegotiation status */ +enum socks_subnegotiation_status { + SOCKS_SUBNEGOTIATION_STATUS_SUCCESS = 0, +}; + +/* enums of socks request reply */ +enum socks_request_reply { + SOCKS_REQUEST_REPLY_SUCCESS = 0, + SOCKS_REQUEST_REPLY_FAILURE_GENERAL = 1, + SOCKS_REQUEST_REPLY_CONNECTION_NOT_ALLOWED = 2, + SOCKS_REQUEST_REPLY_NETWORK_UNREACHABLE = 3, + SOCKS_REQUEST_REPLY_HOST_UNREACHABLE = 4, + SOCKS_REQUEST_REPLY_CONNECTION_REFUSED = 5, + SOCKS_REQUEST_REPLY_TTL_EXPIRED = 6, + SOCKS_REQUEST_REPLY_COMMAND_NOT_SUPPORTED = 7, + SOCKS_REQUEST_REPLY_ATYP_NOT_SUPPORTED = 8 +}; + +/* enums used to generate socks messages */ +enum socks_msg_type { + /* greeting */ + SOCKS_MSG_GREETING, + /* credential, user name and password */ + SOCKS_MSG_USERNAME_PASSWORD, + /* connect command */ + SOCKS_MSG_CONNECT +}; + +enum { + LWS_RXFLOW_ALLOW = (1 << 0), + LWS_RXFLOW_PENDING_CHANGE = (1 << 1), +}; + +struct lws_ring { + void *buf; + void (*destroy_element)(void *element); + size_t buflen; + size_t element_len; + uint32_t head; + uint32_t oldest_tail; +}; + +/* this is not usable directly by user code any more, lws_close_reason() */ +#define LWS_WRITE_CLOSE 4 + +struct lws_protocols; +struct lws; + +#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) + +struct lws_io_watcher { +#ifdef LWS_WITH_LIBEV + ev_io ev_watcher; +#endif +#ifdef LWS_WITH_LIBUV + uv_poll_t uv_watcher; +#endif +#ifdef LWS_WITH_LIBEVENT + struct event *event_watcher; +#endif + struct lws_context *context; + + uint8_t actual_events; +}; + +struct lws_signal_watcher { +#ifdef LWS_WITH_LIBEV + ev_signal ev_watcher; +#endif +#ifdef LWS_WITH_LIBUV + uv_signal_t uv_watcher; +#endif +#ifdef LWS_WITH_LIBEVENT + struct event *event_watcher; +#endif + struct lws_context *context; +}; +#endif + +#ifdef _WIN32 +#define LWS_FD_HASH(fd) ((fd ^ (fd >> 8) ^ (fd >> 16)) % FD_HASHTABLE_MODULUS) +struct lws_fd_hashtable { + struct lws **wsi; + int length; +}; +#endif + +/* + * This is totally opaque to code using the library. It's exported as a + * forward-reference pointer-only declaration; the user can use the pointer with + * other APIs to get information out of it. + */ + +#if defined(LWS_WITH_ESP32) +typedef uint16_t ah_data_idx_t; +#else +typedef uint32_t ah_data_idx_t; +#endif + +struct lws_fragments { + ah_data_idx_t offset; + uint16_t len; + uint8_t nfrag; /* which ah->frag[] continues this content, or 0 */ + uint8_t flags; /* only http2 cares */ +}; + +/* + * these are assigned from a pool held in the context. + * Both client and server mode uses them for http header analysis + */ + +struct allocated_headers { + struct allocated_headers *next; /* linked list */ + struct lws *wsi; /* owner */ + char *data; /* prepared by context init to point to dedicated storage */ + ah_data_idx_t data_length; + /* + * the randomly ordered fragments, indexed by frag_index and + * lws_fragments->nfrag for continuation. + */ + struct lws_fragments frags[WSI_TOKEN_COUNT]; + time_t assigned; + /* + * for each recognized token, frag_index says which frag[] his data + * starts in (0 means the token did not appear) + * the actual header data gets dumped as it comes in, into data[] + */ + uint8_t frag_index[WSI_TOKEN_COUNT]; +#if defined(LWS_WITH_ESP32) + uint8_t rx[256]; +#else + uint8_t rx[2048]; +#endif + + int16_t rxpos; + int16_t rxlen; + uint32_t pos; + uint32_t http_response; + int hdr_token_idx; + +#ifndef LWS_NO_CLIENT + char initial_handshake_hash_base64[30]; +#endif + + uint8_t in_use; + uint8_t nfrag; +}; + +/* + * so we can have n connections being serviced simultaneously, + * these things need to be isolated per-thread. + */ + +struct lws_context_per_thread { +#if LWS_MAX_SMP > 1 + pthread_mutex_t lock; +#endif + struct lws_pollfd *fds; +#if defined(LWS_WITH_ESP8266) + struct lws **lws_vs_fds_index; +#endif + struct lws *rx_draining_ext_list; + struct lws *tx_draining_ext_list; + struct lws *timeout_list; +#if defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) + struct lws_context *context; +#endif +#ifdef LWS_WITH_CGI + struct lws_cgi *cgi_list; +#endif + void *http_header_data; + struct allocated_headers *ah_list; + struct lws *ah_wait_list; + int ah_wait_list_length; +#ifdef LWS_OPENSSL_SUPPORT + struct lws *pending_read_list; /* linked list */ +#endif +#if defined(LWS_WITH_LIBEV) + struct ev_loop *io_loop_ev; +#endif +#if defined(LWS_WITH_LIBUV) + uv_loop_t *io_loop_uv; + uv_signal_t signals[8]; + uv_timer_t uv_timeout_watcher; + uv_idle_t uv_idle; +#endif +#if defined(LWS_WITH_LIBEVENT) + struct event_base *io_loop_event_base; +#endif +#if defined(LWS_WITH_LIBEV) + struct lws_io_watcher w_accept; +#endif +#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) + struct lws_signal_watcher w_sigint; + unsigned char ev_loop_foreign:1; +#endif + + unsigned long count_conns; + /* + * usable by anything in the service code, but only if the scope + * does not last longer than the service action (since next service + * of any socket can likewise use it and overwrite) + */ + unsigned char *serv_buf; +#ifdef _WIN32 + WSAEVENT *events; +#else + lws_sockfd_type dummy_pipe_fds[2]; +#endif + unsigned int fds_count; + uint32_t ah_pool_length; + + short ah_count_in_use; + unsigned char tid; + unsigned char lock_depth; +}; + +struct lws_conn_stats { + unsigned long long rx, tx; + unsigned long h1_conn, h1_trans, h2_trans, ws_upg, h2_alpn, h2_subs, + h2_upg, rejected; +}; + +void +lws_sum_stats(const struct lws_context *ctx, struct lws_conn_stats *cs); + + +enum lws_h2_settings { + H2SET_HEADER_TABLE_SIZE = 1, + H2SET_ENABLE_PUSH, + H2SET_MAX_CONCURRENT_STREAMS, + H2SET_INITIAL_WINDOW_SIZE, + H2SET_MAX_FRAME_SIZE, + H2SET_MAX_HEADER_LIST_SIZE, + + H2SET_COUNT /* always last */ +}; + +struct http2_settings { + uint32_t s[H2SET_COUNT]; +}; + +/* + * virtual host -related context information + * vhostwide SSL context + * vhostwide proxy + * + * hierarchy: + * + * context -> vhost -> wsi + * + * incoming connection non-SSL vhost binding: + * + * listen socket -> wsi -> select vhost after first headers + * + * incoming connection SSL vhost binding: + * + * SSL SNI -> wsi -> bind after SSL negotiation + */ + +struct lws_vhost { +#if !defined(LWS_WITH_ESP8266) + char http_proxy_address[128]; + char proxy_basic_auth_token[128]; +#if defined(LWS_WITH_HTTP2) + struct http2_settings set; +#endif +#if defined(LWS_WITH_SOCKS5) + char socks_proxy_address[128]; + char socks_user[96]; + char socks_password[96]; +#endif +#endif +#if defined(LWS_WITH_ESP8266) + /* listen sockets need a place to hang their hat */ + esp_tcp tcp; +#endif + struct lws_conn_stats conn_stats; + struct lws_context *context; + struct lws_vhost *vhost_next; + const struct lws_http_mount *mount_list; + struct lws *lserv_wsi; + const char *name; + const char *iface; +#if !defined(LWS_WITH_ESP8266) && !defined(LWS_WITH_ESP32) && !defined(OPTEE_TA) && !defined(WIN32) + int bind_iface; +#endif + const struct lws_protocols *protocols; + void **protocol_vh_privs; + const struct lws_protocol_vhost_options *pvo; + const struct lws_protocol_vhost_options *headers; + struct lws **same_vh_protocol_list; +#ifdef LWS_OPENSSL_SUPPORT + SSL_CTX *ssl_ctx; + SSL_CTX *ssl_client_ctx; +#endif +#if defined(LWS_WITH_MBEDTLS) + X509 *x509_client_CA; +#endif +#ifndef LWS_NO_EXTENSIONS + const struct lws_extension *extensions; +#endif + void *user; + + int listen_port; + unsigned int http_proxy_port; +#if defined(LWS_WITH_SOCKS5) + unsigned int socks_proxy_port; +#endif + unsigned int options; + int count_protocols; + int ka_time; + int ka_probes; + int ka_interval; + int keepalive_timeout; + int timeout_secs_ah_idle; + int ssl_info_event_mask; +#ifdef LWS_WITH_ACCESS_LOG + int log_fd; +#endif + +#ifdef LWS_OPENSSL_SUPPORT + int use_ssl; + int allow_non_ssl_on_ssl_port; + unsigned int user_supplied_ssl_ctx:1; +#endif + + unsigned int created_vhost_protocols:1; + unsigned int being_destroyed:1; + + unsigned char default_protocol_index; + unsigned char raw_protocol_index; +}; + +struct lws_deferred_free +{ + struct lws_deferred_free *next; + time_t deadline; + void *payload; +}; + +typedef union { +#ifdef LWS_WITH_IPV6 + struct sockaddr_in6 sa6; +#endif + struct sockaddr_in sa4; +} sockaddr46; + + +#if defined(LWS_WITH_PEER_LIMITS) +struct lws_peer { + struct lws_peer *next; + struct lws_peer *peer_wait_list; + + time_t time_created; + time_t time_closed_all; + + uint8_t addr[32]; + uint32_t hash; + uint32_t count_wsi; + uint32_t count_ah; + + uint32_t total_wsi; + uint32_t total_ah; + + uint8_t af; +}; +#endif + +/* + * the rest is managed per-context, that includes + * + * - processwide single fd -> wsi lookup + * - contextwide headers pool + */ + +struct lws_context { + time_t last_timeout_check_s; + time_t last_ws_ping_pong_check_s; + time_t time_up; + const struct lws_plat_file_ops *fops; + struct lws_plat_file_ops fops_platform; +#if defined(LWS_WITH_HTTP2) + struct http2_settings set; +#endif +#if defined(LWS_WITH_ZIP_FOPS) + struct lws_plat_file_ops fops_zip; +#endif + struct lws_context_per_thread pt[LWS_MAX_SMP]; + struct lws_conn_stats conn_stats; +#if LWS_MAX_SMP > 1 + pthread_mutex_t lock; + int lock_depth; +#endif +#ifdef _WIN32 +/* different implementation between unix and windows */ + struct lws_fd_hashtable fd_hashtable[FD_HASHTABLE_MODULUS]; +#else +#if defined(LWS_WITH_ESP8266) + struct espconn **connpool; /* .reverse points to the wsi */ + void *rxd; + int rxd_len; + os_timer_t to_timer; +#else + struct lws **lws_lookup; /* fd to wsi */ +#endif +#endif + struct lws_vhost *vhost_list; + struct lws_vhost *vhost_pending_destruction_list; + struct lws_plugin *plugin_list; + struct lws_deferred_free *deferred_free_list; +#if defined(LWS_WITH_PEER_LIMITS) + struct lws_peer **pl_hash_table; + struct lws_peer *peer_wait_list; + time_t next_cull; +#endif + + void *external_baggage_free_on_destroy; + const struct lws_token_limits *token_limits; + void *user_space; + const char *server_string; + const struct lws_protocol_vhost_options *reject_service_keywords; + lws_reload_func deprecation_cb; + +#if defined(LWS_HAVE_SYS_CAPABILITY_H) && defined(LWS_HAVE_LIBCAP) + cap_value_t caps[4]; + char count_caps; +#endif + +#if defined(LWS_WITH_LIBEV) + lws_ev_signal_cb_t * lws_ev_sigint_cb; +#endif +#if defined(LWS_WITH_LIBUV) + uv_signal_cb lws_uv_sigint_cb; + uv_loop_t pu_loop; +#endif +#if defined(LWS_WITH_LIBEVENT) + lws_event_signal_cb_t * lws_event_sigint_cb; +#endif + char canonical_hostname[128]; +#ifdef LWS_LATENCY + unsigned long worst_latency; + char worst_latency_info[256]; +#endif + +#if defined(LWS_WITH_STATS) + uint64_t lws_stats[LWSSTATS_SIZE]; + uint64_t last_dump; + int updated; +#endif +#if defined(LWS_WITH_ESP32) + unsigned long time_last_state_dump; + uint32_t last_free_heap; +#endif + + int max_fds; +#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) + int use_ev_sigint; +#endif + int started_with_parent; + int uid, gid; + + int fd_random; + + int count_wsi_allocated; + int count_cgi_spawned; + unsigned int options; + unsigned int fd_limit_per_thread; + unsigned int timeout_secs; + unsigned int pt_serv_buf_size; + int max_http_header_data; + int simultaneous_ssl_restriction; + int simultaneous_ssl; +#if defined(LWS_WITH_PEER_LIMITS) + uint32_t pl_hash_elements; /* protected by context->lock */ + uint32_t count_peers; /* protected by context->lock */ + unsigned short ip_limit_ah; + unsigned short ip_limit_wsi; +#endif + unsigned int deprecated:1; + unsigned int being_destroyed:1; + unsigned int being_destroyed1:1; + unsigned int requested_kill:1; + unsigned int protocol_init_done:1; + unsigned int ssl_gate_accepts:1; + unsigned int doing_protocol_init; + /* + * set to the Thread ID that's doing the service loop just before entry + * to poll indicates service thread likely idling in poll() + * volatile because other threads may check it as part of processing + * for pollfd event change. + */ + volatile int service_tid; + int service_tid_detected; + + short max_http_header_pool; + short count_threads; + short plugin_protocol_count; + short plugin_extension_count; + short server_string_len; + unsigned short ws_ping_pong_interval; + unsigned short deprecation_pending_listen_close_count; + + uint8_t max_fi; +}; + +int +lws_check_deferred_free(struct lws_context *context, int force); + +#define lws_get_context_protocol(ctx, x) ctx->vhost_list->protocols[x] +#define lws_get_vh_protocol(vh, x) vh->protocols[x] + +LWS_EXTERN void +lws_close_free_wsi_final(struct lws *wsi); +LWS_EXTERN void +lws_libuv_closehandle(struct lws *wsi); +LWS_EXTERN void +lws_libuv_closehandle_manually(struct lws *wsi); +LWS_EXTERN int +lws_libuv_check_watcher_active(struct lws *wsi); + +LWS_VISIBLE LWS_EXTERN int +lws_plat_plugins_init(struct lws_context * context, const char * const *d); + +LWS_VISIBLE LWS_EXTERN int +lws_plat_plugins_destroy(struct lws_context * context); + +LWS_EXTERN void +lws_restart_ws_ping_pong_timer(struct lws *wsi); + +struct lws * +lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd); + + +enum { + LWS_EV_READ = (1 << 0), + LWS_EV_WRITE = (1 << 1), + LWS_EV_START = (1 << 2), + LWS_EV_STOP = (1 << 3), + + LWS_EV_PREPARE_DELETION = (1 << 31), +}; + +#if defined(LWS_WITH_LIBEV) +LWS_EXTERN void +lws_libev_accept(struct lws *new_wsi, lws_sock_file_fd_type desc); +LWS_EXTERN void +lws_libev_io(struct lws *wsi, int flags); +LWS_EXTERN int +lws_libev_init_fd_table(struct lws_context *context); +LWS_EXTERN void +lws_libev_destroyloop(struct lws_context *context, int tsi); +LWS_EXTERN void +lws_libev_run(const struct lws_context *context, int tsi); +#define LWS_LIBEV_ENABLED(context) lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEV) +LWS_EXTERN void lws_feature_status_libev(struct lws_context_creation_info *info); +#else +#define lws_libev_accept(_a, _b) ((void) 0) +#define lws_libev_io(_a, _b) ((void) 0) +#define lws_libev_init_fd_table(_a) (0) +#define lws_libev_run(_a, _b) ((void) 0) +#define lws_libev_destroyloop(_a, _b) ((void) 0) +#define LWS_LIBEV_ENABLED(context) (0) +#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#define lws_feature_status_libev(_a) \ + lwsl_info("libev support not compiled in\n") +#else +#define lws_feature_status_libev(_a) +#endif +#endif + +#if defined(LWS_WITH_LIBUV) +LWS_EXTERN void +lws_libuv_accept(struct lws *new_wsi, lws_sock_file_fd_type desc); +LWS_EXTERN void +lws_libuv_io(struct lws *wsi, int flags); +LWS_EXTERN int +lws_libuv_init_fd_table(struct lws_context *context); +LWS_EXTERN void +lws_libuv_run(const struct lws_context *context, int tsi); +LWS_EXTERN void +lws_libuv_destroyloop(struct lws_context *context, int tsi); +LWS_EXTERN int +lws_uv_initvhost(struct lws_vhost* vh, struct lws*); +#define LWS_LIBUV_ENABLED(context) lws_check_opt(context->options, LWS_SERVER_OPTION_LIBUV) +LWS_EXTERN void lws_feature_status_libuv(struct lws_context_creation_info *info); +#else +#define lws_libuv_accept(_a, _b) ((void) 0) +#define lws_libuv_io(_a, _b) ((void) 0) +#define lws_libuv_init_fd_table(_a) (0) +#define lws_libuv_run(_a, _b) ((void) 0) +#define lws_libuv_destroyloop(_a, _b) ((void) 0) +#define LWS_LIBUV_ENABLED(context) (0) +#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#define lws_feature_status_libuv(_a) \ + lwsl_notice("libuv support not compiled in\n") +#else +#define lws_feature_status_libuv(_a) +#endif +#endif + +#if defined(LWS_WITH_LIBEVENT) +LWS_EXTERN void +lws_libevent_accept(struct lws *new_wsi, lws_sock_file_fd_type desc); +LWS_EXTERN void +lws_libevent_io(struct lws *wsi, int flags); +LWS_EXTERN int +lws_libevent_init_fd_table(struct lws_context *context); +LWS_EXTERN void +lws_libevent_destroyloop(struct lws_context *context, int tsi); +LWS_EXTERN void +lws_libevent_run(const struct lws_context *context, int tsi); +#define LWS_LIBEVENT_ENABLED(context) lws_check_opt(context->options, LWS_SERVER_OPTION_LIBEVENT) +LWS_EXTERN void lws_feature_status_libevent(struct lws_context_creation_info *info); +#else +#define lws_libevent_accept(_a, _b) ((void) 0) +#define lws_libevent_io(_a, _b) ((void) 0) +#define lws_libevent_init_fd_table(_a) (0) +#define lws_libevent_run(_a, _b) ((void) 0) +#define lws_libevent_destroyloop(_a, _b) ((void) 0) +#define LWS_LIBEVENT_ENABLED(context) (0) +#if LWS_POSIX && !defined(LWS_WITH_ESP32) +#define lws_feature_status_libevent(_a) \ + lwsl_notice("libevent support not compiled in\n") +#else +#define lws_feature_status_libevent(_a) +#endif +#endif + + +#ifdef LWS_WITH_IPV6 +#define LWS_IPV6_ENABLED(vh) \ + (!lws_check_opt(vh->context->options, LWS_SERVER_OPTION_DISABLE_IPV6) && \ + !lws_check_opt(vh->options, LWS_SERVER_OPTION_DISABLE_IPV6)) +#else +#define LWS_IPV6_ENABLED(context) (0) +#endif + +#ifdef LWS_WITH_UNIX_SOCK +#define LWS_UNIX_SOCK_ENABLED(vhost) \ + (vhost->options & LWS_SERVER_OPTION_UNIX_SOCK) +#else +#define LWS_UNIX_SOCK_ENABLED(vhost) (0) +#endif + +enum uri_path_states { + URIPS_IDLE, + URIPS_SEEN_SLASH, + URIPS_SEEN_SLASH_DOT, + URIPS_SEEN_SLASH_DOT_DOT, +}; + +enum uri_esc_states { + URIES_IDLE, + URIES_SEEN_PERCENT, + URIES_SEEN_PERCENT_H1, +}; + +/* notice that these union members: + * + * hdr + * http + * http2 + * + * all have a pointer to allocated_headers struct as their first member. + * + * It means for allocated_headers access, the three union paths can all be + * used interchangeably to access the same data + */ + + +#ifndef LWS_NO_CLIENT +struct client_info_stash { + char address[256]; + char path[4096]; + char host[256]; + char origin[256]; + char protocol[256]; + char method[16]; + char iface[16]; +}; +#endif + +struct _lws_header_related { + /* MUST be first in struct */ + struct allocated_headers *ah; + struct lws *ah_wait_list; + unsigned char *preamble_rx; +#ifndef LWS_NO_CLIENT + struct client_info_stash *stash; +#endif + unsigned int preamble_rx_len; + enum uri_path_states ups; + enum uri_esc_states ues; + short lextable_pos; + unsigned int current_token_limit; + + char esc_stash; + char post_literal_equal; + unsigned char parser_state; /* enum lws_token_indexes */ +}; + +#if defined(LWS_WITH_RANGES) +enum range_states { + LWSRS_NO_ACTIVE_RANGE, + LWSRS_BYTES_EQ, + LWSRS_FIRST, + LWSRS_STARTING, + LWSRS_ENDING, + LWSRS_COMPLETED, + LWSRS_SYNTAX, +}; + +struct lws_range_parsing { + unsigned long long start, end, extent, agg, budget; + const char buf[128]; + int pos; + enum range_states state; + char start_valid, end_valid, ctr, count_ranges, did_try, inside, send_ctr; +}; + +int +lws_ranges_init(struct lws *wsi, struct lws_range_parsing *rp, unsigned long long extent); +int +lws_ranges_next(struct lws_range_parsing *rp); +void +lws_ranges_reset(struct lws_range_parsing *rp); +#endif + +struct _lws_http_mode_related { + /* MUST be first in struct */ + struct allocated_headers *ah; /* mirroring _lws_header_related */ + struct lws *ah_wait_list; + unsigned char *preamble_rx; +#ifndef LWS_NO_CLIENT + struct client_info_stash *stash; +#endif + unsigned int preamble_rx_len; + struct lws *new_wsi_list; + lws_filepos_t filepos; + lws_filepos_t filelen; + lws_fop_fd_t fop_fd; + +#if defined(LWS_WITH_RANGES) + struct lws_range_parsing range; + char multipart_content_type[64]; +#endif + + enum http_version request_version; + enum http_connection_type connection_type; + lws_filepos_t tx_content_length; + lws_filepos_t tx_content_remain; + lws_filepos_t rx_content_length; + lws_filepos_t rx_content_remain; +}; + +#define LWS_H2_FRAME_HEADER_LENGTH 9 + +#ifdef LWS_WITH_HTTP2 + +enum lws_h2_wellknown_frame_types { + LWS_H2_FRAME_TYPE_DATA, + LWS_H2_FRAME_TYPE_HEADERS, + LWS_H2_FRAME_TYPE_PRIORITY, + LWS_H2_FRAME_TYPE_RST_STREAM, + LWS_H2_FRAME_TYPE_SETTINGS, + LWS_H2_FRAME_TYPE_PUSH_PROMISE, + LWS_H2_FRAME_TYPE_PING, + LWS_H2_FRAME_TYPE_GOAWAY, + LWS_H2_FRAME_TYPE_WINDOW_UPDATE, + LWS_H2_FRAME_TYPE_CONTINUATION, + + LWS_H2_FRAME_TYPE_COUNT /* always last */ +}; + +enum lws_h2_flags { + LWS_H2_FLAG_END_STREAM = 1, + LWS_H2_FLAG_END_HEADERS = 4, + LWS_H2_FLAG_PADDED = 8, + LWS_H2_FLAG_PRIORITY = 0x20, + + LWS_H2_FLAG_SETTINGS_ACK = 1, +}; + +enum lws_h2_errors { + H2_ERR_NO_ERROR, /* Graceful shutdown */ + H2_ERR_PROTOCOL_ERROR, /* Protocol error detected */ + H2_ERR_INTERNAL_ERROR, /* Implementation fault */ + H2_ERR_FLOW_CONTROL_ERROR, /* Flow-control limits exceeded */ + H2_ERR_SETTINGS_TIMEOUT, /* Settings not acknowledged */ + H2_ERR_STREAM_CLOSED, /* Frame received for closed stream */ + H2_ERR_FRAME_SIZE_ERROR, /* Frame size incorrect */ + H2_ERR_REFUSED_STREAM, /* Stream not processed */ + H2_ERR_CANCEL, /* Stream cancelled */ + H2_ERR_COMPRESSION_ERROR, /* Compression state not updated */ + H2_ERR_CONNECT_ERROR, /* TCP connection error for CONNECT method */ + H2_ERR_ENHANCE_YOUR_CALM, /* Processing capacity exceeded */ + H2_ERR_INADEQUATE_SECURITY, /* Negotiated TLS parameters not acceptable */ + H2_ERR_HTTP_1_1_REQUIRED, /* Use HTTP/1.1 for the request */ +}; + +enum lws_h2_states { + LWS_H2_STATE_IDLE, + /* + * Send PUSH_PROMISE -> LWS_H2_STATE_RESERVED_LOCAL + * Recv PUSH_PROMISE -> LWS_H2_STATE_RESERVED_REMOTE + * Send HEADERS -> LWS_H2_STATE_OPEN + * Recv HEADERS -> LWS_H2_STATE_OPEN + * + * - Only PUSH_PROMISE + HEADERS valid to send + * - Only HEADERS or PRIORITY valid to receive + */ + LWS_H2_STATE_RESERVED_LOCAL, + /* + * Send RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv RST_STREAM -> LWS_H2_STATE_CLOSED + * Send HEADERS -> LWS_H2_STATE_HALF_CLOSED_REMOTE + * + * - Only HEADERS, RST_STREAM, or PRIORITY valid to send + * - Only RST_STREAM, PRIORITY, or WINDOW_UPDATE valid to receive + */ + LWS_H2_STATE_RESERVED_REMOTE, + /* + * Send RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv HEADERS -> LWS_H2_STATE_HALF_CLOSED_LOCAL + * + * - Only RST_STREAM, WINDOW_UPDATE, or PRIORITY valid to send + * - Only HEADERS, RST_STREAM, or PRIORITY valid to receive + */ + LWS_H2_STATE_OPEN, + /* + * Send RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv RST_STREAM -> LWS_H2_STATE_CLOSED + * Send END_STREAM flag -> LWS_H2_STATE_HALF_CLOSED_LOCAL + * Recv END_STREAM flag -> LWS_H2_STATE_HALF_CLOSED_REMOTE + */ + LWS_H2_STATE_HALF_CLOSED_REMOTE, + /* + * Send RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv RST_STREAM -> LWS_H2_STATE_CLOSED + * Send END_STREAM flag -> LWS_H2_STATE_CLOSED + * + * - Any frame valid to send + * - Only WINDOW_UPDATE, PRIORITY, or RST_STREAM valid to receive + */ + LWS_H2_STATE_HALF_CLOSED_LOCAL, + /* + * Send RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv RST_STREAM -> LWS_H2_STATE_CLOSED + * Recv END_STREAM flag -> LWS_H2_STATE_CLOSED + * + * - Only WINDOW_UPDATE, PRIORITY, and RST_STREAM valid to send + * - Any frame valid to receive + */ + LWS_H2_STATE_CLOSED, + /* + * - Only PRIORITY, WINDOW_UPDATE (IGNORE) and RST_STREAM (IGNORE) + * may be received + * + * - Only PRIORITY valid to send + */ +}; + +#define LWS_H2_STREAM_ID_MASTER 0 +#define LWS_H2_SETTINGS_LEN 6 + +enum http2_hpack_state { + HPKS_TYPE, + + HPKS_IDX_EXT, + + HPKS_HLEN, + HPKS_HLEN_EXT, + + HPKS_DATA, +}; + +/* + * lws general parsimonious header strategy is only store values from known + * headers, and refer to them by index. + * + * That means if we can't map the peer header name to one that lws knows, we + * will drop the content but track the indexing with associated_lws_hdr_idx = + * LWS_HPACK_IGNORE_ENTRY. + */ + +enum http2_hpack_type { + HPKT_INDEXED_HDR_7, /* 1xxxxxxx: just "header field" */ + HPKT_INDEXED_HDR_6_VALUE_INCR, /* 01xxxxxx: NEW indexed hdr with value */ + HPKT_LITERAL_HDR_VALUE_INCR, /* 01000000: NEW literal hdr with value */ + HPKT_INDEXED_HDR_4_VALUE, /* 0000xxxx: indexed hdr with value */ + HPKT_INDEXED_HDR_4_VALUE_NEVER, /* 0001xxxx: indexed hdr with value NEVER NEW */ + HPKT_LITERAL_HDR_VALUE, /* 00000000: literal hdr with value */ + HPKT_LITERAL_HDR_VALUE_NEVER, /* 00010000: literal hdr with value NEVER NEW */ + HPKT_SIZE_5 +}; + +#define LWS_HPACK_IGNORE_ENTRY 0xffff + + +struct hpack_dt_entry { + char *value; /* malloc'd */ + uint16_t value_len; + uint16_t hdr_len; /* virtual, for accounting */ + uint16_t lws_hdr_idx; /* LWS_HPACK_IGNORE_ENTRY = IGNORE */ +}; + +struct hpack_dynamic_table { + struct hpack_dt_entry *entries; /* malloc'd */ + uint32_t virtual_payload_usage; + uint32_t virtual_payload_max; + uint16_t pos; + uint16_t used_entries; + uint16_t num_entries; +}; + +enum lws_h2_protocol_send_type { + LWS_PPS_NONE, + LWS_H2_PPS_MY_SETTINGS, + LWS_H2_PPS_ACK_SETTINGS, + LWS_H2_PPS_PONG, + LWS_H2_PPS_GOAWAY, + LWS_H2_PPS_RST_STREAM, + LWS_H2_PPS_UPDATE_WINDOW, +}; + +struct lws_h2_protocol_send { + struct lws_h2_protocol_send *next; /* linked list */ + enum lws_h2_protocol_send_type type; + + union uu { + struct { + char str[32]; + uint32_t highest_sid; + uint32_t err; + } ga; + struct { + uint32_t sid; + uint32_t err; + } rs; + struct { + uint8_t ping_payload[8]; + } ping; + struct { + uint32_t sid; + uint32_t credit; + } update_window; + } u; +}; + +struct lws_h2_ghost_sid { + struct lws_h2_ghost_sid *next; + uint32_t sid; +}; + +#define LWS_H2_RX_SCRATCH_SIZE 512 + +/* + * http/2 connection info that is only used by the root connection that has + * the network connection. + * + * h2 tends to spawn many child connections from one network connection, so + * it's necessary to make members only needed by the network connection + * distinct and only malloc'd on network connections. + * + * There's only one HPACK parser per network connection. + * + * But there is an ah per logical child connection... the network connection + * fills it but it belongs to the logical child. + */ +struct lws_h2_netconn { + struct http2_settings set; + struct hpack_dynamic_table hpack_dyn_table; + uint8_t ping_payload[8]; + uint8_t one_setting[LWS_H2_SETTINGS_LEN]; + char goaway_str[32]; /* for rx */ + struct lws *swsi; + struct lws_h2_protocol_send *pps; /* linked list */ + char *rx_scratch; + + enum http2_hpack_state hpack; + enum http2_hpack_type hpack_type; + + unsigned int huff:1; + unsigned int value:1; + unsigned int unknown_header:1; + unsigned int cont_exp:1; + unsigned int cont_exp_headers:1; + unsigned int we_told_goaway:1; + unsigned int pad_length:1; + unsigned int collected_priority:1; + unsigned int is_first_header_char:1; + unsigned int seen_nonpseudoheader:1; + unsigned int zero_huff_padding:1; + unsigned int last_action_dyntable_resize:1; + + uint32_t hdr_idx; + uint32_t hpack_len; + uint32_t hpack_e_dep; + uint32_t count; + uint32_t preamble; + uint32_t length; + uint32_t sid; + uint32_t inside; + uint32_t highest_sid; + uint32_t highest_sid_opened; + uint32_t cont_exp_sid; + uint32_t dep; + uint32_t goaway_last_sid; + uint32_t goaway_err; + uint32_t hpack_hdr_len; + + uint32_t rx_scratch_pos; + uint32_t rx_scratch_len; + + uint16_t hpack_pos; + + uint8_t frame_state; + uint8_t type; + uint8_t flags; + uint8_t padding; + uint8_t weight_temp; + uint8_t huff_pad; + char first_hdr_char; + uint8_t hpack_m; + uint8_t ext_count; +}; + +struct _lws_h2_related { + /* + * having this first lets us also re-use all HTTP union code + * and in turn, http_mode_related has allocated headers in right + * place so we can use the header apis on the wsi directly still + */ + struct _lws_http_mode_related http; /* MUST BE FIRST IN STRUCT */ + + struct lws_h2_netconn *h2n; /* malloc'd for root net conn */ + struct lws *parent_wsi; + struct lws *child_list; + struct lws *sibling_list; + + char *pending_status_body; + + int tx_cr; + int peer_tx_cr_est; + unsigned int my_sid; + unsigned int child_count; + int my_priority; + uint32_t dependent_on; + + unsigned int END_STREAM:1; + unsigned int END_HEADERS:1; + unsigned int send_END_STREAM:1; + unsigned int GOING_AWAY; + unsigned int requested_POLLOUT:1; + unsigned int skint:1; + + uint16_t round_robin_POLLOUT; + uint16_t count_POLLOUT_children; + uint8_t h2_state; /* the RFC7540 state of the connection */ + uint8_t weight; + + uint8_t initialized; +}; + +#define HTTP2_IS_TOPLEVEL_WSI(wsi) (!wsi->u.h2.parent_wsi) + +#endif + +struct _lws_websocket_related { + /* cheapest way to deal with ah overlap with ws union transition */ + struct _lws_header_related hdr; + char *rx_ubuf; + unsigned int rx_ubuf_alloc; + struct lws *rx_draining_ext_list; + struct lws *tx_draining_ext_list; + time_t time_next_ping_check; + size_t rx_packet_length; + unsigned int rx_ubuf_head; + unsigned char mask[4]; + /* Also used for close content... control opcode == < 128 */ + unsigned char ping_payload_buf[128 - 3 + LWS_PRE]; + + unsigned char ping_payload_len; + unsigned char mask_idx; + unsigned char opcode; + unsigned char rsv; + unsigned char rsv_first_msg; + /* zero if no info, or length including 2-byte close code */ + unsigned char close_in_ping_buffer_len; + unsigned char utf8; + unsigned char stashed_write_type; + unsigned char tx_draining_stashed_wp; + + unsigned int final:1; + unsigned int frame_is_binary:1; + unsigned int all_zero_nonce:1; + unsigned int this_frame_masked:1; + unsigned int inside_frame:1; /* next write will be more of frame */ + unsigned int clean_buffer:1; /* buffer not rewritten by extension */ + unsigned int payload_is_close:1; /* process as PONG, but it is close */ + unsigned int ping_pending_flag:1; + unsigned int continuation_possible:1; + unsigned int owed_a_fin:1; + unsigned int check_utf8:1; + unsigned int defeat_check_utf8:1; + unsigned int pmce_compressed_message:1; + unsigned int stashed_write_pending:1; + unsigned int rx_draining_ext:1; + unsigned int tx_draining_ext:1; + unsigned int send_check_ping:1; + unsigned int first_fragment:1; +}; + +#ifdef LWS_WITH_CGI + +#define LWS_HTTP_CHUNK_HDR_SIZE 16 + +enum { + SIGNIFICANT_HDR_CONTENT_LENGTH, + SIGNIFICANT_HDR_LOCATION, + SIGNIFICANT_HDR_STATUS, + SIGNIFICANT_HDR_TRANSFER_ENCODING, + + SIGNIFICANT_HDR_COUNT +}; + +/* wsi who is master of the cgi points to an lws_cgi */ + +struct lws_cgi { + struct lws_cgi *cgi_list; + struct lws *stdwsi[3]; /* points to the associated stdin/out/err wsis */ + struct lws *wsi; /* owner */ + unsigned char *headers_buf; + unsigned char *headers_start; + unsigned char *headers_pos; + unsigned char *headers_dumped; + unsigned char *headers_end; + lws_filepos_t content_length; + lws_filepos_t content_length_seen; + int pipe_fds[3][2]; + int match[SIGNIFICANT_HDR_COUNT]; + int pid; + int response_code; + int lp; + char l[12]; + + unsigned int being_closed:1; + unsigned int explicitly_chunked:1; + + unsigned char chunked_grace; +}; +#endif + +signed char char_to_hex(const char c); + +#ifndef LWS_NO_CLIENT +enum lws_chunk_parser { + ELCP_HEX, + ELCP_CR, + ELCP_CONTENT, + ELCP_POST_CR, + ELCP_POST_LF, +}; +#endif + +enum lws_parse_urldecode_results { + LPUR_CONTINUE, + LPUR_SWALLOW, + LPUR_FORBID, + LPUR_EXCESSIVE, +}; + +struct lws_rewrite; + +#ifdef LWS_WITH_ACCESS_LOG +struct lws_access_log { + char *header_log; + char *user_agent; + char *referrer; + unsigned long sent; + int response; +}; +#endif + +struct lws { + + /* structs */ + /* members with mutually exclusive lifetimes are unionized */ + + union u { + struct _lws_http_mode_related http; +#ifdef LWS_WITH_HTTP2 + struct _lws_h2_related h2; +#endif + struct _lws_header_related hdr; + struct _lws_websocket_related ws; + } u; + + /* lifetime members */ + +#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBUV) || defined(LWS_WITH_LIBEVENT) + struct lws_io_watcher w_read; +#endif +#if defined(LWS_WITH_LIBEV) || defined(LWS_WITH_LIBEVENT) + struct lws_io_watcher w_write; +#endif +#ifdef LWS_WITH_ACCESS_LOG + struct lws_access_log access_log; +#endif + time_t pending_timeout_limit; + + /* pointers */ + + struct lws_context *context; + struct lws_vhost *vhost; + struct lws *parent; /* points to parent, if any */ + struct lws *child_list; /* points to first child */ + struct lws *sibling_list; /* subsequent children at same level */ +#ifdef LWS_WITH_CGI + struct lws_cgi *cgi; /* wsi being cgi master have one of these */ +#endif + const struct lws_protocols *protocol; + struct lws **same_vh_protocol_prev, *same_vh_protocol_next; + struct lws *timeout_list; + struct lws **timeout_list_prev; +#if defined(LWS_WITH_PEER_LIMITS) + struct lws_peer *peer; +#endif + + void *user_space; + void *opaque_parent_data; + /* rxflow handling */ + unsigned char *rxflow_buffer; + /* truncated send handling */ + unsigned char *trunc_alloc; /* non-NULL means buffering in progress */ + +#if defined (LWS_WITH_ESP8266) + void *premature_rx; + unsigned short prem_rx_size, prem_rx_pos; +#endif + +#ifndef LWS_NO_EXTENSIONS + const struct lws_extension *active_extensions[LWS_MAX_EXTENSIONS_ACTIVE]; + void *act_ext_user[LWS_MAX_EXTENSIONS_ACTIVE]; +#endif +#ifdef LWS_OPENSSL_SUPPORT + SSL *ssl; + BIO *client_bio; + struct lws *pending_read_list_prev, *pending_read_list_next; +#if defined(LWS_WITH_STATS) + uint64_t accept_start_us; + char seen_rx; +#endif +#endif +#ifdef LWS_WITH_HTTP_PROXY + struct lws_rewrite *rw; +#endif +#ifdef LWS_LATENCY + unsigned long action_start; + unsigned long latency_start; +#endif + lws_sock_file_fd_type desc; /* .filefd / .sockfd */ +#if defined(LWS_WITH_STATS) + uint64_t active_writable_req_us; +#endif + /* ints */ + int position_in_fds_table; + uint32_t rxflow_len; + uint32_t rxflow_pos; + unsigned int trunc_alloc_len; /* size of malloc */ + unsigned int trunc_offset; /* where we are in terms of spilling */ + unsigned int trunc_len; /* how much is buffered */ +#ifndef LWS_NO_CLIENT + int chunk_remaining; +#endif + unsigned int cache_secs; + + unsigned int hdr_parsing_completed:1; + unsigned int http2_substream:1; + unsigned int upgraded_to_http2:1; + unsigned int listener:1; + unsigned int user_space_externally_allocated:1; + unsigned int socket_is_permanently_unusable:1; + unsigned int rxflow_change_to:2; + unsigned int more_rx_waiting:1; /* has to live here since ah may stick to end */ + unsigned int conn_stat_done:1; + unsigned int cache_reuse:1; + unsigned int cache_revalidate:1; + unsigned int cache_intermediaries:1; + unsigned int favoured_pollin:1; + unsigned int sending_chunked:1; + unsigned int already_did_cce:1; + unsigned int told_user_closed:1; + unsigned int waiting_to_send_close_frame:1; + unsigned int ipv6:1; + unsigned int parent_carries_io:1; + unsigned int parent_pending_cb_on_writable:1; + unsigned int cgi_stdout_zero_length:1; + unsigned int seen_zero_length_recv:1; + unsigned int rxflow_will_be_applied:1; + +#if defined(LWS_WITH_ESP8266) + unsigned int pending_send_completion:3; + unsigned int close_is_pending_send_completion:1; +#endif +#ifdef LWS_WITH_ACCESS_LOG + unsigned int access_log_pending:1; +#endif +#ifndef LWS_NO_CLIENT + unsigned int do_ws:1; /* whether we are doing http or ws flow */ + unsigned int chunked:1; /* if the clientside connection is chunked */ + unsigned int client_rx_avail:1; + unsigned int client_http_body_pending:1; +#endif +#ifdef LWS_WITH_HTTP_PROXY + unsigned int perform_rewrite:1; +#endif +#ifndef LWS_NO_EXTENSIONS + unsigned int extension_data_pending:1; +#endif +#ifdef LWS_OPENSSL_SUPPORT + unsigned int use_ssl:4; +#endif +#ifdef _WIN32 + unsigned int sock_send_blocking:1; +#endif +#ifdef LWS_OPENSSL_SUPPORT + unsigned int redirect_to_https:1; +#endif + + /* volatile to make sure code is aware other thread can change */ + volatile unsigned int handling_pollout:1; + volatile unsigned int leave_pollout_active:1; + +#ifndef LWS_NO_CLIENT + unsigned short c_port; +#endif + + /* chars */ +#ifndef LWS_NO_EXTENSIONS + unsigned char count_act_ext; +#endif + uint8_t ietf_spec_revision; + char mode; /* enum connection_mode */ + char state; /* enum lws_connection_states */ + char state_pre_close; + char lws_rx_parse_state; /* enum lws_rx_parse_state */ + char rx_frame_type; /* enum lws_write_protocol */ + char pending_timeout; /* enum pending_timeout */ + char tsi; /* thread service index we belong to */ + char protocol_interpret_idx; + char redirects; + uint8_t rxflow_bitmap; +#ifdef LWS_WITH_CGI + char cgi_channel; /* which of stdin/out/err */ + char hdr_state; +#endif +#ifndef LWS_NO_CLIENT + char chunk_parser; /* enum lws_chunk_parser */ +#endif +#if defined(LWS_WITH_CGI) || !defined(LWS_NO_CLIENT) + char reason_bf; /* internal writeable callback reason bitfield */ +#endif +}; + +#define lws_is_flowcontrolled(w) (!!(wsi->rxflow_bitmap)) + +LWS_EXTERN int log_level; + +LWS_EXTERN int +lws_socket_bind(struct lws_vhost *vhost, lws_sockfd_type sockfd, int port, + const char *iface); + +#if defined(LWS_WITH_IPV6) +LWS_EXTERN unsigned long +lws_get_addr_scope(const char *ipaddr); +#endif + +LWS_EXTERN void +lws_close_free_wsi(struct lws *wsi, enum lws_close_status); + +LWS_EXTERN void +lws_free_wsi(struct lws *wsi); + +LWS_EXTERN int +remove_wsi_socket_from_fds(struct lws *wsi); +LWS_EXTERN int +lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len); + +#ifndef LWS_LATENCY +static inline void +lws_latency(struct lws_context *context, struct lws *wsi, const char *action, + int ret, int completion) { + do { + (void)context; (void)wsi; (void)action; (void)ret; + (void)completion; + } while (0); +} +static inline void +lws_latency_pre(struct lws_context *context, struct lws *wsi) { + do { (void)context; (void)wsi; } while (0); +} +#else +#define lws_latency_pre(_context, _wsi) lws_latency(_context, _wsi, NULL, 0, 0) +extern void +lws_latency(struct lws_context *context, struct lws *wsi, const char *action, + int ret, int completion); +#endif + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_client_rx_sm(struct lws *wsi, unsigned char c); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_parse(struct lws *wsi, unsigned char c); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_parse_urldecode(struct lws *wsi, uint8_t *_c); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_http_action(struct lws *wsi); + +LWS_EXTERN int +lws_b64_selftest(void); + +LWS_EXTERN int +lws_service_flag_pending(struct lws_context *context, int tsi); + +#if defined(_WIN32) || defined(LWS_WITH_ESP8266) +LWS_EXTERN struct lws * +wsi_from_fd(const struct lws_context *context, lws_sockfd_type fd); + +LWS_EXTERN int +insert_wsi(struct lws_context *context, struct lws *wsi); + +LWS_EXTERN int +delete_from_fd(struct lws_context *context, lws_sockfd_type fd); +#else +#define wsi_from_fd(A,B) A->lws_lookup[B] +#define insert_wsi(A,B) assert(A->lws_lookup[B->desc.sockfd] == 0); A->lws_lookup[B->desc.sockfd]=B +#define delete_from_fd(A,B) A->lws_lookup[B]=0 +#endif + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +insert_wsi_socket_into_fds(struct lws_context *context, struct lws *wsi); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_issue_raw(struct lws *wsi, unsigned char *buf, size_t len); + + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_service_timeout_check(struct lws *wsi, unsigned int sec); + +LWS_EXTERN void +lws_remove_from_timeout_list(struct lws *wsi); + +LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +lws_client_connect_2(struct lws *wsi); + +LWS_VISIBLE struct lws * LWS_WARN_UNUSED_RESULT +lws_client_reset(struct lws **wsi, int ssl, const char *address, int port, + const char *path, const char *host); + +LWS_EXTERN struct lws * LWS_WARN_UNUSED_RESULT +lws_create_new_server_wsi(struct lws_vhost *vhost); + +LWS_EXTERN char * LWS_WARN_UNUSED_RESULT +lws_generate_client_handshake(struct lws *wsi, char *pkt); + +LWS_EXTERN int +lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd); + +LWS_EXTERN struct lws * +lws_client_connect_via_info2(struct lws *wsi); + +LWS_EXTERN int +_lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah); + +/* + * EXTENSIONS + */ + +#ifndef LWS_NO_EXTENSIONS +LWS_VISIBLE void +lws_context_init_extensions(struct lws_context_creation_info *info, + struct lws_context *context); +LWS_EXTERN int +lws_any_extension_handled(struct lws *wsi, enum lws_extension_callback_reasons r, + void *v, size_t len); + +LWS_EXTERN int +lws_ext_cb_active(struct lws *wsi, int reason, void *buf, int len); +LWS_EXTERN int +lws_ext_cb_all_exts(struct lws_context *context, struct lws *wsi, int reason, + void *arg, int len); + +#else +#define lws_any_extension_handled(_a, _b, _c, _d) (0) +#define lws_ext_cb_active(_a, _b, _c, _d) (0) +#define lws_ext_cb_all_exts(_a, _b, _c, _d, _e) (0) +#define lws_issue_raw_ext_access lws_issue_raw +#define lws_context_init_extensions(_a, _b) +#endif + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_client_interpret_server_handshake(struct lws *wsi); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_rx_sm(struct lws *wsi, unsigned char c); + +LWS_EXTERN int +lws_payload_until_length_exhausted(struct lws *wsi, unsigned char **buf, size_t *len); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_issue_raw_ext_access(struct lws *wsi, unsigned char *buf, size_t len); + +LWS_EXTERN void +lws_union_transition(struct lws *wsi, enum connection_mode mode); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +user_callback_handle_rxflow(lws_callback_function, struct lws *wsi, + enum lws_callback_reasons reason, void *user, + void *in, size_t len); +#ifdef LWS_WITH_HTTP2 +struct lws * lws_h2_get_nth_child(struct lws *wsi, int n); +LWS_EXTERN void lws_h2_init(struct lws *wsi); +LWS_EXTERN int +lws_h2_settings(struct lws *nwsi, struct http2_settings *settings, + unsigned char *buf, int len); +LWS_EXTERN int +lws_h2_parser(struct lws *wsi, unsigned char c); +LWS_EXTERN int lws_h2_do_pps_send(struct lws *wsi); +LWS_EXTERN int lws_h2_frame_write(struct lws *wsi, int type, int flags, + unsigned int sid, unsigned int len, + unsigned char *buf); +LWS_EXTERN struct lws * +lws_h2_wsi_from_id(struct lws *wsi, unsigned int sid); +LWS_EXTERN int lws_hpack_interpret(struct lws *wsi, + unsigned char c); +LWS_EXTERN int +lws_add_http2_header_by_name(struct lws *wsi, + const unsigned char *name, + const unsigned char *value, int length, + unsigned char **p, unsigned char *end); +LWS_EXTERN int +lws_add_http2_header_by_token(struct lws *wsi, + enum lws_token_indexes token, + const unsigned char *value, int length, + unsigned char **p, unsigned char *end); +LWS_EXTERN int +lws_add_http2_header_status(struct lws *wsi, + unsigned int code, unsigned char **p, + unsigned char *end); +LWS_EXTERN int +lws_h2_configure_if_upgraded(struct lws *wsi); +LWS_EXTERN void +lws_hpack_destroy_dynamic_header(struct lws *wsi); +LWS_EXTERN int +lws_hpack_dynamic_size(struct lws *wsi, int size); +LWS_EXTERN int +lws_h2_goaway(struct lws *wsi, uint32_t err, const char *reason); +LWS_EXTERN int +lws_h2_tx_cr_get(struct lws *wsi); +LWS_EXTERN void +lws_h2_tx_cr_consume(struct lws *wsi, int consumed); +LWS_EXTERN int +lws_hdr_extant(struct lws *wsi, enum lws_token_indexes h); +LWS_EXTERN void +lws_pps_schedule(struct lws *wsi, struct lws_h2_protocol_send *pss); + +LWS_EXTERN const struct http2_settings lws_h2_defaults; +#else +#define lws_h2_configure_if_upgraded(x) +#endif + +LWS_EXTERN int +lws_plat_set_socket_options(struct lws_vhost *vhost, lws_sockfd_type fd); + +LWS_EXTERN int +lws_plat_check_connection_error(struct lws *wsi); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_header_table_attach(struct lws *wsi, int autoservice); + +LWS_EXTERN int +lws_header_table_detach(struct lws *wsi, int autoservice); + +LWS_EXTERN void +lws_header_table_reset(struct lws *wsi, int autoservice); +void +_lws_header_table_reset(struct allocated_headers *ah); + +void +lws_header_table_force_to_detachable_state(struct lws *wsi); +int +lws_header_table_is_in_detachable_state(struct lws *wsi); + +LWS_EXTERN char * LWS_WARN_UNUSED_RESULT +lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ensure_user_space(struct lws *wsi); + +LWS_EXTERN int +lws_change_pollfd(struct lws *wsi, int _and, int _or); + +#ifndef LWS_NO_SERVER +int lws_context_init_server(struct lws_context_creation_info *info, + struct lws_vhost *vhost); +LWS_EXTERN struct lws_vhost * +lws_select_vhost(struct lws_context *context, int port, const char *servername); +LWS_EXTERN int +handshake_0405(struct lws_context *context, struct lws *wsi); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len); +LWS_EXTERN void +lws_server_get_canonical_hostname(struct lws_context *context, + struct lws_context_creation_info *info); +#else +#define lws_context_init_server(_a, _b) (0) +#define lws_interpret_incoming_packet(_a, _b, _c) (0) +#define lws_server_get_canonical_hostname(_a, _b) +#endif + +#ifndef LWS_NO_DAEMONIZE +LWS_EXTERN int get_daemonize_pid(); +#else +#define get_daemonize_pid() (0) +#endif + +#if !defined(LWS_WITH_ESP8266) +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +interface_to_sa(struct lws_vhost *vh, const char *ifname, + struct sockaddr_in *addr, size_t addrlen); +#endif +LWS_EXTERN void lwsl_emit_stderr(int level, const char *line); + +enum lws_ssl_capable_status { + LWS_SSL_CAPABLE_ERROR = -1, + LWS_SSL_CAPABLE_MORE_SERVICE = -2, +}; + +#ifndef LWS_OPENSSL_SUPPORT +#define LWS_SSL_ENABLED(context) (0) +#define lws_context_init_server_ssl(_a, _b) (0) +#define lws_ssl_destroy(_a) +#define lws_context_init_http2_ssl(_a) +#define lws_ssl_capable_read lws_ssl_capable_read_no_ssl +#define lws_ssl_capable_write lws_ssl_capable_write_no_ssl +#define lws_ssl_pending lws_ssl_pending_no_ssl +#define lws_server_socket_service_ssl(_b, _c) (0) +#define lws_ssl_close(_a) (0) +#define lws_ssl_context_destroy(_a) +#define lws_ssl_SSL_CTX_destroy(_a) +#define lws_ssl_remove_wsi_from_buffered_list(_a) +#define lws_context_init_ssl_library(_a) +#define lws_ssl_anybody_has_buffered_read_tsi(_a, _b) (0) +#else +#define LWS_SSL_ENABLED(context) (context->use_ssl) +LWS_EXTERN int openssl_websocket_private_data_index; +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_pending(struct lws *wsi); +LWS_EXTERN int +lws_context_init_ssl_library(struct lws_context_creation_info *info); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_server_socket_service_ssl(struct lws *new_wsi, lws_sockfd_type accept_fd); +LWS_EXTERN int +lws_ssl_close(struct lws *wsi); +LWS_EXTERN void +lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost); +LWS_EXTERN void +lws_ssl_context_destroy(struct lws_context *context); +LWS_VISIBLE void +lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi); +LWS_EXTERN int +lws_ssl_client_bio_create(struct lws *wsi); +LWS_EXTERN int +lws_ssl_client_connect1(struct lws *wsi); +LWS_EXTERN int +lws_ssl_client_connect2(struct lws *wsi); +LWS_EXTERN void +lws_ssl_elaborate_error(void); +LWS_EXTERN int +lws_ssl_anybody_has_buffered_read_tsi(struct lws_context *context, int tsi); +#ifndef LWS_NO_SERVER +LWS_EXTERN int +lws_context_init_server_ssl(struct lws_context_creation_info *info, + struct lws_vhost *vhost); +#else +#define lws_context_init_server_ssl(_a, _b) (0) +#endif +LWS_EXTERN void +lws_ssl_destroy(struct lws_vhost *vhost); +/* HTTP2-related */ + +#ifdef LWS_WITH_HTTP2 +LWS_EXTERN void +lws_context_init_http2_ssl(struct lws_vhost *vhost); +#else +#define lws_context_init_http2_ssl(_a) +#endif +#endif + +#if LWS_MAX_SMP > 1 +static LWS_INLINE void +lws_pt_mutex_init(struct lws_context_per_thread *pt) +{ + pthread_mutex_init(&pt->lock, NULL); +} + +static LWS_INLINE void +lws_pt_mutex_destroy(struct lws_context_per_thread *pt) +{ + pthread_mutex_destroy(&pt->lock); +} + +static LWS_INLINE void +lws_pt_lock(struct lws_context_per_thread *pt) +{ + if (!pt->lock_depth++) + pthread_mutex_lock(&pt->lock); +} + +static LWS_INLINE void +lws_pt_unlock(struct lws_context_per_thread *pt) +{ + if (!(--pt->lock_depth)) + pthread_mutex_unlock(&pt->lock); +} +static LWS_INLINE void +lws_context_lock(struct lws_context *context) +{ + if (!context->lock_depth++) + pthread_mutex_lock(&context->lock); +} + +static LWS_INLINE void +lws_context_unlock(struct lws_context *context) +{ + if (!(--context->lock_depth)) + pthread_mutex_unlock(&context->lock); +} + +#else +#define lws_pt_mutex_init(_a) (void)(_a) +#define lws_pt_mutex_destroy(_a) (void)(_a) +#define lws_pt_lock(_a) (void)(_a) +#define lws_pt_unlock(_a) (void)(_a) +#define lws_context_lock(_a) (void)(_a) +#define lws_context_unlock(_a) (void)(_a) +#endif + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_capable_read_no_ssl(struct lws *wsi, unsigned char *buf, int len); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_capable_write_no_ssl(struct lws *wsi, unsigned char *buf, int len); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_ssl_pending_no_ssl(struct lws *wsi); + +#ifdef LWS_WITH_HTTP_PROXY +struct lws_rewrite { + hubbub_parser *parser; + hubbub_parser_optparams params; + const char *from, *to; + int from_len, to_len; + unsigned char *p, *end; + struct lws *wsi; +}; +static LWS_INLINE int hstrcmp(hubbub_string *s, const char *p, int len) +{ + if (s->len != len) + return 1; + + return strncmp((const char *)s->ptr, p, len); +} +typedef hubbub_error (*hubbub_callback_t)(const hubbub_token *token, void *pw); +LWS_EXTERN struct lws_rewrite * +lws_rewrite_create(struct lws *wsi, hubbub_callback_t cb, const char *from, const char *to); +LWS_EXTERN void +lws_rewrite_destroy(struct lws_rewrite *r); +LWS_EXTERN int +lws_rewrite_parse(struct lws_rewrite *r, const unsigned char *in, int in_len); +#endif + +#ifndef LWS_NO_CLIENT +LWS_EXTERN int lws_client_socket_service(struct lws_context *context, + struct lws *wsi, + struct lws_pollfd *pollfd); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_http_transaction_completed_client(struct lws *wsi); +#ifdef LWS_OPENSSL_SUPPORT +LWS_EXTERN int +lws_context_init_client_ssl(struct lws_context_creation_info *info, + struct lws_vhost *vhost); + +LWS_EXTERN void +lws_ssl_info_callback(const SSL *ssl, int where, int ret); + +#else + #define lws_context_init_client_ssl(_a, _b) (0) +#endif +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_handshake_client(struct lws *wsi, unsigned char **buf, size_t len); +LWS_EXTERN void +lws_decode_ssl_error(void); +#else +#define lws_context_init_client_ssl(_a, _b) (0) +#define lws_handshake_client(_a, _b, _c) (0) +#endif + +LWS_EXTERN int +_lws_rx_flow_control(struct lws *wsi); + +LWS_EXTERN int +_lws_change_pollfd(struct lws *wsi, int _and, int _or, struct lws_pollargs *pa); + +#ifndef LWS_NO_SERVER +LWS_EXTERN int +lws_server_socket_service(struct lws_context *context, struct lws *wsi, + struct lws_pollfd *pollfd); +LWS_EXTERN int +lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len); +#else +#define lws_server_socket_service(_a, _b, _c) (0) +#define lws_handshake_server(_a, _b, _c) (0) +#endif + +#ifdef LWS_WITH_ACCESS_LOG +LWS_EXTERN int +lws_access_log(struct lws *wsi); +LWS_EXTERN void +lws_prepare_access_log_info(struct lws *wsi, char *uri_ptr, int meth); +#else +#define lws_access_log(_a) +#endif + +LWS_EXTERN int +lws_cgi_kill_terminated(struct lws_context_per_thread *pt); + +LWS_EXTERN void +lws_cgi_remove_and_kill(struct lws *wsi); + +int +lws_protocol_init(struct lws_context *context); + +int +lws_bind_protocol(struct lws *wsi, const struct lws_protocols *p); + +const struct lws_http_mount * +lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len); + +/* + * custom allocator + */ +LWS_EXTERN void * +lws_realloc(void *ptr, size_t size, const char *reason); + +LWS_EXTERN void * LWS_WARN_UNUSED_RESULT +lws_zalloc(size_t size, const char *reason); + +#ifdef LWS_PLAT_OPTEE +void *lws_malloc(size_t size, const char *reason); +void lws_free(void *p); +#define lws_free_set_NULL(P) do { lws_free(P); (P) = NULL; } while(0) +#else +#define lws_malloc(S, R) lws_realloc(NULL, S, R) +#define lws_free(P) lws_realloc(P, 0, "lws_free") +#define lws_free_set_NULL(P) do { lws_realloc(P, 0, "free"); (P) = NULL; } while(0) +#endif + +const struct lws_plat_file_ops * +lws_vfs_select_fops(const struct lws_plat_file_ops *fops, const char *vfs_path, + const char **vpath); + +/* lws_plat_ */ +LWS_EXTERN void +lws_plat_delete_socket_from_fds(struct lws_context *context, + struct lws *wsi, int m); +LWS_EXTERN void +lws_plat_insert_socket_into_fds(struct lws_context *context, + struct lws *wsi); +LWS_EXTERN void +lws_plat_service_periodic(struct lws_context *context); + +LWS_EXTERN int +lws_plat_change_pollfd(struct lws_context *context, struct lws *wsi, + struct lws_pollfd *pfd); +LWS_EXTERN void +lws_add_wsi_to_draining_ext_list(struct lws *wsi); +LWS_EXTERN void +lws_remove_wsi_from_draining_ext_list(struct lws *wsi); +LWS_EXTERN int +lws_plat_context_early_init(void); +LWS_EXTERN void +lws_plat_context_early_destroy(struct lws_context *context); +LWS_EXTERN void +lws_plat_context_late_destroy(struct lws_context *context); +LWS_EXTERN int +lws_poll_listen_fd(struct lws_pollfd *fd); +LWS_EXTERN int +lws_plat_service(struct lws_context *context, int timeout_ms); +LWS_EXTERN LWS_VISIBLE int +_lws_plat_service_tsi(struct lws_context *context, int timeout_ms, int tsi); +LWS_EXTERN int +lws_plat_init(struct lws_context *context, + struct lws_context_creation_info *info); +LWS_EXTERN void +lws_plat_drop_app_privileges(struct lws_context_creation_info *info); +LWS_EXTERN unsigned long long +time_in_microseconds(void); +LWS_EXTERN const char * LWS_WARN_UNUSED_RESULT +lws_plat_inet_ntop(int af, const void *src, char *dst, int cnt); +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_plat_inet_pton(int af, const char *src, void *dst); + +LWS_EXTERN int LWS_WARN_UNUSED_RESULT +lws_check_utf8(unsigned char *state, unsigned char *buf, size_t len); +LWS_EXTERN int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount); +LWS_EXTERN int alloc_pem_to_der_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount); + +LWS_EXTERN void +lws_same_vh_protocol_remove(struct lws *wsi); +LWS_EXTERN void +lws_same_vh_protocol_insert(struct lws *wsi, int n); + +#if defined(LWS_WITH_STATS) +void +lws_stats_atomic_bump(struct lws_context * context, + struct lws_context_per_thread *pt, int index, uint64_t bump); +void +lws_stats_atomic_max(struct lws_context * context, + struct lws_context_per_thread *pt, int index, uint64_t val); +#else +static inline uint64_t lws_stats_atomic_bump(struct lws_context * context, + struct lws_context_per_thread *pt, int index, uint64_t bump) { + (void)context; (void)pt; (void)index; (void)bump; return 0; } +static inline uint64_t lws_stats_atomic_max(struct lws_context * context, + struct lws_context_per_thread *pt, int index, uint64_t val) { + (void)context; (void)pt; (void)index; (void)val; return 0; } +#endif + +/* socks */ +void socks_generate_msg(struct lws *wsi, enum socks_msg_type type, + ssize_t *msg_len); + +#if defined(LWS_WITH_PEER_LIMITS) +void +lws_peer_track_wsi_close(struct lws_context *context, struct lws_peer *peer); +int +lws_peer_confirm_ah_attach_ok(struct lws_context *context, struct lws_peer *peer); +void +lws_peer_track_ah_detach(struct lws_context *context, struct lws_peer *peer); +void +lws_peer_cull_peer_wait_list(struct lws_context *context); +struct lws_peer * +lws_get_or_create_peer(struct lws_vhost *vhost, lws_sockfd_type sockfd); +void +lws_peer_add_wsi(struct lws_context *context, struct lws_peer *peer, + struct lws *wsi); +#endif + +#ifdef __cplusplus +}; +#endif diff --git a/thirdparty/lws/server/fops-zip.c b/thirdparty/lws/server/fops-zip.c new file mode 100644 index 0000000000..2b254f67af --- /dev/null +++ b/thirdparty/lws/server/fops-zip.c @@ -0,0 +1,669 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Original code used in this source file: + * + * https://github.com/PerBothner/DomTerm.git @912add15f3d0aec + * + * ./lws-term/io.c + * ./lws-term/junzip.c + * + * Copyright (C) 2017 Per Bothner <per@bothner.com> + * + * MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * ( copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + * + * + * lws rewrite: + * + * Copyright (C) 2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +#include <zlib.h> + +/* + * This code works with zip format containers which may have files compressed + * with gzip deflate (type 8) or store uncompressed (type 0). + * + * Linux zip produces such zipfiles by default, eg + * + * $ zip ../myzip.zip file1 file2 file3 + */ + +#define ZIP_COMPRESSION_METHOD_STORE 0 +#define ZIP_COMPRESSION_METHOD_DEFLATE 8 + +typedef struct { + lws_filepos_t filename_start; + uint32_t crc32; + uint32_t comp_size; + uint32_t uncomp_size; + uint32_t offset; + uint32_t mod_time; + uint16_t filename_len; + uint16_t extra; + uint16_t method; + uint16_t file_com_len; +} lws_fops_zip_hdr_t; + +typedef struct { + struct lws_fop_fd fop_fd; /* MUST BE FIRST logical fop_fd into + * file inside zip: fops_zip fops */ + lws_fop_fd_t zip_fop_fd; /* logical fop fd on to zip file + * itself: using platform fops */ + lws_fops_zip_hdr_t hdr; + z_stream inflate; + lws_filepos_t content_start; + lws_filepos_t exp_uncomp_pos; + union { + uint8_t trailer8[8]; + uint32_t trailer32[2]; + } u; + uint8_t rbuf[128]; /* decompression chunk size */ + int entry_count; + + unsigned int decompress:1; /* 0 = direct from file */ + unsigned int add_gzip_container:1; +} *lws_fops_zip_t; + +struct lws_plat_file_ops fops_zip; +#define fop_fd_to_priv(FD) ((lws_fops_zip_t)(FD)) + +static const uint8_t hd[] = { 31, 139, 8, 0, 0, 0, 0, 0, 0, 3 }; + +enum { + ZC_SIGNATURE = 0, + ZC_VERSION_MADE_BY = 4, + ZC_VERSION_NEEDED_TO_EXTRACT = 6, + ZC_GENERAL_PURPOSE_BIT_FLAG = 8, + ZC_COMPRESSION_METHOD = 10, + ZC_LAST_MOD_FILE_TIME = 12, + ZC_LAST_MOD_FILE_DATE = 14, + ZC_CRC32 = 16, + ZC_COMPRESSED_SIZE = 20, + ZC_UNCOMPRESSED_SIZE = 24, + ZC_FILE_NAME_LENGTH = 28, + ZC_EXTRA_FIELD_LENGTH = 30, + + ZC_FILE_COMMENT_LENGTH = 32, + ZC_DISK_NUMBER_START = 34, + ZC_INTERNAL_FILE_ATTRIBUTES = 36, + ZC_EXTERNAL_FILE_ATTRIBUTES = 38, + ZC_REL_OFFSET_LOCAL_HEADER = 42, + ZC_DIRECTORY_LENGTH = 46, + + ZE_SIGNATURE_OFFSET = 0, + ZE_DESK_NUMBER = 4, + ZE_CENTRAL_DIRECTORY_DISK_NUMBER = 6, + ZE_NUM_ENTRIES_THIS_DISK = 8, + ZE_NUM_ENTRIES = 10, + ZE_CENTRAL_DIRECTORY_SIZE = 12, + ZE_CENTRAL_DIR_OFFSET = 16, + ZE_ZIP_COMMENT_LENGTH = 20, + ZE_DIRECTORY_LENGTH = 22, + + ZL_REL_OFFSET_CONTENT = 28, + ZL_HEADER_LENGTH = 30, + + LWS_FZ_ERR_SEEK_END_RECORD = 1, + LWS_FZ_ERR_READ_END_RECORD, + LWS_FZ_ERR_END_RECORD_MAGIC, + LWS_FZ_ERR_END_RECORD_SANITY, + LWS_FZ_ERR_CENTRAL_SEEK, + LWS_FZ_ERR_CENTRAL_READ, + LWS_FZ_ERR_CENTRAL_SANITY, + LWS_FZ_ERR_NAME_TOO_LONG, + LWS_FZ_ERR_NAME_SEEK, + LWS_FZ_ERR_NAME_READ, + LWS_FZ_ERR_CONTENT_SANITY, + LWS_FZ_ERR_CONTENT_SEEK, + LWS_FZ_ERR_SCAN_SEEK, + LWS_FZ_ERR_NOT_FOUND, + LWS_FZ_ERR_ZLIB_INIT, + LWS_FZ_ERR_READ_CONTENT, + LWS_FZ_ERR_SEEK_COMPRESSED, +}; + +static uint16_t +get_u16(void *p) +{ + const uint8_t *c = (const uint8_t *)p; + + return (uint16_t)((c[0] | (c[1] << 8))); +} + +static uint32_t +get_u32(void *p) +{ + const uint8_t *c = (const uint8_t *)p; + + return (uint32_t)((c[0] | (c[1] << 8) | (c[2] << 16) | (c[3] << 24))); +} + +int +lws_fops_zip_scan(lws_fops_zip_t priv, const char *name, int len) +{ + lws_filepos_t amount; + uint8_t buf[96]; + int i; + + if (lws_vfs_file_seek_end(priv->zip_fop_fd, -ZE_DIRECTORY_LENGTH) < 0) + return LWS_FZ_ERR_SEEK_END_RECORD; + + if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf, + ZE_DIRECTORY_LENGTH)) + return LWS_FZ_ERR_READ_END_RECORD; + + if (amount != ZE_DIRECTORY_LENGTH) + return LWS_FZ_ERR_READ_END_RECORD; + + /* + * We require the zip to have the last record right at the end + * Linux zip always does this if no zip comment. + */ + if (buf[0] != 'P' || buf[1] != 'K' || buf[2] != 5 || buf[3] != 6) + return LWS_FZ_ERR_END_RECORD_MAGIC; + + i = get_u16(buf + ZE_NUM_ENTRIES); + + if (get_u16(buf + ZE_DESK_NUMBER) || + get_u16(buf + ZE_CENTRAL_DIRECTORY_DISK_NUMBER) || + i != get_u16(buf + ZE_NUM_ENTRIES_THIS_DISK)) + return LWS_FZ_ERR_END_RECORD_SANITY; + + /* end record is OK... look for our file in the central dir */ + + if (lws_vfs_file_seek_set(priv->zip_fop_fd, + get_u32(buf + ZE_CENTRAL_DIR_OFFSET)) < 0) + return LWS_FZ_ERR_CENTRAL_SEEK; + + while (i--) { + priv->content_start = lws_vfs_tell(priv->zip_fop_fd); + + if (lws_vfs_file_read(priv->zip_fop_fd, &amount, buf, + ZC_DIRECTORY_LENGTH)) + return LWS_FZ_ERR_CENTRAL_READ; + + if (amount != ZC_DIRECTORY_LENGTH) + return LWS_FZ_ERR_CENTRAL_READ; + + if (get_u32(buf + ZC_SIGNATURE) != 0x02014B50) + return LWS_FZ_ERR_CENTRAL_SANITY; + + lwsl_debug("cstart 0x%lx\n", (unsigned long)priv->content_start); + + priv->hdr.filename_len = get_u16(buf + ZC_FILE_NAME_LENGTH); + priv->hdr.extra = get_u16(buf + ZC_EXTRA_FIELD_LENGTH); + priv->hdr.filename_start = lws_vfs_tell(priv->zip_fop_fd); + + priv->hdr.method = get_u16(buf + ZC_COMPRESSION_METHOD); + priv->hdr.crc32 = get_u32(buf + ZC_CRC32); + priv->hdr.comp_size = get_u32(buf + ZC_COMPRESSED_SIZE); + priv->hdr.uncomp_size = get_u32(buf + ZC_UNCOMPRESSED_SIZE); + priv->hdr.offset = get_u32(buf + ZC_REL_OFFSET_LOCAL_HEADER); + priv->hdr.mod_time = get_u32(buf + ZC_LAST_MOD_FILE_TIME); + priv->hdr.file_com_len = get_u16(buf + ZC_FILE_COMMENT_LENGTH); + + if (priv->hdr.filename_len != len) + goto next; + + if (len >= sizeof(buf) - 1) + return LWS_FZ_ERR_NAME_TOO_LONG; + + if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd, + &amount, buf, len)) + return LWS_FZ_ERR_NAME_READ; + if (amount != len) + return LWS_FZ_ERR_NAME_READ; + + buf[len] = '\0'; + lwsl_debug("check %s vs %s\n", buf, name); + + if (strcmp((const char *)buf, name)) + goto next; + + /* we found a match */ + if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->hdr.offset) < 0) + return LWS_FZ_ERR_NAME_SEEK; + if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd, + &amount, buf, + ZL_HEADER_LENGTH)) + return LWS_FZ_ERR_NAME_READ; + if (amount != ZL_HEADER_LENGTH) + return LWS_FZ_ERR_NAME_READ; + + priv->content_start = priv->hdr.offset + + ZL_HEADER_LENGTH + + priv->hdr.filename_len + + get_u16(buf + ZL_REL_OFFSET_CONTENT); + + lwsl_debug("content supposed to start at 0x%lx\n", + (unsigned long)priv->content_start); + + if (priv->content_start > priv->zip_fop_fd->len) + return LWS_FZ_ERR_CONTENT_SANITY; + + if (lws_vfs_file_seek_set(priv->zip_fop_fd, + priv->content_start) < 0) + return LWS_FZ_ERR_CONTENT_SEEK; + + /* we are aligned at the start of the content */ + + priv->exp_uncomp_pos = 0; + + return 0; + +next: + if (i && lws_vfs_file_seek_set(priv->zip_fop_fd, + priv->content_start + + ZC_DIRECTORY_LENGTH + + priv->hdr.filename_len + + priv->hdr.extra + + priv->hdr.file_com_len) < 0) + return LWS_FZ_ERR_SCAN_SEEK; + } + + return LWS_FZ_ERR_NOT_FOUND; +} + +static int +lws_fops_zip_reset_inflate(lws_fops_zip_t priv) +{ + if (priv->decompress) + inflateEnd(&priv->inflate); + + priv->inflate.zalloc = Z_NULL; + priv->inflate.zfree = Z_NULL; + priv->inflate.opaque = Z_NULL; + priv->inflate.avail_in = 0; + priv->inflate.next_in = Z_NULL; + + if (inflateInit2(&priv->inflate, -MAX_WBITS) != Z_OK) { + lwsl_err("inflate init failed\n"); + return LWS_FZ_ERR_ZLIB_INIT; + } + + if (lws_vfs_file_seek_set(priv->zip_fop_fd, priv->content_start) < 0) + return LWS_FZ_ERR_CONTENT_SEEK; + + priv->exp_uncomp_pos = 0; + + return 0; +} + +static lws_fop_fd_t +lws_fops_zip_open(const struct lws_plat_file_ops *fops, const char *vfs_path, + const char *vpath, lws_fop_flags_t *flags) +{ + lws_fop_flags_t local_flags = 0; + lws_fops_zip_t priv; + char rp[192]; + int m; + + /* + * vpath points at the / after the fops signature in vfs_path, eg + * with a vfs_path "/var/www/docs/manual.zip/index.html", vpath + * will come pointing at "/index.html" + */ + + priv = lws_zalloc(sizeof(*priv), "fops_zip priv"); + if (!priv) + return NULL; + + priv->fop_fd.fops = &fops_zip; + + m = sizeof(rp) - 1; + if ((vpath - vfs_path - 1) < m) + m = vpath - vfs_path - 1; + strncpy(rp, vfs_path, m); + rp[m] = '\0'; + + /* open the zip file itself using the incoming fops, not fops_zip */ + + priv->zip_fop_fd = fops->LWS_FOP_OPEN(fops, rp, NULL, &local_flags); + if (!priv->zip_fop_fd) { + lwsl_err("unable to open zip %s\n", rp); + goto bail1; + } + + if (*vpath == '/') + vpath++; + + m = lws_fops_zip_scan(priv, vpath, strlen(vpath)); + if (m) { + lwsl_err("unable to find record matching '%s' %d\n", vpath, m); + goto bail2; + } + + /* the directory metadata tells us modification time, so pass it on */ + priv->fop_fd.mod_time = priv->hdr.mod_time; + *flags |= LWS_FOP_FLAG_MOD_TIME_VALID | LWS_FOP_FLAG_VIRTUAL; + priv->fop_fd.flags = *flags; + + /* The zip fop_fd is left pointing at the start of the content. + * + * 1) Content could be uncompressed (STORE), and we can always serve + * that directly + * + * 2) Content could be compressed (GZIP), and the client can handle + * receiving GZIP... we can wrap it in a GZIP header and trailer + * and serve the content part directly. The flag indicating we + * are providing GZIP directly is set so lws will send the right + * headers. + * + * 3) Content could be compressed (GZIP) but the client can't handle + * receiving GZIP... we can decompress it and serve as it is + * inflated piecemeal. + * + * 4) Content may be compressed some unknown way... fail + * + */ + if (priv->hdr.method == ZIP_COMPRESSION_METHOD_STORE) { + /* + * it is stored uncompressed, leave it indicated as + * uncompressed, and just serve it from inside the + * zip with no gzip container; + */ + + lwsl_info("direct zip serving (stored)\n"); + + priv->fop_fd.len = priv->hdr.uncomp_size; + + return &priv->fop_fd; + } + + if ((*flags & LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP) && + priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) { + + /* + * We can serve the gzipped file contents directly as gzip + * from inside the zip container; client says it is OK. + * + * To convert to standalone gzip, we have to add a 10-byte + * constant header and a variable 8-byte trailer around the + * content. + * + * The 8-byte trailer is prepared now and held in the priv. + */ + + lwsl_info("direct zip serving (gzipped)\n"); + + priv->fop_fd.len = sizeof(hd) + priv->hdr.comp_size + + sizeof(priv->u); + + if (lws_is_be()) { + uint8_t *p = priv->u.trailer8; + + *p++ = (uint8_t)priv->hdr.crc32; + *p++ = (uint8_t)(priv->hdr.crc32 >> 8); + *p++ = (uint8_t)(priv->hdr.crc32 >> 16); + *p++ = (uint8_t)(priv->hdr.crc32 >> 24); + *p++ = (uint8_t)priv->hdr.uncomp_size; + *p++ = (uint8_t)(priv->hdr.uncomp_size >> 8); + *p++ = (uint8_t)(priv->hdr.uncomp_size >> 16); + *p = (uint8_t)(priv->hdr.uncomp_size >> 24); + } else { + priv->u.trailer32[0] = priv->hdr.crc32; + priv->u.trailer32[1] = priv->hdr.uncomp_size; + } + + *flags |= LWS_FOP_FLAG_COMPR_IS_GZIP; + priv->fop_fd.flags = *flags; + priv->add_gzip_container = 1; + + return &priv->fop_fd; + } + + if (priv->hdr.method == ZIP_COMPRESSION_METHOD_DEFLATE) { + + /* we must decompress it to serve it */ + + lwsl_info("decompressed zip serving\n"); + + priv->fop_fd.len = priv->hdr.uncomp_size; + + if (lws_fops_zip_reset_inflate(priv)) { + lwsl_err("inflate init failed\n"); + goto bail2; + } + + priv->decompress = 1; + + return &priv->fop_fd; + } + + /* we can't handle it ... */ + + lwsl_err("zipped file %s compressed in unknown way (%d)\n", vfs_path, + priv->hdr.method); + +bail2: + lws_vfs_file_close(&priv->zip_fop_fd); +bail1: + free(priv); + + return NULL; +} + +/* ie, we are closing the fop_fd for the file inside the gzip */ + +static int +lws_fops_zip_close(lws_fop_fd_t *fd) +{ + lws_fops_zip_t priv = fop_fd_to_priv(*fd); + + if (priv->decompress) + inflateEnd(&priv->inflate); + + lws_vfs_file_close(&priv->zip_fop_fd); /* close the gzip fop_fd */ + + free(priv); + *fd = NULL; + + return 0; +} + +static lws_fileofs_t +lws_fops_zip_seek_cur(lws_fop_fd_t fd, lws_fileofs_t offset_from_cur_pos) +{ + fd->pos += offset_from_cur_pos; + + return fd->pos; +} + +static int +lws_fops_zip_read(lws_fop_fd_t fd, lws_filepos_t *amount, uint8_t *buf, + lws_filepos_t len) +{ + lws_fops_zip_t priv = fop_fd_to_priv(fd); + lws_filepos_t ramount, rlen, cur = lws_vfs_tell(fd); + int ret; + + if (priv->decompress) { + + if (priv->exp_uncomp_pos != fd->pos) { + /* + * there has been a seek in the uncompressed fop_fd + * we have to restart the decompression and loop eating + * the decompressed data up to the seek point + */ + lwsl_info("seek in decompressed\n"); + + lws_fops_zip_reset_inflate(priv); + + while (priv->exp_uncomp_pos != fd->pos) { + rlen = len; + if (rlen > fd->pos - priv->exp_uncomp_pos) + rlen = fd->pos - priv->exp_uncomp_pos; + if (lws_fops_zip_read(fd, amount, buf, rlen)) + return LWS_FZ_ERR_SEEK_COMPRESSED; + } + *amount = 0; + } + + priv->inflate.avail_out = (unsigned int)len; + priv->inflate.next_out = buf; + +spin: + if (!priv->inflate.avail_in) { + rlen = sizeof(priv->rbuf); + if (rlen > priv->hdr.comp_size - + (cur - priv->content_start)) + rlen = priv->hdr.comp_size - + (priv->hdr.comp_size - + priv->content_start); + + if (priv->zip_fop_fd->fops->LWS_FOP_READ( + priv->zip_fop_fd, &ramount, priv->rbuf, + rlen)) + return LWS_FZ_ERR_READ_CONTENT; + + cur += ramount; + + priv->inflate.avail_in = (unsigned int)ramount; + priv->inflate.next_in = priv->rbuf; + } + + ret = inflate(&priv->inflate, Z_NO_FLUSH); + if (ret == Z_STREAM_ERROR) + return ret; + + switch (ret) { + case Z_NEED_DICT: + ret = Z_DATA_ERROR; + /* and fall through */ + case Z_DATA_ERROR: + case Z_MEM_ERROR: + + return ret; + } + + if (!priv->inflate.avail_in && priv->inflate.avail_out && + cur != priv->content_start + priv->hdr.comp_size) + goto spin; + + *amount = len - priv->inflate.avail_out; + + priv->exp_uncomp_pos += *amount; + fd->pos += *amount; + + return 0; + } + + if (priv->add_gzip_container) { + + lwsl_info("%s: gzip + container\n", __func__); + *amount = 0; + + /* place the canned header at the start */ + + if (len && fd->pos < sizeof(hd)) { + rlen = sizeof(hd) - fd->pos; + if (rlen > len) + rlen = len; + /* provide stuff from canned header */ + memcpy(buf, hd + fd->pos, (size_t)rlen); + fd->pos += rlen; + buf += rlen; + len -= rlen; + *amount += rlen; + } + + /* serve gzipped data direct from zipfile */ + + if (len && fd->pos >= sizeof(hd) && + fd->pos < priv->hdr.comp_size + sizeof(hd)) { + + rlen = priv->hdr.comp_size - (priv->zip_fop_fd->pos - + priv->content_start); + if (rlen > len) + rlen = len; + + if (rlen && + priv->zip_fop_fd->pos < (priv->hdr.comp_size + + priv->content_start)) { + if (lws_vfs_file_read(priv->zip_fop_fd, + &ramount, buf, rlen)) + return LWS_FZ_ERR_READ_CONTENT; + *amount += ramount; + fd->pos += ramount; // virtual pos + buf += ramount; + len -= ramount; + } + } + + /* place the prepared trailer at the end */ + + if (len && fd->pos >= priv->hdr.comp_size + sizeof(hd) && + fd->pos < priv->hdr.comp_size + sizeof(hd) + + sizeof(priv->u)) { + cur = fd->pos - priv->hdr.comp_size - sizeof(hd); + rlen = sizeof(priv->u) - cur; + if (rlen > len) + rlen = len; + + memcpy(buf, priv->u.trailer8 + cur, (size_t)rlen); + + *amount += rlen; + fd->pos += rlen; + } + + return 0; + } + + lwsl_info("%s: store\n", __func__); + + if (len > priv->hdr.uncomp_size - (cur - priv->content_start)) + len = priv->hdr.comp_size - (priv->hdr.comp_size - + priv->content_start); + + if (priv->zip_fop_fd->fops->LWS_FOP_READ(priv->zip_fop_fd, + amount, buf, len)) + return LWS_FZ_ERR_READ_CONTENT; + + return 0; +} + +struct lws_plat_file_ops fops_zip = { + lws_fops_zip_open, + lws_fops_zip_close, + lws_fops_zip_seek_cur, + lws_fops_zip_read, + NULL, + { { ".zip/", 5 }, { ".jar/", 5 }, { ".war/", 5 } }, + NULL, +}; diff --git a/thirdparty/lws/server/lejp-conf.c b/thirdparty/lws/server/lejp-conf.c new file mode 100644 index 0000000000..c2b684c278 --- /dev/null +++ b/thirdparty/lws/server/lejp-conf.c @@ -0,0 +1,929 @@ +/* + * libwebsockets web server application + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" +#include "../misc/lejp.h" + +#ifndef _WIN32 +/* this is needed for Travis CI */ +#include <dirent.h> +#endif + +#define ESC_INSTALL_DATADIR "_lws_ddir_" + +static const char * const paths_global[] = { + "global.uid", + "global.gid", + "global.count-threads", + "global.init-ssl", + "global.server-string", + "global.plugin-dir", + "global.ws-pingpong-secs", + "global.timeout-secs", + "global.reject-service-keywords[].*", + "global.reject-service-keywords[]", +}; + +enum lejp_global_paths { + LEJPGP_UID, + LEJPGP_GID, + LEJPGP_COUNT_THREADS, + LWJPGP_INIT_SSL, + LEJPGP_SERVER_STRING, + LEJPGP_PLUGIN_DIR, + LWJPGP_PINGPONG_SECS, + LWJPGP_TIMEOUT_SECS, + LWJPGP_REJECT_SERVICE_KEYWORDS_NAME, + LWJPGP_REJECT_SERVICE_KEYWORDS +}; + +static const char * const paths_vhosts[] = { + "vhosts[]", + "vhosts[].mounts[]", + "vhosts[].name", + "vhosts[].port", + "vhosts[].interface", + "vhosts[].unix-socket", + "vhosts[].sts", + "vhosts[].host-ssl-key", + "vhosts[].host-ssl-cert", + "vhosts[].host-ssl-ca", + "vhosts[].access-log", + "vhosts[].mounts[].mountpoint", + "vhosts[].mounts[].origin", + "vhosts[].mounts[].protocol", + "vhosts[].mounts[].default", + "vhosts[].mounts[].auth-mask", + "vhosts[].mounts[].cgi-timeout", + "vhosts[].mounts[].cgi-env[].*", + "vhosts[].mounts[].cache-max-age", + "vhosts[].mounts[].cache-reuse", + "vhosts[].mounts[].cache-revalidate", + "vhosts[].mounts[].basic-auth", + "vhosts[].mounts[].cache-intermediaries", + "vhosts[].mounts[].extra-mimetypes.*", + "vhosts[].mounts[].interpret.*", + "vhosts[].ws-protocols[].*.*", + "vhosts[].ws-protocols[].*", + "vhosts[].ws-protocols[]", + "vhosts[].keepalive_timeout", + "vhosts[].enable-client-ssl", + "vhosts[].ciphers", + "vhosts[].ecdh-curve", + "vhosts[].noipv6", + "vhosts[].ipv6only", + "vhosts[].ssl-option-set", + "vhosts[].ssl-option-clear", + "vhosts[].mounts[].pmo[].*", + "vhosts[].headers[].*", + "vhosts[].headers[]", + "vhosts[].client-ssl-key", + "vhosts[].client-ssl-cert", + "vhosts[].client-ssl-ca", + "vhosts[].client-ssl-ciphers", + "vhosts[].onlyraw", +}; + +enum lejp_vhost_paths { + LEJPVP, + LEJPVP_MOUNTS, + LEJPVP_NAME, + LEJPVP_PORT, + LEJPVP_INTERFACE, + LEJPVP_UNIXSKT, + LEJPVP_STS, + LEJPVP_HOST_SSL_KEY, + LEJPVP_HOST_SSL_CERT, + LEJPVP_HOST_SSL_CA, + LEJPVP_ACCESS_LOG, + LEJPVP_MOUNTPOINT, + LEJPVP_ORIGIN, + LEJPVP_MOUNT_PROTOCOL, + LEJPVP_DEFAULT, + LEJPVP_DEFAULT_AUTH_MASK, + LEJPVP_CGI_TIMEOUT, + LEJPVP_CGI_ENV, + LEJPVP_MOUNT_CACHE_MAX_AGE, + LEJPVP_MOUNT_CACHE_REUSE, + LEJPVP_MOUNT_CACHE_REVALIDATE, + LEJPVP_MOUNT_BASIC_AUTH, + LEJPVP_MOUNT_CACHE_INTERMEDIARIES, + LEJPVP_MOUNT_EXTRA_MIMETYPES, + LEJPVP_MOUNT_INTERPRET, + LEJPVP_PROTOCOL_NAME_OPT, + LEJPVP_PROTOCOL_NAME, + LEJPVP_PROTOCOL, + LEJPVP_KEEPALIVE_TIMEOUT, + LEJPVP_ENABLE_CLIENT_SSL, + LEJPVP_CIPHERS, + LEJPVP_ECDH_CURVE, + LEJPVP_NOIPV6, + LEJPVP_IPV6ONLY, + LEJPVP_SSL_OPTION_SET, + LEJPVP_SSL_OPTION_CLEAR, + LEJPVP_PMO, + LEJPVP_HEADERS_NAME, + LEJPVP_HEADERS, + LEJPVP_CLIENT_SSL_KEY, + LEJPVP_CLIENT_SSL_CERT, + LEJPVP_CLIENT_SSL_CA, + LEJPVP_CLIENT_CIPHERS, + LEJPVP_FLAG_ONLYRAW, +}; + +static const char * const parser_errs[] = { + "", + "", + "No opening '{'", + "Expected closing '}'", + "Expected '\"'", + "String underrun", + "Illegal unescaped control char", + "Illegal escape format", + "Illegal hex number", + "Expected ':'", + "Illegal value start", + "Digit required after decimal point", + "Bad number format", + "Bad exponent format", + "Unknown token", + "Too many ']'", + "Mismatched ']'", + "Expected ']'", + "JSON nesting limit exceeded", + "Nesting tracking used up", + "Number too long", + "Comma or block end expected", + "Unknown", + "Parser callback errored (see earlier error)", +}; + +#define MAX_PLUGIN_DIRS 10 + +struct jpargs { + struct lws_context_creation_info *info; + struct lws_context *context; + const struct lws_protocols *protocols; + const struct lws_extension *extensions; + char *p, *end, valid; + struct lws_http_mount *head, *last; + + struct lws_protocol_vhost_options *pvo; + struct lws_protocol_vhost_options *pvo_em; + struct lws_protocol_vhost_options *pvo_int; + struct lws_http_mount m; + const char **plugin_dirs; + int count_plugin_dirs; + + unsigned int enable_client_ssl:1; + unsigned int fresh_mount:1; + unsigned int any_vhosts:1; +}; + +static void * +lwsws_align(struct jpargs *a) +{ + if ((lws_intptr_t)(a->p) & 15) + a->p += 16 - ((lws_intptr_t)(a->p) & 15); + + return a->p; +} + +static int +arg_to_bool(const char *s) +{ + static const char * const on[] = { "on", "yes", "true" }; + int n = atoi(s); + + if (n) + return 1; + + for (n = 0; n < ARRAY_SIZE(on); n++) + if (!strcasecmp(s, on[n])) + return 1; + + return 0; +} + +static signed char +lejp_globals_cb(struct lejp_ctx *ctx, char reason) +{ + struct jpargs *a = (struct jpargs *)ctx->user; + struct lws_protocol_vhost_options *rej; + int n; + + /* we only match on the prepared path strings */ + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + /* this catches, eg, vhosts[].headers[].xxx */ + if (reason == LEJPCB_VAL_STR_END && + ctx->path_match == LWJPGP_REJECT_SERVICE_KEYWORDS_NAME + 1) { + rej = lwsws_align(a); + a->p += sizeof(*rej); + + n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p); + rej->next = a->info->reject_service_keywords; + a->info->reject_service_keywords = rej; + rej->name = a->p; + lwsl_notice(" adding rej %s=%s\n", a->p, ctx->buf); + a->p += n - 1; + *(a->p++) = '\0'; + rej->value = a->p; + rej->options = NULL; + goto dostring; + } + + switch (ctx->path_match - 1) { + case LEJPGP_UID: + a->info->uid = atoi(ctx->buf); + return 0; + case LEJPGP_GID: + a->info->gid = atoi(ctx->buf); + return 0; + case LEJPGP_COUNT_THREADS: + a->info->count_threads = atoi(ctx->buf); + return 0; + case LWJPGP_INIT_SSL: + if (arg_to_bool(ctx->buf)) + a->info->options |= LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + return 0; + case LEJPGP_SERVER_STRING: + a->info->server_string = a->p; + break; + case LEJPGP_PLUGIN_DIR: + if (a->count_plugin_dirs == MAX_PLUGIN_DIRS - 1) { + lwsl_err("Too many plugin dirs\n"); + return -1; + } + a->plugin_dirs[a->count_plugin_dirs++] = a->p; + break; + + case LWJPGP_PINGPONG_SECS: + a->info->ws_ping_pong_interval = atoi(ctx->buf); + return 0; + + case LWJPGP_TIMEOUT_SECS: + a->info->timeout_secs = atoi(ctx->buf); + return 0; + + default: + return 0; + } + +dostring: + a->p += lws_snprintf(a->p, a->end - a->p, "%s", ctx->buf); + *(a->p)++ = '\0'; + + return 0; +} + +static signed char +lejp_vhosts_cb(struct lejp_ctx *ctx, char reason) +{ + struct jpargs *a = (struct jpargs *)ctx->user; + struct lws_protocol_vhost_options *pvo, *mp_cgienv, *headers; + struct lws_http_mount *m; + char *p, *p1; + int n; + +#if 0 + lwsl_notice(" %d: %s (%d)\n", reason, ctx->path, ctx->path_match); + for (n = 0; n < ctx->wildcount; n++) + lwsl_notice(" %d\n", ctx->wild[n]); +#endif + + if (reason == LEJPCB_OBJECT_START && ctx->path_match == LEJPVP + 1) { + /* set the defaults for this vhost */ + a->valid = 1; + a->head = NULL; + a->last = NULL; + a->info->port = 0; + a->info->iface = NULL; + a->info->protocols = a->protocols; + a->info->extensions = a->extensions; + a->info->ssl_cert_filepath = NULL; + a->info->ssl_private_key_filepath = NULL; + a->info->ssl_ca_filepath = NULL; + a->info->client_ssl_cert_filepath = NULL; + a->info->client_ssl_private_key_filepath = NULL; + a->info->client_ssl_ca_filepath = NULL; + a->info->client_ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-GCM-SHA384:" + "DHE-RSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-SHA384:" + "HIGH:!aNULL:!eNULL:!EXPORT:" + "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:" + "!SHA1:!DHE-RSA-AES128-GCM-SHA256:" + "!DHE-RSA-AES128-SHA256:" + "!AES128-GCM-SHA256:" + "!AES128-SHA256:" + "!DHE-RSA-AES256-SHA256:" + "!AES256-GCM-SHA384:" + "!AES256-SHA256"; + a->info->timeout_secs = 5; + a->info->ssl_cipher_list = "ECDHE-ECDSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-GCM-SHA384:" + "DHE-RSA-AES256-GCM-SHA384:" + "ECDHE-RSA-AES256-SHA384:" + "HIGH:!aNULL:!eNULL:!EXPORT:" + "!DES:!MD5:!PSK:!RC4:!HMAC_SHA1:" + "!SHA1:!DHE-RSA-AES128-GCM-SHA256:" + "!DHE-RSA-AES128-SHA256:" + "!AES128-GCM-SHA256:" + "!AES128-SHA256:" + "!DHE-RSA-AES256-SHA256:" + "!AES256-GCM-SHA384:" + "!AES256-SHA256"; + a->info->pvo = NULL; + a->info->headers = NULL; + a->info->keepalive_timeout = 5; + a->info->log_filepath = NULL; + a->info->options &= ~(LWS_SERVER_OPTION_UNIX_SOCK | + LWS_SERVER_OPTION_STS | LWS_SERVER_OPTION_ONLY_RAW); + a->enable_client_ssl = 0; + } + + if (reason == LEJPCB_OBJECT_START && + ctx->path_match == LEJPVP_MOUNTS + 1) { + a->fresh_mount = 1; + memset(&a->m, 0, sizeof(a->m)); + } + + /* this catches, eg, vhosts[].ws-protocols[].xxx-protocol */ + if (reason == LEJPCB_OBJECT_START && + ctx->path_match == LEJPVP_PROTOCOL_NAME + 1) { + a->pvo = lwsws_align(a); + a->p += sizeof(*a->pvo); + + n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p); + /* ie, enable this protocol, no options yet */ + a->pvo->next = a->info->pvo; + a->info->pvo = a->pvo; + a->pvo->name = a->p; + lwsl_notice(" adding protocol %s\n", a->p); + a->p += n; + a->pvo->value = a->p; + a->pvo->options = NULL; + goto dostring; + } + + /* this catches, eg, vhosts[].headers[].xxx */ + if (reason == LEJPCB_VAL_STR_END && + ctx->path_match == LEJPVP_HEADERS_NAME + 1) { + headers = lwsws_align(a); + a->p += sizeof(*headers); + + n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p); + /* ie, enable this protocol, no options yet */ + headers->next = a->info->headers; + a->info->headers = headers; + headers->name = a->p; + // lwsl_notice(" adding header %s=%s\n", a->p, ctx->buf); + a->p += n - 1; + *(a->p++) = ':'; + if (a->p < a->end) + *(a->p++) = '\0'; + else + *(a->p - 1) = '\0'; + headers->value = a->p; + headers->options = NULL; + goto dostring; + } + + if (reason == LEJPCB_OBJECT_END && + (ctx->path_match == LEJPVP + 1 || !ctx->path[0]) && + a->valid) { + + struct lws_vhost *vhost; + + //lwsl_notice("%s\n", ctx->path); + if (!a->info->port) { + lwsl_err("Port required (eg, 443)"); + return 1; + } + a->valid = 0; + a->info->mounts = a->head; + + vhost = lws_create_vhost(a->context, a->info); + if (!vhost) { + lwsl_err("Failed to create vhost %s\n", + a->info->vhost_name); + return 1; + } + a->any_vhosts = 1; + + if (a->enable_client_ssl) { + const char *cert_filepath = a->info->client_ssl_cert_filepath; + const char *private_key_filepath = a->info->client_ssl_private_key_filepath; + const char *ca_filepath = a->info->client_ssl_ca_filepath; + const char *cipher_list = a->info->client_ssl_cipher_list; + memset(a->info, 0, sizeof(*a->info)); + a->info->client_ssl_cert_filepath = cert_filepath; + a->info->client_ssl_private_key_filepath = private_key_filepath; + a->info->client_ssl_ca_filepath = ca_filepath; + a->info->client_ssl_cipher_list = cipher_list; + a->info->options = LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT; + lws_init_vhost_client_ssl(a->info, vhost); + } + + return 0; + } + + if (reason == LEJPCB_OBJECT_END && + ctx->path_match == LEJPVP_MOUNTS + 1) { + static const char * const mount_protocols[] = { + "http://", + "https://", + "file://", + "cgi://", + ">http://", + ">https://", + "callback://", + "gzip://", + }; + + if (!a->fresh_mount) + return 0; + + if (!a->m.mountpoint || !a->m.origin) { + lwsl_err("mountpoint and origin required\n"); + return 1; + } + lwsl_debug("adding mount %s\n", a->m.mountpoint); + m = lwsws_align(a); + memcpy(m, &a->m, sizeof(*m)); + if (a->last) + a->last->mount_next = m; + + for (n = 0; n < ARRAY_SIZE(mount_protocols); n++) + if (!strncmp(a->m.origin, mount_protocols[n], + strlen(mount_protocols[n]))) { + lwsl_info("----%s\n", a->m.origin); + m->origin_protocol = n; + m->origin = a->m.origin + + strlen(mount_protocols[n]); + break; + } + + if (n == ARRAY_SIZE(mount_protocols)) { + lwsl_err("unsupported protocol:// %s\n", a->m.origin); + return 1; + } + + a->p += sizeof(*m); + if (!a->head) + a->head = m; + + a->last = m; + a->fresh_mount = 0; + } + + /* we only match on the prepared path strings */ + if (!(reason & LEJP_FLAG_CB_IS_VALUE) || !ctx->path_match) + return 0; + + switch (ctx->path_match - 1) { + case LEJPVP_NAME: + a->info->vhost_name = a->p; + break; + case LEJPVP_PORT: + a->info->port = atoi(ctx->buf); + return 0; + case LEJPVP_INTERFACE: + a->info->iface = a->p; + break; + case LEJPVP_UNIXSKT: + if (arg_to_bool(ctx->buf)) + a->info->options |= LWS_SERVER_OPTION_UNIX_SOCK; + else + a->info->options &= ~(LWS_SERVER_OPTION_UNIX_SOCK); + return 0; + case LEJPVP_STS: + if (arg_to_bool(ctx->buf)) + a->info->options |= LWS_SERVER_OPTION_STS; + else + a->info->options &= ~(LWS_SERVER_OPTION_STS); + return 0; + case LEJPVP_HOST_SSL_KEY: + a->info->ssl_private_key_filepath = a->p; + break; + case LEJPVP_HOST_SSL_CERT: + a->info->ssl_cert_filepath = a->p; + break; + case LEJPVP_HOST_SSL_CA: + a->info->ssl_ca_filepath = a->p; + break; + case LEJPVP_ACCESS_LOG: + a->info->log_filepath = a->p; + break; + case LEJPVP_MOUNTPOINT: + a->m.mountpoint = a->p; + a->m.mountpoint_len = (unsigned char)strlen(ctx->buf); + break; + case LEJPVP_ORIGIN: + if (!strncmp(ctx->buf, "callback://", 11)) + a->m.protocol = a->p + 11; + + if (!a->m.origin) + a->m.origin = a->p; + break; + case LEJPVP_DEFAULT: + a->m.def = a->p; + break; + case LEJPVP_DEFAULT_AUTH_MASK: + a->m.auth_mask = atoi(ctx->buf); + return 0; + case LEJPVP_MOUNT_CACHE_MAX_AGE: + a->m.cache_max_age = atoi(ctx->buf); + return 0; + case LEJPVP_MOUNT_CACHE_REUSE: + a->m.cache_reusable = arg_to_bool(ctx->buf); + return 0; + case LEJPVP_MOUNT_CACHE_REVALIDATE: + a->m.cache_revalidate = arg_to_bool(ctx->buf); + return 0; + case LEJPVP_MOUNT_CACHE_INTERMEDIARIES: + a->m.cache_intermediaries = arg_to_bool(ctx->buf);; + return 0; + case LEJPVP_MOUNT_BASIC_AUTH: + a->m.basic_auth_login_file = a->p; + break; + case LEJPVP_CGI_TIMEOUT: + a->m.cgi_timeout = atoi(ctx->buf); + return 0; + case LEJPVP_KEEPALIVE_TIMEOUT: + a->info->keepalive_timeout = atoi(ctx->buf); + return 0; + case LEJPVP_CLIENT_CIPHERS: + a->info->client_ssl_cipher_list = a->p; + break; + case LEJPVP_CIPHERS: + a->info->ssl_cipher_list = a->p; + break; + case LEJPVP_ECDH_CURVE: + a->info->ecdh_curve = a->p; + break; + case LEJPVP_PMO: + case LEJPVP_CGI_ENV: + mp_cgienv = lwsws_align(a); + a->p += sizeof(*a->m.cgienv); + + mp_cgienv->next = a->m.cgienv; + a->m.cgienv = mp_cgienv; + + n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p); + mp_cgienv->name = a->p; + a->p += n; + mp_cgienv->value = a->p; + mp_cgienv->options = NULL; + //lwsl_notice(" adding pmo / cgi-env '%s' = '%s'\n", mp_cgienv->name, + // mp_cgienv->value); + goto dostring; + + case LEJPVP_PROTOCOL_NAME_OPT: + /* this catches, eg, + * vhosts[].ws-protocols[].xxx-protocol.yyy-option + * ie, these are options attached to a protocol with { } + */ + pvo = lwsws_align(a); + a->p += sizeof(*a->pvo); + + n = lejp_get_wildcard(ctx, 1, a->p, a->end - a->p); + /* ie, enable this protocol, no options yet */ + pvo->next = a->pvo->options; + a->pvo->options = pvo; + pvo->name = a->p; + a->p += n; + pvo->value = a->p; + pvo->options = NULL; + break; + + case LEJPVP_MOUNT_EXTRA_MIMETYPES: + a->pvo_em = lwsws_align(a); + a->p += sizeof(*a->pvo_em); + + n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p); + /* ie, enable this protocol, no options yet */ + a->pvo_em->next = a->m.extra_mimetypes; + a->m.extra_mimetypes = a->pvo_em; + a->pvo_em->name = a->p; + lwsl_notice(" adding extra-mimetypes %s -> %s\n", a->p, ctx->buf); + a->p += n; + a->pvo_em->value = a->p; + a->pvo_em->options = NULL; + break; + + case LEJPVP_MOUNT_INTERPRET: + a->pvo_int = lwsws_align(a); + a->p += sizeof(*a->pvo_int); + + n = lejp_get_wildcard(ctx, 0, a->p, a->end - a->p); + /* ie, enable this protocol, no options yet */ + a->pvo_int->next = a->m.interpret; + a->m.interpret = a->pvo_int; + a->pvo_int->name = a->p; + lwsl_notice(" adding interpret %s -> %s\n", a->p, + ctx->buf); + a->p += n; + a->pvo_int->value = a->p; + a->pvo_int->options = NULL; + break; + + case LEJPVP_ENABLE_CLIENT_SSL: + a->enable_client_ssl = arg_to_bool(ctx->buf); + return 0; + case LEJPVP_CLIENT_SSL_KEY: + a->info->client_ssl_private_key_filepath = a->p; + break; + case LEJPVP_CLIENT_SSL_CERT: + a->info->client_ssl_cert_filepath = a->p; + break; + case LEJPVP_CLIENT_SSL_CA: + a->info->client_ssl_ca_filepath = a->p; + break; + + case LEJPVP_NOIPV6: + if (arg_to_bool(ctx->buf)) + a->info->options |= LWS_SERVER_OPTION_DISABLE_IPV6; + else + a->info->options &= ~(LWS_SERVER_OPTION_DISABLE_IPV6); + return 0; + + case LEJPVP_FLAG_ONLYRAW: + if (arg_to_bool(ctx->buf)) + a->info->options |= LWS_SERVER_OPTION_ONLY_RAW; + else + a->info->options &= ~(LWS_SERVER_OPTION_ONLY_RAW); + return 0; + + case LEJPVP_IPV6ONLY: + a->info->options |= LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY; + if (arg_to_bool(ctx->buf)) + a->info->options |= LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE; + else + a->info->options &= ~(LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE); + return 0; + + case LEJPVP_SSL_OPTION_SET: + a->info->ssl_options_set |= atol(ctx->buf); + return 0; + case LEJPVP_SSL_OPTION_CLEAR: + a->info->ssl_options_clear |= atol(ctx->buf); + return 0; + + default: + return 0; + } + +dostring: + p = ctx->buf; + p1 = strstr(p, ESC_INSTALL_DATADIR); + if (p1) { + n = p1 - p; + if (n > a->end - a->p) + n = a->end - a->p; + strncpy(a->p, p, n); + a->p += n; + a->p += lws_snprintf(a->p, a->end - a->p, "%s", LWS_INSTALL_DATADIR); + p += n + strlen(ESC_INSTALL_DATADIR); + } + + a->p += lws_snprintf(a->p, a->end - a->p, "%s", p); + *(a->p)++ = '\0'; + + return 0; +} + +/* + * returns 0 = OK, 1 = can't open, 2 = parsing error + */ + +static int +lwsws_get_config(void *user, const char *f, const char * const *paths, + int count_paths, lejp_callback cb) +{ + unsigned char buf[128]; + struct lejp_ctx ctx; + int n, m, fd; + + fd = open(f, O_RDONLY); + if (fd < 0) { + lwsl_err("Cannot open %s\n", f); + return 2; + } + lwsl_info("%s: %s\n", __func__, f); + lejp_construct(&ctx, cb, user, paths, count_paths); + + do { + n = read(fd, buf, sizeof(buf)); + if (!n) + break; + + m = (int)(signed char)lejp_parse(&ctx, buf, n); + } while (m == LEJP_CONTINUE); + + close(fd); + n = ctx.line; + lejp_destruct(&ctx); + + if (m < 0) { + lwsl_err("%s(%u): parsing error %d: %s\n", f, n, m, + parser_errs[-m]); + return 2; + } + + return 0; +} + +#if defined(LWS_WITH_LIBUV) && UV_VERSION_MAJOR > 0 + +static int +lwsws_get_config_d(void *user, const char *d, const char * const *paths, + int count_paths, lejp_callback cb) +{ + uv_dirent_t dent; + uv_fs_t req; + char path[256]; + int ret = 0, ir; + uv_loop_t loop; + + ir = uv_loop_init(&loop); + if (ir) { + lwsl_err("%s: loop init failed %d\n", __func__, ir); + } + + if (!uv_fs_scandir(&loop, &req, d, 0, NULL)) { + lwsl_err("Scandir on %s failed\n", d); + return 2; + } + + while (uv_fs_scandir_next(&req, &dent) != UV_EOF) { + lws_snprintf(path, sizeof(path) - 1, "%s/%s", d, dent.name); + ret = lwsws_get_config(user, path, paths, count_paths, cb); + if (ret) + goto bail; + } + +bail: + uv_fs_req_cleanup(&req); + while (uv_loop_close(&loop)) + ; + + return ret; +} + +#else + +#ifndef _WIN32 +static int filter(const struct dirent *ent) +{ + if (!strcmp(ent->d_name, ".") || !strcmp(ent->d_name, "..")) + return 0; + + return 1; +} +#endif + +static int +lwsws_get_config_d(void *user, const char *d, const char * const *paths, + int count_paths, lejp_callback cb) +{ +#ifndef _WIN32 + struct dirent **namelist; + char path[256]; + int n, i, ret = 0; + + n = scandir(d, &namelist, filter, alphasort); + if (n < 0) { + lwsl_err("Scandir on %s failed\n", d); + return 1; + } + + for (i = 0; i < n; i++) { + if (strchr(namelist[i]->d_name, '~')) + goto skip; + lws_snprintf(path, sizeof(path) - 1, "%s/%s", d, + namelist[i]->d_name); + ret = lwsws_get_config(user, path, paths, count_paths, cb); + if (ret) { + while (i++ < n) + free(namelist[i]); + goto bail; + } +skip: + free(namelist[i]); + } + +bail: + free(namelist); + + return ret; +#else + return 0; +#endif +} + +#endif + +int +lwsws_get_config_globals(struct lws_context_creation_info *info, const char *d, + char **cs, int *len) +{ + struct jpargs a; + const char * const *old = info->plugin_dirs; + char dd[128]; + + memset(&a, 0, sizeof(a)); + + a.info = info; + a.p = *cs; + a.end = (a.p + *len) - 1; + a.valid = 0; + + lwsws_align(&a); + info->plugin_dirs = (void *)a.p; + a.plugin_dirs = (void *)a.p; /* writeable version */ + a.p += MAX_PLUGIN_DIRS * sizeof(void *); + + /* copy any default paths */ + + while (old && *old) { + a.plugin_dirs[a.count_plugin_dirs++] = *old; + old++; + } + + lws_snprintf(dd, sizeof(dd) - 1, "%s/conf", d); + if (lwsws_get_config(&a, dd, paths_global, + ARRAY_SIZE(paths_global), lejp_globals_cb) > 1) + return 1; + lws_snprintf(dd, sizeof(dd) - 1, "%s/conf.d", d); + if (lwsws_get_config_d(&a, dd, paths_global, + ARRAY_SIZE(paths_global), lejp_globals_cb) > 1) + return 1; + + a.plugin_dirs[a.count_plugin_dirs] = NULL; + + *cs = a.p; + *len = a.end - a.p; + + return 0; +} + +int +lwsws_get_config_vhosts(struct lws_context *context, + struct lws_context_creation_info *info, const char *d, + char **cs, int *len) +{ + struct jpargs a; + char dd[128]; + + memset(&a, 0, sizeof(a)); + + a.info = info; + a.p = *cs; + a.end = a.p + *len; + a.valid = 0; + a.context = context; + a.protocols = info->protocols; + a.extensions = info->extensions; + + lws_snprintf(dd, sizeof(dd) - 1, "%s/conf", d); + if (lwsws_get_config(&a, dd, paths_vhosts, + ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1) + return 1; + lws_snprintf(dd, sizeof(dd) - 1, "%s/conf.d", d); + if (lwsws_get_config_d(&a, dd, paths_vhosts, + ARRAY_SIZE(paths_vhosts), lejp_vhosts_cb) > 1) + return 1; + + *cs = a.p; + *len = a.end - a.p; + + if (!a.any_vhosts) { + lwsl_err("Need at least one vhost\n"); + return 1; + } + +// lws_finalize_startup(context); + + return 0; +} diff --git a/thirdparty/lws/server/parsers.c b/thirdparty/lws/server/parsers.c new file mode 100644 index 0000000000..fb345ab04c --- /dev/null +++ b/thirdparty/lws/server/parsers.c @@ -0,0 +1,1783 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +const unsigned char lextable[] = { + #include "lextable.h" +}; + +#define FAIL_CHAR 0x08 + +int LWS_WARN_UNUSED_RESULT +lextable_decode(int pos, char c) +{ + if (c >= 'A' && c <= 'Z') + c += 'a' - 'A'; + + while (1) { + if (lextable[pos] & (1 << 7)) { /* 1-byte, fail on mismatch */ + if ((lextable[pos] & 0x7f) != c) + return -1; + /* fall thru */ + pos++; + if (lextable[pos] == FAIL_CHAR) + return -1; + return pos; + } + + if (lextable[pos] == FAIL_CHAR) + return -1; + + /* b7 = 0, end or 3-byte */ + if (lextable[pos] < FAIL_CHAR) /* terminal marker */ + return pos; + + if (lextable[pos] == c) /* goto */ + return pos + (lextable[pos + 1]) + + (lextable[pos + 2] << 8); + /* fall thru goto */ + pos += 3; + /* continue */ + } +} + +static struct allocated_headers * +_lws_create_ah(struct lws_context_per_thread *pt, ah_data_idx_t data_size) +{ + struct allocated_headers *ah = lws_zalloc(sizeof(*ah), "ah struct"); + + if (!ah) + return NULL; + + ah->data = lws_malloc(data_size, "ah data"); + if (!ah->data) { + lws_free(ah); + + return NULL; + } + ah->next = pt->ah_list; + pt->ah_list = ah; + ah->data_length = data_size; + pt->ah_pool_length++; + + lwsl_info("%s: created ah %p (size %d): pool length %d\n", __func__, + ah, (int)data_size, pt->ah_pool_length); + + return ah; +} + +int +_lws_destroy_ah(struct lws_context_per_thread *pt, struct allocated_headers *ah) +{ + lws_start_foreach_llp(struct allocated_headers **, a, pt->ah_list) { + if ((*a) == ah) { + *a = ah->next; + pt->ah_pool_length--; + lwsl_info("%s: freed ah %p : pool length %d\n", + __func__, ah, pt->ah_pool_length); + if (ah->data) + lws_free(ah->data); + lws_free(ah); + + return 0; + } + } lws_end_foreach_llp(a, next); + + return 1; +} + +void +_lws_header_table_reset(struct allocated_headers *ah) +{ + /* init the ah to reflect no headers or data have appeared yet */ + memset(ah->frag_index, 0, sizeof(ah->frag_index)); + memset(ah->frags, 0, sizeof(ah->frags)); + ah->nfrag = 0; + ah->pos = 0; + ah->http_response = 0; +} + +// doesn't scrub the ah rxbuffer by default, parent must do if needed + +void +lws_header_table_reset(struct lws *wsi, int autoservice) +{ + struct allocated_headers *ah = wsi->u.hdr.ah; + struct lws_context_per_thread *pt; + struct lws_pollfd *pfd; + + /* if we have the idea we're resetting 'our' ah, must be bound to one */ + assert(ah); + /* ah also concurs with ownership */ + assert(ah->wsi == wsi); + + _lws_header_table_reset(ah); + + wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; + wsi->u.hdr.lextable_pos = 0; + + /* since we will restart the ah, our new headers are not completed */ + wsi->hdr_parsing_completed = 0; + + /* while we hold the ah, keep a timeout on the wsi */ + lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH, + wsi->vhost->timeout_secs_ah_idle); + + time(&ah->assigned); + + /* + * if we inherited pending rx (from socket adoption deferred + * processing), apply and free it. + */ + if (wsi->u.hdr.preamble_rx) { + memcpy(ah->rx, wsi->u.hdr.preamble_rx, + wsi->u.hdr.preamble_rx_len); + ah->rxlen = wsi->u.hdr.preamble_rx_len; + lws_free_set_NULL(wsi->u.hdr.preamble_rx); + + if (autoservice) { + lwsl_debug("%s: service on readbuf ah\n", __func__); + + pt = &wsi->context->pt[(int)wsi->tsi]; + /* + * Unlike a normal connect, we have the headers already + * (or the first part of them anyway) + */ + pfd = &pt->fds[wsi->position_in_fds_table]; + pfd->revents |= LWS_POLLIN; + lwsl_err("%s: calling service\n", __func__); + lws_service_fd_tsi(wsi->context, pfd, wsi->tsi); + } + } +} + +static void +_lws_header_ensure_we_are_on_waiting_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws_pollargs pa; + struct lws **pwsi = &pt->ah_wait_list; + + while (*pwsi) { + if (*pwsi == wsi) + return; + pwsi = &(*pwsi)->u.hdr.ah_wait_list; + } + + lwsl_info("%s: wsi: %p\n", __func__, wsi); + wsi->u.hdr.ah_wait_list = pt->ah_wait_list; + pt->ah_wait_list = wsi; + pt->ah_wait_list_length++; + + /* we cannot accept input then */ + + _lws_change_pollfd(wsi, LWS_POLLIN, 0, &pa); +} + +static int +__lws_remove_from_ah_waiting_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws **pwsi =&pt->ah_wait_list; + + while (*pwsi) { + if (*pwsi == wsi) { + lwsl_info("%s: wsi %p\n", __func__, wsi); + /* point prev guy to our next */ + *pwsi = wsi->u.hdr.ah_wait_list; + /* we shouldn't point anywhere now */ + wsi->u.hdr.ah_wait_list = NULL; + pt->ah_wait_list_length--; + + return 1; + } + pwsi = &(*pwsi)->u.hdr.ah_wait_list; + } + + return 0; +} + +int LWS_WARN_UNUSED_RESULT +lws_header_table_attach(struct lws *wsi, int autoservice) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws_pollargs pa; + int n; + + lwsl_info("%s: wsi %p: ah %p (tsi %d, count = %d) in\n", __func__, + (void *)wsi, (void *)wsi->u.hdr.ah, wsi->tsi, + pt->ah_count_in_use); + + /* if we are already bound to one, just clear it down */ + if (wsi->u.hdr.ah) { + lwsl_info("%s: cleardown\n", __func__); + goto reset; + } + + lws_pt_lock(pt); + + n = pt->ah_count_in_use == context->max_http_header_pool; +#if defined(LWS_WITH_PEER_LIMITS) + if (!n) { + n = lws_peer_confirm_ah_attach_ok(context, wsi->peer); + if (n) + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_PEER_LIMIT_AH_DENIED, 1); + } +#endif + if (n) { + /* + * Pool is either all busy, or we don't want to give this + * particular guy an ah right now... + * + * Make sure we are on the waiting list, and return that we + * weren't able to provide the ah + */ + _lws_header_ensure_we_are_on_waiting_list(wsi); + + goto bail; + } + + __lws_remove_from_ah_waiting_list(wsi); + + wsi->u.hdr.ah = _lws_create_ah(pt, context->max_http_header_data); + if (!wsi->u.hdr.ah) { /* we could not create an ah */ + _lws_header_ensure_we_are_on_waiting_list(wsi); + + goto bail; + } + + wsi->u.hdr.ah->in_use = 1; + wsi->u.hdr.ah->wsi = wsi; /* mark our owner */ + pt->ah_count_in_use++; + +#if defined(LWS_WITH_PEER_LIMITS) + if (wsi->peer) + wsi->peer->count_ah++; +#endif + + _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa); + + lwsl_info("%s: did attach wsi %p: ah %p: count %d (on exit)\n", __func__, + (void *)wsi, (void *)wsi->u.hdr.ah, pt->ah_count_in_use); + + lws_pt_unlock(pt); + +reset: + + /* and reset the rx state */ + wsi->u.hdr.ah->rxpos = 0; + wsi->u.hdr.ah->rxlen = 0; + + lws_header_table_reset(wsi, autoservice); + +#ifndef LWS_NO_CLIENT + if (wsi->state == LWSS_CLIENT_UNCONNECTED) + if (!lws_client_connect_via_info2(wsi)) + /* our client connect has failed, the wsi + * has been closed + */ + return -1; +#endif + + return 0; + +bail: + lws_pt_unlock(pt); + + return 1; +} + +void +lws_header_table_force_to_detachable_state(struct lws *wsi) +{ + if (wsi->u.hdr.ah) { + wsi->u.hdr.ah->rxpos = -1; + wsi->u.hdr.ah->rxlen = -1; + wsi->hdr_parsing_completed = 1; + } +} + +int +lws_header_table_is_in_detachable_state(struct lws *wsi) +{ + struct allocated_headers *ah = wsi->u.hdr.ah; + + return ah && ah->rxpos == ah->rxlen && wsi->hdr_parsing_completed; +} + +int lws_header_table_detach(struct lws *wsi, int autoservice) +{ + struct lws_context *context = wsi->context; + struct allocated_headers *ah = wsi->u.hdr.ah; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws_pollargs pa; + struct lws **pwsi, **pwsi_eligible; + time_t now; + + lws_pt_lock(pt); + __lws_remove_from_ah_waiting_list(wsi); + lws_pt_unlock(pt); + + if (!ah) + return 0; + + lwsl_info("%s: wsi %p: ah %p (tsi=%d, count = %d)\n", __func__, + (void *)wsi, (void *)ah, wsi->tsi, + pt->ah_count_in_use); + + if (wsi->u.hdr.preamble_rx) + lws_free_set_NULL(wsi->u.hdr.preamble_rx); + + /* may not be detached while he still has unprocessed rx */ + if (!lws_header_table_is_in_detachable_state(wsi)) { + lwsl_err("%s: %p: CANNOT DETACH rxpos:%d, rxlen:%d, " + "wsi->hdr_parsing_completed = %d\n", __func__, wsi, + ah->rxpos, ah->rxlen, wsi->hdr_parsing_completed); + return 0; + } + + lws_pt_lock(pt); + + /* we did have an ah attached */ + time(&now); + if (ah->assigned && now - ah->assigned > 3) { + /* + * we're detaching the ah, but it was held an + * unreasonably long time + */ + lwsl_debug("%s: wsi %p: ah held %ds, " + "ah.rxpos %d, ah.rxlen %d, mode/state %d %d," + "wsi->more_rx_waiting %d\n", __func__, wsi, + (int)(now - ah->assigned), + ah->rxpos, ah->rxlen, wsi->mode, wsi->state, + wsi->more_rx_waiting); + } + + ah->assigned = 0; + + /* if we think we're detaching one, there should be one in use */ + assert(pt->ah_count_in_use > 0); + /* and this specific one should have been in use */ + assert(ah->in_use); + wsi->u.hdr.ah = NULL; + ah->wsi = NULL; /* no owner */ +#if defined(LWS_WITH_PEER_LIMITS) + lws_peer_track_ah_detach(context, wsi->peer); +#endif + + pwsi = &pt->ah_wait_list; + + /* oh there is nobody on the waiting list... leave the ah unattached */ + if (!*pwsi) + goto nobody_usable_waiting; + + /* + * at least one wsi on the same tsi is waiting, give it to oldest guy + * who is allowed to take it (if any) + */ + lwsl_info("pt wait list %p\n", *pwsi); + wsi = NULL; + pwsi_eligible = NULL; + + while (*pwsi) { +#if defined(LWS_WITH_PEER_LIMITS) + /* are we willing to give this guy an ah? */ + if (!lws_peer_confirm_ah_attach_ok(context, (*pwsi)->peer)) +#endif + { + wsi = *pwsi; + pwsi_eligible = pwsi; + } +#if defined(LWS_WITH_PEER_LIMITS) + else + if (!(*pwsi)->u.hdr.ah_wait_list) + lws_stats_atomic_bump(context, pt, + LWSSTATS_C_PEER_LIMIT_AH_DENIED, 1); +#endif + pwsi = &(*pwsi)->u.hdr.ah_wait_list; + } + + if (!wsi) /* everybody waiting already has too many ah... */ + goto nobody_usable_waiting; + + lwsl_info("%s: last eligible wsi in wait list %p\n", __func__, wsi); + + wsi->u.hdr.ah = ah; + ah->wsi = wsi; /* new owner */ + + /* and reset the rx state */ + ah->rxpos = 0; + ah->rxlen = 0; + lws_header_table_reset(wsi, autoservice); +#if defined(LWS_WITH_PEER_LIMITS) + if (wsi->peer) + wsi->peer->count_ah++; +#endif + + /* clients acquire the ah and then insert themselves in fds table... */ + if (wsi->position_in_fds_table != -1) { + lwsl_info("%s: Enabling %p POLLIN\n", __func__, wsi); + + /* he has been stuck waiting for an ah, but now his wait is + * over, let him progress */ + + _lws_change_pollfd(wsi, 0, LWS_POLLIN, &pa); + } + + /* point prev guy to next guy in list instead */ + *pwsi_eligible = wsi->u.hdr.ah_wait_list; + /* the guy who got one is out of the list */ + wsi->u.hdr.ah_wait_list = NULL; + pt->ah_wait_list_length--; + +#ifndef LWS_NO_CLIENT + if (wsi->state == LWSS_CLIENT_UNCONNECTED) { + lws_pt_unlock(pt); + + if (!lws_client_connect_via_info2(wsi)) { + /* our client connect has failed, the wsi + * has been closed + */ + + return -1; + } + return 0; + } +#endif + + assert(!!pt->ah_wait_list_length == !!(lws_intptr_t)pt->ah_wait_list); +bail: + lwsl_info("%s: wsi %p: ah %p (tsi=%d, count = %d)\n", __func__, + (void *)wsi, (void *)ah, pt->tid, pt->ah_count_in_use); + + lws_pt_unlock(pt); + + return 0; + +nobody_usable_waiting: + lwsl_info("%s: nobody usable waiting\n", __func__); + _lws_destroy_ah(pt, ah); + pt->ah_count_in_use--; + + goto bail; +} + +LWS_VISIBLE int +lws_hdr_fragment_length(struct lws *wsi, enum lws_token_indexes h, int frag_idx) +{ + int n; + + if (!wsi->u.hdr.ah) + return 0; + + n = wsi->u.hdr.ah->frag_index[h]; + if (!n) + return 0; + do { + if (!frag_idx) + return wsi->u.hdr.ah->frags[n].len; + n = wsi->u.hdr.ah->frags[n].nfrag; + } while (frag_idx-- && n); + + return 0; +} + +LWS_VISIBLE int lws_hdr_total_length(struct lws *wsi, enum lws_token_indexes h) +{ + int n; + int len = 0; + + if (!wsi->u.hdr.ah) + return 0; + + n = wsi->u.hdr.ah->frag_index[h]; + if (!n) + return 0; + do { + len += wsi->u.hdr.ah->frags[n].len; + n = wsi->u.hdr.ah->frags[n].nfrag; + } while (n); + + return len; +} + +LWS_VISIBLE int lws_hdr_copy_fragment(struct lws *wsi, char *dst, int len, + enum lws_token_indexes h, int frag_idx) +{ + int n = 0; + int f; + + if (!wsi->u.hdr.ah) + return -1; + + f = wsi->u.hdr.ah->frag_index[h]; + + if (!f) + return -1; + + while (n < frag_idx) { + f = wsi->u.hdr.ah->frags[f].nfrag; + if (!f) + return -1; + n++; + } + + if (wsi->u.hdr.ah->frags[f].len >= len) + return -1; + + memcpy(dst, wsi->u.hdr.ah->data + wsi->u.hdr.ah->frags[f].offset, + wsi->u.hdr.ah->frags[f].len); + dst[wsi->u.hdr.ah->frags[f].len] = '\0'; + + return wsi->u.hdr.ah->frags[f].len; +} + +LWS_VISIBLE int lws_hdr_copy(struct lws *wsi, char *dst, int len, + enum lws_token_indexes h) +{ + int toklen = lws_hdr_total_length(wsi, h); + int n; + + if (toklen >= len) + return -1; + + if (!wsi->u.hdr.ah) + return -1; + + n = wsi->u.hdr.ah->frag_index[h]; + if (!n) + return 0; + + do { + if (wsi->u.hdr.ah->frags[n].len >= len) + return -1; + strncpy(dst, &wsi->u.hdr.ah->data[wsi->u.hdr.ah->frags[n].offset], + wsi->u.hdr.ah->frags[n].len); + dst += wsi->u.hdr.ah->frags[n].len; + len -= wsi->u.hdr.ah->frags[n].len; + n = wsi->u.hdr.ah->frags[n].nfrag; + } while (n); + *dst = '\0'; + + return toklen; +} + +char *lws_hdr_simple_ptr(struct lws *wsi, enum lws_token_indexes h) +{ + int n; + + n = wsi->u.hdr.ah->frag_index[h]; + if (!n) + return NULL; + + return wsi->u.hdr.ah->data + wsi->u.hdr.ah->frags[n].offset; +} + +int LWS_WARN_UNUSED_RESULT +lws_pos_in_bounds(struct lws *wsi) +{ + if (wsi->u.hdr.ah->pos < + (unsigned int)wsi->context->max_http_header_data) + return 0; + + if (wsi->u.hdr.ah->pos == wsi->context->max_http_header_data) { + lwsl_err("Ran out of header data space\n"); + return 1; + } + + /* + * with these tests everywhere, it should never be able to exceed + * the limit, only meet it + */ + lwsl_err("%s: pos %d, limit %d\n", __func__, wsi->u.hdr.ah->pos, + wsi->context->max_http_header_data); + assert(0); + + return 1; +} + +int LWS_WARN_UNUSED_RESULT +lws_hdr_simple_create(struct lws *wsi, enum lws_token_indexes h, const char *s) +{ + wsi->u.hdr.ah->nfrag++; + if (wsi->u.hdr.ah->nfrag == ARRAY_SIZE(wsi->u.hdr.ah->frags)) { + lwsl_warn("More hdr frags than we can deal with, dropping\n"); + return -1; + } + + wsi->u.hdr.ah->frag_index[h] = wsi->u.hdr.ah->nfrag; + + wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].offset = wsi->u.hdr.ah->pos; + wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len = 0; + wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].nfrag = 0; + + do { + if (lws_pos_in_bounds(wsi)) + return -1; + + wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = *s; + if (*s) + wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len++; + } while (*s++); + + return 0; +} + +signed char char_to_hex(const char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + + if (c >= 'A' && c <= 'F') + return c - 'A' + 10; + + return -1; +} + +static int LWS_WARN_UNUSED_RESULT +issue_char(struct lws *wsi, unsigned char c) +{ + unsigned short frag_len; + + if (lws_pos_in_bounds(wsi)) + return -1; + + frag_len = wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len; + /* + * If we haven't hit the token limit, just copy the character into + * the header + */ + if (frag_len < wsi->u.hdr.current_token_limit) { + wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = c; + if (c) + wsi->u.hdr.ah->frags[wsi->u.hdr.ah->nfrag].len++; + return 0; + } + + /* Insert a null character when we *hit* the limit: */ + if (frag_len == wsi->u.hdr.current_token_limit) { + if (lws_pos_in_bounds(wsi)) + return -1; + + wsi->u.hdr.ah->data[wsi->u.hdr.ah->pos++] = '\0'; + lwsl_warn("header %i exceeds limit %d\n", + wsi->u.hdr.parser_state, + wsi->u.hdr.current_token_limit); + } + + return 1; +} + +int +lws_parse_urldecode(struct lws *wsi, uint8_t *_c) +{ + struct allocated_headers *ah = wsi->u.hdr.ah; + unsigned int enc = 0; + uint8_t c = *_c; + + /* + * PRIORITY 1 + * special URI processing... convert %xx + */ + switch (wsi->u.hdr.ues) { + case URIES_IDLE: + if (c == '%') { + wsi->u.hdr.ues = URIES_SEEN_PERCENT; + goto swallow; + } + break; + case URIES_SEEN_PERCENT: + if (char_to_hex(c) < 0) + /* illegal post-% char */ + goto forbid; + + wsi->u.hdr.esc_stash = c; + wsi->u.hdr.ues = URIES_SEEN_PERCENT_H1; + goto swallow; + + case URIES_SEEN_PERCENT_H1: + if (char_to_hex(c) < 0) + /* illegal post-% char */ + goto forbid; + + *_c = (char_to_hex(wsi->u.hdr.esc_stash) << 4) | + char_to_hex(c); + c = *_c; + enc = 1; + wsi->u.hdr.ues = URIES_IDLE; + break; + } + + /* + * PRIORITY 2 + * special URI processing... + * convert /.. or /... or /../ etc to / + * convert /./ to / + * convert // or /// etc to / + * leave /.dir or whatever alone + */ + + switch (wsi->u.hdr.ups) { + case URIPS_IDLE: + if (!c) + return -1; + /* genuine delimiter */ + if ((c == '&' || c == ';') && !enc) { + if (issue_char(wsi, c) < 0) + return -1; + /* swallow the terminator */ + ah->frags[ah->nfrag].len--; + /* link to next fragment */ + ah->frags[ah->nfrag].nfrag = ah->nfrag + 1; + ah->nfrag++; + if (ah->nfrag >= ARRAY_SIZE(ah->frags)) + goto excessive; + /* start next fragment after the & */ + wsi->u.hdr.post_literal_equal = 0; + ah->frags[ah->nfrag].offset = ah->pos; + ah->frags[ah->nfrag].len = 0; + ah->frags[ah->nfrag].nfrag = 0; + goto swallow; + } + /* uriencoded = in the name part, disallow */ + if (c == '=' && enc && + ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] && + !wsi->u.hdr.post_literal_equal) { + c = '_'; + *_c =c; + } + + /* after the real =, we don't care how many = */ + if (c == '=' && !enc) + wsi->u.hdr.post_literal_equal = 1; + + /* + to space */ + if (c == '+' && !enc) { + c = ' '; + *_c = c; + } + /* issue the first / always */ + if (c == '/' && !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) + wsi->u.hdr.ups = URIPS_SEEN_SLASH; + break; + case URIPS_SEEN_SLASH: + /* swallow subsequent slashes */ + if (c == '/') + goto swallow; + /* track and swallow the first . after / */ + if (c == '.') { + wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT; + goto swallow; + } + wsi->u.hdr.ups = URIPS_IDLE; + break; + case URIPS_SEEN_SLASH_DOT: + /* swallow second . */ + if (c == '.') { + wsi->u.hdr.ups = URIPS_SEEN_SLASH_DOT_DOT; + goto swallow; + } + /* change /./ to / */ + if (c == '/') { + wsi->u.hdr.ups = URIPS_SEEN_SLASH; + goto swallow; + } + /* it was like /.dir ... regurgitate the . */ + wsi->u.hdr.ups = URIPS_IDLE; + if (issue_char(wsi, '.') < 0) + return -1; + break; + + case URIPS_SEEN_SLASH_DOT_DOT: + + /* /../ or /..[End of URI] --> backup to last / */ + if (c == '/' || c == '?') { + /* + * back up one dir level if possible + * safe against header fragmentation because + * the method URI can only be in 1 fragment + */ + if (ah->frags[ah->nfrag].len > 2) { + ah->pos--; + ah->frags[ah->nfrag].len--; + do { + ah->pos--; + ah->frags[ah->nfrag].len--; + } while (ah->frags[ah->nfrag].len > 1 && + ah->data[ah->pos] != '/'); + } + wsi->u.hdr.ups = URIPS_SEEN_SLASH; + if (ah->frags[ah->nfrag].len > 1) + break; + goto swallow; + } + + /* /..[^/] ... regurgitate and allow */ + + if (issue_char(wsi, '.') < 0) + return -1; + if (issue_char(wsi, '.') < 0) + return -1; + wsi->u.hdr.ups = URIPS_IDLE; + break; + } + + if (c == '?' && !enc && + !ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS]) { /* start of URI arguments */ + if (wsi->u.hdr.ues != URIES_IDLE) + goto forbid; + + /* seal off uri header */ + if (issue_char(wsi, '\0') < 0) + return -1; + + /* move to using WSI_TOKEN_HTTP_URI_ARGS */ + ah->nfrag++; + if (ah->nfrag >= ARRAY_SIZE(ah->frags)) + goto excessive; + ah->frags[ah->nfrag].offset = ah->pos; + ah->frags[ah->nfrag].len = 0; + ah->frags[ah->nfrag].nfrag = 0; + + wsi->u.hdr.post_literal_equal = 0; + ah->frag_index[WSI_TOKEN_HTTP_URI_ARGS] = ah->nfrag; + wsi->u.hdr.ups = URIPS_IDLE; + goto swallow; + } + + return LPUR_CONTINUE; + +swallow: + return LPUR_SWALLOW; + +forbid: + return LPUR_FORBID; + +excessive: + return LPUR_EXCESSIVE; +} + +static const unsigned char methods[] = { + WSI_TOKEN_GET_URI, + WSI_TOKEN_POST_URI, + WSI_TOKEN_OPTIONS_URI, + WSI_TOKEN_PUT_URI, + WSI_TOKEN_PATCH_URI, + WSI_TOKEN_DELETE_URI, + WSI_TOKEN_CONNECT, + WSI_TOKEN_HEAD_URI, +}; + +int LWS_WARN_UNUSED_RESULT +lws_parse(struct lws *wsi, unsigned char c) +{ + struct allocated_headers *ah = wsi->u.hdr.ah; + struct lws_context *context = wsi->context; + unsigned int n, m; + int r; + + assert(wsi->u.hdr.ah); + + switch (wsi->u.hdr.parser_state) { + default: + + lwsl_parser("WSI_TOK_(%d) '%c'\n", wsi->u.hdr.parser_state, c); + + /* collect into malloc'd buffers */ + /* optional initial space swallow */ + if (!ah->frags[ah->frag_index[wsi->u.hdr.parser_state]].len && + c == ' ') + break; + + for (m = 0; m < ARRAY_SIZE(methods); m++) + if (wsi->u.hdr.parser_state == methods[m]) + break; + if (m == ARRAY_SIZE(methods)) + /* it was not any of the methods */ + goto check_eol; + + /* special URI processing... end at space */ + + if (c == ' ') { + /* enforce starting with / */ + if (!ah->frags[ah->nfrag].len) + if (issue_char(wsi, '/') < 0) + return -1; + + if (wsi->u.hdr.ups == URIPS_SEEN_SLASH_DOT_DOT) { + /* + * back up one dir level if possible + * safe against header fragmentation because + * the method URI can only be in 1 fragment + */ + if (ah->frags[ah->nfrag].len > 2) { + ah->pos--; + ah->frags[ah->nfrag].len--; + do { + ah->pos--; + ah->frags[ah->nfrag].len--; + } while (ah->frags[ah->nfrag].len > 1 && + ah->data[ah->pos] != '/'); + } + } + + /* begin parsing HTTP version: */ + if (issue_char(wsi, '\0') < 0) + return -1; + wsi->u.hdr.parser_state = WSI_TOKEN_HTTP; + goto start_fragment; + } + + r = lws_parse_urldecode(wsi, &c); + switch (r) { + case LPUR_CONTINUE: + break; + case LPUR_SWALLOW: + goto swallow; + case LPUR_FORBID: + goto forbid; + case LPUR_EXCESSIVE: + goto excessive; + default: + return -1; + } +check_eol: + /* bail at EOL */ + if (wsi->u.hdr.parser_state != WSI_TOKEN_CHALLENGE && + c == '\x0d') { + if (wsi->u.hdr.ues != URIES_IDLE) + goto forbid; + + c = '\0'; + wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING_SAW_CR; + lwsl_parser("*\n"); + } + + n = issue_char(wsi, c); + if ((int)n < 0) + return -1; + if (n > 0) + wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING; + +swallow: + /* per-protocol end of headers management */ + + if (wsi->u.hdr.parser_state == WSI_TOKEN_CHALLENGE) + goto set_parsing_complete; + break; + + /* collecting and checking a name part */ + case WSI_TOKEN_NAME_PART: + lwsl_parser("WSI_TOKEN_NAME_PART '%c' 0x%02X (mode=%d) wsi->u.hdr.lextable_pos=%d\n", c, c, wsi->mode, wsi->u.hdr.lextable_pos); + + wsi->u.hdr.lextable_pos = + lextable_decode(wsi->u.hdr.lextable_pos, c); + /* + * Server needs to look out for unknown methods... + */ + if (wsi->u.hdr.lextable_pos < 0 && + (wsi->mode == LWSCM_HTTP_SERVING)) { + /* this is not a header we know about */ + for (m = 0; m < ARRAY_SIZE(methods); m++) + if (ah->frag_index[methods[m]]) { + /* + * already had the method, no idea what + * this crap from the client is, ignore + */ + wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING; + break; + } + /* + * hm it's an unknown http method from a client in fact, + * it cannot be valid http + */ + if (m == ARRAY_SIZE(methods)) { + /* + * are we set up to accept raw in these cases? + */ + if (lws_check_opt(wsi->vhost->options, + LWS_SERVER_OPTION_FALLBACK_TO_RAW)) + return 2; /* transition to raw */ + + lwsl_info("Unknown method - dropping\n"); + goto forbid; + } + break; + } + /* + * ...otherwise for a client, let him ignore unknown headers + * coming from the server + */ + if (wsi->u.hdr.lextable_pos < 0) { + wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING; + break; + } + + if (lextable[wsi->u.hdr.lextable_pos] < FAIL_CHAR) { + /* terminal state */ + + n = ((unsigned int)lextable[wsi->u.hdr.lextable_pos] << 8) | + lextable[wsi->u.hdr.lextable_pos + 1]; + + lwsl_parser("known hdr %d\n", n); + for (m = 0; m < ARRAY_SIZE(methods); m++) + if (n == methods[m] && + ah->frag_index[methods[m]]) { + lwsl_warn("Duplicated method\n"); + return -1; + } + + /* + * WSORIGIN is protocol equiv to ORIGIN, + * JWebSocket likes to send it, map to ORIGIN + */ + if (n == WSI_TOKEN_SWORIGIN) + n = WSI_TOKEN_ORIGIN; + + wsi->u.hdr.parser_state = (enum lws_token_indexes) + (WSI_TOKEN_GET_URI + n); + + if (context->token_limits) + wsi->u.hdr.current_token_limit = + context->token_limits->token_limit[ + wsi->u.hdr.parser_state]; + else + wsi->u.hdr.current_token_limit = + wsi->context->max_http_header_data; + + if (wsi->u.hdr.parser_state == WSI_TOKEN_CHALLENGE) + goto set_parsing_complete; + + goto start_fragment; + } + break; + +start_fragment: + ah->nfrag++; +excessive: + if (ah->nfrag == ARRAY_SIZE(ah->frags)) { + lwsl_warn("More hdr frags than we can deal with\n"); + return -1; + } + + ah->frags[ah->nfrag].offset = ah->pos; + ah->frags[ah->nfrag].len = 0; + ah->frags[ah->nfrag].nfrag = 0; + ah->frags[ah->nfrag].flags = 2; + + n = ah->frag_index[wsi->u.hdr.parser_state]; + if (!n) { /* first fragment */ + ah->frag_index[wsi->u.hdr.parser_state] = ah->nfrag; + ah->hdr_token_idx = wsi->u.hdr.parser_state; + break; + } + /* continuation */ + while (ah->frags[n].nfrag) + n = ah->frags[n].nfrag; + ah->frags[n].nfrag = ah->nfrag; + + if (issue_char(wsi, ' ') < 0) + return -1; + break; + + /* skipping arg part of a name we didn't recognize */ + case WSI_TOKEN_SKIPPING: + lwsl_parser("WSI_TOKEN_SKIPPING '%c'\n", c); + + if (c == '\x0d') + wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING_SAW_CR; + break; + + case WSI_TOKEN_SKIPPING_SAW_CR: + lwsl_parser("WSI_TOKEN_SKIPPING_SAW_CR '%c'\n", c); + if (wsi->u.hdr.ues != URIES_IDLE) + goto forbid; + if (c == '\x0a') { + wsi->u.hdr.parser_state = WSI_TOKEN_NAME_PART; + wsi->u.hdr.lextable_pos = 0; + } else + wsi->u.hdr.parser_state = WSI_TOKEN_SKIPPING; + break; + /* we're done, ignore anything else */ + + case WSI_PARSING_COMPLETE: + lwsl_parser("WSI_PARSING_COMPLETE '%c'\n", c); + break; + } + + return 0; + +set_parsing_complete: + if (wsi->u.hdr.ues != URIES_IDLE) + goto forbid; + if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { + if (lws_hdr_total_length(wsi, WSI_TOKEN_VERSION)) + wsi->ietf_spec_revision = + atoi(lws_hdr_simple_ptr(wsi, WSI_TOKEN_VERSION)); + + lwsl_parser("v%02d hdrs completed\n", wsi->ietf_spec_revision); + } + wsi->u.hdr.parser_state = WSI_PARSING_COMPLETE; + wsi->hdr_parsing_completed = 1; + + return 0; + +forbid: + lwsl_notice(" forbidding on uri sanitation\n"); + lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); + + return -1; +} + +LWS_VISIBLE int lws_frame_is_binary(struct lws *wsi) +{ + return wsi->u.ws.frame_is_binary; +} + +void +lws_add_wsi_to_draining_ext_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + + if (wsi->u.ws.rx_draining_ext) + return; + + lwsl_ext("%s: RX EXT DRAINING: Adding to list\n", __func__); + + wsi->u.ws.rx_draining_ext = 1; + wsi->u.ws.rx_draining_ext_list = pt->rx_draining_ext_list; + pt->rx_draining_ext_list = wsi; +} + +void +lws_remove_wsi_from_draining_ext_list(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + struct lws **w = &pt->rx_draining_ext_list; + + if (!wsi->u.ws.rx_draining_ext) + return; + + lwsl_ext("%s: RX EXT DRAINING: Removing from list\n", __func__); + + wsi->u.ws.rx_draining_ext = 0; + + /* remove us from context draining ext list */ + while (*w) { + if (*w == wsi) { + /* if us, point it instead to who we were pointing to */ + *w = wsi->u.ws.rx_draining_ext_list; + break; + } + w = &((*w)->u.ws.rx_draining_ext_list); + } + wsi->u.ws.rx_draining_ext_list = NULL; +} + +/* + * client-parser.c: lws_client_rx_sm() needs to be roughly kept in + * sync with changes here, esp related to ext draining + */ + +int +lws_rx_sm(struct lws *wsi, unsigned char c) +{ + int callback_action = LWS_CALLBACK_RECEIVE; + int ret = 0, n, rx_draining_ext = 0; + struct lws_tokens eff_buf; + + eff_buf.token = NULL; + eff_buf.token_len = 0; + if (wsi->socket_is_permanently_unusable) + return -1; + + switch (wsi->lws_rx_parse_state) { + case LWS_RXPS_NEW: + if (wsi->u.ws.rx_draining_ext) { + eff_buf.token = NULL; + eff_buf.token_len = 0; + lws_remove_wsi_from_draining_ext_list(wsi); + rx_draining_ext = 1; + lwsl_debug("%s: doing draining flow\n", __func__); + + goto drain_extension; + } + switch (wsi->ietf_spec_revision) { + case 13: + /* + * no prepended frame key any more + */ + wsi->u.ws.all_zero_nonce = 1; + goto handle_first; + + default: + lwsl_warn("lws_rx_sm: unknown spec version %d\n", + wsi->ietf_spec_revision); + break; + } + break; + case LWS_RXPS_04_mask_1: + wsi->u.ws.mask[1] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_04_mask_2; + break; + case LWS_RXPS_04_mask_2: + wsi->u.ws.mask[2] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_04_mask_3; + break; + case LWS_RXPS_04_mask_3: + wsi->u.ws.mask[3] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + + /* + * start from the zero'th byte in the XOR key buffer since + * this is the start of a frame with a new key + */ + + wsi->u.ws.mask_idx = 0; + + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_1; + break; + + /* + * 04 logical framing from the spec (all this is masked when incoming + * and has to be unmasked) + * + * We ignore the possibility of extension data because we don't + * negotiate any extensions at the moment. + * + * 0 1 2 3 + * 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 + * +-+-+-+-+-------+-+-------------+-------------------------------+ + * |F|R|R|R| opcode|R| Payload len | Extended payload length | + * |I|S|S|S| (4) |S| (7) | (16/63) | + * |N|V|V|V| |V| | (if payload len==126/127) | + * | |1|2|3| |4| | | + * +-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - + + * | Extended payload length continued, if payload len == 127 | + * + - - - - - - - - - - - - - - - +-------------------------------+ + * | | Extension data | + * +-------------------------------+ - - - - - - - - - - - - - - - + + * : : + * +---------------------------------------------------------------+ + * : Application data : + * +---------------------------------------------------------------+ + * + * We pass payload through to userland as soon as we get it, ignoring + * FIN. It's up to userland to buffer it up if it wants to see a + * whole unfragmented block of the original size (which may be up to + * 2^63 long!) + */ + + case LWS_RXPS_04_FRAME_HDR_1: +handle_first: + + wsi->u.ws.opcode = c & 0xf; + wsi->u.ws.rsv = c & 0x70; + wsi->u.ws.final = !!((c >> 7) & 1); + + switch (wsi->u.ws.opcode) { + case LWSWSOPC_TEXT_FRAME: + case LWSWSOPC_BINARY_FRAME: + wsi->u.ws.rsv_first_msg = (c & 0x70); + wsi->u.ws.frame_is_binary = + wsi->u.ws.opcode == LWSWSOPC_BINARY_FRAME; + wsi->u.ws.first_fragment = 1; + break; + case 3: + case 4: + case 5: + case 6: + case 7: + case 0xb: + case 0xc: + case 0xd: + case 0xe: + case 0xf: + lwsl_info("illegal opcode\n"); + return -1; + } + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN: + + wsi->u.ws.this_frame_masked = !!(c & 0x80); + + switch (c & 0x7f) { + case 126: + /* control frames are not allowed to have big lengths */ + if (wsi->u.ws.opcode & 8) + goto illegal_ctl_length; + + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_2; + break; + case 127: + /* control frames are not allowed to have big lengths */ + if (wsi->u.ws.opcode & 8) + goto illegal_ctl_length; + + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_8; + break; + default: + wsi->u.ws.rx_packet_length = c & 0x7f; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else + if (wsi->u.ws.rx_packet_length) + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + else { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + break; + } + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_2: + wsi->u.ws.rx_packet_length = c << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN16_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN16_1: + wsi->u.ws.rx_packet_length |= c; + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_8: + if (c & 0x80) { + lwsl_warn("b63 of length must be zero\n"); + /* kill the connection */ + return -1; + } +#if defined __LP64__ + wsi->u.ws.rx_packet_length = ((size_t)c) << 56; +#else + wsi->u.ws.rx_packet_length = 0; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_7; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_7: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 48; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_6; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_6: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 40; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_5; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_5: +#if defined __LP64__ + wsi->u.ws.rx_packet_length |= ((size_t)c) << 32; +#endif + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_4; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_4: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 24; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_3; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_3: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 16; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_2; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_2: + wsi->u.ws.rx_packet_length |= ((size_t)c) << 8; + wsi->lws_rx_parse_state = LWS_RXPS_04_FRAME_HDR_LEN64_1; + break; + + case LWS_RXPS_04_FRAME_HDR_LEN64_1: + wsi->u.ws.rx_packet_length |= ((size_t)c); + if (wsi->u.ws.this_frame_masked) + wsi->lws_rx_parse_state = + LWS_RXPS_07_COLLECT_FRAME_KEY_1; + else + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_1: + wsi->u.ws.mask[0] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_2; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_2: + wsi->u.ws.mask[1] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_3; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_3: + wsi->u.ws.mask[2] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = LWS_RXPS_07_COLLECT_FRAME_KEY_4; + break; + + case LWS_RXPS_07_COLLECT_FRAME_KEY_4: + wsi->u.ws.mask[3] = c; + if (c) + wsi->u.ws.all_zero_nonce = 0; + wsi->lws_rx_parse_state = + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED; + wsi->u.ws.mask_idx = 0; + if (wsi->u.ws.rx_packet_length == 0) { + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + break; + + + case LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED: + assert(wsi->u.ws.rx_ubuf); + + if (wsi->u.ws.rx_draining_ext) + goto drain_extension; + + if (wsi->u.ws.rx_ubuf_head + LWS_PRE >= + wsi->u.ws.rx_ubuf_alloc) { + lwsl_err("Attempted overflow \n"); + return -1; + } + if (wsi->u.ws.all_zero_nonce) + wsi->u.ws.rx_ubuf[LWS_PRE + + (wsi->u.ws.rx_ubuf_head++)] = c; + else + wsi->u.ws.rx_ubuf[LWS_PRE + + (wsi->u.ws.rx_ubuf_head++)] = + c ^ wsi->u.ws.mask[ + (wsi->u.ws.mask_idx++) & 3]; + + if (--wsi->u.ws.rx_packet_length == 0) { + /* spill because we have the whole frame */ + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + goto spill; + } + + /* + * if there's no protocol max frame size given, we are + * supposed to default to context->pt_serv_buf_size + */ + if (!wsi->protocol->rx_buffer_size && + wsi->u.ws.rx_ubuf_head != wsi->context->pt_serv_buf_size) + break; + + if (wsi->protocol->rx_buffer_size && + wsi->u.ws.rx_ubuf_head != wsi->protocol->rx_buffer_size) + break; + + /* spill because we filled our rx buffer */ +spill: + /* + * is this frame a control packet we should take care of at this + * layer? If so service it and hide it from the user callback + */ + + lwsl_parser("spill on %s\n", wsi->protocol->name); + + switch (wsi->u.ws.opcode) { + case LWSWSOPC_CLOSE: + + /* is this an acknowledgement of our close? */ + if (wsi->state == LWSS_AWAITING_CLOSE_ACK) { + /* + * fine he has told us he is closing too, let's + * finish our close + */ + lwsl_parser("seen client close ack\n"); + return -1; + } + if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY) + /* if he sends us 2 CLOSE, kill him */ + return -1; + + if (lws_partial_buffered(wsi)) { + /* + * if we're in the middle of something, + * we can't do a normal close response and + * have to just close our end. + */ + wsi->socket_is_permanently_unusable = 1; + lwsl_parser("Closing on peer close due to Pending tx\n"); + return -1; + } + + if (user_callback_handle_rxflow( + wsi->protocol->callback, wsi, + LWS_CALLBACK_WS_PEER_INITIATED_CLOSE, + wsi->user_space, + &wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head)) + return -1; + + lwsl_parser("server sees client close packet\n"); + wsi->state = LWSS_RETURNED_CLOSE_ALREADY; + /* deal with the close packet contents as a PONG */ + wsi->u.ws.payload_is_close = 1; + goto process_as_ping; + + case LWSWSOPC_PING: + lwsl_info("received %d byte ping, sending pong\n", + wsi->u.ws.rx_ubuf_head); + + if (wsi->u.ws.ping_pending_flag) { + /* + * there is already a pending ping payload + * we should just log and drop + */ + lwsl_parser("DROP PING since one pending\n"); + goto ping_drop; + } +process_as_ping: + /* control packets can only be < 128 bytes long */ + if (wsi->u.ws.rx_ubuf_head > 128 - 3) { + lwsl_parser("DROP PING payload too large\n"); + goto ping_drop; + } + + /* stash the pong payload */ + memcpy(wsi->u.ws.ping_payload_buf + LWS_PRE, + &wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head); + + wsi->u.ws.ping_payload_len = wsi->u.ws.rx_ubuf_head; + wsi->u.ws.ping_pending_flag = 1; + + /* get it sent as soon as possible */ + lws_callback_on_writable(wsi); +ping_drop: + wsi->u.ws.rx_ubuf_head = 0; + return 0; + + case LWSWSOPC_PONG: + lwsl_info("received pong\n"); + lwsl_hexdump(&wsi->u.ws.rx_ubuf[LWS_PRE], + wsi->u.ws.rx_ubuf_head); + + if (wsi->pending_timeout == PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG) { + lwsl_info("received expected PONG on wsi %p\n", wsi); + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + } + + /* issue it */ + callback_action = LWS_CALLBACK_RECEIVE_PONG; + break; + + case LWSWSOPC_TEXT_FRAME: + case LWSWSOPC_BINARY_FRAME: + case LWSWSOPC_CONTINUATION: + break; + + default: + lwsl_parser("passing opc %x up to exts\n", + wsi->u.ws.opcode); + /* + * It's something special we can't understand here. + * Pass the payload up to the extension's parsing + * state machine. + */ + + eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; + eff_buf.token_len = wsi->u.ws.rx_ubuf_head; + + if (lws_ext_cb_active(wsi, LWS_EXT_CB_EXTENDED_PAYLOAD_RX, + &eff_buf, 0) <= 0) + /* not handle or fail */ + lwsl_ext("ext opc opcode 0x%x unknown\n", + wsi->u.ws.opcode); + + wsi->u.ws.rx_ubuf_head = 0; + return 0; + } + + /* + * No it's real payload, pass it up to the user callback. + * It's nicely buffered with the pre-padding taken care of + * so it can be sent straight out again using lws_write + */ + + eff_buf.token = &wsi->u.ws.rx_ubuf[LWS_PRE]; + eff_buf.token_len = wsi->u.ws.rx_ubuf_head; + + if (wsi->u.ws.opcode == LWSWSOPC_PONG && !eff_buf.token_len) + goto already_done; + +drain_extension: + lwsl_ext("%s: passing %d to ext\n", __func__, eff_buf.token_len); + + if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY || + wsi->state == LWSS_AWAITING_CLOSE_ACK) + goto already_done; + + n = lws_ext_cb_active(wsi, LWS_EXT_CB_PAYLOAD_RX, &eff_buf, 0); + /* + * eff_buf may be pointing somewhere completely different now, + * it's the output + */ + wsi->u.ws.first_fragment = 0; + if (n < 0) { + /* + * we may rely on this to get RX, just drop connection + */ + wsi->socket_is_permanently_unusable = 1; + return -1; + } + + if (rx_draining_ext && eff_buf.token_len == 0) + goto already_done; + + if (n && eff_buf.token_len) + /* extension had more... main loop will come back */ + lws_add_wsi_to_draining_ext_list(wsi); + else + lws_remove_wsi_from_draining_ext_list(wsi); + + if (eff_buf.token_len > 0 || + callback_action == LWS_CALLBACK_RECEIVE_PONG) { + eff_buf.token[eff_buf.token_len] = '\0'; + + if (wsi->protocol->callback) { + + if (callback_action == LWS_CALLBACK_RECEIVE_PONG) + lwsl_info("Doing pong callback\n"); + + ret = user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, + (enum lws_callback_reasons)callback_action, + wsi->user_space, + eff_buf.token, + eff_buf.token_len); + } + else + lwsl_err("No callback on payload spill!\n"); + } + +already_done: + wsi->u.ws.rx_ubuf_head = 0; + break; + } + + return ret; + +illegal_ctl_length: + + lwsl_warn("Control frame with xtended length is illegal\n"); + /* kill the connection */ + return -1; +} + +LWS_VISIBLE size_t +lws_remaining_packet_payload(struct lws *wsi) +{ + return wsi->u.ws.rx_packet_length; +} + +/* Once we reach LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED, we know how much + * to expect in that state and can deal with it in bulk more efficiently. + */ + +int +lws_payload_until_length_exhausted(struct lws *wsi, unsigned char **buf, + size_t *len) +{ + unsigned char *buffer = *buf, mask[4]; + int buffer_size, n; + unsigned int avail; + char *rx_ubuf; + + if (wsi->protocol->rx_buffer_size) + buffer_size = wsi->protocol->rx_buffer_size; + else + buffer_size = wsi->context->pt_serv_buf_size; + avail = buffer_size - wsi->u.ws.rx_ubuf_head; + + /* do not consume more than we should */ + if (avail > wsi->u.ws.rx_packet_length) + avail = wsi->u.ws.rx_packet_length; + + /* do not consume more than what is in the buffer */ + if (avail > *len) + avail = *len; + + /* we want to leave 1 byte for the parser to handle properly */ + if (avail <= 1) + return 0; + + avail--; + rx_ubuf = wsi->u.ws.rx_ubuf + LWS_PRE + wsi->u.ws.rx_ubuf_head; + if (wsi->u.ws.all_zero_nonce) + memcpy(rx_ubuf, buffer, avail); + else { + + for (n = 0; n < 4; n++) + mask[n] = wsi->u.ws.mask[(wsi->u.ws.mask_idx + n) & 3]; + + /* deal with 4-byte chunks using unwrapped loop */ + n = avail >> 2; + while (n--) { + *(rx_ubuf++) = *(buffer++) ^ mask[0]; + *(rx_ubuf++) = *(buffer++) ^ mask[1]; + *(rx_ubuf++) = *(buffer++) ^ mask[2]; + *(rx_ubuf++) = *(buffer++) ^ mask[3]; + } + /* and the remaining bytes bytewise */ + for (n = 0; n < (int)(avail & 3); n++) + *(rx_ubuf++) = *(buffer++) ^ mask[n]; + + wsi->u.ws.mask_idx = (wsi->u.ws.mask_idx + avail) & 3; + } + + (*buf) += avail; + wsi->u.ws.rx_ubuf_head += avail; + wsi->u.ws.rx_packet_length -= avail; + *len -= avail; + + return avail; +} diff --git a/thirdparty/lws/server/ranges.c b/thirdparty/lws/server/ranges.c new file mode 100644 index 0000000000..bc1578d733 --- /dev/null +++ b/thirdparty/lws/server/ranges.c @@ -0,0 +1,214 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * RFC7233 ranges parser + * + * Copyright (C) 2016 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +/* + * RFC7233 examples + * + * o The first 500 bytes (byte offsets 0-499, inclusive): + * + * bytes=0-499 + * + * o The second 500 bytes (byte offsets 500-999, inclusive): + * + * bytes=500-999 + * + * o The final 500 bytes (byte offsets 9500-9999, inclusive): + * + * bytes=-500 + * + * Or: + * + * bytes=9500- + * + * o The first and last bytes only (bytes 0 and 9999): + * + * bytes=0-0,-1 + * + * o Other valid (but not canonical) specifications of the second 500 + * bytes (byte offsets 500-999, inclusive): + * + * bytes=500-600,601-999 + * bytes=500-700,601-999 + */ + +/* + * returns 1 if the range struct represents a usable range + * if no ranges header, you get one of these for the whole + * file. Otherwise you get one for each valid range in the + * header. + * + * returns 0 if no further valid range forthcoming; rp->state + * may be LWSRS_SYNTAX or LWSRS_COMPLETED + */ + +int +lws_ranges_next(struct lws_range_parsing *rp) +{ + static const char * const beq = "bytes="; + char c; + + while (1) { + + c = rp->buf[rp->pos]; + + switch (rp->state) { + case LWSRS_SYNTAX: + case LWSRS_COMPLETED: + return 0; + + case LWSRS_NO_ACTIVE_RANGE: + rp->state = LWSRS_COMPLETED; + return 0; + + case LWSRS_BYTES_EQ: // looking for "bytes=" + if (c != beq[rp->pos]) { + rp->state = LWSRS_SYNTAX; + return -1; + } + if (rp->pos == 5) + rp->state = LWSRS_FIRST; + break; + + case LWSRS_FIRST: + rp->start = 0; + rp->end = 0; + rp->start_valid = 0; + rp->end_valid = 0; + + rp->state = LWSRS_STARTING; + + // fallthru + + case LWSRS_STARTING: + if (c == '-') { + rp->state = LWSRS_ENDING; + break; + } + + if (!(c >= '0' && c <= '9')) { + rp->state = LWSRS_SYNTAX; + return 0; + } + rp->start = (rp->start * 10) + (c - '0'); + rp->start_valid = 1; + break; + + case LWSRS_ENDING: + if (c == ',' || c == '\0') { + rp->state = LWSRS_FIRST; + if (c == ',') + rp->pos++; + + /* + * By the end of this, start and end are + * always valid if the range still is + */ + + if (!rp->start_valid) { /* eg, -500 */ + if (rp->end > rp->extent) + rp->end = rp->extent; + + rp->start = rp->extent - rp->end; + rp->end = rp->extent - 1; + } else + if (!rp->end_valid) + rp->end = rp->extent - 1; + + rp->did_try = 1; + + /* end must be >= start or ignore it */ + if (rp->end < rp->start) { + if (c == ',') + break; + rp->state = LWSRS_COMPLETED; + return 0; + } + + return 1; /* issue range */ + } + + if (!(c >= '0' && c <= '9')) { + rp->state = LWSRS_SYNTAX; + return 0; + } + rp->end = (rp->end * 10) + (c - '0'); + rp->end_valid = 1; + break; + } + + rp->pos++; + } +} + +void +lws_ranges_reset(struct lws_range_parsing *rp) +{ + rp->pos = 0; + rp->ctr = 0; + rp->start = 0; + rp->end = 0; + rp->start_valid = 0; + rp->end_valid = 0; + rp->state = LWSRS_BYTES_EQ; +} + +/* + * returns count of valid ranges + */ +int +lws_ranges_init(struct lws *wsi, struct lws_range_parsing *rp, + unsigned long long extent) +{ + rp->agg = 0; + rp->send_ctr = 0; + rp->inside = 0; + rp->count_ranges = 0; + rp->did_try = 0; + lws_ranges_reset(rp); + rp->state = LWSRS_COMPLETED; + + rp->extent = extent; + + if (lws_hdr_copy(wsi, (char *)rp->buf, sizeof(rp->buf), + WSI_TOKEN_HTTP_RANGE) <= 0) + return 0; + + rp->state = LWSRS_BYTES_EQ; + + while (lws_ranges_next(rp)) { + rp->count_ranges++; + rp->agg += rp->end - rp->start + 1; + } + + lwsl_debug("%s: count %d\n", __func__, rp->count_ranges); + lws_ranges_reset(rp); + + if (rp->did_try && !rp->count_ranges) + return -1; /* "not satisfiable */ + + lws_ranges_next(rp); + + return rp->count_ranges; +} diff --git a/thirdparty/lws/server/server-handshake.c b/thirdparty/lws/server/server-handshake.c new file mode 100644 index 0000000000..3d319c35d6 --- /dev/null +++ b/thirdparty/lws/server/server-handshake.c @@ -0,0 +1,360 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2013 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +#define LWS_CPYAPP(ptr, str) { strcpy(ptr, str); ptr += strlen(str); } + +#ifndef LWS_NO_EXTENSIONS +static int +lws_extension_server_handshake(struct lws *wsi, char **p, int budget) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + char ext_name[64], *args, *end = (*p) + budget - 1; + const struct lws_ext_options *opts, *po; + const struct lws_extension *ext; + struct lws_ext_option_arg oa; + int n, m, more = 1; + int ext_count = 0; + char ignore; + char *c; + + /* + * Figure out which extensions the client has that we want to + * enable on this connection, and give him back the list + */ + if (!lws_hdr_total_length(wsi, WSI_TOKEN_EXTENSIONS)) + return 0; + + /* + * break down the list of client extensions + * and go through them + */ + + if (lws_hdr_copy(wsi, (char *)pt->serv_buf, context->pt_serv_buf_size, + WSI_TOKEN_EXTENSIONS) < 0) + return 1; + + c = (char *)pt->serv_buf; + lwsl_parser("WSI_TOKEN_EXTENSIONS = '%s'\n", c); + wsi->count_act_ext = 0; + ignore = 0; + n = 0; + args = NULL; + + /* + * We may get a simple request + * + * Sec-WebSocket-Extensions: permessage-deflate + * + * or an elaborated one with requested options + * + * Sec-WebSocket-Extensions: permessage-deflate; \ + * server_no_context_takeover; \ + * client_no_context_takeover + */ + + while (more) { + + if (*c && (*c != ',' && *c != '\t')) { + if (*c == ';') { + ignore = 1; + args = c + 1; + } + if (ignore || *c == ' ') { + c++; + continue; + } + ext_name[n] = *c++; + if (n < sizeof(ext_name) - 1) + n++; + continue; + } + ext_name[n] = '\0'; + + ignore = 0; + if (!*c) + more = 0; + else { + c++; + if (!n) + continue; + } + + while (args && *args && *args == ' ') + args++; + + /* check a client's extension against our support */ + + ext = wsi->vhost->extensions; + + while (ext && ext->callback) { + + if (strcmp(ext_name, ext->name)) { + ext++; + continue; + } + + /* + * oh, we do support this one he asked for... but let's + * confirm he only gave it once + */ + for (m = 0; m < wsi->count_act_ext; m++) + if (wsi->active_extensions[m] == ext) { + lwsl_info("extension mentioned twice\n"); + return 1; /* shenanigans */ + } + + /* + * ask user code if it's OK to apply it on this + * particular connection + protocol + */ + m = (wsi->protocol->callback)(wsi, + LWS_CALLBACK_CONFIRM_EXTENSION_OKAY, + wsi->user_space, ext_name, 0); + + /* + * zero return from callback means go ahead and allow + * the extension, it's what we get if the callback is + * unhandled + */ + if (m) { + ext++; + continue; + } + + /* apply it */ + + ext_count++; + + /* instantiate the extension on this conn */ + + wsi->active_extensions[wsi->count_act_ext] = ext; + + /* allow him to construct his context */ + + if (ext->callback(lws_get_context(wsi), ext, wsi, + LWS_EXT_CB_CONSTRUCT, + (void *)&wsi->act_ext_user[ + wsi->count_act_ext], + (void *)&opts, 0)) { + lwsl_info("ext %s failed construction\n", + ext_name); + ext_count--; + ext++; + + continue; + } + + if (ext_count > 1) + *(*p)++ = ','; + else + LWS_CPYAPP(*p, + "\x0d\x0aSec-WebSocket-Extensions: "); + *p += lws_snprintf(*p, (end - *p), "%s", ext_name); + + /* + * go through the options trying to apply the + * recognized ones + */ + + lwsl_debug("ext args %s", args); + + while (args && *args && *args != ',') { + while (*args == ' ') + args++; + po = opts; + while (po->name) { + lwsl_debug("'%s' '%s'\n", po->name, args); + /* only support arg-less options... */ + if (po->type == EXTARG_NONE && + !strncmp(args, po->name, + strlen(po->name))) { + oa.option_name = NULL; + oa.option_index = po - opts; + oa.start = NULL; + lwsl_debug("setting %s\n", po->name); + if (!ext->callback( + lws_get_context(wsi), ext, wsi, + LWS_EXT_CB_OPTION_SET, + wsi->act_ext_user[ + wsi->count_act_ext], + &oa, (end - *p))) { + + *p += lws_snprintf(*p, (end - *p), "; %s", po->name); + lwsl_debug("adding option %s\n", po->name); + } + } + po++; + } + while (*args && *args != ',' && *args != ';') + args++; + } + + wsi->count_act_ext++; + lwsl_parser("count_act_ext <- %d\n", + wsi->count_act_ext); + + ext++; + } + + n = 0; + args = NULL; + } + + return 0; +} +#endif +int +handshake_0405(struct lws_context *context, struct lws *wsi) +{ + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct lws_process_html_args args; + unsigned char hash[20]; + int n, accept_len; + char *response; + char *p; + + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST) || + !lws_hdr_total_length(wsi, WSI_TOKEN_KEY)) { + lwsl_parser("handshake_04 missing pieces\n"); + /* completed header processing, but missing some bits */ + goto bail; + } + + if (lws_hdr_total_length(wsi, WSI_TOKEN_KEY) >= MAX_WEBSOCKET_04_KEY_LEN) { + lwsl_warn("Client key too long %d\n", MAX_WEBSOCKET_04_KEY_LEN); + goto bail; + } + + /* + * since key length is restricted above (currently 128), cannot + * overflow + */ + n = sprintf((char *)pt->serv_buf, + "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_KEY)); + + lws_SHA1(pt->serv_buf, n, hash); + + accept_len = lws_b64_encode_string((char *)hash, 20, + (char *)pt->serv_buf, context->pt_serv_buf_size); + if (accept_len < 0) { + lwsl_warn("Base64 encoded hash too long\n"); + goto bail; + } + + /* allocate the per-connection user memory (if any) */ + if (lws_ensure_user_space(wsi)) + goto bail; + + /* create the response packet */ + + /* make a buffer big enough for everything */ + + response = (char *)pt->serv_buf + MAX_WEBSOCKET_04_KEY_LEN + LWS_PRE; + p = response; + LWS_CPYAPP(p, "HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Upgrade: WebSocket\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Sec-WebSocket-Accept: "); + strcpy(p, (char *)pt->serv_buf); + p += accept_len; + + /* we can only return the protocol header if: + * - one came in, and ... */ + if (lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL) && + /* - it is not an empty string */ + wsi->protocol->name && + wsi->protocol->name[0]) { + LWS_CPYAPP(p, "\x0d\x0aSec-WebSocket-Protocol: "); + p += lws_snprintf(p, 128, "%s", wsi->protocol->name); + } + +#ifndef LWS_NO_EXTENSIONS + /* + * Figure out which extensions the client has that we want to + * enable on this connection, and give him back the list. + * + * Give him a limited write bugdet + */ + if (lws_extension_server_handshake(wsi, &p, 192)) + goto bail; +#endif + LWS_CPYAPP(p, "\x0d\x0a"); + + args.p = p; + args.max_len = ((char *)pt->serv_buf + context->pt_serv_buf_size) - p; + if (user_callback_handle_rxflow(wsi->protocol->callback, wsi, + LWS_CALLBACK_ADD_HEADERS, + wsi->user_space, &args, 0)) + goto bail; + + p = args.p; + + /* end of response packet */ + + LWS_CPYAPP(p, "\x0d\x0a"); + + if (!lws_any_extension_handled(wsi, LWS_EXT_CB_HANDSHAKE_REPLY_TX, + response, p - response)) { + + /* okay send the handshake response accepting the connection */ + + lwsl_parser("issuing resp pkt %d len\n", (int)(p - response)); +#if defined(DEBUG) && ! defined(LWS_WITH_ESP8266) + fwrite(response, 1, p - response, stderr); +#endif + n = lws_write(wsi, (unsigned char *)response, + p - response, LWS_WRITE_HTTP_HEADERS); + if (n != (p - response)) { + lwsl_debug("handshake_0405: ERROR writing to socket\n"); + goto bail; + } + + } + + /* alright clean up and set ourselves into established state */ + + wsi->state = LWSS_ESTABLISHED; + wsi->lws_rx_parse_state = LWS_RXPS_NEW; + + { + const char * uri_ptr = + lws_hdr_simple_ptr(wsi, WSI_TOKEN_GET_URI); + int uri_len = lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI); + const struct lws_http_mount *hit = + lws_find_mount(wsi, uri_ptr, uri_len); + if (hit && hit->cgienv && + wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP_PMO, + wsi->user_space, (void *)hit->cgienv, 0)) + return 1; + } + + return 0; + + +bail: + /* caller will free up his parsing allocations */ + return -1; +} + diff --git a/thirdparty/lws/server/server.c b/thirdparty/lws/server/server.c new file mode 100644 index 0000000000..db05954257 --- /dev/null +++ b/thirdparty/lws/server/server.c @@ -0,0 +1,3025 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +const char * const method_names[] = { + "GET", "POST", "OPTIONS", "PUT", "PATCH", "DELETE", "CONNECT", "HEAD", +#ifdef LWS_WITH_HTTP2 + ":path", +#endif + }; + +#if defined (LWS_WITH_ESP8266) +#undef memcpy +void *memcpy(void *dest, const void *src, size_t n) +{ + return ets_memcpy(dest, src, n); +} +#endif + +int +lws_context_init_server(struct lws_context_creation_info *info, + struct lws_vhost *vhost) +{ +#if LWS_POSIX + int n, opt = 1, limit = 1; +#endif + lws_sockfd_type sockfd; + struct lws_vhost *vh; + struct lws *wsi; + int m = 0; + + (void)method_names; + (void)opt; + /* set up our external listening socket we serve on */ + + if (info->port == CONTEXT_PORT_NO_LISTEN || + info->port == CONTEXT_PORT_NO_LISTEN_SERVER) + return 0; + + vh = vhost->context->vhost_list; + while (vh) { + if (vh->listen_port == info->port) { + if ((!info->iface && !vh->iface) || + (info->iface && vh->iface && + !strcmp(info->iface, vh->iface))) { + vhost->listen_port = info->port; + vhost->iface = info->iface; + lwsl_notice(" using listen skt from vhost %s\n", + vh->name); + return 0; + } + } + vh = vh->vhost_next; + } + +#if LWS_POSIX + (void)n; +#if defined(__linux__) + limit = vhost->context->count_threads; +#endif + + for (m = 0; m < limit; m++) { +#ifdef LWS_WITH_UNIX_SOCK + if (LWS_UNIX_SOCK_ENABLED(vhost)) + sockfd = socket(AF_UNIX, SOCK_STREAM, 0); + else +#endif +#ifdef LWS_WITH_IPV6 + if (LWS_IPV6_ENABLED(vhost)) + sockfd = socket(AF_INET6, SOCK_STREAM, 0); + else +#endif + sockfd = socket(AF_INET, SOCK_STREAM, 0); + + if (sockfd == -1) { +#else +#if defined(LWS_WITH_ESP8266) + sockfd = esp8266_create_tcp_listen_socket(vhost); + if (!lws_sockfd_valid(sockfd)) { +#endif +#endif + lwsl_err("ERROR opening socket\n"); + return 1; + } +#if LWS_POSIX && !defined(LWS_WITH_ESP32) + +#if (defined(WIN32) || defined(_WIN32)) && defined(SO_EXCLUSIVEADDRUSE) + /* + * only accept that we are the only listener on the port + * https://msdn.microsoft.com/zh-tw/library/ + * windows/desktop/ms740621(v=vs.85).aspx + * + * for lws, to match Linux, we default to exclusive listen + */ + if (!lws_check_opt(vhost->options, + LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE)) { + if (setsockopt(sockfd, SOL_SOCKET, SO_EXCLUSIVEADDRUSE, + (const void *)&opt, sizeof(opt)) < 0) { + lwsl_err("reuseaddr failed\n"); + compatible_close(sockfd); + return 1; + } + } else +#endif + + /* + * allow us to restart even if old sockets in TIME_WAIT + */ + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, + (const void *)&opt, sizeof(opt)) < 0) { + lwsl_err("reuseaddr failed\n"); + compatible_close(sockfd); + return 1; + } + +#if defined(LWS_WITH_IPV6) && defined(IPV6_V6ONLY) + if (LWS_IPV6_ENABLED(vhost)) { + if (vhost->options & LWS_SERVER_OPTION_IPV6_V6ONLY_MODIFY) { + int value = (vhost->options & + LWS_SERVER_OPTION_IPV6_V6ONLY_VALUE) ? 1 : 0; + if (setsockopt(sockfd, IPPROTO_IPV6, IPV6_V6ONLY, + (const void*)&value, sizeof(value)) < 0) { + compatible_close(sockfd); + return 1; + } + } + } +#endif + +#if defined(__linux__) && defined(SO_REUSEPORT) + n = lws_check_opt(vhost->options, LWS_SERVER_OPTION_ALLOW_LISTEN_SHARE); +#if LWS_MAX_SMP > 1 + n = 1; +#endif + + if (n) + if (vhost->context->count_threads > 1) + if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT, + (const void *)&opt, sizeof(opt)) < 0) { + compatible_close(sockfd); + return 1; + } +#endif +#endif + lws_plat_set_socket_options(vhost, sockfd); + +#if LWS_POSIX + n = lws_socket_bind(vhost, sockfd, info->port, info->iface); + if (n < 0) + goto bail; + info->port = n; +#endif + vhost->listen_port = info->port; + vhost->iface = info->iface; + + wsi = lws_zalloc(sizeof(struct lws), "listen wsi"); + if (wsi == NULL) { + lwsl_err("Out of mem\n"); + goto bail; + } + wsi->context = vhost->context; + wsi->desc.sockfd = sockfd; + wsi->mode = LWSCM_SERVER_LISTENER; + wsi->protocol = vhost->protocols; + wsi->tsi = m; + wsi->vhost = vhost; + wsi->listener = 1; + +#ifdef LWS_WITH_LIBUV + if (LWS_LIBUV_ENABLED(vhost->context)) + lws_uv_initvhost(vhost, wsi); +#endif + + if (insert_wsi_socket_into_fds(vhost->context, wsi)) + goto bail; + + vhost->context->count_wsi_allocated++; + vhost->lserv_wsi = wsi; + +#if LWS_POSIX + n = listen(wsi->desc.sockfd, LWS_SOMAXCONN); + if (n < 0) { + lwsl_err("listen failed with error %d\n", LWS_ERRNO); + vhost->lserv_wsi = NULL; + vhost->context->count_wsi_allocated--; + remove_wsi_socket_from_fds(wsi); + goto bail; + } + } /* for each thread able to independently listen */ +#else +#if defined(LWS_WITH_ESP8266) + esp8266_tcp_stream_bind(wsi->desc.sockfd, info->port, wsi); +#endif +#endif + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_EXPLICIT_VHOSTS)) { +#ifdef LWS_WITH_UNIX_SOCK + if (LWS_UNIX_SOCK_ENABLED(vhost)) + lwsl_info(" Listening on \"%s\"\n", info->iface); + else +#endif + lwsl_info(" Listening on port %d\n", info->port); + } + + return 0; + +bail: + compatible_close(sockfd); + + return 1; +} + +#if defined(LWS_WITH_ESP8266) +#undef strchr +#define strchr ets_strchr +#endif + +struct lws_vhost * +lws_select_vhost(struct lws_context *context, int port, const char *servername) +{ + struct lws_vhost *vhost = context->vhost_list; + const char *p; + int n, m, colon; + + n = strlen(servername); + colon = n; + p = strchr(servername, ':'); + if (p) + colon = p - servername; + + /* Priotity 1: first try exact matches */ + + while (vhost) { + if (port == vhost->listen_port && + !strncmp(vhost->name, servername, colon)) { + lwsl_info("SNI: Found: %s\n", servername); + return vhost; + } + vhost = vhost->vhost_next; + } + + /* + * Priority 2: if no exact matches, try matching *.vhost-name + * unintentional matches are possible but resolve to x.com for *.x.com + * which is reasonable. If exact match exists we already chose it and + * never reach here. SSL will still fail it if the cert doesn't allow + * *.x.com. + */ + vhost = context->vhost_list; + while (vhost) { + m = strlen(vhost->name); + if (port == vhost->listen_port && + m <= (colon - 2) && + servername[colon - m - 1] == '.' && + !strncmp(vhost->name, servername + colon - m, m)) { + lwsl_info("SNI: Found %s on wildcard: %s\n", + servername, vhost->name); + return vhost; + } + vhost = vhost->vhost_next; + } + + /* Priority 3: match the first vhost on our port */ + + vhost = context->vhost_list; + while (vhost) { + if (port == vhost->listen_port) { + lwsl_info("vhost match to %s based on port %d\n", + vhost->name, port); + return vhost; + } + vhost = vhost->vhost_next; + } + + /* no match */ + + return NULL; +} + +LWS_VISIBLE LWS_EXTERN const char * +lws_get_mimetype(const char *file, const struct lws_http_mount *m) +{ + int n = strlen(file); + const struct lws_protocol_vhost_options *pvo = NULL; + + if (m) + pvo = m->extra_mimetypes; + + if (n < 5) + return NULL; + + if (!strcmp(&file[n - 4], ".ico")) + return "image/x-icon"; + + if (!strcmp(&file[n - 4], ".gif")) + return "image/gif"; + + if (!strcmp(&file[n - 3], ".js")) + return "text/javascript"; + + if (!strcmp(&file[n - 4], ".png")) + return "image/png"; + + if (!strcmp(&file[n - 4], ".jpg")) + return "image/jpeg"; + + if (!strcmp(&file[n - 3], ".gz")) + return "application/gzip"; + + if (!strcmp(&file[n - 4], ".JPG")) + return "image/jpeg"; + + if (!strcmp(&file[n - 5], ".html")) + return "text/html"; + + if (!strcmp(&file[n - 4], ".css")) + return "text/css"; + + if (!strcmp(&file[n - 4], ".txt")) + return "text/plain"; + + if (!strcmp(&file[n - 4], ".svg")) + return "image/svg+xml"; + + if (!strcmp(&file[n - 4], ".ttf")) + return "application/x-font-ttf"; + + if (!strcmp(&file[n - 4], ".otf")) + return "application/font-woff"; + + if (!strcmp(&file[n - 5], ".woff")) + return "application/font-woff"; + + if (!strcmp(&file[n - 4], ".xml")) + return "application/xml"; + + while (pvo) { + if (pvo->name[0] == '*') /* ie, match anything */ + return pvo->value; + + if (!strcmp(&file[n - strlen(pvo->name)], pvo->name)) + return pvo->value; + + pvo = pvo->next; + } + + return NULL; +} +static lws_fop_flags_t +lws_vfs_prepare_flags(struct lws *wsi) +{ + lws_fop_flags_t f = 0; + + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING)) + return f; + + if (strstr(lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_ACCEPT_ENCODING), + "gzip")) { + lwsl_info("client indicates GZIP is acceptable\n"); + f |= LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP; + } + + return f; +} + +static int +lws_http_serve(struct lws *wsi, char *uri, const char *origin, + const struct lws_http_mount *m) +{ + const struct lws_protocol_vhost_options *pvo = m->interpret; + struct lws_process_html_args args; + const char *mimetype; +#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP8266) + const struct lws_plat_file_ops *fops; + const char *vpath; + lws_fop_flags_t fflags = LWS_O_RDONLY; +#if defined(WIN32) && defined(LWS_HAVE__STAT32I64) + struct _stat32i64 st; +#else + struct stat st; +#endif + int spin = 0; +#endif + char path[256], sym[512]; + unsigned char *p = (unsigned char *)sym + 32 + LWS_PRE, *start = p; + unsigned char *end = p + sizeof(sym) - 32 - LWS_PRE; +#if !defined(WIN32) && LWS_POSIX && !defined(LWS_WITH_ESP32) + size_t len; +#endif + int n; + + lws_snprintf(path, sizeof(path) - 1, "%s/%s", origin, uri); + +#if !defined(_WIN32_WCE) && !defined(LWS_WITH_ESP8266) + + fflags |= lws_vfs_prepare_flags(wsi); + + do { + spin++; + fops = lws_vfs_select_fops(wsi->context->fops, path, &vpath); + + if (wsi->u.http.fop_fd) + lws_vfs_file_close(&wsi->u.http.fop_fd); + + wsi->u.http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops, + path, vpath, &fflags); + if (!wsi->u.http.fop_fd) { + lwsl_err("Unable to open '%s'\n", path); + + return -1; + } + + /* if it can't be statted, don't try */ + if (fflags & LWS_FOP_FLAG_VIRTUAL) + break; +#if defined(LWS_WITH_ESP32) + break; +#endif +#if !defined(WIN32) + if (fstat(wsi->u.http.fop_fd->fd, &st)) { + lwsl_info("unable to stat %s\n", path); + goto bail; + } +#else +#if defined(LWS_HAVE__STAT32I64) + if (_stat32i64(path, &st)) { + lwsl_info("unable to stat %s\n", path); + goto bail; + } +#else + if (stat(path, &st)) { + lwsl_info("unable to stat %s\n", path); + goto bail; + } +#endif +#endif + + wsi->u.http.fop_fd->mod_time = (uint32_t)st.st_mtime; + fflags |= LWS_FOP_FLAG_MOD_TIME_VALID; + +#if !defined(WIN32) && LWS_POSIX && !defined(LWS_WITH_ESP32) + if ((S_IFMT & st.st_mode) == S_IFLNK) { + len = readlink(path, sym, sizeof(sym) - 1); + if (len) { + lwsl_err("Failed to read link %s\n", path); + goto bail; + } + sym[len] = '\0'; + lwsl_debug("symlink %s -> %s\n", path, sym); + lws_snprintf(path, sizeof(path) - 1, "%s", sym); + } +#endif + if ((S_IFMT & st.st_mode) == S_IFDIR) { + lwsl_debug("default filename append to dir\n"); + lws_snprintf(path, sizeof(path) - 1, "%s/%s/index.html", + origin, uri); + } + + } while ((S_IFMT & st.st_mode) != S_IFREG && spin < 5); + + if (spin == 5) + lwsl_err("symlink loop %s \n", path); + + n = sprintf(sym, "%08llX%08lX", + (unsigned long long)lws_vfs_get_length(wsi->u.http.fop_fd), + (unsigned long)lws_vfs_get_mod_time(wsi->u.http.fop_fd)); + + /* disable ranges if IF_RANGE token invalid */ + + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_IF_RANGE)) + if (strcmp(sym, lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP_IF_RANGE))) + /* differs - defeat Range: */ + wsi->u.http.ah->frag_index[WSI_TOKEN_HTTP_RANGE] = 0; + + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_IF_NONE_MATCH)) { + /* + * he thinks he has some version of it already, + * check if the tag matches + */ + if (!strcmp(sym, lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_IF_NONE_MATCH))) { + + lwsl_debug("%s: ETAG match %s %s\n", __func__, + uri, origin); + + /* we don't need to send the payload */ + if (lws_add_http_header_status(wsi, + HTTP_STATUS_NOT_MODIFIED, &p, end)) + return -1; + + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_ETAG, + (unsigned char *)sym, n, &p, end)) + return -1; + + if (lws_finalize_http_header(wsi, &p, end)) + return -1; + + n = lws_write(wsi, start, p - start, + LWS_WRITE_HTTP_HEADERS | + LWS_WRITE_H2_STREAM_END); + if (n != (p - start)) { + lwsl_err("_write returned %d from %ld\n", n, + (long)(p - start)); + return -1; + } + + lws_vfs_file_close(&wsi->u.http.fop_fd); + + return lws_http_transaction_completed(wsi); + } + } + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ETAG, + (unsigned char *)sym, n, &p, end)) + return -1; +#endif + + mimetype = lws_get_mimetype(path, m); + if (!mimetype) { + lwsl_err("unknown mimetype for %s\n", path); + goto bail; + } + if (!mimetype[0]) + lwsl_debug("sending no mimetype for %s\n", path); + + wsi->sending_chunked = 0; + + /* + * check if this is in the list of file suffixes to be interpreted by + * a protocol + */ + while (pvo) { + n = strlen(path); + if (n > (int)strlen(pvo->name) && + !strcmp(&path[n - strlen(pvo->name)], pvo->name)) { + wsi->sending_chunked = 1; + wsi->protocol_interpret_idx = + (char)(lws_intptr_t)pvo->value; + lwsl_info("want %s interpreted by %s\n", path, + wsi->vhost->protocols[ + (int)(lws_intptr_t)(pvo->value)].name); + wsi->protocol = &wsi->vhost->protocols[ + (int)(lws_intptr_t)(pvo->value)]; + if (lws_ensure_user_space(wsi)) + return -1; + break; + } + pvo = pvo->next; + } + + if (m->protocol) { + const struct lws_protocols *pp = lws_vhost_name_to_protocol( + wsi->vhost, m->protocol); + + if (lws_bind_protocol(wsi, pp)) + return 1; + args.p = (char *)p; + args.max_len = end - p; + if (pp->callback(wsi, LWS_CALLBACK_ADD_HEADERS, + wsi->user_space, &args, 0)) + return -1; + p = (unsigned char *)args.p; + } + + n = lws_serve_http_file(wsi, path, mimetype, (char *)start, p - start); + + if (n < 0 || ((n > 0) && lws_http_transaction_completed(wsi))) + return -1; /* error or can't reuse connection: close the socket */ + + return 0; +bail: + + return -1; +} + +const struct lws_http_mount * +lws_find_mount(struct lws *wsi, const char *uri_ptr, int uri_len) +{ + const struct lws_http_mount *hm, *hit = NULL; + int best = 0; + + hm = wsi->vhost->mount_list; + while (hm) { + if (uri_len >= hm->mountpoint_len && + !strncmp(uri_ptr, hm->mountpoint, hm->mountpoint_len) && + (uri_ptr[hm->mountpoint_len] == '\0' || + uri_ptr[hm->mountpoint_len] == '/' || + hm->mountpoint_len == 1) + ) { + if (hm->origin_protocol == LWSMPRO_CALLBACK || + ((hm->origin_protocol == LWSMPRO_CGI || + lws_hdr_total_length(wsi, WSI_TOKEN_GET_URI) || + (wsi->http2_substream && + lws_hdr_total_length(wsi, + WSI_TOKEN_HTTP_COLON_PATH)) || + hm->protocol) && + hm->mountpoint_len > best)) { + best = hm->mountpoint_len; + hit = hm; + } + } + hm = hm->mount_next; + } + + return hit; +} + +#if LWS_POSIX + +static int +lws_find_string_in_file(const char *filename, const char *string, int stringlen) +{ + char buf[128]; + int fd, match = 0, pos = 0, n = 0, hit = 0; + + fd = open(filename, O_RDONLY); + if (fd < 0) { + lwsl_err("can't open auth file: %s\n", filename); + return 1; + } + + while (1) { + if (pos == n) { + n = read(fd, buf, sizeof(buf)); + if (n <= 0) { + if (match == stringlen) + hit = 1; + break; + } + pos = 0; + } + + if (match == stringlen) { + if (buf[pos] == '\r' || buf[pos] == '\n') { + hit = 1; + break; + } + match = 0; + } + + if (buf[pos] == string[match]) + match++; + else + match = 0; + + pos++; + } + + close(fd); + + return hit; +} + +static int +lws_unauthorised_basic_auth(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + unsigned char *start = pt->serv_buf + LWS_PRE, + *p = start, *end = p + 512; + char buf[64]; + int n; + + /* no auth... tell him it is required */ + + if (lws_add_http_header_status(wsi, HTTP_STATUS_UNAUTHORIZED, &p, end)) + return -1; + + n = lws_snprintf(buf, sizeof(buf), "Basic realm=\"lwsws\""); + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_WWW_AUTHENTICATE, + (unsigned char *)buf, n, &p, end)) + return -1; + + if (lws_finalize_http_header(wsi, &p, end)) + return -1; + + n = lws_write(wsi, start, p - start, LWS_WRITE_HTTP_HEADERS | + LWS_WRITE_H2_STREAM_END); + if (n < 0) + return -1; + + return lws_http_transaction_completed(wsi); + +} + +#endif + +int lws_clean_url(char *p) +{ + if (p[0] == 'h' && p[1] == 't' && p[2] == 't' && p[3] == 'p') { + p += 4; + if (*p == 's') + p++; + if (*p == ':') { + p++; + if (*p == '/') + p++; + } + } + + while (*p) { + if (p[0] == '/' && p[1] == '/') { + char *p1 = p; + while (*p1) { + *p1 = p1[1]; + p1++; + } + continue; + } + p++; + } + + return 0; +} + + +static const unsigned char methods[] = { + WSI_TOKEN_GET_URI, + WSI_TOKEN_POST_URI, + WSI_TOKEN_OPTIONS_URI, + WSI_TOKEN_PUT_URI, + WSI_TOKEN_PATCH_URI, + WSI_TOKEN_DELETE_URI, + WSI_TOKEN_CONNECT, + WSI_TOKEN_HEAD_URI, +#ifdef LWS_WITH_HTTP2 + WSI_TOKEN_HTTP_COLON_PATH, +#endif +}; + +static int +lws_http_get_uri_and_method(struct lws *wsi, char **puri_ptr, int *puri_len) +{ + int n, count = 0; + + for (n = 0; n < ARRAY_SIZE(methods); n++) + if (lws_hdr_total_length(wsi, methods[n])) + count++; + if (!count) { + lwsl_warn("Missing URI in HTTP request\n"); + return -1; + } + + if (count != 1 && + !(wsi->http2_substream && + lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_COLON_PATH))) { + lwsl_warn("multiple methods?\n"); + return -1; + } + + for (n = 0; n < ARRAY_SIZE(methods); n++) + if (lws_hdr_total_length(wsi, methods[n])) { + *puri_ptr = lws_hdr_simple_ptr(wsi, methods[n]); + *puri_len = lws_hdr_total_length(wsi, methods[n]); + return n; + } + + return -1; +} + +int +lws_http_action(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + enum http_connection_type connection_type; + enum http_version request_version; + char content_length_str[32]; + struct lws_process_html_args args; + const struct lws_http_mount *hit = NULL; + unsigned int n; + char http_version_str[10]; + char http_conn_str[20]; + int http_version_len; + char *uri_ptr = NULL, *s; + int uri_len = 0, meth; + static const char * const oprot[] = { + "http://", "https://" + }; + + meth = lws_http_get_uri_and_method(wsi, &uri_ptr, &uri_len); + if (meth < 0 || meth >= ARRAY_SIZE(method_names)) + goto bail_nuke_ah; + + /* we insist on absolute paths */ + + if (!uri_ptr || uri_ptr[0] != '/') { + lws_return_http_status(wsi, HTTP_STATUS_FORBIDDEN, NULL); + + goto bail_nuke_ah; + } + + lwsl_info("Method: '%s' (%d), request for '%s'\n", method_names[meth], + meth, uri_ptr); + + if (lws_ensure_user_space(wsi)) + goto bail_nuke_ah; + + /* HTTP header had a content length? */ + + wsi->u.http.rx_content_length = 0; + if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI) || + lws_hdr_total_length(wsi, WSI_TOKEN_PATCH_URI) || + lws_hdr_total_length(wsi, WSI_TOKEN_PUT_URI)) + wsi->u.http.rx_content_length = 100 * 1024 * 1024; + + if (lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_CONTENT_LENGTH)) { + lws_hdr_copy(wsi, content_length_str, + sizeof(content_length_str) - 1, + WSI_TOKEN_HTTP_CONTENT_LENGTH); + wsi->u.http.rx_content_length = atoll(content_length_str); + } + + if (wsi->http2_substream) { + wsi->u.http.request_version = HTTP_VERSION_2; + } else { + /* http_version? Default to 1.0, override with token: */ + request_version = HTTP_VERSION_1_0; + + /* Works for single digit HTTP versions. : */ + http_version_len = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP); + if (http_version_len > 7) { + lws_hdr_copy(wsi, http_version_str, + sizeof(http_version_str) - 1, + WSI_TOKEN_HTTP); + if (http_version_str[5] == '1' && + http_version_str[7] == '1') + request_version = HTTP_VERSION_1_1; + } + wsi->u.http.request_version = request_version; + + /* HTTP/1.1 defaults to "keep-alive", 1.0 to "close" */ + if (request_version == HTTP_VERSION_1_1) + connection_type = HTTP_CONNECTION_KEEP_ALIVE; + else + connection_type = HTTP_CONNECTION_CLOSE; + + /* Override default if http "Connection:" header: */ + if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECTION)) { + lws_hdr_copy(wsi, http_conn_str, + sizeof(http_conn_str) - 1, + WSI_TOKEN_CONNECTION); + http_conn_str[sizeof(http_conn_str) - 1] = '\0'; + if (!strcasecmp(http_conn_str, "keep-alive")) + connection_type = HTTP_CONNECTION_KEEP_ALIVE; + else + if (!strcasecmp(http_conn_str, "close")) + connection_type = HTTP_CONNECTION_CLOSE; + } + wsi->u.http.connection_type = connection_type; + } + + n = wsi->protocol->callback(wsi, LWS_CALLBACK_FILTER_HTTP_CONNECTION, + wsi->user_space, uri_ptr, uri_len); + if (n) { + lwsl_info("LWS_CALLBACK_HTTP closing\n"); + + return 1; + } + /* + * if there is content supposed to be coming, + * put a timeout on it having arrived + */ + lws_set_timeout(wsi, PENDING_TIMEOUT_HTTP_CONTENT, + wsi->context->timeout_secs); +#ifdef LWS_OPENSSL_SUPPORT + if (wsi->redirect_to_https) { + /* + * we accepted http:// only so we could redirect to + * https://, so issue the redirect. Create the redirection + * URI from the host: header and ignore the path part + */ + unsigned char *start = pt->serv_buf + LWS_PRE, *p = start, + *end = p + 512; + + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) + goto bail_nuke_ah; + + n = sprintf((char *)end, "https://%s/", + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); + + n = lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY, + end, n, &p, end); + if ((int)n < 0) + goto bail_nuke_ah; + + return lws_http_transaction_completed(wsi); + } +#endif + +#ifdef LWS_WITH_ACCESS_LOG + lws_prepare_access_log_info(wsi, uri_ptr, meth); +#endif + + /* can we serve it from the mount list? */ + + hit = lws_find_mount(wsi, uri_ptr, uri_len); + if (!hit) { + /* deferred cleanup and reset to protocols[0] */ + + lwsl_info("no hit\n"); + + if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0])) + return 1; + + n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, + wsi->user_space, uri_ptr, uri_len); + + goto after; + } + + s = uri_ptr + hit->mountpoint_len; + + /* + * if we have a mountpoint like https://xxx.com/yyy + * there is an implied / at the end for our purposes since + * we can only mount on a "directory". + * + * But if we just go with that, the browser cannot understand + * that he is actually looking down one "directory level", so + * even though we give him /yyy/abc.html he acts like the + * current directory level is /. So relative urls like "x.png" + * wrongly look outside the mountpoint. + * + * Therefore if we didn't come in on a url with an explicit + * / at the end, we must redirect to add it so the browser + * understands he is one "directory level" down. + */ + if ((hit->mountpoint_len > 1 || + (hit->origin_protocol == LWSMPRO_REDIR_HTTP || + hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) && + (*s != '/' || + (hit->origin_protocol == LWSMPRO_REDIR_HTTP || + hit->origin_protocol == LWSMPRO_REDIR_HTTPS)) && + (hit->origin_protocol != LWSMPRO_CGI && + hit->origin_protocol != LWSMPRO_CALLBACK)) { + unsigned char *start = pt->serv_buf + LWS_PRE, + *p = start, *end = p + 512; + + lwsl_debug("Doing 301 '%s' org %s\n", s, hit->origin); + + /* > at start indicates deal with by redirect */ + if (hit->origin_protocol == LWSMPRO_REDIR_HTTP || + hit->origin_protocol == LWSMPRO_REDIR_HTTPS) + n = lws_snprintf((char *)end, 256, "%s%s", + oprot[hit->origin_protocol & 1], + hit->origin); + else { + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { + if (!lws_hdr_total_length(wsi, + WSI_TOKEN_HTTP_COLON_AUTHORITY)) + goto bail_nuke_ah; + n = lws_snprintf((char *)end, 256, + "%s%s%s/", oprot[!!lws_is_ssl(wsi)], + lws_hdr_simple_ptr(wsi, + WSI_TOKEN_HTTP_COLON_AUTHORITY), + uri_ptr); + } else + n = lws_snprintf((char *)end, 256, + "%s%s%s/", oprot[!!lws_is_ssl(wsi)], + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST), + uri_ptr); + } + + lws_clean_url((char *)end); + n = lws_http_redirect(wsi, HTTP_STATUS_MOVED_PERMANENTLY, + end, n, &p, end); + if ((int)n < 0) + goto bail_nuke_ah; + + return lws_http_transaction_completed(wsi); + } + +#if LWS_POSIX + /* basic auth? */ + + if (hit->basic_auth_login_file) { + char b64[160], plain[(sizeof(b64) * 3) / 4]; + int m; + + /* Did he send auth? */ + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_AUTHORIZATION)) + return lws_unauthorised_basic_auth(wsi); + + n = HTTP_STATUS_FORBIDDEN; + + m = lws_hdr_copy(wsi, b64, sizeof(b64), + WSI_TOKEN_HTTP_AUTHORIZATION); + if (m < 7) { + lwsl_err("b64 auth too long\n"); + goto transaction_result_n; + } + + b64[5] = '\0'; + if (strcasecmp(b64, "Basic")) { + lwsl_err("auth missing basic: %s\n", b64); + goto transaction_result_n; + } + + /* It'll be like Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l */ + + m = lws_b64_decode_string(b64 + 6, plain, sizeof(plain)); + if (m < 0) { + lwsl_err("plain auth too long\n"); + goto transaction_result_n; + } + + if (!lws_find_string_in_file(hit->basic_auth_login_file, + plain, m)) { + lwsl_err("basic auth lookup failed\n"); + return lws_unauthorised_basic_auth(wsi); + } + + lwsl_notice("basic auth accepted\n"); + + /* accept the auth */ + } +#endif + +#if defined(LWS_WITH_HTTP_PROXY) + /* + * The mount is a reverse proxy? + */ + + if (hit->origin_protocol == LWSMPRO_HTTPS || + hit->origin_protocol == LWSMPRO_HTTP) { + struct lws_client_connect_info i; + char ads[96], rpath[256], *pcolon, *pslash, *p; + int n, na; + + memset(&i, 0, sizeof(i)); + i.context = lws_get_context(wsi); + + pcolon = strchr(hit->origin, ':'); + pslash = strchr(hit->origin, '/'); + if (!pslash) { + lwsl_err("Proxy mount origin '%s' must have /\n", + hit->origin); + return -1; + } + if (pcolon > pslash) + pcolon = NULL; + + if (pcolon) + n = pcolon - hit->origin; + else + n = pslash - hit->origin; + + if (n >= sizeof(ads) - 2) + n = sizeof(ads) - 2; + + memcpy(ads, hit->origin, n); + ads[n] = '\0'; + + i.address = ads; + i.port = 80; + if (hit->origin_protocol == LWSMPRO_HTTPS) { + i.port = 443; + i.ssl_connection = 1; + } + if (pcolon) + i.port = atoi(pcolon + 1); + + lws_snprintf(rpath, sizeof(rpath) - 1, "/%s/%s", pslash + 1, + uri_ptr + hit->mountpoint_len); + lws_clean_url(rpath); + na = lws_hdr_total_length(wsi, WSI_TOKEN_HTTP_URI_ARGS); + if (na) { + p = rpath + strlen(rpath); + *p++ = '?'; + lws_hdr_copy(wsi, p, &rpath[sizeof(rpath) - 1] - p, + WSI_TOKEN_HTTP_URI_ARGS); + while (--na) { + if (*p == '\0') + *p = '&'; + p++; + } + } + + + i.path = rpath; + i.host = i.address; + i.origin = NULL; + i.method = "GET"; + i.parent_wsi = wsi; + i.uri_replace_from = hit->origin; + i.uri_replace_to = hit->mountpoint; + + lwsl_notice("proxying to %s port %d url %s, ssl %d, from %s, to %s\n", + i.address, i.port, i.path, i.ssl_connection, + i.uri_replace_from, i.uri_replace_to); + + if (!lws_client_connect_via_info(&i)) { + lwsl_err("proxy connect fail\n"); + return 1; + } + + return 0; + } +#endif + + /* + * A particular protocol callback is mounted here? + * + * For the duration of this http transaction, bind us to the + * associated protocol + */ + if (hit->origin_protocol == LWSMPRO_CALLBACK || hit->protocol) { + const struct lws_protocols *pp; + const char *name = hit->origin; + if (hit->protocol) + name = hit->protocol; + + pp = lws_vhost_name_to_protocol(wsi->vhost, name); + if (!pp) { + n = -1; + lwsl_err("Unable to find plugin '%s'\n", + hit->origin); + return 1; + } + + if (lws_bind_protocol(wsi, pp)) + return 1; + + args.p = uri_ptr; + args.len = uri_len; + args.max_len = hit->auth_mask; + args.final = 0; /* used to signal callback dealt with it */ + + n = wsi->protocol->callback(wsi, LWS_CALLBACK_CHECK_ACCESS_RIGHTS, + wsi->user_space, &args, 0); + if (n) { + lws_return_http_status(wsi, HTTP_STATUS_UNAUTHORIZED, + NULL); + goto bail_nuke_ah; + } + if (args.final) /* callback completely handled it well */ + return 0; + + if (hit->cgienv && wsi->protocol->callback(wsi, + LWS_CALLBACK_HTTP_PMO, + wsi->user_space, (void *)hit->cgienv, 0)) + return 1; + + if (lws_hdr_total_length(wsi, WSI_TOKEN_POST_URI)) { + n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, + wsi->user_space, + uri_ptr + hit->mountpoint_len, + uri_len - hit->mountpoint_len); + goto after; + } + } + +#ifdef LWS_WITH_CGI + /* did we hit something with a cgi:// origin? */ + if (hit->origin_protocol == LWSMPRO_CGI) { + const char *cmd[] = { + NULL, /* replace with cgi path */ + NULL + }; + + lwsl_debug("%s: cgi\n", __func__); + cmd[0] = hit->origin; + + n = 5; + if (hit->cgi_timeout) + n = hit->cgi_timeout; + + n = lws_cgi(wsi, cmd, hit->mountpoint_len, n, + hit->cgienv); + if (n) { + lwsl_err("%s: cgi failed\n", __func__); + return -1; + } + + goto deal_body; + } +#endif + + n = strlen(s); + if (s[0] == '\0' || (n == 1 && s[n - 1] == '/')) + s = (char *)hit->def; + if (!s) + s = "index.html"; + + wsi->cache_secs = hit->cache_max_age; + wsi->cache_reuse = hit->cache_reusable; + wsi->cache_revalidate = hit->cache_revalidate; + wsi->cache_intermediaries = hit->cache_intermediaries; + + n = lws_http_serve(wsi, s, hit->origin, hit); + if (n) { + /* + * lws_return_http_status(wsi, HTTP_STATUS_NOT_FOUND, NULL); + */ + if (hit->protocol) { + const struct lws_protocols *pp = lws_vhost_name_to_protocol( + wsi->vhost, hit->protocol); + + if (lws_bind_protocol(wsi, pp)) + return 1; + + n = pp->callback(wsi, LWS_CALLBACK_HTTP, + wsi->user_space, + uri_ptr + hit->mountpoint_len, + uri_len - hit->mountpoint_len); + } else + n = wsi->protocol->callback(wsi, LWS_CALLBACK_HTTP, + wsi->user_space, uri_ptr, uri_len); + } + +after: + if (n) { + lwsl_info("LWS_CALLBACK_HTTP closing\n"); + + return 1; + } + +#ifdef LWS_WITH_CGI +deal_body: +#endif + /* + * If we're not issuing a file, check for content_length or + * HTTP keep-alive. No keep-alive header allocation for + * ISSUING_FILE, as this uses HTTP/1.0. + * + * In any case, return 0 and let lws_read decide how to + * proceed based on state + */ + if (wsi->state != LWSS_HTTP_ISSUING_FILE) { + /* Prepare to read body if we have a content length: */ + lwsl_debug("wsi->u.http.rx_content_length %lld %d %d\n", + (long long)wsi->u.http.rx_content_length, + wsi->upgraded_to_http2, wsi->http2_substream); + if (wsi->u.http.rx_content_length > 0) { + lwsl_notice("%s: %p: LWSS_HTTP_BODY state set\n", + __func__, wsi); + wsi->state = LWSS_HTTP_BODY; + wsi->u.http.rx_content_remain = + wsi->u.http.rx_content_length; + } + } + + return 0; + +bail_nuke_ah: + /* we're closing, losing some rx is OK */ + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 1); + + return 1; + +#if LWS_POSIX +transaction_result_n: + lws_return_http_status(wsi, n, NULL); + + return lws_http_transaction_completed(wsi); +#endif +} + +static int +lws_server_init_wsi_for_ws(struct lws *wsi) +{ + int n; + + wsi->state = LWSS_ESTABLISHED; + lws_restart_ws_ping_pong_timer(wsi); + + /* + * create the frame buffer for this connection according to the + * size mentioned in the protocol definition. If 0 there, use + * a big default for compatibility + */ + + n = wsi->protocol->rx_buffer_size; + if (!n) + n = wsi->context->pt_serv_buf_size; + n += LWS_PRE; + wsi->u.ws.rx_ubuf = lws_malloc(n + 4 /* 0x0000ffff zlib */, "rx_ubuf"); + if (!wsi->u.ws.rx_ubuf) { + lwsl_err("Out of Mem allocating rx buffer %d\n", n); + return 1; + } + wsi->u.ws.rx_ubuf_alloc = n; + lwsl_debug("Allocating RX buffer %d\n", n); + +#if LWS_POSIX && !defined(LWS_WITH_ESP32) + if (!wsi->parent_carries_io) + if (setsockopt(wsi->desc.sockfd, SOL_SOCKET, SO_SNDBUF, + (const char *)&n, sizeof n)) { + lwsl_warn("Failed to set SNDBUF to %d", n); + return 1; + } +#endif + + /* notify user code that we're ready to roll */ + + if (wsi->protocol->callback) + if (wsi->protocol->callback(wsi, LWS_CALLBACK_ESTABLISHED, + wsi->user_space, +#ifdef LWS_OPENSSL_SUPPORT + wsi->ssl, +#else + NULL, +#endif + 0)) + return 1; + + return 0; +} + +int +lws_handshake_server(struct lws *wsi, unsigned char **buf, size_t len) +{ + int protocol_len, n = 0, hit, non_space_char_found = 0, m; + struct lws_context *context = lws_get_context(wsi); + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + struct _lws_header_related hdr; + struct allocated_headers *ah; + unsigned char *obuf = *buf; + char protocol_list[128]; + char protocol_name[64]; + size_t olen = len; + char *p; + + if (len >= 10000000) { + lwsl_err("%s: assert: len %ld\n", __func__, (long)len); + assert(0); + } + + if (!wsi->u.hdr.ah) { + lwsl_err("%s: assert: NULL ah\n", __func__); + assert(0); + } + + lwsl_hexdump(*buf, len); + + while (len--) { + wsi->more_rx_waiting = !!len; + + if (wsi->mode != LWSCM_HTTP_SERVING && + wsi->mode != LWSCM_HTTP2_SERVING && + wsi->mode != LWSCM_HTTP_SERVING_ACCEPTED) { + lwsl_err("%s: bad wsi mode %d\n", __func__, wsi->mode); + goto bail_nuke_ah; + } + + m = lws_parse(wsi, *(*buf)++); + if (m) { + if (m == 2) { + /* + * we are transitioning from http with + * an AH, to raw. Drop the ah and set + * the mode. + */ +raw_transition: + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + lws_bind_protocol(wsi, &wsi->vhost->protocols[ + wsi->vhost-> + raw_protocol_index]); + lwsl_info("transition to raw vh %s prot %d\n", + wsi->vhost->name, + wsi->vhost->raw_protocol_index); + if ((wsi->protocol->callback)(wsi, + LWS_CALLBACK_RAW_ADOPT, + wsi->user_space, NULL, 0)) + goto bail_nuke_ah; + + lws_header_table_force_to_detachable_state(wsi); + lws_union_transition(wsi, LWSCM_RAW); + lws_header_table_detach(wsi, 1); + + if (m == 2 && (wsi->protocol->callback)(wsi, + LWS_CALLBACK_RAW_RX, + wsi->user_space, obuf, olen)) + return 1; + + return 0; + } + lwsl_info("lws_parse failed\n"); + goto bail_nuke_ah; + } + + if (wsi->u.hdr.parser_state != WSI_PARSING_COMPLETE) + continue; + + lwsl_parser("%s: lws_parse sees parsing complete\n", __func__); + lwsl_debug("%s: wsi->more_rx_waiting=%d\n", __func__, + wsi->more_rx_waiting); + + /* select vhost */ + + if (lws_hdr_total_length(wsi, WSI_TOKEN_HOST)) { + struct lws_vhost *vhost = lws_select_vhost( + context, wsi->vhost->listen_port, + lws_hdr_simple_ptr(wsi, WSI_TOKEN_HOST)); + + if (vhost) + wsi->vhost = vhost; + } else + lwsl_info("no host\n"); + + if (wsi->mode != LWSCM_HTTP2_SERVING) { + wsi->vhost->conn_stats.h1_trans++; + if (!wsi->conn_stat_done) { + wsi->vhost->conn_stats.h1_conn++; + wsi->conn_stat_done = 1; + } + } + + /* check for unwelcome guests */ + + if (wsi->context->reject_service_keywords) { + const struct lws_protocol_vhost_options *rej = + wsi->context->reject_service_keywords; + char ua[384], *msg = NULL; + + if (lws_hdr_copy(wsi, ua, sizeof(ua) - 1, + WSI_TOKEN_HTTP_USER_AGENT) > 0) { + ua[sizeof(ua) - 1] = '\0'; + while (rej) { + if (strstr(ua, rej->name)) { +#ifdef LWS_WITH_ACCESS_LOG + char *uri_ptr = NULL; + int meth, uri_len; +#endif + + msg = strchr(rej->value, ' '); + if (msg) + msg++; + lws_return_http_status(wsi, + atoi(rej->value), msg); +#ifdef LWS_WITH_ACCESS_LOG + meth = lws_http_get_uri_and_method(wsi, + &uri_ptr, &uri_len); + if (meth >= 0) + lws_prepare_access_log_info(wsi, + uri_ptr, meth); + + /* wsi close will do the log */ +#endif + wsi->vhost->conn_stats.rejected++; + /* + * We don't want anything from + * this rejected guy. Follow + * the close flow, not the + * transaction complete flow. + */ + goto bail_nuke_ah; + } + rej = rej->next; + } + } + } + + + if (lws_hdr_total_length(wsi, WSI_TOKEN_CONNECT)) { + lwsl_info("Changing to RAW mode\n"); + m = 0; + goto raw_transition; + } + + wsi->mode = LWSCM_PRE_WS_SERVING_ACCEPT; + lws_set_timeout(wsi, NO_PENDING_TIMEOUT, 0); + + /* is this websocket protocol or normal http 1.0? */ + + if (lws_hdr_total_length(wsi, WSI_TOKEN_UPGRADE)) { + if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), + "websocket")) { + wsi->vhost->conn_stats.ws_upg++; + lwsl_info("Upgrade to ws\n"); + goto upgrade_ws; + } +#ifdef LWS_WITH_HTTP2 + if (!strcasecmp(lws_hdr_simple_ptr(wsi, WSI_TOKEN_UPGRADE), + "h2c")) { + wsi->vhost->conn_stats.h2_upg++; + lwsl_info("Upgrade to h2c\n"); + goto upgrade_h2c; + } +#endif + lwsl_info("Unknown upgrade\n"); + /* dunno what he wanted to upgrade to */ + goto bail_nuke_ah; + } + + /* no upgrade ack... he remained as HTTP */ + + lwsl_info("No upgrade\n"); + ah = wsi->u.hdr.ah; + + lws_union_transition(wsi, LWSCM_HTTP_SERVING_ACCEPTED); + wsi->state = LWSS_HTTP; + wsi->u.http.fop_fd = NULL; + + /* expose it at the same offset as u.hdr */ + wsi->u.http.ah = ah; + lwsl_debug("%s: wsi %p: ah %p\n", __func__, (void *)wsi, + (void *)wsi->u.hdr.ah); + + n = lws_http_action(wsi); + + return n; + +#ifdef LWS_WITH_HTTP2 +upgrade_h2c: + if (!lws_hdr_total_length(wsi, WSI_TOKEN_HTTP2_SETTINGS)) { + lwsl_info("missing http2_settings\n"); + goto bail_nuke_ah; + } + + lwsl_info("h2c upgrade...\n"); + + p = lws_hdr_simple_ptr(wsi, WSI_TOKEN_HTTP2_SETTINGS); + /* convert the peer's HTTP-Settings */ + n = lws_b64_decode_string(p, protocol_list, + sizeof(protocol_list)); + if (n < 0) { + lwsl_parser("HTTP2_SETTINGS too long\n"); + return 1; + } + + /* adopt the header info */ + + ah = wsi->u.hdr.ah; + + lws_union_transition(wsi, LWSCM_HTTP2_SERVING); + + /* http2 union member has http union struct at start */ + wsi->u.http.ah = ah; + + if (!wsi->u.h2.h2n) { + wsi->u.h2.h2n = lws_zalloc(sizeof(*wsi->u.h2.h2n), "h2n"); + if (!wsi->u.h2.h2n) + return 1; + } + + lws_h2_init(wsi); + + /* HTTP2 union */ + + lws_h2_settings(wsi, &wsi->u.h2.h2n->set, + (unsigned char *)protocol_list, n); + + lws_hpack_dynamic_size(wsi, wsi->u.h2.h2n->set.s[ + H2SET_HEADER_TABLE_SIZE]); + + strcpy(protocol_list, "HTTP/1.1 101 Switching Protocols\x0d\x0a" + "Connection: Upgrade\x0d\x0a" + "Upgrade: h2c\x0d\x0a\x0d\x0a"); + n = lws_issue_raw(wsi, (unsigned char *)protocol_list, + strlen(protocol_list)); + if (n != strlen(protocol_list)) { + lwsl_debug("http2 switch: ERROR writing to socket\n"); + return 1; + } + + wsi->state = LWSS_HTTP2_AWAIT_CLIENT_PREFACE; + + return 0; +#endif + +upgrade_ws: + if (!wsi->protocol) + lwsl_err("NULL protocol at lws_read\n"); + + /* + * It's websocket + * + * Select the first protocol we support from the list + * the client sent us. + * + * Copy it to remove header fragmentation + */ + + if (lws_hdr_copy(wsi, protocol_list, sizeof(protocol_list) - 1, + WSI_TOKEN_PROTOCOL) < 0) { + lwsl_err("protocol list too long"); + goto bail_nuke_ah; + } + + protocol_len = lws_hdr_total_length(wsi, WSI_TOKEN_PROTOCOL); + protocol_list[protocol_len] = '\0'; + p = protocol_list; + hit = 0; + + while (*p && !hit) { + n = 0; + non_space_char_found = 0; + while (n < sizeof(protocol_name) - 1 && + *p && *p != ',') { + /* ignore leading spaces */ + if (!non_space_char_found && *p == ' ') { + n++; + continue; + } + non_space_char_found = 1; + protocol_name[n++] = *p++; + } + protocol_name[n] = '\0'; + if (*p) + p++; + + lwsl_info("checking %s\n", protocol_name); + + n = 0; + while (wsi->vhost->protocols[n].callback) { + lwsl_info("try %s\n", + wsi->vhost->protocols[n].name); + + if (wsi->vhost->protocols[n].name && + !strcmp(wsi->vhost->protocols[n].name, + protocol_name)) { + wsi->protocol = &wsi->vhost->protocols[n]; + hit = 1; + break; + } + + n++; + } + } + + /* we didn't find a protocol he wanted? */ + + if (!hit) { + if (lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL)) { + lwsl_info("No protocol from \"%s\" supported\n", + protocol_list); + goto bail_nuke_ah; + } + /* + * some clients only have one protocol and + * do not send the protocol list header... + * allow it and match to the vhost's default + * protocol (which itself defaults to zero) + */ + lwsl_info("defaulting to prot handler %d\n", + wsi->vhost->default_protocol_index); + n = wsi->vhost->default_protocol_index; + wsi->protocol = &wsi->vhost->protocols[ + (int)wsi->vhost->default_protocol_index]; + } + + /* allocate wsi->user storage */ + if (lws_ensure_user_space(wsi)) + goto bail_nuke_ah; + + /* + * Give the user code a chance to study the request and + * have the opportunity to deny it + */ + if ((wsi->protocol->callback)(wsi, + LWS_CALLBACK_FILTER_PROTOCOL_CONNECTION, + wsi->user_space, + lws_hdr_simple_ptr(wsi, WSI_TOKEN_PROTOCOL), 0)) { + lwsl_warn("User code denied connection\n"); + goto bail_nuke_ah; + } + + /* + * Perform the handshake according to the protocol version the + * client announced + */ + + switch (wsi->ietf_spec_revision) { + case 13: + lwsl_parser("lws_parse calling handshake_04\n"); + if (handshake_0405(context, wsi)) { + lwsl_info("hs0405 has failed the connection\n"); + goto bail_nuke_ah; + } + break; + + default: + lwsl_info("Unknown client spec version %d\n", + wsi->ietf_spec_revision); + goto bail_nuke_ah; + } + + lws_same_vh_protocol_insert(wsi, n); + + /* we are upgrading to ws, so http/1.1 and keepalive + + * pipelined header considerations about keeping the ah around + * no longer apply. However it's common for the first ws + * protocol data to have been coalesced with the browser + * upgrade request and to already be in the ah rx buffer. + */ + + lwsl_info("%s: %p: inheriting ws ah (rxpos:%d, rxlen:%d)\n", + __func__, wsi, wsi->u.hdr.ah->rxpos, + wsi->u.hdr.ah->rxlen); + lws_pt_lock(pt); + hdr = wsi->u.hdr; + + lws_union_transition(wsi, LWSCM_WS_SERVING); + /* + * first service is WS mode will notice this, use the RX and + * then detach the ah (caution: we are not in u.hdr union + * mode any more then... ah_temp member is at start the same + * though) + * + * Because rxpos/rxlen shows something in the ah, we will get + * service guaranteed next time around the event loop + * + * All union members begin with hdr, so we can use it even + * though we transitioned to ws union mode (the ah detach + * code uses it anyway). + */ + wsi->u.hdr = hdr; + lws_pt_unlock(pt); + + lws_server_init_wsi_for_ws(wsi); + lwsl_parser("accepted v%02d connection\n", + wsi->ietf_spec_revision); + + /* !!! drop ah unreservedly after ESTABLISHED */ + if (!wsi->more_rx_waiting) { + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 1); + } + + return 0; + } /* while all chars are handled */ + + return 0; + +bail_nuke_ah: + /* drop the header info */ + /* we're closing, losing some rx is OK */ + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 1); + + return 1; +} + + +static int +lws_get_idlest_tsi(struct lws_context *context) +{ + unsigned int lowest = ~0; + int n = 0, hit = -1; + + for (; n < context->count_threads; n++) { + if ((unsigned int)context->pt[n].fds_count != + context->fd_limit_per_thread - 1 && + (unsigned int)context->pt[n].fds_count < lowest) { + lowest = context->pt[n].fds_count; + hit = n; + } + } + + return hit; +} + +struct lws * +lws_create_new_server_wsi(struct lws_vhost *vhost) +{ + struct lws *new_wsi; + int n = lws_get_idlest_tsi(vhost->context); + + if (n < 0) { + lwsl_err("no space for new conn\n"); + return NULL; + } + + new_wsi = lws_zalloc(sizeof(struct lws), "new server wsi"); + if (new_wsi == NULL) { + lwsl_err("Out of memory for new connection\n"); + return NULL; + } + + new_wsi->tsi = n; + lwsl_debug("new wsi %p joining vhost %s, tsi %d\n", new_wsi, + vhost->name, new_wsi->tsi); + + new_wsi->vhost = vhost; + new_wsi->context = vhost->context; + new_wsi->pending_timeout = NO_PENDING_TIMEOUT; + new_wsi->rxflow_change_to = LWS_RXFLOW_ALLOW; + + /* initialize the instance struct */ + + new_wsi->state = LWSS_HTTP; + new_wsi->mode = LWSCM_HTTP_SERVING; + new_wsi->hdr_parsing_completed = 0; + +#ifdef LWS_OPENSSL_SUPPORT + new_wsi->use_ssl = LWS_SSL_ENABLED(vhost); +#endif + + /* + * these can only be set once the protocol is known + * we set an un-established connection's protocol pointer + * to the start of the supported list, so it can look + * for matching ones during the handshake + */ + new_wsi->protocol = vhost->protocols; + new_wsi->user_space = NULL; + new_wsi->ietf_spec_revision = 0; + new_wsi->desc.sockfd = LWS_SOCK_INVALID; + new_wsi->position_in_fds_table = -1; + + vhost->context->count_wsi_allocated++; + + /* + * outermost create notification for wsi + * no user_space because no protocol selection + */ + vhost->protocols[0].callback(new_wsi, LWS_CALLBACK_WSI_CREATE, + NULL, NULL, 0); + + return new_wsi; +} + +LWS_VISIBLE int LWS_WARN_UNUSED_RESULT +lws_http_transaction_completed(struct lws *wsi) +{ + int n = NO_PENDING_TIMEOUT; + + lwsl_info("%s: wsi %p\n", __func__, wsi); + + lws_access_log(wsi); + + if (!wsi->hdr_parsing_completed) { + lwsl_notice("%s: ignoring, ah parsing incomplete\n", __func__); + return 0; + } + + lwsl_debug("%s: wsi %p\n", __func__, wsi); + /* if we can't go back to accept new headers, drop the connection */ + if (wsi->http2_substream) + return 0; + + if (wsi->seen_zero_length_recv) + return 1; + + if (wsi->u.http.connection_type != HTTP_CONNECTION_KEEP_ALIVE) { + lwsl_info("%s: %p: close connection\n", __func__, wsi); + return 1; + } + + if (lws_bind_protocol(wsi, &wsi->vhost->protocols[0])) + return 1; + + /* otherwise set ourselves up ready to go again */ + wsi->state = LWSS_HTTP; + wsi->mode = LWSCM_HTTP_SERVING; + wsi->u.http.tx_content_length = 0; + wsi->u.http.tx_content_remain = 0; + wsi->hdr_parsing_completed = 0; +#ifdef LWS_WITH_ACCESS_LOG + wsi->access_log.sent = 0; +#endif + + if (wsi->vhost->keepalive_timeout) + n = PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE; + lws_set_timeout(wsi, n, wsi->vhost->keepalive_timeout); + + /* + * We already know we are on http1.1 / keepalive and the next thing + * coming will be another header set. + * + * If there is no pending rx and we still have the ah, drop it and + * reacquire a new ah when the new headers start to arrive. (Otherwise + * we needlessly hog an ah indefinitely.) + * + * However if there is pending rx and we know from the keepalive state + * that is already at least the start of another header set, simply + * reset the existing header table and keep it. + */ + if (wsi->u.hdr.ah) { + lwsl_debug("%s: wsi->more_rx_waiting=%d\n", __func__, + wsi->more_rx_waiting); + + if (!wsi->more_rx_waiting) { + lws_header_table_force_to_detachable_state(wsi); + lws_header_table_detach(wsi, 1); +#ifdef LWS_OPENSSL_SUPPORT + /* + * additionally... if we are hogging an SSL instance + * with no pending pipelined headers (or ah now), and + * SSL is scarce, drop this connection without waiting + */ + + if (wsi->vhost->use_ssl && + wsi->context->simultaneous_ssl_restriction && + wsi->context->simultaneous_ssl == + wsi->context->simultaneous_ssl_restriction) { + lwsl_info("%s: simultaneous_ssl_restriction\n", + __func__); + return 1; + } +#endif + } else { + lws_header_table_reset(wsi, 1); + /* + * If we kept the ah, we should restrict the amount + * of time we are willing to keep it. Otherwise it + * will be bound the whole time the connection remains + * open. + */ + lws_set_timeout(wsi, PENDING_TIMEOUT_HOLDING_AH, + wsi->vhost->keepalive_timeout); + } + } + + /* If we're (re)starting on headers, need other implied init */ + wsi->u.hdr.ues = URIES_IDLE; + + lwsl_info("%s: %p: keep-alive await new transaction\n", __func__, wsi); + + return 0; +} + +/* if not a socket, it's a raw, non-ssl file descriptor */ + +LWS_VISIBLE struct lws * +lws_adopt_descriptor_vhost(struct lws_vhost *vh, lws_adoption_type type, + lws_sock_file_fd_type fd, const char *vh_prot_name, + struct lws *parent) +{ + struct lws_context *context = vh->context; + struct lws *new_wsi; + struct lws_context_per_thread *pt; + int n, ssl = 0; + +#if defined(LWS_WITH_PEER_LIMITS) + struct lws_peer *peer = NULL; + + if (type & LWS_ADOPT_SOCKET && !(type & LWS_ADOPT_WS_PARENTIO)) { + peer = lws_get_or_create_peer(vh, fd.sockfd); + + if (!peer) { + lwsl_err("OOM creating peer\n"); + return NULL; + } + if (context->ip_limit_wsi && + peer->count_wsi >= context->ip_limit_wsi) { + lwsl_notice("Peer reached wsi limit %d\n", + context->ip_limit_wsi); + lws_stats_atomic_bump(context, &context->pt[0], + LWSSTATS_C_PEER_LIMIT_WSI_DENIED, 1); + return NULL; + } + } +#endif + + new_wsi = lws_create_new_server_wsi(vh); + if (!new_wsi) { + if (type & LWS_ADOPT_SOCKET && !(type & LWS_ADOPT_WS_PARENTIO)) + compatible_close(fd.sockfd); + return NULL; + } +#if defined(LWS_WITH_PEER_LIMITS) + if (peer) + lws_peer_add_wsi(context, peer, new_wsi); +#endif + pt = &context->pt[(int)new_wsi->tsi]; + lws_stats_atomic_bump(context, pt, LWSSTATS_C_CONNECTIONS, 1); + + if (parent) { + new_wsi->parent = parent; + new_wsi->sibling_list = parent->child_list; + parent->child_list = new_wsi; + + if (type & LWS_ADOPT_WS_PARENTIO) + new_wsi->parent_carries_io = 1; + } + + new_wsi->desc = fd; + + if (vh_prot_name) { + new_wsi->protocol = lws_vhost_name_to_protocol(new_wsi->vhost, + vh_prot_name); + if (!new_wsi->protocol) { + lwsl_err("Protocol %s not enabled on vhost %s\n", + vh_prot_name, new_wsi->vhost->name); + goto bail; + } + if (lws_ensure_user_space(new_wsi)) { + lwsl_notice("OOM trying to get user_space\n"); + goto bail; + } + if (type & LWS_ADOPT_WS_PARENTIO) { + new_wsi->desc.sockfd = LWS_SOCK_INVALID; + lwsl_debug("binding to %s\n", new_wsi->protocol->name); + lws_bind_protocol(new_wsi, new_wsi->protocol); + lws_union_transition(new_wsi, LWSCM_WS_SERVING); + lws_server_init_wsi_for_ws(new_wsi); + + return new_wsi; + } + } else + if (type & LWS_ADOPT_HTTP) /* he will transition later */ + new_wsi->protocol = + &vh->protocols[vh->default_protocol_index]; + else { /* this is the only time he will transition */ + lws_bind_protocol(new_wsi, + &vh->protocols[vh->raw_protocol_index]); + lws_union_transition(new_wsi, LWSCM_RAW); + } + + if (type & LWS_ADOPT_SOCKET) { /* socket desc */ + lwsl_debug("%s: new wsi %p, sockfd %d\n", __func__, new_wsi, + (int)(lws_intptr_t)fd.sockfd); + + if (type & LWS_ADOPT_HTTP) + /* the transport is accepted... + * give him time to negotiate */ + lws_set_timeout(new_wsi, + PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, + context->timeout_secs); + +#if LWS_POSIX == 0 +#if defined(LWS_WITH_ESP8266) + esp8266_tcp_stream_accept(accept_fd, new_wsi); +#endif +#endif + } else /* file desc */ + lwsl_debug("%s: new wsi %p, filefd %d\n", __func__, new_wsi, + (int)(lws_intptr_t)fd.filefd); + + /* + * A new connection was accepted. Give the user a chance to + * set properties of the newly created wsi. There's no protocol + * selected yet so we issue this to the vhosts's default protocol, + * itself by default protocols[0] + */ + n = LWS_CALLBACK_SERVER_NEW_CLIENT_INSTANTIATED; + if (!(type & LWS_ADOPT_HTTP)) { + if (!(type & LWS_ADOPT_SOCKET)) + n = LWS_CALLBACK_RAW_ADOPT_FILE; + else + n = LWS_CALLBACK_RAW_ADOPT; + } + + if (!LWS_SSL_ENABLED(new_wsi->vhost) || !(type & LWS_ADOPT_ALLOW_SSL) || + !(type & LWS_ADOPT_SOCKET)) { + /* non-SSL */ + if (!(type & LWS_ADOPT_HTTP)) { + if (!(type & LWS_ADOPT_SOCKET)) + new_wsi->mode = LWSCM_RAW_FILEDESC; + else + new_wsi->mode = LWSCM_RAW; + } + } else { + /* SSL */ + if (!(type & LWS_ADOPT_HTTP)) + new_wsi->mode = LWSCM_SSL_INIT_RAW; + else + new_wsi->mode = LWSCM_SSL_INIT; + + ssl = 1; + } + + lws_libev_accept(new_wsi, new_wsi->desc); + lws_libuv_accept(new_wsi, new_wsi->desc); + lws_libevent_accept(new_wsi, new_wsi->desc); + + if (!ssl) { + if (insert_wsi_socket_into_fds(context, new_wsi)) { + lwsl_err("%s: fail inserting socket\n", __func__); + goto fail; + } + } else + if (lws_server_socket_service_ssl(new_wsi, fd.sockfd)) { + lwsl_info("%s: fail ssl negotiation\n", __func__); + goto fail; + } + + /* + * by deferring callback to this point, after insertion to fds, + * lws_callback_on_writable() can work from the callback + */ + if ((new_wsi->protocol->callback)( + new_wsi, n, new_wsi->user_space, NULL, 0)) + goto fail; + + if (type & LWS_ADOPT_HTTP) { + if (!lws_header_table_attach(new_wsi, 0)) + lwsl_debug("Attached ah immediately\n"); + else + lwsl_info("%s: waiting for ah\n", __func__); + } + + return new_wsi; + +fail: + if (type & LWS_ADOPT_SOCKET) + lws_close_free_wsi(new_wsi, LWS_CLOSE_STATUS_NOSTATUS); + + return NULL; + +bail: + lwsl_notice("%s: exiting on bail\n", __func__); + if (parent) + parent->child_list = new_wsi->sibling_list; + if (new_wsi->user_space) + lws_free(new_wsi->user_space); + lws_free(new_wsi); + compatible_close(fd.sockfd); + + return NULL; +} + +LWS_VISIBLE struct lws * +lws_adopt_socket_vhost(struct lws_vhost *vh, lws_sockfd_type accept_fd) +{ + lws_sock_file_fd_type fd; + + fd.sockfd = accept_fd; + return lws_adopt_descriptor_vhost(vh, LWS_ADOPT_SOCKET | + LWS_ADOPT_HTTP | LWS_ADOPT_ALLOW_SSL, fd, NULL, NULL); +} + +LWS_VISIBLE struct lws * +lws_adopt_socket(struct lws_context *context, lws_sockfd_type accept_fd) +{ + return lws_adopt_socket_vhost(context->vhost_list, accept_fd); +} + +/* Common read-buffer adoption for lws_adopt_*_readbuf */ +static struct lws* +adopt_socket_readbuf(struct lws *wsi, const char *readbuf, size_t len) +{ + struct lws_context_per_thread *pt; + struct allocated_headers *ah; + struct lws_pollfd *pfd; + + if (!wsi) + return NULL; + + if (!readbuf || len == 0) + return wsi; + + if (len > sizeof(ah->rx)) { + lwsl_err("%s: rx in too big\n", __func__); + goto bail; + } + + /* + * we can't process the initial read data until we can attach an ah. + * + * if one is available, get it and place the data in his ah rxbuf... + * wsi with ah that have pending rxbuf get auto-POLLIN service. + * + * no autoservice because we didn't get a chance to attach the + * readbuf data to wsi or ah yet, and we will do it next if we get + * the ah. + */ + if (wsi->u.hdr.ah || !lws_header_table_attach(wsi, 0)) { + ah = wsi->u.hdr.ah; + memcpy(ah->rx, readbuf, len); + ah->rxpos = 0; + ah->rxlen = (int16_t)len; + + lwsl_notice("%s: calling service on readbuf ah\n", __func__); + pt = &wsi->context->pt[(int)wsi->tsi]; + + /* unlike a normal connect, we have the headers already + * (or the first part of them anyway). + * libuv won't come back and service us without a network + * event, so we need to do the header service right here. + */ + pfd = &pt->fds[wsi->position_in_fds_table]; + pfd->revents |= LWS_POLLIN; + lwsl_err("%s: calling service\n", __func__); + if (lws_service_fd_tsi(wsi->context, pfd, wsi->tsi)) + /* service closed us */ + return NULL; + + return wsi; + } + lwsl_err("%s: deferring handling ah\n", __func__); + /* + * hum if no ah came, we are on the wait list and must defer + * dealing with this until the ah arrives. + * + * later successful lws_header_table_attach() will apply the + * below to the rx buffer (via lws_header_table_reset()). + */ + wsi->u.hdr.preamble_rx = lws_malloc(len, "preamble_rx"); + if (!wsi->u.hdr.preamble_rx) { + lwsl_err("OOM\n"); + goto bail; + } + memcpy(wsi->u.hdr.preamble_rx, readbuf, len); + wsi->u.hdr.preamble_rx_len = len; + + return wsi; + +bail: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + + return NULL; +} + +LWS_VISIBLE struct lws * +lws_adopt_socket_readbuf(struct lws_context *context, lws_sockfd_type accept_fd, + const char *readbuf, size_t len) +{ + return adopt_socket_readbuf(lws_adopt_socket(context, accept_fd), + readbuf, len); +} + +LWS_VISIBLE struct lws * +lws_adopt_socket_vhost_readbuf(struct lws_vhost *vhost, + lws_sockfd_type accept_fd, + const char *readbuf, size_t len) +{ + return adopt_socket_readbuf(lws_adopt_socket_vhost(vhost, accept_fd), + readbuf, len); +} + +LWS_VISIBLE int +lws_server_socket_service(struct lws_context *context, struct lws *wsi, + struct lws_pollfd *pollfd) +{ + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + lws_sockfd_type accept_fd = LWS_SOCK_INVALID; + struct allocated_headers *ah; + lws_sock_file_fd_type fd; + int opts = LWS_ADOPT_SOCKET | LWS_ADOPT_ALLOW_SSL; +#if LWS_POSIX + struct sockaddr_storage cli_addr; + socklen_t clilen; +#endif + int n, len; + + switch (wsi->mode) { + + case LWSCM_HTTP_SERVING: + case LWSCM_HTTP_SERVING_ACCEPTED: + case LWSCM_HTTP2_SERVING: + case LWSCM_RAW: + + /* handle http headers coming in */ + + /* pending truncated sends have uber priority */ + + if (wsi->trunc_len) { + if (!(pollfd->revents & LWS_POLLOUT)) + break; + + if (lws_issue_raw(wsi, wsi->trunc_alloc + + wsi->trunc_offset, + wsi->trunc_len) < 0) + goto fail; + /* + * we can't afford to allow input processing to send + * something new, so spin around he event loop until + * he doesn't have any partials + */ + break; + } + + /* any incoming data ready? */ + + if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) + goto try_pollout; + + /* + * If we previously just did POLLIN when IN and OUT were + * signalled (because POLLIN processing may have used up + * the POLLOUT), don't let that happen twice in a row... + * next time we see the situation favour POLLOUT + */ +#if !defined(LWS_WITH_ESP8266) + if (wsi->favoured_pollin && + (pollfd->revents & pollfd->events & LWS_POLLOUT)) { + lwsl_notice("favouring pollout\n"); + wsi->favoured_pollin = 0; + goto try_pollout; + } +#endif + + /* these states imply we MUST have an ah attached */ + + if (wsi->mode != LWSCM_RAW && (wsi->state == LWSS_HTTP || + wsi->state == LWSS_HTTP_ISSUING_FILE || + wsi->state == LWSS_HTTP_HEADERS)) { + if (!wsi->u.hdr.ah) { + /* no autoservice beacuse we will do it next */ + if (lws_header_table_attach(wsi, 0)) { + lwsl_info("wsi %p: ah get fail\n", wsi); + goto try_pollout; + } + } + ah = wsi->u.hdr.ah; + + /* if nothing in ah rx buffer, get some fresh rx */ + if (ah->rxpos == ah->rxlen) { + ah->rxlen = lws_ssl_capable_read(wsi, ah->rx, + sizeof(ah->rx)); + ah->rxpos = 0; + switch (ah->rxlen) { + case 0: + lwsl_info("%s: read 0 len a\n", __func__); + wsi->seen_zero_length_recv = 1; + lws_change_pollfd(wsi, LWS_POLLIN, 0); + goto try_pollout; + /* fallthru */ + case LWS_SSL_CAPABLE_ERROR: + goto fail; + case LWS_SSL_CAPABLE_MORE_SERVICE: + ah->rxlen = ah->rxpos = 0; + goto try_pollout; + } + + /* + * make sure ah does not get detached if we + * have live data in the rx + */ + if (ah->rxlen) + wsi->more_rx_waiting = 1; + } + + if (!(ah->rxpos != ah->rxlen && ah->rxlen)) { + lwsl_err("%s: assert: rxpos %d, rxlen %d\n", + __func__, ah->rxpos, ah->rxlen); + + assert(0); + } + + /* just ignore incoming if waiting for close */ + if (wsi->state != LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE && + wsi->state != LWSS_HTTP_ISSUING_FILE) { + /* + * otherwise give it to whoever wants it + * according to the connection state + */ + + n = lws_read(wsi, ah->rx + ah->rxpos, + ah->rxlen - ah->rxpos); + if (n < 0) /* we closed wsi */ + return 1; + + if (!wsi->u.hdr.ah) + break; + if ( wsi->u.hdr.ah->rxlen) + wsi->u.hdr.ah->rxpos += n; + + lwsl_debug("%s: wsi %p: ah read rxpos %d, rxlen %d\n", + __func__, wsi, wsi->u.hdr.ah->rxpos, + wsi->u.hdr.ah->rxlen); + + if (lws_header_table_is_in_detachable_state(wsi) && + (wsi->mode != LWSCM_HTTP_SERVING && + wsi->mode != LWSCM_HTTP_SERVING_ACCEPTED && + wsi->mode != LWSCM_HTTP2_SERVING)) + lws_header_table_detach(wsi, 1); + + break; + } + + goto try_pollout; + } + + len = lws_ssl_capable_read(wsi, pt->serv_buf, + context->pt_serv_buf_size); + lwsl_debug("%s: wsi %p read %d\r\n", __func__, wsi, len); + switch (len) { + case 0: + lwsl_info("%s: read 0 len b\n", __func__); + + /* fallthru */ + case LWS_SSL_CAPABLE_ERROR: + goto fail; + case LWS_SSL_CAPABLE_MORE_SERVICE: + goto try_pollout; + } + + if (len < 0) /* coverity */ + goto fail; + + if (wsi->mode == LWSCM_RAW) { + n = user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RAW_RX, + wsi->user_space, pt->serv_buf, len); + if (n < 0) { + lwsl_info("LWS_CALLBACK_RAW_RX_fail\n"); + goto fail; + } + goto try_pollout; + } + + /* just ignore incoming if waiting for close */ + if (wsi->state != LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE && + wsi->state != LWSS_HTTP_ISSUING_FILE) { + /* + * this may want to send + * (via HTTP callback for example) + */ + n = lws_read(wsi, pt->serv_buf, len); + if (n < 0) /* we closed wsi */ + return 1; + /* + * he may have used up the + * writability above, if we will defer POLLOUT + * processing in favour of POLLIN, note it + */ + if (pollfd->revents & LWS_POLLOUT) + wsi->favoured_pollin = 1; + break; + } + /* + * he may have used up the + * writability above, if we will defer POLLOUT + * processing in favour of POLLIN, note it + */ + if (pollfd->revents & LWS_POLLOUT) + wsi->favoured_pollin = 1; + +try_pollout: + + /* this handles POLLOUT for http serving fragments */ + + if (!(pollfd->revents & LWS_POLLOUT)) + break; + + /* one shot */ + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_notice("%s a\n", __func__); + goto fail; + } + + if (wsi->mode == LWSCM_RAW) { + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_WRITEABLE_CB, 1); +#if defined(LWS_WITH_STATS) + if (wsi->active_writable_req_us) { + uint64_t ul = time_in_microseconds() - + wsi->active_writable_req_us; + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_WRITABLE_DELAY, ul); + lws_stats_atomic_max(wsi->context, pt, + LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); + wsi->active_writable_req_us = 0; + } +#endif + n = user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RAW_WRITEABLE, + wsi->user_space, NULL, 0); + if (n < 0) { + lwsl_info("writeable_fail\n"); + goto fail; + } + break; + } + + if (!wsi->hdr_parsing_completed) + break; + + if (wsi->state != LWSS_HTTP_ISSUING_FILE) { + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_WRITEABLE_CB, 1); +#if defined(LWS_WITH_STATS) + if (wsi->active_writable_req_us) { + uint64_t ul = time_in_microseconds() - + wsi->active_writable_req_us; + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_WRITABLE_DELAY, ul); + lws_stats_atomic_max(wsi->context, pt, + LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); + wsi->active_writable_req_us = 0; + } +#endif + + n = user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_HTTP_WRITEABLE, + wsi->user_space, NULL, 0); + if (n < 0) { + lwsl_info("writeable_fail\n"); + goto fail; + } + break; + } + + /* >0 == completion, <0 == error + * + * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when + * it's done. That's the case even if we just completed the + * send, so wait for that. + */ + n = lws_serve_http_file_fragment(wsi); + if (n < 0) + goto fail; + + break; + + case LWSCM_SERVER_LISTENER: + +#if LWS_POSIX + /* pollin means a client has connected to us then */ + + do { + if (!(pollfd->revents & LWS_POLLIN) || + !(pollfd->events & LWS_POLLIN)) + break; + +#ifdef LWS_OPENSSL_SUPPORT + /* + * can we really accept it, with regards to SSL limit? + * another vhost may also have had POLLIN on his listener this + * round and used it up already + */ + + if (wsi->vhost->use_ssl && + context->simultaneous_ssl_restriction && + context->simultaneous_ssl == + context->simultaneous_ssl_restriction) + /* no... ignore it, he won't come again until we are + * below the simultaneous_ssl_restriction limit and + * POLLIN is enabled on him again + */ + break; +#endif + /* listen socket got an unencrypted connection... */ + + clilen = sizeof(cli_addr); + lws_latency_pre(context, wsi); + + /* + * We cannot identify the peer who is in the listen + * socket connect queue before we accept it; even if + * we could, not accepting it due to PEER_LIMITS would + * block the connect queue for other legit peers. + */ + accept_fd = accept(pollfd->fd, (struct sockaddr *)&cli_addr, + &clilen); + lws_latency(context, wsi, "listener accept", accept_fd, + accept_fd >= 0); + if (accept_fd < 0) { + if (LWS_ERRNO == LWS_EAGAIN || + LWS_ERRNO == LWS_EWOULDBLOCK) { + break; + } + lwsl_err("ERROR on accept: %s\n", strerror(LWS_ERRNO)); + break; + } + + lws_plat_set_socket_options(wsi->vhost, accept_fd); + +#if defined(LWS_WITH_IPV6) + lwsl_debug("accepted new conn port %u on fd=%d\n", + ((cli_addr.ss_family == AF_INET6) ? + ntohs(((struct sockaddr_in6 *) &cli_addr)->sin6_port) : + ntohs(((struct sockaddr_in *) &cli_addr)->sin_port)), + accept_fd); +#else + lwsl_debug("accepted new conn port %u on fd=%d\n", + ntohs(((struct sockaddr_in *) &cli_addr)->sin_port), + accept_fd); +#endif + +#else + /* not very beautiful... */ + accept_fd = (lws_sockfd_type)pollfd; +#endif + /* + * look at who we connected to and give user code a chance + * to reject based on client IP. There's no protocol selected + * yet so we issue this to protocols[0] + */ + if ((wsi->vhost->protocols[0].callback)(wsi, + LWS_CALLBACK_FILTER_NETWORK_CONNECTION, + NULL, (void *)(lws_intptr_t)accept_fd, 0)) { + lwsl_debug("Callback denied network connection\n"); + compatible_close(accept_fd); + break; + } + + if (!(wsi->vhost->options & LWS_SERVER_OPTION_ONLY_RAW)) + opts |= LWS_ADOPT_HTTP; + else + opts = LWS_ADOPT_SOCKET; + + fd.sockfd = accept_fd; + if (!lws_adopt_descriptor_vhost(wsi->vhost, opts, fd, + NULL, NULL)) + /* already closed cleanly as necessary */ + return 1; + +#if LWS_POSIX + } while (pt->fds_count < context->fd_limit_per_thread - 1 && + lws_poll_listen_fd(&pt->fds[wsi->position_in_fds_table]) > 0); +#endif + return 0; + + default: + break; + } + + if (!lws_server_socket_service_ssl(wsi, accept_fd)) + return 0; + +fail: + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + + return 1; +} + +LWS_VISIBLE int +lws_serve_http_file(struct lws *wsi, const char *file, const char *content_type, + const char *other_headers, int other_headers_len) +{ + static const char * const intermediates[] = { "private", "public" }; + struct lws_context *context = lws_get_context(wsi); + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; +#if defined(LWS_WITH_RANGES) + struct lws_range_parsing *rp = &wsi->u.http.range; +#endif + char cache_control[50], *cc = "no-store"; + unsigned char *response = pt->serv_buf + LWS_PRE; + unsigned char *p = response; + unsigned char *end = p + context->pt_serv_buf_size - LWS_PRE; + lws_filepos_t computed_total_content_length; + int ret = 0, cclen = 8, n = HTTP_STATUS_OK; + lws_fop_flags_t fflags = LWS_O_RDONLY; +#if defined(LWS_WITH_RANGES) + int ranges; +#endif + const struct lws_plat_file_ops *fops; + const char *vpath; + + /* + * We either call the platform fops .open with first arg platform fops, + * or we call fops_zip .open with first arg platform fops, and fops_zip + * open will decide whether to switch to fops_zip or stay with fops_def. + * + * If wsi->u.http.fop_fd is already set, the caller already opened it + */ + if (!wsi->u.http.fop_fd) { + fops = lws_vfs_select_fops(wsi->context->fops, file, &vpath); + fflags |= lws_vfs_prepare_flags(wsi); + wsi->u.http.fop_fd = fops->LWS_FOP_OPEN(wsi->context->fops, + file, vpath, &fflags); + if (!wsi->u.http.fop_fd) { + lwsl_err("Unable to open '%s'\n", file); + + return -1; + } + } + wsi->u.http.filelen = lws_vfs_get_length(wsi->u.http.fop_fd); + computed_total_content_length = wsi->u.http.filelen; + +#if defined(LWS_WITH_RANGES) + ranges = lws_ranges_init(wsi, rp, wsi->u.http.filelen); + + lwsl_debug("Range count %d\n", ranges); + /* + * no ranges -> 200; + * 1 range -> 206 + Content-Type: normal; Content-Range; + * more -> 206 + Content-Type: multipart/byteranges + * Repeat the true Content-Type in each multipart header + * along with Content-Range + */ + if (ranges < 0) { + /* it means he expressed a range in Range:, but it was illegal */ + lws_return_http_status(wsi, HTTP_STATUS_REQ_RANGE_NOT_SATISFIABLE, + NULL); + if (lws_http_transaction_completed(wsi)) + return -1; /* <0 means just hang up */ + + lws_vfs_file_close(&wsi->u.http.fop_fd); + + return 0; /* == 0 means we dealt with the transaction complete */ + } + if (ranges) + n = HTTP_STATUS_PARTIAL_CONTENT; +#endif + + if (lws_add_http_header_status(wsi, n, &p, end)) + return -1; + + if ((wsi->u.http.fop_fd->flags & (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP | + LWS_FOP_FLAG_COMPR_IS_GZIP)) == + (LWS_FOP_FLAG_COMPR_ACCEPTABLE_GZIP | LWS_FOP_FLAG_COMPR_IS_GZIP)) { + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_CONTENT_ENCODING, + (unsigned char *)"gzip", 4, &p, end)) + return -1; + lwsl_info("file is being provided in gzip\n"); + } + + if ( +#if defined(LWS_WITH_RANGES) + ranges < 2 && +#endif + content_type && content_type[0]) + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)content_type, + strlen(content_type), &p, end)) + return -1; + +#if defined(LWS_WITH_RANGES) + if (ranges >= 2) { /* multipart byteranges */ + strncpy(wsi->u.http.multipart_content_type, content_type, + sizeof(wsi->u.http.multipart_content_type) - 1); + wsi->u.http.multipart_content_type[ + sizeof(wsi->u.http.multipart_content_type) - 1] = '\0'; + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_TYPE, + (unsigned char *)"multipart/byteranges; boundary=_lws", + 20, &p, end)) + return -1; + + /* + * our overall content length has to include + * + * - (n + 1) x "_lws\r\n" + * - n x Content-Type: xxx/xxx\r\n + * - n x Content-Range: bytes xxx-yyy/zzz\r\n + * - n x /r/n + * - the actual payloads (aggregated in rp->agg) + * + * Precompute it for the main response header + */ + + computed_total_content_length = (lws_filepos_t)rp->agg + + 6 /* final _lws\r\n */; + + lws_ranges_reset(rp); + while (lws_ranges_next(rp)) { + n = lws_snprintf(cache_control, sizeof(cache_control), + "bytes %llu-%llu/%llu", + rp->start, rp->end, rp->extent); + + computed_total_content_length += + 6 /* header _lws\r\n */ + + /* Content-Type: xxx/xxx\r\n */ + 14 + strlen(content_type) + 2 + + /* Content-Range: xxxx\r\n */ + 15 + n + 2 + + 2; /* /r/n */ + } + + lws_ranges_reset(rp); + lws_ranges_next(rp); + } + + if (ranges == 1) { + computed_total_content_length = (lws_filepos_t)rp->agg; + n = lws_snprintf(cache_control, sizeof(cache_control), + "bytes %llu-%llu/%llu", + rp->start, rp->end, rp->extent); + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CONTENT_RANGE, + (unsigned char *)cache_control, + n, &p, end)) + return -1; + } + + wsi->u.http.range.inside = 0; + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_ACCEPT_RANGES, + (unsigned char *)"bytes", 5, &p, end)) + return -1; +#endif + + if (!wsi->sending_chunked) { + if (lws_add_http_header_content_length(wsi, + computed_total_content_length, + &p, end)) + return -1; + } else { + if (lws_add_http_header_by_token(wsi, + WSI_TOKEN_HTTP_TRANSFER_ENCODING, + (unsigned char *)"chunked", + 7, &p, end)) + return -1; + } + + if (wsi->cache_secs && wsi->cache_reuse) { + if (wsi->cache_revalidate) { + cc = cache_control; + cclen = sprintf(cache_control, "%s max-age: %u", + intermediates[wsi->cache_intermediaries], + wsi->cache_secs); + } else { + cc = "no-cache"; + cclen = 8; + } + } + + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_HTTP_CACHE_CONTROL, + (unsigned char *)cc, cclen, &p, end)) + return -1; + + if (wsi->u.http.connection_type == HTTP_CONNECTION_KEEP_ALIVE) + if (lws_add_http_header_by_token(wsi, WSI_TOKEN_CONNECTION, + (unsigned char *)"keep-alive", 10, &p, end)) + return -1; + + if (other_headers) { + if ((end - p) < other_headers_len) + return -1; + memcpy(p, other_headers, other_headers_len); + p += other_headers_len; + } + + if (lws_finalize_http_header(wsi, &p, end)) + return -1; + + ret = lws_write(wsi, response, p - response, LWS_WRITE_HTTP_HEADERS); + if (ret != (p - response)) { + lwsl_err("_write returned %d from %ld\n", ret, + (long)(p - response)); + return -1; + } + + wsi->u.http.filepos = 0; + wsi->state = LWSS_HTTP_ISSUING_FILE; + + lws_callback_on_writable(wsi); + + return 0; +} + +int +lws_interpret_incoming_packet(struct lws *wsi, unsigned char **buf, size_t len) +{ + int m; + + lwsl_parser("%s: received %d byte packet\n", __func__, (int)len); +#if 0 + lwsl_hexdump(*buf, len); +#endif + + /* let the rx protocol state machine have as much as it needs */ + + while (len) { + /* + * we were accepting input but now we stopped doing so + */ + if (wsi->rxflow_bitmap) { + lws_rxflow_cache(wsi, *buf, 0, len); + lwsl_parser("%s: cached %ld\n", __func__, (long)len); + return 1; + } + + if (wsi->u.ws.rx_draining_ext) { + m = lws_rx_sm(wsi, 0); + if (m < 0) + return -1; + continue; + } + + /* account for what we're using in rxflow buffer */ + if (wsi->rxflow_buffer) { + wsi->rxflow_pos++; + if (wsi->rxflow_pos > wsi->rxflow_len) { + lwsl_err("bumped rxflow buffer too far (%d / %d)", wsi->rxflow_pos, wsi->rxflow_len); + assert(0); + } + } + + /* consume payload bytes efficiently */ + if (wsi->lws_rx_parse_state == + LWS_RXPS_PAYLOAD_UNTIL_LENGTH_EXHAUSTED) { + m = lws_payload_until_length_exhausted(wsi, buf, &len); + if (wsi->rxflow_buffer) + wsi->rxflow_pos += m; + } + + if (wsi->rxflow_buffer && wsi->rxflow_pos == wsi->rxflow_len) { + lwsl_debug("%s: %p flow buf: drained\n", __func__, wsi); + lws_free_set_NULL(wsi->rxflow_buffer); + /* having drained the rxflow buffer, can rearm POLLIN */ +#ifdef LWS_NO_SERVER + m = +#endif + _lws_rx_flow_control(wsi); + /* m ignored, needed for NO_SERVER case */ + } + + /* process the byte */ + m = lws_rx_sm(wsi, *(*buf)++); + if (m < 0) + return -1; + len--; + } + + lwsl_parser("%s: exit with %d unused\n", __func__, (int)len); + + return 0; +} + +LWS_VISIBLE void +lws_server_get_canonical_hostname(struct lws_context *context, + struct lws_context_creation_info *info) +{ + if (lws_check_opt(info->options, + LWS_SERVER_OPTION_SKIP_SERVER_CANONICAL_NAME)) + return; +#if LWS_POSIX && !defined(LWS_WITH_ESP32) + /* find canonical hostname */ + gethostname((char *)context->canonical_hostname, + sizeof(context->canonical_hostname) - 1); + + lwsl_info(" canonical_hostname = %s\n", context->canonical_hostname); +#else + (void)context; +#endif +} + + +LWS_VISIBLE LWS_EXTERN int +lws_chunked_html_process(struct lws_process_html_args *args, + struct lws_process_html_state *s) +{ + char *sp, buffer[32]; + const char *pc; + int old_len, n; + + /* do replacements */ + sp = args->p; + old_len = args->len; + args->len = 0; + s->start = sp; + while (sp < args->p + old_len) { + + if (args->len + 7 >= args->max_len) { + lwsl_err("Used up interpret padding\n"); + return -1; + } + + if ((!s->pos && *sp == '$') || s->pos) { + int hits = 0, hit = 0; + + if (!s->pos) + s->start = sp; + s->swallow[s->pos++] = *sp; + if (s->pos == sizeof(s->swallow) - 1) + goto skip; + for (n = 0; n < s->count_vars; n++) + if (!strncmp(s->swallow, s->vars[n], s->pos)) { + hits++; + hit = n; + } + if (!hits) { +skip: + s->swallow[s->pos] = '\0'; + memcpy(s->start, s->swallow, s->pos); + args->len++; + s->pos = 0; + sp = s->start + 1; + continue; + } + if (hits == 1 && s->pos == strlen(s->vars[hit])) { + pc = s->replace(s->data, hit); + if (!pc) + pc = "NULL"; + n = strlen(pc); + s->swallow[s->pos] = '\0'; + if (n != s->pos) { + memmove(s->start + n, + s->start + s->pos, + old_len - (sp - args->p)); + old_len += (n - s->pos) + 1; + } + memcpy(s->start, pc, n); + args->len++; + sp = s->start + 1; + + s->pos = 0; + } + sp++; + continue; + } + + args->len++; + sp++; + } + + /* no space left for final chunk trailer */ + if (args->final && args->len + 7 >= args->max_len) + return -1; + + n = sprintf(buffer, "%X\x0d\x0a", args->len); + + args->p -= n; + memcpy(args->p, buffer, n); + args->len += n; + + if (args->final) { + sp = args->p + args->len; + *sp++ = '\x0d'; + *sp++ = '\x0a'; + *sp++ = '0'; + *sp++ = '\x0d'; + *sp++ = '\x0a'; + *sp++ = '\x0d'; + *sp++ = '\x0a'; + args->len += 7; + } else { + sp = args->p + args->len; + *sp++ = '\x0d'; + *sp++ = '\x0a'; + args->len += 2; + } + + return 0; +} diff --git a/thirdparty/lws/server/ssl-server.c b/thirdparty/lws/server/ssl-server.c new file mode 100644 index 0000000000..a9516f2239 --- /dev/null +++ b/thirdparty/lws/server/ssl-server.c @@ -0,0 +1,477 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +extern int openssl_websocket_private_data_index, + openssl_SSL_CTX_private_data_index; + +extern void +lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info); + +#if !defined(LWS_WITH_MBEDTLS) +static int +OpenSSL_verify_callback(int preverify_ok, X509_STORE_CTX *x509_ctx) +{ + SSL *ssl; + int n; + struct lws *wsi; + + ssl = X509_STORE_CTX_get_ex_data(x509_ctx, + SSL_get_ex_data_X509_STORE_CTX_idx()); + + /* + * !!! nasty openssl requires the index to come as a library-scope + * static + */ + wsi = SSL_get_ex_data(ssl, openssl_websocket_private_data_index); + + n = wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_OPENSSL_PERFORM_CLIENT_CERT_VERIFICATION, + x509_ctx, ssl, preverify_ok); + + /* convert return code from 0 = OK to 1 = OK */ + return !n; +} +#endif + +static int +lws_context_ssl_init_ecdh(struct lws_vhost *vhost) +{ +#ifdef LWS_SSL_SERVER_WITH_ECDH_CERT + EC_KEY *EC_key = NULL; + EVP_PKEY *pkey; + int KeyType; + X509 *x; + + if (!lws_check_opt(vhost->context->options, LWS_SERVER_OPTION_SSL_ECDH)) + return 0; + + lwsl_notice(" Using ECDH certificate support\n"); + + /* Get X509 certificate from ssl context */ + x = sk_X509_value(vhost->ssl_ctx->extra_certs, 0); + if (!x) { + lwsl_err("%s: x is NULL\n", __func__); + return 1; + } + /* Get the public key from certificate */ + pkey = X509_get_pubkey(x); + if (!pkey) { + lwsl_err("%s: pkey is NULL\n", __func__); + + return 1; + } + /* Get the key type */ + KeyType = EVP_PKEY_type(pkey->type); + + if (EVP_PKEY_EC != KeyType) { + lwsl_notice("Key type is not EC\n"); + return 0; + } + /* Get the key */ + EC_key = EVP_PKEY_get1_EC_KEY(pkey); + /* Set ECDH parameter */ + if (!EC_key) { + lwsl_err("%s: ECDH key is NULL \n", __func__); + return 1; + } + SSL_CTX_set_tmp_ecdh(vhost->ssl_ctx, EC_key); + EC_KEY_free(EC_key); +#endif + return 0; +} + +static int +lws_context_ssl_init_ecdh_curve(struct lws_context_creation_info *info, + struct lws_vhost *vhost) +{ +#if defined(LWS_HAVE_OPENSSL_ECDH_H) && !defined(LWS_WITH_MBEDTLS) + EC_KEY *ecdh; + int ecdh_nid; + const char *ecdh_curve = "prime256v1"; + + if (info->ecdh_curve) + ecdh_curve = info->ecdh_curve; + + ecdh_nid = OBJ_sn2nid(ecdh_curve); + if (NID_undef == ecdh_nid) { + lwsl_err("SSL: Unknown curve name '%s'", ecdh_curve); + return 1; + } + + ecdh = EC_KEY_new_by_curve_name(ecdh_nid); + if (NULL == ecdh) { + lwsl_err("SSL: Unable to create curve '%s'", ecdh_curve); + return 1; + } + SSL_CTX_set_tmp_ecdh(vhost->ssl_ctx, ecdh); + EC_KEY_free(ecdh); + + SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_SINGLE_ECDH_USE); + + lwsl_notice(" SSL ECDH curve '%s'\n", ecdh_curve); +#else +#if !defined(LWS_WITH_MBEDTLS) + lwsl_notice(" OpenSSL doesn't support ECDH\n"); +#endif +#endif + return 0; +} + +#if !defined(LWS_WITH_MBEDTLS) && defined(SSL_TLSEXT_ERR_NOACK) && !defined(OPENSSL_NO_TLSEXT) +static int +lws_ssl_server_name_cb(SSL *ssl, int *ad, void *arg) +{ + struct lws_context *context = (struct lws_context *)arg; + struct lws_vhost *vhost, *vh; + const char *servername; + + if (!ssl) + return SSL_TLSEXT_ERR_NOACK; + + /* + * We can only get ssl accepted connections by using a vhost's ssl_ctx + * find out which listening one took us and only match vhosts on the + * same port. + */ + vh = context->vhost_list; + while (vh) { + if (!vh->being_destroyed && vh->ssl_ctx == SSL_get_SSL_CTX(ssl)) + break; + vh = vh->vhost_next; + } + + if (!vh) { + assert(vh); /* can't match the incoming vh? */ + return SSL_TLSEXT_ERR_OK; + } + + servername = SSL_get_servername(ssl, TLSEXT_NAMETYPE_host_name); + if (!servername) { + /* the client doesn't know what hostname it wants */ + lwsl_info("SNI: Unknown ServerName: %s\n", servername); + + return SSL_TLSEXT_ERR_OK; + } + + vhost = lws_select_vhost(context, vh->listen_port, servername); + if (!vhost) { + lwsl_info("SNI: none: %s:%d\n", servername, vh->listen_port); + + return SSL_TLSEXT_ERR_OK; + } + + lwsl_info("SNI: Found: %s:%d\n", servername, vh->listen_port); + + /* select the ssl ctx from the selected vhost for this conn */ + SSL_set_SSL_CTX(ssl, vhost->ssl_ctx); + + return SSL_TLSEXT_ERR_OK; +} +#endif + +LWS_VISIBLE int +lws_context_init_server_ssl(struct lws_context_creation_info *info, + struct lws_vhost *vhost) +{ + struct lws_context *context = vhost->context; + struct lws wsi; + unsigned long error; + int n; + + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) { + vhost->use_ssl = 0; + return 0; + } + + /* + * If he is giving a cert filepath, take it as a sign he wants to use + * it on this vhost. User code can leave the cert filepath NULL and + * set the LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX option itself, in + * which case he's expected to set up the cert himself at + * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which + * provides the vhost SSL_CTX * in the user parameter. + */ + if (info->ssl_cert_filepath) + info->options |= LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; + + if (info->port != CONTEXT_PORT_NO_LISTEN) { + + vhost->use_ssl = lws_check_opt(info->options, + LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX); + + if (vhost->use_ssl && info->ssl_cipher_list) + lwsl_notice(" SSL ciphers: '%s'\n", info->ssl_cipher_list); + + if (vhost->use_ssl) + lwsl_notice(" Using SSL mode\n"); + else + lwsl_notice(" Using non-SSL mode\n"); + } + + /* + * give him a fake wsi with context + vhost set, so he can use + * lws_get_context() in the callback + */ + memset(&wsi, 0, sizeof(wsi)); + wsi.vhost = vhost; + wsi.context = context; + + (void)n; + (void)error; + + /* + * Firefox insists on SSLv23 not SSLv3 + * Konq disables SSLv2 by default now, SSLv23 works + * + * SSLv23_server_method() is the openssl method for "allow all TLS + * versions", compared to e.g. TLSv1_2_server_method() which only allows + * tlsv1.2. Unwanted versions must be disabled using SSL_CTX_set_options() + */ +#if !defined(LWS_WITH_MBEDTLS) + { + SSL_METHOD *method; + + method = (SSL_METHOD *)SSLv23_server_method(); + if (!method) { + error = ERR_get_error(); + lwsl_err("problem creating ssl method %lu: %s\n", + error, ERR_error_string(error, + (char *)context->pt[0].serv_buf)); + return 1; + } + vhost->ssl_ctx = SSL_CTX_new(method); /* create context */ + if (!vhost->ssl_ctx) { + error = ERR_get_error(); + lwsl_err("problem creating ssl context %lu: %s\n", + error, ERR_error_string(error, + (char *)context->pt[0].serv_buf)); + return 1; + } + } +#else + { + const SSL_METHOD *method = TLSv1_2_server_method(); + + vhost->ssl_ctx = SSL_CTX_new(method); /* create context */ + if (!vhost->ssl_ctx) { + lwsl_err("problem creating ssl context\n"); + return 1; + } + + } +#endif +#if !defined(LWS_WITH_MBEDTLS) + + /* associate the lws context with the SSL_CTX */ + + SSL_CTX_set_ex_data(vhost->ssl_ctx, + openssl_SSL_CTX_private_data_index, (char *)vhost->context); + /* Disable SSLv2 and SSLv3 */ + SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_NO_SSLv2 | SSL_OP_NO_SSLv3); +#ifdef SSL_OP_NO_COMPRESSION + SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_NO_COMPRESSION); +#endif + SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_SINGLE_DH_USE); + SSL_CTX_set_options(vhost->ssl_ctx, SSL_OP_CIPHER_SERVER_PREFERENCE); + + if (info->ssl_cipher_list) + SSL_CTX_set_cipher_list(vhost->ssl_ctx, + info->ssl_cipher_list); +#endif + + /* as a server, are we requiring clients to identify themselves? */ + + if (lws_check_opt(info->options, + LWS_SERVER_OPTION_REQUIRE_VALID_OPENSSL_CLIENT_CERT)) { + int verify_options = SSL_VERIFY_PEER; + + if (!lws_check_opt(info->options, + LWS_SERVER_OPTION_PEER_CERT_NOT_REQUIRED)) + verify_options |= SSL_VERIFY_FAIL_IF_NO_PEER_CERT; + +#if !defined(LWS_WITH_MBEDTLS) + SSL_CTX_set_session_id_context(vhost->ssl_ctx, + (unsigned char *)context, sizeof(void *)); + + /* absolutely require the client cert */ + + SSL_CTX_set_verify(vhost->ssl_ctx, + verify_options, OpenSSL_verify_callback); +#endif + } + +#if !defined(LWS_WITH_MBEDTLS) && !defined(OPENSSL_NO_TLSEXT) + SSL_CTX_set_tlsext_servername_callback(vhost->ssl_ctx, + lws_ssl_server_name_cb); + SSL_CTX_set_tlsext_servername_arg(vhost->ssl_ctx, context); +#endif + + /* + * give user code a chance to load certs into the server + * allowing it to verify incoming client certs + */ +#if !defined(LWS_WITH_MBEDTLS) + if (info->ssl_ca_filepath && + !SSL_CTX_load_verify_locations(vhost->ssl_ctx, + info->ssl_ca_filepath, NULL)) { + lwsl_err("%s: SSL_CTX_load_verify_locations unhappy\n", __func__); + } +#endif + if (vhost->use_ssl) { + if (lws_context_ssl_init_ecdh_curve(info, vhost)) + return -1; + + vhost->protocols[0].callback(&wsi, + LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, + vhost->ssl_ctx, NULL, 0); + } + + if (lws_check_opt(info->options, LWS_SERVER_OPTION_ALLOW_NON_SSL_ON_SSL_PORT)) + /* Normally SSL listener rejects non-ssl, optionally allow */ + vhost->allow_non_ssl_on_ssl_port = 1; + + if (info->ssl_options_set) + SSL_CTX_set_options(vhost->ssl_ctx, info->ssl_options_set); + +/* SSL_clear_options introduced in 0.9.8m */ +#if !defined(LWS_WITH_MBEDTLS) +#if (OPENSSL_VERSION_NUMBER >= 0x009080df) && !defined(USE_WOLFSSL) + if (info->ssl_options_clear) + SSL_CTX_clear_options(vhost->ssl_ctx, info->ssl_options_clear); +#endif +#endif + + lwsl_info(" SSL options 0x%lX\n", SSL_CTX_get_options(vhost->ssl_ctx)); + + if (vhost->use_ssl && info->ssl_cert_filepath) { + /* + * The user code can choose to either pass the cert and + * key filepaths using the info members like this, or it can + * leave them NULL; force the vhost SSL_CTX init using the info + * options flag LWS_SERVER_OPTION_CREATE_VHOST_SSL_CTX; and + * set up the cert himself using the user callback + * LWS_CALLBACK_OPENSSL_LOAD_EXTRA_SERVER_VERIFY_CERTS, which + * happened just above and has the vhost SSL_CTX * in the user + * parameter. + */ +#if !defined(LWS_WITH_MBEDTLS) + /* set the local certificate from CertFile */ + n = SSL_CTX_use_certificate_chain_file(vhost->ssl_ctx, + info->ssl_cert_filepath); + if (n != 1) { + error = ERR_get_error(); + lwsl_err("problem getting cert '%s' %lu: %s\n", + info->ssl_cert_filepath, + error, + ERR_error_string(error, + (char *)context->pt[0].serv_buf)); + return 1; + } + lws_ssl_bind_passphrase(vhost->ssl_ctx, info); +#else + uint8_t *p; + lws_filepos_t flen; + int err; + + if (alloc_pem_to_der_file(vhost->context, info->ssl_cert_filepath, &p, + &flen)) { + lwsl_err("couldn't find cert file %s\n", + info->ssl_cert_filepath); + + return 1; + } + err = SSL_CTX_use_certificate_ASN1(vhost->ssl_ctx, flen, p); + if (!err) { + lwsl_err("Problem loading cert\n"); + return 1; + } +#if !defined(LWS_WITH_ESP32) + free(p); + p = NULL; +#endif + + if (info->ssl_private_key_filepath) { + if (alloc_pem_to_der_file(vhost->context, + info->ssl_private_key_filepath, &p, &flen)) { + lwsl_err("couldn't find cert file %s\n", + info->ssl_cert_filepath); + + return 1; + } + err = SSL_CTX_use_PrivateKey_ASN1(0, vhost->ssl_ctx, p, flen); + if (!err) { + lwsl_err("Problem loading key\n"); + + return 1; + } + } + +#if !defined(LWS_WITH_ESP32) + free(p); + p = NULL; +#endif +#endif + if (info->ssl_private_key_filepath != NULL) { +#if !defined(LWS_WITH_MBEDTLS) + /* set the private key from KeyFile */ + if (SSL_CTX_use_PrivateKey_file(vhost->ssl_ctx, + info->ssl_private_key_filepath, + SSL_FILETYPE_PEM) != 1) { + error = ERR_get_error(); + lwsl_err("ssl problem getting key '%s' %lu: %s\n", + info->ssl_private_key_filepath, error, + ERR_error_string(error, + (char *)context->pt[0].serv_buf)); + return 1; + } +#endif + } else + if (vhost->protocols[0].callback(&wsi, + LWS_CALLBACK_OPENSSL_CONTEXT_REQUIRES_PRIVATE_KEY, + vhost->ssl_ctx, NULL, 0)) { + lwsl_err("ssl private key not set\n"); + + return 1; + } +#if !defined(LWS_WITH_MBEDTLS) + /* verify private key */ + if (!SSL_CTX_check_private_key(vhost->ssl_ctx)) { + lwsl_err("Private SSL key doesn't match cert\n"); + return 1; + } +#endif + } + if (vhost->use_ssl) { + if (lws_context_ssl_init_ecdh(vhost)) + return 1; + + /* + * SSL is happy and has a cert it's content with + * If we're supporting HTTP2, initialize that + */ + lws_context_init_http2_ssl(vhost); + } + + return 0; +} + diff --git a/thirdparty/lws/service.c b/thirdparty/lws/service.c new file mode 100644 index 0000000000..6748e30bd4 --- /dev/null +++ b/thirdparty/lws/service.c @@ -0,0 +1,1699 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +static int +lws_calllback_as_writeable(struct lws *wsi) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + int n; + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_WRITEABLE_CB, 1); +#if defined(LWS_WITH_STATS) + if (wsi->active_writable_req_us) { + uint64_t ul = time_in_microseconds() - + wsi->active_writable_req_us; + + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_WRITABLE_DELAY, ul); + lws_stats_atomic_max(wsi->context, pt, + LWSSTATS_MS_WORST_WRITABLE_DELAY, ul); + wsi->active_writable_req_us = 0; + } +#endif + + switch (wsi->mode) { + case LWSCM_RAW: + n = LWS_CALLBACK_RAW_WRITEABLE; + break; + case LWSCM_RAW_FILEDESC: + n = LWS_CALLBACK_RAW_WRITEABLE_FILE; + break; + case LWSCM_WS_CLIENT: + n = LWS_CALLBACK_CLIENT_WRITEABLE; + break; + case LWSCM_WSCL_ISSUE_HTTP_BODY: + n = LWS_CALLBACK_CLIENT_HTTP_WRITEABLE; + break; + case LWSCM_WS_SERVING: + n = LWS_CALLBACK_SERVER_WRITEABLE; + break; + default: + n = LWS_CALLBACK_HTTP_WRITEABLE; + break; + } + + return user_callback_handle_rxflow(wsi->protocol->callback, + wsi, (enum lws_callback_reasons) n, + wsi->user_space, NULL, 0); +} + +LWS_VISIBLE int +lws_handle_POLLOUT_event(struct lws *wsi, struct lws_pollfd *pollfd) +{ + int write_type = LWS_WRITE_PONG; + struct lws_tokens eff_buf; +#ifdef LWS_WITH_HTTP2 + struct lws **wsi2, *wsi2a; +#endif + int ret, m, n; + + wsi->leave_pollout_active = 0; + wsi->handling_pollout = 1; + /* + * if another thread wants POLLOUT on us, from here on while + * handling_pollout is set, he will only set leave_pollout_active. + * If we are going to disable POLLOUT, we will check that first. + */ + + /* + * user callback is lowest priority to get these notifications + * actually, since other pending things cannot be disordered + */ + + /* Priority 1: pending truncated sends are incomplete ws fragments + * If anything else sent first the protocol would be + * corrupted. + */ + if (wsi->trunc_len) { + //lwsl_notice("%s: completing partial\n", __func__); + if (lws_issue_raw(wsi, wsi->trunc_alloc + wsi->trunc_offset, + wsi->trunc_len) < 0) { + lwsl_info("%s signalling to close\n", __func__); + goto bail_die; + } + /* leave POLLOUT active either way */ + goto bail_ok; + } else + if (wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE) { + wsi->socket_is_permanently_unusable = 1; + goto bail_die; /* retry closing now */ + } + + if (wsi->mode == LWSCM_WSCL_ISSUE_HTTP_BODY) + goto user_service; + +#ifdef LWS_WITH_HTTP2 + /* + * Priority 2: protocol packets + */ + if (wsi->upgraded_to_http2 && wsi->u.h2.h2n->pps) { + lwsl_info("servicing pps\n"); + if (lws_h2_do_pps_send(wsi)) { + wsi->socket_is_permanently_unusable = 1; + goto bail_die; + } + if (wsi->u.h2.h2n->pps) + goto bail_ok; + + /* we can resume whatever we were doing */ + lws_rx_flow_control(wsi, LWS_RXFLOW_REASON_APPLIES_ENABLE | + LWS_RXFLOW_REASON_H2_PPS_PENDING); + + goto bail_ok; /* leave POLLOUT active */ + } +#endif + +#ifdef LWS_WITH_CGI + if (wsi->cgi) { + /* also one shot */ + if (pollfd) + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + return 1; + } + goto user_service_go_again; + } +#endif + + /* Priority 3: pending control packets (pong or close) + * + * 3a: close notification packet requested from close api + */ + + if (wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION) { + lwsl_debug("sending close packet\n"); + wsi->waiting_to_send_close_frame = 0; + n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[LWS_PRE], + wsi->u.ws.close_in_ping_buffer_len, + LWS_WRITE_CLOSE); + if (n >= 0) { + wsi->state = LWSS_AWAITING_CLOSE_ACK; + lws_set_timeout(wsi, PENDING_TIMEOUT_CLOSE_ACK, 1); + lwsl_debug("sent close indication, awaiting ack\n"); + + goto bail_ok; + } + + goto bail_die; + } + + /* else, the send failed and we should just hang up */ + + if ((wsi->state == LWSS_ESTABLISHED && + wsi->u.ws.ping_pending_flag) || + (wsi->state == LWSS_RETURNED_CLOSE_ALREADY && + wsi->u.ws.payload_is_close)) { + + if (wsi->u.ws.payload_is_close) + write_type = LWS_WRITE_CLOSE; + + n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[LWS_PRE], + wsi->u.ws.ping_payload_len, write_type); + if (n < 0) + goto bail_die; + + /* well he is sent, mark him done */ + wsi->u.ws.ping_pending_flag = 0; + if (wsi->u.ws.payload_is_close) + /* oh... a close frame was it... then we are done */ + goto bail_die; + + /* otherwise for PING, leave POLLOUT active either way */ + goto bail_ok; + } + + if (wsi->state == LWSS_ESTABLISHED && + !wsi->socket_is_permanently_unusable && + wsi->u.ws.send_check_ping) { + + lwsl_info("issuing ping on wsi %p\n", wsi); + wsi->u.ws.send_check_ping = 0; + n = lws_write(wsi, &wsi->u.ws.ping_payload_buf[LWS_PRE], + 0, LWS_WRITE_PING); + if (n < 0) + goto bail_die; + + /* + * we apparently were able to send the PING in a reasonable time + * now reset the clock on our peer to be able to send the + * PONG in a reasonable time. + */ + + lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_GET_PONG, + wsi->context->timeout_secs); + + goto bail_ok; + } + + /* Priority 4: if we are closing, not allowed to send more data frags + * which means user callback or tx ext flush banned now + */ + if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY) + goto user_service; + + /* Priority 5: Tx path extension with more to send + * + * These are handled as new fragments each time around + * So while we must block new writeable callback to enforce + * payload ordering, but since they are always complete + * fragments control packets can interleave OK. + */ + if (wsi->state == LWSS_ESTABLISHED && wsi->u.ws.tx_draining_ext) { + lwsl_ext("SERVICING TX EXT DRAINING\n"); + if (lws_write(wsi, NULL, 0, LWS_WRITE_CONTINUATION) < 0) + goto bail_die; + /* leave POLLOUT active */ + goto bail_ok; + } + + /* Priority 6: user can get the callback + */ + m = lws_ext_cb_active(wsi, LWS_EXT_CB_IS_WRITEABLE, NULL, 0); + if (m) + goto bail_die; +#ifndef LWS_NO_EXTENSIONS + if (!wsi->extension_data_pending) + goto user_service; +#endif + /* + * check in on the active extensions, see if they + * had pending stuff to spill... they need to get the + * first look-in otherwise sequence will be disordered + * + * NULL, zero-length eff_buf means just spill pending + */ + + ret = 1; + if (wsi->mode == LWSCM_RAW || wsi->mode == LWSCM_RAW_FILEDESC) + ret = 0; + + while (ret == 1) { + + /* default to nobody has more to spill */ + + ret = 0; + eff_buf.token = NULL; + eff_buf.token_len = 0; + + /* give every extension a chance to spill */ + + m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_TX_PRESEND, + &eff_buf, 0); + if (m < 0) { + lwsl_err("ext reports fatal error\n"); + goto bail_die; + } + if (m) + /* + * at least one extension told us he has more + * to spill, so we will go around again after + */ + ret = 1; + + /* assuming they gave us something to send, send it */ + + if (eff_buf.token_len) { + n = lws_issue_raw(wsi, (unsigned char *)eff_buf.token, + eff_buf.token_len); + if (n < 0) { + lwsl_info("closing from POLLOUT spill\n"); + goto bail_die; + } + /* + * Keep amount spilled small to minimize chance of this + */ + if (n != eff_buf.token_len) { + lwsl_err("Unable to spill ext %d vs %d\n", + eff_buf.token_len, n); + goto bail_die; + } + } else + continue; + + /* no extension has more to spill */ + + if (!ret) + continue; + + /* + * There's more to spill from an extension, but we just sent + * something... did that leave the pipe choked? + */ + + if (!lws_send_pipe_choked(wsi)) + /* no we could add more */ + continue; + + lwsl_info("choked in POLLOUT service\n"); + + /* + * Yes, he's choked. Leave the POLLOUT masked on so we will + * come back here when he is unchoked. Don't call the user + * callback to enforce ordering of spilling, he'll get called + * when we come back here and there's nothing more to spill. + */ + + goto bail_ok; + } +#ifndef LWS_NO_EXTENSIONS + wsi->extension_data_pending = 0; +#endif +user_service: + /* one shot */ + + if (wsi->parent_carries_io) { + wsi->handling_pollout = 0; + wsi->leave_pollout_active = 0; + + return lws_calllback_as_writeable(wsi); + } + + if (pollfd) { + int eff = wsi->leave_pollout_active; + + if (!eff) + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + goto bail_die; + } + + wsi->handling_pollout = 0; + + /* cannot get leave_pollout_active set after the above */ + if (!eff && wsi->leave_pollout_active) + /* got set inbetween sampling eff and clearing + * handling_pollout, force POLLOUT on */ + lws_calllback_as_writeable(wsi); + + wsi->leave_pollout_active = 0; + } + + if (wsi->mode != LWSCM_WSCL_ISSUE_HTTP_BODY && + !wsi->hdr_parsing_completed) + goto bail_ok; + + +#ifdef LWS_WITH_CGI +user_service_go_again: +#endif + +#ifdef LWS_WITH_HTTP2 + /* + * we are the 'network wsi' for potentially many muxed child wsi with + * no network connection of their own, who have to use us for all their + * network actions. So we use a round-robin scheme to share out the + * POLLOUT notifications to our children. + * + * But because any child could exhaust the socket's ability to take + * writes, we can only let one child get notified each time. + * + * In addition children may be closed / deleted / added between POLLOUT + * notifications, so we can't hold pointers + */ + + if (wsi->mode != LWSCM_HTTP2_SERVING) { + lwsl_info("%s: non http2\n", __func__); + goto notify; + } + + wsi->u.h2.requested_POLLOUT = 0; + if (!wsi->u.h2.initialized) { + lwsl_info("pollout on uninitialized http2 conn\n"); + goto bail_ok; + } + +// if (SSL_want_read(wsi->ssl) || SSL_want_write(wsi->ssl)) { +// lws_callback_on_writable(wsi); +// goto bail_ok; +// } + + lwsl_info("%s: %p: children waiting for POLLOUT service:\n", __func__, wsi); + wsi2a = wsi->u.h2.child_list; + while (wsi2a) { + if (wsi2a->u.h2.requested_POLLOUT) + lwsl_debug(" * %p\n", wsi2a); + else + lwsl_debug(" %p\n", wsi2a); + + wsi2a = wsi2a->u.h2.sibling_list; + } + + wsi2 = &wsi->u.h2.child_list; + if (!*wsi2) + goto bail_ok; + + do { + struct lws *w, **wa; + + wa = &(*wsi2)->u.h2.sibling_list; + if (!(*wsi2)->u.h2.requested_POLLOUT) { + lwsl_debug(" child %p doesn't want POLLOUT\n", *wsi2); + goto next_child; + } + + /* + * we're going to do writable callback for this child. + * move him to be the last child + */ + + lwsl_debug("servicing child %p\n", *wsi2); + + w = *wsi2; + while (w) { + if (!w->u.h2.sibling_list) { /* w is the current last */ + lwsl_debug("w=%p, *wsi2 = %p\n", w, *wsi2); + if (w == *wsi2) /* we are already last */ + break; + w->u.h2.sibling_list = *wsi2; /* last points to us as new last */ + *wsi2 = (*wsi2)->u.h2.sibling_list; /* guy pointing to us until now points to our old next */ + w->u.h2.sibling_list->u.h2.sibling_list = NULL; /* we point to nothing because we are last */ + w = w->u.h2.sibling_list; /* w becomes us */ + break; + } + w = w->u.h2.sibling_list; + } + + w->u.h2.requested_POLLOUT = 0; + lwsl_info("%s: child %p (state %d)\n", __func__, (*wsi2), (*wsi2)->state); + + if (w->u.h2.pending_status_body) { + w->u.h2.send_END_STREAM = 1; + n = lws_write(w, + (uint8_t *)w->u.h2.pending_status_body + LWS_PRE, + strlen(w->u.h2.pending_status_body + LWS_PRE), + LWS_WRITE_HTTP_FINAL); + lws_free_set_NULL(w->u.h2.pending_status_body); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS); + wa = &wsi->u.h2.child_list; + goto next_child; + } + + if (w->state == LWSS_HTTP_ISSUING_FILE) { + + w->leave_pollout_active = 0; + + /* >0 == completion, <0 == error + * + * We'll get a LWS_CALLBACK_HTTP_FILE_COMPLETION callback when + * it's done. That's the case even if we just completed the + * send, so wait for that. + */ + n = lws_serve_http_file_fragment(w); + lwsl_debug("lws_serve_http_file_fragment says %d\n", n); + + /* + * We will often hear about out having sent the final + * DATA here... if so close the actual wsi + */ + if (n < 0 || w->u.h2.send_END_STREAM) { + lwsl_debug("Closing POLLOUT child %p\n", w); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS); + wa = &wsi->u.h2.child_list; + goto next_child; + } + if (n > 0) + if (lws_http_transaction_completed(w)) + goto bail_die; + if (!n) { + lws_callback_on_writable(w); + (w)->u.h2.requested_POLLOUT = 1; + } + + goto next_child; + } + + if (lws_calllback_as_writeable(w) || w->u.h2.send_END_STREAM) { + lwsl_debug("Closing POLLOUT child\n"); + lws_close_free_wsi(w, LWS_CLOSE_STATUS_NOSTATUS); + wa = &wsi->u.h2.child_list; + } + +next_child: + wsi2 = wa; + } while (wsi2 && *wsi2 && !lws_send_pipe_choked(wsi)); + + lwsl_info("%s: %p: children waiting for POLLOUT service: %p\n", __func__, wsi, wsi->u.h2.child_list); + wsi2a = wsi->u.h2.child_list; + while (wsi2a) { + if (wsi2a->u.h2.requested_POLLOUT) + lwsl_debug(" * %p\n", wsi2a); + else + lwsl_debug(" %p\n", wsi2a); + + wsi2a = wsi2a->u.h2.sibling_list; + } + + + wsi2a = wsi->u.h2.child_list; + while (wsi2a) { + if (wsi2a->u.h2.requested_POLLOUT) { + lws_change_pollfd(wsi, 0, LWS_POLLOUT); + break; + } + wsi2a = wsi2a->u.h2.sibling_list; + } + + goto bail_ok; + + +notify: +#endif + wsi->leave_pollout_active = 0; + + n = lws_calllback_as_writeable(wsi); + wsi->handling_pollout = 0; + + if (wsi->leave_pollout_active) + lws_change_pollfd(wsi, 0, LWS_POLLOUT); + + return n; + + /* + * since these don't disable the POLLOUT, they are always doing the + * right thing for leave_pollout_active whether it was set or not. + */ + +bail_ok: + wsi->handling_pollout = 0; + wsi->leave_pollout_active = 0; + + return 0; + +bail_die: + wsi->handling_pollout = 0; + wsi->leave_pollout_active = 0; + + return -1; +} + +int +lws_service_timeout_check(struct lws *wsi, unsigned int sec) +{ + struct lws_context_per_thread *pt = &wsi->context->pt[(int)wsi->tsi]; + int n = 0; + + (void)n; + + /* + * if extensions want in on it (eg, we are a mux parent) + * give them a chance to service child timeouts + */ + if (lws_ext_cb_active(wsi, LWS_EXT_CB_1HZ, NULL, sec) < 0) + return 0; + + if (!wsi->pending_timeout) + return 0; + + /* + * if we went beyond the allowed time, kill the + * connection + */ + if ((time_t)sec > wsi->pending_timeout_limit) { + + if (wsi->desc.sockfd != LWS_SOCK_INVALID && + wsi->position_in_fds_table >= 0) + n = pt->fds[wsi->position_in_fds_table].events; + + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_TIMEOUTS, 1); + + /* no need to log normal idle keepalive timeout */ + if (wsi->pending_timeout != PENDING_TIMEOUT_HTTP_KEEPALIVE_IDLE) + lwsl_info("wsi %p: TIMEDOUT WAITING on %d " + "(did hdr %d, ah %p, wl %d, pfd " + "events %d) %llu vs %llu\n", + (void *)wsi, wsi->pending_timeout, + wsi->hdr_parsing_completed, wsi->u.hdr.ah, + pt->ah_wait_list_length, n, + (unsigned long long)sec, + (unsigned long long)wsi->pending_timeout_limit); + + /* + * Since he failed a timeout, he already had a chance to do + * something and was unable to... that includes situations like + * half closed connections. So process this "failed timeout" + * close as a violent death and don't try to do protocol + * cleanup like flush partials. + */ + wsi->socket_is_permanently_unusable = 1; + if (wsi->mode == LWSCM_WSCL_WAITING_SSL) + wsi->vhost->protocols[0].callback(wsi, + LWS_CALLBACK_CLIENT_CONNECTION_ERROR, + wsi->user_space, + (void *)"Timed out waiting SSL", 21); + + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + + return 1; + } + + return 0; +} + +int lws_rxflow_cache(struct lws *wsi, unsigned char *buf, int n, int len) +{ +#if defined(LWS_WITH_HTTP2) + if (wsi->upgraded_to_http2) { + struct lws_h2_netconn *h2n = wsi->u.h2.h2n; + + assert(h2n->rx_scratch); + buf += n; + len -= n; + assert ((char *)buf >= (char *)h2n->rx_scratch && + (char *)&buf[len] <= (char *)&h2n->rx_scratch[LWS_H2_RX_SCRATCH_SIZE]); + + h2n->rx_scratch_pos = ((char *)buf - (char *)h2n->rx_scratch); + h2n->rx_scratch_len = len; + + lwsl_info("%s: %p: pausing h2 rx_scratch\n", __func__, wsi); + + return 0; + } +#endif + /* his RX is flowcontrolled, don't send remaining now */ + if (wsi->rxflow_buffer) { + if (buf >= wsi->rxflow_buffer && + &buf[len - 1] < &wsi->rxflow_buffer[wsi->rxflow_len]) { + /* rxflow while we were spilling prev rxflow */ + lwsl_info("%s: staying in rxflow buf\n", __func__); + return 1; + } else { + lwsl_err("%s: conflicting rxflow buf, " + "current %p len %d, new %p len %d\n", __func__, + wsi->rxflow_buffer, wsi->rxflow_len, buf, len); + assert(0); + return 1; + } + } + + /* a new rxflow, buffer it and warn caller */ + lwsl_info("%s: new rxflow input buffer len %d\n", __func__, len - n); + wsi->rxflow_buffer = lws_malloc(len - n, "rxflow buf"); + if (!wsi->rxflow_buffer) + return -1; + + wsi->rxflow_len = len - n; + wsi->rxflow_pos = 0; + memcpy(wsi->rxflow_buffer, buf + n, len - n); + + return 0; +} + +/* this is used by the platform service code to stop us waiting for network + * activity in poll() when we have something that already needs service + */ + +LWS_VISIBLE LWS_EXTERN int +lws_service_adjust_timeout(struct lws_context *context, int timeout_ms, int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + struct allocated_headers *ah; + + /* Figure out if we really want to wait in poll() + * We only need to wait if really nothing already to do and we have + * to wait for something from network + */ + + /* 1) if we know we are draining rx ext, do not wait in poll */ + if (pt->rx_draining_ext_list) + return 0; + +#ifdef LWS_OPENSSL_SUPPORT + /* 2) if we know we have non-network pending data, do not wait in poll */ + if (lws_ssl_anybody_has_buffered_read_tsi(context, tsi)) { + lwsl_info("ssl buffered read\n"); + return 0; + } +#endif + + /* 3) if any ah has pending rx, do not wait in poll */ + ah = pt->ah_list; + while (ah) { + if (ah->rxpos != ah->rxlen) { + if (!ah->wsi) { + assert(0); + } + return 0; + } + ah = ah->next; + } + + return timeout_ms; +} + +/* + * guys that need POLLIN service again without waiting for network action + * can force POLLIN here if not flowcontrolled, so they will get service. + * + * Return nonzero if anybody got their POLLIN faked + */ +int +lws_service_flag_pending(struct lws_context *context, int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + struct allocated_headers *ah; +#ifdef LWS_OPENSSL_SUPPORT + struct lws *wsi_next; +#endif + struct lws *wsi; + int forced = 0; + + /* POLLIN faking */ + + /* + * 1) For all guys with already-available ext data to drain, if they are + * not flowcontrolled, fake their POLLIN status + */ + wsi = pt->rx_draining_ext_list; + while (wsi) { + pt->fds[wsi->position_in_fds_table].revents |= + pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; + if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) { + forced = 1; + break; + } + wsi = wsi->u.ws.rx_draining_ext_list; + } + +#ifdef LWS_OPENSSL_SUPPORT + /* + * 2) For all guys with buffered SSL read data already saved up, if they + * are not flowcontrolled, fake their POLLIN status so they'll get + * service to use up the buffered incoming data, even though their + * network socket may have nothing + */ + wsi = pt->pending_read_list; + while (wsi) { + wsi_next = wsi->pending_read_list_next; + pt->fds[wsi->position_in_fds_table].revents |= + pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; + if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) { + forced = 1; + /* + * he's going to get serviced now, take him off the + * list of guys with buffered SSL. If he still has some + * at the end of the service, he'll get put back on the + * list then. + */ + lws_ssl_remove_wsi_from_buffered_list(wsi); + } + + wsi = wsi_next; + } +#endif + /* + * 3) For any wsi who have an ah with pending RX who did not + * complete their current headers, and are not flowcontrolled, + * fake their POLLIN status so they will be able to drain the + * rx buffered in the ah + */ + ah = pt->ah_list; + while (ah) { + if (ah->rxpos != ah->rxlen && !ah->wsi->hdr_parsing_completed) { + pt->fds[ah->wsi->position_in_fds_table].revents |= + pt->fds[ah->wsi->position_in_fds_table].events & + LWS_POLLIN; + if (pt->fds[ah->wsi->position_in_fds_table].revents & + LWS_POLLIN) { + forced = 1; + break; + } + } + ah = ah->next; + } + + return forced; +} + +#ifndef LWS_NO_CLIENT + +LWS_VISIBLE int +lws_http_client_read(struct lws *wsi, char **buf, int *len) +{ + int rlen, n; + + rlen = lws_ssl_capable_read(wsi, (unsigned char *)*buf, *len); + *len = 0; + + /* allow the source to signal he has data again next time */ + lws_change_pollfd(wsi, 0, LWS_POLLIN); + + if (rlen == LWS_SSL_CAPABLE_ERROR) { + lwsl_notice("%s: SSL capable error\n", __func__); + return -1; + } + + if (rlen == 0) + return -1; + + if (rlen < 0) + return 0; + + *len = rlen; + wsi->client_rx_avail = 0; + + /* + * server may insist on transfer-encoding: chunked, + * so http client must deal with it + */ +spin_chunks: + while (wsi->chunked && (wsi->chunk_parser != ELCP_CONTENT) && *len) { + switch (wsi->chunk_parser) { + case ELCP_HEX: + if ((*buf)[0] == '\x0d') { + wsi->chunk_parser = ELCP_CR; + break; + } + n = char_to_hex((*buf)[0]); + if (n < 0) { + lwsl_debug("chunking failure\n"); + return -1; + } + wsi->chunk_remaining <<= 4; + wsi->chunk_remaining |= n; + break; + case ELCP_CR: + if ((*buf)[0] != '\x0a') { + lwsl_debug("chunking failure\n"); + return -1; + } + wsi->chunk_parser = ELCP_CONTENT; + lwsl_info("chunk %d\n", wsi->chunk_remaining); + if (wsi->chunk_remaining) + break; + lwsl_info("final chunk\n"); + goto completed; + + case ELCP_CONTENT: + break; + + case ELCP_POST_CR: + if ((*buf)[0] != '\x0d') { + lwsl_debug("chunking failure\n"); + + return -1; + } + + wsi->chunk_parser = ELCP_POST_LF; + break; + + case ELCP_POST_LF: + if ((*buf)[0] != '\x0a') + return -1; + + wsi->chunk_parser = ELCP_HEX; + wsi->chunk_remaining = 0; + break; + } + (*buf)++; + (*len)--; + } + + if (wsi->chunked && !wsi->chunk_remaining) + return 0; + + if (wsi->u.http.rx_content_remain && + wsi->u.http.rx_content_remain < *len) + n = (int)wsi->u.http.rx_content_remain; + else + n = *len; + + if (wsi->chunked && wsi->chunk_remaining && + wsi->chunk_remaining < n) + n = wsi->chunk_remaining; + +#ifdef LWS_WITH_HTTP_PROXY + /* hubbub */ + if (wsi->perform_rewrite) + lws_rewrite_parse(wsi->rw, (unsigned char *)*buf, n); + else +#endif + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ, + wsi->user_space, *buf, n)) { + lwsl_debug("%s: LWS_CALLBACK_RECEIVE_CLIENT_HTTP_READ returned -1\n", __func__); + + return -1; + } + + if (wsi->chunked && wsi->chunk_remaining) { + (*buf) += n; + wsi->chunk_remaining -= n; + *len -= n; + } + + if (wsi->chunked && !wsi->chunk_remaining) + wsi->chunk_parser = ELCP_POST_CR; + + if (wsi->chunked && *len) + goto spin_chunks; + + if (wsi->chunked) + return 0; + + /* if we know the content length, decrement the content remaining */ + if (wsi->u.http.rx_content_length > 0) + wsi->u.http.rx_content_remain -= n; + + if (wsi->u.http.rx_content_remain || !wsi->u.http.rx_content_length) + return 0; + +completed: + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_COMPLETED_CLIENT_HTTP, + wsi->user_space, NULL, 0)) { + lwsl_debug("Completed call returned -1\n"); + return -1; + } + + if (lws_http_transaction_completed_client(wsi)) { + lwsl_notice("%s: transaction completed says -1\n", __func__); + return -1; + } + + return 0; +} +#endif + +static int +lws_is_ws_with_ext(struct lws *wsi) +{ +#if defined(LWS_NO_EXTENSIONS) + return 0; +#else + return wsi->state == LWSS_ESTABLISHED && + !!wsi->count_act_ext; +#endif +} + +LWS_VISIBLE int +lws_service_fd_tsi(struct lws_context *context, struct lws_pollfd *pollfd, int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + lws_sockfd_type our_fd = 0, tmp_fd; + struct allocated_headers *ah; + struct lws_tokens eff_buf; + unsigned int pending = 0; + struct lws *wsi, *wsi1; + char draining_flow = 0; + int timed_out = 0; + time_t now; + int n = 0, m; + int more; + + if (!context->protocol_init_done) + lws_protocol_init(context); + + time(&now); + + /* + * handle case that system time was uninitialized when lws started + * at boot, and got initialized a little later + */ + if (context->time_up < 1464083026 && now > 1464083026) + context->time_up = now; + + /* TODO: if using libev, we should probably use timeout watchers... */ + if (context->last_timeout_check_s != now) { + context->last_timeout_check_s = now; + +#if defined(LWS_WITH_STATS) + if (!tsi && now - context->last_dump > 10) { + lws_stats_log_dump(context); + context->last_dump = now; + } +#endif + + lws_plat_service_periodic(context); + + lws_check_deferred_free(context, 0); + +#if defined(LWS_WITH_PEER_LIMITS) + lws_peer_cull_peer_wait_list(context); +#endif + + /* retire unused deprecated context */ +#if !defined(LWS_PLAT_OPTEE) && !defined(LWS_WITH_ESP32) +#if LWS_POSIX && !defined(_WIN32) + if (context->deprecated && !context->count_wsi_allocated) { + lwsl_notice("%s: ending deprecated context\n", __func__); + kill(getpid(), SIGINT); + return 0; + } +#endif +#endif + /* global timeout check once per second */ + + if (pollfd) + our_fd = pollfd->fd; + + /* + * Phase 1: check every wsi on the timeout check list + */ + + wsi = context->pt[tsi].timeout_list; + while (wsi) { + /* we have to take copies, because he may be deleted */ + wsi1 = wsi->timeout_list; + tmp_fd = wsi->desc.sockfd; + if (lws_service_timeout_check(wsi, (unsigned int)now)) { + /* he did time out... */ + if (tmp_fd == our_fd) + /* it was the guy we came to service! */ + timed_out = 1; + /* he's gone, no need to mark as handled */ + } + wsi = wsi1; + } + + /* + * Phase 2: double-check active ah timeouts independent of wsi + * timeout status + */ + + ah = pt->ah_list; + while (ah) { + int len; + char buf[256]; + const unsigned char *c; + + if (!ah->in_use || !ah->wsi || !ah->assigned || + (ah->wsi->vhost && now - ah->assigned < + ah->wsi->vhost->timeout_secs_ah_idle + 60)) { + ah = ah->next; + continue; + } + + /* + * a single ah session somehow got held for + * an unreasonable amount of time. + * + * Dump info on the connection... + */ + wsi = ah->wsi; + buf[0] = '\0'; + lws_get_peer_simple(wsi, buf, sizeof(buf)); + lwsl_notice("ah excessive hold: wsi %p\n" + " peer address: %s\n" + " ah rxpos %u, rxlen %u, pos %u\n", + wsi, buf, ah->rxpos, ah->rxlen, + ah->pos); + buf[0] = '\0'; + m = 0; + do { + c = lws_token_to_string(m); + if (!c) + break; + + len = lws_hdr_total_length(wsi, m); + if (!len || len > sizeof(buf) - 1) { + m++; + continue; + } + + if (lws_hdr_copy(wsi, buf, + sizeof buf, m) > 0) { + buf[sizeof(buf) - 1] = '\0'; + + lwsl_notice(" %s = %s\n", + (const char *)c, buf); + } + m++; + } while (1); + + /* ... and then drop the connection */ + + if (wsi->desc.sockfd == our_fd) + /* it was the guy we came to service! */ + timed_out = 1; + + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + + ah = ah->next; + } + +#ifdef LWS_WITH_CGI + /* + * Phase 3: handle cgi timeouts + */ + lws_cgi_kill_terminated(pt); +#endif +#if 0 + { + char s[300], *p = s; + + for (n = 0; n < context->count_threads; n++) + p += sprintf(p, " %7lu (%5d), ", + context->pt[n].count_conns, + context->pt[n].fds_count); + + lwsl_notice("load: %s\n", s); + } +#endif + } + + /* + * at intervals, check for ws connections needing ping-pong checks + */ + + if (context->ws_ping_pong_interval && + context->last_ws_ping_pong_check_s < now + 10) { + struct lws_vhost *vh = context->vhost_list; + context->last_ws_ping_pong_check_s = now; + + while (vh) { + for (n = 0; n < vh->count_protocols; n++) { + wsi = vh->same_vh_protocol_list[n]; + + while (wsi) { + if (wsi->state == LWSS_ESTABLISHED && + !wsi->socket_is_permanently_unusable && + !wsi->u.ws.send_check_ping && + wsi->u.ws.time_next_ping_check && + wsi->u.ws.time_next_ping_check < now) { + + lwsl_info("requesting ping-pong on wsi %p\n", wsi); + wsi->u.ws.send_check_ping = 1; + lws_set_timeout(wsi, PENDING_TIMEOUT_WS_PONG_CHECK_SEND_PING, + context->timeout_secs); + lws_callback_on_writable(wsi); + wsi->u.ws.time_next_ping_check = now + + wsi->context->ws_ping_pong_interval; + } + wsi = wsi->same_vh_protocol_next; + } + } + vh = vh->vhost_next; + } + } + + + /* the socket we came to service timed out, nothing to do */ + if (timed_out) + return 0; + + /* just here for timeout management? */ + if (!pollfd) + return 0; + + /* no, here to service a socket descriptor */ + wsi = wsi_from_fd(context, pollfd->fd); + if (!wsi) + /* not lws connection ... leave revents alone and return */ + return 0; + + /* + * so that caller can tell we handled, past here we need to + * zero down pollfd->revents after handling + */ + +#if LWS_POSIX + /* handle session socket closed */ + + if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) && + (pollfd->revents & LWS_POLLHUP)) { + wsi->socket_is_permanently_unusable = 1; + lwsl_debug("Session Socket %p (fd=%d) dead\n", + (void *)wsi, pollfd->fd); + + goto close_and_handled; + } + +#ifdef _WIN32 + if (pollfd->revents & LWS_POLLOUT) + wsi->sock_send_blocking = FALSE; +#endif + +#endif + + if ((!(pollfd->revents & pollfd->events & LWS_POLLIN)) && + (pollfd->revents & LWS_POLLHUP)) { + lwsl_debug("pollhup\n"); + wsi->socket_is_permanently_unusable = 1; + goto close_and_handled; + } + +#ifdef LWS_OPENSSL_SUPPORT + if ((wsi->state == LWSS_SHUTDOWN) && lws_is_ssl(wsi) && wsi->ssl) { + n = SSL_shutdown(wsi->ssl); + lwsl_debug("SSL_shutdown=%d for fd %d\n", n, wsi->desc.sockfd); + switch (n) { + case 1: + n = shutdown(wsi->desc.sockfd, SHUT_WR); + goto close_and_handled; + + case 0: + lws_change_pollfd(wsi, 0, LWS_POLLIN); + n = 0; + goto handled; + + default: + n = SSL_get_error(wsi->ssl, n); + if (n != SSL_ERROR_SYSCALL && n != SSL_ERROR_SSL) { + if (SSL_want_read(wsi->ssl)) { + lwsl_debug("(wants read)\n"); + lws_change_pollfd(wsi, 0, LWS_POLLIN); + n = 0; + goto handled; + } + if (SSL_want_write(wsi->ssl)) { + lwsl_debug("(wants write)\n"); + lws_change_pollfd(wsi, 0, LWS_POLLOUT); + n = 0; + goto handled; + } + } + + /* actual error occurred, just close the connection */ + n = shutdown(wsi->desc.sockfd, SHUT_WR); + goto close_and_handled; + } + } +#endif + + /* okay, what we came here to do... */ + + switch (wsi->mode) { + case LWSCM_HTTP_SERVING: + case LWSCM_HTTP_CLIENT: + case LWSCM_HTTP_SERVING_ACCEPTED: + case LWSCM_SERVER_LISTENER: + case LWSCM_SSL_ACK_PENDING: + case LWSCM_SSL_ACK_PENDING_RAW: + if (wsi->state == LWSS_CLIENT_HTTP_ESTABLISHED) + goto handled; + +#ifdef LWS_WITH_CGI + if (wsi->cgi && (pollfd->revents & LWS_POLLOUT)) { + n = lws_handle_POLLOUT_event(wsi, pollfd); + if (n) + goto close_and_handled; + goto handled; + } +#endif + /* fallthru */ + case LWSCM_RAW: + n = lws_server_socket_service(context, wsi, pollfd); + if (n) /* closed by above */ + return 1; + goto handled; + + case LWSCM_RAW_FILEDESC: + + if (pollfd->revents & LWS_POLLOUT) { + n = lws_calllback_as_writeable(wsi); + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + return 1; + } + if (n) + goto close_and_handled; + } + n = LWS_CALLBACK_RAW_RX; + if (wsi->mode == LWSCM_RAW_FILEDESC) + n = LWS_CALLBACK_RAW_RX_FILE; + + if (pollfd->revents & LWS_POLLIN) { + if (user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, n, + wsi->user_space, NULL, 0)) { + lwsl_debug("raw rx callback closed it\n"); + goto close_and_handled; + } + } + + if (pollfd->revents & LWS_POLLHUP) + goto close_and_handled; + n = 0; + goto handled; + + case LWSCM_WS_SERVING: + case LWSCM_WS_CLIENT: + case LWSCM_HTTP2_SERVING: + case LWSCM_HTTP_CLIENT_ACCEPTED: + + /* 1: something requested a callback when it was OK to write */ + + if ((pollfd->revents & LWS_POLLOUT) && + ((wsi->state == LWSS_ESTABLISHED || + wsi->state == LWSS_HTTP2_ESTABLISHED || + wsi->state == LWSS_HTTP2_ESTABLISHED_PRE_SETTINGS || + wsi->state == LWSS_RETURNED_CLOSE_ALREADY || + wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || + wsi->state == LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE)) && + lws_handle_POLLOUT_event(wsi, pollfd)) { + if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY) + wsi->state = LWSS_FLUSHING_STORED_SEND_BEFORE_CLOSE; + lwsl_info("lws_service_fd: closing\n"); + goto close_and_handled; + } + + if (wsi->state == LWSS_RETURNED_CLOSE_ALREADY || + wsi->state == LWSS_WAITING_TO_SEND_CLOSE_NOTIFICATION || + wsi->state == LWSS_AWAITING_CLOSE_ACK) { + /* + * we stopped caring about anything except control + * packets. Force flow control off, defeat tx + * draining. + */ + lws_rx_flow_control(wsi, 1); + wsi->u.ws.tx_draining_ext = 0; + } + + if (wsi->u.ws.tx_draining_ext) + /* we cannot deal with new RX until the TX ext + * path has been drained. It's because new + * rx will, eg, crap on the wsi rx buf that + * may be needed to retain state. + * + * TX ext drain path MUST go through event loop + * to avoid blocking. + */ + break; + + if (lws_is_flowcontrolled(wsi)) + /* We cannot deal with any kind of new RX + * because we are RX-flowcontrolled. + */ + break; + +#if defined(LWS_WITH_HTTP2) + if (wsi->http2_substream || wsi->upgraded_to_http2) { + wsi1 = lws_get_network_wsi(wsi); + if (wsi1 && wsi1->trunc_len) + /* We cannot deal with any kind of new RX + * because we are dealing with a partial send + * (new RX may trigger new http_action() that + * expect to be able to send) + */ + break; + } +#endif + + /* 2: RX Extension needs to be drained + */ + + if (wsi->state == LWSS_ESTABLISHED && + wsi->u.ws.rx_draining_ext) { + + lwsl_ext("%s: RX EXT DRAINING: Service\n", __func__); +#ifndef LWS_NO_CLIENT + if (wsi->mode == LWSCM_WS_CLIENT) { + n = lws_client_rx_sm(wsi, 0); + if (n < 0) + /* we closed wsi */ + n = 0; + } else +#endif + n = lws_rx_sm(wsi, 0); + + goto handled; + } + + if (wsi->u.ws.rx_draining_ext) + /* + * We have RX EXT content to drain, but can't do it + * right now. That means we cannot do anything lower + * priority either. + */ + break; + + /* 3: RX Flowcontrol buffer / h2 rx scratch needs to be drained + */ + + if (wsi->rxflow_buffer) { + lwsl_info("draining rxflow (len %d)\n", + wsi->rxflow_len - wsi->rxflow_pos); + assert(wsi->rxflow_pos < wsi->rxflow_len); + /* well, drain it */ + eff_buf.token = (char *)wsi->rxflow_buffer + + wsi->rxflow_pos; + eff_buf.token_len = wsi->rxflow_len - wsi->rxflow_pos; + draining_flow = 1; + goto drain; + } + +#if defined(LWS_WITH_HTTP2) + if (wsi->upgraded_to_http2) { + struct lws_h2_netconn *h2n = wsi->u.h2.h2n; + + if (h2n->rx_scratch_len) { + lwsl_info("%s: %p: resuming h2 rx_scratch pos = %d len = %d\n", + __func__, wsi, h2n->rx_scratch_pos, h2n->rx_scratch_len); + eff_buf.token = (char *)h2n->rx_scratch + + h2n->rx_scratch_pos; + eff_buf.token_len = h2n->rx_scratch_len; + + h2n->rx_scratch_len = 0; + goto drain; + } + } +#endif + + /* 4: any incoming (or ah-stashed incoming rx) data ready? + * notice if rx flow going off raced poll(), rx flow wins + */ + + if (!(pollfd->revents & pollfd->events & LWS_POLLIN)) + break; +read: + if (lws_is_flowcontrolled(wsi)) { + lwsl_info("%s: %p should be rxflow (bm 0x%x)..\n", + __func__, wsi, wsi->rxflow_bitmap); + break; + } + + /* all the union members start with hdr, so even in ws mode + * we can deal with the ah via u.hdr + */ + if (wsi->u.hdr.ah) { + lwsl_info("%s: %p: inherited ah rx\n", __func__, wsi); + eff_buf.token_len = wsi->u.hdr.ah->rxlen - + wsi->u.hdr.ah->rxpos; + eff_buf.token = (char *)wsi->u.hdr.ah->rx + + wsi->u.hdr.ah->rxpos; + } else { + if (wsi->mode != LWSCM_HTTP_CLIENT_ACCEPTED) { + /* + * extension may not consume everything (eg, pmd may be constrained + * as to what it can output...) has to go in per-wsi rx buf area. + * Otherwise in large temp serv_buf area. + */ + +#if defined(LWS_WITH_HTTP2) + if (wsi->upgraded_to_http2) { + if (!wsi->u.h2.h2n->rx_scratch) { + wsi->u.h2.h2n->rx_scratch = lws_malloc(LWS_H2_RX_SCRATCH_SIZE, "h2 rx scratch"); + if (!wsi->u.h2.h2n->rx_scratch) + goto close_and_handled; + } + eff_buf.token = wsi->u.h2.h2n->rx_scratch; + eff_buf.token_len = LWS_H2_RX_SCRATCH_SIZE; + } else +#endif + { + eff_buf.token = (char *)pt->serv_buf; + if (lws_is_ws_with_ext(wsi)) { + eff_buf.token_len = wsi->u.ws.rx_ubuf_alloc; + } else { + eff_buf.token_len = context->pt_serv_buf_size; + } + + if ((unsigned int)eff_buf.token_len > context->pt_serv_buf_size) + eff_buf.token_len = context->pt_serv_buf_size; + } + + if ((int)pending > eff_buf.token_len) + pending = eff_buf.token_len; + + eff_buf.token_len = lws_ssl_capable_read(wsi, + (unsigned char *)eff_buf.token, pending ? pending : + eff_buf.token_len); + switch (eff_buf.token_len) { + case 0: + lwsl_info("%s: zero length read\n", __func__); + goto close_and_handled; + case LWS_SSL_CAPABLE_MORE_SERVICE: + lwsl_info("SSL Capable more service\n"); + n = 0; + goto handled; + case LWS_SSL_CAPABLE_ERROR: + lwsl_info("Closing when error\n"); + goto close_and_handled; + } + // lwsl_notice("Actual RX %d\n", eff_buf.token_len); + } + } + +drain: +#ifndef LWS_NO_CLIENT + if (wsi->mode == LWSCM_HTTP_CLIENT_ACCEPTED && + !wsi->told_user_closed) { + + /* + * In SSL mode we get POLLIN notification about + * encrypted data in. + * + * But that is not necessarily related to decrypted + * data out becoming available; in may need to perform + * other in or out before that happens. + * + * simply mark ourselves as having readable data + * and turn off our POLLIN + */ + wsi->client_rx_avail = 1; + lws_change_pollfd(wsi, LWS_POLLIN, 0); + + /* let user code know, he'll usually ask for writeable + * callback and drain / re-enable it there + */ + if (user_callback_handle_rxflow( + wsi->protocol->callback, + wsi, LWS_CALLBACK_RECEIVE_CLIENT_HTTP, + wsi->user_space, NULL, 0)) { + lwsl_info("RECEIVE_CLIENT_HTTP closed it\n"); + goto close_and_handled; + } + + n = 0; + goto handled; + } +#endif + /* + * give any active extensions a chance to munge the buffer + * before parse. We pass in a pointer to an lws_tokens struct + * prepared with the default buffer and content length that's in + * there. Rather than rewrite the default buffer, extensions + * that expect to grow the buffer can adapt .token to + * point to their own per-connection buffer in the extension + * user allocation. By default with no extensions or no + * extension callback handling, just the normal input buffer is + * used then so it is efficient. + */ + do { + more = 0; + + m = lws_ext_cb_active(wsi, LWS_EXT_CB_PACKET_RX_PREPARSE, + &eff_buf, 0); + if (m < 0) + goto close_and_handled; + if (m) + more = 1; + + /* service incoming data */ + + if (eff_buf.token_len) { + /* + * if draining from rxflow buffer, not + * critical to track what was used since at the + * use it bumps wsi->rxflow_pos. If we come + * around again it will pick up from where it + * left off. + */ + n = lws_read(wsi, (unsigned char *)eff_buf.token, + eff_buf.token_len); + if (n < 0) { + /* we closed wsi */ + n = 0; + goto handled; + } + } + + eff_buf.token = NULL; + eff_buf.token_len = 0; + } while (more); + + if (wsi->u.hdr.ah) { + lwsl_debug("%s: %p: detaching\n", __func__, wsi); + lws_header_table_force_to_detachable_state(wsi); + /* we can run the normal ah detach flow despite + * being in ws union mode, since all union members + * start with hdr */ + lws_header_table_detach(wsi, 0); + } + + pending = lws_ssl_pending(wsi); + if (pending) { + if (lws_is_ws_with_ext(wsi)) + pending = pending > wsi->u.ws.rx_ubuf_alloc ? + wsi->u.ws.rx_ubuf_alloc : pending; + else + pending = pending > context->pt_serv_buf_size ? + context->pt_serv_buf_size : pending; + goto read; + } + + if (draining_flow && wsi->rxflow_buffer && + wsi->rxflow_pos == wsi->rxflow_len) { + lwsl_info("%s: %p flow buf: drained\n", __func__, wsi); + lws_free_set_NULL(wsi->rxflow_buffer); + /* having drained the rxflow buffer, can rearm POLLIN */ +#ifdef LWS_NO_SERVER + n = +#endif + _lws_rx_flow_control(wsi); + /* n ignored, needed for NO_SERVER case */ + } + + break; +#ifdef LWS_WITH_CGI + case LWSCM_CGI: /* we exist to handle a cgi's stdin/out/err data... + * do the callback on our master wsi + */ + { + struct lws_cgi_args args; + + if (wsi->cgi_channel >= LWS_STDOUT && + !(pollfd->revents & pollfd->events & LWS_POLLIN)) + break; + if (wsi->cgi_channel == LWS_STDIN && + !(pollfd->revents & pollfd->events & LWS_POLLOUT)) + break; + + if (wsi->cgi_channel == LWS_STDIN) + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_info("failed at set pollfd\n"); + return 1; + } + + args.ch = wsi->cgi_channel; + args.stdwsi = &wsi->parent->cgi->stdwsi[0]; + args.hdr_state = wsi->hdr_state; + + lwsl_debug("CGI LWS_STDOUT %p mode %d state %d\n", + wsi->parent, wsi->parent->mode, + wsi->parent->state); + + if (user_callback_handle_rxflow( + wsi->parent->protocol->callback, + wsi->parent, LWS_CALLBACK_CGI, + wsi->parent->user_space, + (void *)&args, 0)) + return 1; + + break; + } +#endif + default: +#ifdef LWS_NO_CLIENT + break; +#else + if ((pollfd->revents & LWS_POLLOUT) && + lws_handle_POLLOUT_event(wsi, pollfd)) { + lwsl_debug("POLLOUT event closed it\n"); + goto close_and_handled; + } + + n = lws_client_socket_service(context, wsi, pollfd); + if (n) + return 1; + goto handled; +#endif + } + + n = 0; + goto handled; + +close_and_handled: + lwsl_debug("%p: Close and handled\n", wsi); + lws_close_free_wsi(wsi, LWS_CLOSE_STATUS_NOSTATUS); + /* + * pollfd may point to something else after the close + * due to pollfd swapping scheme on delete on some platforms + * we can't clear revents now because it'd be the wrong guy's revents + */ + return 1; + +handled: + pollfd->revents = 0; + return n; +} + +LWS_VISIBLE int +lws_service_fd(struct lws_context *context, struct lws_pollfd *pollfd) +{ + return lws_service_fd_tsi(context, pollfd, 0); +} + +LWS_VISIBLE int +lws_service(struct lws_context *context, int timeout_ms) +{ + return lws_plat_service(context, timeout_ms); +} + +LWS_VISIBLE int +lws_service_tsi(struct lws_context *context, int timeout_ms, int tsi) +{ + return _lws_plat_service_tsi(context, timeout_ms, tsi); +} + diff --git a/thirdparty/lws/ssl.c b/thirdparty/lws/ssl.c new file mode 100644 index 0000000000..0a647b469c --- /dev/null +++ b/thirdparty/lws/ssl.c @@ -0,0 +1,976 @@ +/* + * libwebsockets - small server side websockets and web server implementation + * + * Copyright (C) 2010-2017 Andy Green <andy@warmcat.com> + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation: + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, + * MA 02110-1301 USA + */ + +#include "private-libwebsockets.h" + +/* workaround for mingw */ +#if !defined(ECONNABORTED) +#define ECONNABORTED 103 +#endif + +int lws_alloc_vfs_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount) +{ + lws_filepos_t len; + lws_fop_flags_t flags = LWS_O_RDONLY; + lws_fop_fd_t fops_fd = lws_vfs_file_open( + lws_get_fops(context), filename, &flags); + int ret = 1; + + if (!fops_fd) + return 1; + + len = lws_vfs_get_length(fops_fd); + + *buf = lws_malloc((size_t)len, "lws_alloc_vfs_file"); + if (!*buf) + goto bail; + + if (lws_vfs_file_read(fops_fd, amount, *buf, len)) + goto bail; + + ret = 0; +bail: + lws_vfs_file_close(&fops_fd); + + return ret; +} + +#if defined(LWS_WITH_MBEDTLS) +#if defined(LWS_WITH_ESP32) +int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount) +{ + nvs_handle nvh; + size_t s; + int n = 0; + + ESP_ERROR_CHECK(nvs_open("lws-station", NVS_READWRITE, &nvh)); + if (nvs_get_blob(nvh, filename, NULL, &s) != ESP_OK) { + n = 1; + goto bail; + } + *buf = lws_malloc(s, "alloc_file"); + if (!*buf) { + n = 2; + goto bail; + } + if (nvs_get_blob(nvh, filename, (char *)*buf, &s) != ESP_OK) { + lws_free(*buf); + n = 1; + goto bail; + } + + *amount = s; + +bail: + nvs_close(nvh); + + return n; +} +#else +int alloc_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount) +{ + FILE *f; + size_t s; + int n = 0; + + f = fopen(filename, "rb"); + if (f == NULL) { + n = 1; + goto bail; + } + + if (fseek(f, 0, SEEK_END) != 0) { + n = 1; + goto bail; + } + + s = ftell(f); + if (s == -1) { + n = 1; + goto bail; + } + + if (fseek(f, 0, SEEK_SET) != 0) { + n = 1; + goto bail; + } + + *buf = lws_malloc(s, "alloc_file"); + if (!*buf) { + n = 2; + goto bail; + } + + if (fread(*buf, s, 1, f) != 1) { + lws_free(*buf); + n = 1; + goto bail; + } + + *amount = s; + +bail: + if (f) + fclose(f); + + return n; + +} +#endif +int alloc_pem_to_der_file(struct lws_context *context, const char *filename, uint8_t **buf, + lws_filepos_t *amount) +{ + uint8_t *pem, *p, *q, *end; + lws_filepos_t len; + int n; + + n = alloc_file(context, filename, &pem, &len); + if (n) + return n; + + /* trim the first line */ + + p = pem; + end = p + len; + if (strncmp((char *)p, "-----", 5)) + goto bail; + p += 5; + while (p < end && *p != '\n' && *p != '-') + p++; + + if (*p != '-') + goto bail; + + while (p < end && *p != '\n') + p++; + + if (p >= end) + goto bail; + + p++; + + /* trim the last line */ + + q = end - 2; + + while (q > pem && *q != '\n') + q--; + + if (*q != '\n') + goto bail; + + *q = '\0'; + + *amount = lws_b64_decode_string((char *)p, (char *)pem, len); + *buf = pem; + + return 0; + +bail: + lws_free(pem); + + return 4; +} +#endif + +int openssl_websocket_private_data_index, + openssl_SSL_CTX_private_data_index; + +int lws_ssl_get_error(struct lws *wsi, int n) +{ + int m; + + if (!wsi->ssl) + return 99; + + m = SSL_get_error(wsi->ssl, n); + lwsl_debug("%s: %p %d -> %d\n", __func__, wsi->ssl, n, m); + + return m; +} + +/* Copies a string describing the code returned by lws_ssl_get_error(), + * which may also contain system error information in the case of SSL_ERROR_SYSCALL, + * into buf up to len. + * Returns a pointer to buf. + * + * Note: the lws_ssl_get_error() code is *not* an error code that can be passed + * to ERR_error_string(), + * + * ret is the return value originally passed to lws_ssl_get_error(), needed to disambiguate + * SYS_ERROR_SYSCALL. + * + * See man page for SSL_get_error(). + * + * Not thread safe, uses strerror() + */ +char* lws_ssl_get_error_string(int status, int ret, char *buf, size_t len) { + switch (status) { + case SSL_ERROR_NONE: return strncpy(buf, "SSL_ERROR_NONE", len); + case SSL_ERROR_ZERO_RETURN: return strncpy(buf, "SSL_ERROR_ZERO_RETURN", len); + case SSL_ERROR_WANT_READ: return strncpy(buf, "SSL_ERROR_WANT_READ", len); + case SSL_ERROR_WANT_WRITE: return strncpy(buf, "SSL_ERROR_WANT_WRITE", len); + case SSL_ERROR_WANT_CONNECT: return strncpy(buf, "SSL_ERROR_WANT_CONNECT", len); + case SSL_ERROR_WANT_ACCEPT: return strncpy(buf, "SSL_ERROR_WANT_ACCEPT", len); + case SSL_ERROR_WANT_X509_LOOKUP: return strncpy(buf, "SSL_ERROR_WANT_X509_LOOKUP", len); + case SSL_ERROR_SYSCALL: + switch (ret) { + case 0: + lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: EOF"); + return buf; + case -1: +#ifndef LWS_PLAT_OPTEE + lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: %s", strerror(errno)); +#else + lws_snprintf(buf, len, "SSL_ERROR_SYSCALL: %d", errno); +#endif + return buf; + default: + return strncpy(buf, "SSL_ERROR_SYSCALL", len); + } + case SSL_ERROR_SSL: return "SSL_ERROR_SSL"; + default: return "SSL_ERROR_UNKNOWN"; + } +} + +void +lws_ssl_elaborate_error(void) +{ +#if defined(LWS_WITH_MBEDTLS) +#else + char buf[256]; + u_long err; + + while ((err = ERR_get_error()) != 0) { + ERR_error_string_n(err, buf, sizeof(buf)); + lwsl_info("*** %s\n", buf); + } +#endif +} + +#if !defined(LWS_WITH_MBEDTLS) + +static int +lws_context_init_ssl_pem_passwd_cb(char * buf, int size, int rwflag, void *userdata) +{ + struct lws_context_creation_info * info = + (struct lws_context_creation_info *)userdata; + + strncpy(buf, info->ssl_private_key_password, size); + buf[size - 1] = '\0'; + + return strlen(buf); +} + +void +lws_ssl_bind_passphrase(SSL_CTX *ssl_ctx, struct lws_context_creation_info *info) +{ + if (!info->ssl_private_key_password) + return; + /* + * password provided, set ssl callback and user data + * for checking password which will be trigered during + * SSL_CTX_use_PrivateKey_file function + */ + SSL_CTX_set_default_passwd_cb_userdata(ssl_ctx, (void *)info); + SSL_CTX_set_default_passwd_cb(ssl_ctx, lws_context_init_ssl_pem_passwd_cb); +} +#endif + +int +lws_context_init_ssl_library(struct lws_context_creation_info *info) +{ +#ifdef USE_WOLFSSL +#ifdef USE_OLD_CYASSL + lwsl_info(" Compiled with CyaSSL support\n"); +#else + lwsl_info(" Compiled with wolfSSL support\n"); +#endif +#else +#if defined(LWS_WITH_BORINGSSL) + lwsl_info(" Compiled with BoringSSL support\n"); +#else +#if defined(LWS_WITH_MBEDTLS) + lwsl_info(" Compiled with MbedTLS support\n"); +#else + lwsl_info(" Compiled with OpenSSL support\n"); +#endif +#endif +#endif + if (!lws_check_opt(info->options, LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) { + lwsl_info(" SSL disabled: no LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT\n"); + return 0; + } + + /* basic openssl init */ + + lwsl_info("Doing SSL library init\n"); + +#if !defined(LWS_WITH_MBEDTLS) + SSL_library_init(); + OpenSSL_add_all_algorithms(); + SSL_load_error_strings(); + + openssl_websocket_private_data_index = + SSL_get_ex_new_index(0, "lws", NULL, NULL, NULL); + + openssl_SSL_CTX_private_data_index = SSL_CTX_get_ex_new_index(0, + NULL, NULL, NULL, NULL); +#endif + + return 0; +} + +LWS_VISIBLE void +lws_ssl_destroy(struct lws_vhost *vhost) +{ + if (!lws_check_opt(vhost->context->options, + LWS_SERVER_OPTION_DO_SSL_GLOBAL_INIT)) + return; + + if (vhost->ssl_ctx) + SSL_CTX_free(vhost->ssl_ctx); + if (!vhost->user_supplied_ssl_ctx && vhost->ssl_client_ctx) + SSL_CTX_free(vhost->ssl_client_ctx); + +#if defined(LWS_WITH_MBEDTLS) + if (vhost->x509_client_CA) + X509_free(vhost->x509_client_CA); +#else +// after 1.1.0 no need +#if (OPENSSL_VERSION_NUMBER < 0x10100000) +// <= 1.0.1f = old api, 1.0.1g+ = new api +#if (OPENSSL_VERSION_NUMBER <= 0x1000106f) || defined(USE_WOLFSSL) + ERR_remove_state(0); +#else +#if OPENSSL_VERSION_NUMBER >= 0x1010005f && \ + !defined(LIBRESSL_VERSION_NUMBER) && \ + !defined(OPENSSL_IS_BORINGSSL) + ERR_remove_thread_state(); +#else + ERR_remove_thread_state(NULL); +#endif +#endif + // after 1.1.0 no need +#if (OPENSSL_VERSION_NUMBER >= 0x10002000) && (OPENSSL_VERSION_NUMBER <= 0x10100000) + SSL_COMP_free_compression_methods(); +#endif + ERR_free_strings(); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); +#endif +#endif +} + +int +lws_ssl_anybody_has_buffered_read_tsi(struct lws_context *context, int tsi) +{ + struct lws_context_per_thread *pt = &context->pt[tsi]; + struct lws *wsi, *wsi_next; + + wsi = pt->pending_read_list; + while (wsi) { + wsi_next = wsi->pending_read_list_next; + pt->fds[wsi->position_in_fds_table].revents |= + pt->fds[wsi->position_in_fds_table].events & LWS_POLLIN; + if (pt->fds[wsi->position_in_fds_table].revents & LWS_POLLIN) + return 1; + + wsi = wsi_next; + } + + return 0; +} + +LWS_VISIBLE void +lws_ssl_remove_wsi_from_buffered_list(struct lws *wsi) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + + if (!wsi->pending_read_list_prev && + !wsi->pending_read_list_next && + pt->pending_read_list != wsi) + /* we are not on the list */ + return; + + /* point previous guy's next to our next */ + if (!wsi->pending_read_list_prev) + pt->pending_read_list = wsi->pending_read_list_next; + else + wsi->pending_read_list_prev->pending_read_list_next = + wsi->pending_read_list_next; + + /* point next guy's previous to our previous */ + if (wsi->pending_read_list_next) + wsi->pending_read_list_next->pending_read_list_prev = + wsi->pending_read_list_prev; + + wsi->pending_read_list_prev = NULL; + wsi->pending_read_list_next = NULL; +} + +LWS_VISIBLE int +lws_ssl_capable_read(struct lws *wsi, unsigned char *buf, int len) +{ + struct lws_context *context = wsi->context; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + int n = 0, m; + + if (!wsi->ssl) + return lws_ssl_capable_read_no_ssl(wsi, buf, len); + + lws_stats_atomic_bump(context, pt, LWSSTATS_C_API_READ, 1); + + errno = 0; + n = SSL_read(wsi->ssl, buf, len); +#if defined(LWS_WITH_ESP32) + if (!n && errno == ENOTCONN) { + lwsl_debug("%p: SSL_read ENOTCONN\n", wsi); + return LWS_SSL_CAPABLE_ERROR; + } +#endif +#if defined(LWS_WITH_STATS) + if (!wsi->seen_rx) { + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_MS_SSL_RX_DELAY, + time_in_microseconds() - wsi->accept_start_us); + lws_stats_atomic_bump(wsi->context, pt, LWSSTATS_C_SSL_CONNS_HAD_RX, 1); + wsi->seen_rx = 1; + } +#endif + + + lwsl_debug("%p: SSL_read says %d\n", wsi, n); + /* manpage: returning 0 means connection shut down */ + if (!n) { + wsi->socket_is_permanently_unusable = 1; + + return LWS_SSL_CAPABLE_ERROR; + } + + if (n < 0) { + m = lws_ssl_get_error(wsi, n); + lwsl_debug("%p: ssl err %d errno %d\n", wsi, m, errno); + if (m == SSL_ERROR_ZERO_RETURN || + m == SSL_ERROR_SYSCALL) + return LWS_SSL_CAPABLE_ERROR; + + if (SSL_want_read(wsi->ssl)) { + lwsl_debug("%s: WANT_READ\n", __func__); + lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi); + return LWS_SSL_CAPABLE_MORE_SERVICE; + } + if (SSL_want_write(wsi->ssl)) { + lwsl_debug("%s: WANT_WRITE\n", __func__); + lwsl_debug("%p: LWS_SSL_CAPABLE_MORE_SERVICE\n", wsi); + return LWS_SSL_CAPABLE_MORE_SERVICE; + } + wsi->socket_is_permanently_unusable = 1; + + return LWS_SSL_CAPABLE_ERROR; + } + + lws_stats_atomic_bump(context, pt, LWSSTATS_B_READ, n); + + if (wsi->vhost) + wsi->vhost->conn_stats.rx += n; + + lws_restart_ws_ping_pong_timer(wsi); + + /* + * if it was our buffer that limited what we read, + * check if SSL has additional data pending inside SSL buffers. + * + * Because these won't signal at the network layer with POLLIN + * and if we don't realize, this data will sit there forever + */ + if (n != len) + goto bail; + if (!wsi->ssl) + goto bail; + + if (!SSL_pending(wsi->ssl)) + goto bail; + + if (wsi->pending_read_list_next) + return n; + if (wsi->pending_read_list_prev) + return n; + if (pt->pending_read_list == wsi) + return n; + + /* add us to the linked list of guys with pending ssl */ + if (pt->pending_read_list) + pt->pending_read_list->pending_read_list_prev = wsi; + + wsi->pending_read_list_next = pt->pending_read_list; + wsi->pending_read_list_prev = NULL; + pt->pending_read_list = wsi; + + return n; +bail: + lws_ssl_remove_wsi_from_buffered_list(wsi); + + return n; +} + +LWS_VISIBLE int +lws_ssl_pending(struct lws *wsi) +{ + if (!wsi->ssl) + return 0; + + return SSL_pending(wsi->ssl); +} + +LWS_VISIBLE int +lws_ssl_capable_write(struct lws *wsi, unsigned char *buf, int len) +{ + int n, m; + + if (!wsi->ssl) + return lws_ssl_capable_write_no_ssl(wsi, buf, len); + + n = SSL_write(wsi->ssl, buf, len); + if (n > 0) + return n; + + m = lws_ssl_get_error(wsi, n); + if (m != SSL_ERROR_SYSCALL) { + + if (SSL_want_read(wsi->ssl)) { + lwsl_notice("%s: want read\n", __func__); + + return LWS_SSL_CAPABLE_MORE_SERVICE; + } + + if (SSL_want_write(wsi->ssl)) { + lws_set_blocking_send(wsi); + + lwsl_notice("%s: want write\n", __func__); + + return LWS_SSL_CAPABLE_MORE_SERVICE; + } + } + + lwsl_debug("%s failed: %s\n",__func__, ERR_error_string(m, NULL)); + lws_ssl_elaborate_error(); + + wsi->socket_is_permanently_unusable = 1; + + return LWS_SSL_CAPABLE_ERROR; +} + +static int +lws_gate_accepts(struct lws_context *context, int on) +{ + struct lws_vhost *v = context->vhost_list; + + lwsl_info("gating accepts %d\n", on); + context->ssl_gate_accepts = !on; +#if defined(LWS_WITH_STATS) + context->updated = 1; +#endif + + while (v) { + if (v->use_ssl && v->lserv_wsi) /* gate ability to accept incoming connections */ + if (lws_change_pollfd(v->lserv_wsi, (LWS_POLLIN) * !on, + (LWS_POLLIN) * on)) + lwsl_info("Unable to set accept POLLIN %d\n", on); + + v = v->vhost_next; + } + + return 0; +} + +void +lws_ssl_info_callback(const SSL *ssl, int where, int ret) +{ + struct lws *wsi; + struct lws_context *context; + struct lws_ssl_info si; + + context = (struct lws_context *)SSL_CTX_get_ex_data( + SSL_get_SSL_CTX(ssl), + openssl_SSL_CTX_private_data_index); + if (!context) + return; + wsi = wsi_from_fd(context, SSL_get_fd(ssl)); + if (!wsi) + return; + + if (!(where & wsi->vhost->ssl_info_event_mask)) + return; + + si.where = where; + si.ret = ret; + + if (user_callback_handle_rxflow(wsi->protocol->callback, + wsi, LWS_CALLBACK_SSL_INFO, + wsi->user_space, &si, 0)) + lws_set_timeout(wsi, PENDING_TIMEOUT_KILLED_BY_SSL_INFO, -1); +} + + +LWS_VISIBLE int +lws_ssl_close(struct lws *wsi) +{ + lws_sockfd_type n; + + if (!wsi->ssl) + return 0; /* not handled */ + +#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK) + /* kill ssl callbacks, becausse we will remove the fd from the + * table linking it to the wsi + */ + if (wsi->vhost->ssl_info_event_mask) + SSL_set_info_callback(wsi->ssl, NULL); +#endif + + n = SSL_get_fd(wsi->ssl); + if (!wsi->socket_is_permanently_unusable) + SSL_shutdown(wsi->ssl); + compatible_close(n); + SSL_free(wsi->ssl); + wsi->ssl = NULL; + + if (wsi->context->simultaneous_ssl_restriction && + wsi->context->simultaneous_ssl-- == + wsi->context->simultaneous_ssl_restriction) + /* we made space and can do an accept */ + lws_gate_accepts(wsi->context, 1); +#if defined(LWS_WITH_STATS) + wsi->context->updated = 1; +#endif + + return 1; /* handled */ +} + +/* leave all wsi close processing to the caller */ + +LWS_VISIBLE int +lws_server_socket_service_ssl(struct lws *wsi, lws_sockfd_type accept_fd) +{ + struct lws_context *context = wsi->context; + struct lws_vhost *vh; + struct lws_context_per_thread *pt = &context->pt[(int)wsi->tsi]; + int n, m; +#if !defined(USE_WOLFSSL) && !defined(LWS_WITH_MBEDTLS) + BIO *bio; +#endif + char buf[256]; + + (void)buf; + + if (!LWS_SSL_ENABLED(wsi->vhost)) + return 0; + + switch (wsi->mode) { + case LWSCM_SSL_INIT: + case LWSCM_SSL_INIT_RAW: + if (wsi->ssl) + lwsl_err("%s: leaking ssl\n", __func__); + if (accept_fd == LWS_SOCK_INVALID) + assert(0); + if (context->simultaneous_ssl_restriction && + context->simultaneous_ssl >= context->simultaneous_ssl_restriction) { + lwsl_notice("unable to deal with SSL connection\n"); + return 1; + } + errno = 0; + wsi->ssl = SSL_new(wsi->vhost->ssl_ctx); + if (wsi->ssl == NULL) { + lwsl_err("SSL_new failed: %d (errno %d)\n", + lws_ssl_get_error(wsi, 0), errno); + + lws_ssl_elaborate_error(); + if (accept_fd != LWS_SOCK_INVALID) + compatible_close(accept_fd); + goto fail; + } +#if defined (LWS_HAVE_SSL_SET_INFO_CALLBACK) + if (wsi->vhost->ssl_info_event_mask) + SSL_set_info_callback(wsi->ssl, lws_ssl_info_callback); +#endif + if (context->simultaneous_ssl_restriction && + ++context->simultaneous_ssl == context->simultaneous_ssl_restriction) + /* that was the last allowed SSL connection */ + lws_gate_accepts(context, 0); +#if defined(LWS_WITH_STATS) + context->updated = 1; +#endif + +#if !defined(LWS_WITH_MBEDTLS) + SSL_set_ex_data(wsi->ssl, + openssl_websocket_private_data_index, wsi); +#endif + SSL_set_fd(wsi->ssl, accept_fd); + +#ifdef USE_WOLFSSL +#ifdef USE_OLD_CYASSL + CyaSSL_set_using_nonblock(wsi->ssl, 1); +#else + wolfSSL_set_using_nonblock(wsi->ssl, 1); +#endif +#else +#if defined(LWS_WITH_MBEDTLS) + lws_plat_set_socket_options(wsi->vhost, accept_fd); +#else + SSL_set_mode(wsi->ssl, SSL_MODE_ACCEPT_MOVING_WRITE_BUFFER); + bio = SSL_get_rbio(wsi->ssl); + if (bio) + BIO_set_nbio(bio, 1); /* nonblocking */ + else + lwsl_notice("NULL rbio\n"); + bio = SSL_get_wbio(wsi->ssl); + if (bio) + BIO_set_nbio(bio, 1); /* nonblocking */ + else + lwsl_notice("NULL rbio\n"); +#endif +#endif + + /* + * we are not accepted yet, but we need to enter ourselves + * as a live connection. That way we can retry when more + * pieces come if we're not sorted yet + */ + + if (wsi->mode == LWSCM_SSL_INIT) + wsi->mode = LWSCM_SSL_ACK_PENDING; + else + wsi->mode = LWSCM_SSL_ACK_PENDING_RAW; + + if (insert_wsi_socket_into_fds(context, wsi)) { + lwsl_err("%s: failed to insert into fds\n", __func__); + goto fail; + } + + lws_set_timeout(wsi, PENDING_TIMEOUT_SSL_ACCEPT, + context->timeout_secs); + + lwsl_debug("inserted SSL accept into fds, trying SSL_accept\n"); + + /* fallthru */ + + case LWSCM_SSL_ACK_PENDING: + case LWSCM_SSL_ACK_PENDING_RAW: + if (lws_change_pollfd(wsi, LWS_POLLOUT, 0)) { + lwsl_err("%s: lws_change_pollfd failed\n", __func__); + goto fail; + } + + lws_latency_pre(context, wsi); + + if (wsi->vhost->allow_non_ssl_on_ssl_port) { + + n = recv(wsi->desc.sockfd, (char *)pt->serv_buf, + context->pt_serv_buf_size, MSG_PEEK); + + /* + * optionally allow non-SSL connect on SSL listening socket + * This is disabled by default, if enabled it goes around any + * SSL-level access control (eg, client-side certs) so leave + * it disabled unless you know it's not a problem for you + */ + + if (n >= 1 && pt->serv_buf[0] >= ' ') { + /* + * TLS content-type for Handshake is 0x16, and + * for ChangeCipherSpec Record, it's 0x14 + * + * A non-ssl session will start with the HTTP + * method in ASCII. If we see it's not a legit + * SSL handshake kill the SSL for this + * connection and try to handle as a HTTP + * connection upgrade directly. + */ + wsi->use_ssl = 0; + + SSL_shutdown(wsi->ssl); + SSL_free(wsi->ssl); + wsi->ssl = NULL; + if (lws_check_opt(context->options, + LWS_SERVER_OPTION_REDIRECT_HTTP_TO_HTTPS)) + wsi->redirect_to_https = 1; + goto accepted; + } + if (!n) /* + * connection is gone, or nothing to read + * if it's gone, we will timeout on + * PENDING_TIMEOUT_SSL_ACCEPT + */ + break; + if (n < 0 && (LWS_ERRNO == LWS_EAGAIN || + LWS_ERRNO == LWS_EWOULDBLOCK)) { + /* + * well, we get no way to know ssl or not + * so go around again waiting for something + * to come and give us a hint, or timeout the + * connection. + */ + m = SSL_ERROR_WANT_READ; + goto go_again; + } + } + + /* normal SSL connection processing path */ + +#if defined(LWS_WITH_STATS) + if (!wsi->accept_start_us) + wsi->accept_start_us = time_in_microseconds(); +#endif + errno = 0; + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_SSL_CONNECTIONS_ACCEPT_SPIN, 1); + n = SSL_accept(wsi->ssl); + lws_latency(context, wsi, + "SSL_accept LWSCM_SSL_ACK_PENDING\n", n, n == 1); + lwsl_info("SSL_accept says %d\n", n); + if (n == 1) + goto accepted; + + m = lws_ssl_get_error(wsi, n); + +#if defined(LWS_WITH_MBEDTLS) + if (m == SSL_ERROR_SYSCALL && errno == 11) + m = SSL_ERROR_WANT_READ; +#endif + if (m == SSL_ERROR_SYSCALL || m == SSL_ERROR_SSL) + goto failed; + +go_again: + if (m == SSL_ERROR_WANT_READ || SSL_want_read(wsi->ssl)) { + if (lws_change_pollfd(wsi, 0, LWS_POLLIN)) { + lwsl_info("%s: WANT_READ change_pollfd failed\n", __func__); + goto fail; + } + + lwsl_info("SSL_ERROR_WANT_READ\n"); + break; + } + if (m == SSL_ERROR_WANT_WRITE || SSL_want_write(wsi->ssl)) { + lwsl_debug("%s: WANT_WRITE\n", __func__); + + if (lws_change_pollfd(wsi, 0, LWS_POLLOUT)) { + lwsl_info("%s: WANT_WRITE change_pollfd failed\n", __func__); + goto fail; + } + + break; + } +failed: + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_SSL_CONNECTIONS_FAILED, 1); + lwsl_info("SSL_accept failed socket %u: %s\n", wsi->desc.sockfd, + lws_ssl_get_error_string(m, n, buf, sizeof(buf))); + lws_ssl_elaborate_error(); + goto fail; + +accepted: + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_C_SSL_CONNECTIONS_ACCEPTED, 1); +#if defined(LWS_WITH_STATS) + lws_stats_atomic_bump(wsi->context, pt, + LWSSTATS_MS_SSL_CONNECTIONS_ACCEPTED_DELAY, + time_in_microseconds() - wsi->accept_start_us); + wsi->accept_start_us = time_in_microseconds(); +#endif + + /* adapt our vhost to match the SNI SSL_CTX that was chosen */ + vh = context->vhost_list; + while (vh) { + if (!vh->being_destroyed && + vh->ssl_ctx == SSL_get_SSL_CTX(wsi->ssl)) { + lwsl_info("setting wsi to vh %s\n", vh->name); + wsi->vhost = vh; + break; + } + vh = vh->vhost_next; + } + + /* OK, we are accepted... give him some time to negotiate */ + lws_set_timeout(wsi, PENDING_TIMEOUT_ESTABLISH_WITH_SERVER, + context->timeout_secs); + + if (wsi->mode == LWSCM_SSL_ACK_PENDING_RAW) + wsi->mode = LWSCM_RAW; + else + wsi->mode = LWSCM_HTTP_SERVING; +#if defined(LWS_WITH_HTTP2) + if (lws_h2_configure_if_upgraded(wsi)) + goto fail; +#endif + lwsl_debug("accepted new SSL conn\n"); + break; + } + + return 0; + +fail: + return 1; +} + +void +lws_ssl_SSL_CTX_destroy(struct lws_vhost *vhost) +{ + if (vhost->ssl_ctx) + SSL_CTX_free(vhost->ssl_ctx); + + if (!vhost->user_supplied_ssl_ctx && vhost->ssl_client_ctx) + SSL_CTX_free(vhost->ssl_client_ctx); +} + +void +lws_ssl_context_destroy(struct lws_context *context) +{ + +#if !defined(LWS_WITH_MBEDTLS) + +// after 1.1.0 no need +#if (OPENSSL_VERSION_NUMBER < 0x10100000) +// <= 1.0.1f = old api, 1.0.1g+ = new api +#if (OPENSSL_VERSION_NUMBER <= 0x1000106f) || defined(USE_WOLFSSL) + ERR_remove_state(0); +#else +#if OPENSSL_VERSION_NUMBER >= 0x1010005f && \ + !defined(LIBRESSL_VERSION_NUMBER) && \ + !defined(OPENSSL_IS_BORINGSSL) + ERR_remove_thread_state(); +#else + ERR_remove_thread_state(NULL); +#endif +#endif + // after 1.1.0 no need +#if (OPENSSL_VERSION_NUMBER >= 0x10002000) && (OPENSSL_VERSION_NUMBER <= 0x10100000) + SSL_COMP_free_compression_methods(); +#endif + ERR_free_strings(); + EVP_cleanup(); + CRYPTO_cleanup_all_ex_data(); +#endif +#endif +} diff --git a/thirdparty/lws/win32helpers/getopt.c b/thirdparty/lws/win32helpers/getopt.c new file mode 100644 index 0000000000..3bb21f6f28 --- /dev/null +++ b/thirdparty/lws/win32helpers/getopt.c @@ -0,0 +1,153 @@ +/* $NetBSD: getopt.c,v 1.16 1999/12/02 13:15:56 kleink Exp $ */ + +/* + * Copyright (c) 1987, 1993, 1994 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ + +#if 0 +static char sccsid[] = "@(#)getopt.c 8.3 (Berkeley) 4/27/95"; +#endif + +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <string.h> + +#define __P(x) x +#define _DIAGASSERT(x) assert(x) + +#ifdef __weak_alias +__weak_alias(getopt,_getopt); +#endif + + +int opterr = 1, /* if error message should be printed */ + optind = 1, /* index into parent argv vector */ + optopt, /* character checked for validity */ + optreset; /* reset getopt */ +char *optarg; /* argument associated with option */ + +static char * _progname __P((char *)); +int getopt_internal __P((int, char * const *, const char *)); + +static char * +_progname(nargv0) + char * nargv0; +{ + char * tmp; + + _DIAGASSERT(nargv0 != NULL); + + tmp = strrchr(nargv0, '/'); + if (tmp) + tmp++; + else + tmp = nargv0; + return(tmp); +} + +#define BADCH (int)'?' +#define BADARG (int)':' +#define EMSG "" + +/* + * getopt -- + * Parse argc/argv argument vector. + */ +int +getopt(nargc, nargv, ostr) + int nargc; + char * const nargv[]; + const char *ostr; +{ + static char *__progname = 0; + static char *place = EMSG; /* option letter processing */ + char *oli; /* option letter list index */ + __progname = __progname?__progname:_progname(*nargv); + + _DIAGASSERT(nargv != NULL); + _DIAGASSERT(ostr != NULL); + + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc || *(place = nargv[optind]) != '-') { + place = EMSG; + return (-1); + } + if (place[1] && *++place == '-' /* found "--" */ + && place[1] == '\0') { + ++optind; + place = EMSG; + return (-1); + } + } /* option letter okay? */ + if ((optopt = (int)*place++) == (int)':' || + !(oli = strchr(ostr, optopt))) { + /* + * if the user didn't specify '-' as an option, + * assume it means -1. + */ + if (optopt == (int)'-') + return (-1); + if (!*place) + ++optind; + if (opterr && *ostr != ':') + (void)fprintf(stderr, + "%s: illegal option -- %c\n", __progname, optopt); + return (BADCH); + } + if (*++oli != ':') { /* don't need argument */ + optarg = NULL; + if (!*place) + ++optind; + } + else { /* need an argument */ + if (*place) /* no white space */ + optarg = place; + else if (nargc <= ++optind) { /* no arg */ + place = EMSG; + if (*ostr == ':') + return (BADARG); + if (opterr) + (void)fprintf(stderr, + "%s: option requires an argument -- %c\n", + __progname, optopt); + return (BADCH); + } + else /* white space */ + optarg = nargv[optind]; + place = EMSG; + ++optind; + } + return (optopt); /* dump back option letter */ +} + diff --git a/thirdparty/lws/win32helpers/getopt.h b/thirdparty/lws/win32helpers/getopt.h new file mode 100644 index 0000000000..7137f0379c --- /dev/null +++ b/thirdparty/lws/win32helpers/getopt.h @@ -0,0 +1,33 @@ +#ifndef __GETOPT_H__ +#define __GETOPT_H__ + +#ifdef __cplusplus +extern "C" { +#endif + +extern int opterr; /* if error message should be printed */ +extern int optind; /* index into parent argv vector */ +extern int optopt; /* character checked for validity */ +extern int optreset; /* reset getopt */ +extern char *optarg; /* argument associated with option */ + +struct option +{ + const char *name; + int has_arg; + int *flag; + int val; +}; + +#define no_argument 0 +#define required_argument 1 +#define optional_argument 2 + +int getopt(int, char**, char*); +int getopt_long(int, char**, char*, struct option*, int*); + +#ifdef __cplusplus +} +#endif + +#endif /* __GETOPT_H__ */ diff --git a/thirdparty/lws/win32helpers/getopt_long.c b/thirdparty/lws/win32helpers/getopt_long.c new file mode 100644 index 0000000000..5bcf40060f --- /dev/null +++ b/thirdparty/lws/win32helpers/getopt_long.c @@ -0,0 +1,237 @@ + +/* + * Copyright (c) 1987, 1993, 1994, 1996 + * The Regents of the University of California. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. All advertising materials mentioning features or use of this software + * must display the following acknowledgement: + * This product includes software developed by the University of + * California, Berkeley and its contributors. + * 4. Neither the name of the University nor the names of its contributors + * may be used to endorse or promote products derived from this software + * without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND + * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT + * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY + * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + */ +#include <assert.h> +#include <errno.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include "getopt.h" + +extern int opterr; /* if error message should be printed */ +extern int optind; /* index into parent argv vector */ +extern int optopt; /* character checked for validity */ +extern int optreset; /* reset getopt */ +extern char *optarg; /* argument associated with option */ + +#define __P(x) x +#define _DIAGASSERT(x) assert(x) + +static char * __progname __P((char *)); +int getopt_internal __P((int, char * const *, const char *)); + +static char * +__progname(nargv0) + char * nargv0; +{ + char * tmp; + + _DIAGASSERT(nargv0 != NULL); + + tmp = strrchr(nargv0, '/'); + if (tmp) + tmp++; + else + tmp = nargv0; + return(tmp); +} + +#define BADCH (int)'?' +#define BADARG (int)':' +#define EMSG "" + +/* + * getopt -- + * Parse argc/argv argument vector. + */ +int +getopt_internal(nargc, nargv, ostr) + int nargc; + char * const *nargv; + const char *ostr; +{ + static char *place = EMSG; /* option letter processing */ + char *oli; /* option letter list index */ + + _DIAGASSERT(nargv != NULL); + _DIAGASSERT(ostr != NULL); + + if (optreset || !*place) { /* update scanning pointer */ + optreset = 0; + if (optind >= nargc || *(place = nargv[optind]) != '-') { + place = EMSG; + return (-1); + } + if (place[1] && *++place == '-') { /* found "--" */ + /* ++optind; */ + place = EMSG; + return (-2); + } + } /* option letter okay? */ + if ((optopt = (int)*place++) == (int)':' || + !(oli = strchr(ostr, optopt))) { + /* + * if the user didn't specify '-' as an option, + * assume it means -1. + */ + if (optopt == (int)'-') + return (-1); + if (!*place) + ++optind; + if (opterr && *ostr != ':') + (void)fprintf(stderr, + "%s: illegal option -- %c\n", __progname(nargv[0]), optopt); + return (BADCH); + } + if (*++oli != ':') { /* don't need argument */ + optarg = NULL; + if (!*place) + ++optind; + } else { /* need an argument */ + if (*place) /* no white space */ + optarg = place; + else if (nargc <= ++optind) { /* no arg */ + place = EMSG; + if ((opterr) && (*ostr != ':')) + (void)fprintf(stderr, + "%s: option requires an argument -- %c\n", + __progname(nargv[0]), optopt); + return (BADARG); + } else /* white space */ + optarg = nargv[optind]; + place = EMSG; + ++optind; + } + return (optopt); /* dump back option letter */ +} + +#if 0 +/* + * getopt -- + * Parse argc/argv argument vector. + */ +int +getopt2(nargc, nargv, ostr) + int nargc; + char * const *nargv; + const char *ostr; +{ + int retval; + + if ((retval = getopt_internal(nargc, nargv, ostr)) == -2) { + retval = -1; + ++optind; + } + return(retval); +} +#endif + +/* + * getopt_long -- + * Parse argc/argv argument vector. + */ +int +getopt_long(nargc, nargv, options, long_options, index) + int nargc; + char ** nargv; + char * options; + struct option * long_options; + int * index; +{ + int retval; + + _DIAGASSERT(nargv != NULL); + _DIAGASSERT(options != NULL); + _DIAGASSERT(long_options != NULL); + /* index may be NULL */ + + if ((retval = getopt_internal(nargc, nargv, options)) == -2) { + char *current_argv = nargv[optind++] + 2, *has_equal; + int i, current_argv_len, match = -1; + + if (*current_argv == '\0') { + return(-1); + } + if ((has_equal = strchr(current_argv, '=')) != NULL) { + current_argv_len = has_equal - current_argv; + has_equal++; + } else + current_argv_len = strlen(current_argv); + + for (i = 0; long_options[i].name; i++) { + if (strncmp(current_argv, long_options[i].name, current_argv_len)) + continue; + + if (strlen(long_options[i].name) == (unsigned)current_argv_len) { + match = i; + break; + } + if (match == -1) + match = i; + } + if (match != -1) { + if (long_options[match].has_arg == required_argument || + long_options[match].has_arg == optional_argument) { + if (has_equal) + optarg = has_equal; + else + optarg = nargv[optind++]; + } + if ((long_options[match].has_arg == required_argument) + && (optarg == NULL)) { + /* + * Missing argument, leading : + * indicates no error should be generated + */ + if ((opterr) && (*options != ':')) + (void)fprintf(stderr, + "%s: option requires an argument -- %s\n", + __progname(nargv[0]), current_argv); + return (BADARG); + } + } else { /* No matching argument */ + if ((opterr) && (*options != ':')) + (void)fprintf(stderr, + "%s: illegal option -- %s\n", __progname(nargv[0]), current_argv); + return (BADCH); + } + if (long_options[match].flag) { + *long_options[match].flag = long_options[match].val; + retval = 0; + } else + retval = long_options[match].val; + if (index) + *index = match; + } + return(retval); +} diff --git a/thirdparty/lws/win32helpers/gettimeofday.c b/thirdparty/lws/win32helpers/gettimeofday.c new file mode 100644 index 0000000000..35dd73531d --- /dev/null +++ b/thirdparty/lws/win32helpers/gettimeofday.c @@ -0,0 +1,36 @@ +#include <time.h> +#include <windows.h> //I've omitted context line + +#include "gettimeofday.h" + +int gettimeofday(struct timeval *tv, struct timezone *tz) +{ + FILETIME ft; + unsigned __int64 tmpres = 0; + static int tzflag; + + if (NULL != tv) { + GetSystemTimeAsFileTime(&ft); + + tmpres |= ft.dwHighDateTime; + tmpres <<= 32; + tmpres |= ft.dwLowDateTime; + + /*converting file time to unix epoch*/ + tmpres /= 10; /*convert into microseconds*/ + tmpres -= DELTA_EPOCH_IN_MICROSECS; + tv->tv_sec = (long)(tmpres / 1000000UL); + tv->tv_usec = (long)(tmpres % 1000000UL); + } + + if (NULL != tz) { + if (!tzflag) { + _tzset(); + tzflag++; + } + tz->tz_minuteswest = _timezone / 60; + tz->tz_dsttime = _daylight; + } + + return 0; +} diff --git a/thirdparty/lws/win32helpers/gettimeofday.h b/thirdparty/lws/win32helpers/gettimeofday.h new file mode 100644 index 0000000000..33e7a750fe --- /dev/null +++ b/thirdparty/lws/win32helpers/gettimeofday.h @@ -0,0 +1,27 @@ +#ifndef _GET_TIME_OF_DAY_H +#define _GET_TIME_OF_DAY_H + +#include <time.h> + +#if defined(_MSC_VER) || defined(_MSC_EXTENSIONS) + #define DELTA_EPOCH_IN_MICROSECS 11644473600000000Ui64 +#else + #define DELTA_EPOCH_IN_MICROSECS 11644473600000000ULL +#endif + +#ifdef LWS_MINGW_SUPPORT + #include <winsock2.h> +#endif + +#ifndef _TIMEZONE_DEFINED +struct timezone +{ + int tz_minuteswest; /* minutes W of Greenwich */ + int tz_dsttime; /* type of dst correction */ +}; + +#endif + +int gettimeofday(struct timeval *tv, struct timezone *tz); + +#endif |