├── .gitignore ├── README ├── Rakefile ├── TODO ├── common.rl ├── config ├── get ├── Makefile └── test.c ├── ngx_buf_util.c ├── ngx_buf_util.h ├── ngx_esi_parser.c ├── ngx_esi_parser.h ├── ngx_esi_parser.rl ├── ngx_esi_tag.c ├── ngx_esi_tag.h ├── ngx_http_esi_filter_module.c ├── ngx_http_esi_filter_module.h └── test ├── config └── nginx-esi.conf ├── docroot ├── 404.html ├── 50x.html ├── content │ ├── 500.html │ ├── 500_with_failover.html │ ├── 500_with_failover_to_alt.html │ ├── ajax_test_page.html │ ├── cookie_variable.html │ ├── foo.html │ ├── include_in_include.html │ ├── malformed_transforms.html │ ├── malformed_transforms.html-correct │ ├── static-failover.html │ ├── test2.html │ └── test3.html ├── esi_invalidate.html ├── esi_max_age_varies.html ├── esi_mixed_content.html ├── esi_test_content.html ├── favicon.ico ├── index.html ├── large-no-cache.html ├── test1.html ├── test3.html └── test_failover.html ├── esi_filter_test.rb └── help.rb /.gitignore: -------------------------------------------------------------------------------- 1 | .nginx_config 2 | nginx 3 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | =About 2 | 3 | An nginx esi filter module 4 | 5 | =Tests 6 | 7 | rake start 8 | rake test 9 | rake stop 10 | 11 | or 12 | 13 | rake start 14 | 15 | wget http://127.0.0.1:9997/ 16 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # use this rake file to build, configure, and test nginx esi module 2 | require 'rake' 3 | require 'rake/testtask' 4 | require 'yaml' 5 | 6 | def load_config 7 | $config ||= YAML.load_file('.nginx_config') 8 | end 9 | 10 | desc 'Setup up your build environment NGINX_SRC=/path/to/nginx/src/' 11 | task :setup do 12 | nginx_src = ENV['NGINX_SRC'] 13 | if nginx_src.nil? or !File.exist?(nginx_src) 14 | puts "You must set NGINX_SRC enironment variable" 15 | exit(1) 16 | end 17 | # write configuration 18 | config = { 19 | :nginx_src => File.expand_path(nginx_src), 20 | :mod_src => File.expand_path(File.dirname(__FILE__)) 21 | } 22 | File.open(".nginx_config","w") do|f| 23 | puts "Saving config: #{YAML.dump(config)}" 24 | f << YAML.dump(config) 25 | end 26 | end 27 | 28 | desc 'Configure nginx' 29 | task :configure do 30 | load_config 31 | build_string = "cd #{$config[:nginx_src]} && ./configure --prefix=#{$config[:mod_src]}/nginx/" 32 | if ENV['RELEASE'].nil? 33 | build_string << " --with-debug" 34 | end 35 | build_string << " --add-module=#{$config[:mod_src]}" 36 | puts build_string.inspect 37 | sh build_string 38 | end 39 | 40 | desc 'Compile nginx' 41 | task :compile do 42 | load_config 43 | sh "cd #{$config[:nginx_src]} && make install" 44 | end 45 | 46 | desc 'Build and configure nginx, use RELEASE=true to build without debugging' 47 | task :build => [:configure,:compile] 48 | 49 | desc 'Start nginx for testing' 50 | task :start do 51 | load_config 52 | sh "cd nginx && ./sbin/nginx -c #{File.join(File.dirname(__FILE__),'test/config/nginx-esi.conf')}" 53 | end 54 | 55 | desc 'Start nginx in gdb ' 56 | task :gdb do 57 | load_config 58 | sh "cd nginx && echo 'run -c #{File.join(File.dirname(__FILE__),'test/config/nginx-esi.conf')}' && gdb ./sbin/nginx" 59 | end 60 | desc 'Start nginx with valgrind' 61 | task :valgrind do 62 | load_config 63 | sh "cd nginx && valgrind --leak-check=full ./sbin/nginx -c #{File.join(File.dirname(__FILE__),'test/config/nginx-esi.conf')}" 64 | end 65 | 66 | desc 'Stop nginx test server' 67 | task :stop do 68 | load_config 69 | pid_file = "#{$config[:mod_src]}/nginx/logs/nginx.pid" 70 | if !File.exist?(pid_file) 71 | STDERR.puts "Expected nginx pid file: (#{pid_file}) not found!" 72 | exit(1) 73 | else 74 | pid = `cat #{pid_file}` 75 | sh "kill #{pid}" 76 | sh "rm -f #{pid_file}" 77 | end 78 | end 79 | 80 | namespace :ragel do 81 | def ragel_version 82 | `ragel --version`.scan(/version ([0-9\.]+)/).first.first 83 | end 84 | desc 'test the ragel version' 85 | task :verify do 86 | if ragel_version < "5.24" 87 | puts "You need to install a version of ragel greater or equal to 5.24" 88 | exit(1) 89 | else 90 | puts "Using ragel #{ragel_version}" 91 | end 92 | end 93 | 94 | desc 'generate the ragel parser' 95 | task :gen => :verify do 96 | if ragel_version < "6.0" 97 | sh "ragel ngx_esi_parser.rl | rlgen-cd -G1 -o ngx_esi_parser.c" 98 | else 99 | sh "ragel ngx_esi_parser.rl -G1 -o ngx_esi_parser.c" 100 | end 101 | raise "Failed to build ESI parser source" unless File.exist? "ngx_esi_parser.c" 102 | end 103 | 104 | desc 'generate a PNG graph of the parser' 105 | task :graph => :verify do 106 | if ragel_version < "6.0" 107 | sh 'ragel ngx_esi_parser.rl | rlgen-dot -p > esi.dot' 108 | else 109 | sh 'ragel -V ngx_esi_parser.rl -p > esi.dot' 110 | end 111 | sh 'dot -Tpng esi.dot -o esi.png' 112 | end 113 | end 114 | 115 | Rake::TestTask.new do |t| 116 | t.test_files = FileList["test/*_test.rb"] 117 | t.verbose = true 118 | end 119 | 120 | task :default => [:compile,:test] 121 | -------------------------------------------------------------------------------- /TODO: -------------------------------------------------------------------------------- 1 | * finish basic implementation 2 | 3 | == longer term 4 | * rework the ngx_esi_parser to use ngx_pmalloc functions 5 | * determine how feasable it would be to hook parser up to nginx event loop... 6 | -------------------------------------------------------------------------------- /common.rl: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2008 Todd A. Fisher 3 | */ 4 | %%{ 5 | machine esi_common_parser; 6 | 7 | # valid attribute key characters 8 | attr_key = [a-zA-Z_\-]+ [a-zA-Z_\-0-9]*; 9 | 10 | # attribute values can be contained within either double or single quotes, when single quoted then double quotes are valid 11 | # when double quoted then single quotes are valid 12 | attr_valueq1 = ('"' ("\\\""| [^"])* '"'); 13 | attr_valueq2 = ("'" ("\\\'"| [^'])* "'"); 14 | 15 | # an attribute value is either single or double quoted 16 | attr_value = ( attr_valueq1 | attr_valueq2 ); 17 | 18 | # inline tags e.g. or , etc... 19 | # block tags e.g. 20 | # NOTE: not verifying the tag nesting 21 | esi_tags = ( 22 | start: ( 23 | # first match block start tag with no attributes 24 | '' @block_start_tag -> final | 25 | # next match either an inline or block tag with attributes 26 | ' start_attribute | 27 | # finally match a closing block tag 28 | '' @block_end_tag 29 | ), 30 | start_attribute: ( 31 | # match the attribute key up to the begining = allowing for arbitrary whitespace before the start of the key 32 | space* attr_key '=' @see_attribute_key -> end_attribute | 33 | # match the end of an inline tag, triggers start and end callbacks 34 | space* '/>' @see_end_tag -> final | 35 | # matching the end of a block tag, triggers start callback 36 | '>' @see_block_start_with_attributes -> final 37 | ), 38 | end_attribute: ( 39 | # match the attribute value and return to matching attribute start 40 | space* attr_value @see_attribute_value -> start_attribute 41 | ) 42 | ) >begin %finish; 43 | 44 | main := ((/.*/)? @echo (esi_tags) )*; 45 | }%% 46 | -------------------------------------------------------------------------------- /config: -------------------------------------------------------------------------------- 1 | ngx_addon_name=ngx_http_esi_filter_module 2 | HTTP_AUX_FILTER_MODULES="$HTTP_AUX_FILTER_MODULES ngx_http_esi_filter_module" 3 | NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_esi_filter_module.c \ 4 | $ngx_addon_dir/ngx_esi_parser.c $ngx_addon_dir/ngx_esi_tag.c \ 5 | $ngx_addon_dir/ngx_buf_util.c " 6 | CORE_CFLAGS="$CORE_CFLAGS -g" 7 | CORE_LIBS="$CORE_LIBS -g" 8 | -------------------------------------------------------------------------------- /get/Makefile: -------------------------------------------------------------------------------- 1 | CFLAGS=-g -Wall `curl-config --cflags` 2 | LDFLAGS=-L/usr/local/lib -lev `curl-config --libs` 3 | 4 | 5 | all: test 6 | 7 | test.o: 8 | 9 | clean: 10 | rm -f test test.o 11 | -------------------------------------------------------------------------------- /get/test.c: -------------------------------------------------------------------------------- 1 | #define _GNU_SOURCE 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include 12 | #include 13 | 14 | #define CURL_TO_EV(action) (action&CURL_POLL_IN?EV_READ:0)|(action&CURL_POLL_OUT?EV_WRITE:0) 15 | 16 | struct _HttpRequest; 17 | struct _HttpProcessor; 18 | 19 | typedef void(*HttpRequestCompleteCB)(struct _HttpRequest*,const char*,size_t,const char*,size_t); 20 | typedef void(*HttpProcessorErrorCB)(struct _HttpProcessor *, const char*, CURLMcode); 21 | 22 | /* HttpRequest: 23 | * Each request gets one of these structs. 24 | * 25 | * stores the result body and head, passing them to complete_cb after the request completes 26 | * */ 27 | typedef struct _HttpRequest { 28 | CURL *handle; 29 | HttpRequestCompleteCB complete_cb; 30 | struct ev_io *io_events; 31 | struct ev_timer *timer_events; 32 | char error[CURL_ERROR_SIZE]; 33 | 34 | char *head; 35 | char *body; 36 | 37 | size_t head_size; 38 | size_t body_size; 39 | 40 | } HttpRequest; 41 | 42 | /* create a new request object */ 43 | HttpRequest *http_request_new(); 44 | /* set the request complete callback */ 45 | void http_request_set_complete_cb(HttpRequest *, HttpRequestCompleteCB); 46 | /* free the new request, this can usually be done within the complete callback */ 47 | void http_request_free( HttpRequest *hr ); 48 | 49 | /* runs an event loop, for processing http requests 50 | * Typically, you'll only want 1 of these structs. It handles the main event loop. 51 | **/ 52 | typedef struct _HttpProcessor { 53 | struct ev_loop *loop; 54 | struct ev_timer timer_events; 55 | CURLM *handle; 56 | int running, started; 57 | 58 | HttpProcessorErrorCB error_cb; 59 | 60 | } HttpProcessor; 61 | 62 | HttpProcessor *http_processor_new(); 63 | int http_processor_add_request(HttpProcessor *hp, HttpRequest *hr); 64 | void http_processor_loop(HttpProcessor *hp); 65 | void http_processor_free(HttpProcessor *hp); 66 | void http_processor_set_error_cb(HttpProcessor *, HttpProcessorErrorCB); 67 | 68 | /*** 69 | * Start Example: 70 | * 71 | * Make 2 concurrent requests, and print the header and body of each response to STDOUT 72 | */ 73 | 74 | /* The complete callback calls http_request_free */ 75 | static void on_complete(HttpRequest *hr, const char *head, size_t head_size, const char *body, size_t body_size) 76 | { 77 | char *buf = (char*)strndup(head,head_size); 78 | printf("Response:\n%s", buf); 79 | free(buf); 80 | fwrite(body, sizeof(char), body_size, stdout); 81 | http_request_free(hr); 82 | } 83 | 84 | /* if anything goes wrong, report it and get out */ 85 | static void on_error(HttpProcessor *hp, const char*where, CURLMcode code) 86 | { 87 | printf( "error: %s code(%d)\n", where, code ); 88 | exit(1); 89 | } 90 | 91 | int main(int argc, char **argv) 92 | { 93 | int i = 0; 94 | HttpProcessor *hp = http_processor_new(); 95 | char *samples[] = {"http://www.google.com/","http://www.yahoo.com/"}; 96 | 97 | http_processor_set_error_cb(hp, on_error); 98 | 99 | for( i = 0; i < 2; ++i ) { 100 | HttpRequest *hr = http_request_new(); 101 | 102 | http_request_set_complete_cb(hr, on_complete); 103 | 104 | curl_easy_setopt(hr->handle, CURLOPT_URL, samples[i]); 105 | 106 | http_processor_add_request(hp, hr); 107 | } 108 | 109 | http_processor_loop(hp); 110 | 111 | http_processor_free(hp); 112 | 113 | puts("\n"); 114 | 115 | return 0; 116 | } 117 | 118 | /*** 119 | * End the example. 120 | * 121 | * What follows is the implementation of the above interface 122 | */ 123 | 124 | /* from http://cool.haxx.se/cvs.cgi/curl/docs/examples/hiperfifo.c */ 125 | static void http_processor_check_http_request_status( HttpProcessor *hp ) 126 | { 127 | int msgs_left; 128 | CURLMsg *msg; 129 | HttpRequest *hr=NULL; 130 | CURL *easy; 131 | CURLcode res; 132 | 133 | do { 134 | 135 | easy = NULL; 136 | 137 | while( (msg = curl_multi_info_read(hp->handle, &msgs_left)) ) { 138 | if( msg->msg == CURLMSG_DONE ) { 139 | easy = msg->easy_handle; 140 | res = msg->data.result; 141 | break; 142 | } 143 | } 144 | if( easy ) { 145 | curl_easy_getinfo(easy, CURLINFO_PRIVATE, &hr); 146 | curl_multi_remove_handle(hp->handle, easy); 147 | 148 | if( hr ) { 149 | 150 | hp->started--; 151 | 152 | if( hr->complete_cb ) { 153 | hr->complete_cb(hr,hr->head,hr->head_size,hr->body,hr->body_size); 154 | } 155 | else { 156 | http_request_free( hr ); 157 | } 158 | 159 | } 160 | } 161 | 162 | } while( easy ); 163 | 164 | if( hp->running == 0 && hp->started == 0 ) { 165 | ev_unloop(hp->loop, EVUNLOOP_ALL); 166 | } 167 | } 168 | 169 | /* from http://cool.haxx.se/cvs.cgi/curl/docs/examples/hiperfifo.c */ 170 | static void http_processor_mcode_or_die(HttpProcessor *hp, const char *where, CURLMcode code) 171 | { 172 | if ( CURLM_OK != code ) { 173 | const char *s; 174 | switch (code) { 175 | case CURLM_CALL_MULTI_PERFORM: s="CURLM_CALL_MULTI_PERFORM"; break; 176 | case CURLM_OK: s="CURLM_OK"; break; 177 | case CURLM_BAD_HANDLE: s="CURLM_BAD_HANDLE"; break; 178 | case CURLM_BAD_EASY_HANDLE: s="CURLM_BAD_EASY_HANDLE"; break; 179 | case CURLM_OUT_OF_MEMORY: s="CURLM_OUT_OF_MEMORY"; break; 180 | case CURLM_INTERNAL_ERROR: s="CURLM_INTERNAL_ERROR"; break; 181 | case CURLM_UNKNOWN_OPTION: s="CURLM_UNKNOWN_OPTION"; break; 182 | case CURLM_LAST: s="CURLM_LAST"; break; 183 | default: s="CURLM_unknown"; 184 | break; 185 | case CURLM_BAD_SOCKET: s="CURLM_BAD_SOCKET"; 186 | // fprintf(stderr, "ERROR: %s returns %s\n", where, s); 187 | /* ignore this error */ 188 | return; 189 | } 190 | //fprintf(stderr, "ERROR: %s returns %s\n", where, s); 191 | 192 | if( hp->error_cb ) { 193 | hp->error_cb(hp, where, code); 194 | } 195 | else { 196 | exit(code); 197 | } 198 | } 199 | } 200 | 201 | 202 | /* Follows the sockets of a HttpRequest object, consider this private to the HttpRequest */ 203 | typedef struct _SockInfo { 204 | struct ev_io io_events; 205 | 206 | curl_socket_t sockfd; 207 | 208 | int action; 209 | int evset:1; 210 | 211 | CURL *handle; 212 | HttpProcessor *hp; 213 | 214 | } SockInfo; 215 | 216 | static void sock_info_watch_event(SockInfo *si, CURL *easy, curl_socket_t sockfd, int action ); 217 | 218 | /* called by libev on action from a curl multi socket */ 219 | static void sock_info_io_cb( struct ev_loop *loop, struct ev_io *w, int revents ) 220 | { 221 | CURLMcode rc; 222 | int mask = 0; 223 | SockInfo *si = (SockInfo*)w->data; 224 | HttpProcessor *hp = si->hp; 225 | 226 | if( revents & EV_READ ) mask |= CURL_CSELECT_IN; 227 | if( revents & EV_WRITE ) mask |= CURL_CSELECT_OUT; 228 | if( revents & EV_ERROR ) mask |= CURL_CSELECT_ERR; 229 | 230 | do { 231 | rc = curl_multi_socket_action( si->hp->handle, si->sockfd, mask, &(si->hp->running) ); 232 | } while( rc == CURLM_CALL_MULTI_PERFORM); 233 | 234 | /* NOTE: si may have been freed at this point */ 235 | http_processor_mcode_or_die( hp, __FUNCTION__, rc ); 236 | http_processor_check_http_request_status( hp ); 237 | } 238 | 239 | 240 | static SockInfo *sock_info_new(CURL *easy, HttpProcessor *hp, curl_socket_t sockfd, int action) 241 | { 242 | SockInfo *si = (SockInfo*)calloc(1,sizeof(SockInfo)); 243 | 244 | si->hp = hp; 245 | 246 | sock_info_watch_event( si, easy, sockfd, action ); 247 | 248 | return si; 249 | } 250 | 251 | static void 252 | sock_info_watch_event(SockInfo *si, CURL *easy, curl_socket_t sockfd, int action ) 253 | { 254 | int kind = CURL_TO_EV(action); 255 | 256 | si->sockfd = sockfd; 257 | si->handle = easy; 258 | si->action = action; 259 | 260 | if( si->evset ) { ev_io_stop( si->hp->loop, &si->io_events ); } 261 | 262 | si->io_events.data = si; 263 | ev_io_init( &si->io_events, sock_info_io_cb, sockfd, kind ); 264 | ev_io_start( si->hp->loop, &si->io_events ); 265 | 266 | si->evset = 1; 267 | } 268 | static void 269 | sock_info_free(SockInfo *si) 270 | { 271 | if( si ) { 272 | if( si->evset ) { ev_io_stop( si->hp->loop, &si->io_events ); } 273 | free(si); 274 | } 275 | } 276 | 277 | static size_t http_request_head_write_cb(void *ptr, size_t size, size_t nmemb, void *userp) 278 | { 279 | HttpRequest *hr = (HttpRequest*)userp; 280 | size_t realsize = size * nmemb; 281 | 282 | if( hr->head ) { 283 | hr->head = (char*)realloc(hr->head, realsize + hr->head_size); 284 | memcpy(hr->head+hr->head_size, ptr, realsize); 285 | hr->head_size += realsize; 286 | } 287 | else { 288 | hr->head = (char*)malloc(realsize); 289 | memcpy(hr->head, ptr, realsize); 290 | hr->head_size = realsize; 291 | } 292 | 293 | return realsize; 294 | } 295 | static size_t http_request_body_write_cb(void *ptr, size_t size, size_t nmemb, void *userp) 296 | { 297 | HttpRequest *hr = (HttpRequest*)userp; 298 | size_t realsize = size * nmemb; 299 | 300 | if( hr->body ) { 301 | hr->body = (char*)realloc(hr->body, realsize + hr->body_size); 302 | memcpy(hr->body+hr->body_size, ptr, realsize); 303 | hr->body_size += realsize; 304 | } 305 | else { 306 | hr->body = (char*)malloc(realsize); 307 | memcpy(hr->body, ptr, realsize); 308 | hr->body_size = realsize; 309 | } 310 | 311 | // printf( "write body action\n" ); 312 | 313 | return realsize; 314 | } 315 | 316 | HttpRequest *http_request_new() 317 | { 318 | HttpRequest *hr = (HttpRequest*)malloc(sizeof(HttpRequest)); 319 | memset(hr,0,sizeof(HttpRequest)); 320 | hr->handle = curl_easy_init(); 321 | 322 | hr->error[0] = '\0'; 323 | 324 | curl_easy_setopt(hr->handle, CURLOPT_HEADERFUNCTION, http_request_head_write_cb); 325 | curl_easy_setopt(hr->handle, CURLOPT_HEADERDATA, hr); 326 | curl_easy_setopt(hr->handle, CURLOPT_WRITEFUNCTION, http_request_body_write_cb); 327 | curl_easy_setopt(hr->handle, CURLOPT_WRITEDATA, hr); 328 | curl_easy_setopt(hr->handle, CURLOPT_ERRORBUFFER, hr->error); 329 | curl_easy_setopt(hr->handle, CURLOPT_PRIVATE, hr); 330 | 331 | hr->io_events = (struct ev_io*)malloc(sizeof(struct ev_io)); 332 | memset(hr->io_events,0,sizeof(struct ev_io)); 333 | 334 | hr->timer_events = (struct ev_timer*)malloc(sizeof(struct ev_timer)); 335 | memset(hr->timer_events,0,sizeof(struct ev_timer)); 336 | 337 | hr->head = NULL; 338 | hr->head_size = 0; 339 | hr->body = NULL; 340 | hr->body_size = 0; 341 | 342 | return hr; 343 | } 344 | 345 | void http_request_set_complete_cb(HttpRequest *hr, HttpRequestCompleteCB hrc) 346 | { 347 | hr->complete_cb = hrc; 348 | } 349 | 350 | void http_processor_set_error_cb(HttpProcessor *hp, HttpProcessorErrorCB hpc) 351 | { 352 | hp->error_cb = hpc; 353 | } 354 | 355 | void http_request_free( HttpRequest *hr ) 356 | { 357 | curl_easy_cleanup( hr->handle ); 358 | free( hr->io_events ); 359 | free( hr->timer_events ); 360 | if( hr->head ) { 361 | free( hr->head ); 362 | } 363 | if( hr->body ) { 364 | free( hr->body ); 365 | } 366 | free( hr ); 367 | } 368 | 369 | /* CURLMOPT_SOCKETFUNCTION */ 370 | static int http_processor_socket_cb( CURL *easy, 371 | curl_socket_t sockfd, 372 | int action, /* on of CURL_CSELECT_[IN|OUT|ERR] */ 373 | void *userp, 374 | void *socketp ) 375 | { 376 | HttpProcessor *hp = (HttpProcessor*)userp; 377 | SockInfo *si = (SockInfo*)socketp; 378 | 379 | if( si ) { 380 | if( action == CURL_POLL_REMOVE ) { 381 | curl_multi_assign( hp->handle, sockfd, NULL ); 382 | sock_info_free( si ) ; 383 | si = NULL; 384 | } 385 | else { 386 | sock_info_watch_event( si, easy, sockfd, action ); 387 | curl_multi_assign( hp->handle, sockfd, si ); 388 | } 389 | } 390 | else { 391 | si = sock_info_new( easy, hp, sockfd, action ); 392 | curl_multi_assign( hp->handle, sockfd, si ); 393 | } 394 | return 0; 395 | } 396 | 397 | static void http_processor_timer_timeout_cb( struct ev_loop *loop, struct ev_timer *w, int revents) 398 | { 399 | CURLMcode rc; 400 | HttpProcessor *hp = (HttpProcessor*)w->data; 401 | 402 | do { 403 | rc = curl_multi_socket_action(hp->handle, CURL_SOCKET_TIMEOUT, 0, &hp->running); 404 | } while (rc == CURLM_CALL_MULTI_PERFORM); 405 | 406 | http_processor_check_http_request_status( hp ); 407 | } 408 | 409 | static int http_processor_timer_cb( CURLM *multi, 410 | long timeout_ms, 411 | void *userp ) 412 | { 413 | HttpProcessor *hp = (HttpProcessor*)userp; 414 | 415 | // printf("update timeout: %.5f seconds or %ld ms \n", (timeout_ms/60.0), timeout_ms); 416 | hp->timer_events.data = hp; 417 | if( timeout_ms == 0 ) { 418 | http_processor_timer_timeout_cb( hp->loop, &hp->timer_events, 0 ); 419 | } 420 | else { 421 | ev_timer_init(&hp->timer_events, http_processor_timer_timeout_cb, 0.0, (timeout_ms / 60.0)); 422 | ev_timer_again(hp->loop, &hp->timer_events); 423 | } 424 | 425 | return 0; 426 | } 427 | 428 | HttpProcessor *http_processor_new() 429 | { 430 | HttpProcessor *hp = (HttpProcessor*)malloc(sizeof(HttpProcessor)); 431 | memset(hp,0,sizeof(HttpProcessor)); 432 | 433 | hp->handle = curl_multi_init(); 434 | 435 | /* setup callback functions */ 436 | curl_multi_setopt(hp->handle, CURLMOPT_SOCKETFUNCTION, http_processor_socket_cb); 437 | curl_multi_setopt(hp->handle, CURLMOPT_SOCKETDATA, hp); 438 | curl_multi_setopt(hp->handle, CURLMOPT_TIMERFUNCTION, http_processor_timer_cb); 439 | curl_multi_setopt(hp->handle, CURLMOPT_TIMERDATA, hp); 440 | 441 | hp->error_cb = NULL; 442 | 443 | hp->loop = ev_loop_new(0); 444 | 445 | hp->running = hp->started = 0; 446 | 447 | return hp; 448 | } 449 | 450 | int http_processor_add_request(HttpProcessor *hp, HttpRequest *hr) 451 | { 452 | CURLMcode rc; 453 | 454 | hp->started++; 455 | rc = curl_multi_add_handle( hp->handle, hr->handle ); 456 | 457 | return rc; 458 | } 459 | 460 | void http_processor_loop(HttpProcessor *hp) 461 | { 462 | ev_loop(hp->loop,0); 463 | } 464 | 465 | void http_processor_free(HttpProcessor *hp) 466 | { 467 | curl_multi_cleanup(hp->handle); 468 | ev_loop_destroy(hp->loop); 469 | free(hp); 470 | } 471 | -------------------------------------------------------------------------------- /ngx_buf_util.c: -------------------------------------------------------------------------------- 1 | #include "ngx_buf_util.h" 2 | 3 | ngx_chain_t *ngx_chain_append_buffer(ngx_pool_t *pool, ngx_chain_t *chain, ngx_buf_t *buf) 4 | { 5 | /* add the new buffer to the last_buf in the chain */ 6 | if( chain->buf ) { 7 | // printf( "append to chain: [" ); debug_string( (const char*)buf->pos, ngx_buf_size(buf) );printf("]\n"); 8 | 9 | /* allocate a new buffer link */ 10 | chain->next = ngx_alloc_chain_link(pool); 11 | chain->next->buf = buf; 12 | /* mark this buffer as the last buffer */ 13 | chain->next->next = NULL; 14 | /* advance the last buffer link */ 15 | return chain->next; 16 | //ctx->last_buf = chain->next; 17 | } 18 | else { 19 | // printf( "assign to chain: [" ); debug_string( (const char*)buf->pos, ngx_buf_size(buf) );printf("]\n"); 20 | chain->buf = buf; 21 | chain->next = NULL; 22 | return chain; 23 | } 24 | } 25 | 26 | ngx_buf_t *ngx_buf_from_data(ngx_pool_t *pool, const void *data, size_t length) 27 | { 28 | ngx_buf_t *b = ngx_create_temp_buf(pool,length); 29 | if (b == NULL) { 30 | return NULL; 31 | } 32 | 33 | ngx_memcpy( b->start, (u_char*)data, length ); 34 | b->last = b->end; 35 | b->last_buf = 0; 36 | 37 | return b; 38 | } 39 | 40 | void debug_string( const char *msg, int length ) 41 | { 42 | if( msg && length > 0 ) { 43 | fwrite( msg, sizeof(char), length, stdout ); 44 | } 45 | else { 46 | printf("(NULL)"); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /ngx_buf_util.h: -------------------------------------------------------------------------------- 1 | #ifndef NGX_BUF_UTIL_H 2 | #define NGX_BUF_UTIL_H 3 | 4 | #include "ngx_http_esi_filter_module.h" 5 | 6 | /* methods for working with ngx buffers (ngx_buf_t) and buffer chains (ngx_chain_t) */ 7 | 8 | ngx_chain_t *ngx_chain_append_buffer(ngx_pool_t *pool, ngx_chain_t *chain, ngx_buf_t *buf); 9 | ngx_buf_t *ngx_buf_from_data(ngx_pool_t *pool, const void *data, size_t length); 10 | void debug_string( const char *msg, int length ); 11 | 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /ngx_esi_parser.c: -------------------------------------------------------------------------------- 1 | #line 1 "ngx_esi_parser.rl" 2 | /** 3 | * Copyright (c) 2008 Todd A. Fisher 4 | * see LICENSE 5 | */ 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "ngx_esi_parser.h" 11 | 12 | #ifdef DEBUG 13 | static void debug_string( const char *msg, const char *str, size_t len ) 14 | { 15 | char *pstr = esi_strndup( str, len ); 16 | printf( "%s :'%s'\n", msg, pstr ); 17 | free( pstr ); 18 | } 19 | #else 20 | #define debug_string(m,s,l) 21 | #endif 22 | 23 | /* define default callbacks */ 24 | static void 25 | esi_parser_default_start_cb( const void *data, 26 | const char *name_start, 27 | size_t name_length, 28 | ESIAttribute *attributes, 29 | void *user_data ) 30 | { 31 | } 32 | static void 33 | esi_parser_default_end_cb( const void *data, 34 | const char *name_start, 35 | size_t name_length, 36 | void *user_data ) 37 | { 38 | } 39 | static void 40 | esi_parser_default_output_cp(const void *data, 41 | size_t length, 42 | void *user_data) 43 | { 44 | } 45 | 46 | /* 47 | * flush output buffer 48 | */ 49 | static void esi_parser_flush_output( ESIParser *parser ) 50 | { 51 | if( parser->output_buffer_size > 0 ) { 52 | //debug_string( "esi_parser_flush_output:", parser->output_buffer, parser->output_buffer_size ); 53 | parser->output_handler( (void*)parser->output_buffer, parser->output_buffer_size, parser->user_data ); 54 | parser->output_buffer_size = 0; 55 | } 56 | } 57 | /* send the character to the output handler marking it 58 | * as ready for consumption, e.g. not an esi tag 59 | */ 60 | static void esi_parser_echo_char( ESIParser *parser, char ch ) 61 | { 62 | parser->output_buffer[parser->output_buffer_size++] = ch; 63 | if( parser->output_buffer_size == ESI_OUTPUT_BUFFER_SIZE ) { 64 | // flush the buffer to the consumer 65 | esi_parser_flush_output( parser ); 66 | } 67 | } 68 | /* send any buffered characters to the output handler. 69 | * This happens when we enter a case such as where the 70 | * first two characters < and e match the echobuffer_index + 1;; 75 | //debug_string( "echobuffer", parser->echobuffer, parser->echobuffer_index+1 ); 76 | //parser->output_handler( parser->echobuffer, parser->echobuffer_index+1, parser->user_data ); 77 | for( ; i < len; ++i ) { 78 | esi_parser_echo_char( parser, parser->echobuffer[i] ); 79 | } 80 | } 81 | /* 82 | * clear the buffer, no buffered characters should be emitted . 83 | * e.g. we matched an esi tag completely and all buffered characters can be tossed out 84 | */ 85 | static void esi_parser_echobuffer_clear( ESIParser *parser ) 86 | { 87 | parser->echobuffer_index = -1; 88 | } 89 | 90 | /* 91 | * add a character to the echobuffer. 92 | * this happens when we can't determine if the character is allowed to be sent to the client device 93 | * e.g. matching echobuffer_index++; 98 | 99 | if( parser->echobuffer_allocated <= parser->echobuffer_index ) { 100 | /* double the echobuffer size 101 | * we're getting some crazy input if this case ever happens 102 | */ 103 | parser->echobuffer_allocated *= 2; 104 | parser->echobuffer = (char*)realloc( parser->echobuffer, parser->echobuffer_allocated ); 105 | } 106 | parser->echobuffer[parser->echobuffer_index] = ch; 107 | // debug_string( "echo buffer", parser->echobuffer, parser->echobuffer_index+1 ); 108 | } 109 | /* 110 | * the mark boundary is not always going to be exactly on the attribute or tag name boundary 111 | * this trims characters from the left to right, advancing *ptr and reducing *len 112 | */ 113 | static void ltrim_pointer( const char **ptr, const char *bounds, size_t *len ) 114 | { 115 | // remove any spaces or = at the before the value 116 | while( (isspace( **ptr ) || 117 | **ptr == '=' || 118 | **ptr == '"' || 119 | **ptr == '<' || 120 | **ptr == '\'' ) && (*len > 0) && (*ptr != bounds) ) { 121 | (*ptr)++; 122 | (*len)--; 123 | } 124 | } 125 | /* 126 | * similar to ltrim_pointer, this walks from bounds to *ptr, reducing *len 127 | */ 128 | static void rtrim_pointer( const char **ptr, const char *bounds, size_t *len ) 129 | { 130 | bounds = (*ptr+(*len-1)); 131 | // remove any spaces or = at the before the value 132 | while( (isspace( *bounds ) || 133 | *bounds == '=' || 134 | *bounds == '"' || 135 | *bounds == '>' || 136 | *bounds == '\'') && (*len > 0) && (*ptr != bounds) ){ 137 | bounds--; 138 | (*len)--; 139 | } 140 | } 141 | 142 | #line 322 "ngx_esi_parser.rl" 143 | 144 | 145 | 146 | #line 147 "ngx_esi_parser.c" 147 | static const char _esi_eof_actions[] = { 148 | 0, 0, 0, 0, 0, 0, 0, 0, 149 | 0, 0, 0, 0, 0, 0, 0, 0, 150 | 0, 0, 0, 0, 0, 0, 0, 0, 151 | 0, 0, 0, 0, 0, 0, 0, 0, 152 | 0, 0, 0, 0, 0, 0, 0, 0, 153 | 0, 0, 0, 0, 0, 0, 0, 0, 154 | 0, 0, 0, 0, 0, 0, 0, 0, 155 | 0, 0, 0, 0, 0, 0, 0, 0, 156 | 0, 0, 0, 0, 0, 0, 0, 0, 157 | 0, 0, 0, 0, 10, 10, 10, 10 158 | }; 159 | 160 | static const int esi_start = 75; 161 | static const int esi_first_final = 75; 162 | static const int esi_error = -1; 163 | 164 | static const int esi_en_main = 75; 165 | 166 | #line 325 "ngx_esi_parser.rl" 167 | 168 | /* dup the string up to len */ 169 | char *esi_strndup( const char *str, size_t len ) 170 | { 171 | char *s = (char*)malloc(sizeof(char)*(len+1)); 172 | memcpy( s, str, len ); 173 | s[len] = '\0'; 174 | return s; 175 | } 176 | 177 | ESIAttribute *esi_attribute_new( const char *name, size_t name_length, const char *value, size_t value_length ) 178 | { 179 | ESIAttribute *attr = (ESIAttribute*)malloc(sizeof(ESIAttribute)); 180 | attr->name = esi_strndup(name, name_length); 181 | attr->value = esi_strndup(value, value_length); 182 | attr->next = NULL; 183 | return attr; 184 | } 185 | 186 | ESIAttribute *esi_attribute_copy( ESIAttribute *attribute ) 187 | { 188 | ESIAttribute *head, *nattr; 189 | if( !attribute ){ return NULL; } 190 | 191 | // copy the first attribute 192 | nattr = esi_attribute_new( attribute->name, strlen( attribute->name ), 193 | attribute->value, strlen( attribute->value ) ); 194 | // save a pointer for return 195 | head = nattr; 196 | // copy next attributes 197 | attribute = attribute->next; 198 | while( attribute ) { 199 | // set the next attribute 200 | nattr->next = esi_attribute_new( attribute->name, strlen( attribute->name ), 201 | attribute->value, strlen( attribute->value ) ); 202 | // next attribute 203 | nattr = nattr->next; 204 | attribute = attribute->next; 205 | } 206 | return head; 207 | } 208 | 209 | void esi_attribute_free( ESIAttribute *attribute ) 210 | { 211 | ESIAttribute *ptr; 212 | while( attribute ){ 213 | free( attribute->name ); 214 | free( attribute->value ); 215 | ptr = attribute->next; 216 | free( attribute ); 217 | attribute = ptr; 218 | } 219 | } 220 | 221 | ESIParser *esi_parser_new() 222 | { 223 | ESIParser *parser = (ESIParser*)malloc(sizeof(ESIParser)); 224 | parser->cs = esi_start; 225 | parser->mark = NULL; 226 | parser->tag_text = NULL; 227 | parser->attr_key = NULL; 228 | parser->attr_value = NULL; 229 | parser->overflow_data_size = 0; 230 | parser->overflow_data = NULL; 231 | 232 | /* allocate ESI_OUTPUT_BUFFER_SIZE bytes for the echobuffer */ 233 | parser->echobuffer_allocated = ESI_OUTPUT_BUFFER_SIZE; 234 | parser->echobuffer_index = -1; 235 | parser->echobuffer = (char*)malloc(sizeof(char)*parser->echobuffer_allocated); 236 | 237 | parser->attributes = NULL; 238 | parser->last = NULL; 239 | 240 | parser->start_tag_handler = esi_parser_default_start_cb; 241 | parser->end_tag_handler = esi_parser_default_end_cb; 242 | parser->output_handler = esi_parser_default_output_cp; 243 | 244 | parser->output_buffer_size = 0; 245 | memset( parser->output_buffer, 0, ESI_OUTPUT_BUFFER_SIZE ); 246 | 247 | return parser; 248 | } 249 | void esi_parser_free( ESIParser *parser ) 250 | { 251 | if( parser->overflow_data ){ free( parser->overflow_data ); } 252 | 253 | free( parser->echobuffer ); 254 | esi_attribute_free( parser->attributes ); 255 | 256 | free( parser ); 257 | } 258 | 259 | void esi_parser_output_handler( ESIParser *parser, esi_output_cb output_handler ) 260 | { 261 | parser->output_handler = output_handler; 262 | } 263 | 264 | int esi_parser_init( ESIParser *parser ) 265 | { 266 | int cs; 267 | 268 | #line 269 "ngx_esi_parser.c" 269 | { 270 | cs = esi_start; 271 | } 272 | #line 426 "ngx_esi_parser.rl" 273 | parser->prev_state = parser->cs = cs; 274 | return 0; 275 | } 276 | 277 | static int compute_offset( const char *mark, const char *data ) 278 | { 279 | if( mark ) { 280 | return mark - data; 281 | } 282 | return -1; 283 | } 284 | 285 | /* 286 | * scans the data buffer for a start sequence /<$/, /cs; 340 | const char *p = data; 341 | const char *eof = NULL; // ragel 6.x compat 342 | const char *pe = data + length; 343 | int pindex; 344 | 345 | if( length == 0 || data == 0 ){ return cs; } 346 | 347 | /* scan data for any 'overflow_data && parser->overflow_data_size > 0 ) { 360 | 361 | // recompute mark, tag_text, attr_key, and attr_value since they all exist within overflow_data 362 | int mark_offset = compute_offset( parser->mark, parser->overflow_data ); 363 | int tag_text_offset = compute_offset( parser->tag_text, parser->overflow_data ); 364 | int attr_key_offset = compute_offset( parser->attr_key, parser->overflow_data ); 365 | int attr_value_offset = compute_offset( parser->attr_value, parser->overflow_data ); 366 | 367 | parser->overflow_data = (char*)realloc( parser->overflow_data, sizeof(char)*(parser->overflow_data_size+length) ); 368 | memcpy( parser->overflow_data+parser->overflow_data_size, data, length ); 369 | 370 | p = parser->overflow_data + parser->overflow_data_size; 371 | 372 | // in our new memory space mark will now be 373 | parser->mark = ( mark_offset >= 0 ) ? parser->overflow_data + mark_offset : NULL; 374 | parser->tag_text = ( tag_text_offset >= 0 ) ? parser->overflow_data + tag_text_offset : NULL; 375 | parser->attr_key = ( attr_key_offset >= 0 ) ? parser->overflow_data + attr_key_offset : NULL; 376 | parser->attr_value = ( attr_value_offset >= 0 ) ? parser->overflow_data + attr_value_offset : NULL; 377 | 378 | data = parser->overflow_data; 379 | parser->overflow_data_size = length = length + parser->overflow_data_size; 380 | // printf( "grow overflow data: %ld\n", parser->overflow_data_size ); 381 | pe = data + length; 382 | 383 | } 384 | 385 | if( !parser->mark ) { 386 | parser->mark = p; 387 | } 388 | 389 | // printf( "cs: %d, ", cs ); 390 | 391 | 392 | #line 393 "ngx_esi_parser.c" 393 | { 394 | if ( p == pe ) 395 | goto _test_eof; 396 | _resume: 397 | switch ( cs ) { 398 | case 75: 399 | if ( (*p) == 60 ) 400 | goto tr1; 401 | goto tr0; 402 | case 0: 403 | if ( (*p) == 60 ) 404 | goto tr1; 405 | goto tr0; 406 | case 1: 407 | switch( (*p) ) { 408 | case 47: goto tr2; 409 | case 60: goto tr1; 410 | case 101: goto tr3; 411 | } 412 | goto tr0; 413 | case 2: 414 | switch( (*p) ) { 415 | case 60: goto tr1; 416 | case 101: goto tr4; 417 | } 418 | goto tr0; 419 | case 3: 420 | switch( (*p) ) { 421 | case 60: goto tr1; 422 | case 115: goto tr5; 423 | } 424 | goto tr0; 425 | case 4: 426 | switch( (*p) ) { 427 | case 60: goto tr1; 428 | case 105: goto tr6; 429 | } 430 | goto tr0; 431 | case 5: 432 | switch( (*p) ) { 433 | case 58: goto tr7; 434 | case 60: goto tr1; 435 | } 436 | goto tr0; 437 | case 6: 438 | if ( (*p) == 60 ) 439 | goto tr1; 440 | if ( 97 <= (*p) && (*p) <= 122 ) 441 | goto tr8; 442 | goto tr0; 443 | case 7: 444 | switch( (*p) ) { 445 | case 60: goto tr1; 446 | case 62: goto tr9; 447 | } 448 | if ( 97 <= (*p) && (*p) <= 122 ) 449 | goto tr8; 450 | goto tr0; 451 | case 8: 452 | switch( (*p) ) { 453 | case 60: goto tr1; 454 | case 115: goto tr10; 455 | } 456 | goto tr0; 457 | case 9: 458 | switch( (*p) ) { 459 | case 60: goto tr1; 460 | case 105: goto tr11; 461 | } 462 | goto tr0; 463 | case 10: 464 | switch( (*p) ) { 465 | case 58: goto tr12; 466 | case 60: goto tr1; 467 | } 468 | goto tr0; 469 | case 11: 470 | if ( (*p) == 60 ) 471 | goto tr1; 472 | if ( 97 <= (*p) && (*p) <= 122 ) 473 | goto tr13; 474 | goto tr0; 475 | case 12: 476 | switch( (*p) ) { 477 | case 32: goto tr14; 478 | case 60: goto tr1; 479 | case 62: goto tr15; 480 | } 481 | if ( (*p) > 13 ) { 482 | if ( 97 <= (*p) && (*p) <= 122 ) 483 | goto tr13; 484 | } else if ( (*p) >= 9 ) 485 | goto tr14; 486 | goto tr0; 487 | case 13: 488 | switch( (*p) ) { 489 | case 32: goto tr16; 490 | case 45: goto tr17; 491 | case 47: goto tr18; 492 | case 60: goto tr1; 493 | case 62: goto tr19; 494 | case 95: goto tr17; 495 | } 496 | if ( (*p) < 65 ) { 497 | if ( 9 <= (*p) && (*p) <= 13 ) 498 | goto tr16; 499 | } else if ( (*p) > 90 ) { 500 | if ( 97 <= (*p) && (*p) <= 122 ) 501 | goto tr17; 502 | } else 503 | goto tr17; 504 | goto tr0; 505 | case 14: 506 | switch( (*p) ) { 507 | case 32: goto tr16; 508 | case 45: goto tr17; 509 | case 47: goto tr18; 510 | case 60: goto tr1; 511 | case 95: goto tr17; 512 | } 513 | if ( (*p) < 65 ) { 514 | if ( 9 <= (*p) && (*p) <= 13 ) 515 | goto tr16; 516 | } else if ( (*p) > 90 ) { 517 | if ( 97 <= (*p) && (*p) <= 122 ) 518 | goto tr17; 519 | } else 520 | goto tr17; 521 | goto tr0; 522 | case 15: 523 | switch( (*p) ) { 524 | case 45: goto tr17; 525 | case 60: goto tr1; 526 | case 61: goto tr20; 527 | case 95: goto tr17; 528 | } 529 | if ( (*p) < 65 ) { 530 | if ( 48 <= (*p) && (*p) <= 57 ) 531 | goto tr17; 532 | } else if ( (*p) > 90 ) { 533 | if ( 97 <= (*p) && (*p) <= 122 ) 534 | goto tr17; 535 | } else 536 | goto tr17; 537 | goto tr0; 538 | case 16: 539 | switch( (*p) ) { 540 | case 32: goto tr21; 541 | case 34: goto tr22; 542 | case 39: goto tr23; 543 | case 60: goto tr1; 544 | } 545 | if ( 9 <= (*p) && (*p) <= 13 ) 546 | goto tr21; 547 | goto tr0; 548 | case 17: 549 | switch( (*p) ) { 550 | case 34: goto tr24; 551 | case 60: goto tr25; 552 | case 92: goto tr26; 553 | } 554 | goto tr22; 555 | case 18: 556 | switch( (*p) ) { 557 | case 34: goto tr24; 558 | case 47: goto tr27; 559 | case 60: goto tr25; 560 | case 92: goto tr26; 561 | case 101: goto tr28; 562 | } 563 | goto tr22; 564 | case 19: 565 | switch( (*p) ) { 566 | case 34: goto tr24; 567 | case 60: goto tr25; 568 | case 92: goto tr26; 569 | case 101: goto tr29; 570 | } 571 | goto tr22; 572 | case 20: 573 | switch( (*p) ) { 574 | case 34: goto tr30; 575 | case 60: goto tr25; 576 | case 92: goto tr26; 577 | } 578 | goto tr22; 579 | case 21: 580 | switch( (*p) ) { 581 | case 32: goto tr31; 582 | case 34: goto tr24; 583 | case 45: goto tr32; 584 | case 47: goto tr33; 585 | case 60: goto tr25; 586 | case 62: goto tr34; 587 | case 92: goto tr26; 588 | case 95: goto tr32; 589 | } 590 | if ( (*p) < 65 ) { 591 | if ( 9 <= (*p) && (*p) <= 13 ) 592 | goto tr31; 593 | } else if ( (*p) > 90 ) { 594 | if ( 97 <= (*p) && (*p) <= 122 ) 595 | goto tr32; 596 | } else 597 | goto tr32; 598 | goto tr22; 599 | case 22: 600 | switch( (*p) ) { 601 | case 32: goto tr31; 602 | case 34: goto tr24; 603 | case 45: goto tr32; 604 | case 47: goto tr33; 605 | case 60: goto tr25; 606 | case 92: goto tr26; 607 | case 95: goto tr32; 608 | } 609 | if ( (*p) < 65 ) { 610 | if ( 9 <= (*p) && (*p) <= 13 ) 611 | goto tr31; 612 | } else if ( (*p) > 90 ) { 613 | if ( 97 <= (*p) && (*p) <= 122 ) 614 | goto tr32; 615 | } else 616 | goto tr32; 617 | goto tr22; 618 | case 23: 619 | switch( (*p) ) { 620 | case 34: goto tr24; 621 | case 45: goto tr32; 622 | case 60: goto tr25; 623 | case 61: goto tr35; 624 | case 92: goto tr26; 625 | case 95: goto tr32; 626 | } 627 | if ( (*p) < 65 ) { 628 | if ( 48 <= (*p) && (*p) <= 57 ) 629 | goto tr32; 630 | } else if ( (*p) > 90 ) { 631 | if ( 97 <= (*p) && (*p) <= 122 ) 632 | goto tr32; 633 | } else 634 | goto tr32; 635 | goto tr22; 636 | case 24: 637 | switch( (*p) ) { 638 | case 32: goto tr36; 639 | case 34: goto tr30; 640 | case 39: goto tr37; 641 | case 60: goto tr25; 642 | case 92: goto tr26; 643 | } 644 | if ( 9 <= (*p) && (*p) <= 13 ) 645 | goto tr36; 646 | goto tr22; 647 | case 25: 648 | switch( (*p) ) { 649 | case 34: goto tr38; 650 | case 39: goto tr30; 651 | case 60: goto tr39; 652 | case 92: goto tr40; 653 | } 654 | goto tr37; 655 | case 26: 656 | switch( (*p) ) { 657 | case 32: goto tr41; 658 | case 39: goto tr24; 659 | case 45: goto tr42; 660 | case 47: goto tr43; 661 | case 60: goto tr44; 662 | case 62: goto tr45; 663 | case 92: goto tr46; 664 | case 95: goto tr42; 665 | } 666 | if ( (*p) < 65 ) { 667 | if ( 9 <= (*p) && (*p) <= 13 ) 668 | goto tr41; 669 | } else if ( (*p) > 90 ) { 670 | if ( 97 <= (*p) && (*p) <= 122 ) 671 | goto tr42; 672 | } else 673 | goto tr42; 674 | goto tr23; 675 | case 27: 676 | switch( (*p) ) { 677 | case 39: goto tr24; 678 | case 60: goto tr44; 679 | case 92: goto tr46; 680 | } 681 | goto tr23; 682 | case 28: 683 | switch( (*p) ) { 684 | case 39: goto tr24; 685 | case 47: goto tr47; 686 | case 60: goto tr44; 687 | case 92: goto tr46; 688 | case 101: goto tr48; 689 | } 690 | goto tr23; 691 | case 29: 692 | switch( (*p) ) { 693 | case 39: goto tr24; 694 | case 60: goto tr44; 695 | case 92: goto tr46; 696 | case 101: goto tr49; 697 | } 698 | goto tr23; 699 | case 30: 700 | switch( (*p) ) { 701 | case 39: goto tr38; 702 | case 60: goto tr44; 703 | case 92: goto tr46; 704 | } 705 | goto tr23; 706 | case 31: 707 | switch( (*p) ) { 708 | case 39: goto tr24; 709 | case 60: goto tr44; 710 | case 92: goto tr46; 711 | case 115: goto tr50; 712 | } 713 | goto tr23; 714 | case 32: 715 | switch( (*p) ) { 716 | case 39: goto tr24; 717 | case 60: goto tr44; 718 | case 92: goto tr46; 719 | case 105: goto tr51; 720 | } 721 | goto tr23; 722 | case 33: 723 | switch( (*p) ) { 724 | case 39: goto tr24; 725 | case 58: goto tr52; 726 | case 60: goto tr44; 727 | case 92: goto tr46; 728 | } 729 | goto tr23; 730 | case 34: 731 | switch( (*p) ) { 732 | case 39: goto tr24; 733 | case 60: goto tr44; 734 | case 92: goto tr46; 735 | } 736 | if ( 97 <= (*p) && (*p) <= 122 ) 737 | goto tr53; 738 | goto tr23; 739 | case 35: 740 | switch( (*p) ) { 741 | case 39: goto tr24; 742 | case 60: goto tr44; 743 | case 62: goto tr54; 744 | case 92: goto tr46; 745 | } 746 | if ( 97 <= (*p) && (*p) <= 122 ) 747 | goto tr53; 748 | goto tr23; 749 | case 36: 750 | switch( (*p) ) { 751 | case 39: goto tr24; 752 | case 60: goto tr44; 753 | case 92: goto tr46; 754 | case 115: goto tr55; 755 | } 756 | goto tr23; 757 | case 37: 758 | switch( (*p) ) { 759 | case 39: goto tr24; 760 | case 60: goto tr44; 761 | case 92: goto tr46; 762 | case 105: goto tr56; 763 | } 764 | goto tr23; 765 | case 38: 766 | switch( (*p) ) { 767 | case 39: goto tr24; 768 | case 58: goto tr57; 769 | case 60: goto tr44; 770 | case 92: goto tr46; 771 | } 772 | goto tr23; 773 | case 39: 774 | switch( (*p) ) { 775 | case 39: goto tr24; 776 | case 60: goto tr44; 777 | case 92: goto tr46; 778 | } 779 | if ( 97 <= (*p) && (*p) <= 122 ) 780 | goto tr58; 781 | goto tr23; 782 | case 40: 783 | switch( (*p) ) { 784 | case 32: goto tr59; 785 | case 39: goto tr24; 786 | case 60: goto tr44; 787 | case 62: goto tr60; 788 | case 92: goto tr46; 789 | } 790 | if ( (*p) > 13 ) { 791 | if ( 97 <= (*p) && (*p) <= 122 ) 792 | goto tr58; 793 | } else if ( (*p) >= 9 ) 794 | goto tr59; 795 | goto tr23; 796 | case 76: 797 | switch( (*p) ) { 798 | case 39: goto tr100; 799 | case 60: goto tr101; 800 | case 92: goto tr102; 801 | } 802 | goto tr99; 803 | case 41: 804 | switch( (*p) ) { 805 | case 32: goto tr41; 806 | case 39: goto tr24; 807 | case 45: goto tr42; 808 | case 47: goto tr43; 809 | case 60: goto tr44; 810 | case 92: goto tr46; 811 | case 95: goto tr42; 812 | } 813 | if ( (*p) < 65 ) { 814 | if ( 9 <= (*p) && (*p) <= 13 ) 815 | goto tr41; 816 | } else if ( (*p) > 90 ) { 817 | if ( 97 <= (*p) && (*p) <= 122 ) 818 | goto tr42; 819 | } else 820 | goto tr42; 821 | goto tr23; 822 | case 42: 823 | switch( (*p) ) { 824 | case 39: goto tr24; 825 | case 45: goto tr42; 826 | case 60: goto tr44; 827 | case 61: goto tr61; 828 | case 92: goto tr46; 829 | case 95: goto tr42; 830 | } 831 | if ( (*p) < 65 ) { 832 | if ( 48 <= (*p) && (*p) <= 57 ) 833 | goto tr42; 834 | } else if ( (*p) > 90 ) { 835 | if ( 97 <= (*p) && (*p) <= 122 ) 836 | goto tr42; 837 | } else 838 | goto tr42; 839 | goto tr23; 840 | case 43: 841 | switch( (*p) ) { 842 | case 32: goto tr62; 843 | case 34: goto tr37; 844 | case 39: goto tr38; 845 | case 60: goto tr44; 846 | case 92: goto tr46; 847 | } 848 | if ( 9 <= (*p) && (*p) <= 13 ) 849 | goto tr62; 850 | goto tr23; 851 | case 44: 852 | switch( (*p) ) { 853 | case 39: goto tr24; 854 | case 60: goto tr44; 855 | case 62: goto tr63; 856 | case 92: goto tr46; 857 | } 858 | goto tr23; 859 | case 45: 860 | switch( (*p) ) { 861 | case 34: goto tr38; 862 | case 39: goto tr30; 863 | case 47: goto tr64; 864 | case 60: goto tr39; 865 | case 92: goto tr40; 866 | case 101: goto tr65; 867 | } 868 | goto tr37; 869 | case 46: 870 | switch( (*p) ) { 871 | case 34: goto tr38; 872 | case 39: goto tr30; 873 | case 60: goto tr39; 874 | case 92: goto tr40; 875 | case 101: goto tr66; 876 | } 877 | goto tr37; 878 | case 47: 879 | switch( (*p) ) { 880 | case 34: goto tr67; 881 | case 39: goto tr67; 882 | case 60: goto tr39; 883 | case 92: goto tr40; 884 | } 885 | goto tr37; 886 | case 48: 887 | switch( (*p) ) { 888 | case 32: goto tr68; 889 | case 34: goto tr38; 890 | case 39: goto tr30; 891 | case 45: goto tr69; 892 | case 47: goto tr70; 893 | case 60: goto tr39; 894 | case 62: goto tr71; 895 | case 92: goto tr40; 896 | case 95: goto tr69; 897 | } 898 | if ( (*p) < 65 ) { 899 | if ( 9 <= (*p) && (*p) <= 13 ) 900 | goto tr68; 901 | } else if ( (*p) > 90 ) { 902 | if ( 97 <= (*p) && (*p) <= 122 ) 903 | goto tr69; 904 | } else 905 | goto tr69; 906 | goto tr37; 907 | case 49: 908 | switch( (*p) ) { 909 | case 32: goto tr68; 910 | case 34: goto tr38; 911 | case 39: goto tr30; 912 | case 45: goto tr69; 913 | case 47: goto tr70; 914 | case 60: goto tr39; 915 | case 92: goto tr40; 916 | case 95: goto tr69; 917 | } 918 | if ( (*p) < 65 ) { 919 | if ( 9 <= (*p) && (*p) <= 13 ) 920 | goto tr68; 921 | } else if ( (*p) > 90 ) { 922 | if ( 97 <= (*p) && (*p) <= 122 ) 923 | goto tr69; 924 | } else 925 | goto tr69; 926 | goto tr37; 927 | case 50: 928 | switch( (*p) ) { 929 | case 34: goto tr38; 930 | case 39: goto tr30; 931 | case 45: goto tr69; 932 | case 60: goto tr39; 933 | case 61: goto tr72; 934 | case 92: goto tr40; 935 | case 95: goto tr69; 936 | } 937 | if ( (*p) < 65 ) { 938 | if ( 48 <= (*p) && (*p) <= 57 ) 939 | goto tr69; 940 | } else if ( (*p) > 90 ) { 941 | if ( 97 <= (*p) && (*p) <= 122 ) 942 | goto tr69; 943 | } else 944 | goto tr69; 945 | goto tr37; 946 | case 51: 947 | switch( (*p) ) { 948 | case 32: goto tr73; 949 | case 34: goto tr67; 950 | case 39: goto tr67; 951 | case 60: goto tr39; 952 | case 92: goto tr40; 953 | } 954 | if ( 9 <= (*p) && (*p) <= 13 ) 955 | goto tr73; 956 | goto tr37; 957 | case 52: 958 | switch( (*p) ) { 959 | case 34: goto tr38; 960 | case 39: goto tr30; 961 | case 60: goto tr39; 962 | case 62: goto tr74; 963 | case 92: goto tr40; 964 | } 965 | goto tr37; 966 | case 77: 967 | switch( (*p) ) { 968 | case 34: goto tr104; 969 | case 39: goto tr105; 970 | case 60: goto tr106; 971 | case 92: goto tr107; 972 | } 973 | goto tr103; 974 | case 53: 975 | switch( (*p) ) { 976 | case 34: goto tr38; 977 | case 39: goto tr30; 978 | case 60: goto tr39; 979 | case 92: goto tr40; 980 | case 115: goto tr75; 981 | } 982 | goto tr37; 983 | case 54: 984 | switch( (*p) ) { 985 | case 34: goto tr38; 986 | case 39: goto tr30; 987 | case 60: goto tr39; 988 | case 92: goto tr40; 989 | case 105: goto tr76; 990 | } 991 | goto tr37; 992 | case 55: 993 | switch( (*p) ) { 994 | case 34: goto tr38; 995 | case 39: goto tr30; 996 | case 58: goto tr77; 997 | case 60: goto tr39; 998 | case 92: goto tr40; 999 | } 1000 | goto tr37; 1001 | case 56: 1002 | switch( (*p) ) { 1003 | case 34: goto tr38; 1004 | case 39: goto tr30; 1005 | case 60: goto tr39; 1006 | case 92: goto tr40; 1007 | } 1008 | if ( 97 <= (*p) && (*p) <= 122 ) 1009 | goto tr78; 1010 | goto tr37; 1011 | case 57: 1012 | switch( (*p) ) { 1013 | case 34: goto tr38; 1014 | case 39: goto tr30; 1015 | case 60: goto tr39; 1016 | case 62: goto tr79; 1017 | case 92: goto tr40; 1018 | } 1019 | if ( 97 <= (*p) && (*p) <= 122 ) 1020 | goto tr78; 1021 | goto tr37; 1022 | case 58: 1023 | switch( (*p) ) { 1024 | case 34: goto tr38; 1025 | case 39: goto tr30; 1026 | case 60: goto tr39; 1027 | case 92: goto tr40; 1028 | case 115: goto tr80; 1029 | } 1030 | goto tr37; 1031 | case 59: 1032 | switch( (*p) ) { 1033 | case 34: goto tr38; 1034 | case 39: goto tr30; 1035 | case 60: goto tr39; 1036 | case 92: goto tr40; 1037 | case 105: goto tr81; 1038 | } 1039 | goto tr37; 1040 | case 60: 1041 | switch( (*p) ) { 1042 | case 34: goto tr38; 1043 | case 39: goto tr30; 1044 | case 58: goto tr82; 1045 | case 60: goto tr39; 1046 | case 92: goto tr40; 1047 | } 1048 | goto tr37; 1049 | case 61: 1050 | switch( (*p) ) { 1051 | case 34: goto tr38; 1052 | case 39: goto tr30; 1053 | case 60: goto tr39; 1054 | case 92: goto tr40; 1055 | } 1056 | if ( 97 <= (*p) && (*p) <= 122 ) 1057 | goto tr83; 1058 | goto tr37; 1059 | case 62: 1060 | switch( (*p) ) { 1061 | case 32: goto tr84; 1062 | case 34: goto tr38; 1063 | case 39: goto tr30; 1064 | case 60: goto tr39; 1065 | case 62: goto tr85; 1066 | case 92: goto tr40; 1067 | } 1068 | if ( (*p) > 13 ) { 1069 | if ( 97 <= (*p) && (*p) <= 122 ) 1070 | goto tr83; 1071 | } else if ( (*p) >= 9 ) 1072 | goto tr84; 1073 | goto tr37; 1074 | case 63: 1075 | switch( (*p) ) { 1076 | case 34: goto tr24; 1077 | case 60: goto tr25; 1078 | case 62: goto tr86; 1079 | case 92: goto tr26; 1080 | } 1081 | goto tr22; 1082 | case 78: 1083 | switch( (*p) ) { 1084 | case 34: goto tr100; 1085 | case 60: goto tr109; 1086 | case 92: goto tr110; 1087 | } 1088 | goto tr108; 1089 | case 64: 1090 | switch( (*p) ) { 1091 | case 34: goto tr24; 1092 | case 60: goto tr25; 1093 | case 92: goto tr26; 1094 | case 115: goto tr87; 1095 | } 1096 | goto tr22; 1097 | case 65: 1098 | switch( (*p) ) { 1099 | case 34: goto tr24; 1100 | case 60: goto tr25; 1101 | case 92: goto tr26; 1102 | case 105: goto tr88; 1103 | } 1104 | goto tr22; 1105 | case 66: 1106 | switch( (*p) ) { 1107 | case 34: goto tr24; 1108 | case 58: goto tr89; 1109 | case 60: goto tr25; 1110 | case 92: goto tr26; 1111 | } 1112 | goto tr22; 1113 | case 67: 1114 | switch( (*p) ) { 1115 | case 34: goto tr24; 1116 | case 60: goto tr25; 1117 | case 92: goto tr26; 1118 | } 1119 | if ( 97 <= (*p) && (*p) <= 122 ) 1120 | goto tr90; 1121 | goto tr22; 1122 | case 68: 1123 | switch( (*p) ) { 1124 | case 34: goto tr24; 1125 | case 60: goto tr25; 1126 | case 62: goto tr91; 1127 | case 92: goto tr26; 1128 | } 1129 | if ( 97 <= (*p) && (*p) <= 122 ) 1130 | goto tr90; 1131 | goto tr22; 1132 | case 69: 1133 | switch( (*p) ) { 1134 | case 34: goto tr24; 1135 | case 60: goto tr25; 1136 | case 92: goto tr26; 1137 | case 115: goto tr92; 1138 | } 1139 | goto tr22; 1140 | case 70: 1141 | switch( (*p) ) { 1142 | case 34: goto tr24; 1143 | case 60: goto tr25; 1144 | case 92: goto tr26; 1145 | case 105: goto tr93; 1146 | } 1147 | goto tr22; 1148 | case 71: 1149 | switch( (*p) ) { 1150 | case 34: goto tr24; 1151 | case 58: goto tr94; 1152 | case 60: goto tr25; 1153 | case 92: goto tr26; 1154 | } 1155 | goto tr22; 1156 | case 72: 1157 | switch( (*p) ) { 1158 | case 34: goto tr24; 1159 | case 60: goto tr25; 1160 | case 92: goto tr26; 1161 | } 1162 | if ( 97 <= (*p) && (*p) <= 122 ) 1163 | goto tr95; 1164 | goto tr22; 1165 | case 73: 1166 | switch( (*p) ) { 1167 | case 32: goto tr96; 1168 | case 34: goto tr24; 1169 | case 60: goto tr25; 1170 | case 62: goto tr97; 1171 | case 92: goto tr26; 1172 | } 1173 | if ( (*p) > 13 ) { 1174 | if ( 97 <= (*p) && (*p) <= 122 ) 1175 | goto tr95; 1176 | } else if ( (*p) >= 9 ) 1177 | goto tr96; 1178 | goto tr22; 1179 | case 74: 1180 | switch( (*p) ) { 1181 | case 60: goto tr1; 1182 | case 62: goto tr98; 1183 | } 1184 | goto tr0; 1185 | case 79: 1186 | if ( (*p) == 60 ) 1187 | goto tr112; 1188 | goto tr111; 1189 | } 1190 | 1191 | tr0: cs = 0; goto f0; 1192 | tr9: cs = 0; goto f2; 1193 | tr111: cs = 0; goto f10; 1194 | tr1: cs = 1; goto f1; 1195 | tr112: cs = 1; goto f12; 1196 | tr2: cs = 2; goto f0; 1197 | tr4: cs = 3; goto f0; 1198 | tr5: cs = 4; goto f0; 1199 | tr6: cs = 5; goto f0; 1200 | tr7: cs = 6; goto f0; 1201 | tr8: cs = 7; goto f0; 1202 | tr3: cs = 8; goto f0; 1203 | tr10: cs = 9; goto f0; 1204 | tr11: cs = 10; goto f0; 1205 | tr12: cs = 11; goto f0; 1206 | tr13: cs = 12; goto f0; 1207 | tr14: cs = 13; goto f3; 1208 | tr24: cs = 13; goto f7; 1209 | tr100: cs = 13; goto f11; 1210 | tr16: cs = 14; goto f0; 1211 | tr17: cs = 15; goto f0; 1212 | tr21: cs = 16; goto f0; 1213 | tr20: cs = 16; goto f6; 1214 | tr22: cs = 17; goto f0; 1215 | tr91: cs = 17; goto f2; 1216 | tr108: cs = 17; goto f10; 1217 | tr25: cs = 18; goto f1; 1218 | tr109: cs = 18; goto f12; 1219 | tr27: cs = 19; goto f0; 1220 | tr26: cs = 20; goto f0; 1221 | tr110: cs = 20; goto f10; 1222 | tr96: cs = 21; goto f3; 1223 | tr30: cs = 21; goto f7; 1224 | tr105: cs = 21; goto f11; 1225 | tr31: cs = 22; goto f0; 1226 | tr32: cs = 23; goto f0; 1227 | tr36: cs = 24; goto f0; 1228 | tr35: cs = 24; goto f6; 1229 | tr37: cs = 25; goto f0; 1230 | tr79: cs = 25; goto f2; 1231 | tr103: cs = 25; goto f10; 1232 | tr59: cs = 26; goto f3; 1233 | tr38: cs = 26; goto f7; 1234 | tr104: cs = 26; goto f11; 1235 | tr23: cs = 27; goto f0; 1236 | tr54: cs = 27; goto f2; 1237 | tr99: cs = 27; goto f10; 1238 | tr44: cs = 28; goto f1; 1239 | tr101: cs = 28; goto f12; 1240 | tr47: cs = 29; goto f0; 1241 | tr46: cs = 30; goto f0; 1242 | tr102: cs = 30; goto f10; 1243 | tr49: cs = 31; goto f0; 1244 | tr50: cs = 32; goto f0; 1245 | tr51: cs = 33; goto f0; 1246 | tr52: cs = 34; goto f0; 1247 | tr53: cs = 35; goto f0; 1248 | tr48: cs = 36; goto f0; 1249 | tr55: cs = 37; goto f0; 1250 | tr56: cs = 38; goto f0; 1251 | tr57: cs = 39; goto f0; 1252 | tr58: cs = 40; goto f0; 1253 | tr41: cs = 41; goto f0; 1254 | tr42: cs = 42; goto f0; 1255 | tr62: cs = 43; goto f0; 1256 | tr61: cs = 43; goto f6; 1257 | tr43: cs = 44; goto f0; 1258 | tr39: cs = 45; goto f1; 1259 | tr106: cs = 45; goto f12; 1260 | tr64: cs = 46; goto f0; 1261 | tr40: cs = 47; goto f0; 1262 | tr107: cs = 47; goto f10; 1263 | tr84: cs = 48; goto f3; 1264 | tr67: cs = 48; goto f7; 1265 | tr68: cs = 49; goto f0; 1266 | tr69: cs = 50; goto f0; 1267 | tr73: cs = 51; goto f0; 1268 | tr72: cs = 51; goto f6; 1269 | tr70: cs = 52; goto f0; 1270 | tr66: cs = 53; goto f0; 1271 | tr75: cs = 54; goto f0; 1272 | tr76: cs = 55; goto f0; 1273 | tr77: cs = 56; goto f0; 1274 | tr78: cs = 57; goto f0; 1275 | tr65: cs = 58; goto f0; 1276 | tr80: cs = 59; goto f0; 1277 | tr81: cs = 60; goto f0; 1278 | tr82: cs = 61; goto f0; 1279 | tr83: cs = 62; goto f0; 1280 | tr33: cs = 63; goto f0; 1281 | tr29: cs = 64; goto f0; 1282 | tr87: cs = 65; goto f0; 1283 | tr88: cs = 66; goto f0; 1284 | tr89: cs = 67; goto f0; 1285 | tr90: cs = 68; goto f0; 1286 | tr28: cs = 69; goto f0; 1287 | tr92: cs = 70; goto f0; 1288 | tr93: cs = 71; goto f0; 1289 | tr94: cs = 72; goto f0; 1290 | tr95: cs = 73; goto f0; 1291 | tr18: cs = 74; goto f0; 1292 | tr60: cs = 76; goto f4; 1293 | tr45: cs = 76; goto f5; 1294 | tr63: cs = 76; goto f8; 1295 | tr85: cs = 77; goto f4; 1296 | tr71: cs = 77; goto f5; 1297 | tr74: cs = 77; goto f8; 1298 | tr97: cs = 78; goto f4; 1299 | tr34: cs = 78; goto f5; 1300 | tr86: cs = 78; goto f8; 1301 | tr15: cs = 79; goto f4; 1302 | tr19: cs = 79; goto f5; 1303 | tr98: cs = 79; goto f8; 1304 | 1305 | f0: 1306 | #line 296 "ngx_esi_parser.rl" 1307 | { 1308 | //printf( "[%c:%d],", *p, cs ); 1309 | switch( cs ) { 1310 | case 0: /* non matching state */ 1311 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1312 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1313 | /* send the echo buffer */ 1314 | esi_parser_echo_buffer( parser ); 1315 | } 1316 | /* send the current character */ 1317 | esi_parser_echo_char( parser, *p ); 1318 | } 1319 | /* clear the echo buffer */ 1320 | esi_parser_echobuffer_clear( parser ); 1321 | break; 1322 | default: 1323 | /* append to the echo buffer */ 1324 | esi_parser_concat_to_echobuffer( parser, *p ); 1325 | } 1326 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1327 | is state 12 and 7 1328 | */ 1329 | parser->prev_state = cs; 1330 | } 1331 | goto _again; 1332 | f1: 1333 | #line 296 "ngx_esi_parser.rl" 1334 | { 1335 | //printf( "[%c:%d],", *p, cs ); 1336 | switch( cs ) { 1337 | case 0: /* non matching state */ 1338 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1339 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1340 | /* send the echo buffer */ 1341 | esi_parser_echo_buffer( parser ); 1342 | } 1343 | /* send the current character */ 1344 | esi_parser_echo_char( parser, *p ); 1345 | } 1346 | /* clear the echo buffer */ 1347 | esi_parser_echobuffer_clear( parser ); 1348 | break; 1349 | default: 1350 | /* append to the echo buffer */ 1351 | esi_parser_concat_to_echobuffer( parser, *p ); 1352 | } 1353 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1354 | is state 12 and 7 1355 | */ 1356 | parser->prev_state = cs; 1357 | } 1358 | #line 144 "ngx_esi_parser.rl" 1359 | { 1360 | parser->mark = p; 1361 | //debug_string( "begin", p, 1 ); 1362 | } 1363 | goto _again; 1364 | f10: 1365 | #line 296 "ngx_esi_parser.rl" 1366 | { 1367 | //printf( "[%c:%d],", *p, cs ); 1368 | switch( cs ) { 1369 | case 0: /* non matching state */ 1370 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1371 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1372 | /* send the echo buffer */ 1373 | esi_parser_echo_buffer( parser ); 1374 | } 1375 | /* send the current character */ 1376 | esi_parser_echo_char( parser, *p ); 1377 | } 1378 | /* clear the echo buffer */ 1379 | esi_parser_echobuffer_clear( parser ); 1380 | break; 1381 | default: 1382 | /* append to the echo buffer */ 1383 | esi_parser_concat_to_echobuffer( parser, *p ); 1384 | } 1385 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1386 | is state 12 and 7 1387 | */ 1388 | parser->prev_state = cs; 1389 | } 1390 | #line 148 "ngx_esi_parser.rl" 1391 | { 1392 | // printf( "finish\n" ); 1393 | } 1394 | goto _again; 1395 | f3: 1396 | #line 296 "ngx_esi_parser.rl" 1397 | { 1398 | //printf( "[%c:%d],", *p, cs ); 1399 | switch( cs ) { 1400 | case 0: /* non matching state */ 1401 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1402 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1403 | /* send the echo buffer */ 1404 | esi_parser_echo_buffer( parser ); 1405 | } 1406 | /* send the current character */ 1407 | esi_parser_echo_char( parser, *p ); 1408 | } 1409 | /* clear the echo buffer */ 1410 | esi_parser_echobuffer_clear( parser ); 1411 | break; 1412 | default: 1413 | /* append to the echo buffer */ 1414 | esi_parser_concat_to_echobuffer( parser, *p ); 1415 | } 1416 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1417 | is state 12 and 7 1418 | */ 1419 | parser->prev_state = cs; 1420 | } 1421 | #line 153 "ngx_esi_parser.rl" 1422 | { 1423 | parser->tag_text = parser->mark+1; 1424 | parser->tag_text_length = p - (parser->mark+1); 1425 | parser->mark = p; 1426 | } 1427 | goto _again; 1428 | f8: 1429 | #line 296 "ngx_esi_parser.rl" 1430 | { 1431 | //printf( "[%c:%d],", *p, cs ); 1432 | switch( cs ) { 1433 | case 0: /* non matching state */ 1434 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1435 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1436 | /* send the echo buffer */ 1437 | esi_parser_echo_buffer( parser ); 1438 | } 1439 | /* send the current character */ 1440 | esi_parser_echo_char( parser, *p ); 1441 | } 1442 | /* clear the echo buffer */ 1443 | esi_parser_echobuffer_clear( parser ); 1444 | break; 1445 | default: 1446 | /* append to the echo buffer */ 1447 | esi_parser_concat_to_echobuffer( parser, *p ); 1448 | } 1449 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1450 | is state 12 and 7 1451 | */ 1452 | parser->prev_state = cs; 1453 | } 1454 | #line 160 "ngx_esi_parser.rl" 1455 | { 1456 | /* trim the tag text */ 1457 | ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 1458 | rtrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 1459 | 1460 | /* send the start tag and end tag message */ 1461 | esi_parser_flush_output( parser ); 1462 | parser->start_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->attributes, parser->user_data ); 1463 | esi_parser_flush_output( parser ); 1464 | parser->end_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->user_data ); 1465 | esi_parser_flush_output( parser ); 1466 | 1467 | if( parser->attributes ) { 1468 | esi_attribute_free( parser->attributes ); 1469 | parser->attributes = NULL; 1470 | } 1471 | 1472 | /* mark the position */ 1473 | parser->tag_text = NULL; 1474 | parser->tag_text_length = 0; 1475 | parser->mark = p; 1476 | 1477 | /* clear out the echo buffer */ 1478 | esi_parser_echobuffer_clear( parser ); 1479 | } 1480 | goto _again; 1481 | f5: 1482 | #line 296 "ngx_esi_parser.rl" 1483 | { 1484 | //printf( "[%c:%d],", *p, cs ); 1485 | switch( cs ) { 1486 | case 0: /* non matching state */ 1487 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1488 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1489 | /* send the echo buffer */ 1490 | esi_parser_echo_buffer( parser ); 1491 | } 1492 | /* send the current character */ 1493 | esi_parser_echo_char( parser, *p ); 1494 | } 1495 | /* clear the echo buffer */ 1496 | esi_parser_echobuffer_clear( parser ); 1497 | break; 1498 | default: 1499 | /* append to the echo buffer */ 1500 | esi_parser_concat_to_echobuffer( parser, *p ); 1501 | } 1502 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1503 | is state 12 and 7 1504 | */ 1505 | parser->prev_state = cs; 1506 | } 1507 | #line 187 "ngx_esi_parser.rl" 1508 | { 1509 | /* trim tag text */ 1510 | ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 1511 | rtrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 1512 | 1513 | /* send the start and end tag message */ 1514 | esi_parser_flush_output( parser ); 1515 | parser->start_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->attributes, parser->user_data ); 1516 | esi_parser_flush_output( parser ); 1517 | 1518 | if( parser->attributes ) { 1519 | esi_attribute_free( parser->attributes ); 1520 | parser->attributes = NULL; 1521 | } 1522 | 1523 | /* mark the position */ 1524 | parser->tag_text = NULL; 1525 | parser->tag_text_length = 0; 1526 | parser->mark = p; 1527 | 1528 | /* clear out the echo buffer */ 1529 | esi_parser_echobuffer_clear( parser ); 1530 | } 1531 | goto _again; 1532 | f6: 1533 | #line 296 "ngx_esi_parser.rl" 1534 | { 1535 | //printf( "[%c:%d],", *p, cs ); 1536 | switch( cs ) { 1537 | case 0: /* non matching state */ 1538 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1539 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1540 | /* send the echo buffer */ 1541 | esi_parser_echo_buffer( parser ); 1542 | } 1543 | /* send the current character */ 1544 | esi_parser_echo_char( parser, *p ); 1545 | } 1546 | /* clear the echo buffer */ 1547 | esi_parser_echobuffer_clear( parser ); 1548 | break; 1549 | default: 1550 | /* append to the echo buffer */ 1551 | esi_parser_concat_to_echobuffer( parser, *p ); 1552 | } 1553 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1554 | is state 12 and 7 1555 | */ 1556 | parser->prev_state = cs; 1557 | } 1558 | #line 212 "ngx_esi_parser.rl" 1559 | { 1560 | /* save the attribute key start */ 1561 | parser->attr_key = parser->mark; 1562 | /* compute the length of the key */ 1563 | parser->attr_key_length = p - parser->mark; 1564 | /* save the position following the key */ 1565 | parser->mark = p; 1566 | 1567 | /* trim the attribute key */ 1568 | ltrim_pointer( &(parser->attr_key), p, &(parser->attr_key_length) ); 1569 | rtrim_pointer( &(parser->attr_key), p, &(parser->attr_key_length) ); 1570 | } 1571 | goto _again; 1572 | f7: 1573 | #line 296 "ngx_esi_parser.rl" 1574 | { 1575 | //printf( "[%c:%d],", *p, cs ); 1576 | switch( cs ) { 1577 | case 0: /* non matching state */ 1578 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1579 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1580 | /* send the echo buffer */ 1581 | esi_parser_echo_buffer( parser ); 1582 | } 1583 | /* send the current character */ 1584 | esi_parser_echo_char( parser, *p ); 1585 | } 1586 | /* clear the echo buffer */ 1587 | esi_parser_echobuffer_clear( parser ); 1588 | break; 1589 | default: 1590 | /* append to the echo buffer */ 1591 | esi_parser_concat_to_echobuffer( parser, *p ); 1592 | } 1593 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1594 | is state 12 and 7 1595 | */ 1596 | parser->prev_state = cs; 1597 | } 1598 | #line 226 "ngx_esi_parser.rl" 1599 | { 1600 | ESIAttribute *attr; 1601 | 1602 | /* save the attribute value start */ 1603 | parser->attr_value = parser->mark; 1604 | /* compute the length of the value */ 1605 | parser->attr_value_length = p - parser->mark; 1606 | /* svae the position following the value */ 1607 | parser->mark = p; 1608 | 1609 | /* trim the attribute value */ 1610 | ltrim_pointer( &(parser->attr_value), p, &(parser->attr_value_length) ); 1611 | rtrim_pointer( &(parser->attr_value), p, &(parser->attr_value_length) ); 1612 | 1613 | /* using the attr_key and attr_value, allocate a new attribute object */ 1614 | attr = esi_attribute_new( parser->attr_key, parser->attr_key_length, 1615 | parser->attr_value, parser->attr_value_length ); 1616 | 1617 | /* add the new attribute to the list of attributes */ 1618 | if( parser->attributes ) { 1619 | parser->last->next = attr; 1620 | parser->last = attr; 1621 | } 1622 | else { 1623 | parser->last = parser->attributes = attr; 1624 | } 1625 | } 1626 | goto _again; 1627 | f4: 1628 | #line 296 "ngx_esi_parser.rl" 1629 | { 1630 | //printf( "[%c:%d],", *p, cs ); 1631 | switch( cs ) { 1632 | case 0: /* non matching state */ 1633 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1634 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1635 | /* send the echo buffer */ 1636 | esi_parser_echo_buffer( parser ); 1637 | } 1638 | /* send the current character */ 1639 | esi_parser_echo_char( parser, *p ); 1640 | } 1641 | /* clear the echo buffer */ 1642 | esi_parser_echobuffer_clear( parser ); 1643 | break; 1644 | default: 1645 | /* append to the echo buffer */ 1646 | esi_parser_concat_to_echobuffer( parser, *p ); 1647 | } 1648 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1649 | is state 12 and 7 1650 | */ 1651 | parser->prev_state = cs; 1652 | } 1653 | #line 255 "ngx_esi_parser.rl" 1654 | { 1655 | 1656 | parser->tag_text = parser->mark; 1657 | parser->tag_text_length = p - parser->mark; 1658 | 1659 | parser->mark = p; 1660 | 1661 | ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 1662 | rtrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 1663 | 1664 | esi_parser_flush_output( parser ); 1665 | parser->start_tag_handler( data, parser->tag_text, parser->tag_text_length, NULL, parser->user_data ); 1666 | esi_parser_flush_output( parser ); 1667 | 1668 | if( parser->attributes ) { 1669 | esi_attribute_free( parser->attributes ); 1670 | parser->attributes = NULL; 1671 | } 1672 | 1673 | esi_parser_echobuffer_clear( parser ); 1674 | } 1675 | goto _again; 1676 | f2: 1677 | #line 296 "ngx_esi_parser.rl" 1678 | { 1679 | //printf( "[%c:%d],", *p, cs ); 1680 | switch( cs ) { 1681 | case 0: /* non matching state */ 1682 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1683 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1684 | /* send the echo buffer */ 1685 | esi_parser_echo_buffer( parser ); 1686 | } 1687 | /* send the current character */ 1688 | esi_parser_echo_char( parser, *p ); 1689 | } 1690 | /* clear the echo buffer */ 1691 | esi_parser_echobuffer_clear( parser ); 1692 | break; 1693 | default: 1694 | /* append to the echo buffer */ 1695 | esi_parser_concat_to_echobuffer( parser, *p ); 1696 | } 1697 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1698 | is state 12 and 7 1699 | */ 1700 | parser->prev_state = cs; 1701 | } 1702 | #line 278 "ngx_esi_parser.rl" 1703 | { 1704 | /* offset by 2 to account for the tag_text = parser->mark+2; 1706 | parser->tag_text_length = p - (parser->mark+2); 1707 | 1708 | parser->mark = p; 1709 | 1710 | ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 1711 | rtrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 1712 | 1713 | esi_parser_flush_output( parser ); 1714 | parser->end_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->user_data ); 1715 | esi_parser_flush_output( parser ); 1716 | 1717 | esi_parser_echobuffer_clear( parser ); 1718 | } 1719 | goto _again; 1720 | f12: 1721 | #line 296 "ngx_esi_parser.rl" 1722 | { 1723 | //printf( "[%c:%d],", *p, cs ); 1724 | switch( cs ) { 1725 | case 0: /* non matching state */ 1726 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1727 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1728 | /* send the echo buffer */ 1729 | esi_parser_echo_buffer( parser ); 1730 | } 1731 | /* send the current character */ 1732 | esi_parser_echo_char( parser, *p ); 1733 | } 1734 | /* clear the echo buffer */ 1735 | esi_parser_echobuffer_clear( parser ); 1736 | break; 1737 | default: 1738 | /* append to the echo buffer */ 1739 | esi_parser_concat_to_echobuffer( parser, *p ); 1740 | } 1741 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1742 | is state 12 and 7 1743 | */ 1744 | parser->prev_state = cs; 1745 | } 1746 | #line 144 "ngx_esi_parser.rl" 1747 | { 1748 | parser->mark = p; 1749 | //debug_string( "begin", p, 1 ); 1750 | } 1751 | #line 148 "ngx_esi_parser.rl" 1752 | { 1753 | // printf( "finish\n" ); 1754 | } 1755 | goto _again; 1756 | f11: 1757 | #line 296 "ngx_esi_parser.rl" 1758 | { 1759 | //printf( "[%c:%d],", *p, cs ); 1760 | switch( cs ) { 1761 | case 0: /* non matching state */ 1762 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 1763 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 1764 | /* send the echo buffer */ 1765 | esi_parser_echo_buffer( parser ); 1766 | } 1767 | /* send the current character */ 1768 | esi_parser_echo_char( parser, *p ); 1769 | } 1770 | /* clear the echo buffer */ 1771 | esi_parser_echobuffer_clear( parser ); 1772 | break; 1773 | default: 1774 | /* append to the echo buffer */ 1775 | esi_parser_concat_to_echobuffer( parser, *p ); 1776 | } 1777 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 1778 | is state 12 and 7 1779 | */ 1780 | parser->prev_state = cs; 1781 | } 1782 | #line 226 "ngx_esi_parser.rl" 1783 | { 1784 | ESIAttribute *attr; 1785 | 1786 | /* save the attribute value start */ 1787 | parser->attr_value = parser->mark; 1788 | /* compute the length of the value */ 1789 | parser->attr_value_length = p - parser->mark; 1790 | /* svae the position following the value */ 1791 | parser->mark = p; 1792 | 1793 | /* trim the attribute value */ 1794 | ltrim_pointer( &(parser->attr_value), p, &(parser->attr_value_length) ); 1795 | rtrim_pointer( &(parser->attr_value), p, &(parser->attr_value_length) ); 1796 | 1797 | /* using the attr_key and attr_value, allocate a new attribute object */ 1798 | attr = esi_attribute_new( parser->attr_key, parser->attr_key_length, 1799 | parser->attr_value, parser->attr_value_length ); 1800 | 1801 | /* add the new attribute to the list of attributes */ 1802 | if( parser->attributes ) { 1803 | parser->last->next = attr; 1804 | parser->last = attr; 1805 | } 1806 | else { 1807 | parser->last = parser->attributes = attr; 1808 | } 1809 | } 1810 | #line 148 "ngx_esi_parser.rl" 1811 | { 1812 | // printf( "finish\n" ); 1813 | } 1814 | goto _again; 1815 | 1816 | _again: 1817 | if ( ++p != pe ) 1818 | goto _resume; 1819 | _test_eof: {} 1820 | if ( p == eof ) 1821 | { 1822 | switch ( _esi_eof_actions[cs] ) { 1823 | case 10: 1824 | #line 148 "ngx_esi_parser.rl" 1825 | { 1826 | // printf( "finish\n" ); 1827 | } 1828 | break; 1829 | #line 1830 "ngx_esi_parser.c" 1830 | } 1831 | } 1832 | 1833 | } 1834 | #line 545 "ngx_esi_parser.rl" 1835 | 1836 | parser->cs = cs; 1837 | 1838 | if( cs != esi_start && cs != 0 ) { 1839 | 1840 | /* reached the end and we're not at a termination point save the buffer as overflow */ 1841 | if( !parser->overflow_data ){ 1842 | // recompute mark, tag_text, attr_key, and attr_value since they all exist within overflow_data 1843 | int mark_offset = compute_offset( parser->mark, data ); 1844 | int tag_text_offset = compute_offset( parser->tag_text, data ); 1845 | int attr_key_offset = compute_offset( parser->attr_key, data ); 1846 | int attr_value_offset = compute_offset( parser->attr_value, data ); 1847 | //debug_string( "mark before move", parser->mark, 1 ); 1848 | 1849 | if( ESI_OUTPUT_BUFFER_SIZE > length ) { 1850 | parser->echobuffer_allocated = ESI_OUTPUT_BUFFER_SIZE; 1851 | } 1852 | else { 1853 | parser->echobuffer_allocated = length; 1854 | } 1855 | parser->overflow_data = (char*)malloc( sizeof( char ) * parser->echobuffer_allocated ); 1856 | memcpy( parser->overflow_data, data, length ); 1857 | parser->overflow_data_size = length; 1858 | //printf( "allocate overflow data: %ld\n", parser->echobuffer_allocated ); 1859 | 1860 | // in our new memory space mark will now be 1861 | parser->mark = ( mark_offset >= 0 ) ? parser->overflow_data + mark_offset : NULL; 1862 | parser->tag_text = ( tag_text_offset >= 0 ) ? parser->overflow_data + tag_text_offset : NULL; 1863 | parser->attr_key = ( attr_key_offset >= 0 ) ? parser->overflow_data + attr_key_offset : NULL; 1864 | parser->attr_value = ( attr_value_offset >= 0 ) ? parser->overflow_data + attr_value_offset : NULL; 1865 | //if( parser->mark ){ debug_string( "mark after move", parser->mark, 1 ); } else { printf( "mark is now empty\n" ); } 1866 | } 1867 | 1868 | }else if( parser->overflow_data ) { 1869 | /* dump the overflow buffer execution ended at a final state */ 1870 | free( parser->overflow_data ); 1871 | parser->overflow_data = NULL; 1872 | parser->overflow_data_size = 0; 1873 | } 1874 | 1875 | return cs; 1876 | } 1877 | int esi_parser_finish( ESIParser *parser ) 1878 | { 1879 | esi_parser_flush_output( parser ); 1880 | return 0; 1881 | } 1882 | 1883 | void esi_parser_start_tag_handler( ESIParser *parser, esi_start_tag_cb callback ) 1884 | { 1885 | parser->start_tag_handler = callback; 1886 | } 1887 | 1888 | void esi_parser_end_tag_handler( ESIParser *parser, esi_end_tag_cb callback ) 1889 | { 1890 | parser->end_tag_handler = callback; 1891 | } 1892 | -------------------------------------------------------------------------------- /ngx_esi_parser.h: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2007 Todd A. Fisher 3 | * You can redistribute it and/or modify it under the same terms as Mozilla Public License 1.1. 4 | */ 5 | #ifndef ESI_PARSER_H 6 | #define ESI_PARSER_H 7 | #include 8 | 9 | /* how much output to hold in memory before sending out */ 10 | #define ESI_OUTPUT_BUFFER_SIZE 1024 11 | 12 | char *esi_strndup( const char *str, size_t len ); 13 | 14 | /* 15 | * ESI Attribute is a single attribute with name and value 16 | * 17 | * e.g. for an esi include tag: 18 | * 19 | * 20 | * 21 | * 2 attributes would be allocated 22 | * 23 | * attrs[0]->name => 'src' 24 | * attrs[0]->value => '/foo/bar/' 25 | * 26 | * attrs[1]->name => 'timeout' 27 | * attrs[1]->value => '10' 28 | * 29 | * */ 30 | typedef struct _ESIAttr { 31 | char *name; 32 | char *value; 33 | struct _ESIAttr *next; 34 | }ESIAttribute; 35 | 36 | ESIAttribute *esi_attribute_new( const char *name, size_t name_length, const char *value, size_t value_length ); 37 | ESIAttribute *esi_attribute_copy( ESIAttribute *attribute ); 38 | void esi_attribute_free( ESIAttribute *attribute ); 39 | 40 | typedef void (*esi_start_tag_cb)(const void *data, 41 | const char *name_start, 42 | size_t name_length, 43 | ESIAttribute *attributes, 44 | void *user_data); 45 | 46 | typedef void (*esi_end_tag_cb)(const void *data, 47 | const char *name_start, 48 | size_t name_length, 49 | void *user_data); 50 | 51 | typedef void (*esi_output_cb)(const void *data, 52 | size_t length, 53 | void *user_data); 54 | 55 | typedef struct _ESIParser { 56 | int cs; 57 | int prev_state; 58 | 59 | void *user_data; 60 | 61 | const char *mark; 62 | size_t overflow_data_size; /* current size of the overflow data buffer */ 63 | char *overflow_data; /* overflow buffer if execute finishes and we are not in a final state store the parse data */ 64 | 65 | size_t echobuffer_allocated; /* amount of memory allocated for the echobuffer */ 66 | size_t echobuffer_index; /* current write position of the last echo'ed character */ 67 | char *echobuffer; /* echo buffer if the parse state is not 0 we store the characters here */ 68 | 69 | const char *tag_text; /* start pointer in data */ 70 | size_t tag_text_length; /* length from tag_text within data */ 71 | 72 | const char *attr_key; /* start pointer in data */ 73 | size_t attr_key_length; 74 | 75 | const char *attr_value; /* start pointer in data */ 76 | size_t attr_value_length; 77 | 78 | ESIAttribute *attributes, *last; 79 | 80 | /* this memory will be pass to the output_cb when either it's full 81 | * or eof is encountered */ 82 | char output_buffer[ESI_OUTPUT_BUFFER_SIZE+1]; 83 | size_t output_buffer_size; 84 | 85 | esi_start_tag_cb start_tag_handler; 86 | esi_end_tag_cb end_tag_handler; 87 | esi_output_cb output_handler; 88 | 89 | } ESIParser; 90 | 91 | /* create a new Edge Side Include Parser */ 92 | ESIParser *esi_parser_new(); 93 | void esi_parser_free( ESIParser *parser ); 94 | 95 | /* initialize the parser */ 96 | int esi_parser_init( ESIParser *parser ); 97 | 98 | /* 99 | * send a chunk of data to the parser, the internal parser state is returned 100 | */ 101 | int esi_parser_execute( ESIParser *parser, const char *data, size_t length ); 102 | /* 103 | * let the parser know that it has reached the end and it should flush any remaining data to the desired output device 104 | */ 105 | int esi_parser_finish( ESIParser *parser ); 106 | 107 | /* 108 | * setup a callback to execute when a new esi: start tag is encountered 109 | * this will fire for all block tags e.g. , and also 110 | * inline tags 111 | */ 112 | void esi_parser_start_tag_handler( ESIParser *parser, esi_start_tag_cb callback ); 113 | 114 | void esi_parser_end_tag_handler( ESIParser *parser, esi_end_tag_cb callback ); 115 | 116 | /* setup a callback to recieve data ready for output */ 117 | void esi_parser_output_handler( ESIParser *parser, esi_output_cb output_handler ); 118 | 119 | 120 | #endif 121 | -------------------------------------------------------------------------------- /ngx_esi_parser.rl: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) 2008 Todd A. Fisher 3 | * see LICENSE 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include "ngx_esi_parser.h" 10 | 11 | #ifdef DEBUG 12 | static void debug_string( const char *msg, const char *str, size_t len ) 13 | { 14 | char *pstr = esi_strndup( str, len ); 15 | printf( "%s :'%s'\n", msg, pstr ); 16 | free( pstr ); 17 | } 18 | #else 19 | #define debug_string(m,s,l) 20 | #endif 21 | 22 | /* define default callbacks */ 23 | static void 24 | esi_parser_default_start_cb( const void *data, 25 | const char *name_start, 26 | size_t name_length, 27 | ESIAttribute *attributes, 28 | void *user_data ) 29 | { 30 | } 31 | static void 32 | esi_parser_default_end_cb( const void *data, 33 | const char *name_start, 34 | size_t name_length, 35 | void *user_data ) 36 | { 37 | } 38 | static void 39 | esi_parser_default_output_cp(const void *data, 40 | size_t length, 41 | void *user_data) 42 | { 43 | } 44 | 45 | /* 46 | * flush output buffer 47 | */ 48 | static void esi_parser_flush_output( ESIParser *parser ) 49 | { 50 | if( parser->output_buffer_size > 0 ) { 51 | //debug_string( "esi_parser_flush_output:", parser->output_buffer, parser->output_buffer_size ); 52 | parser->output_handler( (void*)parser->output_buffer, parser->output_buffer_size, parser->user_data ); 53 | parser->output_buffer_size = 0; 54 | } 55 | } 56 | /* send the character to the output handler marking it 57 | * as ready for consumption, e.g. not an esi tag 58 | */ 59 | static void esi_parser_echo_char( ESIParser *parser, char ch ) 60 | { 61 | parser->output_buffer[parser->output_buffer_size++] = ch; 62 | if( parser->output_buffer_size == ESI_OUTPUT_BUFFER_SIZE ) { 63 | // flush the buffer to the consumer 64 | esi_parser_flush_output( parser ); 65 | } 66 | } 67 | /* send any buffered characters to the output handler. 68 | * This happens when we enter a case such as where the 69 | * first two characters < and e match the echobuffer_index + 1;; 74 | //debug_string( "echobuffer", parser->echobuffer, parser->echobuffer_index+1 ); 75 | //parser->output_handler( parser->echobuffer, parser->echobuffer_index+1, parser->user_data ); 76 | for( ; i < len; ++i ) { 77 | esi_parser_echo_char( parser, parser->echobuffer[i] ); 78 | } 79 | } 80 | /* 81 | * clear the buffer, no buffered characters should be emitted . 82 | * e.g. we matched an esi tag completely and all buffered characters can be tossed out 83 | */ 84 | static void esi_parser_echobuffer_clear( ESIParser *parser ) 85 | { 86 | parser->echobuffer_index = -1; 87 | } 88 | 89 | /* 90 | * add a character to the echobuffer. 91 | * this happens when we can't determine if the character is allowed to be sent to the client device 92 | * e.g. matching echobuffer_index++; 97 | 98 | if( parser->echobuffer_allocated <= parser->echobuffer_index ) { 99 | /* double the echobuffer size 100 | * we're getting some crazy input if this case ever happens 101 | */ 102 | parser->echobuffer_allocated *= 2; 103 | parser->echobuffer = (char*)realloc( parser->echobuffer, parser->echobuffer_allocated ); 104 | } 105 | parser->echobuffer[parser->echobuffer_index] = ch; 106 | // debug_string( "echo buffer", parser->echobuffer, parser->echobuffer_index+1 ); 107 | } 108 | /* 109 | * the mark boundary is not always going to be exactly on the attribute or tag name boundary 110 | * this trims characters from the left to right, advancing *ptr and reducing *len 111 | */ 112 | static void ltrim_pointer( const char **ptr, const char *bounds, size_t *len ) 113 | { 114 | // remove any spaces or = at the before the value 115 | while( (isspace( **ptr ) || 116 | **ptr == '=' || 117 | **ptr == '"' || 118 | **ptr == '<' || 119 | **ptr == '\'' ) && (*len > 0) && (*ptr != bounds) ) { 120 | (*ptr)++; 121 | (*len)--; 122 | } 123 | } 124 | /* 125 | * similar to ltrim_pointer, this walks from bounds to *ptr, reducing *len 126 | */ 127 | static void rtrim_pointer( const char **ptr, const char *bounds, size_t *len ) 128 | { 129 | bounds = (*ptr+(*len-1)); 130 | // remove any spaces or = at the before the value 131 | while( (isspace( *bounds ) || 132 | *bounds == '=' || 133 | *bounds == '"' || 134 | *bounds == '>' || 135 | *bounds == '\'') && (*len > 0) && (*ptr != bounds) ){ 136 | bounds--; 137 | (*len)--; 138 | } 139 | } 140 | 141 | %%{ 142 | machine esi; 143 | 144 | action begin { 145 | parser->mark = p; 146 | //debug_string( "begin", p, 1 ); 147 | } 148 | action finish { 149 | // printf( "finish\n" ); 150 | } 151 | 152 | # record the position of the start tag 153 | action see_start_tag { 154 | parser->tag_text = parser->mark+1; 155 | parser->tag_text_length = p - (parser->mark+1); 156 | parser->mark = p; 157 | } 158 | 159 | # detected an inline tag end, sends the start tag and end tag callback 160 | action see_end_tag { 161 | /* trim the tag text */ 162 | ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 163 | rtrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 164 | 165 | /* send the start tag and end tag message */ 166 | esi_parser_flush_output( parser ); 167 | parser->start_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->attributes, parser->user_data ); 168 | esi_parser_flush_output( parser ); 169 | parser->end_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->user_data ); 170 | esi_parser_flush_output( parser ); 171 | 172 | if( parser->attributes ) { 173 | esi_attribute_free( parser->attributes ); 174 | parser->attributes = NULL; 175 | } 176 | 177 | /* mark the position */ 178 | parser->tag_text = NULL; 179 | parser->tag_text_length = 0; 180 | parser->mark = p; 181 | 182 | /* clear out the echo buffer */ 183 | esi_parser_echobuffer_clear( parser ); 184 | } 185 | 186 | # block tag start, with attributes 187 | action see_block_start_with_attributes { 188 | /* trim tag text */ 189 | ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 190 | rtrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 191 | 192 | /* send the start and end tag message */ 193 | esi_parser_flush_output( parser ); 194 | parser->start_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->attributes, parser->user_data ); 195 | esi_parser_flush_output( parser ); 196 | 197 | if( parser->attributes ) { 198 | esi_attribute_free( parser->attributes ); 199 | parser->attributes = NULL; 200 | } 201 | 202 | /* mark the position */ 203 | parser->tag_text = NULL; 204 | parser->tag_text_length = 0; 205 | parser->mark = p; 206 | 207 | /* clear out the echo buffer */ 208 | esi_parser_echobuffer_clear( parser ); 209 | } 210 | 211 | # see an attribute key, /foo\s*=/ 212 | action see_attribute_key { 213 | /* save the attribute key start */ 214 | parser->attr_key = parser->mark; 215 | /* compute the length of the key */ 216 | parser->attr_key_length = p - parser->mark; 217 | /* save the position following the key */ 218 | parser->mark = p; 219 | 220 | /* trim the attribute key */ 221 | ltrim_pointer( &(parser->attr_key), p, &(parser->attr_key_length) ); 222 | rtrim_pointer( &(parser->attr_key), p, &(parser->attr_key_length) ); 223 | } 224 | 225 | # see an attribute value, aprox ~= /['"].*['"]/ 226 | action see_attribute_value { 227 | ESIAttribute *attr; 228 | 229 | /* save the attribute value start */ 230 | parser->attr_value = parser->mark; 231 | /* compute the length of the value */ 232 | parser->attr_value_length = p - parser->mark; 233 | /* svae the position following the value */ 234 | parser->mark = p; 235 | 236 | /* trim the attribute value */ 237 | ltrim_pointer( &(parser->attr_value), p, &(parser->attr_value_length) ); 238 | rtrim_pointer( &(parser->attr_value), p, &(parser->attr_value_length) ); 239 | 240 | /* using the attr_key and attr_value, allocate a new attribute object */ 241 | attr = esi_attribute_new( parser->attr_key, parser->attr_key_length, 242 | parser->attr_value, parser->attr_value_length ); 243 | 244 | /* add the new attribute to the list of attributes */ 245 | if( parser->attributes ) { 246 | parser->last->next = attr; 247 | parser->last = attr; 248 | } 249 | else { 250 | parser->last = parser->attributes = attr; 251 | } 252 | } 253 | 254 | # simple block start tag detected, e.g. no attributes 255 | action block_start_tag { 256 | 257 | parser->tag_text = parser->mark; 258 | parser->tag_text_length = p - parser->mark; 259 | 260 | parser->mark = p; 261 | 262 | ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 263 | rtrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 264 | 265 | esi_parser_flush_output( parser ); 266 | parser->start_tag_handler( data, parser->tag_text, parser->tag_text_length, NULL, parser->user_data ); 267 | esi_parser_flush_output( parser ); 268 | 269 | if( parser->attributes ) { 270 | esi_attribute_free( parser->attributes ); 271 | parser->attributes = NULL; 272 | } 273 | 274 | esi_parser_echobuffer_clear( parser ); 275 | } 276 | 277 | # block end tag detected, e.g. 278 | action block_end_tag { 279 | /* offset by 2 to account for the tag_text = parser->mark+2; 281 | parser->tag_text_length = p - (parser->mark+2); 282 | 283 | parser->mark = p; 284 | 285 | ltrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 286 | rtrim_pointer( &(parser->tag_text), p, &(parser->tag_text_length) ); 287 | 288 | esi_parser_flush_output( parser ); 289 | parser->end_tag_handler( data, parser->tag_text, parser->tag_text_length, parser->user_data ); 290 | esi_parser_flush_output( parser ); 291 | 292 | esi_parser_echobuffer_clear( parser ); 293 | } 294 | 295 | # process each character in the input stream for output 296 | action echo { 297 | //printf( "[%c:%d],", *p, cs ); 298 | switch( cs ) { 299 | case 0: /* non matching state */ 300 | if( parser->prev_state != 12 && parser->prev_state != 7 ){ /* states following a possible end state for a tag */ 301 | if( parser->echobuffer && parser->echobuffer_index != (size_t)-1 ){ 302 | /* send the echo buffer */ 303 | esi_parser_echo_buffer( parser ); 304 | } 305 | /* send the current character */ 306 | esi_parser_echo_char( parser, *p ); 307 | } 308 | /* clear the echo buffer */ 309 | esi_parser_echobuffer_clear( parser ); 310 | break; 311 | default: 312 | /* append to the echo buffer */ 313 | esi_parser_concat_to_echobuffer( parser, *p ); 314 | } 315 | /* save the previous state, necessary for end case detection such as /> and the trailing > character 316 | is state 12 and 7 317 | */ 318 | parser->prev_state = cs; 319 | } 320 | 321 | include esi_common_parser "common.rl"; 322 | }%% 323 | 324 | %%write data; 325 | 326 | /* dup the string up to len */ 327 | char *esi_strndup( const char *str, size_t len ) 328 | { 329 | char *s = (char*)malloc(sizeof(char)*(len+1)); 330 | memcpy( s, str, len ); 331 | s[len] = '\0'; 332 | return s; 333 | } 334 | 335 | ESIAttribute *esi_attribute_new( const char *name, size_t name_length, const char *value, size_t value_length ) 336 | { 337 | ESIAttribute *attr = (ESIAttribute*)malloc(sizeof(ESIAttribute)); 338 | attr->name = esi_strndup(name, name_length); 339 | attr->value = esi_strndup(value, value_length); 340 | attr->next = NULL; 341 | return attr; 342 | } 343 | 344 | ESIAttribute *esi_attribute_copy( ESIAttribute *attribute ) 345 | { 346 | ESIAttribute *head, *nattr; 347 | if( !attribute ){ return NULL; } 348 | 349 | // copy the first attribute 350 | nattr = esi_attribute_new( attribute->name, strlen( attribute->name ), 351 | attribute->value, strlen( attribute->value ) ); 352 | // save a pointer for return 353 | head = nattr; 354 | // copy next attributes 355 | attribute = attribute->next; 356 | while( attribute ) { 357 | // set the next attribute 358 | nattr->next = esi_attribute_new( attribute->name, strlen( attribute->name ), 359 | attribute->value, strlen( attribute->value ) ); 360 | // next attribute 361 | nattr = nattr->next; 362 | attribute = attribute->next; 363 | } 364 | return head; 365 | } 366 | 367 | void esi_attribute_free( ESIAttribute *attribute ) 368 | { 369 | ESIAttribute *ptr; 370 | while( attribute ){ 371 | free( attribute->name ); 372 | free( attribute->value ); 373 | ptr = attribute->next; 374 | free( attribute ); 375 | attribute = ptr; 376 | } 377 | } 378 | 379 | ESIParser *esi_parser_new() 380 | { 381 | ESIParser *parser = (ESIParser*)malloc(sizeof(ESIParser)); 382 | parser->cs = esi_start; 383 | parser->mark = NULL; 384 | parser->tag_text = NULL; 385 | parser->attr_key = NULL; 386 | parser->attr_value = NULL; 387 | parser->overflow_data_size = 0; 388 | parser->overflow_data = NULL; 389 | 390 | /* allocate ESI_OUTPUT_BUFFER_SIZE bytes for the echobuffer */ 391 | parser->echobuffer_allocated = ESI_OUTPUT_BUFFER_SIZE; 392 | parser->echobuffer_index = -1; 393 | parser->echobuffer = (char*)malloc(sizeof(char)*parser->echobuffer_allocated); 394 | 395 | parser->attributes = NULL; 396 | parser->last = NULL; 397 | 398 | parser->start_tag_handler = esi_parser_default_start_cb; 399 | parser->end_tag_handler = esi_parser_default_end_cb; 400 | parser->output_handler = esi_parser_default_output_cp; 401 | 402 | parser->output_buffer_size = 0; 403 | memset( parser->output_buffer, 0, ESI_OUTPUT_BUFFER_SIZE ); 404 | 405 | return parser; 406 | } 407 | void esi_parser_free( ESIParser *parser ) 408 | { 409 | if( parser->overflow_data ){ free( parser->overflow_data ); } 410 | 411 | free( parser->echobuffer ); 412 | esi_attribute_free( parser->attributes ); 413 | 414 | free( parser ); 415 | } 416 | 417 | void esi_parser_output_handler( ESIParser *parser, esi_output_cb output_handler ) 418 | { 419 | parser->output_handler = output_handler; 420 | } 421 | 422 | int esi_parser_init( ESIParser *parser ) 423 | { 424 | int cs; 425 | %% write init; 426 | parser->prev_state = parser->cs = cs; 427 | return 0; 428 | } 429 | 430 | static int compute_offset( const char *mark, const char *data ) 431 | { 432 | if( mark ) { 433 | return mark - data; 434 | } 435 | return -1; 436 | } 437 | 438 | /* 439 | * scans the data buffer for a start sequence /<$/, /cs; 493 | const char *p = data; 494 | const char *eof = NULL; // ragel 6.x compat 495 | const char *pe = data + length; 496 | int pindex; 497 | 498 | if( length == 0 || data == 0 ){ return cs; } 499 | 500 | /* scan data for any 'overflow_data && parser->overflow_data_size > 0 ) { 513 | 514 | // recompute mark, tag_text, attr_key, and attr_value since they all exist within overflow_data 515 | int mark_offset = compute_offset( parser->mark, parser->overflow_data ); 516 | int tag_text_offset = compute_offset( parser->tag_text, parser->overflow_data ); 517 | int attr_key_offset = compute_offset( parser->attr_key, parser->overflow_data ); 518 | int attr_value_offset = compute_offset( parser->attr_value, parser->overflow_data ); 519 | 520 | parser->overflow_data = (char*)realloc( parser->overflow_data, sizeof(char)*(parser->overflow_data_size+length) ); 521 | memcpy( parser->overflow_data+parser->overflow_data_size, data, length ); 522 | 523 | p = parser->overflow_data + parser->overflow_data_size; 524 | 525 | // in our new memory space mark will now be 526 | parser->mark = ( mark_offset >= 0 ) ? parser->overflow_data + mark_offset : NULL; 527 | parser->tag_text = ( tag_text_offset >= 0 ) ? parser->overflow_data + tag_text_offset : NULL; 528 | parser->attr_key = ( attr_key_offset >= 0 ) ? parser->overflow_data + attr_key_offset : NULL; 529 | parser->attr_value = ( attr_value_offset >= 0 ) ? parser->overflow_data + attr_value_offset : NULL; 530 | 531 | data = parser->overflow_data; 532 | parser->overflow_data_size = length = length + parser->overflow_data_size; 533 | // printf( "grow overflow data: %ld\n", parser->overflow_data_size ); 534 | pe = data + length; 535 | 536 | } 537 | 538 | if( !parser->mark ) { 539 | parser->mark = p; 540 | } 541 | 542 | // printf( "cs: %d, ", cs ); 543 | 544 | %% write exec; 545 | 546 | parser->cs = cs; 547 | 548 | if( cs != esi_start && cs != 0 ) { 549 | 550 | /* reached the end and we're not at a termination point save the buffer as overflow */ 551 | if( !parser->overflow_data ){ 552 | // recompute mark, tag_text, attr_key, and attr_value since they all exist within overflow_data 553 | int mark_offset = compute_offset( parser->mark, data ); 554 | int tag_text_offset = compute_offset( parser->tag_text, data ); 555 | int attr_key_offset = compute_offset( parser->attr_key, data ); 556 | int attr_value_offset = compute_offset( parser->attr_value, data ); 557 | //debug_string( "mark before move", parser->mark, 1 ); 558 | 559 | if( ESI_OUTPUT_BUFFER_SIZE > length ) { 560 | parser->echobuffer_allocated = ESI_OUTPUT_BUFFER_SIZE; 561 | } 562 | else { 563 | parser->echobuffer_allocated = length; 564 | } 565 | parser->overflow_data = (char*)malloc( sizeof( char ) * parser->echobuffer_allocated ); 566 | memcpy( parser->overflow_data, data, length ); 567 | parser->overflow_data_size = length; 568 | //printf( "allocate overflow data: %ld\n", parser->echobuffer_allocated ); 569 | 570 | // in our new memory space mark will now be 571 | parser->mark = ( mark_offset >= 0 ) ? parser->overflow_data + mark_offset : NULL; 572 | parser->tag_text = ( tag_text_offset >= 0 ) ? parser->overflow_data + tag_text_offset : NULL; 573 | parser->attr_key = ( attr_key_offset >= 0 ) ? parser->overflow_data + attr_key_offset : NULL; 574 | parser->attr_value = ( attr_value_offset >= 0 ) ? parser->overflow_data + attr_value_offset : NULL; 575 | //if( parser->mark ){ debug_string( "mark after move", parser->mark, 1 ); } else { printf( "mark is now empty\n" ); } 576 | } 577 | 578 | }else if( parser->overflow_data ) { 579 | /* dump the overflow buffer execution ended at a final state */ 580 | free( parser->overflow_data ); 581 | parser->overflow_data = NULL; 582 | parser->overflow_data_size = 0; 583 | } 584 | 585 | return cs; 586 | } 587 | int esi_parser_finish( ESIParser *parser ) 588 | { 589 | esi_parser_flush_output( parser ); 590 | return 0; 591 | } 592 | 593 | void esi_parser_start_tag_handler( ESIParser *parser, esi_start_tag_cb callback ) 594 | { 595 | parser->start_tag_handler = callback; 596 | } 597 | 598 | void esi_parser_end_tag_handler( ESIParser *parser, esi_end_tag_cb callback ) 599 | { 600 | parser->end_tag_handler = callback; 601 | } 602 | -------------------------------------------------------------------------------- /ngx_esi_tag.c: -------------------------------------------------------------------------------- 1 | #include "ngx_regex.h" 2 | #include "ngx_esi_tag.h" 3 | #include "ngx_buf_util.h" 4 | 5 | ESITag *esi_tag_new(esi_tag_t type, ngx_http_esi_ctx_t *ctx) 6 | { 7 | ESITag *t = (ESITag*)malloc(sizeof(ESITag)); 8 | t->type = type; 9 | t->ctx = ctx; 10 | t->next = NULL; 11 | return t; 12 | } 13 | void esi_tag_free(ESITag *tag) 14 | { 15 | ESITag *n, *next = tag->next; 16 | while( next ) { 17 | n = next->next; 18 | free(next); 19 | next = n; 20 | } 21 | 22 | //ngx_free_chain(tag->pool, tag->chain); 23 | free(tag); 24 | } 25 | 26 | esi_tag_t esi_tag_str_to_type( const char *tag_name, size_t length ) 27 | { 28 | if( !strncmp("esi:try",tag_name,length) ) { 29 | return ESI_TRY; 30 | } 31 | else if( !strncmp("esi:attempt",tag_name,length) ) { 32 | return ESI_ATTEMPT; 33 | } 34 | else if( !strncmp("esi:except",tag_name,length) ) { 35 | return ESI_EXCEPT; 36 | } 37 | else if( !strncmp("esi:include",tag_name,length) ) { 38 | return ESI_INCLUDE; 39 | } 40 | else if( !strncmp("esi:invalidate",tag_name,length) ) { 41 | return ESI_INVALIDATE; 42 | } 43 | else if( !strncmp("esi:vars",tag_name,length) ) { 44 | return ESI_VARS; 45 | } 46 | else if( !strncmp("esi:remove",tag_name,length) ) { 47 | return ESI_REMOVE; 48 | } 49 | return ESI_NONE; 50 | } 51 | 52 | static ngx_int_t 53 | ngx_http_esi_stub_output(ngx_http_request_t *r, void *data, ngx_int_t rc) 54 | { 55 | ngx_chain_t *out; 56 | if (rc == NGX_ERROR || r->connection->error || r->request_output) { 57 | return rc; 58 | } 59 | 60 | ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0, 61 | "ssi stub output: \"%V?%V\"", &r->uri, &r->args); 62 | 63 | out = data; 64 | 65 | if (!r->header_sent) { 66 | if (ngx_http_set_content_type(r) == NGX_ERROR) { 67 | return NGX_ERROR; 68 | } 69 | 70 | if (ngx_http_send_header(r) == NGX_ERROR) { 71 | return NGX_ERROR; 72 | } 73 | } 74 | 75 | return ngx_http_output_filter(r, out); 76 | } 77 | 78 | static void esi_tag_start_include(ESITag *tag, ESIAttribute *attributes) 79 | { 80 | ngx_int_t rc; 81 | ngx_str_t uri, args; 82 | ngx_http_request_t *sr; 83 | ESIAttribute *attr = attributes; 84 | ngx_http_request_t *request = tag->ctx->request; 85 | ngx_pool_t *pool = request->pool; 86 | ngx_uint_t flags = 0; 87 | ngx_http_post_subrequest_t *psr; 88 | ngx_chain_t *link; 89 | ngx_buf_t *buf; 90 | 91 | flags |= NGX_HTTP_SUBREQUEST_IN_MEMORY; 92 | args.len = 0; 93 | args.data = NULL; 94 | 95 | // printf( "esi:include\n" ); 96 | while( attr ) { 97 | // printf( "\t%s => %s\n", attr->name, attr->value ); 98 | if( !ngx_strcmp( attr->name, "src" ) ) { 99 | uri.len = strlen(attr->value)+1; 100 | uri.data = ngx_palloc(pool, uri.len ); 101 | ngx_memcpy( uri.data, attr->value, uri.len ); 102 | printf( "uri: %s\n", uri.data ); 103 | } 104 | attr = attr->next; 105 | } 106 | 107 | if( uri.len > 0 ) { 108 | psr = ngx_palloc(pool, sizeof(ngx_http_post_subrequest_t)); 109 | if( psr == NULL ) { 110 | return; //return NGX_ERROR; 111 | } 112 | /* attach the handler */ 113 | psr->handler = ngx_http_esi_stub_output; 114 | 115 | /* allocate a buffer */ 116 | buf = ngx_alloc_buf(pool); 117 | if( buf == NULL ) { 118 | return; //return NGX_ERROR; 119 | } 120 | link = ngx_alloc_chain_link(pool); 121 | if( link == NULL ) { 122 | return; //return NGX_ERROR; 123 | } 124 | link->buf = buf; 125 | link->next = NULL; 126 | 127 | psr->data = link; 128 | 129 | rc = ngx_http_subrequest(request, &uri, &args, &sr, psr, flags); 130 | } 131 | 132 | ngx_pfree( pool, uri.data ); 133 | } 134 | 135 | void esi_tag_open(ESITag *tag, ESIAttribute *attributes) 136 | { 137 | switch(tag->type) { 138 | case ESI_TRY: 139 | break; 140 | case ESI_ATTEMPT: 141 | tag->ctx->exception_raised = 0; /* reset the exception raised state to 0 */ 142 | break; 143 | case ESI_EXCEPT: 144 | if( !tag->ctx->exception_raised ) { 145 | tag->ctx->ignore_tag = 1; 146 | } 147 | break; 148 | case ESI_INCLUDE: 149 | if( !tag->ctx->ignore_tag ) { esi_tag_start_include( tag, attributes ); } 150 | break; 151 | case ESI_INVALIDATE: 152 | break; 153 | case ESI_VARS: 154 | break; 155 | case ESI_REMOVE: 156 | break; 157 | default: 158 | break; 159 | } 160 | // printf("start "); esi_tag_debug(tag); 161 | } 162 | void esi_tag_close(ESITag *tag) 163 | { 164 | switch(tag->type) { 165 | case ESI_TRY: 166 | break; 167 | case ESI_ATTEMPT: 168 | tag->ctx->exception_raised = 0; /* reset the exception raised state to 0 */ 169 | break; 170 | case ESI_EXCEPT: 171 | if( !tag->ctx->exception_raised ) { tag->ctx->ignore_tag = 0; } 172 | tag->ctx->exception_raised = 0; /* reset the exception raised state to 0 */ 173 | break; 174 | case ESI_INCLUDE: 175 | break; 176 | case ESI_INVALIDATE: 177 | break; 178 | case ESI_VARS: 179 | break; 180 | case ESI_REMOVE: 181 | break; 182 | default: 183 | break; 184 | } 185 | // printf("close tag: "); esi_tag_debug( tag ); 186 | esi_tag_free( tag ); 187 | } 188 | 189 | void esi_tag_debug(ESITag *tag) 190 | { 191 | if( !tag ) { printf("tag:(NULL)\n"); return; } 192 | 193 | switch(tag->type) { 194 | case ESI_TRY: 195 | printf("esi:try\n"); 196 | break; 197 | case ESI_ATTEMPT: 198 | printf("esi:attempt\n"); 199 | break; 200 | case ESI_EXCEPT: 201 | printf("esi:except\n"); 202 | break; 203 | case ESI_INCLUDE: 204 | printf("esi:include\n"); 205 | break; 206 | case ESI_INVALIDATE: 207 | printf("esi:invalidate\n"); 208 | break; 209 | case ESI_VARS: 210 | printf("esi:vars\n"); 211 | break; 212 | case ESI_REMOVE: 213 | printf("esi:remove\n"); 214 | break; 215 | default: 216 | printf("unknown esi type\n"); 217 | break; 218 | } 219 | 220 | } 221 | 222 | ESITag *esi_tag_close_children( ESITag *tag, esi_tag_t type ) 223 | { 224 | ESITag *n, *next, *ptr, *prev; 225 | 226 | ptr = tag; 227 | next = tag->next; 228 | 229 | while( next ) { 230 | prev = ptr; 231 | n = next->next; 232 | if( next->type == type ) { 233 | esi_tag_close( next ); 234 | prev->next = NULL; 235 | return prev; 236 | } 237 | ptr = next; 238 | next = n; 239 | } 240 | return NULL; 241 | } 242 | 243 | static ngx_buf_t *esi_tag_alloc_buf(ESITag *tag, const void *data, size_t length) 244 | { 245 | ngx_buf_t *b; 246 | b = ngx_pcalloc(tag->ctx->request->pool, sizeof(ngx_buf_t)); 247 | if (b == NULL) { 248 | ngx_log_error(NGX_LOG_ERR, tag->ctx->request->connection->log, 0, "Failed to allocate response buffer."); 249 | // return NGX_HTTP_INTERNAL_SERVER_ERROR; 250 | return NULL; 251 | } 252 | b->pos = (u_char*)(data); /* first position in memory of the data */ 253 | b->last = (((u_char*)data) + length); /* last position */ 254 | 255 | b->memory = 1; /* content is in read-only memory */ 256 | /* (i.e., filters should copy it rather than rewrite in place) */ 257 | b->last_buf = 0; 258 | return b; 259 | } 260 | 261 | ngx_buf_t *esi_tag_buffer(ESITag *tag, const void *data, size_t length) 262 | { 263 | //printf("buffer: %lu for tag: ", length); esi_tag_debug(tag); 264 | switch( tag->type ) { 265 | case ESI_VARS: 266 | case ESI_ATTEMPT: 267 | return ngx_buf_from_data( tag->ctx->request->pool, data, length ); 268 | case ESI_EXCEPT: 269 | if( tag->ctx->exception_raised ) { 270 | return ngx_buf_from_data( tag->ctx->request->pool, data, length ); 271 | } 272 | /* fall through */ 273 | case ESI_INCLUDE: 274 | case ESI_INVALIDATE: 275 | case ESI_REMOVE: 276 | case ESI_TRY: 277 | default: 278 | return NULL; 279 | } 280 | } 281 | 282 | int esi_vars_filter( ngx_chain_t *chain ) 283 | { 284 | return 0; 285 | } 286 | -------------------------------------------------------------------------------- /ngx_esi_tag.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Todd A. Fisher 3 | */ 4 | #ifndef NGX_ESI_TAG_H 5 | #define NGX_ESI_TAG_H 6 | 7 | #include "ngx_http_esi_filter_module.h" 8 | 9 | typedef enum { 10 | ESI_TRY, 11 | ESI_ATTEMPT, 12 | ESI_EXCEPT, 13 | ESI_INCLUDE, 14 | ESI_INVALIDATE, 15 | ESI_VARS, 16 | ESI_REMOVE, 17 | ESI_NONE 18 | }esi_tag_t; 19 | 20 | typedef struct _ESITag { 21 | ngx_http_esi_ctx_t *ctx; /* context stores request info */ 22 | esi_tag_t type; /* tag type */ 23 | struct _ESITag *next; /* children of this tag e.g. 24 | , 25 | try has 2 children attempt and include, and include has one child include */ 26 | } ESITag; 27 | 28 | ESITag *esi_tag_new(esi_tag_t tag, ngx_http_esi_ctx_t *ctx); 29 | void esi_tag_free(ESITag *tag); 30 | void esi_tag_open(ESITag *tag, ESIAttribute *attributes); 31 | void esi_tag_close(ESITag *tag); 32 | ESITag *esi_tag_close_children( ESITag *tag, esi_tag_t type ); 33 | ngx_buf_t *esi_tag_buffer(ESITag *tag, const void *data, size_t length); 34 | void esi_tag_debug(ESITag *tag); 35 | 36 | esi_tag_t esi_tag_str_to_type( const char *tag_name, size_t length ); 37 | int esi_vars_filter( ngx_chain_t *chain ); 38 | 39 | 40 | #endif 41 | -------------------------------------------------------------------------------- /ngx_http_esi_filter_module.c: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Todd A. Fisher 3 | * 4 | * This is a first attempt at nginx module. The idea will be to use mongrel-esi ragel based parser to detect and 5 | * tigger esi events. 6 | * 7 | * This module is model'ed after ssi module 8 | */ 9 | 10 | #include "ngx_esi_tag.h" 11 | #include "ngx_http_esi_filter_module.h" 12 | #include "ngx_buf_util.h" 13 | 14 | typedef struct { 15 | ngx_hash_t hash; 16 | ngx_hash_keys_arrays_t commands; 17 | } ngx_http_esi_main_conf_t; 18 | 19 | typedef struct { 20 | ngx_flag_t enable; /* enable esi filter */ 21 | ngx_flag_t silent_errors; /* ignore include errors, don't raise exceptions */ 22 | ngx_array_t *types; /* array of ngx_str_t */ 23 | 24 | size_t min_file_chunk; /* smallest size chunk */ 25 | size_t max_depth; /* how many times to follow an esi:include redirect... */ 26 | } ngx_http_esi_loc_conf_t; 27 | 28 | 29 | static ngx_int_t ngx_http_esi_preconfiguration(ngx_conf_t *cf); 30 | static ngx_int_t ngx_http_esi_filter_init(ngx_conf_t *cf); 31 | static char *ngx_http_esi_types(ngx_conf_t *cf, ngx_command_t *cmd, void *conf); 32 | static void *ngx_http_esi_create_main_conf(ngx_conf_t *cf); 33 | static char *ngx_http_esi_init_main_conf(ngx_conf_t *cf, void *conf); 34 | static void *ngx_http_esi_create_loc_conf(ngx_conf_t *cf); 35 | static char *ngx_http_esi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child); 36 | 37 | /* modified from ssi module */ 38 | static ngx_command_t ngx_http_esi_filter_commands[] = { 39 | 40 | { ngx_string("esi"), 41 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF 42 | |NGX_CONF_FLAG, 43 | ngx_conf_set_flag_slot, 44 | NGX_HTTP_LOC_CONF_OFFSET, 45 | offsetof(ngx_http_esi_loc_conf_t, enable), 46 | NULL }, 47 | 48 | { ngx_string("esi_silent_errors"), 49 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 50 | ngx_conf_set_flag_slot, 51 | NGX_HTTP_LOC_CONF_OFFSET, 52 | offsetof(ngx_http_esi_loc_conf_t, silent_errors), 53 | NULL }, 54 | 55 | { ngx_string("esi_min_file_chunk"), 56 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 57 | ngx_conf_set_size_slot, 58 | NGX_HTTP_LOC_CONF_OFFSET, 59 | offsetof(ngx_http_esi_loc_conf_t, min_file_chunk), 60 | NULL }, 61 | 62 | { ngx_string("esi_max_depth"), 63 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG, 64 | ngx_conf_set_size_slot, 65 | NGX_HTTP_LOC_CONF_OFFSET, 66 | offsetof(ngx_http_esi_loc_conf_t, max_depth), 67 | NULL }, 68 | 69 | { ngx_string("esi_types"), 70 | NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE, 71 | ngx_http_esi_types, 72 | NGX_HTTP_LOC_CONF_OFFSET, 73 | 0, 74 | NULL }, 75 | 76 | 77 | ngx_null_command 78 | }; 79 | 80 | 81 | static ngx_http_module_t ngx_http_esi_filter_module_ctx = { 82 | ngx_http_esi_preconfiguration, /* preconfiguration */ 83 | ngx_http_esi_filter_init, /* postconfiguration */ 84 | 85 | ngx_http_esi_create_main_conf, /* create main configuration */ 86 | ngx_http_esi_init_main_conf, /* init main configuration */ 87 | 88 | NULL, /* create server configuration */ 89 | NULL, /* merge server configuration */ 90 | 91 | ngx_http_esi_create_loc_conf, /* create location configuration */ 92 | ngx_http_esi_merge_loc_conf /* merge location configuration */ 93 | }; 94 | 95 | 96 | ngx_module_t ngx_http_esi_filter_module = { 97 | NGX_MODULE_V1, 98 | &ngx_http_esi_filter_module_ctx, /* module context */ 99 | ngx_http_esi_filter_commands, /* module directives */ 100 | NGX_HTTP_MODULE, /* module type */ 101 | NULL, /* init master */ 102 | NULL, /* init module */ 103 | NULL, /* init process */ 104 | NULL, /* init thread */ 105 | NULL, /* exit thread */ 106 | NULL, /* exit process */ 107 | NULL, /* exit master */ 108 | NGX_MODULE_V1_PADDING 109 | }; 110 | 111 | 112 | static ngx_http_output_header_filter_pt ngx_http_next_header_filter; 113 | static ngx_http_output_body_filter_pt ngx_http_next_body_filter; 114 | 115 | static char * 116 | ngx_http_esi_types(ngx_conf_t *cf, ngx_command_t *cmd, void *conf) 117 | { 118 | ngx_http_esi_loc_conf_t *slcf = conf; 119 | 120 | ngx_str_t *value, *type; 121 | ngx_uint_t i; 122 | 123 | 124 | if (slcf->types == NULL) { 125 | slcf->types = ngx_array_create(cf->pool, 4, sizeof(ngx_str_t)); 126 | if (slcf->types == NULL) { 127 | return NGX_CONF_ERROR; 128 | } 129 | 130 | type = ngx_array_push(slcf->types); 131 | if (type == NULL) { 132 | return NGX_CONF_ERROR; 133 | } 134 | 135 | type->len = sizeof("text/html") - 1; 136 | type->data = (u_char *) "text/html"; 137 | } 138 | 139 | value = cf->args->elts; 140 | 141 | for (i = 1; i < cf->args->nelts; i++) { 142 | 143 | if (ngx_strcmp(value[i].data, "text/html") == 0) { 144 | continue; 145 | } 146 | 147 | type = ngx_array_push(slcf->types); 148 | if (type == NULL) { 149 | return NGX_CONF_ERROR; 150 | } 151 | 152 | type->len = value[i].len; 153 | 154 | type->data = ngx_palloc(cf->pool, type->len + 1); 155 | if (type->data == NULL) { 156 | return NGX_CONF_ERROR; 157 | } 158 | 159 | ngx_cpystrn(type->data, value[i].data, type->len + 1); 160 | } 161 | 162 | return NGX_CONF_OK; 163 | } 164 | 165 | static ngx_int_t 166 | ngx_http_esi_preconfiguration(ngx_conf_t *cf) 167 | { 168 | ngx_http_esi_main_conf_t *smcf; 169 | 170 | smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_esi_filter_module); 171 | 172 | 173 | 174 | return NGX_OK; 175 | } 176 | 177 | static void * 178 | ngx_http_esi_create_loc_conf(ngx_conf_t *cf) 179 | { 180 | ngx_http_esi_loc_conf_t *slcf; 181 | 182 | slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_esi_loc_conf_t)); 183 | if (slcf == NULL) { 184 | return NGX_CONF_ERROR; 185 | } 186 | 187 | /* 188 | * set by ngx_pcalloc(): 189 | * 190 | * conf->types = NULL; 191 | */ 192 | 193 | slcf->enable = NGX_CONF_UNSET; 194 | slcf->silent_errors = NGX_CONF_UNSET; 195 | slcf->min_file_chunk = NGX_CONF_UNSET_SIZE; 196 | slcf->max_depth = NGX_CONF_UNSET_SIZE; 197 | 198 | 199 | return slcf; 200 | } 201 | 202 | 203 | static char * 204 | ngx_http_esi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child) 205 | { 206 | ngx_http_esi_loc_conf_t *prev = parent; 207 | ngx_http_esi_loc_conf_t *conf = child; 208 | 209 | ngx_str_t *type; 210 | 211 | 212 | ngx_conf_merge_value(conf->enable, prev->enable, 0); 213 | ngx_conf_merge_value(conf->silent_errors, prev->silent_errors, 0); 214 | 215 | ngx_conf_merge_size_value(conf->min_file_chunk, prev->min_file_chunk, 1024); 216 | ngx_conf_merge_size_value(conf->max_depth, prev->max_depth, 256); 217 | 218 | 219 | if (conf->types == NULL) { 220 | if (prev->types == NULL) { 221 | conf->types = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t)); 222 | if (conf->types == NULL) { 223 | return NGX_CONF_ERROR; 224 | } 225 | 226 | type = ngx_array_push(conf->types); 227 | if (type == NULL) { 228 | return NGX_CONF_ERROR; 229 | } 230 | 231 | type->len = sizeof("text/html") - 1; 232 | type->data = (u_char *) "text/html"; 233 | 234 | } else { 235 | conf->types = prev->types; 236 | } 237 | } 238 | 239 | return NGX_CONF_OK; 240 | } 241 | 242 | static ngx_int_t 243 | ngx_http_esi_header_filter(ngx_http_request_t *r) 244 | { 245 | ngx_uint_t i; 246 | ngx_str_t *type; 247 | ngx_http_esi_ctx_t *ctx; 248 | ngx_http_esi_loc_conf_t *slcf; 249 | 250 | slcf = ngx_http_get_module_loc_conf(r, ngx_http_esi_filter_module); 251 | 252 | if (!slcf->enable 253 | || r->headers_out.content_type.len == 0 254 | || r->headers_out.content_length_n == 0) 255 | { 256 | return ngx_http_next_header_filter(r); 257 | } 258 | 259 | 260 | type = slcf->types->elts; 261 | for (i = 0; i < slcf->types->nelts; ++i) { 262 | //debug_string((const char*)type[i].data, type[i].len); 263 | if (r->headers_out.content_type.len >= type[i].len 264 | && ngx_strncasecmp(r->headers_out.content_type.data, 265 | type[i].data, type[i].len) == 0) 266 | { 267 | goto found; 268 | } 269 | } 270 | 271 | return ngx_http_next_header_filter(r); 272 | 273 | found: 274 | 275 | ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_esi_ctx_t)); 276 | if (ctx == NULL) { 277 | return NGX_ERROR; 278 | } 279 | 280 | ngx_http_set_ctx(r, ctx, ngx_http_esi_filter_module); 281 | 282 | ctx->request = r; 283 | ctx->root_tag = NULL; 284 | ctx->open_tag = NULL; 285 | /* allocate some output buffers */ 286 | ctx->last_buf = ctx->chain = ngx_alloc_chain_link(r->pool); 287 | ctx->last_buf->buf = ctx->chain->buf = NULL; 288 | ctx->last_buf->next = ctx->chain->next = NULL; 289 | 290 | r->filter_need_in_memory = 1; 291 | 292 | if (r == r->main) { 293 | ngx_http_clear_content_length(r); 294 | ngx_http_clear_last_modified(r); 295 | } 296 | 297 | return ngx_http_next_header_filter(r); 298 | } 299 | 300 | static void 301 | esi_parser_start_tag_cb( const void *data, const char *name_start, size_t length, ESIAttribute *attributes, void *context ) 302 | { 303 | ESITag *tag = NULL; 304 | ngx_http_esi_ctx_t *ctx = (ngx_http_esi_ctx_t*)context; 305 | esi_tag_t type = esi_tag_str_to_type( name_start, length ); 306 | 307 | if( type == ESI_NONE ) { 308 | ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0, "Invalid ESI Tag!"); 309 | return; 310 | } 311 | 312 | tag = esi_tag_new(type, ctx); 313 | 314 | if( ctx->root_tag ) { 315 | ESITag *last = ctx->root_tag; 316 | while( last->next ) { 317 | last = last->next; 318 | } 319 | last->next = tag; 320 | } 321 | else { 322 | ctx->root_tag = tag; 323 | } 324 | ctx->open_tag = tag; 325 | esi_tag_open( tag , attributes ); 326 | // printf("start tag:%d ", (int)length ); debug_string( name_start, length ); printf("\n" ); 327 | } 328 | 329 | static void 330 | esi_parser_end_tag_cb( const void *data, const char *name_start, size_t length, void *context ) 331 | { 332 | ngx_http_esi_ctx_t *ctx = (ngx_http_esi_ctx_t*)context; 333 | esi_tag_t type = esi_tag_str_to_type( name_start, length ); 334 | 335 | if( type == ESI_NONE ) { 336 | ngx_log_error(NGX_LOG_ERR, ctx->request->connection->log, 0, "Invalid ESI Tag!"); 337 | return; 338 | } 339 | 340 | if( ctx->root_tag ) { 341 | if( ctx->root_tag->type == type ) { 342 | esi_tag_close( ctx->root_tag ); 343 | ctx->open_tag = ctx->root_tag = NULL; 344 | } 345 | else { 346 | ctx->open_tag = esi_tag_close_children( ctx->root_tag, type ); 347 | } 348 | } 349 | 350 | // printf("end tag:%d ", (int)length ); debug_string( name_start, length ); printf("\n" ); 351 | } 352 | 353 | static void 354 | esi_parser_output_cb( const void *data, size_t length, void *context ) 355 | { 356 | ngx_buf_t *buf; 357 | ngx_http_esi_ctx_t *ctx = (ngx_http_esi_ctx_t*)context; 358 | 359 | if( ctx->root_tag && ctx->open_tag ) { 360 | buf = esi_tag_buffer( ctx->open_tag, data, length ); 361 | } 362 | else { 363 | buf = ngx_buf_from_data( ctx->request->pool, data, length ); 364 | } 365 | 366 | if( buf ) { 367 | ctx->last_buf = ngx_chain_append_buffer( ctx->request->pool, ctx->last_buf, buf ); 368 | } 369 | //printf("output char len: %d \n", (int)length );debug_string( (const char*)data, (int)length );printf("\n"); 370 | } 371 | 372 | static ngx_int_t 373 | ngx_http_esi_body_filter(ngx_http_request_t *r, ngx_chain_t *in) 374 | { 375 | off_t size; 376 | int count = 0; 377 | ngx_chain_t *chain_link; 378 | short has_last_buffer = 0; 379 | ngx_http_esi_ctx_t *ctx; 380 | 381 | ctx = ngx_http_get_module_ctx(r, ngx_http_esi_filter_module); 382 | 383 | if( ctx == NULL || in == NULL || r->header_only ) { 384 | return ngx_http_next_body_filter(r, in); 385 | } 386 | 387 | ctx->request = r; 388 | ctx->root_tag = NULL; 389 | ctx->open_tag = NULL; 390 | 391 | if( !ctx->parser ) { 392 | ctx->parser = esi_parser_new(); 393 | esi_parser_init( ctx->parser ); 394 | ctx->parser->user_data = (void*)ctx; 395 | esi_parser_start_tag_handler( ctx->parser, esi_parser_start_tag_cb ); 396 | esi_parser_end_tag_handler( ctx->parser, esi_parser_end_tag_cb ); 397 | esi_parser_output_handler( ctx->parser, esi_parser_output_cb ); 398 | } 399 | 400 | // printf( "called esi body filter\n" ); 401 | 402 | for( chain_link = in; chain_link != NULL; chain_link = chain_link->next ) { 403 | size = ngx_buf_size(chain_link->buf); 404 | 405 | //printf("buf size: %d, link: %d, buf: '", (int)size, count ); debug_string( (const char*)chain_link->buf->start, (int)size ); printf("'\n"); 406 | esi_parser_execute( ctx->parser, (const char*)chain_link->buf->pos, (size_t)size ); 407 | 408 | if( chain_link->buf->last_buf ) { 409 | esi_parser_finish( ctx->parser ); 410 | has_last_buffer = 1; 411 | break; 412 | } 413 | else { 414 | // hrm... ? 415 | } 416 | ++count; 417 | } 418 | 419 | ctx->last_buf->buf->last_buf = 1; 420 | 421 | #if 0 422 | printf( "debugging buffer\n" ); 423 | count = 0; 424 | for( chain_link = ctx->chain; chain_link != NULL; chain_link = chain_link->next ) { 425 | size = ngx_buf_size(chain_link->buf); 426 | printf("buf size: %d, link: %d, buf: '", (int)size, count ); debug_string( (const char*)chain_link->buf->pos, (int)size ); printf("'\n"); 427 | ++count; 428 | } 429 | #endif 430 | 431 | if( has_last_buffer ) { 432 | esi_parser_free( ctx->parser ); 433 | ctx->parser = NULL; 434 | } 435 | 436 | //return ngx_http_next_body_filter(r, in); 437 | ngx_free_chain(r->pool, in); 438 | return ngx_http_next_body_filter(r, ctx->chain); 439 | } 440 | 441 | static void * 442 | ngx_http_esi_create_main_conf(ngx_conf_t *cf) 443 | { 444 | ngx_http_esi_main_conf_t *smcf; 445 | 446 | smcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_esi_main_conf_t)); 447 | if (smcf == NULL) { 448 | return NGX_CONF_ERROR; 449 | } 450 | 451 | smcf->commands.pool = cf->pool; 452 | smcf->commands.temp_pool = cf->temp_pool; 453 | 454 | if (ngx_hash_keys_array_init(&smcf->commands, NGX_HASH_SMALL) != NGX_OK) { 455 | return NGX_CONF_ERROR; 456 | } 457 | 458 | return smcf; 459 | } 460 | 461 | 462 | static char * 463 | ngx_http_esi_init_main_conf(ngx_conf_t *cf, void *conf) 464 | { 465 | ngx_http_esi_main_conf_t *smcf = conf; 466 | 467 | ngx_hash_init_t hash; 468 | 469 | 470 | hash.hash = &smcf->hash; 471 | hash.key = ngx_hash_key; 472 | hash.max_size = 1024; 473 | hash.bucket_size = ngx_cacheline_size; 474 | hash.name = "esi_command_hash"; 475 | hash.pool = cf->pool; 476 | hash.temp_pool = NULL; 477 | 478 | if (ngx_hash_init(&hash, smcf->commands.keys.elts, 479 | smcf->commands.keys.nelts) 480 | != NGX_OK) 481 | { 482 | return NGX_CONF_ERROR; 483 | } 484 | 485 | return NGX_CONF_OK; 486 | } 487 | 488 | static ngx_int_t 489 | ngx_http_esi_filter_init(ngx_conf_t *cf) 490 | { 491 | ngx_http_next_header_filter = ngx_http_top_header_filter; 492 | ngx_http_top_header_filter = ngx_http_esi_header_filter; 493 | 494 | ngx_http_next_body_filter = ngx_http_top_body_filter; 495 | ngx_http_top_body_filter = ngx_http_esi_body_filter; 496 | 497 | return NGX_OK; 498 | } 499 | -------------------------------------------------------------------------------- /ngx_http_esi_filter_module.h: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) Todd A. Fisher 3 | */ 4 | 5 | 6 | #ifndef _NGX_HTTP_ESI_FILTER_H_INCLUDED_ 7 | #define _NGX_HTTP_ESI_FILTER_H_INCLUDED_ 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include "ngx_esi_parser.h" 14 | #include 15 | #include 16 | 17 | typedef struct { 18 | ESIParser *parser; 19 | struct _ESITag *root_tag; /* If any tags are being actively processed there is always a root node */ 20 | struct _ESITag *open_tag; /* The deepest nested tag open */ 21 | ngx_http_request_t *request; 22 | ngx_chain_t *chain; /* store buffered content */ 23 | ngx_chain_t *last_buf; 24 | 25 | unsigned exception_raised:1; /* this is toggled to 1 if an exception is raised while processing an attempt tag */ 26 | unsigned ignore_tag:1; /* this is toggled to 1 when for some reason typically no exception was raised so the tags should not be processed */ 27 | 28 | } ngx_http_esi_ctx_t; 29 | 30 | #endif /* _NGX_HTTP_ESI_FILTER_H_INCLUDED_ */ 31 | -------------------------------------------------------------------------------- /test/config/nginx-esi.conf: -------------------------------------------------------------------------------- 1 | 2 | #user nobody; 3 | #worker_processes 1; 4 | 5 | daemon off; 6 | master_process off; 7 | debug_points abort; 8 | 9 | #error_log logs/error.log; 10 | #error_log logs/error.log notice; 11 | error_log logs/error.log info; 12 | #error_log logs/error.log debug; 13 | 14 | pid logs/nginx.pid; 15 | 16 | 17 | events { 18 | worker_connections 1024; 19 | } 20 | 21 | 22 | http { 23 | include mime.types; 24 | default_type application/octet-stream; 25 | 26 | #log_format main '$remote_addr - $remote_user [$time_local] $request ' 27 | # '"$status" $body_bytes_sent "$http_referer" ' 28 | # '"$http_user_agent" "$http_x_forwarded_for"'; 29 | 30 | #access_log logs/access.log main; 31 | 32 | sendfile on; 33 | #tcp_nopush on; 34 | 35 | #keepalive_timeout 0; 36 | keepalive_timeout 65; 37 | 38 | #gzip on; 39 | 40 | server { 41 | listen 9997; 42 | server_name localhost; 43 | 44 | #charset koi8-r; 45 | 46 | #access_log logs/host.access.log main; 47 | 48 | location / { 49 | root ../test/docroot/; 50 | index index.html index.htm; 51 | esi on; 52 | esi_types text/html; 53 | } 54 | 55 | error_page 404 /404.html; 56 | 57 | # redirect server error pages to the static page /50x.html 58 | # 59 | error_page 500 502 503 504 /50x.html; 60 | location = /50x.html { 61 | root html; 62 | } 63 | 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /test/docroot/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | The page is not found 6 | 7 | 80 | 81 | 82 | 83 |

