text: 
  /*
   * Copyright (C) Igor Sysoev
   * Copyright (C) Nginx, Inc.
   */
  
  
  #include <ngx_config.h>
  #include <ngx_core.h>
  #include <ngx_http.h>
  
  #define NGX_HTTP_SSI_ERROR          1
  
  #define NGX_HTTP_SSI_DATE_LEN       2048
  
  #define NGX_HTTP_SSI_ADD_PREFIX     1
  #define NGX_HTTP_SSI_ADD_ZERO       2
  
  
  typedef struct {
      ngx_flag_t    enable;
      ngx_flag_t    silent_errors;
      ngx_flag_t    ignore_recycled_buffers;
      ngx_flag_t    last_modified;
  
      ngx_hash_t    types;
  
      size_t        min_file_chunk;
      size_t        value_len;
  
      ngx_array_t  *types_keys;
  } ngx_http_ssi_loc_conf_t;
  
  
  typedef struct {
      ngx_str_t     name;
      ngx_uint_t    key;
      ngx_str_t     value;
  } ngx_http_ssi_var_t;
  
  
  typedef struct {
      ngx_str_t     name;
      ngx_chain_t  *bufs;
      ngx_uint_t    count;
  } ngx_http_ssi_block_t;
  
  
  typedef enum {
      ssi_start_state = 0,
      ssi_tag_state,
      ssi_comment0_state,
      ssi_comment1_state,
      ssi_sharp_state,
      ssi_precommand_state,
      ssi_command_state,
      ssi_preparam_state,
      ssi_param_state,
      ssi_preequal_state,
      ssi_prevalue_state,
      ssi_double_quoted_value_state,
      ssi_quoted_value_state,
      ssi_quoted_symbol_state,
      ssi_postparam_state,
      ssi_comment_end0_state,
      ssi_comment_end1_state,
      ssi_error_state,
      ssi_error_end0_state,
      ssi_error_end1_state
  } ngx_http_ssi_state_e;
  
  
  static ngx_int_t ngx_http_ssi_output(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx);
  static void ngx_http_ssi_buffered(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx);
  static ngx_int_t ngx_http_ssi_parse(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx);
  static ngx_str_t *ngx_http_ssi_get_variable(ngx_http_request_t *r,
      ngx_str_t *name, ngx_uint_t key);
  static ngx_int_t ngx_http_ssi_evaluate_string(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx, ngx_str_t *text, ngx_uint_t flags);
  static ngx_int_t ngx_http_ssi_regex_match(ngx_http_request_t *r,
      ngx_str_t *pattern, ngx_str_t *str);
  
  static ngx_int_t ngx_http_ssi_include(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
  static ngx_int_t ngx_http_ssi_stub_output(ngx_http_request_t *r, void *data,
      ngx_int_t rc);
  static ngx_int_t ngx_http_ssi_set_variable(ngx_http_request_t *r, void *data,
      ngx_int_t rc);
  static ngx_int_t ngx_http_ssi_echo(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
  static ngx_int_t ngx_http_ssi_config(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
  static ngx_int_t ngx_http_ssi_set(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
  static ngx_int_t ngx_http_ssi_if(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
  static ngx_int_t ngx_http_ssi_else(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
  static ngx_int_t ngx_http_ssi_endif(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
  static ngx_int_t ngx_http_ssi_block(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
  static ngx_int_t ngx_http_ssi_endblock(ngx_http_request_t *r,
      ngx_http_ssi_ctx_t *ctx, ngx_str_t **params);
  
  static ngx_int_t ngx_http_ssi_date_gmt_local_variable(ngx_http_request_t *r,
      ngx_http_variable_value_t *v, uintptr_t gmt);
  
  static ngx_int_t ngx_http_ssi_preconfiguration(ngx_conf_t *cf);
  static void *ngx_http_ssi_create_main_conf(ngx_conf_t *cf);
  static char *ngx_http_ssi_init_main_conf(ngx_conf_t *cf, void *conf);
  static void *ngx_http_ssi_create_loc_conf(ngx_conf_t *cf);
  static char *ngx_http_ssi_merge_loc_conf(ngx_conf_t *cf,
      void *parent, void *child);
  static ngx_int_t ngx_http_ssi_filter_init(ngx_conf_t *cf);
  
  
  static ngx_command_t  ngx_http_ssi_filter_commands[] = {
  
      { ngx_string("ssi"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                          |NGX_CONF_FLAG,
        ngx_conf_set_flag_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ssi_loc_conf_t, enable),
        NULL },
  
      { ngx_string("ssi_silent_errors"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
        ngx_conf_set_flag_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ssi_loc_conf_t, silent_errors),
        NULL },
  
      { ngx_string("ssi_ignore_recycled_buffers"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
        ngx_conf_set_flag_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ssi_loc_conf_t, ignore_recycled_buffers),
        NULL },
  
      { ngx_string("ssi_min_file_chunk"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_size_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ssi_loc_conf_t, min_file_chunk),
        NULL },
  
      { ngx_string("ssi_value_length"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
        ngx_conf_set_size_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ssi_loc_conf_t, value_len),
        NULL },
  
      { ngx_string("ssi_types"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
        ngx_http_types_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ssi_loc_conf_t, types_keys),
        &ngx_http_html_default_types[0] },
  
      { ngx_string("ssi_last_modified"),
        NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
        ngx_conf_set_flag_slot,
        NGX_HTTP_LOC_CONF_OFFSET,
        offsetof(ngx_http_ssi_loc_conf_t, last_modified),
        NULL },
  
        ngx_null_command
  };
  
  
  
  static ngx_http_module_t  ngx_http_ssi_filter_module_ctx = {
      ngx_http_ssi_preconfiguration,         /* preconfiguration */
      ngx_http_ssi_filter_init,              /* postconfiguration */
  
      ngx_http_ssi_create_main_conf,         /* create main configuration */
      ngx_http_ssi_init_main_conf,           /* init main configuration */
  
      NULL,                                  /* create server configuration */
      NULL,                                  /* merge server configuration */
  
      ngx_http_ssi_create_loc_conf,          /* create location configuration */
      ngx_http_ssi_merge_loc_conf            /* merge location configuration */
  };
  
  
  /*
  表6-1  默认即编译进Nginx的HTTP过滤模块
  ┏━━━━━━━━━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
  ┃默认即编译进Nginx的HTTP过滤模块     ┃    功能                                                          ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃                                    ┃  仅对HTTP头部做处理. 在返回200成功时,根据请求中If-              ┃
  ┃                                    ┃Modified-Since或者If-Unmodified-Since头部取得浏览器缓存文件的时   ┃
  ┃ngx_http_not_modified_filter_module ┃                                                                  ┃
  ┃                                    ┃间,再分析返回用户文件的最后修改时间,以此决定是否直接发送304     ┃
  ┃                                    ┃ Not Modified响应给用户                                           ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃                                    ┃  处理请求中的Range信息,根据Range中的要求返回文件的一部分给      ┃
  ┃ngx_http_range_body_filter_module   ┃                                                                  ┃
  ┃                                    ┃用户                                                              ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃                                    ┃  仅对HTTP包体做处理. 将用户发送的ngx_chain_t结构的HTTP包         ┃
  ┃                                    ┃体复制到新的ngx_chain_t结构中(都是各种指针的复制,不包括实际     ┃
  ┃ngx_http_copy_filter_module         ┃                                                                  ┃
  ┃                                    ┃HTTP响应内容),后续的HTTP过滤模块处埋的ngx_chain_t类型的成       ┃
  ┃                                    ┃员都是ngx_http_copy_filter_module模块处理后的变量                 ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃                                    ┃  仅对HTTP头部做处理. 允许通过修改nginx.conf配置文件,在返回      ┃
  ┃ngx_http_headers_filter_module      ┃                                                                  ┃
  ┃                                    ┃给用户的响应中添加任意的HTTP头部                                  ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃                                    ┃  仅对HTTP头部做处理. 这就是执行configure命令时提到的http_        ┃
  ┃ngx_http_userid_filter_module       ┃                                                                  ┃
  ┃                                    ┃userid module模块,它基于cookie提供了简单的认证管理功能           ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃                                    ┃  可以将文本类型返回给用户的响应包,按照nginx．conf中的配置重新   ┃
  ┃ngx_http_charset_filter_module      ┃                                                                  ┃
  ┃                                    ┃进行编码,再返回给用户                                            ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃                                    ┃  支持SSI(Server Side Include,服务器端嵌入)功能,将文件内容包  ┃
  ┃ngx_http_ssi_filter_module          ┃                                                                  ┃
  ┃                                    ┃含到网页中并返回给用户                                            ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃                                    ┃  仅对HTTP包体做处理. 5.5.2节详细介绍过该过滤模块. 它仅应用于     ┃
  ┃ngx_http_postpone_filter_module     ┃subrequest产生的子请求. 它使得多个子请求同时向客户端发送响应时    ┃
  ┃                                    ┃能够有序,所谓的“有序”是揩按照构造子请求的顺序发送响应            ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃                                    ┃  对特定的HTTP响应包体(如网页或者文本文件)进行gzip压缩,再      ┃
  ┃ngx_http_gzip_filter_module         ┃                                                                  ┃
  ┃                                    ┃把压缩后的内容返回给用户                                          ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃ngx_http_range_header_filter_module ┃  支持range协议                                                   ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃ngx_http_chunked_filter_module      ┃  支持chunk编码                                                   ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃                                    ┃  仅对HTTP头部做处理. 该过滤模块将会把r->headers out结构体        ┃
  ┃                                    ┃中的成员序列化为返回给用户的HTTP响应字符流,包括响应行(如         ┃
  ┃ngx_http_header_filter_module       ┃                                                                  ┃
  ┃                                    ┃HTTP/I.1 200 0K)和响应头部,并通过调用ngx_http_write filter       ┃
  ┃                                    ┃ module过滤模块中的过滤方法直接将HTTP包头发送到客户端             ┃
  ┣━━━━━━━━━━━━━━━━━━╋━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫
  ┃ngx_http_write_filter_module        ┃  仅对HTTP包体做处理. 该模块负责向客户端发送HTTP响应              ┃
  ┗━━━━━━━━━━━━━━━━━━┻━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛
  
  */ngx_module_t  ngx_http_ssi_filter_module = {
      NGX_MODULE_V1,
      &ngx_http_ssi_filter_module_ctx,       /* module context */
      ngx_http_ssi_filter_commands,          /* module directives */
      NGX_HTTP_MODULE,                       /* module type */
      NULL,                                  /* init master */
      NULL,                                  /* init module */
      NULL,                                  /* init process */
      NULL,                                  /* init thread */
      NULL,                                  /* exit thread */
      NULL,                                  /* exit process */
      NULL,                                  /* exit master */
      NGX_MODULE_V1_PADDING
  };
  
  
  static ngx_http_output_header_filter_pt  ngx_http_next_header_filter;
  static ngx_http_output_body_filter_pt    ngx_http_next_body_filter;
  
  
  static u_char ngx_http_ssi_string[] = "<!--";
  
  static ngx_str_t ngx_http_ssi_none = ngx_string("(none)");
  static ngx_str_t ngx_http_ssi_timefmt = ngx_string("%A, %d-%b-%Y %H:%M:%S %Z");
  static ngx_str_t ngx_http_ssi_null_string = ngx_null_string;
  
  
  #define  NGX_HTTP_SSI_INCLUDE_VIRTUAL  0
  #define  NGX_HTTP_SSI_INCLUDE_FILE     1
  #define  NGX_HTTP_SSI_INCLUDE_WAIT     2
  #define  NGX_HTTP_SSI_INCLUDE_SET      3
  #define  NGX_HTTP_SSI_INCLUDE_STUB     4
  
  #define  NGX_HTTP_SSI_ECHO_VAR         0
  #define  NGX_HTTP_SSI_ECHO_DEFAULT     1
  #define  NGX_HTTP_SSI_ECHO_ENCODING    2
  
  #define  NGX_HTTP_SSI_CONFIG_ERRMSG    0
  #define  NGX_HTTP_SSI_CONFIG_TIMEFMT   1
  
  #define  NGX_HTTP_SSI_SET_VAR          0
  #define  NGX_HTTP_SSI_SET_VALUE        1
  
  #define  NGX_HTTP_SSI_IF_EXPR          0
  
  #define  NGX_HTTP_SSI_BLOCK_NAME       0
  
  
  static ngx_http_ssi_param_t  ngx_http_ssi_include_params[] = {
      { ngx_string("virtual"), NGX_HTTP_SSI_INCLUDE_VIRTUAL, 0, 0 },
      { ngx_string("file"), NGX_HTTP_SSI_INCLUDE_FILE, 0, 0 },
      { ngx_string("wait"), NGX_HTTP_SSI_INCLUDE_WAIT, 0, 0 },
      { ngx_string("set"), NGX_HTTP_SSI_INCLUDE_SET, 0, 0 },
      { ngx_string("stub"), NGX_HTTP_SSI_INCLUDE_STUB, 0, 0 },
      { ngx_null_string, 0, 0, 0 }
  };
  
  
  static ngx_http_ssi_param_t  ngx_http_ssi_echo_params[] = {
      { ngx_string("var"), NGX_HTTP_SSI_ECHO_VAR, 1, 0 },
      { ngx_string("default"), NGX_HTTP_SSI_ECHO_DEFAULT, 0, 0 },
      { ngx_string("encoding"), NGX_HTTP_SSI_ECHO_ENCODING, 0, 0 },
      { ngx_null_string, 0, 0, 0 }
  };
  
  
  static ngx_http_ssi_param_t  ngx_http_ssi_config_params[] = {
      { ngx_string("errmsg"), NGX_HTTP_SSI_CONFIG_ERRMSG, 0, 0 },
      { ngx_string("timefmt"), NGX_HTTP_SSI_CONFIG_TIMEFMT, 0, 0 },
      { ngx_null_string, 0, 0, 0 }
  };
  
  
  static ngx_http_ssi_param_t  ngx_http_ssi_set_params[] = {
      { ngx_string("var"), NGX_HTTP_SSI_SET_VAR, 1, 0 },
      { ngx_string("value"), NGX_HTTP_SSI_SET_VALUE, 1, 0 },
      { ngx_null_string, 0, 0, 0 }
  };
  
  
  static ngx_http_ssi_param_t  ngx_http_ssi_if_params[] = {
      { ngx_string("expr"), NGX_HTTP_SSI_IF_EXPR, 1, 0 },
      { ngx_null_string, 0, 0, 0 }
  };
  
  
  static ngx_http_ssi_param_t  ngx_http_ssi_block_params[] = {
      { ngx_string("name"), NGX_HTTP_SSI_BLOCK_NAME, 1, 0 },
      { ngx_null_string, 0, 0, 0 }
  };
  
  
  static ngx_http_ssi_param_t  ngx_http_ssi_no_params[] = {
      { ngx_null_string, 0, 0, 0 }
  };
  
  
  static ngx_http_ssi_command_t  ngx_http_ssi_commands[] = {
      { ngx_string("include"), ngx_http_ssi_include,
                         ngx_http_ssi_include_params, 0, 0, 1 },//嵌入其他配置文件
      { ngx_string("echo"), ngx_http_ssi_echo,
                         ngx_http_ssi_echo_params, 0, 0, 0 },
      { ngx_string("config"), ngx_http_ssi_config,
                         ngx_http_ssi_config_params, 0, 0, 0 },
      { ngx_string("set"), ngx_http_ssi_set, ngx_http_ssi_set_params, 0, 0, 0 },
  
      { ngx_string("if"), ngx_http_ssi_if, ngx_http_ssi_if_params, 0, 0, 0 },
      { ngx_string("elif"), ngx_http_ssi_if, ngx_http_ssi_if_params,
                         NGX_HTTP_SSI_COND_IF, 0, 0 },
      { ngx_string("else"), ngx_http_ssi_else, ngx_http_ssi_no_params,
                         NGX_HTTP_SSI_COND_IF, 0, 0 },
      { ngx_string("endif"), ngx_http_ssi_endif, ngx_http_ssi_no_params,
                         NGX_HTTP_SSI_COND_ELSE, 0, 0 },
  
      { ngx_string("block"), ngx_http_ssi_block,
                         ngx_http_ssi_block_params, 0, 0, 0 },
      { ngx_string("endblock"), ngx_http_ssi_endblock,
                         ngx_http_ssi_no_params, 0, 1, 0 },
  
      { ngx_null_string, NULL, NULL, 0, 0, 0 }
  };
  
  
  static ngx_http_variable_t  ngx_http_ssi_vars[] = {
  
      { ngx_string("date_local"), NULL, ngx_http_ssi_date_gmt_local_variable, 0,
        NGX_HTTP_VAR_NOCACHEABLE, 0 },
  
      { ngx_string("date_gmt"), NULL, ngx_http_ssi_date_gmt_local_variable, 1,
        NGX_HTTP_VAR_NOCACHEABLE, 0 },
  
        ngx_http_null_variable
  };
  
  
  
  static ngx_int_t
  ngx_http_ssi_header_filter(ngx_http_request_t *r)
  {
      ngx_http_ssi_ctx_t       *ctx;
      ngx_http_ssi_loc_conf_t  *slcf;
  
      slcf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);
  
      if (!slcf->enable
          || r->headers_out.content_length_n == 0
          || ngx_http_test_content_type(r, &slcf->types) == NULL)
      {
          return ngx_http_next_header_filter(r);
      }
  
      ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ssi_ctx_t));
      if (ctx == NULL) {
          return NGX_ERROR;
      }
  
      ngx_http_set_ctx(r, ctx, ngx_http_ssi_filter_module);
  
  
      ctx->value_len = slcf->value_len;
      ctx->last_out = &ctx->out;
  
      ctx->encoding = NGX_HTTP_SSI_ENTITY_ENCODING;
      ctx->output = 1;
  
      ctx->params.elts = ctx->params_array;
      ctx->params.size = sizeof(ngx_table_elt_t);
      ctx->params.nalloc = NGX_HTTP_SSI_PARAMS_N;
      ctx->params.pool = r->pool;
  
      ctx->timefmt = ngx_http_ssi_timefmt;
      ngx_str_set(&ctx->errmsg,
                  "[an error occurred while processing the directive]");
  
      r->filter_need_in_memory = 1;
  
      if (r == r->main) {
          ngx_http_clear_content_length(r);
          ngx_http_clear_accept_ranges(r);
  
          r->preserve_body = 1;
  
          if (!slcf->last_modified) {
              ngx_http_clear_last_modified(r);
              ngx_http_clear_etag(r);
  
          } else {
              ngx_http_weak_etag(r);
          }
      }
  
      return ngx_http_next_header_filter(r);
  }
  
  
  static ngx_int_t
  ngx_http_ssi_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
  {
      size_t                     len;
      ngx_int_t                  rc;
      ngx_buf_t                 *b;
      ngx_uint_t                 i, index;
      ngx_chain_t               *cl, **ll;
      ngx_table_elt_t           *param;
      ngx_http_ssi_ctx_t        *ctx, *mctx;
      ngx_http_ssi_block_t      *bl;
      ngx_http_ssi_param_t      *prm;
      ngx_http_ssi_command_t    *cmd;
      ngx_http_ssi_loc_conf_t   *slcf;
      ngx_http_ssi_main_conf_t  *smcf;
      ngx_str_t                 *params[NGX_HTTP_SSI_MAX_PARAMS + 1];
  
      ctx = ngx_http_get_module_ctx(r, ngx_http_ssi_filter_module);
  
      if (ctx == NULL
          || (in == NULL
              && ctx->buf == NULL
              && ctx->in == NULL
              && ctx->busy == NULL))
      {
          return ngx_http_next_body_filter(r, in);
      }
  
      /* add the incoming chain to the chain ctx->in */
  
      if (in) {
          if (ngx_chain_add_copy(r->pool, &ctx->in, in) != NGX_OK) {
              return NGX_ERROR;
          }
      }
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "http ssi filter \"%V?%V\"", &r->uri, &r->args);
  
      if (ctx->wait) {
  
          if (r != r->connection->data) {
              ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                             "http ssi filter wait \"%V?%V\" non-active",
                             &ctx->wait->uri, &ctx->wait->args);
  
              return NGX_AGAIN;
          }
  
          if (ctx->wait->done) {
              ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                             "http ssi filter wait \"%V?%V\" done",
                             &ctx->wait->uri, &ctx->wait->args);
  
              ctx->wait = NULL;
  
          } else {
              ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                             "http ssi filter wait \"%V?%V\"",
                             &ctx->wait->uri, &ctx->wait->args);
  
              return ngx_http_next_body_filter(r, NULL);
          }
      }
  
      slcf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);
  
      while (ctx->in || ctx->buf) {
  
          if (ctx->buf == NULL) {
              ctx->buf = ctx->in->buf;
              ctx->in = ctx->in->next;
              ctx->pos = ctx->buf->pos;
          }
  
          if (ctx->state == ssi_start_state) {
              ctx->copy_start = ctx->pos;
              ctx->copy_end = ctx->pos;
          }
  
          b = NULL;
  
          while (ctx->pos < ctx->buf->last) {
  
              ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                             "saved: %uz state: %ui", ctx->saved, ctx->state);
  
              rc = ngx_http_ssi_parse(r, ctx);
  
              ngx_log_debug4(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                             "parse: %i, looked: %uz %p-%p",
                             rc, ctx->looked, ctx->copy_start, ctx->copy_end);
  
              if (rc == NGX_ERROR) {
                  return rc;
              }
  
              if (ctx->copy_start != ctx->copy_end) {
  
                  if (ctx->output) {
  
                      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                                     "saved: %uz", ctx->saved);
  
                      if (ctx->saved) {
  
                          if (ctx->free) {
                              cl = ctx->free;
                              ctx->free = ctx->free->next;
                              b = cl->buf;
                              ngx_memzero(b, sizeof(ngx_buf_t));
  
                          } else {
                              b = ngx_calloc_buf(r->pool);
                              if (b == NULL) {
                                  return NGX_ERROR;
                              }
  
                              cl = ngx_alloc_chain_link(r->pool);
                              if (cl == NULL) {
                                  return NGX_ERROR;
                              }
  
                              cl->buf = b;
                          }
  
                          b->memory = 1;
                          b->pos = ngx_http_ssi_string;
                          b->last = ngx_http_ssi_string + ctx->saved;
  
                          *ctx->last_out = cl;
                          ctx->last_out = &cl->next;
  
                          ctx->saved = 0;
                      }
  
                      if (ctx->free) {
                          cl = ctx->free;
                          ctx->free = ctx->free->next;
                          b = cl->buf;
  
                      } else {
                          b = ngx_alloc_buf(r->pool);
                          if (b == NULL) {
                              return NGX_ERROR;
                          }
  
                          cl = ngx_alloc_chain_link(r->pool);
                          if (cl == NULL) {
                              return NGX_ERROR;
                          }
  
                          cl->buf = b;
                      }
  
                      ngx_memcpy(b, ctx->buf, sizeof(ngx_buf_t));
  
                      b->pos = ctx->copy_start;
                      b->last = ctx->copy_end;
                      b->shadow = NULL;
                      b->last_buf = 0;
                      b->recycled = 0;
  
                      if (b->in_file) {
                          if (slcf->min_file_chunk < (size_t) (b->last - b->pos))
                          {
                              b->file_last = b->file_pos
                                                     + (b->last - ctx->buf->pos);
                              b->file_pos += b->pos - ctx->buf->pos;
  
                          } else {
                              b->in_file = 0;
                          }
                      }
  
                      cl->next = NULL;
                      *ctx->last_out = cl;
                      ctx->last_out = &cl->next;
  
                  } else {
                      if (ctx->block
                          && ctx->saved + (ctx->copy_end - ctx->copy_start))
                      {
                          b = ngx_create_temp_buf(r->pool,
                                 ctx->saved + (ctx->copy_end - ctx->copy_start));
  
                          if (b == NULL) {
                              return NGX_ERROR;
                          }
  
                          if (ctx->saved) {
                              b->last = ngx_cpymem(b->pos, ngx_http_ssi_string,
                                                   ctx->saved);
                          }
  
                          b->last = ngx_cpymem(b->last, ctx->copy_start,
                                               ctx->copy_end - ctx->copy_start);
  
                          cl = ngx_alloc_chain_link(r->pool);
                          if (cl == NULL) {
                              return NGX_ERROR;
                          }
  
                          cl->buf = b;
                          cl->next = NULL;
  
                          b = NULL;
  
                          mctx = ngx_http_get_module_ctx(r->main,
                                                     ngx_http_ssi_filter_module);
                          bl = mctx->blocks->elts;
                          for (ll = &bl[mctx->blocks->nelts - 1].bufs;
                               *ll;
                               ll = &(*ll)->next)
                          {
                              /* void */
                          }
  
                          *ll = cl;
                      }
  
                      ctx->saved = 0;
                  }
              }
  
              if (ctx->state == ssi_start_state) {
                  ctx->copy_start = ctx->pos;
                  ctx->copy_end = ctx->pos;
  
              } else {
                  ctx->copy_start = NULL;
                  ctx->copy_end = NULL;
              }
  
              if (rc == NGX_AGAIN) {
                  continue;
              }
  
  
              b = NULL;
  
              if (rc == NGX_OK) {
  
                  smcf = ngx_http_get_module_main_conf(r,
                                                     ngx_http_ssi_filter_module);
  
                  cmd = ngx_hash_find(&smcf->hash, ctx->key, ctx->command.data,
                                      ctx->command.len);
  
                  if (cmd == NULL) {
                      if (ctx->output) {
                          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                        "invalid SSI command: \"%V\"",
                                        &ctx->command);
                          goto ssi_error;
                      }
  
                      continue;
                  }
  
                  if (!ctx->output && !cmd->block) {
  
                      if (ctx->block) {
  
                          /* reconstruct the SSI command text */
  
                          len = 5 + ctx->command.len + 4;
  
                          param = ctx->params.elts;
                          for (i = 0; i < ctx->params.nelts; i++) {
                              len += 1 + param[i].key.len + 2
                                  + param[i].value.len + 1;
                          }
  
                          b = ngx_create_temp_buf(r->pool, len);
  
                          if (b == NULL) {
                              return NGX_ERROR;
                          }
  
                          cl = ngx_alloc_chain_link(r->pool);
                          if (cl == NULL) {
                              return NGX_ERROR;
                          }
  
                          cl->buf = b;
                          cl->next = NULL;
  
                          *b->last++ = '<';
                          *b->last++ = '!';
                          *b->last++ = '-';
                          *b->last++ = '-';
                          *b->last++ = '#';
  
                          b->last = ngx_cpymem(b->last, ctx->command.data,
                                               ctx->command.len);
  
                          for (i = 0; i < ctx->params.nelts; i++) {
                              *b->last++ = ' ';
                              b->last = ngx_cpymem(b->last, param[i].key.data,
                                                   param[i].key.len);
                              *b->last++ = '=';
                              *b->last++ = '"';
                              b->last = ngx_cpymem(b->last, param[i].value.data,
                                                   param[i].value.len);
                              *b->last++ = '"';
                          }
  
                          *b->last++ = ' ';
                          *b->last++ = '-';
                          *b->last++ = '-';
                          *b->last++ = '>';
  
                          mctx = ngx_http_get_module_ctx(r->main,
                                                     ngx_http_ssi_filter_module);
                          bl = mctx->blocks->elts;
                          for (ll = &bl[mctx->blocks->nelts - 1].bufs;
                               *ll;
                               ll = &(*ll)->next)
                          {
                              /* void */
                          }
  
                          *ll = cl;
  
                          b = NULL;
  
                          continue;
                      }
  
                      if (cmd->conditional == 0) {
                          continue;
                      }
                  }
  
                  if (cmd->conditional
                      && (ctx->conditional == 0
                          || ctx->conditional > cmd->conditional))
                  {
                      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                    "invalid context of SSI command: \"%V\"",
                                    &ctx->command);
                      goto ssi_error;
                  }
  
                  if (ctx->params.nelts > NGX_HTTP_SSI_MAX_PARAMS) {
                      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                    "too many SSI command parameters: \"%V\"",
                                    &ctx->command);
                      goto ssi_error;
                  }
  
                  ngx_memzero(params,
                             (NGX_HTTP_SSI_MAX_PARAMS + 1) * sizeof(ngx_str_t *));
  
                  param = ctx->params.elts;
  
                  for (i = 0; i < ctx->params.nelts; i++) {
  
                      for (prm = cmd->params; prm->name.len; prm++) {
  
                          if (param[i].key.len != prm->name.len
                              || ngx_strncmp(param[i].key.data, prm->name.data,
                                             prm->name.len) != 0)
                          {
                              continue;
                          }
  
                          if (!prm->multiple) {
                              if (params[prm->index]) {
                                  ngx_log_error(NGX_LOG_ERR,
                                                r->connection->log, 0,
                                                "duplicate \"%V\" parameter "
                                                "in \"%V\" SSI command",
                                                &param[i].key, &ctx->command);
  
                                  goto ssi_error;
                              }
  
                              params[prm->index] = &param[i].value;
  
                              break;
                          }
  
                          for (index = prm->index; params[index]; index++) {
                              /* void */
                          }
  
                          params[index] = &param[i].value;
  
                          break;
                      }
  
                      if (prm->name.len == 0) {
                          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                        "invalid parameter name: \"%V\" "
                                        "in \"%V\" SSI command",
                                        &param[i].key, &ctx->command);
  
                          goto ssi_error;
                      }
                  }
  
                  for (prm = cmd->params; prm->name.len; prm++) {
                      if (prm->mandatory && params[prm->index] == 0) {
                          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                        "mandatory \"%V\" parameter is absent "
                                        "in \"%V\" SSI command",
                                        &prm->name, &ctx->command);
  
                          goto ssi_error;
                      }
                  }
  
                  if (cmd->flush && ctx->out) {
  
                      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                                     "ssi flush");
  
                      if (ngx_http_ssi_output(r, ctx) == NGX_ERROR) {
                          return NGX_ERROR;
                      }
                  }
  
                  rc = cmd->handler(r, ctx, params);
  
                  if (rc == NGX_OK) {
                      continue;
                  }
  
                  if (rc == NGX_DONE || rc == NGX_AGAIN || rc == NGX_ERROR) {
                      ngx_http_ssi_buffered(r, ctx);
                      return rc;
                  }
              }
  
  
              /* rc == NGX_HTTP_SSI_ERROR */
  
      ssi_error:
  
              if (slcf->silent_errors) {
                  continue;
              }
  
              if (ctx->free) {
                  cl = ctx->free;
                  ctx->free = ctx->free->next;
                  b = cl->buf;
                  ngx_memzero(b, sizeof(ngx_buf_t));
  
              } else {
                  b = ngx_calloc_buf(r->pool);
                  if (b == NULL) {
                      return NGX_ERROR;
                  }
  
                  cl = ngx_alloc_chain_link(r->pool);
                  if (cl == NULL) {
                      return NGX_ERROR;
                  }
  
                  cl->buf = b;
              }
  
              b->memory = 1;
              b->pos = ctx->errmsg.data;
              b->last = ctx->errmsg.data + ctx->errmsg.len;
  
              cl->next = NULL;
              *ctx->last_out = cl;
              ctx->last_out = &cl->next;
  
              continue;
          }
  
          if (ctx->buf->last_buf || ngx_buf_in_memory(ctx->buf)) {
              if (b == NULL) {
                  if (ctx->free) {
                      cl = ctx->free;
                      ctx->free = ctx->free->next;
                      b = cl->buf;
                      ngx_memzero(b, sizeof(ngx_buf_t));
  
                  } else {
                      b = ngx_calloc_buf(r->pool);
                      if (b == NULL) {
                          return NGX_ERROR;
                      }
  
                      cl = ngx_alloc_chain_link(r->pool);
                      if (cl == NULL) {
                          return NGX_ERROR;
                      }
  
                      cl->buf = b;
                  }
  
                  b->sync = 1;
  
                  cl->next = NULL;
                  *ctx->last_out = cl;
                  ctx->last_out = &cl->next;
              }
  
              b->last_buf = ctx->buf->last_buf;
              b->shadow = ctx->buf;
  
              if (slcf->ignore_recycled_buffers == 0)  {
                  b->recycled = ctx->buf->recycled;
              }
          }
  
          ctx->buf = NULL;
  
          ctx->saved = ctx->looked;
      }
  
      if (ctx->out == NULL && ctx->busy == NULL) {
          return NGX_OK;
      }
  
      return ngx_http_ssi_output(r, ctx);
  }
  
  
  static ngx_int_t
  ngx_http_ssi_output(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
  {
      ngx_int_t     rc;
      ngx_buf_t    *b;
      ngx_chain_t  *cl;
  
  #if 1
      b = NULL;
      for (cl = ctx->out; cl; cl = cl->next) {
          ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                         "ssi out: %p %p", cl->buf, cl->buf->pos);
          if (cl->buf == b) {
              ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                            "the same buf was used in ssi");
              ngx_debug_point();
              return NGX_ERROR;
          }
          b = cl->buf;
      }
  #endif
  
      rc = ngx_http_next_body_filter(r, ctx->out);
  
      if (ctx->busy == NULL) {
          ctx->busy = ctx->out;
  
      } else {
          for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
          cl->next = ctx->out;
      }
  
      ctx->out = NULL;
      ctx->last_out = &ctx->out;
  
      while (ctx->busy) {
  
          cl = ctx->busy;
          b = cl->buf;
  
          if (ngx_buf_size(b) != 0) {
              break;
          }
  
          if (b->shadow) {
              b->shadow->pos = b->shadow->last;
          }
  
          ctx->busy = cl->next;
  
          if (ngx_buf_in_memory(b) || b->in_file) {
              /* add data bufs only to the free buf chain */
  
              cl->next = ctx->free;
              ctx->free = cl;
          }
      }
  
      ngx_http_ssi_buffered(r, ctx);
  
      return rc;
  }
  
  
  static void
  ngx_http_ssi_buffered(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
  {
      if (ctx->in || ctx->buf) {
          r->buffered |= NGX_HTTP_SSI_BUFFERED;
  
      } else {
          r->buffered &= ~NGX_HTTP_SSI_BUFFERED;
      }
  }
  
  
  static ngx_int_t
  ngx_http_ssi_parse(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx)
  {
      u_char                *p, *value, *last, *copy_end, ch;
      size_t                 looked;
      ngx_http_ssi_state_e   state;
  
      state = ctx->state;
      looked = ctx->looked;
      last = ctx->buf->last;
      copy_end = ctx->copy_end;
  
      for (p = ctx->pos; p < last; p++) {
  
          ch = *p;
  
          if (state == ssi_start_state) {
  
              /* the tight loop */
  
              for ( ;; ) {
                  if (ch == '<') {
                      copy_end = p;
                      looked = 1;
                      state = ssi_tag_state;
  
                      goto tag_started;
                  }
  
                  if (++p == last) {
                      break;
                  }
  
                  ch = *p;
              }
  
              ctx->state = state;
              ctx->pos = p;
              ctx->looked = looked;
              ctx->copy_end = p;
  
              if (ctx->copy_start == NULL) {
                  ctx->copy_start = ctx->buf->pos;
              }
  
              return NGX_AGAIN;
  
          tag_started:
  
              continue;
          }
  
          switch (state) {
  
          case ssi_start_state:
              /* not reached */
              break;
  
          case ssi_tag_state:
              switch (ch) {
              case '!':
                  looked = 2;
                  state = ssi_comment0_state;
                  break;
  
              case '<':
                  copy_end = p;
                  break;
  
              default:
                  copy_end = p;
                  looked = 0;
                  state = ssi_start_state;
                  break;
              }
  
              break;
  
          case ssi_comment0_state:
              switch (ch) {
              case '-':
                  looked = 3;
                  state = ssi_comment1_state;
                  break;
  
              case '<':
                  copy_end = p;
                  looked = 1;
                  state = ssi_tag_state;
                  break;
  
              default:
                  copy_end = p;
                  looked = 0;
                  state = ssi_start_state;
                  break;
              }
  
              break;
  
          case ssi_comment1_state:
              switch (ch) {
              case '-':
                  looked = 4;
                  state = ssi_sharp_state;
                  break;
  
              case '<':
                  copy_end = p;
                  looked = 1;
                  state = ssi_tag_state;
                  break;
  
              default:
                  copy_end = p;
                  looked = 0;
                  state = ssi_start_state;
                  break;
              }
  
              break;
  
          case ssi_sharp_state:
              switch (ch) {
              case '#':
                  if (p - ctx->pos < 4) {
                      ctx->saved = 0;
                  }
                  looked = 0;
                  state = ssi_precommand_state;
                  break;
  
              case '<':
                  copy_end = p;
                  looked = 1;
                  state = ssi_tag_state;
                  break;
  
              default:
                  copy_end = p;
                  looked = 0;
                  state = ssi_start_state;
                  break;
              }
  
              break;
  
          case ssi_precommand_state:
              switch (ch) {
              case ' ':
              case CR:
              case LF:
              case '\t':
                  break;
  
              default:
                  ctx->command.len = 1;
                  ctx->command.data = ngx_pnalloc(r->pool,
                                                  NGX_HTTP_SSI_COMMAND_LEN);
                  if (ctx->command.data == NULL) {
                      return NGX_ERROR;
                  }
  
                  ctx->command.data[0] = ch;
  
                  ctx->key = 0;
                  ctx->key = ngx_hash(ctx->key, ch);
  
                  ctx->params.nelts = 0;
  
                  state = ssi_command_state;
                  break;
              }
  
              break;
  
          case ssi_command_state:
              switch (ch) {
              case ' ':
              case CR:
              case LF:
              case '\t':
                  state = ssi_preparam_state;
                  break;
  
              case '-':
                  state = ssi_comment_end0_state;
                  break;
  
              default:
                  if (ctx->command.len == NGX_HTTP_SSI_COMMAND_LEN) {
                      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                    "the \"%V%c...\" SSI command is too long",
                                    &ctx->command, ch);
  
                      state = ssi_error_state;
                      break;
                  }
  
                  ctx->command.data[ctx->command.len++] = ch;
                  ctx->key = ngx_hash(ctx->key, ch);
              }
  
              break;
  
          case ssi_preparam_state:
              switch (ch) {
              case ' ':
              case CR:
              case LF:
              case '\t':
                  break;
  
              case '-':
                  state = ssi_comment_end0_state;
                  break;
  
              default:
                  ctx->param = ngx_array_push(&ctx->params);
                  if (ctx->param == NULL) {
                      return NGX_ERROR;
                  }
  
                  ctx->param->key.len = 1;
                  ctx->param->key.data = ngx_pnalloc(r->pool,
                                                     NGX_HTTP_SSI_PARAM_LEN);
                  if (ctx->param->key.data == NULL) {
                      return NGX_ERROR;
                  }
  
                  ctx->param->key.data[0] = ch;
  
                  ctx->param->value.len = 0;
  
                  if (ctx->value_buf == NULL) {
                      ctx->param->value.data = ngx_pnalloc(r->pool,
                                                           ctx->value_len + 1);
                      if (ctx->param->value.data == NULL) {
                          return NGX_ERROR;
                      }
  
                  } else {
                      ctx->param->value.data = ctx->value_buf;
                  }
  
                  state = ssi_param_state;
                  break;
              }
  
              break;
  
          case ssi_param_state:
              switch (ch) {
              case ' ':
              case CR:
              case LF:
              case '\t':
                  state = ssi_preequal_state;
                  break;
  
              case '=':
                  state = ssi_prevalue_state;
                  break;
  
              case '-':
                  state = ssi_error_end0_state;
  
                  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                "unexpected \"-\" symbol after \"%V\" "
                                "parameter in \"%V\" SSI command",
                                &ctx->param->key, &ctx->command);
                  break;
  
              default:
                  if (ctx->param->key.len == NGX_HTTP_SSI_PARAM_LEN) {
                      state = ssi_error_state;
                      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                    "too long \"%V%c...\" parameter in "
                                    "\"%V\" SSI command",
                                    &ctx->param->key, ch, &ctx->command);
                      break;
                  }
  
                  ctx->param->key.data[ctx->param->key.len++] = ch;
              }
  
              break;
  
          case ssi_preequal_state:
              switch (ch) {
              case ' ':
              case CR:
              case LF:
              case '\t':
                  break;
  
              case '=':
                  state = ssi_prevalue_state;
                  break;
  
              default:
                  if (ch == '-') {
                      state = ssi_error_end0_state;
                  } else {
                      state = ssi_error_state;
                  }
  
                  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                "unexpected \"%c\" symbol after \"%V\" "
                                "parameter in \"%V\" SSI command",
                                ch, &ctx->param->key, &ctx->command);
                  break;
              }
  
              break;
  
          case ssi_prevalue_state:
              switch (ch) {
              case ' ':
              case CR:
              case LF:
              case '\t':
                  break;
  
              case '"':
                  state = ssi_double_quoted_value_state;
                  break;
  
              case '\'':
                  state = ssi_quoted_value_state;
                  break;
  
              default:
                  if (ch == '-') {
                      state = ssi_error_end0_state;
                  } else {
                      state = ssi_error_state;
                  }
  
                  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                "unexpected \"%c\" symbol before value of "
                                "\"%V\" parameter in \"%V\" SSI command",
                                ch, &ctx->param->key, &ctx->command);
                  break;
              }
  
              break;
  
          case ssi_double_quoted_value_state:
              switch (ch) {
              case '"':
                  state = ssi_postparam_state;
                  break;
  
              case '\\':
                  ctx->saved_state = ssi_double_quoted_value_state;
                  state = ssi_quoted_symbol_state;
  
                  /* fall through */
  
              default:
                  if (ctx->param->value.len == ctx->value_len) {
                      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                    "too long \"%V%c...\" value of \"%V\" "
                                    "parameter in \"%V\" SSI command",
                                    &ctx->param->value, ch, &ctx->param->key,
                                    &ctx->command);
                      state = ssi_error_state;
                      break;
                  }
  
                  ctx->param->value.data[ctx->param->value.len++] = ch;
              }
  
              break;
  
          case ssi_quoted_value_state:
              switch (ch) {
              case '\'':
                  state = ssi_postparam_state;
                  break;
  
              case '\\':
                  ctx->saved_state = ssi_quoted_value_state;
                  state = ssi_quoted_symbol_state;
  
                  /* fall through */
  
              default:
                  if (ctx->param->value.len == ctx->value_len) {
                      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                    "too long \"%V%c...\" value of \"%V\" "
                                    "parameter in \"%V\" SSI command",
                                    &ctx->param->value, ch, &ctx->param->key,
                                    &ctx->command);
                      state = ssi_error_state;
                      break;
                  }
  
                  ctx->param->value.data[ctx->param->value.len++] = ch;
              }
  
              break;
  
          case ssi_quoted_symbol_state:
              state = ctx->saved_state;
  
              if (ctx->param->value.len == ctx->value_len) {
                  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                "too long \"%V%c...\" value of \"%V\" "
                                "parameter in \"%V\" SSI command",
                                &ctx->param->value, ch, &ctx->param->key,
                                &ctx->command);
                  state = ssi_error_state;
                  break;
              }
  
              ctx->param->value.data[ctx->param->value.len++] = ch;
  
              break;
  
          case ssi_postparam_state:
  
              if (ctx->param->value.len + 1 < ctx->value_len / 2) {
                  value = ngx_pnalloc(r->pool, ctx->param->value.len + 1);
                  if (value == NULL) {
                      return NGX_ERROR;
                  }
  
                  ngx_memcpy(value, ctx->param->value.data,
                             ctx->param->value.len);
  
                  ctx->value_buf = ctx->param->value.data;
                  ctx->param->value.data = value;
  
              } else {
                  ctx->value_buf = NULL;
              }
  
              switch (ch) {
              case ' ':
              case CR:
              case LF:
              case '\t':
                  state = ssi_preparam_state;
                  break;
  
              case '-':
                  state = ssi_comment_end0_state;
                  break;
  
              default:
                  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                "unexpected \"%c\" symbol after \"%V\" value "
                                "of \"%V\" parameter in \"%V\" SSI command",
                                ch, &ctx->param->value, &ctx->param->key,
                                &ctx->command);
                  state = ssi_error_state;
                  break;
              }
  
              break;
  
          case ssi_comment_end0_state:
              switch (ch) {
              case '-':
                  state = ssi_comment_end1_state;
                  break;
  
              default:
                  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                "unexpected \"%c\" symbol in \"%V\" SSI command",
                                ch, &ctx->command);
                  state = ssi_error_state;
                  break;
              }
  
              break;
  
          case ssi_comment_end1_state:
              switch (ch) {
              case '>':
                  ctx->state = ssi_start_state;
                  ctx->pos = p + 1;
                  ctx->looked = looked;
                  ctx->copy_end = copy_end;
  
                  if (ctx->copy_start == NULL && copy_end) {
                      ctx->copy_start = ctx->buf->pos;
                  }
  
                  return NGX_OK;
  
              default:
                  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                "unexpected \"%c\" symbol in \"%V\" SSI command",
                                ch, &ctx->command);
                  state = ssi_error_state;
                  break;
              }
  
              break;
  
          case ssi_error_state:
              switch (ch) {
              case '-':
                  state = ssi_error_end0_state;
                  break;
  
              default:
                  break;
              }
  
              break;
  
          case ssi_error_end0_state:
              switch (ch) {
              case '-':
                  state = ssi_error_end1_state;
                  break;
  
              default:
                  state = ssi_error_state;
                  break;
              }
  
              break;
  
          case ssi_error_end1_state:
              switch (ch) {
              case '>':
                  ctx->state = ssi_start_state;
                  ctx->pos = p + 1;
                  ctx->looked = looked;
                  ctx->copy_end = copy_end;
  
                  if (ctx->copy_start == NULL && copy_end) {
                      ctx->copy_start = ctx->buf->pos;
                  }
  
                  return NGX_HTTP_SSI_ERROR;
  
              default:
                  state = ssi_error_state;
                  break;
              }
  
              break;
          }
      }
  
      ctx->state = state;
      ctx->pos = p;
      ctx->looked = looked;
  
      ctx->copy_end = (state == ssi_start_state) ? p : copy_end;
  
      if (ctx->copy_start == NULL && ctx->copy_end) {
          ctx->copy_start = ctx->buf->pos;
      }
  
      return NGX_AGAIN;
  }
  
  
  static ngx_str_t *
  ngx_http_ssi_get_variable(ngx_http_request_t *r, ngx_str_t *name,
      ngx_uint_t key)
  {
      ngx_uint_t           i;
      ngx_list_part_t     *part;
      ngx_http_ssi_var_t  *var;
      ngx_http_ssi_ctx_t  *ctx;
  
      ctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
  
  #if (NGX_PCRE)
      {
      ngx_str_t  *value;
  
      if (key >= '0' && key <= '9') {
          i = key - '0';
  
          if (i < ctx->ncaptures) {
              value = ngx_palloc(r->pool, sizeof(ngx_str_t));
              if (value == NULL) {
                  return NULL;
              }
  
              i *= 2;
  
              value->data = ctx->captures_data + ctx->captures[i];
              value->len = ctx->captures[i + 1] - ctx->captures[i];
  
              return value;
          }
      }
      }
  #endif
  
      if (ctx->variables == NULL) {
          return NULL;
      }
  
      part = &ctx->variables->part;
      var = part->elts;
  
      for (i = 0; /* void */ ; i++) {
  
          if (i >= part->nelts) {
              if (part->next == NULL) {
                  break;
              }
  
              part = part->next;
              var = part->elts;
              i = 0;
          }
  
          if (name->len != var[i].name.len) {
              continue;
          }
  
          if (key != var[i].key) {
              continue;
          }
  
          if (ngx_strncmp(name->data, var[i].name.data, name->len) == 0) {
              return &var[i].value;
          }
      }
  
      return NULL;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_evaluate_string(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
      ngx_str_t *text, ngx_uint_t flags)
  {
      u_char                      ch, *p, **value, *data, *part_data;
      size_t                     *size, len, prefix, part_len;
      ngx_str_t                   var, *val;
      ngx_uint_t                  i, n, bracket, quoted, key;
      ngx_array_t                 lengths, values;
      ngx_http_variable_value_t  *vv;
  
      n = ngx_http_script_variables_count(text);
  
      if (n == 0) {
  
          data = text->data;
          p = data;
  
          if ((flags & NGX_HTTP_SSI_ADD_PREFIX) && text->data[0] != '/') {
  
              for (prefix = r->uri.len; prefix; prefix--) {
                  if (r->uri.data[prefix - 1] == '/') {
                      break;
                  }
              }
  
              if (prefix) {
                  len = prefix + text->len;
  
                  data = ngx_pnalloc(r->pool, len);
                  if (data == NULL) {
                      return NGX_ERROR;
                  }
  
                  p = ngx_copy(data, r->uri.data, prefix);
              }
          }
  
          quoted = 0;
  
          for (i = 0; i < text->len; i++) {
              ch = text->data[i];
  
              if (!quoted) {
  
                  if (ch == '\\') {
                      quoted = 1;
                      continue;
                  }
  
              } else {
                  quoted = 0;
  
                  if (ch != '\\' && ch != '\'' && ch != '"' && ch != '$') {
                      *p++ = '\\';
                  }
              }
  
              *p++ = ch;
          }
  
          text->len = p - data;
          text->data = data;
  
          return NGX_OK;
      }
  
      if (ngx_array_init(&lengths, r->pool, 8, sizeof(size_t *)) != NGX_OK) {
          return NGX_ERROR;
      }
  
      if (ngx_array_init(&values, r->pool, 8, sizeof(u_char *)) != NGX_OK) {
          return NGX_ERROR;
      }
  
      len = 0;
      i = 0;
  
      while (i < text->len) {
  
          if (text->data[i] == '$') {
  
              var.len = 0;
  
              if (++i == text->len) {
                  goto invalid_variable;
              }
  
              if (text->data[i] == '{') {
                  bracket = 1;
  
                  if (++i == text->len) {
                      goto invalid_variable;
                  }
  
                  var.data = &text->data[i];
  
              } else {
                  bracket = 0;
                  var.data = &text->data[i];
              }
  
              for ( /* void */ ; i < text->len; i++, var.len++) {
                  ch = text->data[i];
  
                  if (ch == '}' && bracket) {
                      i++;
                      bracket = 0;
                      break;
                  }
  
                  if ((ch >= 'A' && ch <= 'Z')
                      || (ch >= 'a' && ch <= 'z')
                      || (ch >= '0' && ch <= '9')
                      || ch == '_')
                  {
                      continue;
                  }
  
                  break;
              }
  
              if (bracket) {
                  ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                                "the closing bracket in \"%V\" "
                                "variable is missing", &var);
                  return NGX_HTTP_SSI_ERROR;
              }
  
              if (var.len == 0) {
                  goto invalid_variable;
              }
  
              key = ngx_hash_strlow(var.data, var.data, var.len);
  
              val = ngx_http_ssi_get_variable(r, &var, key);
  
              if (val == NULL) {
                  vv = ngx_http_get_variable(r, &var, key);
                  if (vv == NULL) {
                      return NGX_ERROR;
                  }
  
                  if (vv->not_found) {
                      continue;
                  }
  
                  part_data = vv->data;
                  part_len = vv->len;
  
              } else {
                  part_data = val->data;
                  part_len = val->len;
              }
  
          } else {
              part_data = &text->data[i];
              quoted = 0;
  
              for (p = part_data; i < text->len; i++) {
                  ch = text->data[i];
  
                  if (!quoted) {
  
                      if (ch == '\\') {
                          quoted = 1;
                          continue;
                      }
  
                      if (ch == '$') {
                          break;
                      }
  
                  } else {
                      quoted = 0;
  
                      if (ch != '\\' && ch != '\'' && ch != '"' && ch != '$') {
                          *p++ = '\\';
                      }
                  }
  
                  *p++ = ch;
              }
  
              part_len = p - part_data;
          }
  
          len += part_len;
  
          size = ngx_array_push(&lengths);
          if (size == NULL) {
              return NGX_ERROR;
          }
  
          *size = part_len;
  
          value = ngx_array_push(&values);
          if (value == NULL) {
              return NGX_ERROR;
          }
  
          *value = part_data;
      }
  
      prefix = 0;
  
      size = lengths.elts;
      value = values.elts;
  
      if (flags & NGX_HTTP_SSI_ADD_PREFIX) {
          for (i = 0; i < values.nelts; i++) {
              if (size[i] != 0) {
                  if (*value[i] != '/') {
                      for (prefix = r->uri.len; prefix; prefix--) {
                          if (r->uri.data[prefix - 1] == '/') {
                              len += prefix;
                              break;
                          }
                      }
                  }
  
                  break;
              }
          }
      }
  
      p = ngx_pnalloc(r->pool, len + ((flags & NGX_HTTP_SSI_ADD_ZERO) ? 1 : 0));
      if (p == NULL) {
          return NGX_ERROR;
      }
  
      text->len = len;
      text->data = p;
  
      p = ngx_copy(p, r->uri.data, prefix);
  
      for (i = 0; i < values.nelts; i++) {
          p = ngx_copy(p, value[i], size[i]);
      }
  
      return NGX_OK;
  
  invalid_variable:
  
      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                    "invalid variable name in \"%V\"", text);
  
      return NGX_HTTP_SSI_ERROR;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_regex_match(ngx_http_request_t *r, ngx_str_t *pattern,
      ngx_str_t *str)
  {
  #if (NGX_PCRE)
      int                   rc, *captures;
      u_char               *p, errstr[NGX_MAX_CONF_ERRSTR];
      size_t                size;
      ngx_str_t            *vv, name, value;
      ngx_uint_t            i, n, key;
      ngx_http_ssi_ctx_t   *ctx;
      ngx_http_ssi_var_t   *var;
      ngx_regex_compile_t   rgc;
  
      ngx_memzero(&rgc, sizeof(ngx_regex_compile_t));
  
      rgc.pattern = *pattern;
      rgc.pool = r->pool;
      rgc.err.len = NGX_MAX_CONF_ERRSTR;
      rgc.err.data = errstr;
  
      if (ngx_regex_compile(&rgc) != NGX_OK) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "%V", &rgc.err);
          return NGX_HTTP_SSI_ERROR;
      }
  
      n = (rgc.captures + 1) * 3;
  
      captures = ngx_palloc(r->pool, n * sizeof(int));
      if (captures == NULL) {
          return NGX_ERROR;
      }
  
      rc = ngx_regex_exec(rgc.regex, str, captures, n);
  
      if (rc < NGX_REGEX_NO_MATCHED) {
          ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                        ngx_regex_exec_n " failed: %d on \"%V\" using \"%V\"",
                        rc, str, pattern);
          return NGX_HTTP_SSI_ERROR;
      }
  
      if (rc == NGX_REGEX_NO_MATCHED) {
          return NGX_DECLINED;
      }
  
      ctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
  
      ctx->ncaptures = rc;
      ctx->captures = captures;
      ctx->captures_data = str->data;
  
      if (rgc.named_captures > 0) {
  
          if (ctx->variables == NULL) {
              ctx->variables = ngx_list_create(r->pool, 4,
                                               sizeof(ngx_http_ssi_var_t));
              if (ctx->variables == NULL) {
                  return NGX_ERROR;
              }
          }
  
          size = rgc.name_size;
          p = rgc.names;
  
          for (i = 0; i < (ngx_uint_t) rgc.named_captures; i++, p += size) {
  
              name.data = &p[2];
              name.len = ngx_strlen(name.data);
  
              n = 2 * ((p[0] << 8) + p[1]);
  
              value.data = &str->data[captures[n]];
              value.len = captures[n + 1] - captures[n];
  
              key = ngx_hash_strlow(name.data, name.data, name.len);
  
              vv = ngx_http_ssi_get_variable(r, &name, key);
  
              if (vv) {
                  *vv = value;
                  continue;
              }
  
              var = ngx_list_push(ctx->variables);
              if (var == NULL) {
                  return NGX_ERROR;
              }
  
              var->name = name;
              var->key = key;
              var->value = value;
          }
      }
  
      return NGX_OK;
  
  #else
  
      ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
                    "the using of the regex \"%V\" in SSI requires PCRE library",
                    pattern);
      return NGX_HTTP_SSI_ERROR;
  
  #endif
  }
  
  
  static ngx_int_t
  ngx_http_ssi_include(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
      ngx_str_t **params)
  {
      ngx_int_t                    rc;
      ngx_str_t                   *uri, *file, *wait, *set, *stub, args;
      ngx_buf_t                   *b;
      ngx_uint_t                   flags, i, key;
      ngx_chain_t                 *cl, *tl, **ll, *out;
      ngx_http_request_t          *sr;
      ngx_http_ssi_var_t          *var;
      ngx_http_ssi_ctx_t          *mctx;
      ngx_http_ssi_block_t        *bl;
      ngx_http_post_subrequest_t  *psr;
  
      uri = params[NGX_HTTP_SSI_INCLUDE_VIRTUAL];
      file = params[NGX_HTTP_SSI_INCLUDE_FILE];
      wait = params[NGX_HTTP_SSI_INCLUDE_WAIT];
      set = params[NGX_HTTP_SSI_INCLUDE_SET];
      stub = params[NGX_HTTP_SSI_INCLUDE_STUB];
  
      if (uri && file) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                        "inclusion may be either virtual=\"%V\" or file=\"%V\"",
                        uri, file);
          return NGX_HTTP_SSI_ERROR;
      }
  
      if (uri == NULL && file == NULL) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                        "no parameter in \"include\" SSI command");
          return NGX_HTTP_SSI_ERROR;
      }
  
      if (set && stub) {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                        "\"set\" and \"stub\" cannot be used together "
                        "in \"include\" SSI command");
          return NGX_HTTP_SSI_ERROR;
      }
  
      if (wait) {
          if (uri == NULL) {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                            "\"wait\" cannot be used with file=\"%V\"", file);
              return NGX_HTTP_SSI_ERROR;
          }
  
          if (wait->len == 2
              && ngx_strncasecmp(wait->data, (u_char *) "no", 2) == 0)
          {
              wait = NULL;
  
          } else if (wait->len != 3
                     || ngx_strncasecmp(wait->data, (u_char *) "yes", 3) != 0)
          {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                            "invalid value \"%V\" in the \"wait\" parameter",
                            wait);
              return NGX_HTTP_SSI_ERROR;
          }
      }
  
      if (uri == NULL) {
          uri = file;
          wait = (ngx_str_t *) -1;
      }
  
      rc = ngx_http_ssi_evaluate_string(r, ctx, uri, NGX_HTTP_SSI_ADD_PREFIX);
  
      if (rc != NGX_OK) {
          return rc;
      }
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "ssi include: \"%V\"", uri);
  
      ngx_str_null(&args);
      flags = NGX_HTTP_LOG_UNSAFE;
  
      if (ngx_http_parse_unsafe_uri(r, uri, &args, &flags) != NGX_OK) {
          return NGX_HTTP_SSI_ERROR;
      }
  
      psr = NULL;
  
      mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
  
      if (stub) {
          if (mctx->blocks) {
              bl = mctx->blocks->elts;
              for (i = 0; i < mctx->blocks->nelts; i++) {
                  if (stub->len == bl[i].name.len
                      && ngx_strncmp(stub->data, bl[i].name.data, stub->len) == 0)
                  {
                      goto found;
                  }
              }
          }
  
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                        "\"stub\"=\"%V\" for \"include\" not found", stub);
          return NGX_HTTP_SSI_ERROR;
  
      found:
  
          psr = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
          if (psr == NULL) {
              return NGX_ERROR;
          }
  
          psr->handler = ngx_http_ssi_stub_output;
  
          if (bl[i].count++) {
  
              out = NULL;
              ll = &out;
  
              for (tl = bl[i].bufs; tl; tl = tl->next) {
  
                  if (ctx->free) {
                      cl = ctx->free;
                      ctx->free = ctx->free->next;
                      b = cl->buf;
  
                  } else {
                      b = ngx_alloc_buf(r->pool);
                      if (b == NULL) {
                          return NGX_ERROR;
                      }
  
                      cl = ngx_alloc_chain_link(r->pool);
                      if (cl == NULL) {
                          return NGX_ERROR;
                      }
  
                      cl->buf = b;
                  }
  
                  ngx_memcpy(b, tl->buf, sizeof(ngx_buf_t));
  
                  b->pos = b->start;
  
                  *ll = cl;
                  cl->next = NULL;
                  ll = &cl->next;
              }
  
              psr->data = out;
  
          } else {
              psr->data = bl[i].bufs;
          }
      }
  
      if (wait) {
          flags |= NGX_HTTP_SUBREQUEST_WAITED;
      }
  
      if (set) {
          key = ngx_hash_strlow(set->data, set->data, set->len);
  
          psr = ngx_palloc(r->pool, sizeof(ngx_http_post_subrequest_t));
          if (psr == NULL) {
              return NGX_ERROR;
          }
  
          psr->handler = ngx_http_ssi_set_variable;
          psr->data = ngx_http_ssi_get_variable(r, set, key);
  
          if (psr->data == NULL) {
  
              if (mctx->variables == NULL) {
                  mctx->variables = ngx_list_create(r->pool, 4,
                                                    sizeof(ngx_http_ssi_var_t));
                  if (mctx->variables == NULL) {
                      return NGX_ERROR;
                  }
              }
  
              var = ngx_list_push(mctx->variables);
              if (var == NULL) {
                  return NGX_ERROR;
              }
  
              var->name = *set;
              var->key = key;
              var->value = ngx_http_ssi_null_string;
              psr->data = &var->value;
          }
  
          flags |= NGX_HTTP_SUBREQUEST_IN_MEMORY|NGX_HTTP_SUBREQUEST_WAITED;
      }
  
      if (ngx_http_subrequest(r, uri, &args, &sr, psr, flags) != NGX_OK) {
          return NGX_HTTP_SSI_ERROR;
      }
  
      if (wait == NULL && set == NULL) {
          return NGX_OK;
      }
  
      if (ctx->wait == NULL) {
          ctx->wait = sr;
  
          return NGX_AGAIN;
  
      } else {
          ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                        "can only wait for one subrequest at a time");
      }
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_stub_output(ngx_http_request_t *r, void *data, ngx_int_t rc)
  {
      ngx_chain_t  *out;
  
      if (rc == NGX_ERROR || r->connection->error || r->request_output) {
          return rc;
      }
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "ssi stub output: \"%V?%V\"", &r->uri, &r->args);
  
      out = data;
  
      if (!r->header_sent) {
          r->headers_out.content_type_len =
                                        r->parent->headers_out.content_type_len;
          r->headers_out.content_type = r->parent->headers_out.content_type;
  
          if (ngx_http_send_header(r) == NGX_ERROR) {
              return NGX_ERROR;
          }
      }
  
      return ngx_http_output_filter(r, out);
  }
  
  
  static ngx_int_t
  ngx_http_ssi_set_variable(ngx_http_request_t *r, void *data, ngx_int_t rc)
  {
      ngx_str_t  *value = data;
  
      if (r->headers_out.status < NGX_HTTP_SPECIAL_RESPONSE
          && r->out && r->out->buf)
      {
          value->len = r->out->buf->last - r->out->buf->pos;
          value->data = r->out->buf->pos;
      }
  
      return rc;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_echo(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
      ngx_str_t **params)
  {
      u_char                     *p;
      uintptr_t                   len;
      ngx_buf_t                  *b;
      ngx_str_t                  *var, *value, *enc, text;
      ngx_uint_t                  key;
      ngx_chain_t                *cl;
      ngx_http_variable_value_t  *vv;
  
      var = params[NGX_HTTP_SSI_ECHO_VAR];
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "ssi echo \"%V\"", var);
  
      key = ngx_hash_strlow(var->data, var->data, var->len);
  
      value = ngx_http_ssi_get_variable(r, var, key);
  
      if (value == NULL) {
          vv = ngx_http_get_variable(r, var, key);
  
          if (vv == NULL) {
              return NGX_HTTP_SSI_ERROR;
          }
  
          if (!vv->not_found) {
              text.data = vv->data;
              text.len = vv->len;
              value = &text;
          }
      }
  
      if (value == NULL) {
          value = params[NGX_HTTP_SSI_ECHO_DEFAULT];
  
          if (value == NULL) {
              value = &ngx_http_ssi_none;
  
          } else if (value->len == 0) {
              return NGX_OK;
          }
  
      } else {
          if (value->len == 0) {
              return NGX_OK;
          }
      }
  
      enc = params[NGX_HTTP_SSI_ECHO_ENCODING];
  
      if (enc) {
          if (enc->len == 4 && ngx_strncmp(enc->data, "none", 4) == 0) {
  
              ctx->encoding = NGX_HTTP_SSI_NO_ENCODING;
  
          } else if (enc->len == 3 && ngx_strncmp(enc->data, "url", 3) == 0) {
  
              ctx->encoding = NGX_HTTP_SSI_URL_ENCODING;
  
          } else if (enc->len == 6 && ngx_strncmp(enc->data, "entity", 6) == 0) {
  
              ctx->encoding = NGX_HTTP_SSI_ENTITY_ENCODING;
  
          } else {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                            "unknown encoding \"%V\" in the \"echo\" command",
                            enc);
          }
      }
  
      p = value->data;
  
      switch (ctx->encoding) {
  
      case NGX_HTTP_SSI_URL_ENCODING:
          len = 2 * ngx_escape_uri(NULL, value->data, value->len,
                                   NGX_ESCAPE_HTML);
  
          if (len) {
              p = ngx_pnalloc(r->pool, value->len + len);
              if (p == NULL) {
                  return NGX_HTTP_SSI_ERROR;
              }
  
              (void) ngx_escape_uri(p, value->data, value->len, NGX_ESCAPE_HTML);
          }
  
          len += value->len;
          break;
  
      case NGX_HTTP_SSI_ENTITY_ENCODING:
          len = ngx_escape_html(NULL, value->data, value->len);
  
          if (len) {
              p = ngx_pnalloc(r->pool, value->len + len);
              if (p == NULL) {
                  return NGX_HTTP_SSI_ERROR;
              }
  
              (void) ngx_escape_html(p, value->data, value->len);
          }
  
          len += value->len;
          break;
  
      default: /* NGX_HTTP_SSI_NO_ENCODING */
          len = value->len;
          break;
      }
  
      b = ngx_calloc_buf(r->pool);
      if (b == NULL) {
          return NGX_HTTP_SSI_ERROR;
      }
  
      cl = ngx_alloc_chain_link(r->pool);
      if (cl == NULL) {
          return NGX_HTTP_SSI_ERROR;
      }
  
      b->memory = 1;
      b->pos = p;
      b->last = p + len;
  
      cl->buf = b;
      cl->next = NULL;
      *ctx->last_out = cl;
      ctx->last_out = &cl->next;
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_config(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
      ngx_str_t **params)
  {
      ngx_str_t  *value;
  
      value = params[NGX_HTTP_SSI_CONFIG_TIMEFMT];
  
      if (value) {
          ctx->timefmt.len = value->len;
          ctx->timefmt.data = ngx_pnalloc(r->pool, value->len + 1);
          if (ctx->timefmt.data == NULL) {
              return NGX_ERROR;
          }
  
          ngx_cpystrn(ctx->timefmt.data, value->data, value->len + 1);
      }
  
      value = params[NGX_HTTP_SSI_CONFIG_ERRMSG];
  
      if (value) {
          ctx->errmsg = *value;
      }
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_set(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
      ngx_str_t **params)
  {
      ngx_int_t            rc;
      ngx_str_t           *name, *value, *vv;
      ngx_uint_t           key;
      ngx_http_ssi_var_t  *var;
      ngx_http_ssi_ctx_t  *mctx;
  
      mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
  
      if (mctx->variables == NULL) {
          mctx->variables = ngx_list_create(r->pool, 4,
                                            sizeof(ngx_http_ssi_var_t));
          if (mctx->variables == NULL) {
              return NGX_ERROR;
          }
      }
  
      name = params[NGX_HTTP_SSI_SET_VAR];
      value = params[NGX_HTTP_SSI_SET_VALUE];
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "ssi set \"%V\" \"%V\"", name, value);
  
      rc = ngx_http_ssi_evaluate_string(r, ctx, value, 0);
  
      if (rc != NGX_OK) {
          return rc;
      }
  
      key = ngx_hash_strlow(name->data, name->data, name->len);
  
      vv = ngx_http_ssi_get_variable(r, name, key);
  
      if (vv) {
          *vv = *value;
          return NGX_OK;
      }
  
      var = ngx_list_push(mctx->variables);
      if (var == NULL) {
          return NGX_ERROR;
      }
  
      var->name = *name;
      var->key = key;
      var->value = *value;
  
      ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "set: \"%V\"=\"%V\"", name, value);
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_if(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
      ngx_str_t **params)
  {
      u_char       *p, *last;
      ngx_str_t    *expr, left, right;
      ngx_int_t     rc;
      ngx_uint_t    negative, noregex, flags;
  
      if (ctx->command.len == 2) {
          if (ctx->conditional) {
              ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                            "the \"if\" command inside the \"if\" command");
              return NGX_HTTP_SSI_ERROR;
          }
      }
  
      if (ctx->output_chosen) {
          ctx->output = 0;
          return NGX_OK;
      }
  
      expr = params[NGX_HTTP_SSI_IF_EXPR];
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "ssi if expr=\"%V\"", expr);
  
      left.data = expr->data;
      last = expr->data + expr->len;
  
      for (p = left.data; p < last; p++) {
          if (*p >= 'A' && *p <= 'Z') {
              *p |= 0x20;
              continue;
          }
  
          if ((*p >= 'a' && *p <= 'z')
               || (*p >= '0' && *p <= '9')
               || *p == '$' || *p == '{' || *p == '}' || *p == '_'
               || *p == '"' || *p == '\'')
          {
              continue;
          }
  
          break;
      }
  
      left.len = p - left.data;
  
      while (p < last && *p == ' ') {
          p++;
      }
  
      flags = 0;
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "left: \"%V\"", &left);
  
      rc = ngx_http_ssi_evaluate_string(r, ctx, &left, flags);
  
      if (rc != NGX_OK) {
          return rc;
      }
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "evaluated left: \"%V\"", &left);
  
      if (p == last) {
          if (left.len) {
              ctx->output = 1;
              ctx->output_chosen = 1;
  
          } else {
              ctx->output = 0;
          }
  
          ctx->conditional = NGX_HTTP_SSI_COND_IF;
  
          return NGX_OK;
      }
  
      if (p < last && *p == '=') {
          negative = 0;
          p++;
  
      } else if (p + 1 < last && *p == '!' && *(p + 1) == '=') {
          negative = 1;
          p += 2;
  
      } else {
          goto invalid_expression;
      }
  
      while (p < last && *p == ' ') {
          p++;
      }
  
      if (p < last - 1 && *p == '/') {
          if (*(last - 1) != '/') {
              goto invalid_expression;
          }
  
          noregex = 0;
          flags = NGX_HTTP_SSI_ADD_ZERO;
          last--;
          p++;
  
      } else {
          noregex = 1;
          flags = 0;
  
          if (p < last - 1 && p[0] == '\\' && p[1] == '/') {
              p++;
          }
      }
  
      right.len = last - p;
      right.data = p;
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "right: \"%V\"", &right);
  
      rc = ngx_http_ssi_evaluate_string(r, ctx, &right, flags);
  
      if (rc != NGX_OK) {
          return rc;
      }
  
      ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "evaluated right: \"%V\"", &right);
  
      if (noregex) {
          if (left.len != right.len) {
              rc = -1;
  
          } else {
              rc = ngx_strncmp(left.data, right.data, right.len);
          }
  
      } else {
          right.data[right.len] = '\0';
  
          rc = ngx_http_ssi_regex_match(r, &right, &left);
  
          if (rc == NGX_OK) {
              rc = 0;
          } else if (rc == NGX_DECLINED) {
              rc = -1;
          } else {
              return rc;
          }
      }
  
      if ((rc == 0 && !negative) || (rc != 0 && negative)) {
          ctx->output = 1;
          ctx->output_chosen = 1;
  
      } else {
          ctx->output = 0;
      }
  
      ctx->conditional = NGX_HTTP_SSI_COND_IF;
  
      return NGX_OK;
  
  invalid_expression:
  
      ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
                    "invalid expression in \"%V\"", expr);
  
      return NGX_HTTP_SSI_ERROR;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_else(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
      ngx_str_t **params)
  {
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "ssi else");
  
      if (ctx->output_chosen) {
          ctx->output = 0;
      } else {
          ctx->output = 1;
      }
  
      ctx->conditional = NGX_HTTP_SSI_COND_ELSE;
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_endif(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
      ngx_str_t **params)
  {
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "ssi endif");
  
      ctx->output = 1;
      ctx->output_chosen = 0;
      ctx->conditional = 0;
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_block(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
      ngx_str_t **params)
  {
      ngx_http_ssi_ctx_t    *mctx;
      ngx_http_ssi_block_t  *bl;
  
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "ssi block");
  
      mctx = ngx_http_get_module_ctx(r->main, ngx_http_ssi_filter_module);
  
      if (mctx->blocks == NULL) {
          mctx->blocks = ngx_array_create(r->pool, 4,
                                          sizeof(ngx_http_ssi_block_t));
          if (mctx->blocks == NULL) {
              return NGX_HTTP_SSI_ERROR;
          }
      }
  
      bl = ngx_array_push(mctx->blocks);
      if (bl == NULL) {
          return NGX_HTTP_SSI_ERROR;
      }
  
      bl->name = *params[NGX_HTTP_SSI_BLOCK_NAME];
      bl->bufs = NULL;
      bl->count = 0;
  
      ctx->output = 0;
      ctx->block = 1;
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_endblock(ngx_http_request_t *r, ngx_http_ssi_ctx_t *ctx,
      ngx_str_t **params)
  {
      ngx_log_debug0(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                     "ssi endblock");
  
      ctx->output = 1;
      ctx->block = 0;
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_date_gmt_local_variable(ngx_http_request_t *r,
      ngx_http_variable_value_t *v, uintptr_t gmt)
  {
      time_t               now;
      ngx_http_ssi_ctx_t  *ctx;
      ngx_str_t           *timefmt;
      struct tm            tm;
      char                 buf[NGX_HTTP_SSI_DATE_LEN];
  
      v->valid = 1;
      v->no_cacheable = 0;
      v->not_found = 0;
  
      now = ngx_time();
  
      ctx = ngx_http_get_module_ctx(r, ngx_http_ssi_filter_module);
  
      timefmt = ctx ? &ctx->timefmt : &ngx_http_ssi_timefmt;
  
      if (timefmt->len == sizeof("%s") - 1
          && timefmt->data[0] == '%' && timefmt->data[1] == 's')
      {
          v->data = ngx_pnalloc(r->pool, NGX_TIME_T_LEN);
          if (v->data == NULL) {
              return NGX_ERROR;
          }
  
          v->len = ngx_sprintf(v->data, "%T", now) - v->data;
  
          return NGX_OK;
      }
  
      if (gmt) {
          ngx_libc_gmtime(now, &tm);
      } else {
          ngx_libc_localtime(now, &tm);
      }
  
      v->len = strftime(buf, NGX_HTTP_SSI_DATE_LEN,
                        (char *) timefmt->data, &tm);
      if (v->len == 0) {
          return NGX_ERROR;
      }
  
      v->data = ngx_pnalloc(r->pool, v->len);
      if (v->data == NULL) {
          return NGX_ERROR;
      }
  
      ngx_memcpy(v->data, buf, v->len);
  
      return NGX_OK;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_preconfiguration(ngx_conf_t *cf)
  {
      ngx_int_t                  rc;
      ngx_http_variable_t       *var, *v;
      ngx_http_ssi_command_t    *cmd;
      ngx_http_ssi_main_conf_t  *smcf;
  
      for (v = ngx_http_ssi_vars; v->name.len; v++) {
          var = ngx_http_add_variable(cf, &v->name, v->flags);
          if (var == NULL) {
              return NGX_ERROR;
          }
  
          var->get_handler = v->get_handler;
          var->data = v->data;
      }
  
      smcf = ngx_http_conf_get_module_main_conf(cf, ngx_http_ssi_filter_module);
  
      for (cmd = ngx_http_ssi_commands; cmd->name.len; cmd++) {
          rc = ngx_hash_add_key(&smcf->commands, &cmd->name, cmd,
                                NGX_HASH_READONLY_KEY);
  
          if (rc == NGX_OK) {
              continue;
          }
  
          if (rc == NGX_BUSY) {
              ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                 "conflicting SSI command \"%V\"", &cmd->name);
          }
  
          return NGX_ERROR;
      }
  
      return NGX_OK;
  }
  
  
  static void *
  ngx_http_ssi_create_main_conf(ngx_conf_t *cf)
  {
      ngx_http_ssi_main_conf_t  *smcf;
  
      smcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssi_main_conf_t));
      if (smcf == NULL) {
          return NULL;
      }
  
      smcf->commands.pool = cf->pool;
      smcf->commands.temp_pool = cf->temp_pool;
  
      if (ngx_hash_keys_array_init(&smcf->commands, NGX_HASH_SMALL) != NGX_OK) {
          return NULL;
      }
  
      return smcf;
  }
  
  
  static char *
  ngx_http_ssi_init_main_conf(ngx_conf_t *cf, void *conf)
  {
      ngx_http_ssi_main_conf_t *smcf = conf;
  
      ngx_hash_init_t  hash;
  
      hash.hash = &smcf->hash;
      hash.key = ngx_hash_key;
      hash.max_size = 1024;
      hash.bucket_size = ngx_cacheline_size;
      hash.name = "ssi_command_hash";
      hash.pool = cf->pool;
      hash.temp_pool = NULL;
  
      if (ngx_hash_init(&hash, smcf->commands.keys.elts,
                        smcf->commands.keys.nelts)
          != NGX_OK)
      {
          return NGX_CONF_ERROR;
      }
  
      return NGX_CONF_OK;
  }
  
  
  static void *
  ngx_http_ssi_create_loc_conf(ngx_conf_t *cf)
  {
      ngx_http_ssi_loc_conf_t  *slcf;
  
      slcf = ngx_pcalloc(cf->pool, sizeof(ngx_http_ssi_loc_conf_t));
      if (slcf == NULL) {
          return NULL;
      }
  
      /*
       * set by ngx_pcalloc():
       *
       *     conf->types = { NULL };
       *     conf->types_keys = NULL;
       */
  
      slcf->enable = NGX_CONF_UNSET;
      slcf->silent_errors = NGX_CONF_UNSET;
      slcf->ignore_recycled_buffers = NGX_CONF_UNSET;
      slcf->last_modified = NGX_CONF_UNSET;
  
      slcf->min_file_chunk = NGX_CONF_UNSET_SIZE;
      slcf->value_len = NGX_CONF_UNSET_SIZE;
  
      return slcf;
  }
  
  
  static char *
  ngx_http_ssi_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
  {
      ngx_http_ssi_loc_conf_t *prev = parent;
      ngx_http_ssi_loc_conf_t *conf = child;
  
      ngx_conf_merge_value(conf->enable, prev->enable, 0);
      ngx_conf_merge_value(conf->silent_errors, prev->silent_errors, 0);
      ngx_conf_merge_value(conf->ignore_recycled_buffers,
                           prev->ignore_recycled_buffers, 0);
      ngx_conf_merge_value(conf->last_modified, prev->last_modified, 0);
  
      ngx_conf_merge_size_value(conf->min_file_chunk, prev->min_file_chunk, 1024);
      ngx_conf_merge_size_value(conf->value_len, prev->value_len, 255);
  
      if (ngx_http_merge_types(cf, &conf->types_keys, &conf->types,
                               &prev->types_keys, &prev->types,
                               ngx_http_html_default_types)
          != NGX_OK)
      {
          return NGX_CONF_ERROR;
      }
  
      return NGX_CONF_OK;
  }
  
  
  static ngx_int_t
  ngx_http_ssi_filter_init(ngx_conf_t *cf)
  {
      ngx_http_next_header_filter = ngx_http_top_header_filter;
      ngx_http_top_header_filter = ngx_http_ssi_header_filter;
  
      ngx_http_next_body_filter = ngx_http_top_body_filter;
      ngx_http_top_body_filter = ngx_http_ssi_body_filter;
  
      return NGX_OK;
  }
  