├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md ├── SublimeAStyleFormatter.sublime-settings ├── component.mk ├── esp_request.c ├── include ├── esp_request.h ├── req_list.h └── uri_parser.h ├── req_list.c └── uri_parser.c /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # http://editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 4 10 | end_of_line = lf 11 | charset = utf-8 12 | trim_trailing_whitespace = true 13 | insert_final_newline = true 14 | 15 | [*.md] 16 | trim_trailing_whitespace = false 17 | 18 | [Makefile] 19 | indent_style = tab 20 | indent_size = 2 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .output 2 | .vscode 3 | map.txt 4 | main/include/user_config.local.h 5 | bin 6 | build 7 | *.old 8 | sdkconfig 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright [yyyy] [name of copyright owner] 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightweight HTTP client for ESP32 2 | ## Example 3 | 4 | ## This project is no longer supported, please use 5 | 6 | https://github.com/espressif/esp-idf/tree/master/components/esp_http_client 7 | 8 | 9 | ```cpp 10 | int download_callback(request_t *req, char *data, int len) 11 | { 12 | req_list_t *found = req->response->header; 13 | while(found->next != NULL) { 14 | found = found->next; 15 | ESP_LOGI(TAG,"Response header %s:%s", (char*)found->key, (char*)found->value); 16 | } 17 | //or 18 | found = req_list_get_key(req->response->header, "Content-Length"); 19 | if(found) { 20 | ESP_LOGI(TAG,"Get header %s:%s", (char*)found->key, (char*)found->value); 21 | } 22 | ESP_LOGI(TAG,"%s", data); 23 | return 0; 24 | } 25 | static void request_task(void *pvParameters) 26 | { 27 | request_t *req; 28 | int status; 29 | xEventGroupWaitBits(wifi_event_group, CONNECTED_BIT, false, true, portMAX_DELAY); 30 | ESP_LOGI(TAG, "Connected to AP, freemem=%d",esp_get_free_heap_size()); 31 | // vTaskDelay(1000/portTICK_RATE_MS); 32 | req = req_new("http://httpbin.org/post"); 33 | //or 34 | //request *req = req_new("https://google.com"); //for SSL 35 | req_setopt(req, REQ_SET_METHOD, "POST"); 36 | req_setopt(req, REQ_SET_POSTFIELDS, "test=data&test2=data2"); 37 | req_setopt(req, REQ_FUNC_DOWNLOAD_CB, download_callback); 38 | status = req_perform(req); 39 | req_clean(req); 40 | vTaskDelete(NULL); 41 | } 42 | 43 | ``` 44 | 45 | ## Websocket 46 | 47 | ```cpp 48 | 49 | int websocket_cb(request_t *req, int status, void *buffer, int len) 50 | { 51 | switch(status) { 52 | case WS_CONNECTED: 53 | ESP_LOGI(TAG, "websocket connected"); 54 | req_write(req, "hello world", 11); 55 | break; 56 | case WS_DATA: 57 | ((char*)buffer)[len] = 0; 58 | ESP_LOGI(TAG, "websocket data = %s", (char*)buffer); 59 | req_close(req); 60 | break; 61 | case WS_DISCONNECTED: 62 | ESP_LOGI(TAG, "websocket disconnected"); 63 | req_clean(req); 64 | req = NULL; 65 | break; 66 | } 67 | return 0; 68 | } 69 | void app() 70 | { 71 | request_t *req = req_new("ws://echo.websocket.org"); // or wss://echo.websocket.org 72 | req_setopt(req, REQ_FUNC_WEBSOCKET, websocket_cb); 73 | req_perform(req); 74 | } 75 | 76 | ``` 77 | 78 | ## Usage 79 | - Create ESP-IDF application https://github.com/espressif/esp-idf-template 80 | - Clone `git submodule add https://github.com/tuanpmt/esp-request components/esp-request` 81 | - Example `esp-request` application: https://github.com/tuanpmt/esp-request-app 82 | - OTA application using `esp-request`: https://github.com/tuanpmt/esp32-fota 83 | 84 | ## API 85 | 86 | ### Function 87 | - `req_new` 88 | - `req_setopt` 89 | - `req_clean` 90 | 91 | ### Options for `req_setopt` 92 | - REQ_SET_METHOD - `req_setopt(req, REQ_SET_METHOD, "GET");//or POST/PUT/DELETE` 93 | - REQ_SET_HEADER - `req_setopt(req, REQ_SET_HEADER, "HeaderKey: HeaderValue");` 94 | - REQ_SET_HOST - `req_setopt(req, REQ_SET_HOST, "google.com"); //or 192.168.0.1` 95 | - REQ_SET_PORT - `req_setopt(req, REQ_SET_PORT, "80");//must be string` 96 | - REQ_SET_PATH - `req_setopt(req, REQ_SET_PATH, "/path");` 97 | - REQ_SET_SECURITY 98 | - REQ_SET_URI - `req_setopt(req, REQ_SET_URI, "http://uri.com"); //will replace host, port, path, security and Auth if present` 99 | - REQ_SET_DATAFIELDS 100 | - REQ_SET_UPLOAD_LEN - Not effect for now 101 | - REQ_FUNC_DOWNLOAD_CB - `req_setopt(req, REQ_FUNC_DOWNLOAD_CB, download_callback);` 102 | - REQ_FUNC_UPLOAD_CB 103 | - REQ_FUNC_WEBSOCKET 104 | - REQ_REDIRECT_FOLLOW - `req_setopt(req, REQ_REDIRECT_FOLLOW, "true"); //or "false"` 105 | 106 | ### URI format 107 | - Follow this: https://en.wikipedia.org/wiki/Uniform_Resource_Identifier 108 | 109 | ## Todo 110 | - [x] Support URI parser 111 | - [x] Follow redirect 112 | - [x] Support SSL 113 | - [x] Support Set POST Fields (simple) 114 | - [x] Support Websocket & Websocket Secure 115 | - [ ] Support Basic Auth 116 | - [ ] Support Upload multipart 117 | - [ ] Support Cookie 118 | 119 | ## Known Issues 120 | - Uri parse need more work 121 | 122 | ## Authors 123 | - [Tuan PM](https://twitter.com/tuanpmt) 124 | -------------------------------------------------------------------------------- /SublimeAStyleFormatter.sublime-settings: -------------------------------------------------------------------------------- 1 | { 2 | // NOTE: You should always edit options in user file, not this file. 3 | 4 | // Print debug message 5 | "debug": false, 6 | 7 | // Auto format on file save 8 | "autoformat_on_save": false, 9 | 10 | // The mapping key is `syntax name`, and the value is `formatting mode`. 11 | // Note that the value for each mapping should be "c", "java" or "cs". 12 | "user_defined_syntax_mode_mapping": { 13 | // For example: 14 | 15 | /* 16 | "arduino": "c", 17 | "pde": "java", 18 | "apex": "java" 19 | */ 20 | }, 21 | 22 | // Please visit http://astyle.sourceforge.net/astyle.html for more information 23 | "options_default": { 24 | // Default bracket style 25 | // Can be one of "allman", "bsd", "break", "java", "attach", "kr", "k&r", 26 | // "k/r" "stroustrup", "whitesmith", "banner", "gnu", "linux", "horstmann", 27 | // "1tbs", "otbs ", "google", "pico", "lisp", "python", "vtk", or null 28 | // for default. 29 | "style": "k&r", 30 | 31 | // Tab options 32 | // "indent": Can be one of "spaces", "tab" "force-tab", "force-tab-x", or null 33 | // "indent-spaces": Can be either null or numbers 34 | // 35 | // While both "indent" or "indent-spaces" are null (default), the indentation options 36 | // will be retrieved from Sublime Text view settings: `tab_size`, `translate_tabs_to_spaces`: 37 | // 1. If `translate_tabs_to_spaces` is true, "indent" will be "spaces", otherwise, "tab"; 38 | // 2. "indent-spaces" will equal to `tab_size`. 39 | "indent": null, 40 | "indent-spaces": null, 41 | 42 | // === Bracket Modify Options === 43 | // Attach brackets to a namespace statement. This is done regardless of 44 | // the bracket style being used. 45 | "attach-namespaces": false, 46 | 47 | // Attach brackets to a class statement. This is done regardless of the 48 | // bracket style being used. 49 | "attach-classes": false, 50 | 51 | // Attach brackets to class and struct inline function definitions. This 52 | // is not done for run-in type brackets (Horstmann and Pico styles). This 53 | // option is effective for C++ files only. 54 | "attach-inlines": false, 55 | 56 | // Attach brackets to a bracketed extern "C" statement. This is done 57 | // regardless of the bracket style being used. This option is effective 58 | // for C++ files only. 59 | // 60 | // An extern "C" statement that is part of a function definition is 61 | // formatted according to the requested bracket style. Bracketed extern 62 | // "C" statements are unaffected by the bracket style and this option is 63 | // the only way to change them. 64 | "attach-extern-c": false, 65 | 66 | // === Indentation Options === 67 | 68 | // Indent 'class' and 'struct' blocks so that the blocks 'public:', 69 | // 'protected:' and 'private:' are indented. The struct blocks are 70 | // indented only if an access modifier is declared somewhere in the 71 | // struct. The entire block is indented. This option is effective for C++ files only. 72 | "indent-classes": false, 73 | 74 | // Indent 'class' and 'struct' access modifiers, 'public:', 'protected:' 75 | // and 'private:', one half indent. The rest of the class is not indented. 76 | // This option is effective for C++ files only. If used with indent‑classes 77 | // this option will be ignored. 78 | "indent-modifiers": false, 79 | 80 | // Indent 'switch' blocks so that the 'case X:' statements are indented 81 | // in the switch block. The entire case block is indented. 82 | "indent-switches": false, 83 | 84 | // Indent 'case X:' blocks from the 'case X:' headers. Case statements 85 | // not enclosed in blocks are NOT indented. 86 | "indent-cases": false, 87 | 88 | // Add extra indentation to namespace blocks. This option has 89 | // no effect on Java files. 90 | "indent-namespaces": false, 91 | 92 | // Add extra indentation to labels so they appear 1 indent less than the 93 | // current indentation, rather than being flushed to the left (the default). 94 | "indent-labels": false, 95 | 96 | // Indent preprocessor blocks at bracket level zero, and immediately within 97 | // a namespace. There are restrictions on what will be indented. Blocks 98 | // within methods, classes, arrays, etc, will not be indented. Blocks 99 | // containing brackets or multi-line define statements will not be indented. 100 | // Without this option the preprocessor block is not indented. 101 | "indent-preproc-block": true, 102 | 103 | // Indent multi-line preprocessor definitions ending with a backslash. 104 | // Should be used with `convert-tabs` for proper results. Does a pretty 105 | // good job, but cannot perform miracles in obfuscated preprocessor 106 | // definitions. Without this option the preprocessor statements remain unchanged. 107 | "indent-preproc-define": true, 108 | 109 | // Indent preprocessor conditional statements to the same level as the 110 | // source code. 111 | "indent-preproc-cond": false, 112 | 113 | // Indent C++ comments beginning in column one. By default C++ comments 114 | // beginning in column one are not indented. This option will allow the 115 | // comments to be indented with the code. 116 | "indent-col1-comments": false, 117 | 118 | // Set the minimal indent that is added when a header is built of multiple 119 | // lines. This indent helps to easily separate the header from the command 120 | // statements that follow. The value for # indicates a number of indents 121 | // and is a minimum value. The indent may be greater to align with the data 122 | // on the previous line. 123 | // The valid values are: 124 | // 0 - no minimal indent. The lines will be aligned with the paren on the 125 | // preceding line. 126 | // 1 - indent at least one additional indent. 127 | // 2 - indent at least two additional indents. 128 | // 3 - indent at least one-half an additional indent. This is intended for 129 | // large indents (e.g. 8). 130 | // The default value is 2, two additional indents. 131 | "min-conditional-indent": 2, 132 | 133 | // Set the maximum spaces to indent a continuation line. 134 | // The maximum spaces indicate a number of columns and 135 | // must not be greater than 120. A maximum of less 136 | // than two indent lengths will be ignored. This option 137 | // will prevent continuation lines from extending too 138 | // far to the right. Setting a larger value will allow 139 | // the code to be extended further to the right. 140 | // 141 | // range: [40, 120] 142 | "max-instatement-indent": 40, 143 | 144 | // === Padding Options === 145 | 146 | // null - Do nothing 147 | // "default" - Pad empty lines around header blocks (e.g. 'if', 148 | // 'for', 'while'...). 149 | // "all" - Pad empty lines around header blocks (e.g. 'if', 150 | // 'for', 'while'...). Treat closing header blocks 151 | // (e.g. 'else', 'catch') as stand-alone blocks. 152 | "break-blocks": null, 153 | 154 | // Insert space padding around operators. Any end of line comments 155 | // will remain in the original column, if possible. Note that there 156 | // is no option to unpad. Once padded, they stay padded. 157 | "pad-oper": true, 158 | 159 | // Insert space padding around parenthesis on both the outside and 160 | // the inside. Any end of line comments will remain in the original 161 | // column, if possible. 162 | "pad-paren": false, 163 | 164 | // Insert space padding around parenthesis on the outside only. Any 165 | // end of line comments will remain in the original column, if possible. 166 | // This can be used with `unpad-paren` below to remove unwanted spaces. 167 | "pad-paren-out": false, 168 | 169 | // Insert space padding around the first parenthesis in a series on the 170 | // outside only. Any end of line comments will remain in the original 171 | // column, if possible. This can be used with unpad-paren below to remove 172 | // unwanted spaces. If used with pad-paren or pad-paren-out, this 173 | // option will be ignored. If used with pad-paren-in, the result will 174 | // be the same as pad-paren. 175 | "pad-first-paren-out": false, 176 | 177 | // Insert space padding around parenthesis on the inside only. Any 178 | // end of line comments will remain in the original column, if possible. 179 | // This can be used with `unpad-paren` below to remove unwanted spaces. 180 | "pad-paren-in": false, 181 | 182 | // Insert space padding after paren headers only (e.g. 'if', 'for', 183 | //'while'...). Any end of line comments will remain in the original 184 | // column, if possible. This can be used with unpad-paren to remove 185 | // unwanted spaces. 186 | "pad-header": true, 187 | 188 | // Remove extra space padding around parenthesis on the inside and outside. 189 | // Any end of line comments will remain in the original column, if possible. 190 | // This option can be used in combination with the paren padding options 191 | // `pad-paren`, `pad-paren-out`, `pad-paren-in`, and `pad-header` above. 192 | // Only padding that has not been requested by other options will be removed. 193 | // For example, if a source has parens padded on both the inside and outside, 194 | // and you want inside only. You need to use unpad-paren to remove the outside 195 | // padding, and pad-paren-in to retain the inside padding. Using only `pad-paren-in` 196 | // would not remove the outside padding. 197 | "unpad-paren": false, 198 | 199 | // Delete empty lines within a function or method. Empty lines outside of functions 200 | // or methods are NOT deleted. If used with break-blocks or break-blocks=all it will 201 | // delete all lines EXCEPT the lines added by the `break-blocks` options. 202 | "delete-empty-lines": false, 203 | 204 | // Fill empty lines with the white space of the previous line. 205 | "fill-empty-lines": false, 206 | 207 | // Attach a pointer or reference operator (* or &) to either the variable type (left) 208 | // or variable name (right), or place it between the type and name (middle). 209 | // The spacing between the type and name will be preserved, if possible. To format 210 | // references separately use the following `align-reference` option. 211 | // can be one of null, "type", "middle" or "name" 212 | "align-pointer": null, 213 | 214 | // This option will align references separate from pointers. Pointers are not changed 215 | // by this option. If pointers and references are to be aligned the same, use the 216 | // previous `align-pointer` option. The option align-reference=none will not change 217 | // the reference alignment. The other options are the same as for `align-pointer`. 218 | // In the case of a reference to a pointer (*&) with conflicting alignments, the 219 | // `align-pointer` value will be used. 220 | // can be one of "none", "type", "middle", "name", or null for default. 221 | "align-reference": null, 222 | 223 | // === Formatting Options === 224 | 225 | // When `style` is "attach", "linux" or "stroustrup", this breaks closing headers 226 | // (e.g. 'else', 'catch', ...) from their immediately preceding closing brackets. 227 | // Closing header brackets are always broken with broken brackets, horstmann 228 | // rackets, indented blocks, and indented brackets. 229 | "break-closing-brackets": false, 230 | 231 | // Break "else if" header combinations into separate lines. This option has no effect 232 | // if keep-one-line-statements is used, the "else if" statements will remain as they are. 233 | // If this option is NOT used, "else if" header combinations will be placed on a single line. 234 | "break-elseifs": false, 235 | 236 | // Add brackets to unbracketed one line conditional statements (e.g. 'if', 'for', 'while'...). 237 | // The statement must be on a single line. The brackets will be added according to the 238 | // currently requested predefined style or bracket type. If no style or bracket type is 239 | // requested the brackets will be attached. If `add-one-line-brackets` is also used the 240 | // result will be one line brackets. 241 | "add-brackets": false, 242 | 243 | // Add one line brackets to unbracketed one line conditional statements 244 | // (e.g. 'if', 'for', 'while'...). The statement must be on a single line. 245 | // The option implies `keep-one-line-blocks` and will not break the one line blocks. 246 | "add-one-line-brackets": false, 247 | 248 | // Remove brackets from conditional statements (e.g. 'if', 'for', 'while'...). 249 | // The statement must be a single statement on a single line. If 250 | // --add-brackets or --add-one-line-brackets is also used the result will 251 | // be to add brackets. Brackets will not be removed from 252 | // "One True Brace Style", --style=1tbs. 253 | "remove-brackets": false, 254 | 255 | // Don't break one-line blocks. 256 | "keep-one-line-blocks": true, 257 | 258 | // Don't break complex statements and multiple statements residing on a single line. 259 | "keep-one-line-statements": true, 260 | 261 | // Closes whitespace in the angle brackets of template definitions. Closing 262 | // the ending angle brackets is now allowed by the C++11 standard. 263 | // Be sure your compiler supports this before making the changes. 264 | "close-templates": false, 265 | 266 | // Remove the preceding '*' in a multi-line comment that begins a line. 267 | // A trailing '*', if present, is also removed. Text that is less than one 268 | // is indent is indented to one indent. Text greater than one indent is 269 | // not changed. Multi-line comments that begin a line but without the 270 | // preceding '*' are indented to one indent for consistency. This can 271 | // slightly modify the indentation of commented out blocks of code. 272 | // Lines containing all '*' are left unchanged. Extra spacing is removed 273 | // from the comment close '*/'. 274 | "remove-comment-prefix": false, 275 | 276 | // The option max-code-length will break a line if the code exceeds # characters. 277 | // The valid values are 50 thru 200. Lines without logical conditionals will 278 | // break on a logical conditional (||, &&, ...), comma, paren, semicolon, or space. 279 | // 280 | // Some code will not be broken, such as comments, quotes, and arrays. 281 | // If used with keep-one-line-blocks or add-one-line-brackets the blocks 282 | // will NOT be broken. If used with keep-one-line-statements the statements 283 | // will be broken at a semicolon if the line goes over the maximum length. 284 | // If there is no available break point within the max code length, the 285 | // line will be broken at the first available break point after the max code length. 286 | // 287 | // By default logical conditionals will be placed first on the new line. 288 | // The option break-after-logical will cause the logical conditionals to be 289 | // placed last on the previous line. This option has no effect without max-code-length. 290 | "max-code-length": -1, 291 | "break-after-logical": false, 292 | 293 | // == Objective-C Options == 294 | // Because of the longer indents sometimes needed for Objective-C, the option 295 | // "max-instatement-indent" may need to be increased. If you are not getting 296 | // the paren and square bracket alignment you want, try increasing this value. 297 | 298 | // Align the colons in Objective-C method declarations. This option is effective 299 | // for Objective-C files only. 300 | "align-method-colon": false, 301 | 302 | // Insert space padding after the '-' or '+' Objective-C method prefix. This will 303 | // add exactly one space. Any additional spaces will be deleted. This option is 304 | // effective for Objective-C files only. 305 | "pad-method-prefix": false, 306 | 307 | // Remove all space padding after the '-' or '+' Objective-C method prefix. 308 | // If used with pad-method-prefix, this option will be ignored. This option 309 | // is effective for Objective-C files only. 310 | "unpad-method-prefix": false, 311 | 312 | // Add or remove space padding before or after the colons in an Objective-C 313 | // method call. These options will pad exactly one space. Any additional 314 | // spaces will be deleted. Colons immediarely preceeding a paren will not 315 | // be padded. This option is effective for Objective-C files only. 316 | // 317 | // Can be one of "none", "all", "after" or "before", or null for default. 318 | "pad-method-colon": null 319 | }, 320 | 321 | // 322 | // Language Specific Options 323 | // 324 | // You can override default options in language-specific options here. 325 | // Addtional options are also provided: 326 | // 327 | // "additional_options": Addtional options following astyle options file style 328 | // (http://astyle.sourceforge.net/astyle.html). 329 | // e.g.: "additional_options": ["--indent=spaces=2", "--convert-tabs"] 330 | // 331 | // "additional_options_file": Addtional options file for astyle (aka astylerc), you must specify 332 | // a full path for that file, environment variable may be included. 333 | // e.g.: "additional_options_file": "%USERPROFILE%/astylerc" // (Windows) 334 | // e.g.: "additional_options_file": "$HOME/.astylerc" // (Linux) 335 | // === Additional variables that you may use === 336 | // $file_path The directory of the current file, e.g., C:\Files. 337 | // $file The full path to the current file, e.g., C:\Files\Chapter1.txt. 338 | // $file_name The name portion of the current file, e.g., Chapter1.txt. 339 | // $file_extension The extension portion of the current file, e.g., txt. 340 | // $file_base_name The name-only portion of the current file, e.g., Document. 341 | // $packages The full path to the Packages folder. 342 | // The following requires "Sublime Text 3" 343 | // $project The full path to the current project file. 344 | // $project_path The directory of the current project file. 345 | // $project_name The name portion of the current project file. 346 | // $project_extension The extension portion of the current project file. 347 | // $project_base_name The name-only portion of the current project file. 348 | // 349 | // "use_only_additional_options": While true, SublimeAStyleFormatter will *only* process 350 | // options in *both* "additional_options" and "additional_options_file". 351 | 352 | 353 | // Language-specific options for C 354 | "options_c": { 355 | "use_only_additional_options": false, 356 | "additional_options_file": "", 357 | "additional_options": [] 358 | }, 359 | 360 | // Language-specific for C++ 361 | "options_c++": { 362 | "use_only_additional_options": false, 363 | "additional_options_file": "", 364 | "additional_options": [] 365 | }, 366 | 367 | // Language-specific for Java 368 | "options_java": { 369 | "style": "java", 370 | "use_only_additional_options": false, 371 | "additional_options_file": "", 372 | "additional_options": [] 373 | }, 374 | 375 | // Language-specific for C# 376 | "options_cs": { 377 | "use_only_additional_options": false, 378 | "additional_options_file": "", 379 | "additional_options": [] 380 | } 381 | } 382 | -------------------------------------------------------------------------------- /component.mk: -------------------------------------------------------------------------------- 1 | # 2 | # "main" pseudo-component makefile. 3 | # 4 | # (Uses default behaviour of compiling all source files in directory, adding 'include' to include path.) 5 | 6 | 7 | -------------------------------------------------------------------------------- /esp_request.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @2017 3 | * Tuan PM 4 | */ 5 | #include "freertos/FreeRTOS.h" 6 | #include "freertos/task.h" 7 | 8 | #include 9 | #include 10 | #include 11 | #include "esp_request.h" 12 | #include "esp_log.h" 13 | #include "esp_system.h" 14 | 15 | #include "lwip/sockets.h" 16 | #include "lwip/dns.h" 17 | #include "lwip/netdb.h" 18 | #include "lwip/igmp.h" 19 | #include "req_list.h" 20 | 21 | #include "mbedtls/base64.h" 22 | #include "mbedtls/sha1.h" 23 | 24 | #define REQ_TAG "HTTP_REQ" 25 | 26 | #define REQ_CHECK(check, log, ret) if(check) {ESP_LOGE(REQ_TAG, log);ret;} 27 | 28 | static int resolve_dns(const char *host, struct sockaddr_in *ip) { 29 | struct hostent *he; 30 | struct in_addr **addr_list; 31 | he = gethostbyname(host); 32 | if(he == NULL) 33 | return -1; 34 | addr_list = (struct in_addr **)he->h_addr_list; 35 | if(addr_list[0] == NULL) 36 | return -1; 37 | ip->sin_family = AF_INET; 38 | memcpy(&ip->sin_addr, addr_list[0], sizeof(ip->sin_addr)); 39 | return 0; 40 | } 41 | 42 | static char *http_auth_basic_encode(const char *username, const char *password) 43 | { 44 | return NULL; 45 | } 46 | 47 | 48 | static int nossl_connect(request_t *req) 49 | { 50 | int req_socket; 51 | struct sockaddr_in remote_ip; 52 | struct timeval tv; 53 | req_list_t *host, *port, *timeout; 54 | bzero(&remote_ip, sizeof(struct sockaddr_in)); 55 | //if stream_host is not ip address, resolve it AF_INET,servername,&serveraddr.sin_addr 56 | host = req_list_get_key(req->opt, "host"); 57 | REQ_CHECK(host == NULL, "host = NULL", return -1); 58 | 59 | if(inet_pton(AF_INET, (const char*)host->value, &remote_ip.sin_addr) != 1) { 60 | if(resolve_dns((const char*)host->value, &remote_ip) < 0) { 61 | return -1; 62 | } 63 | } 64 | 65 | req_socket = socket(PF_INET, SOCK_STREAM, 0); 66 | REQ_CHECK(req_socket < 0, "socket failed", return -1); 67 | 68 | port = req_list_get_key(req->opt, "port"); 69 | if(port == NULL) 70 | return -1; 71 | 72 | remote_ip.sin_family = AF_INET; 73 | remote_ip.sin_port = htons(atoi(port->value)); 74 | 75 | tv.tv_sec = 10; //default timeout is 10 seconds 76 | timeout = req_list_get_key(req->opt, "timeout"); 77 | if(timeout) { 78 | tv.tv_sec = atoi(timeout->value); 79 | } 80 | tv.tv_usec = 0; 81 | setsockopt(req_socket, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)); 82 | 83 | ESP_LOGD(REQ_TAG, "[sock=%d],connecting to server IP:%s,Port:%s...", 84 | req_socket, ipaddr_ntoa((const ip_addr_t*)&remote_ip.sin_addr.s_addr), (char*)port->value); 85 | if(connect(req_socket, (struct sockaddr *)(&remote_ip), sizeof(struct sockaddr)) != 0) { 86 | close(req_socket); 87 | req->socket = -1; 88 | return -1; 89 | } 90 | req->socket = req_socket; 91 | return req_socket; 92 | } 93 | static int ssl_connect(request_t *req) 94 | { 95 | nossl_connect(req); 96 | REQ_CHECK(req->socket < 0, "socket failed", return -1); 97 | 98 | //TODO: Check 99 | req->ctx = SSL_CTX_new(TLSv1_1_client_method()); 100 | req->ssl = SSL_new(req->ctx); 101 | SSL_set_fd(req->ssl, req->socket); 102 | SSL_connect(req->ssl); 103 | return 0; 104 | } 105 | static char *ws_esc(char *buffer, int len, int *outlen) 106 | { 107 | int header_len = 0; 108 | char *data_buffer = malloc(len + MAX_WEBSOCKET_HEADER_SIZE), *mask; 109 | // Opcode; final fragment 110 | data_buffer[header_len++] = WS_OPCODE_BINARY | WS_FIN; 111 | 112 | // NOTE: no support for > 16-bit sized messages 113 | if(len > 125) { 114 | data_buffer[header_len++] = WS_SIZE16 | WS_MASK; 115 | data_buffer[header_len++] = (uint8_t)(len >> 8); 116 | data_buffer[header_len++] = (uint8_t)(len & 0xFF); 117 | } else { 118 | data_buffer[header_len++] = (uint8_t)(len | WS_MASK); 119 | } 120 | mask = &data_buffer[header_len]; 121 | data_buffer[header_len++] = rand() & 0xFF; 122 | data_buffer[header_len++] = rand() & 0xFF; 123 | data_buffer[header_len++] = rand() & 0xFF; 124 | data_buffer[header_len++] = rand() & 0xFF; 125 | 126 | for(int i = 0; i < len; ++i) { 127 | data_buffer[header_len++] = (buffer[i] ^ mask[i % 4]); 128 | } 129 | *outlen = header_len; 130 | return data_buffer; 131 | } 132 | static int ws_unesc(unsigned char *ws_buffer, unsigned char *buffer, int len) 133 | { 134 | int payloadLen; 135 | unsigned char *data_ptr = ws_buffer, opcode, mask, *maskKey = NULL; 136 | if(len <= 0) 137 | { 138 | return -1; 139 | } 140 | opcode = (*data_ptr & 0x0F); 141 | data_ptr ++; 142 | mask = ((*data_ptr >> 7) & 0x01); 143 | payloadLen = (*data_ptr & 0x7F); 144 | data_ptr++; 145 | ESP_LOGI(REQ_TAG, "Opcode: %d, mask: %d, len: %d\r\n", opcode, mask, payloadLen); 146 | if(payloadLen == 126) { 147 | // headerLen += 2; 148 | payloadLen = data_ptr[0] << 8 | data_ptr[1]; 149 | data_ptr += 2; 150 | } else if(payloadLen == 127) { 151 | // headerLen += 8; 152 | 153 | if(data_ptr[0] != 0 || data_ptr[1] != 0 || data_ptr[2] != 0 || data_ptr[3] != 0) { 154 | // really too big! 155 | payloadLen = 0xFFFFFFFF; 156 | } else { 157 | payloadLen = data_ptr[4] << 24 | data_ptr[5] << 16 | data_ptr[6] << 8 | data_ptr[7]; 158 | } 159 | data_ptr += 8; 160 | } 161 | 162 | if(mask) { 163 | maskKey = data_ptr; 164 | data_ptr += 4; 165 | for(int i = 0; i < payloadLen; i++) { 166 | buffer[i] = (data_ptr[i] ^ maskKey[i % 4]); 167 | } 168 | } else { 169 | memcpy(buffer, data_ptr, payloadLen); 170 | } 171 | return payloadLen; 172 | } 173 | 174 | static int ssl_write(request_t *req, char *buffer, int len) 175 | { 176 | if(req->valid_websocket) { 177 | int ws_len = 0; 178 | char *ws_buffer = ws_esc(buffer, len, &ws_len); 179 | SSL_write(req->ssl, ws_buffer, ws_len); 180 | free(ws_buffer); 181 | return len; 182 | } 183 | return SSL_write(req->ssl, buffer, len); 184 | } 185 | 186 | static int nossl_write(request_t *req, char *buffer, int len) 187 | { 188 | if(req->valid_websocket) { 189 | int ws_len = 0; 190 | char *ws_buffer = ws_esc(buffer, len, &ws_len); 191 | write(req->socket, ws_buffer, ws_len); 192 | free(ws_buffer); 193 | return len; 194 | } 195 | return write(req->socket, buffer, len); 196 | } 197 | 198 | static int ssl_read(request_t *req, char *buffer, int len) 199 | { 200 | int ret = -1; 201 | if(req->valid_websocket) { 202 | unsigned char *ws_buffer = (unsigned char*) malloc(len + MAX_WEBSOCKET_HEADER_SIZE); 203 | ret = SSL_read(req->ssl, ws_buffer, len + MAX_WEBSOCKET_HEADER_SIZE); 204 | ret = ws_unesc(ws_buffer, (unsigned char *)buffer, ret); 205 | free(ws_buffer); 206 | } else { 207 | ret = SSL_read(req->ssl, buffer, len); 208 | } 209 | 210 | return ret; 211 | } 212 | 213 | static int nossl_read(request_t *req, char *buffer, int len) 214 | { 215 | int ret = -1; 216 | if(req->valid_websocket) { 217 | unsigned char *ws_buffer = (unsigned char*) malloc(len + MAX_WEBSOCKET_HEADER_SIZE); 218 | ret = read(req->socket, ws_buffer, len + MAX_WEBSOCKET_HEADER_SIZE); 219 | ret = ws_unesc(ws_buffer, (unsigned char *)buffer, ret); 220 | free(ws_buffer); 221 | } else { 222 | ret = read(req->socket, buffer, len); 223 | } 224 | return ret; 225 | } 226 | static int ssl_close(request_t *req) 227 | { 228 | SSL_shutdown(req->ssl); 229 | SSL_free(req->ssl); 230 | close(req->socket); 231 | SSL_CTX_free(req->ctx); 232 | return 0; 233 | } 234 | 235 | static int nossl_close(request_t *req) 236 | { 237 | return close(req->socket); 238 | } 239 | static int req_setopt_from_uri(request_t *req, const char* uri) 240 | { 241 | //TODO: relative path 242 | parsed_uri_t *puri; 243 | char port[] = "443"; 244 | puri = parse_uri(uri); 245 | REQ_CHECK(puri == NULL, "Error parse uri", return -1); 246 | 247 | req->is_websocket = 0; 248 | 249 | if(strcasecmp(puri->scheme, "https") == 0) { 250 | req_setopt(req, REQ_SET_SECURITY, "true"); 251 | } else if(strcasecmp(puri->scheme, "ws") == 0) { 252 | req_setopt(req, REQ_SET_SECURITY, "false"); 253 | req->is_websocket = 1; 254 | strcpy(port, "80"); 255 | port[2] = 0; 256 | } else if(strcasecmp(puri->scheme, "wss") == 0) { 257 | req_setopt(req, REQ_SET_SECURITY, "true"); 258 | req->is_websocket = 1; 259 | } else { 260 | req_setopt(req, REQ_SET_SECURITY, "false"); 261 | strcpy(port, "80"); 262 | port[2] = 0; 263 | } 264 | 265 | if(puri->username && puri->password) { 266 | char *auth = http_auth_basic_encode(puri->username, puri->password); 267 | if(auth) { 268 | req_setopt(req, REQ_SET_HEADER, auth); 269 | free(auth); 270 | } 271 | 272 | } 273 | if(puri->username) { 274 | req_list_set_key(req->opt, "username", puri->username); 275 | } 276 | if(puri->password) { 277 | req_list_set_key(req->opt, "password", puri->password); 278 | } 279 | 280 | req_setopt(req, REQ_SET_HOST, puri->host); 281 | req_setopt(req, REQ_SET_PATH, puri->path); 282 | //port 283 | if(puri->port) { 284 | req_setopt(req, REQ_SET_PORT, puri->port); 285 | } else { 286 | req_setopt(req, REQ_SET_PORT, port); 287 | } 288 | free_parsed_uri(puri); 289 | return 0; 290 | } 291 | request_t *req_new(const char *uri) 292 | { 293 | unsigned char random_key[16] = { 0 }, b64_key[32] = {0}; 294 | request_t *req = malloc(sizeof(request_t)); 295 | 296 | REQ_CHECK(req == NULL, "Error allocate req", return NULL); 297 | memset(req, 0, sizeof(request_t)); 298 | 299 | req->buffer = malloc(sizeof(req_buffer_t)); 300 | REQ_CHECK(req->buffer == NULL, "Error allocate buffer", return NULL); 301 | memset(req->buffer, 0, sizeof(req_buffer_t)); 302 | 303 | req->buffer->data = malloc(REQ_BUFFER_LEN + 1); //1 byte null for end of string 304 | //TODO: Free req before return 305 | REQ_CHECK(req->buffer->data == NULL, "Error allocate buffer", return NULL); 306 | 307 | req->opt = malloc(sizeof(req_list_t)); 308 | memset(req->opt, 0, sizeof(req_list_t)); 309 | req->header = malloc(sizeof(req_list_t)); 310 | memset(req->header, 0, sizeof(req_list_t)); 311 | 312 | req->response = malloc(sizeof(response_t)); 313 | REQ_CHECK(req->response == NULL, "Error create response", return NULL); 314 | memset(req->response, 0, sizeof(response_t)); 315 | 316 | req->response->header = malloc(sizeof(req_list_t)); 317 | REQ_CHECK(req->response->header == NULL, "Error create response header", return NULL); 318 | memset(req->response->header, 0, sizeof(req_list_t)); 319 | 320 | req_setopt(req, REQ_SET_PROTOCOL, (void*)PROTOCOL_HTTP); 321 | req->socket = -1; 322 | 323 | req_setopt_from_uri(req, uri); 324 | 325 | req->valid_websocket = 0; 326 | if(req->is_websocket) { 327 | int i; 328 | for(i = 0; i < sizeof(random_key); i++) { 329 | random_key[i] = rand() & 0xFF; 330 | } 331 | size_t outlen = 0; 332 | mbedtls_base64_encode(b64_key, 32, &outlen, random_key, 16); 333 | 334 | req_setopt(req, REQ_SET_HEADER, "Connection: Upgrade"); 335 | req_setopt(req, REQ_SET_HEADER, "Upgrade: websocket"); 336 | req_setopt(req, REQ_SET_HEADER, "Sec-WebSocket-Version: 13"); 337 | req_list_set_key(req->header, "Sec-WebSocket-Key", (char *)b64_key); 338 | } 339 | req_setopt(req, REQ_REDIRECT_FOLLOW, "true"); 340 | req_setopt(req, REQ_SET_METHOD, "GET"); 341 | req_setopt(req, REQ_SET_HEADER, "User-Agent: ESP32 Http Client"); 342 | return req; 343 | 344 | } 345 | 346 | void req_setopt(request_t *req, REQ_OPTS opt, void* data) 347 | { 348 | int post_len; 349 | char len_str[10] = {0}; 350 | req_list_t *tmp; 351 | char *host_w_port = malloc(1024); 352 | if(!req || !data) 353 | return; 354 | switch(opt) { 355 | case REQ_SET_METHOD: 356 | req_list_set_key(req->opt, "method", data); 357 | break; 358 | case REQ_SET_HEADER: 359 | req_list_set_from_string(req->header, data); 360 | break; 361 | case REQ_SET_HOST: 362 | req_list_set_key(req->opt, "host", data); 363 | tmp = req_list_get_key(req->opt, "port"); 364 | if(tmp != NULL) { 365 | sprintf(host_w_port, "%s:%s", (char*)data, (char*)tmp->value); 366 | } else { 367 | sprintf(host_w_port, "%s", (char*)data); 368 | } 369 | req_list_set_key(req->header, "Host", host_w_port); 370 | break; 371 | case REQ_SET_PORT: 372 | req_list_set_key(req->opt, "port", data); 373 | tmp = req_list_get_key(req->opt, "host"); 374 | if(tmp != NULL) { 375 | sprintf(host_w_port, "%s:%s", (char*)tmp->value, (char*)data); 376 | req_list_set_key(req->header, "Host", host_w_port); 377 | } 378 | 379 | break; 380 | case REQ_SET_PATH: 381 | req_list_set_key(req->opt, "path", data); 382 | break; 383 | case REQ_SET_URI: 384 | req_setopt_from_uri(req, data); 385 | break; 386 | case REQ_SET_PROTOCOL: 387 | req->protocol = (REQ_PROTOCOL)data; 388 | 389 | if(req->protocol == PROTOCOL_HTTP) { 390 | req_list_set_key(req->opt, "protocol", "HTTP/1.1"); 391 | } else if(req->protocol == PROTOCOL_SIP) { 392 | req_list_set_key(req->opt, "protocol", "SIP/2.0"); 393 | } else { 394 | req_list_set_key(req->opt, "protocol", "Unknown"); 395 | } 396 | 397 | break; 398 | case REQ_SET_SECURITY: 399 | req_list_set_key(req->opt, "secure", data); 400 | if(req_list_check_key(req->opt, "secure", "true")) { 401 | ESP_LOGD(REQ_TAG, "Secure"); 402 | req->_read = ssl_read; 403 | req->_write = ssl_write; 404 | req->_connect = ssl_connect; 405 | req->_close = ssl_close; 406 | } else { 407 | req->_read = nossl_read; 408 | req->_write = nossl_write; 409 | req->_connect = nossl_connect; 410 | req->_close = nossl_close; 411 | } 412 | 413 | break; 414 | case REQ_SET_POSTFIELDS: 415 | req_list_set_key(req->header, "Content-Type", "application/x-www-form-urlencoded"); 416 | req_list_set_key(req->opt, "method", "POST"); 417 | case REQ_SET_DATAFIELDS: 418 | post_len = strlen((char*)data); 419 | sprintf(len_str, "%d", post_len); 420 | req_list_set_key(req->opt, "postfield", data); 421 | req_list_set_key(req->header, "Content-Length", len_str); 422 | break; 423 | case REQ_FUNC_UPLOAD_CB: 424 | req->upload_callback = data; 425 | break; 426 | case REQ_FUNC_DOWNLOAD_CB: 427 | req->download_callback = data; 428 | break; 429 | case REQ_FUNC_WEBSOCKET: 430 | req->websocket_callback = data; 431 | break; 432 | case REQ_REDIRECT_FOLLOW: 433 | req_list_set_key(req->opt, "follow", data); 434 | break; 435 | default: 436 | break; 437 | } 438 | free(host_w_port); 439 | } 440 | static int req_process_upload(request_t *req) 441 | { 442 | int tx_write_len = 0; 443 | req_list_t *found, *protocol; 444 | 445 | 446 | found = req_list_get_key(req->opt, "method"); 447 | REQ_CHECK(found == NULL, "method required", return -1); 448 | tx_write_len += sprintf(req->buffer->data + tx_write_len, "%s ", (char*)found->value); 449 | 450 | found = req_list_get_key(req->opt, "path"); 451 | REQ_CHECK(found == NULL, "path required", return -1); 452 | 453 | protocol = req_list_get_key(req->opt, "protocol"); 454 | REQ_CHECK(protocol == NULL, "protocol required", return -1); 455 | 456 | tx_write_len += sprintf(req->buffer->data + tx_write_len, "%s %s\r\n", (char*)found->value, (char*)protocol->value); 457 | 458 | //TODO: Check header len < REQ_BUFFER_LEN 459 | found = req->header; 460 | while(found->next != NULL) { 461 | found = found->next; 462 | tx_write_len += sprintf(req->buffer->data + tx_write_len, "%s: %s\r\n", (char*)found->key, (char*)found->value); 463 | } 464 | tx_write_len += sprintf(req->buffer->data + tx_write_len, "\r\n"); 465 | 466 | ESP_LOGD(REQ_TAG, "Request header, len= %d, real_len= %d\r\n%s", tx_write_len, strlen(req->buffer->data), req->buffer->data); 467 | 468 | REQ_CHECK(req->_write(req, req->buffer->data, tx_write_len) < 0, "Error write header", return -1); 469 | 470 | found = req_list_get_key(req->opt, "postfield"); 471 | if(found) { 472 | ESP_LOGD(REQ_TAG, "Begin write %d bytes", strlen((char*)found->value)); 473 | int bwrite = req->_write(req, (char*)found->value, strlen((char*)found->value)); 474 | ESP_LOGD(REQ_TAG, "end write %d bytes", bwrite); 475 | if(bwrite < 0) { 476 | ESP_LOGE(REQ_TAG, "Error write"); 477 | return -1; 478 | } 479 | } 480 | 481 | if(req->upload_callback) { 482 | while((tx_write_len = req->upload_callback(req, (void *)req->buffer->data, REQ_BUFFER_LEN)) > 0) { 483 | REQ_CHECK(req->_write(req, req->buffer->data, tx_write_len) < 0, "Error write data", return -1); 484 | } 485 | } 486 | return 0; 487 | } 488 | 489 | static int reset_buffer(request_t *req) 490 | { 491 | req->buffer->bytes_read = 0; 492 | req->buffer->bytes_write = 0; 493 | req->buffer->at_eof = 0; 494 | req->buffer->bytes_total = 0; 495 | return 0; 496 | } 497 | 498 | static int fill_buffer(request_t *req) 499 | { 500 | int bread; 501 | int bytes_inside_buffer = req->buffer->bytes_write - req->buffer->bytes_read; 502 | int buffer_free_bytes; 503 | if(bytes_inside_buffer) 504 | { 505 | memmove((void*)req->buffer->data, (void*)(req->buffer->data + req->buffer->bytes_read), 506 | bytes_inside_buffer); 507 | req->buffer->bytes_read = 0; 508 | req->buffer->bytes_write = bytes_inside_buffer; 509 | if(req->buffer->bytes_write < 0) 510 | req->buffer->bytes_write = 0; 511 | ESP_LOGD(REQ_TAG, "move=%d, write=%d, read=%d", bytes_inside_buffer, req->buffer->bytes_write, req->buffer->bytes_read); 512 | } 513 | if(!req->buffer->at_eof) 514 | { 515 | //reset if buffer full 516 | if(req->buffer->bytes_write == req->buffer->bytes_read) { 517 | req->buffer->bytes_write = 0; 518 | req->buffer->bytes_read = 0; 519 | } 520 | buffer_free_bytes = REQ_BUFFER_LEN - req->buffer->bytes_write; 521 | ESP_LOGD(REQ_TAG, "Begin read %d bytes", buffer_free_bytes); 522 | bread = req->_read(req, (void*)(req->buffer->data + req->buffer->bytes_write), buffer_free_bytes); 523 | // ESP_LOGD(REQ_TAG, "bread = %d, bytes_write = %d, buffer_free_bytes = %d", bread, req->buffer->bytes_write, buffer_free_bytes); 524 | ESP_LOGD(REQ_TAG, "End read, byte read= %d bytes", bread); 525 | if(bread < 0) { 526 | req->buffer->at_eof = 1; 527 | return -1; 528 | } 529 | req->buffer->bytes_write += bread; 530 | req->buffer->data[req->buffer->bytes_write] = 0;//terminal string 531 | 532 | if(bread == 0) { 533 | req->buffer->at_eof = 1; 534 | } 535 | } 536 | 537 | return 0; 538 | } 539 | 540 | 541 | static char *req_readline(request_t *req) 542 | { 543 | char *cr, *ret = NULL; 544 | if(req->buffer->bytes_read + 2 > req->buffer->bytes_write) { 545 | return NULL; 546 | } 547 | cr = strstr(req->buffer->data + req->buffer->bytes_read, "\r\n"); 548 | if(cr == NULL) { 549 | return NULL; 550 | } 551 | memset(cr, 0, 2); 552 | ret = req->buffer->data + req->buffer->bytes_read; 553 | req->buffer->bytes_read += (cr - (req->buffer->data + req->buffer->bytes_read)) + 2; 554 | // ESP_LOGD(REQ_TAG, "next offset=%d", req->buffer->bytes_read); 555 | return ret; 556 | } 557 | static int req_process_download(request_t *req) 558 | { 559 | int process_header = 1, header_off = 0; 560 | char *line; 561 | req_list_t *content_len; 562 | req->response->status_code = -1; 563 | reset_buffer(req); 564 | req_list_clear(req->response->header); 565 | req->response->len = 0; 566 | do { 567 | fill_buffer(req); 568 | if(process_header) { 569 | while((line = req_readline(req)) != NULL) { 570 | if(line[0] == 0) { 571 | ESP_LOGD(REQ_TAG, "end process_idx=%d", req->buffer->bytes_read); 572 | header_off = req->buffer->bytes_read; 573 | process_header = 0; //end of http header 574 | req_list_t *server_key = req_list_get_key(req->response->header, "Sec-WebSocket-Accept"); 575 | req_list_t *client_key = req_list_get_key(req->header, "Sec-WebSocket-Key"); 576 | if(server_key && client_key) { 577 | unsigned char client_key_b64[64], valid_client_key[20], accept_key[32] = {0}; 578 | int key_len = sprintf((char*)client_key_b64, "%s258EAFA5-E914-47DA-95CA-C5AB0DC85B11", (char*)client_key->value); 579 | mbedtls_sha1(client_key_b64, (size_t)key_len, valid_client_key); 580 | size_t outlen = 0; 581 | mbedtls_base64_encode(accept_key, 32, &outlen, valid_client_key, 20); 582 | accept_key[outlen] = 0; 583 | 584 | if(strcmp((char*)accept_key, (char*)server_key->value) == 0) { 585 | req->valid_websocket = 1; 586 | 587 | if(req->websocket_callback) { 588 | req->websocket_callback(req, WS_CONNECTED, NULL, 0); 589 | } 590 | } 591 | ESP_LOGD(REQ_TAG, "server key=%s, send_key=%s, accept_key=%s, valid=%d", (char *)server_key->value, (char*)client_key->value, accept_key, req->valid_websocket); 592 | } 593 | break; 594 | } else { 595 | if(req->response->status_code < 0) { 596 | char *temp = NULL; 597 | int status_idx = 0; 598 | if(req->protocol == PROTOCOL_HTTP) { 599 | temp = strstr(line, "HTTP/1."); 600 | status_idx = 9; 601 | } else if(req->protocol == PROTOCOL_SIP) { 602 | temp = strstr(line, "SIP/2.0"); 603 | status_idx = 8; 604 | } 605 | if(temp) { 606 | char statusCode[4] = { 0 }; 607 | memcpy(statusCode, temp + status_idx, 3); 608 | req->response->status_code = atoi(statusCode); 609 | ESP_LOGD(REQ_TAG, "status code: %d", req->response->status_code); 610 | } 611 | } else { 612 | req_list_set_from_string(req->response->header, line); 613 | ESP_LOGD(REQ_TAG, "header line: %s", line); 614 | } 615 | } 616 | } 617 | } 618 | 619 | if(process_header == 0) 620 | { 621 | if(req->buffer->at_eof) { 622 | fill_buffer(req); 623 | } 624 | 625 | req->buffer->bytes_read = req->buffer->bytes_write; 626 | content_len = req_list_get_key(req->response->header, "Content-Length"); 627 | if(content_len) { 628 | req->response->len = atoi(content_len->value); 629 | } 630 | if(req->response->len && req->download_callback && (req->buffer->bytes_write - header_off) != 0) { 631 | if(req->download_callback(req, (void *)(req->buffer->data + header_off), req->buffer->bytes_write - header_off) < 0) break; 632 | 633 | req->buffer->bytes_total += req->buffer->bytes_write - header_off; 634 | if(req->buffer->bytes_total == req->response->len) { 635 | break; 636 | } 637 | } 638 | header_off = 0; 639 | if(req->response->len == 0) { 640 | break; 641 | } 642 | 643 | } 644 | 645 | } while(req->buffer->at_eof == 0); 646 | return 0; 647 | } 648 | 649 | void req_websocket_task(void *pv) 650 | { 651 | request_t *req = pv; 652 | while(req->valid_websocket) { 653 | int len = req->_read(req, (char *)req->buffer->data, REQ_BUFFER_LEN); 654 | if(len < 0) { 655 | req->valid_websocket = 0; 656 | break; 657 | } 658 | if(len > 0) { 659 | if(req->websocket_callback) { 660 | req->websocket_callback(req, WS_DATA, req->buffer->data, len); 661 | } 662 | 663 | } 664 | 665 | } 666 | req->_close(req); 667 | if(req->websocket_callback) { 668 | req->websocket_callback(req, WS_DISCONNECTED, NULL, 0); 669 | } 670 | vTaskDelete(NULL); 671 | } 672 | 673 | int req_perform(request_t *req) 674 | { 675 | do { 676 | if(req->socket < 0) { 677 | REQ_CHECK(req->_connect(req) < 0, "Error connnect", break); 678 | } 679 | REQ_CHECK(req_process_upload(req) < 0, "Error send request", break); 680 | REQ_CHECK(req_process_download(req) < 0, "Error download", break); 681 | if(req->valid_websocket) { 682 | xTaskCreate(req_websocket_task, "req_websocket_task", 2*1024, req, 5, NULL); 683 | } 684 | if((req->response->status_code == 301 || req->response->status_code == 302) && req_list_check_key(req->opt, "follow", "true")) { 685 | req_list_t *found = req_list_get_key(req->response->header, "Location"); 686 | if(found) { 687 | req_list_set_key(req->header, "Referer", (const char*)found->value); 688 | req_setopt_from_uri(req, (const char*)found->value); 689 | ESP_LOGI(REQ_TAG, "Following: %s", (char*)found->value); 690 | req->_close(req); 691 | continue; 692 | } 693 | break; 694 | } else { 695 | break; 696 | } 697 | } while(1); 698 | if(req->protocol == PROTOCOL_HTTP) { 699 | req->_close(req); 700 | } 701 | return req->response->status_code; 702 | } 703 | void req_close(request_t *req) 704 | { 705 | req->valid_websocket = 0; 706 | } 707 | int req_write(request_t *req, const char *buffer, int len) 708 | { 709 | return req->_write(req, (char *)buffer, len); 710 | } 711 | void req_clean(request_t *req) 712 | { 713 | if(req->valid_websocket) { 714 | req->_close(req); 715 | } 716 | req_list_clear(req->opt); 717 | req_list_clear(req->header); 718 | req_list_clear(req->response->header); 719 | free(req->opt); 720 | free(req->header); 721 | free(req->response->header); 722 | free(req->response); 723 | free(req->buffer->data); 724 | free(req->buffer); 725 | free(req); 726 | } 727 | -------------------------------------------------------------------------------- /include/esp_request.h: -------------------------------------------------------------------------------- 1 | #ifndef _ESP_REQUEST_H_ 2 | #define _ESP_REQUEST_H_ 3 | #include "req_list.h" 4 | #include "uri_parser.h" 5 | #include "openssl/ssl.h" 6 | #include "lwip/sockets.h" 7 | #include "lwip/netdb.h" 8 | 9 | #define REQ_BUFFER_LEN (2048) 10 | 11 | #define WS_FIN 0x80 12 | #define WS_OPCODE_TEXT 0x01 13 | #define WS_OPCODE_BINARY 0x02 14 | #define WS_OPCODE_CLOSE 0x08 15 | #define WS_OPCODE_PING 0x09 16 | #define WS_OPCODE_PONG 0x0a 17 | // Second byte 18 | #define WS_MASK 0x80 19 | #define WS_SIZE16 126 20 | #define WS_SIZE64 127 21 | #define MAX_WEBSOCKET_HEADER_SIZE 10 22 | #define WS_RESPONSE_OK 101 23 | 24 | typedef enum { 25 | REQ_SET_METHOD = 0x01, 26 | REQ_SET_HEADER, 27 | REQ_SET_HOST, 28 | REQ_SET_PORT, 29 | REQ_SET_PATH, 30 | REQ_SET_URI, 31 | REQ_SET_SECURITY, 32 | REQ_SET_POSTFIELDS, 33 | REQ_SET_DATAFIELDS, 34 | REQ_SET_UPLOAD_LEN, 35 | REQ_SET_PROTOCOL, 36 | REQ_FUNC_DOWNLOAD_CB, 37 | REQ_FUNC_UPLOAD_CB, 38 | REQ_REDIRECT_FOLLOW, 39 | REQ_FUNC_WEBSOCKET 40 | } REQ_OPTS; 41 | 42 | typedef enum { 43 | PROTOCOL_HTTP = 1, 44 | PROTOCOL_WEBSOCKET, 45 | PROTOCOL_SIP 46 | } REQ_PROTOCOL; 47 | 48 | typedef enum { 49 | WS_CONNECTED = 0x01, 50 | WS_DATA, 51 | WS_DISCONNECTED, 52 | WS_ERROR 53 | } REQ_WS_STATUS; 54 | 55 | typedef struct response_t { 56 | req_list_t *header; 57 | int status_code; 58 | int len; 59 | } response_t; 60 | 61 | typedef struct { 62 | int bytes_read; 63 | int bytes_write; 64 | int bytes_total; 65 | char *data; 66 | int at_eof; 67 | } req_buffer_t; 68 | 69 | typedef struct request_t { 70 | req_list_t *opt; 71 | req_list_t *header; 72 | SSL_CTX *ctx; 73 | SSL *ssl; 74 | req_buffer_t *buffer; 75 | void *context; 76 | int socket; 77 | int (*_connect)(struct request_t *req); 78 | int (*_read)(struct request_t *req, char *buffer, int len); 79 | int (*_write)(struct request_t *req, char *buffer, int len); 80 | int (*_close)(struct request_t *req); 81 | int (*upload_callback)(struct request_t *req, void *buffer, int len); 82 | int (*download_callback)(struct request_t *req, void *buffer, int len); 83 | int (*websocket_callback)(struct request_t *req, int status, void *buffer, int len); 84 | response_t *response; 85 | int is_websocket; 86 | int valid_websocket; 87 | REQ_PROTOCOL protocol; 88 | } request_t; 89 | 90 | typedef int (*download_cb)(request_t *req, void *buffer, int len); 91 | typedef int (*upload_cb)(request_t *req, void *buffer, int len); 92 | // typedef int (*websocket_cb)(request_t *req, int status, void *buffer, int len); 93 | 94 | request_t *req_new(const char *url); 95 | void req_setopt(request_t *req, REQ_OPTS opt, void* data); 96 | void req_clean(request_t *req); 97 | void req_close(request_t *req); 98 | int req_write(request_t *req, const char *buffer, int len); 99 | int req_perform(request_t *req); 100 | 101 | #endif 102 | -------------------------------------------------------------------------------- /include/req_list.h: -------------------------------------------------------------------------------- 1 | #ifndef _LIST_H 2 | #define _LIST_H 3 | 4 | typedef struct req_list_t { 5 | void *key; 6 | void *value; 7 | struct req_list_t *next; 8 | struct req_list_t *prev; 9 | } req_list_t; 10 | 11 | void req_list_add(req_list_t *root, req_list_t *new_tree); 12 | req_list_t *req_list_get_last(req_list_t *root); 13 | req_list_t *req_list_get_first(req_list_t *root); 14 | void req_list_remove(req_list_t *tree); 15 | void req_list_clear(req_list_t *root); 16 | req_list_t *req_list_set_key(req_list_t *root, const char *key, const char *value); 17 | req_list_t *req_list_get_key(req_list_t *root, const char *key); 18 | int req_list_check_key(req_list_t *root, const char *key, const char *value); 19 | req_list_t *req_list_set_from_string(req_list_t *root, const char *data); //data = "key=value" 20 | req_list_t *req_list_set_format(req_list_t *root, const char *key, const char *format, ...); 21 | #endif 22 | -------------------------------------------------------------------------------- /include/uri_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef _uri_parser_ 2 | #define _uri_parser_ 3 | #ifdef __cplusplus 4 | extern "C" { 5 | #endif 6 | typedef struct { 7 | char *scheme; /* mandatory */ 8 | char *host; /* mandatory */ 9 | char *port; /* optional */ 10 | char *path; /* optional */ 11 | char *query; /* optional */ 12 | char *fragment; /* optional */ 13 | char *username; /* optional */ 14 | char *password; /* optional */ 15 | char *extension; 16 | char *host_ext; 17 | char *_uri; /* private */ 18 | int _uri_len; /* private */ 19 | } parsed_uri_t; 20 | 21 | parsed_uri_t *parse_uri(const char *); 22 | void free_parsed_uri(parsed_uri_t *); 23 | void parse_uri_info(parsed_uri_t *puri); 24 | #ifdef __cplusplus 25 | } 26 | #endif 27 | 28 | #endif /* _uri_parser_ */ 29 | -------------------------------------------------------------------------------- /req_list.c: -------------------------------------------------------------------------------- 1 | /* 2 | * @2017 3 | * Tuan PM 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "req_list.h" 11 | #include "esp_log.h" 12 | #define LIST_TAG "LIST" 13 | static char *trimwhitespace(char *str) 14 | { 15 | char *end; 16 | 17 | // Trim leading space 18 | while(isspace((unsigned char)*str)) str++; 19 | 20 | if(*str == 0) // All spaces? 21 | return str; 22 | 23 | // Trim trailing space 24 | end = str + strlen(str) - 1; 25 | while(end > str && isspace((unsigned char)*end)) end--; 26 | 27 | // Write new null terminator 28 | *(end+1) = 0; 29 | 30 | return str; 31 | } 32 | void req_list_add(req_list_t *root, req_list_t *new_tree) 33 | { 34 | req_list_t *last = req_list_get_last(root); 35 | if(last != NULL) { 36 | last->next = new_tree; 37 | new_tree->prev = last; 38 | } 39 | } 40 | req_list_t *req_list_get_last(req_list_t *root) 41 | { 42 | req_list_t *last; 43 | if(root == NULL) 44 | return NULL; 45 | last = root; 46 | while(last->next != NULL) { 47 | last = last->next; 48 | } 49 | return last; 50 | } 51 | req_list_t *req_list_get_first(req_list_t *root) 52 | { 53 | if(root == NULL) 54 | return NULL; 55 | if(root->next == NULL) 56 | return NULL; 57 | // ESP_LOGD(LIST_TAG, "root->next = %x", (int)root->next); 58 | return root->next; 59 | } 60 | void req_list_remove(req_list_t *tree) 61 | { 62 | req_list_t *found = tree; 63 | if (found != NULL) { 64 | if (found->next && found->prev) { 65 | // ESP_LOGD(LIST_TAG, "found->prev->next= %x, found->next->prev=%x", (int)found->prev->next, (int)found->next->prev); 66 | found->prev->next = found->next; 67 | found->next->prev = found->prev; 68 | } else if (found->next) { 69 | // ESP_LOGD(LIST_TAG, "found->next->prev= %x", (int)found->next->prev); 70 | found->next->prev = NULL; 71 | } else if (found->prev) { 72 | // ESP_LOGD(LIST_TAG, "found->prev->next =%x", (int)found->prev->next); 73 | found->prev->next = NULL; 74 | } 75 | free(found->key); 76 | free(found->value); 77 | free(found); 78 | } 79 | } 80 | 81 | void req_list_clear(req_list_t *root) 82 | { 83 | //FIXME: Need to test this function 84 | req_list_t *found; 85 | while((found = req_list_get_first(root)) != NULL) { 86 | // ESP_LOGD(LIST_TAG, "free key=%s, value=%s, found=%x", (char*)found->key, (char*)found->value, (int)found); 87 | req_list_remove(found); 88 | } 89 | } 90 | 91 | req_list_t *req_list_set_key(req_list_t *root, const char *key, const char *value) 92 | { 93 | req_list_t *found; 94 | if(root == NULL) 95 | return NULL; 96 | found = root; 97 | while(found->next != NULL) { 98 | found = found->next; 99 | if (strcasecmp(found->key, key) == 0) { 100 | if (found->value) { 101 | free(found->value); 102 | } 103 | found->value = calloc(1, strlen(value)+1); 104 | strcpy(found->value, value); 105 | return found; 106 | } 107 | } 108 | req_list_t *new_tree = calloc(1, sizeof(req_list_t)); 109 | if (new_tree == NULL) 110 | return NULL; 111 | new_tree->key = calloc(1, strlen(key) + 1); 112 | strcpy(new_tree->key, key); 113 | new_tree->value = calloc(1, strlen(value)+1); 114 | strcpy(new_tree->value, value); 115 | 116 | req_list_add(root, new_tree); 117 | return new_tree; 118 | } 119 | req_list_t *req_list_get_key(req_list_t *root, const char *key) 120 | { 121 | req_list_t *found; 122 | if(root == NULL) 123 | return NULL; 124 | found = root; 125 | while(found->next != NULL) { 126 | found = found->next; 127 | if (strcasecmp(found->key, key) == 0) { 128 | return found; 129 | } 130 | } 131 | return NULL; 132 | } 133 | int req_list_check_key(req_list_t *root, const char *key, const char *value) 134 | { 135 | req_list_t *found = req_list_get_key(root, key); 136 | if(found && strcasecmp(found->value, value) == 0) 137 | return 1; 138 | return 0; 139 | 140 | } 141 | req_list_t *req_list_set_from_string(req_list_t *root, const char *data) 142 | { 143 | int len = strlen(data); 144 | char* eq_ch = strchr(data, ':'); 145 | int key_len, value_len; 146 | req_list_t *ret = NULL; 147 | 148 | if (eq_ch == NULL) 149 | return NULL; 150 | key_len = eq_ch - data; 151 | value_len = len - key_len - 1; 152 | 153 | char *key = calloc(1, key_len + 1); 154 | char *value = calloc(1, value_len + 1); 155 | memcpy(key, data, key_len); 156 | memcpy(value, eq_ch + 1, value_len); 157 | 158 | ret = req_list_set_key(root, trimwhitespace(key), trimwhitespace(value)); 159 | free(key); 160 | free(value); 161 | return ret; 162 | } 163 | req_list_t *req_list_set_format(req_list_t *root, const char *key, const char *format, ...) 164 | { 165 | req_list_t *ret = NULL; 166 | va_list argptr; 167 | char *buf = calloc(1, 1024); 168 | if(buf == NULL) { 169 | return NULL; 170 | } 171 | va_start(argptr, format); 172 | vsnprintf(buf, 1024, format, argptr); 173 | va_end(argptr); 174 | ret = req_list_set_key(root, key, buf); 175 | free(buf); 176 | return ret; 177 | } 178 | req_list_t *req_list_clear_key(req_list_t *root, const char *key) 179 | { 180 | return NULL; 181 | } 182 | -------------------------------------------------------------------------------- /uri_parser.c: -------------------------------------------------------------------------------- 1 | /* 2 | URI Parser 3 | Copyright (c) 2016 Tuan PM (tuanpm@live.com) 4 | Inspired by Hirochika Asai, http://draft.scyphus.co.jp/lang/c/url_parser.html 5 | License (MIT license): 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 18 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 19 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 20 | THE SOFTWARE. 21 | */ 22 | #include "uri_parser.h" 23 | #include 24 | #include 25 | #include 26 | #include 27 | #include "esp_err.h" 28 | static __inline__ int 29 | _is_scheme_char(int c) 30 | { 31 | return (!isalpha(c) && '+' != c && '-' != c && '.' != c) ? 0 : 1; 32 | } 33 | 34 | #define JUMP_NEXT_STATE(var, state) { *curr_ptr = 0; curr_ptr ++; var = curr_ptr; parse_state = state; break;} 35 | parsed_uri_t *parse_uri(const char *url) 36 | { 37 | parsed_uri_t *puri; 38 | char *curr_ptr; 39 | int bracket_flag; 40 | enum parse_state_t { 41 | PARSE_SCHEME = 0, 42 | PARSE_USERNAME_OR_HOST, 43 | PARSE_PASSWORD_OR_PORT, 44 | PARSE_HOST, 45 | PARSE_PORT, 46 | PARSE_PATH, 47 | PARSE_QUERY, 48 | PARSE_FRAGMENT 49 | } parse_state = 0; 50 | puri = (parsed_uri_t *)malloc(sizeof(parsed_uri_t)); 51 | memset(puri, 0, sizeof(parsed_uri_t)); 52 | if(NULL == puri) { 53 | return NULL; 54 | } 55 | puri->_uri_len = strlen(url); 56 | puri->_uri = (char*) malloc(puri->_uri_len + 1); 57 | memset(puri->_uri, 0, puri->_uri_len + 1); 58 | if(puri->_uri == NULL) { 59 | free_parsed_uri(puri); 60 | return NULL; 61 | } 62 | strcpy(puri->_uri, url); 63 | puri->_uri[puri->_uri_len] = 0; 64 | puri->scheme = NULL; 65 | puri->host = NULL; 66 | puri->port = NULL; 67 | puri->path = NULL; 68 | puri->query = NULL; 69 | puri->fragment = NULL; 70 | puri->username = NULL; 71 | puri->password = NULL; 72 | 73 | curr_ptr = puri->_uri; 74 | puri->scheme = curr_ptr; 75 | parse_state = PARSE_SCHEME; 76 | bracket_flag = 0; 77 | while(*curr_ptr) { 78 | // *curr_ptr = tolower((unsigned char)*curr_ptr); 79 | switch(parse_state) { 80 | case PARSE_SCHEME: /* parse scheme */ 81 | if(curr_ptr + 3 < (puri->_uri + puri->_uri_len) && memcmp(curr_ptr, "://", 3) == 0) { 82 | *curr_ptr++ = 0; 83 | *curr_ptr++ = 0; 84 | *curr_ptr++ = 0; 85 | puri->host = curr_ptr; 86 | puri->username = curr_ptr; 87 | parse_state = PARSE_USERNAME_OR_HOST; //next is username or host 88 | break; 89 | } 90 | // if(!_is_scheme_char(*curr_ptr)) { 91 | // free_parsed_uri(puri); 92 | // return NULL; 93 | // } 94 | curr_ptr ++; 95 | break; 96 | case PARSE_USERNAME_OR_HOST: /* username or host*/ 97 | if('[' == *curr_ptr && bracket_flag == 0) { 98 | bracket_flag = 1; 99 | } else if(']' == *curr_ptr && bracket_flag == 1) { 100 | bracket_flag = 0; 101 | } 102 | if(bracket_flag == 0 && *curr_ptr == ':') { 103 | JUMP_NEXT_STATE(puri->port = puri->password, PARSE_PASSWORD_OR_PORT); 104 | } else if(bracket_flag == 0 && *curr_ptr == '#') { 105 | puri->username = NULL; 106 | JUMP_NEXT_STATE(puri->fragment, PARSE_FRAGMENT); 107 | } else if(bracket_flag == 0 && *curr_ptr == '/') { 108 | puri->username = NULL; 109 | JUMP_NEXT_STATE(puri->path, PARSE_PATH); 110 | } 111 | curr_ptr ++; 112 | break; 113 | case PARSE_PASSWORD_OR_PORT: /* password or port */ 114 | if(*curr_ptr == '@') { 115 | puri->port = NULL; 116 | JUMP_NEXT_STATE(puri->host, PARSE_HOST); 117 | break; 118 | } else if(*curr_ptr == '/') { 119 | puri->username = NULL; 120 | puri->password = NULL; 121 | JUMP_NEXT_STATE(puri->path, PARSE_PATH); 122 | break; 123 | } else if(*curr_ptr == '#') { 124 | puri->username = NULL; 125 | puri->password = NULL; 126 | JUMP_NEXT_STATE(puri->fragment, PARSE_FRAGMENT); 127 | break; 128 | } 129 | curr_ptr ++; 130 | break; 131 | case PARSE_HOST: /* host */ 132 | if('[' == *curr_ptr && bracket_flag == 0) { 133 | bracket_flag = 1; 134 | } else if(']' == *curr_ptr && bracket_flag == 1) { 135 | bracket_flag = 0; 136 | } 137 | if(bracket_flag == 0 && *curr_ptr == ':') { 138 | JUMP_NEXT_STATE(puri->port, PARSE_PORT); 139 | } else if(bracket_flag == 0 && *curr_ptr == '/') { 140 | puri->port = NULL; 141 | JUMP_NEXT_STATE(puri->path, PARSE_PATH); 142 | } else if(bracket_flag == 0 && *curr_ptr == '#') { 143 | puri->port = NULL; 144 | JUMP_NEXT_STATE(puri->fragment, PARSE_FRAGMENT); 145 | } 146 | curr_ptr ++; 147 | break; 148 | case PARSE_PORT: /* port */ 149 | if(*curr_ptr == '/') { 150 | JUMP_NEXT_STATE(puri->path, PARSE_PATH); 151 | } else if(*curr_ptr == '?') { 152 | JUMP_NEXT_STATE(puri->query, PARSE_QUERY); 153 | } else if(*curr_ptr == '#') { 154 | JUMP_NEXT_STATE(puri->fragment, PARSE_FRAGMENT); 155 | } 156 | curr_ptr ++; 157 | break; 158 | case PARSE_PATH: /* path */ 159 | if(*curr_ptr == '?') { 160 | // JUMP_NEXT_STATE(puri->query, PARSE_QUERY); 161 | } else if(*curr_ptr == '#') { 162 | JUMP_NEXT_STATE(puri->fragment, PARSE_FRAGMENT); 163 | } 164 | curr_ptr ++; 165 | case PARSE_QUERY: /* query */ 166 | if(*curr_ptr == '#') { 167 | JUMP_NEXT_STATE(puri->fragment, PARSE_FRAGMENT); 168 | } 169 | case PARSE_FRAGMENT: /* fragment*/ 170 | curr_ptr ++; 171 | break; 172 | } 173 | 174 | } 175 | if(parse_state < PARSE_HOST) { 176 | puri->host = puri->username; 177 | puri->port = puri->password; 178 | puri->username = NULL; 179 | puri->password = NULL; 180 | } 181 | if (puri->path && puri->path[0]!= 0){ 182 | char *temp = malloc(strlen(puri->path) + 2); 183 | sprintf(temp, "/%s", puri->path); 184 | puri->path = temp; 185 | } else { 186 | puri->path = malloc(2); 187 | puri->path[0] = '/'; 188 | puri->path[1] = 0; 189 | } 190 | return puri; 191 | } 192 | void parse_uri_info(parsed_uri_t *puri) 193 | { 194 | printf( "scheme addr: %x\n" 195 | "Username addr: %x\n" 196 | "password addr: %x\n" 197 | "host addr: %x\n" 198 | "port addr: %x\n" 199 | "path addr: %x\n" 200 | "fragment addr: %x\n" 201 | "extension addr: %x\n" 202 | "host_ext addr: %x\r\n", 203 | (int)puri->scheme, 204 | (int)puri->username, 205 | (int)puri->password, 206 | (int)puri->host, 207 | (int)puri->port, 208 | (int)puri->path, 209 | (int)puri->fragment, 210 | (int)puri->extension, 211 | (int)puri->host_ext); 212 | 213 | if(puri->scheme && puri->scheme[0] != 0) { 214 | printf("scheme: %s\n", puri->scheme); 215 | } 216 | if(puri->host && puri->host[0] != 0) { 217 | printf("Host: %s\n", puri->host); 218 | } 219 | if(puri->path && puri->path[0] != 0) { 220 | printf("path: %s\n", puri->path); 221 | } 222 | if(puri->port && puri->port[0] != 0) { 223 | printf("port: %s\n", puri->port); 224 | } 225 | if(puri->username && puri->username[0] != 0) { 226 | printf("username: %s\n", puri->username); 227 | } 228 | if(puri->password && puri->password[0] != 0) { 229 | printf("password: %s\n", puri->password); 230 | } 231 | if(puri->fragment && puri->fragment[0] != 0) { 232 | printf("fragment: %s\n", puri->fragment); 233 | } 234 | if(puri->extension && puri->extension[0] != 0) { 235 | printf("extension: %s\n", puri->extension); 236 | } 237 | if(puri->host_ext && puri->host_ext[0] != 0) { 238 | printf("host_ext: %s\n", puri->host_ext); 239 | } 240 | } 241 | void free_parsed_uri(parsed_uri_t *puri) 242 | { 243 | if(NULL != puri) { 244 | if(puri->path && puri->path[0] != 0) { 245 | free(puri->path); 246 | } 247 | if(NULL != puri->_uri) { 248 | free(puri->_uri); 249 | } 250 | free(puri); 251 | } 252 | } 253 | --------------------------------------------------------------------------------