nginx error!

84 | 85 |
86 | 87 |

The page you are looking for is not found.

88 | 89 |
90 |

Website Administrator

91 |
92 |

Something has triggered missing webpage on your 93 | website. This is the default 404 error page for 94 | nginx that is distributed with 95 | Fedora. It is located 96 | /usr/share/nginx/html/404.html

97 | 98 |

You should customize this error page for your own 99 | site or edit the error_page directive in 100 | the nginx configuration file 101 | /etc/nginx/nginx.conf.

102 | 103 |
104 |
105 | 106 |
107 | [ Powered by nginx ] 111 | 112 | [ Powered by Fedora ] 116 |
117 |
118 | 119 | 120 | -------------------------------------------------------------------------------- /test/docroot/50x.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | The page is temporarily unavailable 6 | 7 | 80 | 81 | 82 | 83 |

nginx error!

84 | 85 |
86 | 87 |

The page you are looking for is temporarily unavailable. Please try again later.

88 | 89 |
90 |

Website Administrator

91 |
92 |

Something has triggered an error on your 93 | website. This is the default error page for 94 | nginx that is distributed with 95 | Fedora. It is located 96 | /usr/share/nginx/html/50x.html

97 | 98 |

You should customize this error page for your own 99 | site or edit the error_page directive in 100 | the nginx configuration file 101 | /etc/nginx/nginx.conf.

