├── README.md ├── config ├── src ├── ngx_stream_http_parser.c ├── ngx_stream_http_parser.h ├── ngx_stream_json.c ├── ngx_stream_json.h ├── ngx_stream_upsync_module.c └── ngx_stream_upsync_module.h └── test ├── README ├── conf-server.sh ├── t ├── lib │ ├── Test │ │ ├── Nginx.pm │ │ └── Nginx │ │ │ ├── IMAP.pm │ │ │ ├── POP3.pm │ │ │ └── SMTP.pm │ └── Time │ │ ├── Parse.pm │ │ └── Zone.pm └── upsync.t └── test.sh /README.md: -------------------------------------------------------------------------------- 1 | Name 2 | ==== 3 | 4 | nginx-stream-upsync-module - Nginx C module, sync upstreams from consul or others, dynamically modify backend-servers attribute(weight, max_fails,...), needn't reload nginx. 5 | 6 | It may not always be convenient to modify configuration files and restart NGINX. For example, if you are experiencing large amounts of traffic and high load, restarting NGINX and reloading the configuration at that point further increases load on the system and can temporarily degrade performance. 7 | 8 | The module can be more smoothly expansion and constriction, and will not influence the performance. 9 | 10 | Another module, [nginx-upsync-module](https://github.com/weibocom/nginx-upsync-module) supports nginx http module(HTTP protocol), please be noticed. 11 | 12 | If you want to use [nginx-upsync-module](https://github.com/weibocom/nginx-upsync-module) and [nginx-stream-upsync-module](https://github.com/xiaokai-wang/nginx-stream-upsync-module) both, please refer to [nginx-upsync](https://github.com/CallMeFoxie/nginx-upsync). 13 | 14 | Table of Contents 15 | ================= 16 | 17 | * [Name](#name) 18 | * [Status](#status) 19 | * [Synopsis](#synopsis) 20 | * [Description](#description) 21 | * [Directives](#directives) 22 | * [upsync](#upsync) 23 | * [upsync_interval](#upsync_interval) 24 | * [upsync_timeout](#upsync_timeout) 25 | * [upsync_type](#upsync_type) 26 | * [strong_dependency](#strong_dependency) 27 | * [upsync_dump_path](#upsync_dump_path) 28 | * [upsync_lb](#upsync_lb) 29 | * [upstream_show](#upstream_show) 30 | * [Consul_interface](#consul_interface) 31 | * [Etcd_interface](#etcd_interface) 32 | * [TODO](#todo) 33 | * [Compatibility](#compatibility) 34 | * [Installation](#installation) 35 | * [Code style](#code-style) 36 | * [Author](#author) 37 | * [Copyright and License](#copyright-and-license) 38 | * [See Also](#see-also) 39 | * [Source Dependency](#source-dependency) 40 | 41 | Status 42 | ====== 43 | 44 | This module is still under active development and is considered production ready. 45 | 46 | Synopsis 47 | ======== 48 | 49 | nginx-consul: 50 | ```nginx-consul 51 | stream { 52 | upstream test { 53 | upsync 127.0.0.1:8500/v1/kv/upstreams/test/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off; 54 | upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf; 55 | 56 | include /usr/local/nginx/conf/servers/servers_test.conf; 57 | } 58 | 59 | upstream bar { 60 | server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3; 61 | } 62 | 63 | server { 64 | listen 12345; 65 | 66 | proxy_connect_timeout 1s; 67 | proxy_timeout 3s; 68 | proxy_pass test; 69 | } 70 | 71 | server { 72 | listen 2345; 73 | 74 | upstream_show 75 | } 76 | 77 | server { 78 | listen 127.0.0.1:9091; 79 | 80 | proxy_responses 1; 81 | proxy_timeout 20s; 82 | proxy_pass bar; 83 | } 84 | } 85 | ``` 86 | nginx-etcd: 87 | ```nginx-etcd 88 | stream { 89 | upstream test { 90 | upsync 127.0.0.1:2379/v2/keys/upstreams/test upsync_timeout=6m upsync_interval=500ms upsync_type=etcd strong_dependency=off; 91 | upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf; 92 | 93 | include /usr/local/nginx/conf/servers/servers_test.conf; 94 | } 95 | 96 | upstream bar { 97 | server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3; 98 | } 99 | 100 | server { 101 | listen 12345; 102 | 103 | proxy_connect_timeout 1s; 104 | proxy_timeout 3s; 105 | proxy_pass test; 106 | } 107 | 108 | server { 109 | listen 2345; 110 | 111 | upstream_show 112 | } 113 | 114 | server { 115 | listen 127.0.0.1:9091; 116 | 117 | proxy_responses 1; 118 | proxy_timeout 20s; 119 | proxy_pass bar; 120 | } 121 | } 122 | ``` 123 | upsync_lb: 124 | ```upsync_lb 125 | stream { 126 | upstream test { 127 | least_conn; //hash $uri consistent; 128 | 129 | upsync 127.0.0.1:8500/v1/kv/upstreams/test/ upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off; 130 | upsync_dump_path /usr/local/nginx/conf/servers/servers_test.conf; 131 | upsync_lb least_conn; //hash_ketama; 132 | 133 | include /usr/local/nginx/conf/servers/servers_test.conf; 134 | } 135 | 136 | upstream bar { 137 | server 127.0.0.1:8090 weight=1 fail_timeout=10 max_fails=3; 138 | } 139 | 140 | server { 141 | listen 12345; 142 | 143 | proxy_connect_timeout 1s; 144 | proxy_timeout 3s; 145 | proxy_pass test; 146 | } 147 | 148 | server { 149 | listen 2345; 150 | 151 | upstream_show 152 | } 153 | 154 | server { 155 | listen 127.0.0.1:9091; 156 | 157 | proxy_responses 1; 158 | proxy_timeout 20s; 159 | proxy_pass bar; 160 | } 161 | } 162 | ``` 163 | 164 | NOTE: upstream: include command is neccesary, first time the dumped file should include all the servers. 165 | 166 | [Back to TOC](#table-of-contents) 167 | 168 | Description 169 | ====== 170 | 171 | This module provides a method to discover backend servers. Supporting dynamicly adding or deleting backend server through consul/etcd and dynamicly adjusting backend servers weight, module will timely pull new backend server list from consul/etcd to upsync nginx ip router. Nginx needn't reload. Having some advantages than others: 172 | 173 | * timely 174 | 175 | module send key to consul/etcd with index, consul/etcd will compare it with its index, if index doesn't change connection will hang five minutes, in the period any operation to the key-value, will feed back rightaway. 176 | 177 | * performance 178 | 179 | Pulling from consul/etcd equal a request to nginx, updating ip router nginx needn't reload, so affecting nginx performance is little. 180 | 181 | * stability 182 | 183 | Even if one pulling failed, it will pull next upsync_interval, so guaranteing backend server stably provides service. And support dumping the latest config to location, so even if consul/etcd hung up, and nginx can be reload anytime. 184 | 185 | [Back to TOC](#table-of-contents) 186 | 187 | Directives 188 | ====== 189 | 190 | upsync 191 | ----------- 192 | ``` 193 | syntax: upsync $consul/etcd.api.com:$port/v1/kv/upstreams/$upstream_name/ [upsync_type=consul/etcd] [upsync_interval=second/minutes] [upsync_timeout=second/minutes] [strong_dependency=off/on] 194 | ``` 195 | default: none, if parameters omitted, default parameters are upsync_interval=5s upsync_timeout=6m strong_dependency=off 196 | 197 | context: upstream 198 | 199 | description: Pull upstream servers from consul/etcd... . 200 | 201 | The parameters' meanings are: 202 | 203 | * upsync_interval 204 | 205 | pulling servers from consul/etcd interval time. 206 | 207 | * upsync_timeout 208 | 209 | pulling servers from consul/etcd request timeout. 210 | 211 | * upsync_type 212 | 213 | pulling servers from conf server type. 214 | 215 | * strong_dependency 216 | 217 | when nginx start up if strong_dependency is on that means servers will be depended on consul/etcd and will pull servers from consul/etcd. 218 | 219 | 220 | upsync_dump_path 221 | ----------- 222 | `syntax: upsync_dump_path $path` 223 | 224 | default: /tmp/servers_$host.conf 225 | 226 | context: upstream 227 | 228 | description: dump the upstream backends to the $path. 229 | 230 | 231 | upsync_lb 232 | ----------- 233 | `syntax: upsync_lb $load_balance` 234 | 235 | default: round_robin/ip_hash/hash modula 236 | 237 | context: upstream 238 | 239 | description: mainly for least_conn and hash consistent, when using one of them, you must point out using upsync_lb. 240 | 241 | 242 | upsync_show 243 | ----------- 244 | `syntax: upsync_show` 245 | 246 | default: none 247 | 248 | context: server 249 | 250 | description: show all upstreams. 251 | 252 | ```request 253 | curl http://localhost:2345/upstream_show 254 | 255 | show all upstreams 256 | ``` 257 | 258 | [Back to TOC](#table-of-contents) 259 | 260 | Consul_interface 261 | ====== 262 | 263 | Data can be taken from key/value store or service catalog. In the first case parameter upsync_type of directive must be *consul*. For example 264 | 265 | ```nginx-consul 266 | upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_timeout=6m upsync_interval=500ms upsync_type=consul strong_dependency=off; 267 | ``` 268 | 269 | In the second case it must be *consul_services*. 270 | 271 | ```nginx-consul 272 | upsync 127.0.0.1:8500/v1/catalog/service/test upsync_timeout=6m upsync_interval=500ms upsync_type=consul_services strong_dependency=off; 273 | ``` 274 | 275 | you can add or delete backend server through consul_ui or http_interface. Below are examples for key/value store. 276 | 277 | http_interface example: 278 | 279 | * add 280 | ``` 281 | curl -X PUT http://$consul_ip:$port/v1/kv/upstreams/$upstream_name/$backend_ip:$backend_port 282 | ``` 283 | default: weight=1 max_fails=2 fail_timeout=10 down=0 backup=0; 284 | 285 | ``` 286 | curl -X PUT -d "{\"weight\":1, \"max_fails\":2, \"fail_timeout\":10}" http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 287 | or 288 | curl -X PUT -d '{"weight":1, "max_fails":2, "fail_timeout":10}' http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 289 | ``` 290 | value support json format. 291 | 292 | * delete 293 | ``` 294 | curl -X DELETE http://$consul_ip:$port/v1/kv/upstreams/$upstream_name/$backend_ip:$backend_port 295 | ``` 296 | 297 | * adjust-weight 298 | ``` 299 | curl -X PUT -d "{\"weight\":2, \"max_fails\":2, \"fail_timeout\":10}" http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 300 | or 301 | curl -X PUT -d '{"weight":2, "max_fails":2, "fail_timeout":10}' http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 302 | ``` 303 | 304 | * mark server-down 305 | ``` 306 | curl -X PUT -d "{\"weight\":2, \"max_fails\":2, \"fail_timeout\":10, \"down\":1}" http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 307 | or 308 | curl -X PUT -d '{"weight":2, "max_fails":2, "fail_timeout":10, "down":1}' http://$consul_ip:$port/v1/kv/$dir1/$upstream_name/$backend_ip:$backend_port 309 | ``` 310 | 311 | * check 312 | ``` 313 | curl http://$consul_ip:$port/v1/kv/upstreams/$upstream_name?recurse 314 | ``` 315 | 316 | [Back to TOC](#table-of-contents) 317 | 318 | Etcd_interface 319 | ====== 320 | 321 | you can add or delete backend server through http_interface. 322 | 323 | mainly like etcd, http_interface example: 324 | 325 | * add 326 | ``` 327 | curl -X PUT http://$etcd_ip:$port/v2/keys/upstreams/$upstream_name/$backend_ip:$backend_port 328 | ``` 329 | default: weight=1 max_fails=2 fail_timeout=10 down=0 backup=0; 330 | 331 | ``` 332 | curl -X PUT -d value="{\"weight\":1, \"max_fails\":2, \"fail_timeout\":10}" http://$etcd_ip:$port/v2/keys/$dir1/$upstream_name/$backend_ip:$backend_port 333 | ``` 334 | value support json format. 335 | 336 | * delete 337 | ``` 338 | curl -X DELETE http://$etcd_ip:$port/v2/keys/upstreams/$upstream_name/$backend_ip:$backend_port 339 | ``` 340 | 341 | * adjust-weight 342 | ``` 343 | curl -X PUT -d "{\"weight\":2, \"max_fails\":2, \"fail_timeout\":10}" http://$etcd_ip:$port/v2/keys/$dir1/$upstream_name/$backend_ip:$backend_port 344 | ``` 345 | 346 | * mark server-down 347 | ``` 348 | curl -X PUT -d value="{\"weight\":2, \"max_fails\":2, \"fail_timeout\":10, \"down\":1}" http://$etcd_ip:$port/v2/keys/$dir1/$upstream_name/$backend_ip:$backend_port 349 | ``` 350 | 351 | * check 352 | ``` 353 | curl http://$etcd_ip:$port/v2/keys/upstreams/$upstream_name 354 | ``` 355 | 356 | [Back to TOC](#table-of-contents) 357 | 358 | TODO 359 | ==== 360 | 361 | * support zookeeper and so on 362 | 363 | [Back to TOC](#table-of-contents) 364 | 365 | Compatibility 366 | ============= 367 | 368 | The module was developed base on nginx-1.9.10. 369 | 370 | Master branch compatible with nginx-1.11.0+. 371 | 372 | Nginx-1.10.3- branch compatible with nginx-1.9.10 ~ nginx-1.11.0+. 373 | 374 | [Back to TOC](#table-of-contents) 375 | 376 | Installation 377 | ============ 378 | 379 | This module can be used independently, can be download[Github](https://github.com/xiaokai-wang/nginx-stream-upsync-module.git). 380 | 381 | Grab the nginx source code from [nginx.org](http://nginx.org/), for example, the version 1.8.0 (see nginx compatibility), and then build the source with this module: 382 | 383 | ```bash 384 | wget 'http://nginx.org/download/nginx-1.8.0.tar.gz' 385 | tar -xzvf nginx-1.8.0.tar.gz 386 | cd nginx-1.8.0/ 387 | ``` 388 | 389 | ```bash 390 | ./configure --add-module=/path/to/nginx-stream_upsync-module 391 | make 392 | make install 393 | ``` 394 | 395 | if you support nginx-upstream-check-module 396 | ```bash 397 | ./configure --add-module=/path/to/nginx-upstream-check-module --add-module=/path/to/nginx-stream_upsync-module 398 | make 399 | make install 400 | ``` 401 | 402 | [Back to TOC](#table-of-contents) 403 | 404 | Code style 405 | ====== 406 | 407 | Code style is mainly based on [style](http://tengine.taobao.org/book/appendix_a.html) 408 | 409 | [Back to TOC](#table-of-contents) 410 | 411 | Author 412 | ====== 413 | 414 | Xiaokai Wang (王晓开) , Weibo Inc. 415 | 416 | [Back to TOC](#table-of-contents) 417 | 418 | Copyright and License 419 | ===================== 420 | 421 | This README template copy from agentzh. 422 | 423 | This module is licensed under the BSD license. 424 | 425 | Copyright (C) 2014 by Xiaokai Wang 426 | 427 | All rights reserved. 428 | 429 | Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 430 | 431 | * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 432 | 433 | * 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. 434 | 435 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS 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 COPYRIGHT HOLDER 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. 436 | 437 | [Back to TOC](#table-of-contents) 438 | 439 | see also 440 | ======== 441 | * the nginx_upstream_check_module: https://github.com/alibaba/tengine/blob/master/src/http/ngx_http_upstream_check_module.c 442 | * the nginx_upstream_check_module patch: https://github.com/yaoweibin/nginx_upstream_check_module 443 | * or based on https://github.com/xiaokai-wang/nginx_upstream_check_module 444 | 445 | [back to toc](#table-of-contents) 446 | 447 | source dependency 448 | ======== 449 | * Cjson: https://github.com/kbranigan/cJSON 450 | * http-parser: https://github.com/nodejs/http-parser 451 | 452 | [back to toc](#table-of-contents) 453 | 454 | 455 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_stream_upsync_module 2 | 3 | ngx_feature_libs="-lm" 4 | 5 | ngx_module_incs=$ngx_addon_dir/src 6 | 7 | _STREAM_UPSYNC_SRCS="\ 8 | $ngx_addon_dir/src/ngx_stream_upsync_module.c \ 9 | $ngx_addon_dir/src/ngx_stream_json.c \ 10 | $ngx_addon_dir/src/ngx_stream_http_parser.c \ 11 | " 12 | 13 | have=NGX_STREAM_UPSYNC . auto/have 14 | 15 | if test -n "$ngx_module_link"; then 16 | ngx_module_type=STREAM 17 | ngx_module_name=$ngx_addon_name 18 | ngx_module_srcs="$_STREAM_UPSYNC_SRCS" 19 | ngx_module_libs=$ngx_feature_libs 20 | . auto/module 21 | else 22 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $_STREAM_UPSYNC_SRCS" 23 | CORE_LIBS="$CORE_LIBS $ngx_feature_libs" 24 | CORE_INCS="$CORE_INCS $ngx_module_incs" 25 | STREAM_MODULES="$STREAM_MODULES $ngx_addon_name" 26 | fi 27 | 28 | -------------------------------------------------------------------------------- /src/ngx_stream_http_parser.c: -------------------------------------------------------------------------------- 1 | /* Based on src/http/ngx_http_parse.c from NGINX copyright Igor Sysoev 2 | * 3 | * Additional changes are licensed under the same terms as NGINX and 4 | * copyright Joyent, Inc. and other Node contributors. All rights reserved. 5 | * 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 8 | * deal in the Software without restriction, including without limitation the 9 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 10 | * sell copies of the Software, and to permit persons to whom the Software is 11 | * furnished to do so, subject to the following conditions: 12 | * 13 | * The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * 16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 21 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 22 | * IN THE SOFTWARE. 23 | */ 24 | #include 25 | #ifndef NGX_HTTP_UPSYNC 26 | 27 | #include "ngx_stream_http_parser.h" 28 | #include 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | 35 | #ifndef ULLONG_MAX 36 | # define ULLONG_MAX ((uint64_t) -1) /* 2^64-1 */ 37 | #endif 38 | 39 | #ifndef MIN 40 | # define MIN(a,b) ((a) < (b) ? (a) : (b)) 41 | #endif 42 | 43 | #ifndef ARRAY_SIZE 44 | # define ARRAY_SIZE(a) (sizeof(a) / sizeof((a)[0])) 45 | #endif 46 | 47 | #ifndef BIT_AT 48 | # define BIT_AT(a, i) \ 49 | (!!((unsigned int) (a)[(unsigned int) (i) >> 3] & \ 50 | (1 << ((unsigned int) (i) & 7)))) 51 | #endif 52 | 53 | #ifndef ELEM_AT 54 | # define ELEM_AT(a, i, v) ((unsigned int) (i) < ARRAY_SIZE(a) ? (a)[(i)] : (v)) 55 | #endif 56 | 57 | #define SET_ERRNO(e) \ 58 | do { \ 59 | parser->http_errno = (e); \ 60 | } while(0) 61 | 62 | #define CURRENT_STATE() p_state 63 | #define UPDATE_STATE(V) p_state = (enum state) (V); 64 | #define RETURN(V) \ 65 | do { \ 66 | parser->state = CURRENT_STATE(); \ 67 | return (V); \ 68 | } while (0); 69 | #define REEXECUTE() \ 70 | goto reexecute; \ 71 | 72 | 73 | #ifdef __GNUC__ 74 | # define LIKELY(X) __builtin_expect(!!(X), 1) 75 | # define UNLIKELY(X) __builtin_expect(!!(X), 0) 76 | #else 77 | # define LIKELY(X) (X) 78 | # define UNLIKELY(X) (X) 79 | #endif 80 | 81 | 82 | /* Run the notify callback FOR, returning ER if it fails */ 83 | #define CALLBACK_NOTIFY_(FOR, ER) \ 84 | do { \ 85 | assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ 86 | \ 87 | if (LIKELY(settings->on_##FOR)) { \ 88 | parser->state = CURRENT_STATE(); \ 89 | if (UNLIKELY(0 != settings->on_##FOR(parser))) { \ 90 | SET_ERRNO(HPE_CB_##FOR); \ 91 | } \ 92 | UPDATE_STATE(parser->state); \ 93 | \ 94 | /* We either errored above or got paused; get out */ \ 95 | if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ 96 | return (ER); \ 97 | } \ 98 | } \ 99 | } while (0) 100 | 101 | /* Run the notify callback FOR and consume the current byte */ 102 | #define CALLBACK_NOTIFY(FOR) CALLBACK_NOTIFY_(FOR, p - data + 1) 103 | 104 | /* Run the notify callback FOR and don't consume the current byte */ 105 | #define CALLBACK_NOTIFY_NOADVANCE(FOR) CALLBACK_NOTIFY_(FOR, p - data) 106 | 107 | /* Run data callback FOR with LEN bytes, returning ER if it fails */ 108 | #define CALLBACK_DATA_(FOR, LEN, ER) \ 109 | do { \ 110 | assert(HTTP_PARSER_ERRNO(parser) == HPE_OK); \ 111 | \ 112 | if (FOR##_mark) { \ 113 | if (LIKELY(settings->on_##FOR)) { \ 114 | parser->state = CURRENT_STATE(); \ 115 | if (UNLIKELY(0 != \ 116 | settings->on_##FOR(parser, FOR##_mark, (LEN)))) { \ 117 | SET_ERRNO(HPE_CB_##FOR); \ 118 | } \ 119 | UPDATE_STATE(parser->state); \ 120 | \ 121 | /* We either errored above or got paused; get out */ \ 122 | if (UNLIKELY(HTTP_PARSER_ERRNO(parser) != HPE_OK)) { \ 123 | return (ER); \ 124 | } \ 125 | } \ 126 | FOR##_mark = NULL; \ 127 | } \ 128 | } while (0) 129 | 130 | /* Run the data callback FOR and consume the current byte */ 131 | #define CALLBACK_DATA(FOR) \ 132 | CALLBACK_DATA_(FOR, p - FOR##_mark, p - data + 1) 133 | 134 | /* Run the data callback FOR and don't consume the current byte */ 135 | #define CALLBACK_DATA_NOADVANCE(FOR) \ 136 | CALLBACK_DATA_(FOR, p - FOR##_mark, p - data) 137 | 138 | /* Set the mark FOR; non-destructive if mark is already set */ 139 | #define MARK(FOR) \ 140 | do { \ 141 | if (!FOR##_mark) { \ 142 | FOR##_mark = p; \ 143 | } \ 144 | } while (0) 145 | 146 | /* Don't allow the total size of the HTTP headers (including the status 147 | * line) to exceed HTTP_MAX_HEADER_SIZE. This check is here to protect 148 | * embedders against denial-of-service attacks where the attacker feeds 149 | * us a never-ending header that the embedder keeps buffering. 150 | * 151 | * This check is arguably the responsibility of embedders but we're doing 152 | * it on the embedder's behalf because most won't bother and this way we 153 | * make the web a little safer. HTTP_MAX_HEADER_SIZE is still far bigger 154 | * than any reasonable request or response so this should never affect 155 | * day-to-day operation. 156 | */ 157 | #define COUNT_HEADER_SIZE(V) \ 158 | do { \ 159 | parser->nread += (V); \ 160 | if (UNLIKELY(parser->nread > (HTTP_MAX_HEADER_SIZE))) { \ 161 | SET_ERRNO(HPE_HEADER_OVERFLOW); \ 162 | goto error; \ 163 | } \ 164 | } while (0) 165 | 166 | 167 | #define PROXY_CONNECTION "proxy-connection" 168 | #define CONNECTION "connection" 169 | #define CONTENT_LENGTH "content-length" 170 | #define TRANSFER_ENCODING "transfer-encoding" 171 | #define UPGRADE "upgrade" 172 | #define CHUNKED "chunked" 173 | #define KEEP_ALIVE "keep-alive" 174 | #define CLOSE "close" 175 | 176 | 177 | static const char *method_strings[] = 178 | { 179 | #define XX(num, name, string) #string, 180 | HTTP_METHOD_MAP(XX) 181 | #undef XX 182 | }; 183 | 184 | 185 | /* Tokens as defined by rfc 2616. Also lowercases them. 186 | * token = 1* 187 | * separators = "(" | ")" | "<" | ">" | "@" 188 | * | "," | ";" | ":" | "\" | <"> 189 | * | "/" | "[" | "]" | "?" | "=" 190 | * | "{" | "}" | SP | HT 191 | */ 192 | static const char tokens[256] = { 193 | /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 194 | 0, 0, 0, 0, 0, 0, 0, 0, 195 | /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 196 | 0, 0, 0, 0, 0, 0, 0, 0, 197 | /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 198 | 0, 0, 0, 0, 0, 0, 0, 0, 199 | /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 200 | 0, 0, 0, 0, 0, 0, 0, 0, 201 | /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 202 | 0, '!', 0, '#', '$', '%', '&', '\'', 203 | /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 204 | 0, 0, '*', '+', 0, '-', '.', 0, 205 | /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ 206 | '0', '1', '2', '3', '4', '5', '6', '7', 207 | /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ 208 | '8', '9', 0, 0, 0, 0, 0, 0, 209 | /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 210 | 0, 'a', 'b', 'c', 'd', 'e', 'f', 'g', 211 | /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 212 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 213 | /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 214 | 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 215 | /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 216 | 'x', 'y', 'z', 0, 0, 0, '^', '_', 217 | /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ 218 | '`', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 219 | /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 220 | 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 221 | /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 222 | 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 223 | /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 224 | 'x', 'y', 'z', 0, '|', 0, '~', 0 }; 225 | 226 | 227 | static const int8_t unhex[256] = 228 | {-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 229 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 230 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 231 | , 0, 1, 2, 3, 4, 5, 6, 7, 8, 9,-1,-1,-1,-1,-1,-1 232 | ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 233 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 234 | ,-1,10,11,12,13,14,15,-1,-1,-1,-1,-1,-1,-1,-1,-1 235 | ,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1,-1 236 | }; 237 | 238 | 239 | #if HTTP_PARSER_STRICT 240 | # define T(v) 0 241 | #else 242 | # define T(v) v 243 | #endif 244 | 245 | 246 | static const uint8_t normal_url_char[32] = { 247 | /* 0 nul 1 soh 2 stx 3 etx 4 eot 5 enq 6 ack 7 bel */ 248 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, 249 | /* 8 bs 9 ht 10 nl 11 vt 12 np 13 cr 14 so 15 si */ 250 | 0 | T(2) | 0 | 0 | T(16) | 0 | 0 | 0, 251 | /* 16 dle 17 dc1 18 dc2 19 dc3 20 dc4 21 nak 22 syn 23 etb */ 252 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, 253 | /* 24 can 25 em 26 sub 27 esc 28 fs 29 gs 30 rs 31 us */ 254 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0, 255 | /* 32 sp 33 ! 34 " 35 # 36 $ 37 % 38 & 39 ' */ 256 | 0 | 2 | 4 | 0 | 16 | 32 | 64 | 128, 257 | /* 40 ( 41 ) 42 * 43 + 44 , 45 - 46 . 47 / */ 258 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 259 | /* 48 0 49 1 50 2 51 3 52 4 53 5 54 6 55 7 */ 260 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 261 | /* 56 8 57 9 58 : 59 ; 60 < 61 = 62 > 63 ? */ 262 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, 263 | /* 64 @ 65 A 66 B 67 C 68 D 69 E 70 F 71 G */ 264 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 265 | /* 72 H 73 I 74 J 75 K 76 L 77 M 78 N 79 O */ 266 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 267 | /* 80 P 81 Q 82 R 83 S 84 T 85 U 86 V 87 W */ 268 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 269 | /* 88 X 89 Y 90 Z 91 [ 92 \ 93 ] 94 ^ 95 _ */ 270 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 271 | /* 96 ` 97 a 98 b 99 c 100 d 101 e 102 f 103 g */ 272 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 273 | /* 104 h 105 i 106 j 107 k 108 l 109 m 110 n 111 o */ 274 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 275 | /* 112 p 113 q 114 r 115 s 116 t 117 u 118 v 119 w */ 276 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128, 277 | /* 120 x 121 y 122 z 123 { 124 | 125 } 126 ~ 127 del */ 278 | 1 | 2 | 4 | 8 | 16 | 32 | 64 | 0, }; 279 | 280 | #undef T 281 | 282 | enum state 283 | { s_dead = 1 /* important that this is > 0 */ 284 | 285 | , s_start_req_or_res 286 | , s_res_or_resp_H 287 | , s_start_res 288 | , s_res_H 289 | , s_res_HT 290 | , s_res_HTT 291 | , s_res_HTTP 292 | , s_res_first_http_major 293 | , s_res_http_major 294 | , s_res_first_http_minor 295 | , s_res_http_minor 296 | , s_res_first_status_code 297 | , s_res_status_code 298 | , s_res_status_start 299 | , s_res_status 300 | , s_res_line_almost_done 301 | 302 | , s_start_req 303 | 304 | , s_req_method 305 | , s_req_spaces_before_url 306 | , s_req_schema 307 | , s_req_schema_slash 308 | , s_req_schema_slash_slash 309 | , s_req_server_start 310 | , s_req_server 311 | , s_req_server_with_at 312 | , s_req_path 313 | , s_req_query_string_start 314 | , s_req_query_string 315 | , s_req_fragment_start 316 | , s_req_fragment 317 | , s_req_http_start 318 | , s_req_http_H 319 | , s_req_http_HT 320 | , s_req_http_HTT 321 | , s_req_http_HTTP 322 | , s_req_first_http_major 323 | , s_req_http_major 324 | , s_req_first_http_minor 325 | , s_req_http_minor 326 | , s_req_line_almost_done 327 | 328 | , s_header_field_start 329 | , s_header_field 330 | , s_header_value_discard_ws 331 | , s_header_value_discard_ws_almost_done 332 | , s_header_value_discard_lws 333 | , s_header_value_start 334 | , s_header_value 335 | , s_header_value_lws 336 | 337 | , s_header_almost_done 338 | 339 | , s_chunk_size_start 340 | , s_chunk_size 341 | , s_chunk_parameters 342 | , s_chunk_size_almost_done 343 | 344 | , s_headers_almost_done 345 | , s_headers_done 346 | 347 | /* Important: 's_headers_done' must be the last 'header' state. All 348 | * states beyond this must be 'body' states. It is used for overflow 349 | * checking. See the PARSING_HEADER() macro. 350 | */ 351 | 352 | , s_chunk_data 353 | , s_chunk_data_almost_done 354 | , s_chunk_data_done 355 | 356 | , s_body_identity 357 | , s_body_identity_eof 358 | 359 | , s_message_done 360 | }; 361 | 362 | 363 | #define PARSING_HEADER(state) (state <= s_headers_done) 364 | 365 | 366 | enum header_states 367 | { h_general = 0 368 | , h_C 369 | , h_CO 370 | , h_CON 371 | 372 | , h_matching_connection 373 | , h_matching_proxy_connection 374 | , h_matching_content_length 375 | , h_matching_transfer_encoding 376 | , h_matching_upgrade 377 | 378 | , h_connection 379 | , h_content_length 380 | , h_transfer_encoding 381 | , h_upgrade 382 | 383 | , h_matching_transfer_encoding_chunked 384 | , h_matching_connection_token_start 385 | , h_matching_connection_keep_alive 386 | , h_matching_connection_close 387 | , h_matching_connection_upgrade 388 | , h_matching_connection_token 389 | 390 | , h_transfer_encoding_chunked 391 | , h_connection_keep_alive 392 | , h_connection_close 393 | , h_connection_upgrade 394 | }; 395 | 396 | enum http_host_state 397 | { 398 | s_http_host_dead = 1 399 | , s_http_userinfo_start 400 | , s_http_userinfo 401 | , s_http_host_start 402 | , s_http_host_v6_start 403 | , s_http_host 404 | , s_http_host_v6 405 | , s_http_host_v6_end 406 | , s_http_host_port_start 407 | , s_http_host_port 408 | }; 409 | 410 | /* Macros for character classes; depends on strict-mode */ 411 | #define CR '\r' 412 | #define LF '\n' 413 | #define LOWER(c) (unsigned char)(c | 0x20) 414 | #define IS_ALPHA(c) (LOWER(c) >= 'a' && LOWER(c) <= 'z') 415 | #define IS_NUM(c) ((c) >= '0' && (c) <= '9') 416 | #define IS_ALPHANUM(c) (IS_ALPHA(c) || IS_NUM(c)) 417 | #define IS_HEX(c) (IS_NUM(c) || (LOWER(c) >= 'a' && LOWER(c) <= 'f')) 418 | #define IS_MARK(c) ((c) == '-' || (c) == '_' || (c) == '.' || \ 419 | (c) == '!' || (c) == '~' || (c) == '*' || (c) == '\'' || (c) == '(' || \ 420 | (c) == ')') 421 | #define IS_USERINFO_CHAR(c) (IS_ALPHANUM(c) || IS_MARK(c) || (c) == '%' || \ 422 | (c) == ';' || (c) == ':' || (c) == '&' || (c) == '=' || (c) == '+' || \ 423 | (c) == '$' || (c) == ',') 424 | 425 | #define STRICT_TOKEN(c) (tokens[(unsigned char)c]) 426 | 427 | #if HTTP_PARSER_STRICT 428 | #define TOKEN(c) (tokens[(unsigned char)c]) 429 | #define IS_URL_CHAR(c) (BIT_AT(normal_url_char, (unsigned char)c)) 430 | #define IS_HOST_CHAR(c) (IS_ALPHANUM(c) || (c) == '.' || (c) == '-') 431 | #else 432 | #define TOKEN(c) ((c == ' ') ? ' ' : tokens[(unsigned char)c]) 433 | #define IS_URL_CHAR(c) \ 434 | (BIT_AT(normal_url_char, (unsigned char)c) || ((c) & 0x80)) 435 | #define IS_HOST_CHAR(c) \ 436 | (IS_ALPHANUM(c) || (c) == '.' || (c) == '-' || (c) == '_') 437 | #endif 438 | 439 | 440 | #define start_state (parser->type == HTTP_REQUEST ? s_start_req : s_start_res) 441 | 442 | 443 | #if HTTP_PARSER_STRICT 444 | # define STRICT_CHECK(cond) \ 445 | do { \ 446 | if (cond) { \ 447 | SET_ERRNO(HPE_STRICT); \ 448 | goto error; \ 449 | } \ 450 | } while (0) 451 | # define NEW_MESSAGE() (http_should_keep_alive(parser) ? start_state : s_dead) 452 | #else 453 | # define STRICT_CHECK(cond) 454 | # define NEW_MESSAGE() start_state 455 | #endif 456 | 457 | 458 | /* Map errno values to strings for human-readable output */ 459 | #define HTTP_STRERROR_GEN(n, s) { "HPE_" #n, s }, 460 | static struct { 461 | const char *name; 462 | const char *description; 463 | } http_strerror_tab[] = { 464 | HTTP_ERRNO_MAP(HTTP_STRERROR_GEN) 465 | }; 466 | #undef HTTP_STRERROR_GEN 467 | 468 | int http_message_needs_eof(const http_parser *parser); 469 | 470 | /* Our URL parser. 471 | * 472 | * This is designed to be shared by http_parser_execute() for URL validation, 473 | * hence it has a state transition + byte-for-byte interface. In addition, it 474 | * is meant to be embedded in http_parser_parse_url(), which does the dirty 475 | * work of turning state transitions URL components for its API. 476 | * 477 | * This function should only be invoked with non-space characters. It is 478 | * assumed that the caller cares about (and can detect) the transition between 479 | * URL and non-URL states by looking for these. 480 | */ 481 | static enum state 482 | parse_url_char(enum state s, const char ch) 483 | { 484 | if (ch == ' ' || ch == '\r' || ch == '\n') { 485 | return s_dead; 486 | } 487 | 488 | #if HTTP_PARSER_STRICT 489 | if (ch == '\t' || ch == '\f') { 490 | return s_dead; 491 | } 492 | #endif 493 | 494 | switch (s) { 495 | case s_req_spaces_before_url: 496 | /* Proxied requests are followed by scheme of an absolute URI (alpha). 497 | * All methods except CONNECT are followed by '/' or '*'. 498 | */ 499 | 500 | if (ch == '/' || ch == '*') { 501 | return s_req_path; 502 | } 503 | 504 | if (IS_ALPHA(ch)) { 505 | return s_req_schema; 506 | } 507 | 508 | break; 509 | 510 | case s_req_schema: 511 | if (IS_ALPHA(ch)) { 512 | return s; 513 | } 514 | 515 | if (ch == ':') { 516 | return s_req_schema_slash; 517 | } 518 | 519 | break; 520 | 521 | case s_req_schema_slash: 522 | if (ch == '/') { 523 | return s_req_schema_slash_slash; 524 | } 525 | 526 | break; 527 | 528 | case s_req_schema_slash_slash: 529 | if (ch == '/') { 530 | return s_req_server_start; 531 | } 532 | 533 | break; 534 | 535 | case s_req_server_with_at: 536 | if (ch == '@') { 537 | return s_dead; 538 | } 539 | 540 | /* FALLTHROUGH */ 541 | case s_req_server_start: 542 | case s_req_server: 543 | if (ch == '/') { 544 | return s_req_path; 545 | } 546 | 547 | if (ch == '?') { 548 | return s_req_query_string_start; 549 | } 550 | 551 | if (ch == '@') { 552 | return s_req_server_with_at; 553 | } 554 | 555 | if (IS_USERINFO_CHAR(ch) || ch == '[' || ch == ']') { 556 | return s_req_server; 557 | } 558 | 559 | break; 560 | 561 | case s_req_path: 562 | if (IS_URL_CHAR(ch)) { 563 | return s; 564 | } 565 | 566 | switch (ch) { 567 | case '?': 568 | return s_req_query_string_start; 569 | 570 | case '#': 571 | return s_req_fragment_start; 572 | } 573 | 574 | break; 575 | 576 | case s_req_query_string_start: 577 | case s_req_query_string: 578 | if (IS_URL_CHAR(ch)) { 579 | return s_req_query_string; 580 | } 581 | 582 | switch (ch) { 583 | case '?': 584 | /* allow extra '?' in query string */ 585 | return s_req_query_string; 586 | 587 | case '#': 588 | return s_req_fragment_start; 589 | } 590 | 591 | break; 592 | 593 | case s_req_fragment_start: 594 | if (IS_URL_CHAR(ch)) { 595 | return s_req_fragment; 596 | } 597 | 598 | switch (ch) { 599 | case '?': 600 | return s_req_fragment; 601 | 602 | case '#': 603 | return s; 604 | } 605 | 606 | break; 607 | 608 | case s_req_fragment: 609 | if (IS_URL_CHAR(ch)) { 610 | return s; 611 | } 612 | 613 | switch (ch) { 614 | case '?': 615 | case '#': 616 | return s; 617 | } 618 | 619 | break; 620 | 621 | default: 622 | break; 623 | } 624 | 625 | /* We should never fall out of the switch above unless there's an error */ 626 | return s_dead; 627 | } 628 | 629 | size_t http_parser_execute (http_parser *parser, 630 | const http_parser_settings *settings, 631 | const char *data, 632 | size_t len) 633 | { 634 | char c, ch; 635 | int8_t unhex_val; 636 | const char *p = data; 637 | const char *header_field_mark = 0; 638 | const char *header_value_mark = 0; 639 | const char *url_mark = 0; 640 | const char *body_mark = 0; 641 | const char *status_mark = 0; 642 | enum state p_state = (enum state) parser->state; 643 | 644 | /* We're in an error state. Don't bother doing anything. */ 645 | if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { 646 | return 0; 647 | } 648 | 649 | if (len == 0) { 650 | switch (CURRENT_STATE()) { 651 | case s_body_identity_eof: 652 | /* Use of CALLBACK_NOTIFY() here would erroneously return 1 byte read if 653 | * we got paused. 654 | */ 655 | CALLBACK_NOTIFY_NOADVANCE(message_complete); 656 | return 0; 657 | 658 | case s_dead: 659 | case s_start_req_or_res: 660 | case s_start_res: 661 | case s_start_req: 662 | return 0; 663 | 664 | default: 665 | SET_ERRNO(HPE_INVALID_EOF_STATE); 666 | return 1; 667 | } 668 | } 669 | 670 | 671 | if (CURRENT_STATE() == s_header_field) 672 | header_field_mark = data; 673 | if (CURRENT_STATE() == s_header_value) 674 | header_value_mark = data; 675 | switch (CURRENT_STATE()) { 676 | case s_req_path: 677 | case s_req_schema: 678 | case s_req_schema_slash: 679 | case s_req_schema_slash_slash: 680 | case s_req_server_start: 681 | case s_req_server: 682 | case s_req_server_with_at: 683 | case s_req_query_string_start: 684 | case s_req_query_string: 685 | case s_req_fragment_start: 686 | case s_req_fragment: 687 | url_mark = data; 688 | break; 689 | case s_res_status: 690 | status_mark = data; 691 | break; 692 | default: 693 | break; 694 | } 695 | 696 | for (p=data; p != data + len; p++) { 697 | ch = *p; 698 | 699 | if (PARSING_HEADER(CURRENT_STATE())) 700 | COUNT_HEADER_SIZE(1); 701 | 702 | reexecute: 703 | switch (CURRENT_STATE()) { 704 | 705 | case s_dead: 706 | /* this state is used after a 'Connection: close' message 707 | * the parser will error out if it reads another message 708 | */ 709 | if (LIKELY(ch == CR || ch == LF)) 710 | break; 711 | 712 | SET_ERRNO(HPE_CLOSED_CONNECTION); 713 | goto error; 714 | 715 | case s_start_req_or_res: 716 | { 717 | if (ch == CR || ch == LF) 718 | break; 719 | parser->flags = 0; 720 | parser->content_length = ULLONG_MAX; 721 | 722 | if (ch == 'H') { 723 | UPDATE_STATE(s_res_or_resp_H); 724 | 725 | CALLBACK_NOTIFY(message_begin); 726 | } else { 727 | parser->type = HTTP_REQUEST; 728 | UPDATE_STATE(s_start_req); 729 | REEXECUTE(); 730 | } 731 | 732 | break; 733 | } 734 | 735 | case s_res_or_resp_H: 736 | if (ch == 'T') { 737 | parser->type = HTTP_RESPONSE; 738 | UPDATE_STATE(s_res_HT); 739 | } else { 740 | if (UNLIKELY(ch != 'E')) { 741 | SET_ERRNO(HPE_INVALID_CONSTANT); 742 | goto error; 743 | } 744 | 745 | parser->type = HTTP_REQUEST; 746 | parser->method = HTTP_HEAD; 747 | parser->index = 2; 748 | UPDATE_STATE(s_req_method); 749 | } 750 | break; 751 | 752 | case s_start_res: 753 | { 754 | parser->flags = 0; 755 | parser->content_length = ULLONG_MAX; 756 | 757 | switch (ch) { 758 | case 'H': 759 | UPDATE_STATE(s_res_H); 760 | break; 761 | 762 | case CR: 763 | case LF: 764 | break; 765 | 766 | default: 767 | SET_ERRNO(HPE_INVALID_CONSTANT); 768 | goto error; 769 | } 770 | 771 | CALLBACK_NOTIFY(message_begin); 772 | break; 773 | } 774 | 775 | case s_res_H: 776 | STRICT_CHECK(ch != 'T'); 777 | UPDATE_STATE(s_res_HT); 778 | break; 779 | 780 | case s_res_HT: 781 | STRICT_CHECK(ch != 'T'); 782 | UPDATE_STATE(s_res_HTT); 783 | break; 784 | 785 | case s_res_HTT: 786 | STRICT_CHECK(ch != 'P'); 787 | UPDATE_STATE(s_res_HTTP); 788 | break; 789 | 790 | case s_res_HTTP: 791 | STRICT_CHECK(ch != '/'); 792 | UPDATE_STATE(s_res_first_http_major); 793 | break; 794 | 795 | case s_res_first_http_major: 796 | if (UNLIKELY(ch < '0' || ch > '9')) { 797 | SET_ERRNO(HPE_INVALID_VERSION); 798 | goto error; 799 | } 800 | 801 | parser->http_major = ch - '0'; 802 | UPDATE_STATE(s_res_http_major); 803 | break; 804 | 805 | /* major HTTP version or dot */ 806 | case s_res_http_major: 807 | { 808 | if (ch == '.') { 809 | UPDATE_STATE(s_res_first_http_minor); 810 | break; 811 | } 812 | 813 | if (!IS_NUM(ch)) { 814 | SET_ERRNO(HPE_INVALID_VERSION); 815 | goto error; 816 | } 817 | 818 | parser->http_major *= 10; 819 | parser->http_major += ch - '0'; 820 | 821 | if (UNLIKELY(parser->http_major > 999)) { 822 | SET_ERRNO(HPE_INVALID_VERSION); 823 | goto error; 824 | } 825 | 826 | break; 827 | } 828 | 829 | /* first digit of minor HTTP version */ 830 | case s_res_first_http_minor: 831 | if (UNLIKELY(!IS_NUM(ch))) { 832 | SET_ERRNO(HPE_INVALID_VERSION); 833 | goto error; 834 | } 835 | 836 | parser->http_minor = ch - '0'; 837 | UPDATE_STATE(s_res_http_minor); 838 | break; 839 | 840 | /* minor HTTP version or end of request line */ 841 | case s_res_http_minor: 842 | { 843 | if (ch == ' ') { 844 | UPDATE_STATE(s_res_first_status_code); 845 | break; 846 | } 847 | 848 | if (UNLIKELY(!IS_NUM(ch))) { 849 | SET_ERRNO(HPE_INVALID_VERSION); 850 | goto error; 851 | } 852 | 853 | parser->http_minor *= 10; 854 | parser->http_minor += ch - '0'; 855 | 856 | if (UNLIKELY(parser->http_minor > 999)) { 857 | SET_ERRNO(HPE_INVALID_VERSION); 858 | goto error; 859 | } 860 | 861 | break; 862 | } 863 | 864 | case s_res_first_status_code: 865 | { 866 | if (!IS_NUM(ch)) { 867 | if (ch == ' ') { 868 | break; 869 | } 870 | 871 | SET_ERRNO(HPE_INVALID_STATUS); 872 | goto error; 873 | } 874 | parser->status_code = ch - '0'; 875 | UPDATE_STATE(s_res_status_code); 876 | break; 877 | } 878 | 879 | case s_res_status_code: 880 | { 881 | if (!IS_NUM(ch)) { 882 | switch (ch) { 883 | case ' ': 884 | UPDATE_STATE(s_res_status_start); 885 | break; 886 | case CR: 887 | UPDATE_STATE(s_res_line_almost_done); 888 | break; 889 | case LF: 890 | UPDATE_STATE(s_header_field_start); 891 | break; 892 | default: 893 | SET_ERRNO(HPE_INVALID_STATUS); 894 | goto error; 895 | } 896 | break; 897 | } 898 | 899 | parser->status_code *= 10; 900 | parser->status_code += ch - '0'; 901 | 902 | if (UNLIKELY(parser->status_code > 999)) { 903 | SET_ERRNO(HPE_INVALID_STATUS); 904 | goto error; 905 | } 906 | 907 | break; 908 | } 909 | 910 | case s_res_status_start: 911 | { 912 | if (ch == CR) { 913 | UPDATE_STATE(s_res_line_almost_done); 914 | break; 915 | } 916 | 917 | if (ch == LF) { 918 | UPDATE_STATE(s_header_field_start); 919 | break; 920 | } 921 | 922 | MARK(status); 923 | UPDATE_STATE(s_res_status); 924 | parser->index = 0; 925 | break; 926 | } 927 | 928 | case s_res_status: 929 | if (ch == CR) { 930 | UPDATE_STATE(s_res_line_almost_done); 931 | CALLBACK_DATA(status); 932 | break; 933 | } 934 | 935 | if (ch == LF) { 936 | UPDATE_STATE(s_header_field_start); 937 | CALLBACK_DATA(status); 938 | break; 939 | } 940 | 941 | break; 942 | 943 | case s_res_line_almost_done: 944 | STRICT_CHECK(ch != LF); 945 | UPDATE_STATE(s_header_field_start); 946 | break; 947 | 948 | case s_start_req: 949 | { 950 | if (ch == CR || ch == LF) 951 | break; 952 | parser->flags = 0; 953 | parser->content_length = ULLONG_MAX; 954 | 955 | if (UNLIKELY(!IS_ALPHA(ch))) { 956 | SET_ERRNO(HPE_INVALID_METHOD); 957 | goto error; 958 | } 959 | 960 | parser->method = (enum http_method) 0; 961 | parser->index = 1; 962 | switch (ch) { 963 | case 'C': parser->method = HTTP_CONNECT; /* or COPY, CHECKOUT */ break; 964 | case 'D': parser->method = HTTP_DELETE; break; 965 | case 'G': parser->method = HTTP_GET; break; 966 | case 'H': parser->method = HTTP_HEAD; break; 967 | case 'L': parser->method = HTTP_LOCK; break; 968 | case 'M': parser->method = HTTP_MKCOL; /* or MOVE, MKACTIVITY, MERGE, M-SEARCH, MKCALENDAR */ break; 969 | case 'N': parser->method = HTTP_NOTIFY; break; 970 | case 'O': parser->method = HTTP_OPTIONS; break; 971 | case 'P': parser->method = HTTP_POST; 972 | /* or PROPFIND|PROPPATCH|PUT|PATCH|PURGE */ 973 | break; 974 | case 'R': parser->method = HTTP_REPORT; break; 975 | case 'S': parser->method = HTTP_SUBSCRIBE; /* or SEARCH */ break; 976 | case 'T': parser->method = HTTP_TRACE; break; 977 | case 'U': parser->method = HTTP_UNLOCK; /* or UNSUBSCRIBE */ break; 978 | default: 979 | SET_ERRNO(HPE_INVALID_METHOD); 980 | goto error; 981 | } 982 | UPDATE_STATE(s_req_method); 983 | 984 | CALLBACK_NOTIFY(message_begin); 985 | 986 | break; 987 | } 988 | 989 | case s_req_method: 990 | { 991 | const char *matcher; 992 | if (UNLIKELY(ch == '\0')) { 993 | SET_ERRNO(HPE_INVALID_METHOD); 994 | goto error; 995 | } 996 | 997 | matcher = method_strings[parser->method]; 998 | if (ch == ' ' && matcher[parser->index] == '\0') { 999 | UPDATE_STATE(s_req_spaces_before_url); 1000 | } else if (ch == matcher[parser->index]) { 1001 | ; /* nada */ 1002 | } else if (parser->method == HTTP_CONNECT) { 1003 | if (parser->index == 1 && ch == 'H') { 1004 | parser->method = HTTP_CHECKOUT; 1005 | } else if (parser->index == 2 && ch == 'P') { 1006 | parser->method = HTTP_COPY; 1007 | } else { 1008 | SET_ERRNO(HPE_INVALID_METHOD); 1009 | goto error; 1010 | } 1011 | } else if (parser->method == HTTP_MKCOL) { 1012 | if (parser->index == 1 && ch == 'O') { 1013 | parser->method = HTTP_MOVE; 1014 | } else if (parser->index == 1 && ch == 'E') { 1015 | parser->method = HTTP_MERGE; 1016 | } else if (parser->index == 1 && ch == '-') { 1017 | parser->method = HTTP_MSEARCH; 1018 | } else if (parser->index == 2 && ch == 'A') { 1019 | parser->method = HTTP_MKACTIVITY; 1020 | } else if (parser->index == 3 && ch == 'A') { 1021 | parser->method = HTTP_MKCALENDAR; 1022 | } else { 1023 | SET_ERRNO(HPE_INVALID_METHOD); 1024 | goto error; 1025 | } 1026 | } else if (parser->method == HTTP_SUBSCRIBE) { 1027 | if (parser->index == 1 && ch == 'E') { 1028 | parser->method = HTTP_SEARCH; 1029 | } else { 1030 | SET_ERRNO(HPE_INVALID_METHOD); 1031 | goto error; 1032 | } 1033 | } else if (parser->index == 1 && parser->method == HTTP_POST) { 1034 | if (ch == 'R') { 1035 | parser->method = HTTP_PROPFIND; /* or HTTP_PROPPATCH */ 1036 | } else if (ch == 'U') { 1037 | parser->method = HTTP_PUT; /* or HTTP_PURGE */ 1038 | } else if (ch == 'A') { 1039 | parser->method = HTTP_PATCH; 1040 | } else { 1041 | SET_ERRNO(HPE_INVALID_METHOD); 1042 | goto error; 1043 | } 1044 | } else if (parser->index == 2) { 1045 | if (parser->method == HTTP_PUT) { 1046 | if (ch == 'R') { 1047 | parser->method = HTTP_PURGE; 1048 | } else { 1049 | SET_ERRNO(HPE_INVALID_METHOD); 1050 | goto error; 1051 | } 1052 | } else if (parser->method == HTTP_UNLOCK) { 1053 | if (ch == 'S') { 1054 | parser->method = HTTP_UNSUBSCRIBE; 1055 | } else { 1056 | SET_ERRNO(HPE_INVALID_METHOD); 1057 | goto error; 1058 | } 1059 | } else { 1060 | SET_ERRNO(HPE_INVALID_METHOD); 1061 | goto error; 1062 | } 1063 | } else if (parser->index == 4 && parser->method == HTTP_PROPFIND && ch == 'P') { 1064 | parser->method = HTTP_PROPPATCH; 1065 | } else { 1066 | SET_ERRNO(HPE_INVALID_METHOD); 1067 | goto error; 1068 | } 1069 | 1070 | ++parser->index; 1071 | break; 1072 | } 1073 | 1074 | case s_req_spaces_before_url: 1075 | { 1076 | if (ch == ' ') break; 1077 | 1078 | MARK(url); 1079 | if (parser->method == HTTP_CONNECT) { 1080 | UPDATE_STATE(s_req_server_start); 1081 | } 1082 | 1083 | UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); 1084 | if (UNLIKELY(CURRENT_STATE() == s_dead)) { 1085 | SET_ERRNO(HPE_INVALID_URL); 1086 | goto error; 1087 | } 1088 | 1089 | break; 1090 | } 1091 | 1092 | case s_req_schema: 1093 | case s_req_schema_slash: 1094 | case s_req_schema_slash_slash: 1095 | case s_req_server_start: 1096 | { 1097 | switch (ch) { 1098 | /* No whitespace allowed here */ 1099 | case ' ': 1100 | case CR: 1101 | case LF: 1102 | SET_ERRNO(HPE_INVALID_URL); 1103 | goto error; 1104 | default: 1105 | UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); 1106 | if (UNLIKELY(CURRENT_STATE() == s_dead)) { 1107 | SET_ERRNO(HPE_INVALID_URL); 1108 | goto error; 1109 | } 1110 | } 1111 | 1112 | break; 1113 | } 1114 | 1115 | case s_req_server: 1116 | case s_req_server_with_at: 1117 | case s_req_path: 1118 | case s_req_query_string_start: 1119 | case s_req_query_string: 1120 | case s_req_fragment_start: 1121 | case s_req_fragment: 1122 | { 1123 | switch (ch) { 1124 | case ' ': 1125 | UPDATE_STATE(s_req_http_start); 1126 | CALLBACK_DATA(url); 1127 | break; 1128 | case CR: 1129 | case LF: 1130 | parser->http_major = 0; 1131 | parser->http_minor = 9; 1132 | UPDATE_STATE((ch == CR) ? 1133 | s_req_line_almost_done : 1134 | s_header_field_start); 1135 | CALLBACK_DATA(url); 1136 | break; 1137 | default: 1138 | UPDATE_STATE(parse_url_char(CURRENT_STATE(), ch)); 1139 | if (UNLIKELY(CURRENT_STATE() == s_dead)) { 1140 | SET_ERRNO(HPE_INVALID_URL); 1141 | goto error; 1142 | } 1143 | } 1144 | break; 1145 | } 1146 | 1147 | case s_req_http_start: 1148 | switch (ch) { 1149 | case 'H': 1150 | UPDATE_STATE(s_req_http_H); 1151 | break; 1152 | case ' ': 1153 | break; 1154 | default: 1155 | SET_ERRNO(HPE_INVALID_CONSTANT); 1156 | goto error; 1157 | } 1158 | break; 1159 | 1160 | case s_req_http_H: 1161 | STRICT_CHECK(ch != 'T'); 1162 | UPDATE_STATE(s_req_http_HT); 1163 | break; 1164 | 1165 | case s_req_http_HT: 1166 | STRICT_CHECK(ch != 'T'); 1167 | UPDATE_STATE(s_req_http_HTT); 1168 | break; 1169 | 1170 | case s_req_http_HTT: 1171 | STRICT_CHECK(ch != 'P'); 1172 | UPDATE_STATE(s_req_http_HTTP); 1173 | break; 1174 | 1175 | case s_req_http_HTTP: 1176 | STRICT_CHECK(ch != '/'); 1177 | UPDATE_STATE(s_req_first_http_major); 1178 | break; 1179 | 1180 | /* first digit of major HTTP version */ 1181 | case s_req_first_http_major: 1182 | if (UNLIKELY(ch < '1' || ch > '9')) { 1183 | SET_ERRNO(HPE_INVALID_VERSION); 1184 | goto error; 1185 | } 1186 | 1187 | parser->http_major = ch - '0'; 1188 | UPDATE_STATE(s_req_http_major); 1189 | break; 1190 | 1191 | /* major HTTP version or dot */ 1192 | case s_req_http_major: 1193 | { 1194 | if (ch == '.') { 1195 | UPDATE_STATE(s_req_first_http_minor); 1196 | break; 1197 | } 1198 | 1199 | if (UNLIKELY(!IS_NUM(ch))) { 1200 | SET_ERRNO(HPE_INVALID_VERSION); 1201 | goto error; 1202 | } 1203 | 1204 | parser->http_major *= 10; 1205 | parser->http_major += ch - '0'; 1206 | 1207 | if (UNLIKELY(parser->http_major > 999)) { 1208 | SET_ERRNO(HPE_INVALID_VERSION); 1209 | goto error; 1210 | } 1211 | 1212 | break; 1213 | } 1214 | 1215 | /* first digit of minor HTTP version */ 1216 | case s_req_first_http_minor: 1217 | if (UNLIKELY(!IS_NUM(ch))) { 1218 | SET_ERRNO(HPE_INVALID_VERSION); 1219 | goto error; 1220 | } 1221 | 1222 | parser->http_minor = ch - '0'; 1223 | UPDATE_STATE(s_req_http_minor); 1224 | break; 1225 | 1226 | /* minor HTTP version or end of request line */ 1227 | case s_req_http_minor: 1228 | { 1229 | if (ch == CR) { 1230 | UPDATE_STATE(s_req_line_almost_done); 1231 | break; 1232 | } 1233 | 1234 | if (ch == LF) { 1235 | UPDATE_STATE(s_header_field_start); 1236 | break; 1237 | } 1238 | 1239 | /* XXX allow spaces after digit? */ 1240 | 1241 | if (UNLIKELY(!IS_NUM(ch))) { 1242 | SET_ERRNO(HPE_INVALID_VERSION); 1243 | goto error; 1244 | } 1245 | 1246 | parser->http_minor *= 10; 1247 | parser->http_minor += ch - '0'; 1248 | 1249 | if (UNLIKELY(parser->http_minor > 999)) { 1250 | SET_ERRNO(HPE_INVALID_VERSION); 1251 | goto error; 1252 | } 1253 | 1254 | break; 1255 | } 1256 | 1257 | /* end of request line */ 1258 | case s_req_line_almost_done: 1259 | { 1260 | if (UNLIKELY(ch != LF)) { 1261 | SET_ERRNO(HPE_LF_EXPECTED); 1262 | goto error; 1263 | } 1264 | 1265 | UPDATE_STATE(s_header_field_start); 1266 | break; 1267 | } 1268 | 1269 | case s_header_field_start: 1270 | { 1271 | if (ch == CR) { 1272 | UPDATE_STATE(s_headers_almost_done); 1273 | break; 1274 | } 1275 | 1276 | if (ch == LF) { 1277 | /* they might be just sending \n instead of \r\n so this would be 1278 | * the second \n to denote the end of headers*/ 1279 | UPDATE_STATE(s_headers_almost_done); 1280 | REEXECUTE(); 1281 | } 1282 | 1283 | c = TOKEN(ch); 1284 | 1285 | if (UNLIKELY(!c)) { 1286 | SET_ERRNO(HPE_INVALID_HEADER_TOKEN); 1287 | goto error; 1288 | } 1289 | 1290 | MARK(header_field); 1291 | 1292 | parser->index = 0; 1293 | UPDATE_STATE(s_header_field); 1294 | 1295 | switch (c) { 1296 | case 'c': 1297 | parser->header_state = h_C; 1298 | break; 1299 | 1300 | case 'p': 1301 | parser->header_state = h_matching_proxy_connection; 1302 | break; 1303 | 1304 | case 't': 1305 | parser->header_state = h_matching_transfer_encoding; 1306 | break; 1307 | 1308 | case 'u': 1309 | parser->header_state = h_matching_upgrade; 1310 | break; 1311 | 1312 | default: 1313 | parser->header_state = h_general; 1314 | break; 1315 | } 1316 | break; 1317 | } 1318 | 1319 | case s_header_field: 1320 | { 1321 | const char* start = p; 1322 | for (; p != data + len; p++) { 1323 | ch = *p; 1324 | c = TOKEN(ch); 1325 | 1326 | if (!c) 1327 | break; 1328 | 1329 | switch (parser->header_state) { 1330 | case h_general: 1331 | break; 1332 | 1333 | case h_C: 1334 | parser->index++; 1335 | parser->header_state = (c == 'o' ? h_CO : h_general); 1336 | break; 1337 | 1338 | case h_CO: 1339 | parser->index++; 1340 | parser->header_state = (c == 'n' ? h_CON : h_general); 1341 | break; 1342 | 1343 | case h_CON: 1344 | parser->index++; 1345 | switch (c) { 1346 | case 'n': 1347 | parser->header_state = h_matching_connection; 1348 | break; 1349 | case 't': 1350 | parser->header_state = h_matching_content_length; 1351 | break; 1352 | default: 1353 | parser->header_state = h_general; 1354 | break; 1355 | } 1356 | break; 1357 | 1358 | /* connection */ 1359 | 1360 | case h_matching_connection: 1361 | parser->index++; 1362 | if (parser->index > sizeof(CONNECTION)-1 1363 | || c != CONNECTION[parser->index]) { 1364 | parser->header_state = h_general; 1365 | } else if (parser->index == sizeof(CONNECTION)-2) { 1366 | parser->header_state = h_connection; 1367 | } 1368 | break; 1369 | 1370 | /* proxy-connection */ 1371 | 1372 | case h_matching_proxy_connection: 1373 | parser->index++; 1374 | if (parser->index > sizeof(PROXY_CONNECTION)-1 1375 | || c != PROXY_CONNECTION[parser->index]) { 1376 | parser->header_state = h_general; 1377 | } else if (parser->index == sizeof(PROXY_CONNECTION)-2) { 1378 | parser->header_state = h_connection; 1379 | } 1380 | break; 1381 | 1382 | /* content-length */ 1383 | 1384 | case h_matching_content_length: 1385 | parser->index++; 1386 | if (parser->index > sizeof(CONTENT_LENGTH)-1 1387 | || c != CONTENT_LENGTH[parser->index]) { 1388 | parser->header_state = h_general; 1389 | } else if (parser->index == sizeof(CONTENT_LENGTH)-2) { 1390 | parser->header_state = h_content_length; 1391 | } 1392 | break; 1393 | 1394 | /* transfer-encoding */ 1395 | 1396 | case h_matching_transfer_encoding: 1397 | parser->index++; 1398 | if (parser->index > sizeof(TRANSFER_ENCODING)-1 1399 | || c != TRANSFER_ENCODING[parser->index]) { 1400 | parser->header_state = h_general; 1401 | } else if (parser->index == sizeof(TRANSFER_ENCODING)-2) { 1402 | parser->header_state = h_transfer_encoding; 1403 | } 1404 | break; 1405 | 1406 | /* upgrade */ 1407 | 1408 | case h_matching_upgrade: 1409 | parser->index++; 1410 | if (parser->index > sizeof(UPGRADE)-1 1411 | || c != UPGRADE[parser->index]) { 1412 | parser->header_state = h_general; 1413 | } else if (parser->index == sizeof(UPGRADE)-2) { 1414 | parser->header_state = h_upgrade; 1415 | } 1416 | break; 1417 | 1418 | case h_connection: 1419 | case h_content_length: 1420 | case h_transfer_encoding: 1421 | case h_upgrade: 1422 | if (ch != ' ') parser->header_state = h_general; 1423 | break; 1424 | 1425 | default: 1426 | assert(0 && "Unknown header_state"); 1427 | break; 1428 | } 1429 | } 1430 | 1431 | COUNT_HEADER_SIZE(p - start); 1432 | 1433 | if (p == data + len) { 1434 | --p; 1435 | break; 1436 | } 1437 | 1438 | if (ch == ':') { 1439 | UPDATE_STATE(s_header_value_discard_ws); 1440 | CALLBACK_DATA(header_field); 1441 | break; 1442 | } 1443 | 1444 | SET_ERRNO(HPE_INVALID_HEADER_TOKEN); 1445 | goto error; 1446 | } 1447 | 1448 | case s_header_value_discard_ws: 1449 | if (ch == ' ' || ch == '\t') break; 1450 | 1451 | if (ch == CR) { 1452 | UPDATE_STATE(s_header_value_discard_ws_almost_done); 1453 | break; 1454 | } 1455 | 1456 | if (ch == LF) { 1457 | UPDATE_STATE(s_header_value_discard_lws); 1458 | break; 1459 | } 1460 | 1461 | /* FALLTHROUGH */ 1462 | 1463 | case s_header_value_start: 1464 | { 1465 | MARK(header_value); 1466 | 1467 | UPDATE_STATE(s_header_value); 1468 | parser->index = 0; 1469 | 1470 | c = LOWER(ch); 1471 | 1472 | switch (parser->header_state) { 1473 | case h_upgrade: 1474 | parser->flags |= F_UPGRADE; 1475 | parser->header_state = h_general; 1476 | break; 1477 | 1478 | case h_transfer_encoding: 1479 | /* looking for 'Transfer-Encoding: chunked' */ 1480 | if ('c' == c) { 1481 | parser->header_state = h_matching_transfer_encoding_chunked; 1482 | } else { 1483 | parser->header_state = h_general; 1484 | } 1485 | break; 1486 | 1487 | case h_content_length: 1488 | if (UNLIKELY(!IS_NUM(ch))) { 1489 | SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); 1490 | goto error; 1491 | } 1492 | 1493 | parser->content_length = ch - '0'; 1494 | break; 1495 | 1496 | case h_connection: 1497 | /* looking for 'Connection: keep-alive' */ 1498 | if (c == 'k') { 1499 | parser->header_state = h_matching_connection_keep_alive; 1500 | /* looking for 'Connection: close' */ 1501 | } else if (c == 'c') { 1502 | parser->header_state = h_matching_connection_close; 1503 | } else if (c == 'u') { 1504 | parser->header_state = h_matching_connection_upgrade; 1505 | } else { 1506 | parser->header_state = h_matching_connection_token; 1507 | } 1508 | break; 1509 | 1510 | /* Multi-value `Connection` header */ 1511 | case h_matching_connection_token_start: 1512 | break; 1513 | 1514 | default: 1515 | parser->header_state = h_general; 1516 | break; 1517 | } 1518 | break; 1519 | } 1520 | 1521 | case s_header_value: 1522 | { 1523 | const char* start = p; 1524 | enum header_states h_state = (enum header_states) parser->header_state; 1525 | for (; p != data + len; p++) { 1526 | ch = *p; 1527 | if (ch == CR) { 1528 | UPDATE_STATE(s_header_almost_done); 1529 | parser->header_state = h_state; 1530 | CALLBACK_DATA(header_value); 1531 | break; 1532 | } 1533 | 1534 | if (ch == LF) { 1535 | UPDATE_STATE(s_header_almost_done); 1536 | COUNT_HEADER_SIZE(p - start); 1537 | parser->header_state = h_state; 1538 | CALLBACK_DATA_NOADVANCE(header_value); 1539 | REEXECUTE(); 1540 | } 1541 | 1542 | c = LOWER(ch); 1543 | 1544 | switch (h_state) { 1545 | case h_general: 1546 | { 1547 | const char* p_cr; 1548 | const char* p_lf; 1549 | size_t limit = data + len - p; 1550 | 1551 | limit = MIN(limit, HTTP_MAX_HEADER_SIZE); 1552 | 1553 | p_cr = (const char*) memchr(p, CR, limit); 1554 | p_lf = (const char*) memchr(p, LF, limit); 1555 | if (p_cr != NULL) { 1556 | if (p_lf != NULL && p_cr >= p_lf) 1557 | p = p_lf; 1558 | else 1559 | p = p_cr; 1560 | } else if (UNLIKELY(p_lf != NULL)) { 1561 | p = p_lf; 1562 | } else { 1563 | p = data + len; 1564 | } 1565 | --p; 1566 | 1567 | break; 1568 | } 1569 | 1570 | case h_connection: 1571 | case h_transfer_encoding: 1572 | assert(0 && "Shouldn't get here."); 1573 | break; 1574 | 1575 | case h_content_length: 1576 | { 1577 | uint64_t t; 1578 | 1579 | if (ch == ' ') break; 1580 | 1581 | if (UNLIKELY(!IS_NUM(ch))) { 1582 | SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); 1583 | parser->header_state = h_state; 1584 | goto error; 1585 | } 1586 | 1587 | t = parser->content_length; 1588 | t *= 10; 1589 | t += ch - '0'; 1590 | 1591 | /* Overflow? Test against a conservative limit for simplicity. */ 1592 | if (UNLIKELY((ULLONG_MAX - 10) / 10 < parser->content_length)) { 1593 | SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); 1594 | parser->header_state = h_state; 1595 | goto error; 1596 | } 1597 | 1598 | parser->content_length = t; 1599 | break; 1600 | } 1601 | 1602 | /* Transfer-Encoding: chunked */ 1603 | case h_matching_transfer_encoding_chunked: 1604 | parser->index++; 1605 | if (parser->index > sizeof(CHUNKED)-1 1606 | || c != CHUNKED[parser->index]) { 1607 | h_state = h_general; 1608 | } else if (parser->index == sizeof(CHUNKED)-2) { 1609 | h_state = h_transfer_encoding_chunked; 1610 | } 1611 | break; 1612 | 1613 | case h_matching_connection_token_start: 1614 | /* looking for 'Connection: keep-alive' */ 1615 | if (c == 'k') { 1616 | h_state = h_matching_connection_keep_alive; 1617 | /* looking for 'Connection: close' */ 1618 | } else if (c == 'c') { 1619 | h_state = h_matching_connection_close; 1620 | } else if (c == 'u') { 1621 | h_state = h_matching_connection_upgrade; 1622 | } else if (STRICT_TOKEN(c)) { 1623 | h_state = h_matching_connection_token; 1624 | } else if (c == ' ' || c == '\t') { 1625 | /* Skip lws */ 1626 | } else { 1627 | h_state = h_general; 1628 | } 1629 | break; 1630 | 1631 | /* looking for 'Connection: keep-alive' */ 1632 | case h_matching_connection_keep_alive: 1633 | parser->index++; 1634 | if (parser->index > sizeof(KEEP_ALIVE)-1 1635 | || c != KEEP_ALIVE[parser->index]) { 1636 | h_state = h_matching_connection_token; 1637 | } else if (parser->index == sizeof(KEEP_ALIVE)-2) { 1638 | h_state = h_connection_keep_alive; 1639 | } 1640 | break; 1641 | 1642 | /* looking for 'Connection: close' */ 1643 | case h_matching_connection_close: 1644 | parser->index++; 1645 | if (parser->index > sizeof(CLOSE)-1 || c != CLOSE[parser->index]) { 1646 | h_state = h_matching_connection_token; 1647 | } else if (parser->index == sizeof(CLOSE)-2) { 1648 | h_state = h_connection_close; 1649 | } 1650 | break; 1651 | 1652 | /* looking for 'Connection: upgrade' */ 1653 | case h_matching_connection_upgrade: 1654 | parser->index++; 1655 | if (parser->index > sizeof(UPGRADE) - 1 || 1656 | c != UPGRADE[parser->index]) { 1657 | h_state = h_matching_connection_token; 1658 | } else if (parser->index == sizeof(UPGRADE)-2) { 1659 | h_state = h_connection_upgrade; 1660 | } 1661 | break; 1662 | 1663 | case h_matching_connection_token: 1664 | if (ch == ',') { 1665 | h_state = h_matching_connection_token_start; 1666 | parser->index = 0; 1667 | } 1668 | break; 1669 | 1670 | case h_transfer_encoding_chunked: 1671 | if (ch != ' ') h_state = h_general; 1672 | break; 1673 | 1674 | case h_connection_keep_alive: 1675 | case h_connection_close: 1676 | case h_connection_upgrade: 1677 | if (ch == ',') { 1678 | if (h_state == h_connection_keep_alive) { 1679 | parser->flags |= F_CONNECTION_KEEP_ALIVE; 1680 | } else if (h_state == h_connection_close) { 1681 | parser->flags |= F_CONNECTION_CLOSE; 1682 | } else if (h_state == h_connection_upgrade) { 1683 | parser->flags |= F_CONNECTION_UPGRADE; 1684 | } 1685 | h_state = h_matching_connection_token_start; 1686 | parser->index = 0; 1687 | } else if (ch != ' ') { 1688 | h_state = h_matching_connection_token; 1689 | } 1690 | break; 1691 | 1692 | default: 1693 | UPDATE_STATE(s_header_value); 1694 | h_state = h_general; 1695 | break; 1696 | } 1697 | } 1698 | parser->header_state = h_state; 1699 | 1700 | COUNT_HEADER_SIZE(p - start); 1701 | 1702 | if (p == data + len) 1703 | --p; 1704 | break; 1705 | } 1706 | 1707 | case s_header_almost_done: 1708 | { 1709 | STRICT_CHECK(ch != LF); 1710 | 1711 | UPDATE_STATE(s_header_value_lws); 1712 | break; 1713 | } 1714 | 1715 | case s_header_value_lws: 1716 | { 1717 | if (ch == ' ' || ch == '\t') { 1718 | UPDATE_STATE(s_header_value_start); 1719 | REEXECUTE(); 1720 | } 1721 | 1722 | /* finished the header */ 1723 | switch (parser->header_state) { 1724 | case h_connection_keep_alive: 1725 | parser->flags |= F_CONNECTION_KEEP_ALIVE; 1726 | break; 1727 | case h_connection_close: 1728 | parser->flags |= F_CONNECTION_CLOSE; 1729 | break; 1730 | case h_transfer_encoding_chunked: 1731 | parser->flags |= F_CHUNKED; 1732 | break; 1733 | case h_connection_upgrade: 1734 | parser->flags |= F_CONNECTION_UPGRADE; 1735 | break; 1736 | default: 1737 | break; 1738 | } 1739 | 1740 | UPDATE_STATE(s_header_field_start); 1741 | REEXECUTE(); 1742 | } 1743 | 1744 | case s_header_value_discard_ws_almost_done: 1745 | { 1746 | STRICT_CHECK(ch != LF); 1747 | UPDATE_STATE(s_header_value_discard_lws); 1748 | break; 1749 | } 1750 | 1751 | case s_header_value_discard_lws: 1752 | { 1753 | if (ch == ' ' || ch == '\t') { 1754 | UPDATE_STATE(s_header_value_discard_ws); 1755 | break; 1756 | } else { 1757 | switch (parser->header_state) { 1758 | case h_connection_keep_alive: 1759 | parser->flags |= F_CONNECTION_KEEP_ALIVE; 1760 | break; 1761 | case h_connection_close: 1762 | parser->flags |= F_CONNECTION_CLOSE; 1763 | break; 1764 | case h_connection_upgrade: 1765 | parser->flags |= F_CONNECTION_UPGRADE; 1766 | break; 1767 | case h_transfer_encoding_chunked: 1768 | parser->flags |= F_CHUNKED; 1769 | break; 1770 | default: 1771 | break; 1772 | } 1773 | 1774 | /* header value was empty */ 1775 | MARK(header_value); 1776 | UPDATE_STATE(s_header_field_start); 1777 | CALLBACK_DATA_NOADVANCE(header_value); 1778 | REEXECUTE(); 1779 | } 1780 | } 1781 | 1782 | case s_headers_almost_done: 1783 | { 1784 | STRICT_CHECK(ch != LF); 1785 | 1786 | if (parser->flags & F_TRAILING) { 1787 | /* End of a chunked request */ 1788 | UPDATE_STATE(NEW_MESSAGE()); 1789 | CALLBACK_NOTIFY(message_complete); 1790 | break; 1791 | } 1792 | 1793 | UPDATE_STATE(s_headers_done); 1794 | 1795 | /* Set this here so that on_headers_complete() callbacks can see it */ 1796 | parser->upgrade = 1797 | ((parser->flags & (F_UPGRADE | F_CONNECTION_UPGRADE)) == 1798 | (F_UPGRADE | F_CONNECTION_UPGRADE) || 1799 | parser->method == HTTP_CONNECT); 1800 | 1801 | /* Here we call the headers_complete callback. This is somewhat 1802 | * different than other callbacks because if the user returns 1, we 1803 | * will interpret that as saying that this message has no body. This 1804 | * is needed for the annoying case of recieving a response to a HEAD 1805 | * request. 1806 | * 1807 | * We'd like to use CALLBACK_NOTIFY_NOADVANCE() here but we cannot, so 1808 | * we have to simulate it by handling a change in errno below. 1809 | */ 1810 | if (settings->on_headers_complete) { 1811 | switch (settings->on_headers_complete(parser)) { 1812 | case 0: 1813 | break; 1814 | 1815 | case 1: 1816 | parser->flags |= F_SKIPBODY; 1817 | break; 1818 | 1819 | default: 1820 | SET_ERRNO(HPE_CB_headers_complete); 1821 | RETURN(p - data); /* Error */ 1822 | } 1823 | } 1824 | 1825 | if (HTTP_PARSER_ERRNO(parser) != HPE_OK) { 1826 | RETURN(p - data); 1827 | } 1828 | 1829 | REEXECUTE(); 1830 | } 1831 | 1832 | case s_headers_done: 1833 | { 1834 | STRICT_CHECK(ch != LF); 1835 | 1836 | parser->nread = 0; 1837 | 1838 | /* Exit, the rest of the connect is in a different protocol. */ 1839 | if (parser->upgrade) { 1840 | UPDATE_STATE(NEW_MESSAGE()); 1841 | CALLBACK_NOTIFY(message_complete); 1842 | RETURN((p - data) + 1); 1843 | } 1844 | 1845 | if (parser->flags & F_SKIPBODY) { 1846 | UPDATE_STATE(NEW_MESSAGE()); 1847 | CALLBACK_NOTIFY(message_complete); 1848 | } else if (parser->flags & F_CHUNKED) { 1849 | /* chunked encoding - ignore Content-Length header */ 1850 | UPDATE_STATE(s_chunk_size_start); 1851 | } else { 1852 | if (parser->content_length == 0) { 1853 | /* Content-Length header given but zero: Content-Length: 0\r\n */ 1854 | UPDATE_STATE(NEW_MESSAGE()); 1855 | CALLBACK_NOTIFY(message_complete); 1856 | } else if (parser->content_length != ULLONG_MAX) { 1857 | /* Content-Length header given and non-zero */ 1858 | UPDATE_STATE(s_body_identity); 1859 | } else { 1860 | if (parser->type == HTTP_REQUEST || 1861 | !http_message_needs_eof(parser)) { 1862 | /* Assume content-length 0 - read the next */ 1863 | UPDATE_STATE(NEW_MESSAGE()); 1864 | CALLBACK_NOTIFY(message_complete); 1865 | } else { 1866 | /* Read body until EOF */ 1867 | UPDATE_STATE(s_body_identity_eof); 1868 | } 1869 | } 1870 | } 1871 | 1872 | break; 1873 | } 1874 | 1875 | case s_body_identity: 1876 | { 1877 | uint64_t to_read = MIN(parser->content_length, 1878 | (uint64_t) ((data + len) - p)); 1879 | 1880 | assert(parser->content_length != 0 1881 | && parser->content_length != ULLONG_MAX); 1882 | 1883 | /* The difference between advancing content_length and p is because 1884 | * the latter will automaticaly advance on the next loop iteration. 1885 | * Further, if content_length ends up at 0, we want to see the last 1886 | * byte again for our message complete callback. 1887 | */ 1888 | MARK(body); 1889 | parser->content_length -= to_read; 1890 | p += to_read - 1; 1891 | 1892 | if (parser->content_length == 0) { 1893 | UPDATE_STATE(s_message_done); 1894 | 1895 | /* Mimic CALLBACK_DATA_NOADVANCE() but with one extra byte. 1896 | * 1897 | * The alternative to doing this is to wait for the next byte to 1898 | * trigger the data callback, just as in every other case. The 1899 | * problem with this is that this makes it difficult for the test 1900 | * harness to distinguish between complete-on-EOF and 1901 | * complete-on-length. It's not clear that this distinction is 1902 | * important for applications, but let's keep it for now. 1903 | */ 1904 | CALLBACK_DATA_(body, p - body_mark + 1, p - data); 1905 | REEXECUTE(); 1906 | } 1907 | 1908 | break; 1909 | } 1910 | 1911 | /* read until EOF */ 1912 | case s_body_identity_eof: 1913 | MARK(body); 1914 | p = data + len - 1; 1915 | 1916 | break; 1917 | 1918 | case s_message_done: 1919 | UPDATE_STATE(NEW_MESSAGE()); 1920 | CALLBACK_NOTIFY(message_complete); 1921 | break; 1922 | 1923 | case s_chunk_size_start: 1924 | { 1925 | assert(parser->nread == 1); 1926 | assert(parser->flags & F_CHUNKED); 1927 | 1928 | unhex_val = unhex[(unsigned char)ch]; 1929 | if (UNLIKELY(unhex_val == -1)) { 1930 | SET_ERRNO(HPE_INVALID_CHUNK_SIZE); 1931 | goto error; 1932 | } 1933 | 1934 | parser->content_length = unhex_val; 1935 | UPDATE_STATE(s_chunk_size); 1936 | break; 1937 | } 1938 | 1939 | case s_chunk_size: 1940 | { 1941 | uint64_t t; 1942 | 1943 | assert(parser->flags & F_CHUNKED); 1944 | 1945 | if (ch == CR) { 1946 | UPDATE_STATE(s_chunk_size_almost_done); 1947 | break; 1948 | } 1949 | 1950 | unhex_val = unhex[(unsigned char)ch]; 1951 | 1952 | if (unhex_val == -1) { 1953 | if (ch == ';' || ch == ' ') { 1954 | UPDATE_STATE(s_chunk_parameters); 1955 | break; 1956 | } 1957 | 1958 | SET_ERRNO(HPE_INVALID_CHUNK_SIZE); 1959 | goto error; 1960 | } 1961 | 1962 | t = parser->content_length; 1963 | t *= 16; 1964 | t += unhex_val; 1965 | 1966 | /* Overflow? Test against a conservative limit for simplicity. */ 1967 | if (UNLIKELY((ULLONG_MAX - 16) / 16 < parser->content_length)) { 1968 | SET_ERRNO(HPE_INVALID_CONTENT_LENGTH); 1969 | goto error; 1970 | } 1971 | 1972 | parser->content_length = t; 1973 | break; 1974 | } 1975 | 1976 | case s_chunk_parameters: 1977 | { 1978 | assert(parser->flags & F_CHUNKED); 1979 | /* just ignore this shit. TODO check for overflow */ 1980 | if (ch == CR) { 1981 | UPDATE_STATE(s_chunk_size_almost_done); 1982 | break; 1983 | } 1984 | break; 1985 | } 1986 | 1987 | case s_chunk_size_almost_done: 1988 | { 1989 | assert(parser->flags & F_CHUNKED); 1990 | STRICT_CHECK(ch != LF); 1991 | 1992 | parser->nread = 0; 1993 | 1994 | if (parser->content_length == 0) { 1995 | parser->flags |= F_TRAILING; 1996 | UPDATE_STATE(s_header_field_start); 1997 | } else { 1998 | UPDATE_STATE(s_chunk_data); 1999 | } 2000 | break; 2001 | } 2002 | 2003 | case s_chunk_data: 2004 | { 2005 | uint64_t to_read = MIN(parser->content_length, 2006 | (uint64_t) ((data + len) - p)); 2007 | 2008 | assert(parser->flags & F_CHUNKED); 2009 | assert(parser->content_length != 0 2010 | && parser->content_length != ULLONG_MAX); 2011 | 2012 | /* See the explanation in s_body_identity for why the content 2013 | * length and data pointers are managed this way. 2014 | */ 2015 | MARK(body); 2016 | parser->content_length -= to_read; 2017 | p += to_read - 1; 2018 | 2019 | if (parser->content_length == 0) { 2020 | UPDATE_STATE(s_chunk_data_almost_done); 2021 | } 2022 | 2023 | break; 2024 | } 2025 | 2026 | case s_chunk_data_almost_done: 2027 | assert(parser->flags & F_CHUNKED); 2028 | assert(parser->content_length == 0); 2029 | STRICT_CHECK(ch != CR); 2030 | UPDATE_STATE(s_chunk_data_done); 2031 | CALLBACK_DATA(body); 2032 | break; 2033 | 2034 | case s_chunk_data_done: 2035 | assert(parser->flags & F_CHUNKED); 2036 | STRICT_CHECK(ch != LF); 2037 | parser->nread = 0; 2038 | UPDATE_STATE(s_chunk_size_start); 2039 | break; 2040 | 2041 | default: 2042 | assert(0 && "unhandled state"); 2043 | SET_ERRNO(HPE_INVALID_INTERNAL_STATE); 2044 | goto error; 2045 | } 2046 | } 2047 | 2048 | /* Run callbacks for any marks that we have leftover after we ran our of 2049 | * bytes. There should be at most one of these set, so it's OK to invoke 2050 | * them in series (unset marks will not result in callbacks). 2051 | * 2052 | * We use the NOADVANCE() variety of callbacks here because 'p' has already 2053 | * overflowed 'data' and this allows us to correct for the off-by-one that 2054 | * we'd otherwise have (since CALLBACK_DATA() is meant to be run with a 'p' 2055 | * value that's in-bounds). 2056 | */ 2057 | 2058 | assert(((header_field_mark ? 1 : 0) + 2059 | (header_value_mark ? 1 : 0) + 2060 | (url_mark ? 1 : 0) + 2061 | (body_mark ? 1 : 0) + 2062 | (status_mark ? 1 : 0)) <= 1); 2063 | 2064 | CALLBACK_DATA_NOADVANCE(header_field); 2065 | CALLBACK_DATA_NOADVANCE(header_value); 2066 | CALLBACK_DATA_NOADVANCE(url); 2067 | CALLBACK_DATA_NOADVANCE(body); 2068 | CALLBACK_DATA_NOADVANCE(status); 2069 | 2070 | RETURN(len); 2071 | 2072 | error: 2073 | if (HTTP_PARSER_ERRNO(parser) == HPE_OK) { 2074 | SET_ERRNO(HPE_UNKNOWN); 2075 | } 2076 | 2077 | RETURN(p - data); 2078 | } 2079 | 2080 | 2081 | /* Does the parser need to see an EOF to find the end of the message? */ 2082 | int 2083 | http_message_needs_eof (const http_parser *parser) 2084 | { 2085 | if (parser->type == HTTP_REQUEST) { 2086 | return 0; 2087 | } 2088 | 2089 | /* See RFC 2616 section 4.4 */ 2090 | if (parser->status_code / 100 == 1 || /* 1xx e.g. Continue */ 2091 | parser->status_code == 204 || /* No Content */ 2092 | parser->status_code == 304 || /* Not Modified */ 2093 | parser->flags & F_SKIPBODY) { /* response to a HEAD request */ 2094 | return 0; 2095 | } 2096 | 2097 | if ((parser->flags & F_CHUNKED) || parser->content_length != ULLONG_MAX) { 2098 | return 0; 2099 | } 2100 | 2101 | return 1; 2102 | } 2103 | 2104 | 2105 | int 2106 | http_should_keep_alive (const http_parser *parser) 2107 | { 2108 | if (parser->http_major > 0 && parser->http_minor > 0) { 2109 | /* HTTP/1.1 */ 2110 | if (parser->flags & F_CONNECTION_CLOSE) { 2111 | return 0; 2112 | } 2113 | } else { 2114 | /* HTTP/1.0 or earlier */ 2115 | if (!(parser->flags & F_CONNECTION_KEEP_ALIVE)) { 2116 | return 0; 2117 | } 2118 | } 2119 | 2120 | return !http_message_needs_eof(parser); 2121 | } 2122 | 2123 | 2124 | const char * 2125 | http_method_str (enum http_method m) 2126 | { 2127 | return ELEM_AT(method_strings, m, ""); 2128 | } 2129 | 2130 | 2131 | void 2132 | http_parser_init (http_parser *parser, enum http_parser_type t) 2133 | { 2134 | void *data = parser->data; /* preserve application data */ 2135 | memset(parser, 0, sizeof(*parser)); 2136 | parser->data = data; 2137 | parser->type = t; 2138 | parser->state = (t == HTTP_REQUEST ? s_start_req : (t == HTTP_RESPONSE ? s_start_res : s_start_req_or_res)); 2139 | parser->http_errno = HPE_OK; 2140 | } 2141 | 2142 | void 2143 | http_parser_settings_init(http_parser_settings *settings) 2144 | { 2145 | memset(settings, 0, sizeof(*settings)); 2146 | } 2147 | 2148 | const char * 2149 | http_errno_name(enum http_errno err) { 2150 | assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); 2151 | return http_strerror_tab[err].name; 2152 | } 2153 | 2154 | const char * 2155 | http_errno_description(enum http_errno err) { 2156 | assert(err < (sizeof(http_strerror_tab)/sizeof(http_strerror_tab[0]))); 2157 | return http_strerror_tab[err].description; 2158 | } 2159 | 2160 | static enum http_host_state 2161 | http_parse_host_char(enum http_host_state s, const char ch) { 2162 | switch(s) { 2163 | case s_http_userinfo: 2164 | case s_http_userinfo_start: 2165 | if (ch == '@') { 2166 | return s_http_host_start; 2167 | } 2168 | 2169 | if (IS_USERINFO_CHAR(ch)) { 2170 | return s_http_userinfo; 2171 | } 2172 | break; 2173 | 2174 | case s_http_host_start: 2175 | if (ch == '[') { 2176 | return s_http_host_v6_start; 2177 | } 2178 | 2179 | if (IS_HOST_CHAR(ch)) { 2180 | return s_http_host; 2181 | } 2182 | 2183 | break; 2184 | 2185 | case s_http_host: 2186 | if (IS_HOST_CHAR(ch)) { 2187 | return s_http_host; 2188 | } 2189 | 2190 | /* FALLTHROUGH */ 2191 | case s_http_host_v6_end: 2192 | if (ch == ':') { 2193 | return s_http_host_port_start; 2194 | } 2195 | 2196 | break; 2197 | 2198 | case s_http_host_v6: 2199 | if (ch == ']') { 2200 | return s_http_host_v6_end; 2201 | } 2202 | 2203 | /* FALLTHROUGH */ 2204 | case s_http_host_v6_start: 2205 | if (IS_HEX(ch) || ch == ':' || ch == '.') { 2206 | return s_http_host_v6; 2207 | } 2208 | 2209 | break; 2210 | 2211 | case s_http_host_port: 2212 | case s_http_host_port_start: 2213 | if (IS_NUM(ch)) { 2214 | return s_http_host_port; 2215 | } 2216 | 2217 | break; 2218 | 2219 | default: 2220 | break; 2221 | } 2222 | return s_http_host_dead; 2223 | } 2224 | 2225 | static int 2226 | http_parse_host(const char * buf, struct http_parser_url *u, int found_at) { 2227 | enum http_host_state s; 2228 | 2229 | const char *p; 2230 | size_t buflen = u->field_data[UF_HOST].off + u->field_data[UF_HOST].len; 2231 | 2232 | u->field_data[UF_HOST].len = 0; 2233 | 2234 | s = found_at ? s_http_userinfo_start : s_http_host_start; 2235 | 2236 | for (p = buf + u->field_data[UF_HOST].off; p < buf + buflen; p++) { 2237 | enum http_host_state new_s = http_parse_host_char(s, *p); 2238 | 2239 | if (new_s == s_http_host_dead) { 2240 | return 1; 2241 | } 2242 | 2243 | switch(new_s) { 2244 | case s_http_host: 2245 | if (s != s_http_host) { 2246 | u->field_data[UF_HOST].off = p - buf; 2247 | } 2248 | u->field_data[UF_HOST].len++; 2249 | break; 2250 | 2251 | case s_http_host_v6: 2252 | if (s != s_http_host_v6) { 2253 | u->field_data[UF_HOST].off = p - buf; 2254 | } 2255 | u->field_data[UF_HOST].len++; 2256 | break; 2257 | 2258 | case s_http_host_port: 2259 | if (s != s_http_host_port) { 2260 | u->field_data[UF_PORT].off = p - buf; 2261 | u->field_data[UF_PORT].len = 0; 2262 | u->field_set |= (1 << UF_PORT); 2263 | } 2264 | u->field_data[UF_PORT].len++; 2265 | break; 2266 | 2267 | case s_http_userinfo: 2268 | if (s != s_http_userinfo) { 2269 | u->field_data[UF_USERINFO].off = p - buf ; 2270 | u->field_data[UF_USERINFO].len = 0; 2271 | u->field_set |= (1 << UF_USERINFO); 2272 | } 2273 | u->field_data[UF_USERINFO].len++; 2274 | break; 2275 | 2276 | default: 2277 | break; 2278 | } 2279 | s = new_s; 2280 | } 2281 | 2282 | /* Make sure we don't end somewhere unexpected */ 2283 | switch (s) { 2284 | case s_http_host_start: 2285 | case s_http_host_v6_start: 2286 | case s_http_host_v6: 2287 | case s_http_host_port_start: 2288 | case s_http_userinfo: 2289 | case s_http_userinfo_start: 2290 | return 1; 2291 | default: 2292 | break; 2293 | } 2294 | 2295 | return 0; 2296 | } 2297 | 2298 | int 2299 | http_parser_parse_url(const char *buf, size_t buflen, int is_connect, 2300 | struct http_parser_url *u) 2301 | { 2302 | enum state s; 2303 | const char *p; 2304 | enum http_parser_url_fields uf, old_uf; 2305 | int found_at = 0; 2306 | 2307 | u->port = u->field_set = 0; 2308 | s = is_connect ? s_req_server_start : s_req_spaces_before_url; 2309 | old_uf = UF_MAX; 2310 | 2311 | for (p = buf; p < buf + buflen; p++) { 2312 | s = parse_url_char(s, *p); 2313 | 2314 | /* Figure out the next field that we're operating on */ 2315 | switch (s) { 2316 | case s_dead: 2317 | return 1; 2318 | 2319 | /* Skip delimeters */ 2320 | case s_req_schema_slash: 2321 | case s_req_schema_slash_slash: 2322 | case s_req_server_start: 2323 | case s_req_query_string_start: 2324 | case s_req_fragment_start: 2325 | continue; 2326 | 2327 | case s_req_schema: 2328 | uf = UF_SCHEMA; 2329 | break; 2330 | 2331 | case s_req_server_with_at: 2332 | found_at = 1; 2333 | /* FALLTHRU */ 2334 | 2335 | case s_req_server: 2336 | uf = UF_HOST; 2337 | break; 2338 | 2339 | case s_req_path: 2340 | uf = UF_PATH; 2341 | break; 2342 | 2343 | case s_req_query_string: 2344 | uf = UF_QUERY; 2345 | break; 2346 | 2347 | case s_req_fragment: 2348 | uf = UF_FRAGMENT; 2349 | break; 2350 | 2351 | default: 2352 | assert(!"Unexpected state"); 2353 | return 1; 2354 | } 2355 | 2356 | /* Nothing's changed; soldier on */ 2357 | if (uf == old_uf) { 2358 | u->field_data[uf].len++; 2359 | continue; 2360 | } 2361 | 2362 | u->field_data[uf].off = p - buf; 2363 | u->field_data[uf].len = 1; 2364 | 2365 | u->field_set |= (1 << uf); 2366 | old_uf = uf; 2367 | } 2368 | 2369 | /* host must be present if there is a schema */ 2370 | /* parsing http:///toto will fail */ 2371 | if ((u->field_set & ((1 << UF_SCHEMA) | (1 << UF_HOST))) != 0) { 2372 | if (http_parse_host(buf, u, found_at) != 0) { 2373 | return 1; 2374 | } 2375 | } 2376 | 2377 | /* CONNECT requests can only contain "hostname:port" */ 2378 | if (is_connect && u->field_set != ((1 << UF_HOST)|(1 << UF_PORT))) { 2379 | return 1; 2380 | } 2381 | 2382 | if (u->field_set & (1 << UF_PORT)) { 2383 | /* Don't bother with endp; we've already validated the string */ 2384 | unsigned long v = strtoul(buf + u->field_data[UF_PORT].off, NULL, 10); 2385 | 2386 | /* Ports have a max value of 2^16 */ 2387 | if (v > 0xffff) { 2388 | return 1; 2389 | } 2390 | 2391 | u->port = (uint16_t) v; 2392 | } 2393 | 2394 | return 0; 2395 | } 2396 | 2397 | void 2398 | http_parser_pause(http_parser *parser, int paused) { 2399 | /* Users should only be pausing/unpausing a parser that is not in an error 2400 | * state. In non-debug builds, there's not much that we can do about this 2401 | * other than ignore it. 2402 | */ 2403 | if (HTTP_PARSER_ERRNO(parser) == HPE_OK || 2404 | HTTP_PARSER_ERRNO(parser) == HPE_PAUSED) { 2405 | SET_ERRNO((paused) ? HPE_PAUSED : HPE_OK); 2406 | } else { 2407 | assert(0 && "Attempting to pause parser in error state"); 2408 | } 2409 | } 2410 | 2411 | int 2412 | http_body_is_final(const struct http_parser *parser) { 2413 | return parser->state == s_message_done; 2414 | } 2415 | 2416 | unsigned long 2417 | http_parser_version(void) { 2418 | return HTTP_PARSER_VERSION_MAJOR * 0x10000 | 2419 | HTTP_PARSER_VERSION_MINOR * 0x00100 | 2420 | HTTP_PARSER_VERSION_PATCH * 0x00001; 2421 | } 2422 | 2423 | #endif 2424 | -------------------------------------------------------------------------------- /src/ngx_stream_http_parser.h: -------------------------------------------------------------------------------- 1 | /* Copyright Joyent, Inc. and other Node contributors. All rights reserved. 2 | * 3 | * Permission is hereby granted, free of charge, to any person obtaining a copy 4 | * of this software and associated documentation files (the "Software"), to 5 | * deal in the Software without restriction, including without limitation the 6 | * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or 7 | * sell copies of the Software, and to permit persons to whom the Software is 8 | * furnished to do so, subject to the following conditions: 9 | * 10 | * The above copyright notice and this permission notice shall be included in 11 | * all copies or substantial portions of the Software. 12 | * 13 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 18 | * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS 19 | * IN THE SOFTWARE. 20 | */ 21 | #ifndef http_parser_h 22 | #define http_parser_h 23 | #ifdef __cplusplus 24 | extern "C" { 25 | #endif 26 | 27 | /* Also update SONAME in the Makefile whenever you change these. */ 28 | #define HTTP_PARSER_VERSION_MAJOR 2 29 | #define HTTP_PARSER_VERSION_MINOR 4 30 | #define HTTP_PARSER_VERSION_PATCH 2 31 | 32 | #include 33 | #if defined(_WIN32) && !defined(__MINGW32__) && (!defined(_MSC_VER) || _MSC_VER<1600) 34 | #include 35 | #include 36 | typedef __int8 int8_t; 37 | typedef unsigned __int8 uint8_t; 38 | typedef __int16 int16_t; 39 | typedef unsigned __int16 uint16_t; 40 | typedef __int32 int32_t; 41 | typedef unsigned __int32 uint32_t; 42 | typedef __int64 int64_t; 43 | typedef unsigned __int64 uint64_t; 44 | #else 45 | #include 46 | #endif 47 | 48 | /* Compile with -DHTTP_PARSER_STRICT=0 to make less checks, but run 49 | * faster 50 | */ 51 | #ifndef HTTP_PARSER_STRICT 52 | # define HTTP_PARSER_STRICT 1 53 | #endif 54 | 55 | /* Maximium header size allowed. If the macro is not defined 56 | * before including this header then the default is used. To 57 | * change the maximum header size, define the macro in the build 58 | * environment (e.g. -DHTTP_MAX_HEADER_SIZE=). To remove 59 | * the effective limit on the size of the header, define the macro 60 | * to a very large number (e.g. -DHTTP_MAX_HEADER_SIZE=0x7fffffff) 61 | */ 62 | #ifndef HTTP_MAX_HEADER_SIZE 63 | # define HTTP_MAX_HEADER_SIZE (80*1024) 64 | #endif 65 | 66 | typedef struct http_parser http_parser; 67 | typedef struct http_parser_settings http_parser_settings; 68 | 69 | 70 | /* Callbacks should return non-zero to indicate an error. The parser will 71 | * then halt execution. 72 | * 73 | * The one exception is on_headers_complete. In a HTTP_RESPONSE parser 74 | * returning '1' from on_headers_complete will tell the parser that it 75 | * should not expect a body. This is used when receiving a response to a 76 | * HEAD request which may contain 'Content-Length' or 'Transfer-Encoding: 77 | * chunked' headers that indicate the presence of a body. 78 | * 79 | * http_data_cb does not return data chunks. It will be called arbitrarily 80 | * many times for each string. E.G. you might get 10 callbacks for "on_url" 81 | * each providing just a few characters more data. 82 | */ 83 | typedef int (*http_data_cb) (http_parser*, const char *at, size_t length); 84 | typedef int (*http_cb) (http_parser*); 85 | 86 | 87 | /* Request Methods */ 88 | #define HTTP_METHOD_MAP(XX) \ 89 | XX(0, DELETE, DELETE) \ 90 | XX(1, GET, GET) \ 91 | XX(2, HEAD, HEAD) \ 92 | XX(3, POST, POST) \ 93 | XX(4, PUT, PUT) \ 94 | /* pathological */ \ 95 | XX(5, CONNECT, CONNECT) \ 96 | XX(6, OPTIONS, OPTIONS) \ 97 | XX(7, TRACE, TRACE) \ 98 | /* webdav */ \ 99 | XX(8, COPY, COPY) \ 100 | XX(9, LOCK, LOCK) \ 101 | XX(10, MKCOL, MKCOL) \ 102 | XX(11, MOVE, MOVE) \ 103 | XX(12, PROPFIND, PROPFIND) \ 104 | XX(13, PROPPATCH, PROPPATCH) \ 105 | XX(14, SEARCH, SEARCH) \ 106 | XX(15, UNLOCK, UNLOCK) \ 107 | /* subversion */ \ 108 | XX(16, REPORT, REPORT) \ 109 | XX(17, MKACTIVITY, MKACTIVITY) \ 110 | XX(18, CHECKOUT, CHECKOUT) \ 111 | XX(19, MERGE, MERGE) \ 112 | /* upnp */ \ 113 | XX(20, MSEARCH, M-SEARCH) \ 114 | XX(21, NOTIFY, NOTIFY) \ 115 | XX(22, SUBSCRIBE, SUBSCRIBE) \ 116 | XX(23, UNSUBSCRIBE, UNSUBSCRIBE) \ 117 | /* RFC-5789 */ \ 118 | XX(24, PATCH, PATCH) \ 119 | XX(25, PURGE, PURGE) \ 120 | /* CalDAV */ \ 121 | XX(26, MKCALENDAR, MKCALENDAR) \ 122 | 123 | enum http_method 124 | { 125 | #define XX(num, name, string) HTTP_##name = num, 126 | HTTP_METHOD_MAP(XX) 127 | #undef XX 128 | }; 129 | 130 | 131 | enum http_parser_type { HTTP_REQUEST, HTTP_RESPONSE, HTTP_BOTH }; 132 | 133 | 134 | /* Flag values for http_parser.flags field */ 135 | enum flags 136 | { F_CHUNKED = 1 << 0 137 | , F_CONNECTION_KEEP_ALIVE = 1 << 1 138 | , F_CONNECTION_CLOSE = 1 << 2 139 | , F_CONNECTION_UPGRADE = 1 << 3 140 | , F_TRAILING = 1 << 4 141 | , F_UPGRADE = 1 << 5 142 | , F_SKIPBODY = 1 << 6 143 | }; 144 | 145 | 146 | /* Map for errno-related constants 147 | * 148 | * The provided argument should be a macro that takes 2 arguments. 149 | */ 150 | #define HTTP_ERRNO_MAP(XX) \ 151 | /* No error */ \ 152 | XX(OK, "success") \ 153 | \ 154 | /* Callback-related errors */ \ 155 | XX(CB_message_begin, "the on_message_begin callback failed") \ 156 | XX(CB_url, "the on_url callback failed") \ 157 | XX(CB_header_field, "the on_header_field callback failed") \ 158 | XX(CB_header_value, "the on_header_value callback failed") \ 159 | XX(CB_headers_complete, "the on_headers_complete callback failed") \ 160 | XX(CB_body, "the on_body callback failed") \ 161 | XX(CB_message_complete, "the on_message_complete callback failed") \ 162 | XX(CB_status, "the on_status callback failed") \ 163 | \ 164 | /* Parsing-related errors */ \ 165 | XX(INVALID_EOF_STATE, "stream ended at an unexpected time") \ 166 | XX(HEADER_OVERFLOW, \ 167 | "too many header bytes seen; overflow detected") \ 168 | XX(CLOSED_CONNECTION, \ 169 | "data received after completed connection: close message") \ 170 | XX(INVALID_VERSION, "invalid HTTP version") \ 171 | XX(INVALID_STATUS, "invalid HTTP status code") \ 172 | XX(INVALID_METHOD, "invalid HTTP method") \ 173 | XX(INVALID_URL, "invalid URL") \ 174 | XX(INVALID_HOST, "invalid host") \ 175 | XX(INVALID_PORT, "invalid port") \ 176 | XX(INVALID_PATH, "invalid path") \ 177 | XX(INVALID_QUERY_STRING, "invalid query string") \ 178 | XX(INVALID_FRAGMENT, "invalid fragment") \ 179 | XX(LF_EXPECTED, "LF character expected") \ 180 | XX(INVALID_HEADER_TOKEN, "invalid character in header") \ 181 | XX(INVALID_CONTENT_LENGTH, \ 182 | "invalid character in content-length header") \ 183 | XX(INVALID_CHUNK_SIZE, \ 184 | "invalid character in chunk size header") \ 185 | XX(INVALID_CONSTANT, "invalid constant string") \ 186 | XX(INVALID_INTERNAL_STATE, "encountered unexpected internal state")\ 187 | XX(STRICT, "strict mode assertion failed") \ 188 | XX(PAUSED, "parser is paused") \ 189 | XX(UNKNOWN, "an unknown error occurred") 190 | 191 | 192 | /* Define HPE_* values for each errno value above */ 193 | #define HTTP_ERRNO_GEN(n, s) HPE_##n, 194 | enum http_errno { 195 | HTTP_ERRNO_MAP(HTTP_ERRNO_GEN) 196 | }; 197 | #undef HTTP_ERRNO_GEN 198 | 199 | 200 | /* Get an http_errno value from an http_parser */ 201 | #define HTTP_PARSER_ERRNO(p) ((enum http_errno) (p)->http_errno) 202 | 203 | 204 | struct http_parser { 205 | /** PRIVATE **/ 206 | unsigned int type : 2; /* enum http_parser_type */ 207 | unsigned int flags : 7; /* F_* values from 'flags' enum; semi-public */ 208 | unsigned int state : 7; /* enum state from http_parser.c */ 209 | unsigned int header_state : 8; /* enum header_state from http_parser.c */ 210 | unsigned int index : 8; /* index into current matcher */ 211 | 212 | uint32_t nread; /* # bytes read in various scenarios */ 213 | uint64_t content_length; /* # bytes in body (0 if no Content-Length header) */ 214 | 215 | /** READ-ONLY **/ 216 | unsigned short http_major; 217 | unsigned short http_minor; 218 | unsigned int status_code : 16; /* responses only */ 219 | unsigned int method : 8; /* requests only */ 220 | unsigned int http_errno : 7; 221 | 222 | /* 1 = Upgrade header was present and the parser has exited because of that. 223 | * 0 = No upgrade header present. 224 | * Should be checked when http_parser_execute() returns in addition to 225 | * error checking. 226 | */ 227 | unsigned int upgrade : 1; 228 | 229 | /** PUBLIC **/ 230 | void *data; /* A pointer to get hook to the "connection" or "socket" object */ 231 | }; 232 | 233 | 234 | struct http_parser_settings { 235 | http_cb on_message_begin; 236 | http_data_cb on_url; 237 | http_data_cb on_status; 238 | http_data_cb on_header_field; 239 | http_data_cb on_header_value; 240 | http_cb on_headers_complete; 241 | http_data_cb on_body; 242 | http_cb on_message_complete; 243 | }; 244 | 245 | 246 | enum http_parser_url_fields 247 | { UF_SCHEMA = 0 248 | , UF_HOST = 1 249 | , UF_PORT = 2 250 | , UF_PATH = 3 251 | , UF_QUERY = 4 252 | , UF_FRAGMENT = 5 253 | , UF_USERINFO = 6 254 | , UF_MAX = 7 255 | }; 256 | 257 | 258 | /* Result structure for http_parser_parse_url(). 259 | * 260 | * Callers should index into field_data[] with UF_* values iff field_set 261 | * has the relevant (1 << UF_*) bit set. As a courtesy to clients (and 262 | * because we probably have padding left over), we convert any port to 263 | * a uint16_t. 264 | */ 265 | struct http_parser_url { 266 | uint16_t field_set; /* Bitmask of (1 << UF_*) values */ 267 | uint16_t port; /* Converted UF_PORT string */ 268 | 269 | struct { 270 | uint16_t off; /* Offset into buffer in which field starts */ 271 | uint16_t len; /* Length of run in buffer */ 272 | } field_data[UF_MAX]; 273 | }; 274 | 275 | 276 | /* Returns the library version. Bits 16-23 contain the major version number, 277 | * bits 8-15 the minor version number and bits 0-7 the patch level. 278 | * Usage example: 279 | * 280 | * unsigned long version = http_parser_version(); 281 | * unsigned major = (version >> 16) & 255; 282 | * unsigned minor = (version >> 8) & 255; 283 | * unsigned patch = version & 255; 284 | * printf("http_parser v%u.%u.%u\n", major, minor, patch); 285 | */ 286 | unsigned long http_parser_version(void); 287 | 288 | void http_parser_init(http_parser *parser, enum http_parser_type type); 289 | 290 | 291 | /* Initialize http_parser_settings members to 0 292 | */ 293 | void http_parser_settings_init(http_parser_settings *settings); 294 | 295 | 296 | /* Executes the parser. Returns number of parsed bytes. Sets 297 | * `parser->http_errno` on error. */ 298 | size_t http_parser_execute(http_parser *parser, 299 | const http_parser_settings *settings, 300 | const char *data, 301 | size_t len); 302 | 303 | 304 | /* If http_should_keep_alive() in the on_headers_complete or 305 | * on_message_complete callback returns 0, then this should be 306 | * the last message on the connection. 307 | * If you are the server, respond with the "Connection: close" header. 308 | * If you are the client, close the connection. 309 | */ 310 | int http_should_keep_alive(const http_parser *parser); 311 | 312 | /* Returns a string version of the HTTP method. */ 313 | const char *http_method_str(enum http_method m); 314 | 315 | /* Return a string name of the given error */ 316 | const char *http_errno_name(enum http_errno err); 317 | 318 | /* Return a string description of the given error */ 319 | const char *http_errno_description(enum http_errno err); 320 | 321 | /* Parse a URL; return nonzero on failure */ 322 | int http_parser_parse_url(const char *buf, size_t buflen, 323 | int is_connect, 324 | struct http_parser_url *u); 325 | 326 | /* Pause or un-pause the parser; a nonzero value pauses */ 327 | void http_parser_pause(http_parser *parser, int paused); 328 | 329 | /* Checks if this is the final chunk of the body. */ 330 | int http_body_is_final(const http_parser *parser); 331 | 332 | #ifdef __cplusplus 333 | } 334 | #endif 335 | #endif 336 | -------------------------------------------------------------------------------- /src/ngx_stream_json.c: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 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 | 23 | /* cJSON */ 24 | /* JSON parser in C. */ 25 | 26 | #include 27 | #ifndef NGX_HTTP_UPSYNC 28 | 29 | #include 30 | #include 31 | #include 32 | #include 33 | #include 34 | #include 35 | #include 36 | #include "ngx_stream_json.h" 37 | 38 | static const char *ep; 39 | 40 | const char *cJSON_GetErrorPtr(void) {return ep;} 41 | 42 | static int cJSON_strcasecmp(const char *s1,const char *s2) 43 | { 44 | if (!s1) return (s1==s2)?0:1; 45 | if (!s2) return 1; 46 | for(; tolower(*s1) == tolower(*s2); ++s1, ++s2) if(*s1 == 0) return 0; 47 | return tolower(*(const unsigned char *)s1) - tolower(*(const unsigned char *)s2); 48 | } 49 | 50 | static void *(*cJSON_malloc)(size_t sz) = malloc; 51 | static void (*cJSON_free)(void *ptr) = free; 52 | 53 | static char* cJSON_strdup(const char* str) 54 | { 55 | size_t len; 56 | char* copy; 57 | 58 | len = strlen(str) + 1; 59 | if (!(copy = (char*)cJSON_malloc(len))) return 0; 60 | memcpy(copy,str,len); 61 | return copy; 62 | } 63 | 64 | void cJSON_InitHooks(cJSON_Hooks* hooks) 65 | { 66 | if (!hooks) { /* Reset hooks */ 67 | cJSON_malloc = malloc; 68 | cJSON_free = free; 69 | return; 70 | } 71 | 72 | cJSON_malloc = (hooks->malloc_fn)?hooks->malloc_fn:malloc; 73 | cJSON_free = (hooks->free_fn)?hooks->free_fn:free; 74 | } 75 | 76 | /* Internal constructor. */ 77 | static cJSON *cJSON_New_Item(void) 78 | { 79 | cJSON* node = (cJSON*)cJSON_malloc(sizeof(cJSON)); 80 | if (node) memset(node,0,sizeof(cJSON)); 81 | return node; 82 | } 83 | 84 | /* Delete a cJSON structure. */ 85 | void cJSON_Delete(cJSON *c) 86 | { 87 | cJSON *next; 88 | while (c) 89 | { 90 | next=c->next; 91 | if (!(c->type&cJSON_IsReference) && c->child) cJSON_Delete(c->child); 92 | if (!(c->type&cJSON_IsReference) && c->valuestring) cJSON_free(c->valuestring); 93 | if (c->string) cJSON_free(c->string); 94 | cJSON_free(c); 95 | c=next; 96 | } 97 | } 98 | 99 | /* Parse the input text to generate a number, and populate the result into item. */ 100 | static const char *parse_number(cJSON *item,const char *num) 101 | { 102 | double n=0,sign=1,scale=0;int subscale=0,signsubscale=1; 103 | 104 | /* Could use sscanf for this? */ 105 | if (*num=='-') sign=-1,num++; /* Has sign? */ 106 | if (*num=='0') num++; /* is zero */ 107 | if (*num>='1' && *num<='9') do n=(n*10.0)+(*num++ -'0'); while (*num>='0' && *num<='9'); /* Number? */ 108 | if (*num=='.' && num[1]>='0' && num[1]<='9') {num++; do n=(n*10.0)+(*num++ -'0'),scale--; while (*num>='0' && *num<='9');} /* Fractional part? */ 109 | if (*num=='e' || *num=='E') /* Exponent? */ 110 | { num++;if (*num=='+') num++; else if (*num=='-') signsubscale=-1,num++; /* With sign? */ 111 | while (*num>='0' && *num<='9') subscale=(subscale*10)+(*num++ - '0'); /* Number? */ 112 | } 113 | 114 | n=sign*n*pow(10.0,(scale+subscale*signsubscale)); /* number = +/- number.fraction * 10^+/- exponent */ 115 | 116 | item->valuedouble=n; 117 | item->valueint=(int)n; 118 | item->type=cJSON_Number; 119 | return num; 120 | } 121 | 122 | /* Render the number nicely from the given item into a string. */ 123 | static char *print_number(cJSON *item) 124 | { 125 | char *str; 126 | double d=item->valuedouble; 127 | if (fabs(((double)item->valueint)-d)<=DBL_EPSILON && d<=INT_MAX && d>=INT_MIN) 128 | { 129 | str=(char*)cJSON_malloc(21); /* 2^64+1 can be represented in 21 chars. */ 130 | if (str) sprintf(str,"%d",item->valueint); 131 | } 132 | else 133 | { 134 | str=(char*)cJSON_malloc(64); /* This is a nice tradeoff. */ 135 | if (str) 136 | { 137 | if (fabs(floor(d)-d)<=DBL_EPSILON && fabs(d)<1.0e60)sprintf(str,"%.0f",d); 138 | else if (fabs(d)<1.0e-6 || fabs(d)>1.0e9) sprintf(str,"%e",d); 139 | else sprintf(str,"%f",d); 140 | } 141 | } 142 | return str; 143 | } 144 | 145 | /* Parse the input text into an unescaped cstring, and populate item. */ 146 | static const unsigned char firstByteMark[7] = { 0x00, 0x00, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC }; 147 | static const char *parse_string(cJSON *item,const char *str) 148 | { 149 | const char *ptr=str+1;char *ptr2;char *out;int len=0;unsigned uc,uc2; 150 | if (*str!='\"') {ep=str;return 0;} /* not a string! */ 151 | 152 | while (*ptr!='\"' && *ptr && ++len) if (*ptr++ == '\\') ptr++; /* Skip escaped quotes. */ 153 | 154 | out=(char*)cJSON_malloc(len+1); /* This is how long we need for the string, roughly. */ 155 | if (!out) return 0; 156 | 157 | ptr=str+1;ptr2=out; 158 | while (*ptr!='\"' && *ptr) 159 | { 160 | if (*ptr!='\\') *ptr2++=*ptr++; 161 | else 162 | { 163 | ptr++; 164 | switch (*ptr) 165 | { 166 | case 'b': *ptr2++='\b'; break; 167 | case 'f': *ptr2++='\f'; break; 168 | case 'n': *ptr2++='\n'; break; 169 | case 'r': *ptr2++='\r'; break; 170 | case 't': *ptr2++='\t'; break; 171 | case 'u': /* transcode utf16 to utf8. */ 172 | sscanf(ptr+1,"%4x",&uc);ptr+=4; /* get the unicode char. */ 173 | 174 | if ((uc>=0xDC00 && uc<=0xDFFF) || uc==0) break; /* check for invalid. */ 175 | 176 | if (uc>=0xD800 && uc<=0xDBFF) /* UTF16 surrogate pairs. */ 177 | { 178 | if (ptr[1]!='\\' || ptr[2]!='u') break; /* missing second-half of surrogate. */ 179 | sscanf(ptr+3,"%4x",&uc2);ptr+=6; 180 | if (uc2<0xDC00 || uc2>0xDFFF) break; /* invalid second-half of surrogate. */ 181 | uc=0x10000 + (((uc&0x3FF)<<10) | (uc2&0x3FF)); 182 | } 183 | 184 | len=4;if (uc<0x80) len=1;else if (uc<0x800) len=2;else if (uc<0x10000) len=3; ptr2+=len; 185 | 186 | switch (len) { 187 | case 4: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; /* FALLTHRU */ 188 | case 3: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; /* FALLTHRU */ 189 | case 2: *--ptr2 =((uc | 0x80) & 0xBF); uc >>= 6; /* FALLTHRU */ 190 | case 1: *--ptr2 =(uc | firstByteMark[len]); 191 | } 192 | ptr2+=len; 193 | break; 194 | default: *ptr2++=*ptr; break; 195 | } 196 | ptr++; 197 | } 198 | } 199 | *ptr2=0; 200 | if (*ptr=='\"') ptr++; 201 | item->valuestring=out; 202 | item->type=cJSON_String; 203 | return ptr; 204 | } 205 | 206 | /* Render the cstring provided to an escaped version that can be printed. */ 207 | static char *print_string_ptr(const char *str) 208 | { 209 | const char *ptr;char *ptr2,*out;int len=0;unsigned char token; 210 | 211 | if (!str) return cJSON_strdup(""); 212 | ptr=str;while ((token=*ptr) && ++len) {if (strchr("\"\\\b\f\n\r\t",token)) len++; else if (token<32) len+=5;ptr++;} 213 | 214 | out=(char*)cJSON_malloc(len+3); 215 | if (!out) return 0; 216 | 217 | ptr2=out;ptr=str; 218 | *ptr2++='\"'; 219 | while (*ptr) 220 | { 221 | if ((unsigned char)*ptr>31 && *ptr!='\"' && *ptr!='\\') *ptr2++=*ptr++; 222 | else 223 | { 224 | *ptr2++='\\'; 225 | switch (token=*ptr++) 226 | { 227 | case '\\': *ptr2++='\\'; break; 228 | case '\"': *ptr2++='\"'; break; 229 | case '\b': *ptr2++='b'; break; 230 | case '\f': *ptr2++='f'; break; 231 | case '\n': *ptr2++='n'; break; 232 | case '\r': *ptr2++='r'; break; 233 | case '\t': *ptr2++='t'; break; 234 | default: sprintf(ptr2,"u%04x",token);ptr2+=5; break; /* escape and print */ 235 | } 236 | } 237 | } 238 | *ptr2++='\"';*ptr2++=0; 239 | return out; 240 | } 241 | /* Invote print_string_ptr (which is useful) on an item. */ 242 | static char *print_string(cJSON *item) {return print_string_ptr(item->valuestring);} 243 | 244 | /* Predeclare these prototypes. */ 245 | static const char *parse_value(cJSON *item,const char *value); 246 | static char *print_value(cJSON *item,int depth,int fmt); 247 | static const char *parse_array(cJSON *item,const char *value); 248 | static char *print_array(cJSON *item,int depth,int fmt); 249 | static const char *parse_object(cJSON *item,const char *value); 250 | static char *print_object(cJSON *item,int depth,int fmt); 251 | 252 | /* Utility to jump whitespace and cr/lf */ 253 | static const char *skip(const char *in) {while (in && *in && (unsigned char)*in<=32) in++; return in;} 254 | 255 | /* Parse an object - create a new root, and populate. */ 256 | cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated) 257 | { 258 | const char *end=0; 259 | cJSON *c=cJSON_New_Item(); 260 | ep=0; 261 | if (!c) return 0; /* memory fail */ 262 | 263 | end=parse_value(c,skip(value)); 264 | if (!end) {cJSON_Delete(c);return 0;} /* parse failure. ep is set. */ 265 | 266 | /* if we require null-terminated JSON without appended garbage, skip and then check for a null terminator */ 267 | if (require_null_terminated) {end=skip(end);if (*end) {cJSON_Delete(c);ep=end;return 0;}} 268 | if (return_parse_end) *return_parse_end=end; 269 | return c; 270 | } 271 | /* Default options for cJSON_Parse */ 272 | cJSON *cJSON_Parse(const char *value) {return cJSON_ParseWithOpts(value,0,0);} 273 | 274 | /* Render a cJSON item/entity/structure to text. */ 275 | char *cJSON_Print(cJSON *item) {return print_value(item,0,1);} 276 | char *cJSON_PrintUnformatted(cJSON *item) {return print_value(item,0,0);} 277 | 278 | /* Parser core - when encountering text, process appropriately. */ 279 | static const char *parse_value(cJSON *item,const char *value) 280 | { 281 | if (!value) return 0; /* Fail on null. */ 282 | if (!strncmp(value,"null",4)) { item->type=cJSON_NULL; return value+4; } 283 | if (!strncmp(value,"false",5)) { item->type=cJSON_False; return value+5; } 284 | if (!strncmp(value,"true",4)) { item->type=cJSON_True; item->valueint=1; return value+4; } 285 | if (*value=='\"') { return parse_string(item,value); } 286 | if (*value=='-' || (*value>='0' && *value<='9')) { return parse_number(item,value); } 287 | if (*value=='[') { return parse_array(item,value); } 288 | if (*value=='{') { return parse_object(item,value); } 289 | 290 | ep=value;return 0; /* failure. */ 291 | } 292 | 293 | /* Render a value to text. */ 294 | static char *print_value(cJSON *item,int depth,int fmt) 295 | { 296 | char *out=0; 297 | if (!item) return 0; 298 | switch ((item->type)&255) 299 | { 300 | case cJSON_NULL: out=cJSON_strdup("null"); break; 301 | case cJSON_False: out=cJSON_strdup("false");break; 302 | case cJSON_True: out=cJSON_strdup("true"); break; 303 | case cJSON_Number: out=print_number(item);break; 304 | case cJSON_String: out=print_string(item);break; 305 | case cJSON_Array: out=print_array(item,depth,fmt);break; 306 | case cJSON_Object: out=print_object(item,depth,fmt);break; 307 | } 308 | return out; 309 | } 310 | 311 | /* Build an array from input text. */ 312 | static const char *parse_array(cJSON *item,const char *value) 313 | { 314 | cJSON *child; 315 | if (*value!='[') {ep=value;return 0;} /* not an array! */ 316 | 317 | item->type=cJSON_Array; 318 | value=skip(value+1); 319 | if (*value==']') return value+1; /* empty array. */ 320 | 321 | item->child=child=cJSON_New_Item(); 322 | if (!item->child) return 0; /* memory fail */ 323 | value=skip(parse_value(child,skip(value))); /* skip any spacing, get the value. */ 324 | if (!value) return 0; 325 | 326 | while (*value==',') 327 | { 328 | cJSON *new_item; 329 | if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ 330 | child->next=new_item;new_item->prev=child;child=new_item; 331 | value=skip(parse_value(child,skip(value+1))); 332 | if (!value) return 0; /* memory fail */ 333 | } 334 | 335 | if (*value==']') return value+1; /* end of array */ 336 | ep=value;return 0; /* malformed. */ 337 | } 338 | 339 | /* Render an array to text */ 340 | static char *print_array(cJSON *item,int depth,int fmt) 341 | { 342 | char **entries; 343 | char *out=0,*ptr,*ret;int len=5; 344 | cJSON *child=item->child; 345 | int numentries=0,i=0,fail=0; 346 | 347 | /* How many entries in the array? */ 348 | while (child) numentries++,child=child->next; 349 | /* Explicitly handle numentries==0 */ 350 | if (!numentries) 351 | { 352 | out=(char*)cJSON_malloc(3); 353 | if (out) strcpy(out,"[]"); 354 | return out; 355 | } 356 | /* Allocate an array to hold the values for each */ 357 | entries=(char**)cJSON_malloc(numentries*sizeof(char*)); 358 | if (!entries) return 0; 359 | memset(entries,0,numentries*sizeof(char*)); 360 | /* Retrieve all the results: */ 361 | child=item->child; 362 | while (child && !fail) 363 | { 364 | ret=print_value(child,depth+1,fmt); 365 | entries[i++]=ret; 366 | if (ret) len+=strlen(ret)+2+(fmt?1:0); else fail=1; 367 | child=child->next; 368 | } 369 | 370 | /* If we didn't fail, try to malloc the output string */ 371 | if (!fail) out=(char*)cJSON_malloc(len); 372 | /* If that fails, we fail. */ 373 | if (!out) fail=1; 374 | 375 | /* Handle failure. */ 376 | if (fail) 377 | { 378 | for (i=0;itype=cJSON_Object; 404 | value=skip(value+1); 405 | if (*value=='}') return value+1; /* empty array. */ 406 | 407 | item->child=child=cJSON_New_Item(); 408 | if (!item->child) return 0; 409 | value=skip(parse_string(child,skip(value))); 410 | if (!value) return 0; 411 | child->string=child->valuestring;child->valuestring=0; 412 | if (*value!=':') {ep=value;return 0;} /* fail! */ 413 | value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ 414 | if (!value) return 0; 415 | 416 | while (*value==',') 417 | { 418 | cJSON *new_item; 419 | if (!(new_item=cJSON_New_Item())) return 0; /* memory fail */ 420 | child->next=new_item;new_item->prev=child;child=new_item; 421 | value=skip(parse_string(child,skip(value+1))); 422 | if (!value) return 0; 423 | child->string=child->valuestring;child->valuestring=0; 424 | if (*value!=':') {ep=value;return 0;} /* fail! */ 425 | value=skip(parse_value(child,skip(value+1))); /* skip any spacing, get the value. */ 426 | if (!value) return 0; 427 | } 428 | 429 | if (*value=='}') return value+1; /* end of array */ 430 | ep=value;return 0; /* malformed. */ 431 | } 432 | 433 | /* Render an object to text. */ 434 | static char *print_object(cJSON *item,int depth,int fmt) 435 | { 436 | char **entries=0,**names=0; 437 | char *out=0,*ptr,*ret,*str;int len=7,i=0,j; 438 | cJSON *child=item->child; 439 | int numentries=0,fail=0; 440 | /* Count the number of entries. */ 441 | while (child) numentries++,child=child->next; 442 | /* Explicitly handle empty object case */ 443 | if (!numentries) 444 | { 445 | out=(char*)cJSON_malloc(fmt?depth+3:3); 446 | if (!out) return 0; 447 | ptr=out;*ptr++='{'; 448 | if (fmt) {*ptr++='\n';for (i=0;ichild;depth++;if (fmt) len+=depth; 462 | while (child) 463 | { 464 | names[i]=str=print_string_ptr(child->string); 465 | entries[i++]=ret=print_value(child,depth,fmt); 466 | if (str && ret) len+=strlen(ret)+strlen(str)+2+(fmt?2+depth:0); else fail=1; 467 | child=child->next; 468 | } 469 | 470 | /* Try to allocate the output string */ 471 | if (!fail) out=(char*)cJSON_malloc(len); 472 | if (!out) fail=1; 473 | 474 | /* Handle failure */ 475 | if (fail) 476 | { 477 | for (i=0;ichild;int i=0;while(c)i++,c=c->next;return i;} 504 | cJSON *cJSON_GetArrayItem(cJSON *array,int item) {cJSON *c=array->child; while (c && item>0) item--,c=c->next; return c;} 505 | cJSON *cJSON_GetObjectItem(cJSON *object,const char *string) {cJSON *c=object->child; while (c && cJSON_strcasecmp(c->string,string)) c=c->next; return c;} 506 | 507 | /* Utility for array list handling. */ 508 | static void suffix_object(cJSON *prev,cJSON *item) {prev->next=item;item->prev=prev;} 509 | /* Utility for handling references. */ 510 | static cJSON *create_reference(cJSON *item) {cJSON *ref=cJSON_New_Item();if (!ref) return 0;memcpy(ref,item,sizeof(cJSON));ref->string=0;ref->type|=cJSON_IsReference;ref->next=ref->prev=0;return ref;} 511 | 512 | /* Add item to array/object. */ 513 | void cJSON_AddItemToArray(cJSON *array, cJSON *item) {cJSON *c=array->child;if (!item) return; if (!c) {array->child=item;} else {while (c && c->next) c=c->next; suffix_object(c,item);}} 514 | void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item) {if (!item) return; if (item->string) cJSON_free(item->string);item->string=cJSON_strdup(string);cJSON_AddItemToArray(object,item);} 515 | void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item) {cJSON_AddItemToArray(array,create_reference(item));} 516 | void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item) {cJSON_AddItemToObject(object,string,create_reference(item));} 517 | 518 | cJSON *cJSON_DetachItemFromArray(cJSON *array,int which) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return 0; 519 | if (c->prev) c->prev->next=c->next; 520 | if (c->next) c->next->prev=c->prev; 521 | if (c==array->child) array->child=c->next; 522 | c->prev=c->next=0;return c;} 523 | void cJSON_DeleteItemFromArray(cJSON *array,int which) {cJSON_Delete(cJSON_DetachItemFromArray(array,which));} 524 | cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string) {int i=0;cJSON *c=object->child;while (c && cJSON_strcasecmp(c->string,string)) i++,c=c->next;if (c) return cJSON_DetachItemFromArray(object,i);return 0;} 525 | void cJSON_DeleteItemFromObject(cJSON *object,const char *string) {cJSON_Delete(cJSON_DetachItemFromObject(object,string));} 526 | 527 | /* Replace array/object items with new ones. */ 528 | void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem) {cJSON *c=array->child;while (c && which>0) c=c->next,which--;if (!c) return; 529 | newitem->next=c->next;newitem->prev=c->prev;if (newitem->next) newitem->next->prev=newitem; 530 | if (c==array->child) array->child=newitem; else newitem->prev->next=newitem;c->next=c->prev=0;cJSON_Delete(c);} 531 | void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem){int i=0;cJSON *c=object->child;while(c && cJSON_strcasecmp(c->string,string))i++,c=c->next;if(c){newitem->string=cJSON_strdup(string);cJSON_ReplaceItemInArray(object,i,newitem);}} 532 | 533 | /* Create basic types: */ 534 | cJSON *cJSON_CreateNull(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_NULL;return item;} 535 | cJSON *cJSON_CreateTrue(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_True;return item;} 536 | cJSON *cJSON_CreateFalse(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_False;return item;} 537 | cJSON *cJSON_CreateBool(int b) {cJSON *item=cJSON_New_Item();if(item)item->type=b?cJSON_True:cJSON_False;return item;} 538 | cJSON *cJSON_CreateNumber(double num) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_Number;item->valuedouble=num;item->valueint=(int)num;}return item;} 539 | cJSON *cJSON_CreateString(const char *string) {cJSON *item=cJSON_New_Item();if(item){item->type=cJSON_String;item->valuestring=cJSON_strdup(string);}return item;} 540 | cJSON *cJSON_CreateArray(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Array;return item;} 541 | cJSON *cJSON_CreateObject(void) {cJSON *item=cJSON_New_Item();if(item)item->type=cJSON_Object;return item;} 542 | 543 | /* Create Arrays: */ 544 | cJSON *cJSON_CreateIntArray(int *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} 545 | cJSON *cJSON_CreateFloatArray(float *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} 546 | cJSON *cJSON_CreateDoubleArray(double *numbers,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} 547 | cJSON *cJSON_CreateStringArray(const char **strings,int count) {int i;cJSON *n=0,*p=0,*a=cJSON_CreateArray();for(i=0;a && ichild=n;else suffix_object(p,n);p=n;}return a;} 548 | 549 | /* Duplication */ 550 | cJSON *cJSON_Duplicate(cJSON *item,int recurse) 551 | { 552 | cJSON *newitem,*cptr,*nptr=0,*newchild; 553 | /* Bail on bad ptr */ 554 | if (!item) return 0; 555 | /* Create new item */ 556 | newitem=cJSON_New_Item(); 557 | if (!newitem) return 0; 558 | /* Copy over all vars */ 559 | newitem->type=item->type&(~cJSON_IsReference),newitem->valueint=item->valueint,newitem->valuedouble=item->valuedouble; 560 | if (item->valuestring) {newitem->valuestring=cJSON_strdup(item->valuestring); if (!newitem->valuestring) {cJSON_Delete(newitem);return 0;}} 561 | if (item->string) {newitem->string=cJSON_strdup(item->string); if (!newitem->string) {cJSON_Delete(newitem);return 0;}} 562 | /* If non-recursive, then we're done! */ 563 | if (!recurse) return newitem; 564 | /* Walk the ->next chain for the child. */ 565 | cptr=item->child; 566 | while (cptr) 567 | { 568 | newchild=cJSON_Duplicate(cptr,1); /* Duplicate (with recurse) each item in the ->next chain */ 569 | if (!newchild) {cJSON_Delete(newitem);return 0;} 570 | if (nptr) {nptr->next=newchild,newchild->prev=nptr;nptr=newchild;} /* If newitem->child already set, then crosswire ->prev and ->next and move on */ 571 | else {newitem->child=newchild;nptr=newchild;} /* Set newitem->child and move to it */ 572 | cptr=cptr->next; 573 | } 574 | return newitem; 575 | } 576 | 577 | #endif 578 | -------------------------------------------------------------------------------- /src/ngx_stream_json.h: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2009 Dave Gamble 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy 5 | of this software and associated documentation files (the "Software"), to deal 6 | in the Software without restriction, including without limitation the rights 7 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 8 | copies of the Software, and to permit persons to whom the Software is 9 | furnished to do so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in 12 | all copies or substantial portions of the Software. 13 | 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 | 23 | #ifndef cJSON__h 24 | #define cJSON__h 25 | 26 | #ifdef __cplusplus 27 | extern "C" 28 | { 29 | #endif 30 | 31 | /* cJSON Types: */ 32 | #define cJSON_False 0 33 | #define cJSON_True 1 34 | #define cJSON_NULL 2 35 | #define cJSON_Number 3 36 | #define cJSON_String 4 37 | #define cJSON_Array 5 38 | #define cJSON_Object 6 39 | 40 | #define cJSON_IsReference 256 41 | 42 | /* The cJSON structure: */ 43 | typedef struct cJSON { 44 | struct cJSON *next,*prev; /* next/prev allow you to walk array/object chains. Alternatively, use GetArraySize/GetArrayItem/GetObjectItem */ 45 | struct cJSON *child; /* An array or object item will have a child pointer pointing to a chain of the items in the array/object. */ 46 | 47 | int type; /* The type of the item, as above. */ 48 | 49 | char *valuestring; /* The item's string, if type==cJSON_String */ 50 | int valueint; /* The item's number, if type==cJSON_Number */ 51 | double valuedouble; /* The item's number, if type==cJSON_Number */ 52 | 53 | char *string; /* The item's name string, if this item is the child of, or is in the list of subitems of an object. */ 54 | } cJSON; 55 | 56 | typedef struct cJSON_Hooks { 57 | void *(*malloc_fn)(size_t sz); 58 | void (*free_fn)(void *ptr); 59 | } cJSON_Hooks; 60 | 61 | /* Supply malloc, realloc and free functions to cJSON */ 62 | extern void cJSON_InitHooks(cJSON_Hooks* hooks); 63 | 64 | 65 | /* Supply a block of JSON, and this returns a cJSON object you can interrogate. Call cJSON_Delete when finished. */ 66 | extern cJSON *cJSON_Parse(const char *value); 67 | /* Render a cJSON entity to text for transfer/storage. Free the char* when finished. */ 68 | extern char *cJSON_Print(cJSON *item); 69 | /* Render a cJSON entity to text for transfer/storage without any formatting. Free the char* when finished. */ 70 | extern char *cJSON_PrintUnformatted(cJSON *item); 71 | /* Delete a cJSON entity and all subentities. */ 72 | extern void cJSON_Delete(cJSON *c); 73 | 74 | /* Returns the number of items in an array (or object). */ 75 | extern int cJSON_GetArraySize(cJSON *array); 76 | /* Retrieve item number "item" from array "array". Returns NULL if unsuccessful. */ 77 | extern cJSON *cJSON_GetArrayItem(cJSON *array,int item); 78 | /* Get item "string" from object. Case insensitive. */ 79 | extern cJSON *cJSON_GetObjectItem(cJSON *object,const char *string); 80 | 81 | /* For analysing failed parses. This returns a pointer to the parse error. You'll probably need to look a few chars back to make sense of it. Defined when cJSON_Parse() returns 0. 0 when cJSON_Parse() succeeds. */ 82 | extern const char *cJSON_GetErrorPtr(void); 83 | 84 | /* These calls create a cJSON item of the appropriate type. */ 85 | extern cJSON *cJSON_CreateNull(void); 86 | extern cJSON *cJSON_CreateTrue(void); 87 | extern cJSON *cJSON_CreateFalse(void); 88 | extern cJSON *cJSON_CreateBool(int b); 89 | extern cJSON *cJSON_CreateNumber(double num); 90 | extern cJSON *cJSON_CreateString(const char *string); 91 | extern cJSON *cJSON_CreateArray(void); 92 | extern cJSON *cJSON_CreateObject(void); 93 | 94 | /* These utilities create an Array of count items. */ 95 | extern cJSON *cJSON_CreateIntArray(int *numbers,int count); 96 | extern cJSON *cJSON_CreateFloatArray(float *numbers,int count); 97 | extern cJSON *cJSON_CreateDoubleArray(double *numbers,int count); 98 | extern cJSON *cJSON_CreateStringArray(const char **strings,int count); 99 | 100 | /* Append item to the specified array/object. */ 101 | extern void cJSON_AddItemToArray(cJSON *array, cJSON *item); 102 | extern void cJSON_AddItemToObject(cJSON *object,const char *string,cJSON *item); 103 | /* Append reference to item to the specified array/object. Use this when you want to add an existing cJSON to a new cJSON, but don't want to corrupt your existing cJSON. */ 104 | extern void cJSON_AddItemReferenceToArray(cJSON *array, cJSON *item); 105 | extern void cJSON_AddItemReferenceToObject(cJSON *object,const char *string,cJSON *item); 106 | 107 | /* Remove/Detatch items from Arrays/Objects. */ 108 | extern cJSON *cJSON_DetachItemFromArray(cJSON *array,int which); 109 | extern void cJSON_DeleteItemFromArray(cJSON *array,int which); 110 | extern cJSON *cJSON_DetachItemFromObject(cJSON *object,const char *string); 111 | extern void cJSON_DeleteItemFromObject(cJSON *object,const char *string); 112 | 113 | /* Update array items. */ 114 | extern void cJSON_ReplaceItemInArray(cJSON *array,int which,cJSON *newitem); 115 | extern void cJSON_ReplaceItemInObject(cJSON *object,const char *string,cJSON *newitem); 116 | 117 | /* Duplicate a cJSON item */ 118 | extern cJSON *cJSON_Duplicate(cJSON *item,int recurse); 119 | /* Duplicate will create a new, identical cJSON item to the one you pass, in new memory that will 120 | need to be released. With recurse!=0, it will duplicate any children connected to the item. 121 | The item->next and ->prev pointers are always zero on return from Duplicate. */ 122 | 123 | /* ParseWithOpts allows you to require (and check) that the JSON is null terminated, and to retrieve the pointer to the final byte parsed. */ 124 | extern cJSON *cJSON_ParseWithOpts(const char *value,const char **return_parse_end,int require_null_terminated); 125 | 126 | /* Macros for creating things quickly. */ 127 | #define cJSON_AddNullToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateNull()) 128 | #define cJSON_AddTrueToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateTrue()) 129 | #define cJSON_AddFalseToObject(object,name) cJSON_AddItemToObject(object, name, cJSON_CreateFalse()) 130 | #define cJSON_AddBoolToObject(object,name,b) cJSON_AddItemToObject(object, name, cJSON_CreateBool(b)) 131 | #define cJSON_AddNumberToObject(object,name,n) cJSON_AddItemToObject(object, name, cJSON_CreateNumber(n)) 132 | #define cJSON_AddStringToObject(object,name,s) cJSON_AddItemToObject(object, name, cJSON_CreateString(s)) 133 | 134 | /* When assigning an integer value, it needs to be propagated to valuedouble too. */ 135 | #define cJSON_SetIntValue(object,val) ((object)?(object)->valueint=(object)->valuedouble=(val):(val)) 136 | 137 | #ifdef __cplusplus 138 | } 139 | #endif 140 | 141 | #endif 142 | -------------------------------------------------------------------------------- /src/ngx_stream_upsync_module.h: -------------------------------------------------------------------------------- 1 | #ifndef _NGX_HTTP_UPSYNC_MODELE_H_INCLUDED_ 2 | #define _NGX_HTTP_UPSYNC_MODELE_H_INCLUDED_ 3 | 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | #include "ngx_stream_json.h" 10 | #include "ngx_stream_http_parser.h" 11 | 12 | 13 | #define ngx_strrchr(s1, c) strrchr((const char *) s1, (int) c) 14 | #define ngx_ftruncate(fd, offset) ftruncate(fd, offset) 15 | #define ngx_lseek(fd, offset, whence) lseek(fd, offset, whence) 16 | #define ngx_fgets(fp, offset, whence) fgets(fp, offset, whence) 17 | #define ngx_fopen(path, mode) fopen(path, mode) 18 | #define ngx_fclose(fp) fclose(fp) 19 | 20 | #define ngx_strtoull(nptr, endptr, base) strtoull((const char *) nptr, \ 21 | (char **) endptr, (int) base) 22 | 23 | #define NGX_INDEX_HEADER "X-Consul-Index" 24 | #define NGX_INDEX_HEADER_LEN 14 25 | 26 | #define NGX_INDEX_ETCD_HEADER "X-Etcd-Index" 27 | #define NGX_INDEX_ETCD_HEADER_LEN 12 28 | 29 | #define NGX_MAX_HEADERS 20 30 | #define NGX_MAX_ELEMENT_SIZE 512 31 | 32 | #define NGX_DELAY_DELETE 30 * 60 * 1000 //75 * 1000 33 | 34 | #define NGX_ADD 0 35 | #define NGX_DEL 1 36 | #define NGX_ALL 2 37 | 38 | #define NGX_PAGE_SIZE 4 * 1024 39 | #define NGX_PAGE_NUMBER 1024 40 | 41 | #define NGX_STREAM_RETRY_TIMES 3 42 | #define NGX_STREAM_SOCKET_TIMEOUT 1 43 | 44 | #define NGX_STREAM_LB_DEFAULT 0 45 | #define NGX_STREAM_LB_ROUNDROBIN 1 46 | #define NGX_STREAM_LB_IP_HASH 2 47 | #define NGX_STREAM_LB_LEAST_CONN 4 48 | #define NGX_STREAM_LB_HASH_MODULA 8 49 | #define NGX_STREAM_LB_HASH_KETAMA 16 50 | 51 | 52 | /******************************hash*********************************/ 53 | 54 | extern ngx_module_t ngx_stream_upstream_hash_module; 55 | 56 | 57 | typedef struct { 58 | uint32_t hash; 59 | ngx_str_t *server; 60 | } ngx_stream_upstream_chash_point_t; 61 | 62 | 63 | typedef struct { 64 | ngx_uint_t number; 65 | ngx_stream_upstream_chash_point_t point[1]; 66 | } ngx_stream_upstream_chash_points_t; 67 | 68 | 69 | typedef struct { 70 | ngx_stream_complex_value_t key; 71 | ngx_stream_upstream_chash_points_t *points; 72 | } ngx_stream_upstream_hash_srv_conf_t; 73 | 74 | /****************************hash_end*******************************/ 75 | 76 | 77 | static int ngx_libc_cdecl ngx_stream_upsync_chash_cmp_points(const void *one, 78 | const void *two); 79 | static ngx_int_t ngx_stream_upsync_chash_init(ngx_stream_upstream_srv_conf_t *uscf, 80 | ngx_stream_upstream_rr_peers_t *tmp_peers); 81 | static ngx_int_t ngx_stream_upsync_del_chash_peer( 82 | ngx_stream_upstream_srv_conf_t *uscf); 83 | 84 | 85 | static int ngx_libc_cdecl 86 | ngx_stream_upsync_chash_cmp_points(const void *one, const void *two) 87 | { 88 | ngx_stream_upstream_chash_point_t *first = 89 | (ngx_stream_upstream_chash_point_t *) one; 90 | ngx_stream_upstream_chash_point_t *second = 91 | (ngx_stream_upstream_chash_point_t *) two; 92 | 93 | if (first->hash < second->hash) { 94 | return -1; 95 | 96 | } else if (first->hash > second->hash) { 97 | return 1; 98 | 99 | } else { 100 | return 0; 101 | } 102 | } 103 | 104 | 105 | static ngx_int_t 106 | ngx_stream_upsync_chash_init(ngx_stream_upstream_srv_conf_t *uscf, 107 | ngx_stream_upstream_rr_peers_t *tmp_peers) 108 | { 109 | size_t new_size; 110 | size_t host_len, port_len; 111 | u_char *host, *port, c; 112 | uint32_t hash, base_hash; 113 | ngx_str_t *server; 114 | ngx_uint_t npoints, new_npoints; 115 | ngx_uint_t i, j; 116 | ngx_stream_upstream_rr_peer_t *peer; 117 | ngx_stream_upstream_rr_peers_t *peers; 118 | ngx_stream_upstream_chash_points_t *points; 119 | ngx_stream_upstream_hash_srv_conf_t *hcf; 120 | union { 121 | uint32_t value; 122 | u_char byte[4]; 123 | } prev_hash; 124 | 125 | hcf = ngx_stream_conf_upstream_srv_conf(uscf, ngx_stream_upstream_hash_module); 126 | if(hcf->points == NULL) { 127 | return 0; 128 | } 129 | 130 | peers = uscf->peer.data; 131 | if (tmp_peers != NULL) { 132 | new_npoints = peers->total_weight * 160; 133 | 134 | new_size = sizeof(ngx_stream_upstream_chash_points_t) 135 | + sizeof(ngx_stream_upstream_chash_point_t) * (new_npoints - 1); 136 | 137 | points = ngx_calloc(new_size, ngx_cycle->log); 138 | if (points == NULL ) { 139 | return NGX_ERROR; 140 | } 141 | ngx_free(hcf->points); /* free old points */ 142 | hcf->points = points; 143 | 144 | for (peer = peers->peer; peer; peer = peer->next) { 145 | server = &peer->server; 146 | 147 | /* 148 | * Hash expression is compatible with Cache::Memcached::Fast: 149 | * crc32(HOST \0 PORT PREV_HASH). 150 | */ 151 | 152 | if (server->len >= 5 153 | && ngx_strncasecmp(server->data, (u_char *) "unix:", 5) == 0) 154 | { 155 | host = server->data + 5; 156 | host_len = server->len - 5; 157 | port = NULL; 158 | port_len = 0; 159 | goto done; 160 | } 161 | 162 | for (j = 0; j < server->len; j++) { 163 | c = server->data[server->len - j - 1]; 164 | 165 | if (c == ':') { 166 | host = server->data; 167 | host_len = server->len - j - 1; 168 | port = server->data + server->len - j; 169 | port_len = j; 170 | goto done; 171 | } 172 | 173 | if (c < '0' || c > '9') { 174 | break; 175 | } 176 | } 177 | 178 | host = server->data; 179 | host_len = server->len; 180 | port = NULL; 181 | port_len = 0; 182 | 183 | done: 184 | 185 | ngx_crc32_init(base_hash); 186 | ngx_crc32_update(&base_hash, host, host_len); 187 | ngx_crc32_update(&base_hash, (u_char *) "", 1); 188 | ngx_crc32_update(&base_hash, port, port_len); 189 | 190 | prev_hash.value = 0; 191 | npoints = peer->weight * 160; 192 | 193 | for (j = 0; j < npoints; j++) { 194 | hash = base_hash; 195 | 196 | ngx_crc32_update(&hash, prev_hash.byte, 4); 197 | ngx_crc32_final(hash); 198 | 199 | points->point[points->number].hash = hash; 200 | points->point[points->number].server = server; 201 | points->number++; 202 | 203 | #if (NGX_HAVE_LITTLE_ENDIAN) 204 | prev_hash.value = hash; 205 | #else 206 | prev_hash.byte[0] = (u_char) (hash & 0xff); 207 | prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff); 208 | prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff); 209 | prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff); 210 | #endif 211 | } 212 | } 213 | 214 | } else { 215 | new_npoints = peers->total_weight * 160; 216 | 217 | new_size = sizeof(ngx_stream_upstream_chash_points_t) 218 | + sizeof(ngx_stream_upstream_chash_point_t) * (new_npoints - 1); 219 | 220 | points = ngx_calloc(new_size, ngx_cycle->log); 221 | if (points == NULL ) { 222 | return NGX_ERROR; 223 | } 224 | 225 | ngx_memcpy(points, hcf->points, new_size); 226 | ngx_pfree(ngx_cycle->pool, hcf->points); 227 | 228 | hcf->points = points; 229 | 230 | return NGX_OK; 231 | } 232 | 233 | ngx_qsort(points->point, 234 | points->number, 235 | sizeof(ngx_stream_upstream_chash_point_t), 236 | ngx_stream_upsync_chash_cmp_points); 237 | 238 | for (i = 0, j = 1; j < points->number; j++) { 239 | if (points->point[i].hash != points->point[j].hash) { 240 | points->point[++i] = points->point[j]; 241 | } 242 | } 243 | 244 | points->number = i + 1; 245 | 246 | return NGX_OK; 247 | } 248 | 249 | 250 | static ngx_int_t 251 | ngx_stream_upsync_del_chash_peer(ngx_stream_upstream_srv_conf_t *uscf) 252 | { 253 | size_t host_len, port_len; 254 | u_char *host, *port, c; 255 | uint32_t hash, base_hash; 256 | ngx_str_t *server; 257 | ngx_uint_t npoints, i, j; 258 | ngx_stream_upstream_rr_peer_t *peer; 259 | ngx_stream_upstream_rr_peers_t *peers; 260 | ngx_stream_upstream_chash_points_t *points; 261 | ngx_stream_upstream_hash_srv_conf_t *hcf; 262 | union { 263 | uint32_t value; 264 | u_char byte[4]; 265 | } prev_hash; 266 | 267 | hcf = ngx_stream_conf_upstream_srv_conf(uscf, ngx_stream_upstream_hash_module); 268 | if(hcf->points == NULL) { 269 | return 0; 270 | } 271 | 272 | peers = uscf->peer.data; 273 | 274 | points = hcf->points; 275 | points->number = 0; 276 | 277 | for (peer = peers->peer; peer; peer = peer->next) { 278 | server = &peer->server; 279 | 280 | /* 281 | * Hash expression is compatible with Cache::Memcached::Fast: 282 | * crc32(HOST \0 PORT PREV_HASH). 283 | */ 284 | 285 | if (server->len >= 5 286 | && ngx_strncasecmp(server->data, (u_char *) "unix:", 5) == 0) 287 | { 288 | host = server->data + 5; 289 | host_len = server->len - 5; 290 | port = NULL; 291 | port_len = 0; 292 | goto done; 293 | } 294 | 295 | for (j = 0; j < server->len; j++) { 296 | c = server->data[server->len - j - 1]; 297 | 298 | if (c == ':') { 299 | host = server->data; 300 | host_len = server->len - j - 1; 301 | port = server->data + server->len - j; 302 | port_len = j; 303 | goto done; 304 | } 305 | 306 | if (c < '0' || c > '9') { 307 | break; 308 | } 309 | } 310 | 311 | host = server->data; 312 | host_len = server->len; 313 | port = NULL; 314 | port_len = 0; 315 | 316 | done: 317 | 318 | ngx_crc32_init(base_hash); 319 | ngx_crc32_update(&base_hash, host, host_len); 320 | ngx_crc32_update(&base_hash, (u_char *) "", 1); 321 | ngx_crc32_update(&base_hash, port, port_len); 322 | 323 | prev_hash.value = 0; 324 | npoints = peer->weight * 160; 325 | 326 | for (j = 0; j < npoints; j++) { 327 | hash = base_hash; 328 | 329 | ngx_crc32_update(&hash, prev_hash.byte, 4); 330 | ngx_crc32_final(hash); 331 | 332 | points->point[points->number].hash = hash; 333 | points->point[points->number].server = server; 334 | points->number++; 335 | 336 | #if (NGX_HAVE_LITTLE_ENDIAN) 337 | prev_hash.value = hash; 338 | #else 339 | prev_hash.byte[0] = (u_char) (hash & 0xff); 340 | prev_hash.byte[1] = (u_char) ((hash >> 8) & 0xff); 341 | prev_hash.byte[2] = (u_char) ((hash >> 16) & 0xff); 342 | prev_hash.byte[3] = (u_char) ((hash >> 24) & 0xff); 343 | #endif 344 | } 345 | } 346 | 347 | ngx_qsort(points->point, 348 | points->number, 349 | sizeof(ngx_stream_upstream_chash_point_t), 350 | ngx_stream_upsync_chash_cmp_points); 351 | 352 | for (i = 0, j = 1; j < points->number; j++) { 353 | if (points->point[i].hash != points->point[j].hash) { 354 | points->point[++i] = points->point[j]; 355 | } 356 | } 357 | 358 | points->number = i + 1; 359 | 360 | return NGX_OK; 361 | } 362 | 363 | 364 | #endif //_NGX_HTTP_UPSYNC_MODELE_H_INCLUDED_ 365 | -------------------------------------------------------------------------------- /test/README: -------------------------------------------------------------------------------- 1 | NAME 2 | Test::Nginx - Testing modules for Nginx C module development 3 | 4 | DESCRIPTION 5 | This distribution provides two testing modules for Nginx C module 6 | development: 7 | 8 | * Test::Nginx::LWP 9 | 10 | * Test::Nginx::Socket 11 | 12 | All of them are based on Test::Base. 13 | 14 | Usually, Test::Nginx::Socket is preferred because it works on a much 15 | lower level and not that fault tolerant like Test::Nginx::LWP. 16 | 17 | Also, a lot of connection hang issues (like wrong "r->main->count" value 18 | in nginx 0.8.x) can only be captured by Test::Nginx::Socket because 19 | Perl's LWP::UserAgent client will close the connection itself which will 20 | conceal such issues from the testers. 21 | 22 | Test::Nginx automatically starts an nginx instance (from the "PATH" env) 23 | rooted at t/servroot/ and the default config template makes this nginx 24 | instance listen on the port 1984 by default. One can specify a different 25 | port number by setting his port number to the "TEST_NGINX_PORT" 26 | environment, as in 27 | 28 | export TEST_NGINX_PORT=1989 29 | 30 | etcproxy integration 31 | The default settings in etcproxy 32 | (https://github.com/chaoslawful/etcproxy) makes this small TCP proxy 33 | split the TCP packets into bytes and introduce 1 ms latency among them. 34 | 35 | There's usually various TCP chains that we can put etcproxy into, for 36 | example 37 | 38 | Test::Nginx <=> nginx 39 | $ ./etcproxy 1234 1984 40 | 41 | Here we tell etcproxy to listen on port 1234 and to delegate all the TCP 42 | traffic to the port 1984, the default port that Test::Nginx makes nginx 43 | listen to. 44 | 45 | And then we tell Test::Nginx to test against the port 1234, where 46 | etcproxy listens on, rather than the port 1984 that nginx directly 47 | listens on: 48 | 49 | $ TEST_NGINX_CLIENT_PORT=1234 prove -r t/ 50 | 51 | Then the TCP chain now looks like this: 52 | 53 | Test::Nginx <=> etcproxy (1234) <=> nginx (1984) 54 | 55 | So etcproxy can effectively emulate extreme network conditions and 56 | exercise "unusual" code paths in your nginx server by your tests. 57 | 58 | In practice, *tons* of weird bugs can be captured by this setting. Even 59 | ourselves didn't expect that this simple approach is so effective. 60 | 61 | nginx <=> memcached 62 | We first start the memcached server daemon on port 11211: 63 | 64 | memcached -p 11211 -vv 65 | 66 | and then we another etcproxy instance to listen on port 11984 like this 67 | 68 | $ ./etcproxy 11984 11211 69 | 70 | Then we tell our t/foo.t test script to connect to 11984 rather than 71 | 11211: 72 | 73 | # foo.t 74 | use Test::Nginx::Socket; 75 | repeat_each(1); 76 | plan tests => 2 * repeat_each() * blocks(); 77 | $ENV{TEST_NGINX_MEMCACHED_PORT} ||= 11211; # make this env take a default value 78 | run_tests(); 79 | 80 | __DATA__ 81 | 82 | === TEST 1: sanity 83 | --- config 84 | location /foo { 85 | set $memc_cmd set; 86 | set $memc_key foo; 87 | set $memc_value bar; 88 | memc_pass 127.0.0.1:$TEST_NGINX_MEMCACHED_PORT; 89 | } 90 | --- request 91 | GET /foo 92 | --- response_body_like: STORED 93 | 94 | The Test::Nginx library will automatically expand the special macro 95 | $TEST_NGINX_MEMCACHED_PORT to the environment with the same name. You 96 | can define your own $TEST_NGINX_BLAH_BLAH_PORT macros as long as its 97 | prefix is "TEST_NGINX_" and all in upper case letters. 98 | 99 | And now we can run your test script against the etcproxy port 11984: 100 | 101 | TEST_NGINX_MEMCACHED_PORT=11984 prove t/foo.t 102 | 103 | Then the TCP chains look like this: 104 | 105 | Test::Nginx <=> nginx (1984) <=> etcproxy (11984) <=> memcached (11211) 106 | 107 | If "TEST_NGINX_MEMCACHED_PORT" is not set, then it will take the default 108 | value 11211, which is what we want when there's no etcproxy configured: 109 | 110 | Test::Nginx <=> nginx (1984) <=> memcached (11211) 111 | 112 | This approach also works for proxied mysql and postgres traffic. Please 113 | see the live test suite of ngx_drizzle and ngx_postgres for more 114 | details. 115 | 116 | Usually we set both "TEST_NGINX_CLIENT_PORT" and 117 | "TEST_NGINX_MEMCACHED_PORT" (and etc) at the same time, effectively 118 | yielding the following chain: 119 | 120 | Test::Nginx <=> etcproxy (1234) <=> nginx (1984) <=> etcproxy (11984) <=> memcached (11211) 121 | 122 | as long as you run two separate etcproxy instances in two separate 123 | terminals. 124 | 125 | It's easy to verify if the traffic actually goes through your etcproxy 126 | server. Just check if the terminal running etcproxy emits outputs. By 127 | default, etcproxy always dump out the incoming and outgoing data to 128 | stdout/stderr. 129 | 130 | valgrind integration 131 | Test::Nginx has integrated support for valgrind () 132 | even though by default it does not bother running it with the tests 133 | because valgrind will significantly slow down the test sutie. 134 | 135 | First ensure that your valgrind executable visible in your PATH env. And 136 | then run your test suite with the "TEST_NGINX_USE_VALGRIND" env set to 137 | true: 138 | 139 | TEST_NGINX_USE_VALGRIND=1 prove -r t 140 | 141 | If you see false alarms, you do have a chance to skip them by defining a 142 | ./valgrind.suppress file at the root of your module source tree, as in 143 | 144 | 146 | 147 | This is the suppression file for ngx_drizzle. Test::Nginx will 148 | automatically use it to start nginx with valgrind memcheck if this file 149 | does exist at the expected location. 150 | 151 | If you do see a lot of "Connection refused" errors while running the 152 | tests this way, then you probably have a slow machine (or a very busy 153 | one) that the default waiting time is not sufficient for valgrind to 154 | start. You can define the sleep time to a larger value by setting the 155 | "TEST_NGINX_SLEEP" env: 156 | 157 | TEST_NGINX_SLEEP=1 prove -r t 158 | 159 | The time unit used here is "second". The default sleep setting just fits 160 | my ThinkPad ("Core2Duo T9600"). 161 | 162 | Applying the no-pool patch to your nginx core is recommended while 163 | running nginx with valgrind: 164 | 165 | 166 | 167 | The nginx memory pool can prevent valgrind from spotting lots of invalid 168 | memory reads/writes as well as certain double-free errors. We did find a 169 | lot more memory issues in many of our modules when we first introduced 170 | the no-pool patch in practice ;) 171 | 172 | There's also more advanced features in Test::Nginx that have never 173 | documented. I'd like to write more about them in the near future ;) 174 | 175 | Nginx C modules that use Test::Nginx to drive their test suites 176 | ngx_echo 177 | 178 | 179 | ngx_headers_more 180 | 181 | 182 | ngx_chunkin 183 | 184 | 185 | ngx_memc 186 | 187 | 188 | ngx_drizzle 189 | 190 | 191 | ngx_rds_json 192 | 193 | 194 | ngx_rds_csv 195 | 196 | 197 | ngx_xss 198 | 199 | 200 | ngx_srcache 201 | 202 | 203 | ngx_lua 204 | 205 | 206 | ngx_set_misc 207 | 208 | 209 | ngx_array_var 210 | 211 | 212 | ngx_form_input 213 | 214 | 215 | ngx_iconv 216 | 217 | 218 | ngx_set_cconv 219 | 220 | 221 | ngx_postgres 222 | 223 | 224 | ngx_coolkit 225 | 226 | 227 | Naxsi 228 | 229 | 230 | SOURCE REPOSITORY 231 | This module has a Git repository on Github, which has access for all. 232 | 233 | http://github.com/agentzh/test-nginx 234 | 235 | If you want a commit bit, feel free to drop me a line. 236 | 237 | AUTHORS 238 | agentzh (章亦春) "" 239 | 240 | Antoine BONAVITA "" 241 | 242 | COPYRIGHT & LICENSE 243 | Copyright (c) 2009-2012, agentzh "". 244 | 245 | Copyright (c) 2011-2012, Antoine Bonavita 246 | "". 247 | 248 | This module is licensed under the terms of the BSD license. 249 | 250 | Redistribution and use in source and binary forms, with or without 251 | modification, are permitted provided that the following conditions are 252 | met: 253 | 254 | * Redistributions of source code must retain the above copyright 255 | notice, this list of conditions and the following disclaimer. 256 | 257 | * Redistributions in binary form must reproduce the above copyright 258 | notice, this list of conditions and the following disclaimer in the 259 | documentation and/or other materials provided with the distribution. 260 | 261 | * Neither the name of the authors nor the names of its contributors 262 | may be used to endorse or promote products derived from this 263 | software without specific prior written permission. 264 | 265 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 266 | IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 267 | TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 268 | PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 269 | HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 270 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 271 | TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 272 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 273 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 274 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 275 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 276 | 277 | SEE ALSO 278 | Test::Nginx::LWP, Test::Nginx::Socket, Test::Base. 279 | 280 | -------------------------------------------------------------------------------- /test/conf-server.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | /usr/local/bin/consul agent -server -bootstrap -data-dir=/tmp/consul 4 | 5 | #/usr/local/bin/etcd --peers 127.0.0.1:8500 6 | -------------------------------------------------------------------------------- /test/t/lib/Test/Nginx.pm: -------------------------------------------------------------------------------- 1 | package Test::Nginx; 2 | 3 | # (C) Maxim Dounin 4 | 5 | # Generic module for nginx tests. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use base qw/ Exporter /; 13 | 14 | our @EXPORT = qw/ log_in log_out http http_get http_head /; 15 | our @EXPORT_OK = qw/ http_gzip_request http_gzip_like http_start http_end /; 16 | our %EXPORT_TAGS = ( 17 | gzip => [ qw/ http_gzip_request http_gzip_like / ] 18 | ); 19 | 20 | ############################################################################### 21 | 22 | use File::Path qw/ rmtree /; 23 | use File::Temp qw/ tempdir /; 24 | use IO::Socket; 25 | use POSIX qw/ waitpid WNOHANG /; 26 | use Socket qw/ CRLF /; 27 | use Test::More qw//; 28 | 29 | ############################################################################### 30 | 31 | our $NGINX = defined $ENV{TEST_NGINX_BINARY} ? $ENV{TEST_NGINX_BINARY} 32 | : '../nginx/objs/nginx'; 33 | 34 | sub new { 35 | my $self = {}; 36 | bless $self; 37 | 38 | $self->{_pid} = $$; 39 | 40 | $self->{_testdir} = tempdir( 41 | 'nginx-test-XXXXXXXXXX', 42 | TMPDIR => 1, 43 | CLEANUP => not $ENV{TEST_NGINX_LEAVE} 44 | ) 45 | or die "Can't create temp directory: $!\n"; 46 | $self->{_testdir} =~ s!\\!/!g if $^O eq 'MSWin32'; 47 | $self->{_dso_module} = (); 48 | mkdir "$self->{_testdir}/logs" 49 | or die "Can't create logs directory: $!\n"; 50 | 51 | return $self; 52 | } 53 | 54 | sub DESTROY { 55 | my ($self) = @_; 56 | local $?; 57 | 58 | return if $self->{_pid} != $$; 59 | $self->stop(); 60 | $self->stop_daemons(); 61 | if ($ENV{TEST_NGINX_CATLOG}) { 62 | system("cat $self->{_testdir}/error.log"); 63 | } 64 | if (not $ENV{TEST_NGINX_LEAVE}) { 65 | eval { rmtree($self->{_testdir}); }; 66 | } 67 | } 68 | 69 | sub has($;) { 70 | my ($self, @features) = @_; 71 | 72 | foreach my $feature (@features) { 73 | Test::More::plan(skip_all => "$feature not compiled in") 74 | unless $self->has_module($feature); 75 | } 76 | 77 | return $self; 78 | } 79 | 80 | sub set_dso($;) { 81 | my ($self, $module_name, $module_path) = @_; 82 | 83 | $self->{_dso_module}{$module_name} = $module_path; 84 | } 85 | 86 | sub has_module($) { 87 | my ($self, $feature) = @_; 88 | 89 | my %regex = ( 90 | sni => 'TLS SNI support enabled', 91 | mail => '--with-mail(?!\S)', 92 | flv => '--with-http_flv_module', 93 | perl => '--with-http_perl_module', 94 | auth_request 95 | => '--with-http_auth_request_module', 96 | charset => '(?s)^(?!.*--without-http_charset_module)', 97 | gzip => '(?s)^(?!.*--without-http_gzip_module)', 98 | ssi => '(?s)^(?!.*--without-http_ssi_module)', 99 | userid => '(?s)^(?!.*--without-http_userid_module)', 100 | access => '(?s)^(?!.*--without-http_access_module)', 101 | auth_basic 102 | => '(?s)^(?!.*--without-http_auth_basic_module)', 103 | autoindex 104 | => '(?s)^(?!.*--without-http_autoindex_module)', 105 | geo => '(?s)^(?!.*--without-http_geo_module)', 106 | map => '(?s)^(?!.*--without-http_map_module)', 107 | referer => '(?s)^(?!.*--without-http_referer_module)', 108 | rewrite => '(?s)^(?!.*--without-http_rewrite_module)', 109 | proxy => '(?s)^(?!.*--without-http_proxy_module)', 110 | fastcgi => '(?s)^(?!.*--without-http_fastcgi_module)', 111 | uwsgi => '(?s)^(?!.*--without-http_uwsgi_module)', 112 | scgi => '(?s)^(?!.*--without-http_scgi_module)', 113 | memcached 114 | => '(?s)^(?!.*--without-http_memcached_module)', 115 | limit_conn 116 | => '(?s)^(?!.*--without-http_limit_conn_module)', 117 | limit_req 118 | => '(?s)^(?!.*--without-http_limit_req_module)', 119 | empty_gif 120 | => '(?s)^(?!.*--without-http_empty_gif_module)', 121 | browser => '(?s)^(?!.*--without-http_browser_module)', 122 | upstream_hash 123 | => '(?s)^(?!.*--without-http_upstream_hash_module)', 124 | upstream_ip_hash 125 | => '(?s)^(?!.*--without-http_upstream_ip_hash_module)', 126 | reqstat 127 | => '(?s)^(?!.*--without-http_reqstat_module)', 128 | upstream_least_conn 129 | => '(?s)^(?!.*--without-http_upstream_least_conn_mod)', 130 | upstream_keepalive 131 | => '(?s)^(?!.*--without-http_upstream_keepalive_modu)', 132 | http => '(?s)^(?!.*--without-http(?!\S))', 133 | cache => '(?s)^(?!.*--without-http-cache)', 134 | pop3 => '(?s)^(?!.*--without-mail_pop3_module)', 135 | imap => '(?s)^(?!.*--without-mail_imap_module)', 136 | smtp => '(?s)^(?!.*--without-mail_smtp_module)', 137 | pcre => '(?s)^(?!.*--without-pcre)', 138 | split_clients 139 | => '(?s)^(?!.*--without-http_split_clients_module)', 140 | ); 141 | 142 | my $re = $regex{$feature}; 143 | $re = $feature if !defined $re; 144 | 145 | $self->{_configure_args} = `$NGINX -V 2>&1` 146 | if !defined $self->{_configure_args}; 147 | 148 | return ($self->{_configure_args} =~ $re or $self->{_configure_args} =~ '--enable-mods-static=all') ? 1 : 0; 149 | } 150 | 151 | sub has_version($) { 152 | my ($self, $need) = @_; 153 | 154 | $self->{_configure_args} = `$NGINX -V 2>&1` 155 | if !defined $self->{_configure_args}; 156 | 157 | $self->{_configure_args} =~ m!nginx/([0-9.]+)!; 158 | 159 | my @v = split(/\./, $1); 160 | my ($n, $v); 161 | 162 | for $n (split(/\./, $need)) { 163 | $v = shift @v || 0; 164 | return 0 if $n > $v; 165 | return 1 if $v > $n; 166 | } 167 | 168 | return 1; 169 | } 170 | 171 | sub has_daemon($) { 172 | my ($self, $daemon) = @_; 173 | 174 | if ($^O eq 'MSWin32') { 175 | Test::More::plan(skip_all => "win32"); 176 | return $self; 177 | } 178 | 179 | if ($^O eq 'solaris') { 180 | Test::More::plan(skip_all => "$daemon not found") 181 | unless `command -v $daemon`; 182 | return $self; 183 | } 184 | 185 | Test::More::plan(skip_all => "$daemon not found") 186 | unless `which $daemon`; 187 | 188 | return $self; 189 | } 190 | 191 | sub try_run($$) { 192 | my ($self, $message) = @_; 193 | 194 | $self->run(); 195 | 196 | eval { 197 | open OLDERR, ">&", \*STDERR; close STDERR; 198 | $self->run(); 199 | open STDERR, ">&", \*OLDERR; 200 | }; 201 | 202 | Test::More::plan(skip_all => $message) if $@; 203 | return $self; 204 | } 205 | 206 | sub plan($) { 207 | my ($self, $plan) = @_; 208 | 209 | Test::More::plan(tests => $plan); 210 | 211 | return $self; 212 | } 213 | 214 | sub run(;$) { 215 | my ($self, $conf) = @_; 216 | 217 | my $testdir = $self->{_testdir}; 218 | 219 | if (defined $conf) { 220 | my $c = `cat $conf`; 221 | $self->write_file_expand('nginx.conf', $c); 222 | } 223 | 224 | my $pid = fork(); 225 | die "Unable to fork(): $!\n" unless defined $pid; 226 | 227 | if ($pid == 0) { 228 | my @globals = $self->{_test_globals} ? 229 | () : ('-g', "pid $testdir/nginx.pid; " 230 | . "error_log $testdir/error.log debug;"); 231 | exec($NGINX, '-p', $testdir, '-c', 'nginx.conf', @globals), 232 | or die "Unable to exec(): $!\n"; 233 | } 234 | 235 | # wait for nginx to start 236 | 237 | $self->waitforfile("$testdir/nginx.pid", $pid) 238 | or die "Can't start nginx"; 239 | 240 | $self->{_started} = 1; 241 | return $self; 242 | } 243 | 244 | sub waitforfile($;$) { 245 | my ($self, $file, $pid) = @_; 246 | my $exited; 247 | 248 | # wait for file to appear 249 | # or specified process to exit 250 | 251 | for (1 .. 30) { 252 | return 1 if -e $file; 253 | return 0 if $exited; 254 | $exited = waitpid($pid, WNOHANG) != 0 if $pid; 255 | select undef, undef, undef, 0.1; 256 | } 257 | 258 | return undef; 259 | } 260 | 261 | sub waitforsocket($) { 262 | my ($self, $peer) = @_; 263 | 264 | # wait for socket to accept connections 265 | 266 | for (1 .. 30) { 267 | my $s = IO::Socket::INET->new( 268 | Proto => 'tcp', 269 | PeerAddr => $peer 270 | ); 271 | 272 | return 1 if defined $s; 273 | 274 | select undef, undef, undef, 0.1; 275 | } 276 | 277 | return undef; 278 | } 279 | 280 | sub reload() { 281 | my ($self) = @_; 282 | 283 | return $self unless $self->{_started}; 284 | 285 | local $/; 286 | open F, '<' . $self->{_testdir} . '/nginx.pid' 287 | or die "Can't open nginx.pid: $!"; 288 | my $pid = ; 289 | close F; 290 | 291 | if ($^O eq 'MSWin32') { 292 | my $testdir = $self->{_testdir}; 293 | my @globals = $self->{_test_globals} ? 294 | () : ('-g', "pid $testdir/nginx.pid; " 295 | . "error_log $testdir/error.log debug;"); 296 | system($NGINX, '-c', "$testdir/nginx.conf", '-s', 'reload', 297 | @globals) == 0 298 | or die "system() failed: $?\n"; 299 | 300 | } else { 301 | kill 'HUP', $pid; 302 | } 303 | 304 | sleep(1); 305 | 306 | return $self; 307 | } 308 | 309 | sub stop() { 310 | my ($self) = @_; 311 | 312 | return $self unless $self->{_started}; 313 | 314 | local $/; 315 | open F, '<' . $self->{_testdir} . '/nginx.pid' 316 | or die "Can't open nginx.pid: $!"; 317 | my $pid = ; 318 | close F; 319 | 320 | if ($^O eq 'MSWin32') { 321 | my $testdir = $self->{_testdir}; 322 | my @globals = $self->{_test_globals} ? 323 | () : ('-g', "pid $testdir/nginx.pid; " 324 | . "error_log $testdir/error.log debug;"); 325 | system($NGINX, '-p', $testdir, '-c', "nginx.conf", 326 | '-s', 'stop', @globals) == 0 327 | or die "system() failed: $?\n"; 328 | 329 | } else { 330 | kill 'QUIT', $pid; 331 | } 332 | 333 | waitpid($pid, 0); 334 | 335 | $self->{_started} = 0; 336 | 337 | return $self; 338 | } 339 | 340 | sub stop_daemons() { 341 | my ($self) = @_; 342 | 343 | while ($self->{_daemons} && scalar @{$self->{_daemons}}) { 344 | my $p = shift @{$self->{_daemons}}; 345 | kill $^O eq 'MSWin32' ? 9 : 'TERM', $p; 346 | waitpid($p, 0); 347 | } 348 | 349 | return $self; 350 | } 351 | 352 | sub read_file($) { 353 | my ($self, $name) = @_; 354 | local $/; 355 | 356 | open F, '<', $self->{_testdir} . '/' . $name or die "Can't open $name: $!"; 357 | my $content = ; 358 | close F; 359 | 360 | return $content; 361 | } 362 | 363 | sub write_file($$) { 364 | my ($self, $name, $content) = @_; 365 | 366 | open F, '>' . $self->{_testdir} . '/' . $name 367 | or die "Can't create $name: $!"; 368 | print F $content; 369 | close F; 370 | 371 | return $self; 372 | } 373 | 374 | sub write_file_expand($$) { 375 | my ($self, $name, $content) = @_; 376 | 377 | $content =~ s/%%TEST_GLOBALS%%/$self->test_globals()/gmse; 378 | $content =~ s/%%TEST_GLOBALS_DSO%%/$self->test_globals_dso()/gmse; 379 | $content =~ s/%%TEST_GLOBALS_HTTP%%/$self->test_globals_http()/gmse; 380 | $content =~ s/%%TESTDIR%%/$self->{_testdir}/gms; 381 | 382 | return $self->write_file($name, $content); 383 | } 384 | 385 | sub run_daemon($;@) { 386 | my ($self, $code, @args) = @_; 387 | 388 | my $pid = fork(); 389 | die "Can't fork daemon: $!\n" unless defined $pid; 390 | 391 | if ($pid == 0) { 392 | if (ref($code) eq 'CODE') { 393 | $code->(@args); 394 | exit 0; 395 | } else { 396 | exec($code, @args); 397 | exit 0; 398 | } 399 | } 400 | 401 | $self->{_daemons} = [] unless defined $self->{_daemons}; 402 | push @{$self->{_daemons}}, $pid; 403 | 404 | return $self; 405 | } 406 | 407 | sub testdir() { 408 | my ($self) = @_; 409 | return $self->{_testdir}; 410 | } 411 | 412 | sub test_globals() { 413 | my ($self) = @_; 414 | 415 | return $self->{_test_globals} 416 | if defined $self->{_test_globals}; 417 | 418 | my $s = ''; 419 | 420 | $s .= "pid $self->{_testdir}/nginx.pid;\n"; 421 | $s .= "error_log $self->{_testdir}/error.log debug;\n"; 422 | 423 | $s .= $ENV{TEST_NGINX_GLOBALS} 424 | if $ENV{TEST_NGINX_GLOBALS}; 425 | $self->{_test_globals} = $s; 426 | } 427 | 428 | sub test_globals_dso() { 429 | my ($self) = @_; 430 | 431 | return "" unless defined $ENV{TEST_NGINX_DSO}; 432 | 433 | return $self->{_test_globals_dso} if defined $self->{_test_globals_dso}; 434 | 435 | my $s = ''; 436 | 437 | $s .= "dso {\n"; 438 | if (defined $ENV{NGINX_DSO_PATH}) { 439 | $s .= "path $ENV{NGINX_DSO_PATH};\n"; 440 | } 441 | 442 | while ( my ($key, $value) = each(%{$self->{_dso_module}}) ) { 443 | $s .= "load $key $value;\n"; 444 | } 445 | $s .= "}\n"; 446 | 447 | $self->{_test_globals_dso} = $s; 448 | } 449 | 450 | sub test_globals_http() { 451 | my ($self) = @_; 452 | 453 | return $self->{_test_globals_http} 454 | if defined $self->{_test_globals_http}; 455 | 456 | my $s = ''; 457 | 458 | $s .= "root $self->{_testdir};\n"; 459 | $s .= "access_log $self->{_testdir}/access.log;\n"; 460 | $s .= "client_body_temp_path $self->{_testdir}/client_body_temp;\n"; 461 | 462 | $s .= "fastcgi_temp_path $self->{_testdir}/fastcgi_temp;\n" 463 | if $self->has_module('fastcgi'); 464 | 465 | $s .= "proxy_temp_path $self->{_testdir}/proxy_temp;\n" 466 | if $self->has_module('proxy'); 467 | 468 | $s .= "uwsgi_temp_path $self->{_testdir}/uwsgi_temp;\n" 469 | if $self->has_module('uwsgi'); 470 | 471 | $s .= "scgi_temp_path $self->{_testdir}/scgi_temp;\n" 472 | if $self->has_module('scgi'); 473 | 474 | $s .= $ENV{TEST_NGINX_GLOBALS_HTTP} 475 | if $ENV{TEST_NGINX_GLOBALS_HTTP}; 476 | 477 | $self->{_test_globals_http} = $s; 478 | } 479 | 480 | ############################################################################### 481 | 482 | sub log_core { 483 | return unless $ENV{TEST_NGINX_VERBOSE}; 484 | my ($prefix, $msg) = @_; 485 | ($prefix, $msg) = ('', $prefix) unless defined $msg; 486 | $prefix .= ' ' if length($prefix) > 0; 487 | 488 | if (length($msg) > 2048) { 489 | $msg = substr($msg, 0, 2048) 490 | . "(...logged only 2048 of " . length($msg) 491 | . " bytes)"; 492 | } 493 | 494 | $msg =~ s/^/# $prefix/gm; 495 | $msg =~ s/([^\x20-\x7e])/sprintf('\\x%02x', ord($1)) . (($1 eq "\n") ? "\n" : '')/gmxe; 496 | $msg .= "\n" unless $msg =~ /\n\Z/; 497 | print $msg; 498 | } 499 | 500 | sub log_out { 501 | log_core('>>', @_); 502 | } 503 | 504 | sub log_in { 505 | log_core('<<', @_); 506 | } 507 | 508 | ############################################################################### 509 | 510 | sub http_get($;%) { 511 | my ($url, %extra) = @_; 512 | return http(<new( 547 | Proto => 'tcp', 548 | PeerAddr => '127.0.0.1:8080' 549 | ) 550 | or die "Can't connect to nginx: $!\n"; 551 | 552 | log_out($request); 553 | $s->print($request); 554 | 555 | select undef, undef, undef, $extra{sleep} if $extra{sleep}; 556 | return '' if $extra{aborted}; 557 | 558 | if ($extra{body}) { 559 | log_out($extra{body}); 560 | $s->print($extra{body}); 561 | } 562 | 563 | alarm(0); 564 | }; 565 | alarm(0); 566 | if ($@) { 567 | log_in("died: $@"); 568 | return undef; 569 | } 570 | 571 | return $s; 572 | } 573 | 574 | sub http_end($;%) { 575 | my ($s) = @_; 576 | my $reply; 577 | 578 | eval { 579 | local $SIG{ALRM} = sub { die "timeout\n" }; 580 | local $SIG{PIPE} = sub { die "sigpipe\n" }; 581 | alarm(5); 582 | 583 | local $/; 584 | $reply = $s->getline(); 585 | 586 | alarm(0); 587 | }; 588 | alarm(0); 589 | if ($@) { 590 | log_in("died: $@"); 591 | return undef; 592 | } 593 | 594 | log_in($reply); 595 | return $reply; 596 | } 597 | 598 | ############################################################################### 599 | 600 | sub http_gzip_request { 601 | my ($url) = @_; 602 | my $r = http(<builder->skip( 642 | "IO::Uncompress::Gunzip not installed", 1) if $@; 643 | 644 | my $in = http_content($text); 645 | my $out; 646 | 647 | IO::Uncompress::Gunzip::gunzip(\$in => \$out); 648 | 649 | Test::More->builder->like($out, $re, $name); 650 | } 651 | } 652 | 653 | ############################################################################### 654 | 655 | 1; 656 | 657 | ############################################################################### 658 | -------------------------------------------------------------------------------- /test/t/lib/Test/Nginx/IMAP.pm: -------------------------------------------------------------------------------- 1 | package Test::Nginx::IMAP; 2 | 3 | # (C) Maxim Dounin 4 | 5 | # Module for nginx imap tests. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More qw//; 13 | use IO::Socket; 14 | use Socket qw/ CRLF /; 15 | 16 | use Test::Nginx; 17 | 18 | use base qw/ IO::Socket::INET /; 19 | 20 | sub new { 21 | my $class = shift; 22 | 23 | my $self = return $class->SUPER::new( 24 | Proto => "tcp", 25 | PeerAddr => "127.0.0.1:8143", 26 | @_ 27 | ) 28 | or die "Can't connect to nginx: $!\n"; 29 | 30 | $self->autoflush(1); 31 | 32 | return $self; 33 | } 34 | 35 | sub send { 36 | my ($self, $cmd) = @_; 37 | log_out($cmd); 38 | $self->print($cmd . CRLF); 39 | } 40 | 41 | sub read { 42 | my ($self) = @_; 43 | eval { 44 | local $SIG{ALRM} = sub { die "timeout\n" }; 45 | alarm(3); 46 | while (<$self>) { 47 | log_in($_); 48 | # XXX 49 | next if m/^\d\d\d-/; 50 | last; 51 | } 52 | alarm(0); 53 | }; 54 | alarm(0); 55 | if ($@) { 56 | log_in("died: $@"); 57 | return undef; 58 | } 59 | return $_; 60 | } 61 | 62 | sub check { 63 | my ($self, $regex, $name) = @_; 64 | Test::More->builder->like($self->read(), $regex, $name); 65 | } 66 | 67 | sub ok { 68 | my $self = shift; 69 | Test::More->builder->like($self->read(), qr/^\S+ OK/, @_); 70 | } 71 | 72 | ############################################################################### 73 | 74 | sub imap_test_daemon { 75 | my $server = IO::Socket::INET->new( 76 | Proto => 'tcp', 77 | LocalAddr => '127.0.0.1:8144', 78 | Listen => 5, 79 | Reuse => 1 80 | ) 81 | or die "Can't create listening socket: $!\n"; 82 | 83 | while (my $client = $server->accept()) { 84 | $client->autoflush(1); 85 | print $client "* OK fake imap server ready" . CRLF; 86 | 87 | while (<$client>) { 88 | my $tag = ''; 89 | 90 | $tag = $1 if m/^(\S+)/; 91 | s/^(\S+)\s+//; 92 | 93 | if (/^logout/i) { 94 | print $client $tag . ' OK logout ok' . CRLF; 95 | } elsif (/^login /i) { 96 | print $client $tag . ' OK login ok' . CRLF; 97 | } else { 98 | print $client $tag . ' ERR unknown command' . CRLF; 99 | } 100 | } 101 | 102 | close $client; 103 | } 104 | } 105 | 106 | ############################################################################### 107 | 108 | 1; 109 | 110 | ############################################################################### 111 | -------------------------------------------------------------------------------- /test/t/lib/Test/Nginx/POP3.pm: -------------------------------------------------------------------------------- 1 | package Test::Nginx::POP3; 2 | 3 | # (C) Maxim Dounin 4 | 5 | # Module for nginx pop3 tests. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More qw//; 13 | use IO::Socket; 14 | use Socket qw/ CRLF /; 15 | 16 | use Test::Nginx; 17 | 18 | use base qw/ IO::Socket::INET /; 19 | 20 | sub new { 21 | my $class = shift; 22 | 23 | my $self = return $class->SUPER::new( 24 | Proto => "tcp", 25 | PeerAddr => "127.0.0.1:8110", 26 | @_ 27 | ) 28 | or die "Can't connect to nginx: $!\n"; 29 | 30 | $self->autoflush(1); 31 | 32 | return $self; 33 | } 34 | 35 | sub send { 36 | my ($self, $cmd) = @_; 37 | log_out($cmd); 38 | $self->print($cmd . CRLF); 39 | } 40 | 41 | sub read { 42 | my ($self) = @_; 43 | eval { 44 | local $SIG{ALRM} = sub { die "timeout\n" }; 45 | alarm(3); 46 | while (<$self>) { 47 | log_in($_); 48 | # XXX 49 | next if m/^\d\d\d-/; 50 | last; 51 | } 52 | alarm(0); 53 | }; 54 | alarm(0); 55 | if ($@) { 56 | log_in("died: $@"); 57 | return undef; 58 | } 59 | return $_; 60 | } 61 | 62 | sub check { 63 | my ($self, $regex, $name) = @_; 64 | Test::More->builder->like($self->read(), $regex, $name); 65 | } 66 | 67 | sub ok { 68 | my $self = shift; 69 | Test::More->builder->like($self->read(), qr/^\+OK/, @_); 70 | } 71 | 72 | ############################################################################### 73 | 74 | sub pop3_test_daemon { 75 | my $server = IO::Socket::INET->new( 76 | Proto => 'tcp', 77 | LocalAddr => '127.0.0.1:8111', 78 | Listen => 5, 79 | Reuse => 1 80 | ) 81 | or die "Can't create listening socket: $!\n"; 82 | 83 | while (my $client = $server->accept()) { 84 | $client->autoflush(1); 85 | print $client "+OK fake pop3 server ready" . CRLF; 86 | 87 | while (<$client>) { 88 | if (/^quit/i) { 89 | print $client '+OK quit ok' . CRLF; 90 | } elsif (/^user test\@example.com/i) { 91 | print $client '+OK user ok' . CRLF; 92 | } elsif (/^pass secret/i) { 93 | print $client '+OK pass ok' . CRLF; 94 | } else { 95 | print $client "-ERR unknown command" . CRLF; 96 | } 97 | } 98 | 99 | close $client; 100 | } 101 | } 102 | 103 | ############################################################################### 104 | 105 | 1; 106 | 107 | ############################################################################### 108 | -------------------------------------------------------------------------------- /test/t/lib/Test/Nginx/SMTP.pm: -------------------------------------------------------------------------------- 1 | package Test::Nginx::SMTP; 2 | 3 | # (C) Maxim Dounin 4 | 5 | # Module for nginx smtp tests. 6 | 7 | ############################################################################### 8 | 9 | use warnings; 10 | use strict; 11 | 12 | use Test::More qw//; 13 | use IO::Socket; 14 | use Socket qw/ CRLF /; 15 | 16 | use Test::Nginx; 17 | 18 | use base qw/ IO::Socket::INET /; 19 | 20 | sub new { 21 | my $class = shift; 22 | 23 | my $self = return $class->SUPER::new( 24 | Proto => "tcp", 25 | PeerAddr => "127.0.0.1:8025", 26 | @_ 27 | ) 28 | or die "Can't connect to nginx: $!\n"; 29 | 30 | $self->autoflush(1); 31 | 32 | return $self; 33 | } 34 | 35 | sub send { 36 | my ($self, $cmd) = @_; 37 | log_out($cmd); 38 | $self->print($cmd . CRLF); 39 | } 40 | 41 | sub read { 42 | my ($self) = @_; 43 | eval { 44 | local $SIG{ALRM} = sub { die "timeout\n" }; 45 | alarm(3); 46 | while (<$self>) { 47 | log_in($_); 48 | next if m/^\d\d\d-/; 49 | last; 50 | } 51 | alarm(0); 52 | }; 53 | alarm(0); 54 | if ($@) { 55 | log_in("died: $@"); 56 | return undef; 57 | } 58 | return $_; 59 | } 60 | 61 | sub check { 62 | my ($self, $regex, $name) = @_; 63 | Test::More->builder->like($self->read(), $regex, $name); 64 | } 65 | 66 | sub ok { 67 | my $self = shift; 68 | Test::More->builder->like($self->read(), qr/^2\d\d /, @_); 69 | } 70 | 71 | sub authok { 72 | my $self = shift; 73 | Test::More->builder->like($self->read(), qr/^235 /, @_); 74 | } 75 | 76 | ############################################################################### 77 | 78 | sub smtp_test_daemon { 79 | my $server = IO::Socket::INET->new( 80 | Proto => 'tcp', 81 | LocalAddr => '127.0.0.1:8026', 82 | Listen => 5, 83 | Reuse => 1 84 | ) 85 | or die "Can't create listening socket: $!\n"; 86 | 87 | while (my $client = $server->accept()) { 88 | $client->autoflush(1); 89 | print $client "220 fake esmtp server ready" . CRLF; 90 | 91 | while (<$client>) { 92 | Test::Nginx::log_core('||', $_); 93 | 94 | if (/^quit/i) { 95 | print $client '221 quit ok' . CRLF; 96 | } elsif (/^(ehlo|helo)/i) { 97 | print $client '250 hello ok' . CRLF; 98 | } elsif (/^rset/i) { 99 | print $client '250 rset ok' . CRLF; 100 | } elsif (/^mail from:[^@]+$/i) { 101 | print $client '500 mail from error' . CRLF; 102 | } elsif (/^mail from:/i) { 103 | print $client '250 mail from ok' . CRLF; 104 | } elsif (/^rcpt to:[^@]+$/i) { 105 | print $client '500 rcpt to error' . CRLF; 106 | } elsif (/^rcpt to:/i) { 107 | print $client '250 rcpt to ok' . CRLF; 108 | } elsif (/^xclient/i) { 109 | print $client '220 xclient ok' . CRLF; 110 | } else { 111 | print $client "500 unknown command" . CRLF; 112 | } 113 | } 114 | 115 | close $client; 116 | } 117 | } 118 | 119 | ############################################################################### 120 | 121 | 1; 122 | 123 | ############################################################################### 124 | -------------------------------------------------------------------------------- /test/t/lib/Time/Parse.pm: -------------------------------------------------------------------------------- 1 | # Date::Parse 2 | # 3 | # Copyright (c) 1995 Graham Barr. All rights reserved. This program is free 4 | # software; you can redistribute it and/or modify it under the same terms 5 | # as Perl itself. 6 | 7 | package Time::Parse; 8 | 9 | =head1 NAME 10 | 11 | Date::Parse - Parse date strings into time values 12 | 13 | =head1 SYNOPSIS 14 | 15 | use Date::Parse; 16 | 17 | $time = str2time($date); 18 | 19 | ($ss,$mm,$hh,$day,$month,$year,$zone) = strptime($date); 20 | 21 | =head1 DESCRIPTION 22 | 23 | C provides two routines for parsing date strings into time values. 24 | 25 | =over 4 26 | 27 | =item str2time(DATE [, ZONE]) 28 | 29 | C parses C and returns a unix time value, or undef upon failure. 30 | C, if given, specifies the timezone to assume when parsing if the 31 | date string does not specify a timezome. 32 | 33 | =item strptime(DATE [, ZONE]) 34 | 35 | C takes the same arguments as str2time but returns an array of 36 | values C<($ss,$mm,$hh,$day,$month,$year,$zone)>. Elements are only defined 37 | if they could be extracted from the date string. The C<$zone> element is 38 | the timezone offset in seconds from GMT. An empty array is returned upon 39 | failure. 40 | 41 | =head1 MULTI-LANGUAGE SUPPORT 42 | 43 | Date::Parse is capable of parsing dates in several languages, these are 44 | English, French, German and Italian. Changing the language is done via 45 | a static method call, for example 46 | 47 | Date::Parse->language('German'); 48 | 49 | will cause Date::Parse to attempt to parse any subsequent dates in German. 50 | 51 | This is only a first pass, I am considering changing this to be 52 | 53 | $lang = Date::Language->new('German'); 54 | $lang->str2time("25 Jun 1996 21:09:55 +0100"); 55 | 56 | I am open to suggestions on this. 57 | 58 | =head1 AUTHOR 59 | 60 | Graham Barr 61 | 62 | =head1 REVISION 63 | 64 | $Revision: 2.6 $ 65 | 66 | =head1 COPYRIGHT 67 | 68 | Copyright (c) 1995 Graham Barr. All rights reserved. This program is free 69 | software; you can redistribute it and/or modify it under the same terms 70 | as Perl itself. 71 | 72 | =cut 73 | 74 | require 5.000; 75 | use strict; 76 | use vars qw($VERSION @ISA @EXPORT); 77 | use Time::Local; 78 | use Carp; 79 | use Time::Zone; 80 | use Exporter; 81 | 82 | @ISA = qw(Exporter); 83 | @EXPORT = qw(&strtotime &str2time &strptime); 84 | 85 | $VERSION = sprintf("%d.%02d", q$Revision: 2.6 $ =~ m#(\d+)\.(\d+)#); 86 | 87 | my %month = ( 88 | january => 0, 89 | february => 1, 90 | march => 2, 91 | april => 3, 92 | may => 4, 93 | june => 5, 94 | july => 6, 95 | august => 7, 96 | september => 8, 97 | sept => 8, 98 | october => 9, 99 | november => 10, 100 | december => 11, 101 | ); 102 | 103 | my %day = ( 104 | sunday => 0, 105 | monday => 1, 106 | tuesday => 2, 107 | tues => 2, 108 | wednesday => 3, 109 | wednes => 3, 110 | thursday => 4, 111 | thur => 4, 112 | thurs => 4, 113 | friday => 5, 114 | saturday => 6, 115 | ); 116 | 117 | my @suf = (qw(th st nd rd th th th th th th)) x 3; 118 | @suf[11,12,13] = qw(th th th); 119 | 120 | #Abbreviations 121 | 122 | map { $month{substr($_,0,3)} = $month{$_} } keys %month; 123 | map { $day{substr($_,0,3)} = $day{$_} } keys %day; 124 | 125 | my $strptime = <<'ESQ'; 126 | my %month = map { lc $_ } %$mon_ref; 127 | my $daypat = join("|", map { lc $_ } keys %$day_ref); 128 | my $monpat = join("|", keys %month); 129 | my $sufpat = join("|", map { lc $_ } @$suf_ref); 130 | 131 | my %ampm = ( 132 | am => 0, 133 | pm => 12 134 | ); 135 | 136 | # allow map am +. a.m. 137 | map { my($z) = $_; $z =~ s#(\w)#$1\.#g; $ampm{$z} = $ampm{$_} } keys %ampm; 138 | 139 | my($AM, $PM) = (0,12); 140 | 141 | sub 142 | { 143 | 144 | my $dtstr = lc shift; 145 | my $merid = 24; 146 | 147 | my($year,$month,$day,$hh,$mm,$ss,$zone) = (undef) x 7; 148 | 149 | $zone = tz_offset(shift) 150 | if(@_); 151 | 152 | while(1) { last unless($dtstr =~ s#\([^\(\)]*\)# #o) } 153 | 154 | $dtstr =~ s#(\A|\n|\Z)# #sog; 155 | 156 | # ignore day names 157 | $dtstr =~ s#([\d\w\s])[\.\,]\s#$1 #sog; 158 | $dtstr =~ s#($daypat)\s*(den\s)?# #o; 159 | # Time: 12:00 or 12:00:00 with optional am/pm 160 | 161 | if($dtstr =~ s#[:\s](\d\d?):(\d\d)(:(\d\d)(?:\.\d+)?)?\s*([ap]\.?m\.?)?\s# #o) 162 | { 163 | ($hh,$mm,$ss) = ($1,$2,$4 || 0); 164 | $merid = $ampm{$5} if($5); 165 | } 166 | 167 | # Time: 12 am 168 | 169 | elsif($dtstr =~ s#\s(\d\d?)\s*([ap]\.?m\.?)\s# #o) 170 | { 171 | ($hh,$mm,$ss) = ($1,0,0); 172 | $merid = $ampm{$2}; 173 | } 174 | 175 | # Date: 12-June-96 (using - . or /) 176 | 177 | if($dtstr =~ s#\s(\d\d?)([\-\./])($monpat)(\2(\d\d+))?\s# #o) 178 | { 179 | ($month,$day) = ($month{$3},$1); 180 | $year = $5 181 | if($5); 182 | } 183 | 184 | # Date: 12-12-96 (using '-', '.' or '/' ) 185 | 186 | elsif($dtstr =~ s#\s(\d\d*)([\-\./])(\d\d?)(\2(\d\d+))?\s# #o) 187 | { 188 | ($month,$day) = ($1 - 1,$3); 189 | if($5) 190 | { 191 | $year = $5; 192 | # Possible match for 1995-01-24 (short mainframe date format); 193 | ($year,$month,$day) = ($1, $3 - 1, $5) 194 | if($month > 12); 195 | } 196 | } 197 | elsif($dtstr =~ s#\s(\d+)\s*($sufpat)?\s*($monpat)# #o) 198 | { 199 | ($month,$day) = ($month{$3},$1); 200 | } 201 | elsif($dtstr =~ s#($monpat)\s*(\d+)\s*($sufpat)?\s# #o) 202 | { 203 | ($month,$day) = ($month{$1},$2); 204 | } 205 | 206 | # Date: 961212 207 | 208 | elsif($dtstr =~ s#\s(\d\d)(\d\d)(\d\d)\s# #o) 209 | { 210 | ($year,$month,$day) = ($1,$2-1,$3); 211 | } 212 | 213 | $year = $1 214 | if(!defined($year) && $dtstr =~ s#\s(\d{2}(\d{2})?)[\s\.,]# #o); 215 | 216 | # Zone 217 | 218 | if($dtstr =~ s#\s"?(\w{3,})\s# #o) 219 | { 220 | $zone = tz_offset($1); 221 | return () 222 | unless(defined $zone); 223 | } 224 | elsif($dtstr =~ s#\s(([\-\+])\d\d?)(\d\d)\s# #o) 225 | { 226 | my $m = $2 . $3; 227 | $zone = 60 * ($m + (60 * $1)); 228 | } 229 | 230 | return () 231 | if($dtstr =~ /\S/o); 232 | 233 | $hh += 12 234 | if(defined $hh && $merid == $PM); 235 | 236 | $year -= 1900 237 | if(defined $year && $year > 1900); 238 | 239 | return ($ss,$mm,$hh,$day,$month,$year,$zone); 240 | } 241 | ESQ 242 | 243 | use vars qw($day_ref $mon_ref $suf_ref $obj); 244 | 245 | sub gen_parser 246 | { 247 | local($day_ref,$mon_ref,$suf_ref,$obj) = @_; 248 | 249 | if($obj) 250 | { 251 | my $obj_strptime = $strptime; 252 | substr($obj_strptime,index($strptime,"sub")+6,0) = <<'ESQ'; 253 | shift; # package 254 | ESQ 255 | return eval "$obj_strptime"; 256 | } 257 | 258 | eval "$strptime"; 259 | 260 | } 261 | 262 | *strptime = gen_parser(\%day,\%month,\@suf); 263 | 264 | sub str2time 265 | { 266 | my @t = strptime(@_); 267 | 268 | return undef 269 | unless @t; 270 | 271 | my($ss,$mm,$hh,$day,$month,$year,$zone) = @t; 272 | my @lt = localtime(time); 273 | 274 | $hh ||= 0; 275 | $mm ||= 0; 276 | $ss ||= 0; 277 | 278 | $month = $lt[4] 279 | unless(defined $month); 280 | 281 | $day = $lt[3] 282 | unless(defined $day); 283 | 284 | $year = ($month > $lt[4]) ? ($lt[5] - 1) : $lt[5] 285 | unless(defined $year); 286 | 287 | return defined $zone ? timegm($ss,$mm,$hh,$day,$month,$year) - $zone 288 | : timelocal($ss,$mm,$hh,$day,$month,$year); 289 | } 290 | 291 | 1; 292 | 293 | -------------------------------------------------------------------------------- /test/t/lib/Time/Zone.pm: -------------------------------------------------------------------------------- 1 | 2 | package Time::Zone; 3 | 4 | =head1 NAME 5 | 6 | Time::Zone -- miscellaneous timezone manipulations routines 7 | 8 | =head1 SYNOPSIS 9 | 10 | use Time::Zone; 11 | print tz2zone(); 12 | print tz2zone($ENV{'TZ'}); 13 | print tz2zone($ENV{'TZ'}, time()); 14 | print tz2zone($ENV{'TZ'}, undef, $isdst); 15 | $offset = tz_local_offset(); 16 | $offset = tz_offset($TZ); 17 | 18 | =head1 DESCRIPTION 19 | 20 | This is a collection of miscellaneous timezone manipulation routines. 21 | 22 | C parses the TZ environment variable and returns a timezone 23 | string suitable for inclusion in L-like output. It opionally takes 24 | a timezone string, a time, and a is-dst flag. 25 | 26 | C determins the offset from GMT time in seconds. It 27 | only does the calculation once. 28 | 29 | C determines the offset from GMT in seconds of a specified 30 | timezone. 31 | 32 | C determines the name of the timezone based on its offset 33 | 34 | =head1 AUTHORS 35 | 36 | Graham Barr 37 | David Muir Sharnoff 38 | Paul Foley 39 | 40 | =cut 41 | 42 | require 5.002; 43 | 44 | require Exporter; 45 | use Carp; 46 | use strict; 47 | use vars qw(@ISA @EXPORT $VERSION @tz_local); 48 | 49 | @ISA = qw(Exporter); 50 | @EXPORT = qw(tz2zone tz_local_offset tz_offset tz_name); 51 | $VERSION = "2.04"; 52 | 53 | # Parts stolen from code by Paul Foley 54 | 55 | sub tz2zone (;$$$) 56 | { 57 | my($TZ, $time, $isdst) = @_; 58 | 59 | use vars qw(%tzn_cache); 60 | 61 | $TZ = defined($ENV{'TZ'}) ? ( $ENV{'TZ'} ? $ENV{'TZ'} : 'GMT' ) : '' 62 | unless $TZ; 63 | 64 | # Hack to deal with 'PST8PDT' format of TZ 65 | # Note that this can't deal with all the esoteric forms, but it 66 | # does recognize the most common: [:]STDoff[DST[off][,rule]] 67 | 68 | if (! defined $isdst) { 69 | my $j; 70 | $time = time() unless $time; 71 | ($j, $j, $j, $j, $j, $j, $j, $j, $isdst) = localtime($time); 72 | } 73 | 74 | if (defined $tzn_cache{$TZ}->[$isdst]) { 75 | return $tzn_cache{$TZ}->[$isdst]; 76 | } 77 | 78 | if ($TZ =~ /^ 79 | ( [^:\d+\-,] {3,} ) 80 | ( [+-] ? 81 | \d {1,2} 82 | ( : \d {1,2} ) {0,2} 83 | ) 84 | ( [^\d+\-,] {3,} )? 85 | /x 86 | ) { 87 | $TZ = $isdst ? $4 : $1; 88 | $tzn_cache{$TZ} = [ $1, $4 ]; 89 | } else { 90 | $tzn_cache{$TZ} = [ $TZ, $TZ ]; 91 | } 92 | return $TZ; 93 | } 94 | 95 | sub tz_local_offset (;$) 96 | { 97 | my ($time) = @_; 98 | 99 | $time = time() unless $time; 100 | my (@l) = localtime($time); 101 | my $isdst = $l[8]; 102 | 103 | if (defined($tz_local[$isdst])) { 104 | return $tz_local[$isdst]; 105 | } 106 | 107 | $tz_local[$isdst] = &calc_off($time); 108 | 109 | return $tz_local[$isdst]; 110 | } 111 | 112 | sub calc_off 113 | { 114 | my ($time) = @_; 115 | 116 | my (@l) = localtime($time); 117 | my (@g) = gmtime($time); 118 | 119 | my $off; 120 | 121 | $off = $l[0] - $g[0] 122 | + ($l[1] - $g[1]) * 60 123 | + ($l[2] - $g[2]) * 3600; 124 | 125 | # subscript 7 is yday. 126 | 127 | if ($l[7] == $g[7]) { 128 | # done 129 | } elsif ($l[7] == $g[7] + 1) { 130 | $off += 86400; 131 | } elsif ($l[7] == $g[7] - 1) { 132 | $off -= 86400; 133 | } elsif ($l[7] < $g[7]) { 134 | # crossed over a year boundry! 135 | # localtime is beginning of year, gmt is end 136 | # therefore local is ahead 137 | $off += 86400; 138 | } else { 139 | $off -= 86400; 140 | } 141 | 142 | return $off; 143 | } 144 | 145 | # constants 146 | 147 | CONFIG: { 148 | use vars qw(%dstZone %zoneOff %dstZoneOff %Zone); 149 | 150 | %dstZone = ( 151 | # "ndt" => -2*3600-1800, # Newfoundland Daylight 152 | "adt" => -3*3600, # Atlantic Daylight 153 | "edt" => -4*3600, # Eastern Daylight 154 | "cdt" => -5*3600, # Central Daylight 155 | "mdt" => -6*3600, # Mountain Daylight 156 | "pdt" => -7*3600, # Pacific Daylight 157 | "ydt" => -8*3600, # Yukon Daylight 158 | "hdt" => -9*3600, # Hawaii Daylight 159 | "bst" => +1*3600, # British Summer 160 | "mest" => +2*3600, # Middle European Summer 161 | "sst" => +2*3600, # Swedish Summer 162 | "fst" => +2*3600, # French Summer 163 | "wadt" => +8*3600, # West Australian Daylight 164 | # "cadt" => +10*3600+1800, # Central Australian Daylight 165 | "eadt" => +11*3600, # Eastern Australian Daylight 166 | "nzdt" => +13*3600, # New Zealand Daylight 167 | ); 168 | 169 | %Zone = ( 170 | "gmt" => 0, # Greenwich Mean 171 | "ut" => 0, # Universal (Coordinated) 172 | "utc" => 0, 173 | "wet" => 0, # Western European 174 | "wat" => -1*3600, # West Africa 175 | "at" => -2*3600, # Azores 176 | # For completeness. BST is also British Summer, and GST is also Guam Standard. 177 | # "bst" => -3*3600, # Brazil Standard 178 | # "gst" => -3*3600, # Greenland Standard 179 | # "nft" => -3*3600-1800,# Newfoundland 180 | # "nst" => -3*3600-1800,# Newfoundland Standard 181 | "ast" => -4*3600, # Atlantic Standard 182 | "est" => -5*3600, # Eastern Standard 183 | "cst" => -6*3600, # Central Standard 184 | "mst" => -7*3600, # Mountain Standard 185 | "pst" => -8*3600, # Pacific Standard 186 | "yst" => -9*3600, # Yukon Standard 187 | "hst" => -10*3600, # Hawaii Standard 188 | "cat" => -10*3600, # Central Alaska 189 | "ahst" => -10*3600, # Alaska-Hawaii Standard 190 | "nt" => -11*3600, # Nome 191 | "idlw" => -12*3600, # International Date Line West 192 | "cet" => +1*3600, # Central European 193 | "met" => +1*3600, # Middle European 194 | "mewt" => +1*3600, # Middle European Winter 195 | "swt" => +1*3600, # Swedish Winter 196 | "fwt" => +1*3600, # French Winter 197 | "eet" => +2*3600, # Eastern Europe, USSR Zone 1 198 | "bt" => +3*3600, # Baghdad, USSR Zone 2 199 | # "it" => +3*3600+1800,# Iran 200 | "zp4" => +4*3600, # USSR Zone 3 201 | "zp5" => +5*3600, # USSR Zone 4 202 | # "ist" => +5*3600+1800,# Indian Standard 203 | "zp6" => +6*3600, # USSR Zone 5 204 | # For completeness. NST is also Newfoundland Stanard, and SST is also Swedish Summer. 205 | # "nst" => +6*3600+1800,# North Sumatra 206 | # "sst" => +7*3600, # South Sumatra, USSR Zone 6 207 | "wast" => +7*3600, # West Australian Standard 208 | # "jt" => +7*3600+1800,# Java (3pm in Cronusland!) 209 | "cct" => +8*3600, # China Coast, USSR Zone 7 210 | "jst" => +9*3600, # Japan Standard, USSR Zone 8 211 | # "cast" => +9*3600+1800,# Central Australian Standard 212 | "east" => +10*3600, # Eastern Australian Standard 213 | "gst" => +10*3600, # Guam Standard, USSR Zone 9 214 | "nzt" => +12*3600, # New Zealand 215 | "nzst" => +12*3600, # New Zealand Standard 216 | "idle" => +12*3600, # International Date Line East 217 | ); 218 | 219 | %zoneOff = reverse(%Zone); 220 | %dstZoneOff = reverse(%dstZone); 221 | 222 | # Preferences 223 | 224 | $zoneOff{0} = 'gmt'; 225 | $dstZoneOff{3600} = 'bst'; 226 | 227 | } 228 | 229 | sub tz_offset (;$$) 230 | { 231 | my ($zone, $time) = @_; 232 | 233 | return &tz_local_offset() unless($zone); 234 | 235 | $time = time() unless $time; 236 | my(@l) = localtime($time); 237 | my $dst = $l[8]; 238 | 239 | $zone = lc $zone; 240 | 241 | if($zone =~ /^(([\-\+])\d\d?)(\d\d)$/) { 242 | my $v = $2 . $3; 243 | return $1 * 3600 + $v * 60; 244 | } elsif (exists $dstZone{$zone} && ($dst || !exists $Zone{$zone})) { 245 | return $dstZone{$zone}; 246 | } elsif(exists $Zone{$zone}) { 247 | return $Zone{$zone}; 248 | } 249 | undef; 250 | } 251 | 252 | sub tz_name (;$$) 253 | { 254 | my ($off, $dst) = @_; 255 | 256 | $off = tz_offset() 257 | unless(defined $off); 258 | 259 | $dst = (localtime(time))[8] 260 | unless(defined $dst); 261 | 262 | if (exists $dstZoneOff{$off} && ($dst || !exists $zoneOff{$off})) { 263 | return $dstZoneOff{$off}; 264 | } elsif (exists $zoneOff{$off}) { 265 | return $zoneOff{$off}; 266 | } 267 | sprintf("%+05d", int($off / 60) * 100 + $off % 60); 268 | } 269 | 270 | 1; 271 | -------------------------------------------------------------------------------- /test/t/upsync.t: -------------------------------------------------------------------------------- 1 | use warnings; 2 | use strict; 3 | 4 | use Test::More; 5 | 6 | BEGIN { use FindBin; chdir($FindBin::Bin); } 7 | 8 | 9 | use lib 'lib'; 10 | use File::Path; 11 | use Test::Nginx; 12 | 13 | 14 | my $NGINX = defined $ENV{TEST_NGINX_BINARY} ? $ENV{TEST_NGINX_BINARY} 15 | : '../../nginx/objs/nginx'; 16 | my $t = Test::Nginx->new()->plan(40); 17 | 18 | sub mhttp_get($;$;$;%) { 19 | my ($url, $host, $port, %extra) = @_; 20 | return mhttp(< }; 54 | 55 | close $fd; 56 | 57 | } else { 58 | $content = 'The file could not be opened.'; 59 | } 60 | 61 | return $content; 62 | } 63 | 64 | sub mrun($;$) { 65 | my ($self, $conf) = @_; 66 | 67 | my $testdir = $self->{_testdir}; 68 | 69 | if (defined $conf) { 70 | my $c = `cat $conf`; 71 | $self->write_file_expand('nginx.conf', $c); 72 | } 73 | 74 | my $pid = fork(); 75 | die "Unable to fork(): $!\n" unless defined $pid; 76 | 77 | if ($pid == 0) { 78 | my @globals = $self->{_test_globals} ? 79 | () : ('-g', "pid $testdir/nginx.pid; " 80 | . "error_log $testdir/error.log debug;"); 81 | exec($NGINX, '-c', "$testdir/nginx.conf", '-p', "$testdir", 82 | @globals) or die "Unable to exec(): $!\n"; 83 | } 84 | 85 | # wait for nginx to start 86 | 87 | $self->waitforfile("$testdir/nginx.pid") 88 | or die "Can't start nginx"; 89 | 90 | $self->{_started} = 1; 91 | return $self; 92 | } 93 | 94 | ############################################################################### 95 | 96 | select STDERR; $| = 1; 97 | select STDOUT; $| = 1; 98 | 99 | warn "your test dir is ".$t->testdir(); 100 | 101 | $t->write_file_expand('nginx.conf', <<'EOF'); 102 | 103 | %%TEST_GLOBALS%% 104 | 105 | daemon off; 106 | 107 | worker_processes 1; 108 | 109 | events { 110 | accept_mutex off; 111 | } 112 | 113 | stream { 114 | 115 | upstream test { 116 | upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_interval=50ms upsync_timeout=6m upsync_type=consul; 117 | upsync_dump_path /tmp/servers_test.conf; 118 | 119 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10; 120 | } 121 | 122 | upstream backend { 123 | server 127.0.0.1:8090 weight=10 max_fails=3 fail_timeout=10; 124 | } 125 | 126 | server { 127 | listen 8080; 128 | 129 | upstream_show; 130 | } 131 | 132 | server { 133 | listen 8081; 134 | 135 | proxy_connect_timeout 1s; 136 | proxy_timeout 3s; 137 | proxy_pass test; 138 | } 139 | 140 | server { 141 | listen 8082; 142 | 143 | proxy_connect_timeout 1s; 144 | proxy_timeout 3s; 145 | proxy_pass backend; 146 | } 147 | } 148 | EOF 149 | 150 | mrun($t); 151 | 152 | ############################################################################### 153 | my $rep; 154 | my $dump; 155 | 156 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 18:21:37'); 157 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 18:20:37'); 158 | 159 | sleep(1); 160 | 161 | like(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 18:22:37'); 162 | 163 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8089', '', 8500), qr/true/m, '2015-12-27 17:50:35'); 164 | 165 | $rep = qr/ 166 | Upstream name: test;Backend server counts: 1 167 | server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10; 168 | /m; 169 | 170 | sleep(1); 171 | 172 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-17 17:43:30'); 173 | 174 | ############################################################################### 175 | 176 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":10,"max_fails":3,"fail_timeout":10}', 8500), qr/true/m, '2015-12-27 17:42:35'); 177 | 178 | $rep = qr/ 179 | Upstream name: test;Backend server counts: 2 180 | server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10; 181 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10; 182 | /m; 183 | 184 | sleep(1); 185 | 186 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:17:53'); 187 | 188 | ######################### 189 | 190 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 18:24:37'); 191 | 192 | $rep = qr/ 193 | Upstream name: test;Backend server counts: 1 194 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10; 195 | /m; 196 | 197 | sleep(1); 198 | 199 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-17 18:30:40'); 200 | 201 | ######################### 202 | 203 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8089', '{"weight":20,"max_fails":0,"fail_timeout":30}', 8500), qr/true/m, '2015-12-27 18:31:39'); 204 | 205 | $rep = qr/ 206 | Upstream name: test;Backend server counts: 2 207 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10; 208 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30; 209 | /m; 210 | 211 | sleep(1); 212 | 213 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-17 18:32:53'); 214 | 215 | ######################## 216 | 217 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 18:33:37'); 218 | 219 | $rep = qr/ 220 | Upstream name: test;Backend server counts: 1 221 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30; 222 | /m; 223 | 224 | sleep(1); 225 | 226 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:34:40'); 227 | 228 | ####################### 229 | 230 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":20,"max_fails":0,"fail_timeout":30}', 8500), qr/true/m, '2015-12-27 18:35:35'); 231 | 232 | $rep = qr/ 233 | Upstream name: test;Backend server counts: 2 234 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30; 235 | server 127.0.0.1:8088 weight=20 max_fails=0 fail_timeout=30; 236 | /m; 237 | 238 | sleep(1); 239 | 240 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:34:40'); 241 | 242 | ####################### 243 | 244 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40}', 8500), qr/true/m, '2015-12-27 18:42:35'); 245 | 246 | $rep = qr/ 247 | Upstream name: test;Backend server counts: 2 248 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30; 249 | server 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30; 250 | /m; 251 | 252 | sleep(1); 253 | 254 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 18:43:40'); 255 | 256 | ####################### 257 | 258 | like(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 18:22:33'); 259 | 260 | sleep(1); 261 | 262 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-14 18:44:51'); 263 | 264 | ####################### 265 | 266 | $dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 267 | server 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30s; 268 | /m; 269 | 270 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2015-12-27 18:51:35'); 271 | 272 | ####################### 273 | 274 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40,"max_fails":3,"fail_timeout":20, "down":1}', 8500), qr/true/m, '2016-03-15 18:35:35'); 275 | 276 | sleep(1); 277 | 278 | $dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 279 | server 127.0.0.1:8088 weight=40 max_fails=3 fail_timeout=20s down; 280 | /m; 281 | 282 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 18:51:35'); 283 | 284 | ####################### 285 | 286 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40,"max_fails":0,"fail_timeout":30, "down":0}', 8500), qr/true/m, '2016-03-15 17:35:35'); 287 | 288 | sleep(1); 289 | 290 | $dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 291 | server 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30s; 292 | /m; 293 | 294 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 17:51:35'); 295 | 296 | $t->stop(); 297 | 298 | ############################################################################## 299 | 300 | $t->write_file_expand('nginx.conf', <<'EOF'); 301 | 302 | %%TEST_GLOBALS%% 303 | 304 | daemon off; 305 | 306 | worker_processes auto; 307 | 308 | events { 309 | accept_mutex off; 310 | } 311 | 312 | http { 313 | 314 | upstream test { 315 | upsync 127.0.0.1:8500/v1/kv/upstreams/test upsync_interval=50ms upsync_timeout=6m upsync_type=consul; 316 | upsync_dump_path /tmp/servers_test.conf; 317 | 318 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10; 319 | } 320 | 321 | upstream backend { 322 | server 127.0.0.1:8090 weight=10 max_fails=3 fail_timeout=10; 323 | } 324 | 325 | server { 326 | listen 8080; 327 | 328 | upstream_show; 329 | } 330 | 331 | server { 332 | listen 8081; 333 | 334 | proxy_connect_timeout 1s; 335 | proxy_timeout 3s; 336 | proxy_pass test; 337 | } 338 | 339 | server { 340 | listen 8082; 341 | 342 | proxy_connect_timeout 1s; 343 | proxy_timeout 3s; 344 | proxy_pass backend; 345 | } 346 | } 347 | EOF 348 | 349 | mrun($t); 350 | 351 | ############################################################################### 352 | 353 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 19:20:37'); 354 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 19:21:37'); 355 | 356 | sleep(1); 357 | 358 | like(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 19:49:37'); 359 | 360 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8089', '', 8500), qr/true/m, '2015-12-27 19:25:35'); 361 | 362 | $rep = qr/ 363 | Upstream name: test;Backend server counts: 1 364 | server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10; 365 | /m; 366 | 367 | sleep(1); 368 | 369 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:20:30'); 370 | 371 | ############################################################################### 372 | 373 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":10,"max_fails":3,"fail_timeout":10}', 8500), qr/true/m, '2015-12-27 17:50:35'); 374 | 375 | $rep = qr/ 376 | Upstream name: test;Backend server counts: 2 377 | server 127.0.0.1:8089 weight=1 max_fails=2 fail_timeout=10; 378 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10; 379 | /m; 380 | 381 | sleep(1); 382 | 383 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:26:53'); 384 | 385 | ########################### 386 | 387 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8089', 8500), qr/true/m, '2015-12-27 19:28:37'); 388 | 389 | $rep = qr/ 390 | Upstream name: test;Backend server counts: 1 391 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10; 392 | /m; 393 | 394 | sleep(1); 395 | 396 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:30:40'); 397 | 398 | ########################### 399 | 400 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8089', '{"weight":20,"max_fails":0,"fail_timeout":30}', 8500), qr/true/m, '2015-12-27 19:31:39'); 401 | 402 | $rep = qr/ 403 | Upstream name: test;Backend server counts: 2 404 | server 127.0.0.1:8088 weight=10 max_fails=3 fail_timeout=10; 405 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30; 406 | /m; 407 | 408 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:32:53'); 409 | 410 | ########################## 411 | 412 | like(mhttp_delete('/v1/kv/upstreams/test/127.0.0.1:8088', 8500), qr/true/m, '2015-12-27 19:35:37'); 413 | 414 | $rep = qr/ 415 | Upstream name: test;Backend server counts: 1 416 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30; 417 | /m; 418 | 419 | sleep(1); 420 | 421 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-17 19:37:40'); 422 | 423 | ########################### 424 | 425 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":20,"max_fails":0,"fail_timeout":30}', 8500), qr/true/m, '2015-12-27 19:39:35'); 426 | 427 | $rep = qr/ 428 | Upstream name: test;Backend server counts: 2 429 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30; 430 | server 127.0.0.1:8088 weight=20 max_fails=0 fail_timeout=30; 431 | /m; 432 | 433 | sleep(1); 434 | 435 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2016-04-17 19:47:40'); 436 | 437 | ########################### 438 | 439 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40}', 8500), qr/true/m, '2015-12-27 19:48:35'); 440 | 441 | $rep = qr/ 442 | Upstream name: test;Backend server counts: 2 443 | server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30; 444 | server 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30; 445 | /m; 446 | 447 | sleep(1); 448 | 449 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:49:40'); 450 | 451 | ########################### 452 | 453 | like(mhttp_delete('/v1/kv/upstreams/test?recurse', 8500), qr/true/m, '2015-12-27 19:50:37'); 454 | 455 | sleep(1); 456 | 457 | like(mhttp_get('/upstream_list', 'localhost', 8080), $rep, '2015-04-14 19:51:40'); 458 | 459 | ########################## 460 | 461 | $dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 462 | server 127.0.0.1:8088 weight=40 max_fails=0 fail_timeout=30s; 463 | /m; 464 | 465 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2015-12-27 19:53:35'); 466 | 467 | ########################## 468 | 469 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40,"max_fails":3,"fail_timeout":20, "down":1}', 8500), qr/true/m, '2016-03-15 19:39:35'); 470 | 471 | sleep(1); 472 | 473 | $dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 474 | server 127.0.0.1:8088 weight=40 max_fails=3 fail_timeout=20s down; 475 | /m; 476 | 477 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 19:53:35'); 478 | 479 | ########################## 480 | 481 | like(mhttp_put('/v1/kv/upstreams/test/127.0.0.1:8088', '{"weight":40,"max_fails":3,"fail_timeout":20, "down":0}', 8500), qr/true/m, '2016-03-15 20:39:35'); 482 | 483 | sleep(1); 484 | 485 | $dump = qr/server 127.0.0.1:8089 weight=20 max_fails=0 fail_timeout=30s; 486 | server 127.0.0.1:8088 weight=40 max_fails=3 fail_timeout=20s; 487 | /m; 488 | 489 | like(get_dump_content('/tmp/servers_test.conf'), $dump, '2016-03-15 20:53:35'); 490 | 491 | $t->stop(); 492 | 493 | ############################################################################### 494 | 495 | sub mhttp($;$;%) { 496 | my ($request, $port, %extra) = @_; 497 | my $reply; 498 | eval { 499 | local $SIG{ALRM} = sub { die "timeout\n" }; 500 | local $SIG{PIPE} = sub { die "sigpipe\n" }; 501 | alarm(2); 502 | my $s = IO::Socket::INET->new( 503 | Proto => "tcp", 504 | PeerAddr => "127.0.0.1:$port" 505 | ); 506 | log_out($request); 507 | $s->print($request); 508 | local $/; 509 | select undef, undef, undef, $extra{sleep} if $extra{sleep}; 510 | return '' if $extra{aborted}; 511 | $reply = $s->getline(); 512 | alarm(0); 513 | }; 514 | alarm(0); 515 | if ($@) { 516 | log_in("died: $@"); 517 | return undef; 518 | } 519 | log_in($reply); 520 | return $reply; 521 | } 522 | -------------------------------------------------------------------------------- /test/test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | TEST_NGINX_USE_HUP=1 TEST_NGINX_BINARY=/usr/local/nginx/sbin/nginx prove -r t 4 | --------------------------------------------------------------------------------