102 | 103 |
104 |
105 | 106 |
107 | [ Powered by nginx ] 111 | 112 | [ Powered by Fedora ] 116 |
117 |
118 | 119 | 120 | -------------------------------------------------------------------------------- /test/docroot/content/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

testing whether markup is altered after passing through cache

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/docroot/content/500_with_failover.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

testing whether markup is altered after passing through cache

6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/docroot/content/500_with_failover_to_alt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

testing alt

6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /test/docroot/content/ajax_test_page.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

This is a test document for esi fragments being request using ajax

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/docroot/content/cookie_variable.html: -------------------------------------------------------------------------------- 1 |

This is the main weblog view

2 | Here's an article:
3 | 4 | -------------------------------------------------------------------------------- /test/docroot/content/foo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

This is a test document

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/docroot/content/include_in_include.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

This test will determine whether or not we can request multiple esi fragments within a single fragment

6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /test/docroot/content/malformed_transforms.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

testing whether markup is altered after passing through cache

6 |
7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /test/docroot/content/malformed_transforms.html-correct: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

testing whether markup is altered after passing through cache

6 |
7 | 8 | 9 | HTTP Request 10 | 11 | 12 | -------------------------------------------------------------------------------- /test/docroot/content/static-failover.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

This is the main weblog view

4 | Here's an article:
5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 |
www.example.com 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /test/docroot/content/test2.html: -------------------------------------------------------------------------------- 1 |
test2
2 | -------------------------------------------------------------------------------- /test/docroot/content/test3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

Testing whether markup within an attempt block is retained after passing through the cache

6 | 7 | 8 |

Some markup that should not be lost

9 | 10 |
11 | 12 |

Exception, without any includes

13 |
14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /test/docroot/esi_invalidate.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |

Include invalidating modules

4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /test/docroot/esi_max_age_varies.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

This is a test document

6 |
Some content before
7 |
Some content before
8 |
Some content before
9 | 10 | 11 | -------------------------------------------------------------------------------- /test/docroot/esi_mixed_content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

This is a test document

6 | 7 | 8 |
Some content before
9 |
10 | 11 | 12 | 13 |
14 | 15 | 16 | -------------------------------------------------------------------------------- /test/docroot/esi_test_content.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 |

This is a test document

6 |

7 | The only content you should see below this line is the string: 8 |

9 |
10 |     valid
11 |     test1
12 |     test2
13 |   
14 |
15 | 16 | invalid 17 | 18 | valid 19 | 20 | 21 | 22 | 23 | valid 24 | 25 | invalid 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /test/docroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/taf2/nginx-esi/0fe5af21d0669b95006bd985c09de510fe49367b/test/docroot/favicon.ico -------------------------------------------------------------------------------- /test/docroot/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sample Webcoolzone - Sample Webcoolzone 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |

sorry

99 |
100 |
101 | 102 | 105 |
106 | 107 | 108 | 178 | Print header 179 |
180 | 598 |
599 |
600 |
 
 
601 |
602 | 603 |
604 | 677 |
678 | Nice!!! Seal 679 |
680 |
681 |
682 | 683 | 686 | 687 | 688 | 689 | -------------------------------------------------------------------------------- /test/docroot/large-no-cache.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Sample Webcoolzone - Sample Webcoolzone 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 73 | 74 | 75 | 76 | 77 | 78 | 81 | 82 | 83 | 84 | 85 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |

sorry

99 |
100 |
101 | 102 | 105 |
106 | 107 | 108 | 178 | Print header 179 |
180 | 598 |
599 |
600 |
 
 
601 |
602 | 603 |
604 | 677 |
678 | Nice!!! Seal 679 |
680 |
681 |
682 | 683 | 686 | 687 | 688 | 689 | -------------------------------------------------------------------------------- /test/docroot/test1.html: -------------------------------------------------------------------------------- 1 |
test1
2 | -------------------------------------------------------------------------------- /test/docroot/test3.html: -------------------------------------------------------------------------------- 1 |
test3
2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /test/docroot/test_failover.html: -------------------------------------------------------------------------------- 1 |
test failover
2 | -------------------------------------------------------------------------------- /test/esi_filter_test.rb: -------------------------------------------------------------------------------- 1 | require "#{File.dirname(__FILE__)}/help" 2 | 3 | class EsiFilterTest < Test::Unit::TestCase 4 | 5 | def cached?( uri ) 6 | $esi_dispatcher.config.cache.get( uri, {} ) 7 | end 8 | 9 | 10 | def setup 11 | ESI::Server.start 12 | if !defined?($started) 13 | $started = true 14 | system("rake start") 15 | at_exit { system("rake stop") } 16 | end 17 | end 18 | 19 | =begin 20 | def test_more_web_server 21 | results = hit([ "http://localhost:9997/esi_test_content.html", "http://localhost:9997/esi_mixed_content.html"]) 22 | 23 | results.each do |res| 24 | assert_match $fragment_test1, res 25 | assert_no_match /', req.body, "Missing document head" 39 | assert_match $fragment_test1, req.body, "Fragment not found" 40 | assert_match $fragment_test2, req.body, "Fragment not found" 41 | assert_match '', req.body, "Missing document footer" 42 | assert_equal %{\n\n\n\n

This is a test document

\n \n valid\n \n
test1
\n\n \n \n
test2
\n\n\n\n}, req.body 43 | end 44 | end 45 | 46 | =begin 47 | def test_large_document 48 | Net::HTTP.start("localhost", 9997) do |h| 49 | req = h.get("/large-no-cache.html") 50 | assert_not_nil req 51 | assert_not_nil req.header 52 | assert_not_nil req.body 53 | assert_equal Net::HTTPOK, req.header.class 54 | assert_match $fragment_test1, req.body, "Fragment not found" 55 | # should have 37 fragments 56 | count = 0 57 | req.body.scan $fragment_test1 do|m| 58 | count += 1 59 | end 60 | assert_equal 18, count, "There should be 18 fragment_test 1's in the document" 61 | count = 0 62 | req.body.scan $fragment_test2 do|m| 63 | count += 1 64 | end 65 | assert_equal 19, count, "There should be 19 fragment_test 2's in the document" 66 | end 67 | end 68 | 69 | def test_reverse_proxy 70 | Net::HTTP.start("localhost", 9997) do |h| 71 | req = h.get("/content/foo.html") 72 | assert_not_nil req 73 | assert_not_nil req.header 74 | assert_not_nil req.body 75 | assert_equal Net::HTTPOK, req.header.class 76 | assert_match $fragment_test2, req.body, "Fragment not found" 77 | end 78 | end 79 | 80 | # test that we're including http headers as part of the request fragment cache 81 | def test_ajax_caching 82 | # start by making the request using the X-Requested-With Header 83 | Net::HTTP.start("localhost", 9997) do |h| 84 | req = h.get("/content/ajax_test_page.html", {"X-Requested-With" => "XMLHttpRequest"}) 85 | assert_not_nil req 86 | assert_not_nil req.header 87 | assert_not_nil req.body 88 | assert_equal Net::HTTPOK, req.header.class 89 | assert_match "XMLHttpRequest", req.body, "Did not get the expected response for Javascript, should see XMLHttpRequest in the response" 90 | end 91 | # now the content should be cached so make the request again without the javascript header to get the alternate version 92 | Net::HTTP.start("localhost", 9997) do |h| 93 | req = h.get("/content/ajax_test_page.html") 94 | assert_not_nil req 95 | assert_not_nil req.header 96 | assert_not_nil req.body 97 | assert_equal Net::HTTPOK, req.header.class 98 | assert_match "HTTP Request", req.body, "Did not get the expected response for noscript" 99 | end 100 | # make the ajax request one more time for good measure 101 | Net::HTTP.start("localhost", 9997) do |h| 102 | req = h.get("/content/ajax_test_page.html", {"X-Requested-With" => "XMLHttpRequest"}) 103 | assert_not_nil req 104 | assert_not_nil req.header 105 | assert_not_nil req.body 106 | assert_equal Net::HTTPOK, req.header.class 107 | assert_match "XMLHttpRequest", req.body, "Did not get the expected response for Javascript" 108 | end 109 | end 110 | 111 | # test that if a fragment has a test3<\/div>/, req.body, "assert fragment is included" 121 | assert_match /
test1<\/div>/, req.body, "assert inner fragment is included" 122 | end 123 | end 124 | 125 | # test whether the cache server alters the markup 126 | def test_markup 127 | Net::HTTP.start("localhost", 9997) do |h| 128 | req = h.get("/content/malformed_transforms.html") 129 | assert_not_nil req 130 | assert_not_nil req.header 131 | assert_not_nil req.body 132 | assert_equal Net::HTTPOK, req.header.class 133 | assert_equal File.open("#{DOCROOT}/content/malformed_transforms.html-correct").read, req.body 134 | end 135 | end 136 | 137 | def test_failing_fragments 138 | Net::HTTP.start("localhost", 9997) do |h| 139 | res = h.get("/content/500.html") 140 | assert_not_nil res 141 | assert_not_nil res.header 142 | assert_not_nil res.body 143 | assert_equal Net::HTTPOK, res.header.class, res.body 144 | end 145 | end 146 | 147 | def test_failing_fragments_can_failover 148 | Net::HTTP.start("localhost", 9997) do |h| 149 | res = h.get("/content/500_with_failover.html") 150 | assert_not_nil res 151 | assert_not_nil res.header 152 | assert_not_nil res.body 153 | assert_equal Net::HTTPOK, res.header.class, res.body 154 | assert_match /
test1<\/div>/, res.body, "The failover esi include should have been requested" 155 | end 156 | end 157 | 158 | def test_failing_fragments_can_failover_to_alt_attribute 159 | Net::HTTP.start("localhost", 9997) do |h| 160 | res = h.get("/content/500_with_failover_to_alt.html") 161 | assert_not_nil res 162 | assert_not_nil res.header 163 | assert_not_nil res.body 164 | assert_equal Net::HTTPOK, res.header.class, res.body 165 | assert_match /
test1<\/div>/, res.body, "The failover esi include should have been requested" 166 | end 167 | end 168 | 169 | def test_failing_response_from_surrogate 170 | Net::HTTP.start("localhost", 9997) do |h| 171 | res = h.get("/500") 172 | assert_not_nil res 173 | assert_not_nil res.header 174 | assert_not_nil res.body 175 | assert_equal "Internal Server Error", res.body 176 | assert_equal Net::HTTPInternalServerError, res.header.class, res.body 177 | end 178 | end 179 | 180 | def test_static_failover 181 | Net::HTTP.start("localhost", 9997) do |h| 182 | res = h.get("/content/static-failover.html") 183 | assert_not_nil res 184 | assert_not_nil res.header 185 | assert_not_nil res.body 186 | #puts res.body 187 | assert_equal Net::HTTPOK, res.header.class, res.body 188 | assert_match /
test1<\/div>/, res.body, "Normal esi include tags failed" 189 | assert_match /
test1<\/div>/, res.body, "Normal esi include tags failed" 190 | assert_match /www.example.com <\/a>/, res.body, "Static Failover failed" 191 | end 192 | end 193 | 194 | def test_markup_in_attempt_passed_through 195 | Net::HTTP.start("localhost", 9997) do |h| 196 | res = h.get("/content/test3.html") 197 | assert_not_nil res 198 | assert_not_nil res.header 199 | assert_not_nil res.body 200 | assert_equal Net::HTTPOK, res.header.class 201 | assert_match /

Some markup that should not be lost<\/p>/, res.body 202 | end 203 | end 204 | =end 205 | 206 | =begin 207 | def test_surrogate_control_header 208 | # first tell the esi handler to use the surrogate control header 209 | $esi_dispatcher.config.config.send(:store,:enable_for_surrogate_only, true) 210 | Net::HTTP.start("localhost", 9997) do |h| 211 | res = h.get("/404") 212 | assert_not_nil res 213 | assert_not_nil res.header 214 | assert_not_nil res.body 215 | assert_equal Net::HTTPNotFound, res.header.class 216 | assert_match /#{$fragment_test1}/, res.body 217 | end 218 | $esi_dispatcher.config.config.send(:store,:enable_for_surrogate_only, nil) 219 | 220 | Net::HTTP.start("localhost", 9997) do |h| 221 | res = h.get("/404-no-surrogate") 222 | assert_not_nil res 223 | assert_not_nil res.header 224 | assert_not_nil res.body 225 | assert_equal Net::HTTPNotFound, res.header.class 226 | assert_no_match /#{$fragment_test1}/, res.body 227 | end 228 | end 229 | =end 230 | 231 | =begin 232 | def test_invalidation 233 | Net::HTTP.start("localhost", 9997) do |h| 234 | res = h.get("/esi_test_content.html") 235 | assert_not_nil res 236 | assert_not_nil res.header 237 | assert_not_nil res.body 238 | assert_equal Net::HTTPOK, res.header.class 239 | assert_match $fragment_test1, res.body, "Fragment not found" 240 | assert_match $fragment_test2, res.body, "Fragment not found" 241 | end 242 | 243 | assert cached?( FRAGMENT_TEST1_URI ), "Error Fragment should be cached!" 244 | assert cached?( FRAGMENT_TEST2_URI ), "Error Fragment should be cached!" 245 | 246 | Net::HTTP.start("localhost", 9997) do |h| 247 | res = h.get("/esi_invalidate.html") 248 | assert_not_nil res 249 | assert_not_nil res.header 250 | assert_not_nil res.body 251 | assert_equal Net::HTTPOK, res.header.class 252 | end 253 | 254 | assert !cached?( FRAGMENT_TEST1_URI ), "Error Fragment 1 should not be cached!" 255 | assert !cached?( FRAGMENT_TEST2_URI ), "Error Fragment 2 should not be cached!" 256 | end 257 | 258 | def test_max_age_include_attribute_cache_ttl 259 | Net::HTTP.start("localhost", 9997) do |h| 260 | res = h.get("/esi_max_age_varies.html") 261 | assert_not_nil res 262 | assert_not_nil res.header 263 | assert_not_nil res.body 264 | assert_equal Net::HTTPOK, res.header.class 265 | assert_match $fragment_test1, res.body, "Fragment not found" 266 | assert_match $fragment_test2, res.body, "Fragment not found" 267 | end 268 | 269 | assert cached?( FRAGMENT_TEST1_URI ), "Error Fragment should be cached!" 270 | assert !cached?( FRAGMENT_TEST2_URI ), "Error Fragment should not be cached!" 271 | end 272 | 273 | =end 274 | 275 | end 276 | 277 | -------------------------------------------------------------------------------- /test/help.rb: -------------------------------------------------------------------------------- 1 | # modified version from mongrel 2 | require 'test/unit' 3 | require 'net/http' 4 | require 'rubygems' 5 | require 'mongrel' # use this to service some dynamic requests 6 | require 'daemons' 7 | 8 | DOCROOT = "#{File.dirname(__FILE__)}/docroot/" 9 | 10 | # Either takes a string to do a get request against, or a tuple of [URI, HTTP] where 11 | # HTTP is some kind of Net::HTTP request object (POST, HEAD, etc.) 12 | def hit(uris) 13 | results = [] 14 | uris.each do |u| 15 | res = nil 16 | 17 | assert_nothing_raised do 18 | if u.kind_of? String 19 | res = Net::HTTP.get(URI.parse(u)) 20 | else 21 | url = URI.parse(u[0]) 22 | res = Net::HTTP.new(url.host, url.port).start {|h| h.request(u[1]) } 23 | end 24 | end 25 | 26 | assert_not_nil res, "Didn't get a response: #{u}" 27 | results << res 28 | end 29 | 30 | return results 31 | end 32 | 33 | def check_status(results, expecting) 34 | results.each do |res| 35 | assert(res.kind_of?(expecting), "Didn't get #{expecting}, got: #{res.class}") 36 | end 37 | end 38 | 39 | 40 | require 'singleton' 41 | 42 | TEST_COOKIE1 = "s_cc=true; s_sq=%5B%5BB%5D%5D; _session_id=367df8904d23ee48ddac56012b9d509f;".freeze 43 | TEST_COOKIE2 = "rhg_gsid=83d7e46425b431c6651e2beb3e4fd9e41175970072%3A; cookies_enabled=true;".freeze 44 | TEST_COOKIE3 = "RTOKEN=dG9kZGZAc2ltb2hlYWx0aC5jb20%3D; rhg_sa=xbSssuHwmP5rohZ0Pcu%2BmaU3bSFU%2Br1eBq4iZgTd%2BbQ%3D; edn=Z%25A16W%25E9F%25D4f%25A5%2522j%255D%25DA%2528%2585%2586; ems=%25F2%25A9%25FBlZ%2594.a%253D%250A%251F%2506%25BD%251B%25BA%25EC; av=13; s_se_sig=dG9kZGZAc2ltb2hlYWx0aC5jb20%253D; rhg_css_to_warn=1175974191515".freeze 45 | FRAGMENT_TEST1_URI = "http://127.0.0.1:9998/fragments/test1.html".freeze 46 | FRAGMENT_TEST2_URI = "http://127.0.0.1:9999/content/test2.html".freeze 47 | 48 | class Basic404Handler < Mongrel::HttpHandler 49 | include Mongrel::HttpHandlerPlugin 50 | def process(request, response) 51 | response.start(404,true) do |head,out| 52 | head["Surrogate-Control"] = %q(max-age=0, content="ESI/1.0 ESI-INV/1.0") 53 | out << %q() 54 | end 55 | end 56 | end 57 | 58 | class Basic404HandlerWithoutHeader < Mongrel::HttpHandler 59 | include Mongrel::HttpHandlerPlugin 60 | def process(request, response) 61 | response.start(404,true) do |head,out| 62 | #head["Surrogate-Control"] = %q(max-age=0, content="ESI/1.0 ESI-INV/1.0") 63 | out << %q() 64 | end 65 | end 66 | end 67 | 68 | class Basic500Handler < Mongrel::HttpHandler 69 | include Mongrel::HttpHandlerPlugin 70 | def process(request, response) 71 | response.start(500,true) do |head,out| 72 | head["Surrogate-Control"] = %q(max-age=0, content="ESI/1.0 ESI-INV/1.0") 73 | out << %q(Internal Server Error) 74 | end 75 | end 76 | end 77 | 78 | class AjaxCacheHandler < Mongrel::HttpHandler 79 | include Mongrel::HttpHandlerPlugin 80 | def process(request, response) 81 | response.start(200,true) do |head,out| 82 | # return hello if requested with 83 | if request.params["HTTP_X_REQUESTED_WITH"] == "XMLHttpRequest" 84 | out << "XMLHttpRequest Request" 85 | else 86 | out << "HTTP Request" 87 | end 88 | end 89 | end 90 | end 91 | 92 | class InvalidateHandler < Mongrel::HttpHandler 93 | include Mongrel::HttpHandlerPlugin 94 | def process(request, response) 95 | response.start(200,true) do |head,out| 96 | head["Surrogate-Control"] = %q(max-age=0, content="ESI/1.0 ESI-INV/1.0") 97 | 98 | out << %Q( 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | ) 122 | end 123 | end 124 | end 125 | 126 | $fragment_test1 = File.open("#{DOCROOT}/test1.html").read 127 | $fragment_test2 = File.open("#{DOCROOT}/content/test2.html").read 128 | 129 | module ESI 130 | class Server 131 | include Singleton 132 | 133 | CONFIG = [ 134 | { :host_port => { :host => '127.0.0.1', :port => 9999 }, 135 | :handlers => [ 136 | { :uri => '/', :handler => Mongrel::DirHandler.new(DOCROOT) }, 137 | { :uri => '/get_test', :handler => AjaxCacheHandler.new(DOCROOT) } 138 | ] 139 | }, 140 | { :host_port => { :host => '127.0.0.1', :port => 9998 }, 141 | :handlers => [ 142 | { :uri => "/", :handler => Mongrel::DirHandler.new(DOCROOT) }, 143 | { :uri => "/fragments/", :handler => Mongrel::DirHandler.new(DOCROOT) }, 144 | { :uri => '/404', :handler => Basic404Handler.new }, 145 | { :uri => '/404-no-surrogate', :handler => Basic404HandlerWithoutHeader.new }, 146 | { :uri => '/invalidate', :handler => InvalidateHandler.new }, 147 | { :uri => '/500', :handler => Basic500Handler.new } 148 | ] 149 | } 150 | ] 151 | 152 | def self.start 153 | ESI::Server.instance 154 | end 155 | 156 | def initialize 157 | @servers = CONFIG.collect do|conf| 158 | Mongrel::Configurator.new conf[:host_port] do 159 | listener do 160 | conf[:handlers].each do|handler| 161 | uri handler[:uri], :handler => handler[:handler] 162 | end 163 | end 164 | end 165 | end 166 | 167 | @servers.each { |server| server.run } 168 | at_exit do 169 | @servers.each { |server| server.stop } 170 | end 171 | end 172 | end 173 | end 174 | --------------------------------------------------------------------------------