changeset 657:400711951595 release-0.3.50

nginx-0.3.50-RELEASE import *) Change: the "proxy_redirect_errors" and "fastcgi_redirect_errors" directives was renamed to the "proxy_intercept_errors" and "fastcgi_intercept_errors" directives. *) Feature: the ngx_http_charset_module supports the recoding from the single byte encodings to the UTF-8 encoding and back. *) Feature: the "X-Accel-Charset" response header line is supported in proxy and FastCGI mode. *) Bugfix: the "\" escape symbol in the "\"" and "\'" pairs in the SSI command was removed only if the command also has the "$" symbol. *) Bugfix: the "<!--" string might be added on some conditions in the SSI after inclusion. *) Bugfix: if the "Content-Length: 0" header line was in response, then in nonbuffered proxying mode the client connection was not closed.
author Igor Sysoev <igor@sysoev.ru>
date Wed, 28 Jun 2006 16:00:26 +0000
parents 97f36f8e65dd
children 0de045bf51ad
files auto/install auto/types/sizeof conf/koi-utf conf/koi-win conf/nginx.conf conf/win-utf docs/html/50x.html docs/xml/nginx/changes.xml src/core/nginx.h src/core/ngx_inet.h src/core/ngx_string.c src/core/ngx_string.h src/http/modules/ngx_http_autoindex_module.c src/http/modules/ngx_http_charset_filter_module.c src/http/modules/ngx_http_fastcgi_module.c src/http/modules/ngx_http_log_module.c src/http/modules/ngx_http_memcached_module.c src/http/modules/ngx_http_proxy_module.c src/http/modules/ngx_http_range_filter_module.c src/http/modules/ngx_http_ssi_filter_module.c src/http/modules/ngx_http_userid_filter_module.c src/http/ngx_http_core_module.c src/http/ngx_http_header_filter_module.c src/http/ngx_http_parse.c src/http/ngx_http_request.c src/http/ngx_http_request.h src/http/ngx_http_upstream.c src/http/ngx_http_upstream.h src/http/ngx_http_variables.c
diffstat 29 files changed, 1481 insertions(+), 221 deletions(-) [+]
line wrap: on
line diff
--- a/auto/install
+++ b/auto/install
@@ -30,6 +30,8 @@ install:	$NGX_OBJS${ngx_dirsep}nginx${ng
 		|| mkdir -p '`dirname "$NGX_CONF_PATH"`'
 
 	cp conf/koi-win '`dirname "$NGX_CONF_PATH"`'
+	cp conf/koi-utf '`dirname "$NGX_CONF_PATH"`'
+	cp conf/win-utf '`dirname "$NGX_CONF_PATH"`'
 
 	test -f '`dirname "$NGX_CONF_PATH"`/mime.types' || \
 		cp conf/mime.types '`dirname "$NGX_CONF_PATH"`/mime.types'
--- a/auto/types/sizeof
+++ b/auto/types/sizeof
@@ -54,7 +54,7 @@ case $ngx_size in
             ngx_max_value=2147483647
         fi
 
-        ngx_max_len='sizeof("-2147483648") - 1'
+        ngx_max_len='(sizeof("-2147483648") - 1)'
     ;;
 
     8)
@@ -64,7 +64,7 @@ case $ngx_size in
             ngx_max_value=9223372036854775807L
         fi
 
-        ngx_max_len='sizeof("-9223372036854775808") - 1'
+        ngx_max_len='(sizeof("-9223372036854775808") - 1)'
     ;;
 
     *)
new file mode 100644
--- /dev/null
+++ b/conf/koi-utf
@@ -0,0 +1,103 @@
+
+charset_map  koi8-r  utf-8 {
+
+    80  E282AC ; # euro
+
+    95  E280A2 ; # bullet
+
+    9A  C2A0 ;   # &nbsp;
+
+    9E  C2B7 ;   # &middot;
+
+    A3  D191 ;   # small yo
+    A4  D194 ;   # small Ukrainian ye
+
+    A6  D196 ;   # small Ukrainian i
+    A7  D197 ;   # small Ukrainian yi
+
+    AD  D291 ;   # small Ukrainian soft g
+    AE  D19E ;   # small Byelorussian short u
+
+    B0  C2B0 ;   # &deg;
+
+    B3  D081 ;   # capital YO
+    B4  D084 ;   # capital Ukrainian YE
+
+    B6  D086 ;   # capital Ukrainian I
+    B7  D087 ;   # capital Ukrainian YI
+
+    B9  E28496 ; # numero sign
+
+    BD  D290 ;   # capital Ukrainian soft G
+    BE  D18E ;   # capital Byelorussian short U
+
+    BF  C2A9 ;   # (C)
+
+    C0  D18E ;   # small yu
+    C1  D0B0 ;   # small a
+    C2  D0B1 ;   # small b
+    C3  D186 ;   # small ts
+    C4  D0B4 ;   # small d
+    C5  D0B5 ;   # small ye
+    C6  D184 ;   # small f
+    C7  D0B3 ;   # small g
+    C8  D185 ;   # small kh
+    C9  D0B8 ;   # small i
+    CA  D0B9 ;   # small j
+    CB  D0BA ;   # small k
+    CC  D0BB ;   # small l
+    CD  D0BC ;   # small m
+    CE  D0BD ;   # small n
+    CF  D0BE ;   # small o
+
+    D0  D0BF ;   # small p
+    D1  D18F ;   # small ya
+    D2  D180 ;   # small r
+    D3  D181 ;   # small s
+    D4  D182 ;   # small t
+    D5  D183 ;   # small u
+    D6  D0B6 ;   # small zh
+    D7  D0B2 ;   # small v
+    D8  D18C ;   # small soft sign
+    D9  D18B ;   # small y
+    DA  D0B7 ;   # small z
+    DB  D188 ;   # small sh
+    DC  D18D ;   # small e
+    DD  D189 ;   # small shch
+    DE  D187 ;   # small ch
+    DF  D18A ;   # small hard sign
+
+    E0  D0AE ;   # capital YU
+    E1  D090 ;   # capital A
+    E2  D091 ;   # capital B
+    E3  D0A6 ;   # capital TS
+    E4  D094 ;   # capital D
+    E5  D095 ;   # capital YE
+    E6  D0A4 ;   # capital F
+    E7  D093 ;   # capital G
+    E8  D0A5 ;   # capital KH
+    E9  D098 ;   # capital I
+    EA  D099 ;   # capital J
+    EB  D09A ;   # capital K
+    EC  D09B ;   # capital L
+    ED  D09C ;   # capital M
+    EE  D09D ;   # capital N
+    EF  D09E ;   # capital O
+
+    F0  D09F ;   # capital P
+    F1  D0AF ;   # capital YA
+    F2  D0A0 ;   # capital R
+    F3  D0A1 ;   # capital S
+    F4  D0A2 ;   # capital T
+    F5  D0A3 ;   # capital U
+    F6  D096 ;   # capital ZH
+    F7  D092 ;   # capital V
+    F8  D0AC ;   # capital soft sign
+    F9  D0AB ;   # capital Y
+    FA  D097 ;   # capital Z
+    FB  D0A8 ;   # capital SH
+    FC  D0AD ;   # capital E
+    FD  D0A9 ;   # capital SHCH
+    FE  D0A7 ;   # capital CH
+    FF  D0AA ;   # capital hard sign
+}
--- a/conf/koi-win
+++ b/conf/koi-win
@@ -13,7 +13,7 @@ charset_map  koi8-r  windows-1251 {
     A4  BA ; # small Ukrainian ye
 
     A6  B3 ; # small Ukrainian i
-    A7  BF ; # small Ukrainian j
+    A7  BF ; # small Ukrainian yi
 
     AD  B4 ; # small Ukrainian soft g
     AE  A2 ; # small Byelorussian short u
@@ -24,9 +24,9 @@ charset_map  koi8-r  windows-1251 {
     B4  AA ; # capital Ukrainian YE
 
     B6  B2 ; # capital Ukrainian I
-    B7  AF ; # capital Ukrainian J
+    B7  AF ; # capital Ukrainian YI
 
-    B9  B9 ; # No
+    B9  B9 ; # numero sign
 
     BD  A5 ; # capital Ukrainian soft G
     BE  A1 ; # capital Byelorussian short U
--- a/conf/nginx.conf
+++ b/conf/nginx.conf
@@ -20,7 +20,7 @@ http {
 
     #log_format  main  '$remote_addr - $remote_user [$time_local] $status '
     #                  '"$request" $body_bytes_sent "$http_referer" '
-    #                  '"$http_user_agent" "http_x_forwarded_for"';
+    #                  '"$http_user_agent" "$http_x_forwarded_for"';
 
     #access_log  logs/access.log  main;
 
@@ -46,6 +46,15 @@ http {
             index  index.html index.htm;
         }
 
+        #error_page  404              /404.html;
+
+        # redirect server error pages to the static page /50x.html
+        #
+        error_page   500 502 503 504  /50x.html;
+        location = /50x.html {
+            root   html;
+        }
+
         # proxy the PHP scripts to Apache listening on 127.0.0.1:80
         #
         #location ~ \.php$ {
@@ -67,9 +76,6 @@ http {
         #location ~ /\.ht {
         #    deny  all;
         #}
-
-        #error_page  404              /404.html;
-        #error_page  500 502 503 504  /50x.html;
     }
 
 
new file mode 100644
--- /dev/null
+++ b/conf/win-utf
@@ -0,0 +1,122 @@
+
+charset_map  windows-1251  utf-8 {
+
+    82  E2809A ; # single low-9 quotation mark
+
+    84  E2809E ; # double low-9 quotation mark
+    85  E280A6 ; # ellipsis
+    86  E280A0 ; # dagger
+    87  E280A1 ; # double dagger
+    88  E282AC ; # euro
+    89  E280B0 ; # per mille
+
+    91  E28098 ; # left single quotation mark
+    92  E28099 ; # right single quotation mark
+    93  E2809C ; # left double quotation mark
+    94  E2809D ; # right double quotation mark
+    95  E280A2 ; # bullet
+    96  E28093 ; # en dash
+    97  E28094 ; # em dash
+
+    99  E284A2 ; # trade mark sign
+
+    A0  C2A0 ;   # &nbsp;
+    A1  D18E ;   # capital Byelorussian short U
+    A2  D19E ;   # small Byelorussian short u
+
+    A4  C2A4 ;   # currency sign
+    A5  D290 ;   # capital Ukrainian soft G
+    A6  C2A6 ;   # borken bar
+    A7  C2A7 ;   # section sign
+    A8  D081 ;   # capital YO
+    A9  C2A9 ;   # (C)
+    AA  D084 ;   # capital Ukrainian YE
+    AB  C2AB ;   # left-pointing double angle quotation mark
+    AC  C2AC ;   # not sign
+    AD  C2AD ;   # soft hypen
+    AE  C2AE ;   # (R)
+    AF  D087 ;   # capital Ukrainian YI
+
+    B0  C2B0 ;   # &deg;
+    B1  C2B1 ;   # plus-minus sign
+    B2  D086 ;   # capital Ukrainian I
+    B3  D196 ;   # small Ukrainian i
+    B4  D291 ;   # small Ukrainian soft g
+    B5  C2B5 ;   # micro sign
+    B6  C2B6 ;   # pilcrow sign
+    B7  C2B7 ;   # &middot;
+    B8  D191 ;   # small yo
+    B9  E28496 ; # numero sign
+    BA  D194 ;   # small Ukrainian ye
+    BB  C2BB ;   # right-pointing double angle quotation mark
+
+    BF  D197 ;   # small Ukrainian yi
+
+    C0  D090 ;   # capital A
+    C1  D091 ;   # capital B
+    C2  D092 ;   # capital V
+    C3  D093 ;   # capital G
+    C4  D094 ;   # capital D
+    C5  D095 ;   # capital YE
+    C6  D096 ;   # capital ZH
+    C7  D097 ;   # capital Z
+    C8  D098 ;   # capital I
+    C9  D099 ;   # capital J
+    CA  D09A ;   # capital K
+    CB  D09B ;   # capital L
+    CC  D09C ;   # capital M
+    CD  D09D ;   # capital N
+    CE  D09E ;   # capital O
+    CF  D09F ;   # capital P
+
+    D0  D0A0 ;   # capital R
+    D1  D0A1 ;   # capital S
+    D2  D0A2 ;   # capital T
+    D3  D0A3 ;   # capital U
+    D4  D0A4 ;   # capital F
+    D5  D0A5 ;   # capital KH
+    D6  D0A6 ;   # capital TS
+    D7  D0A7 ;   # capital CH
+    D8  D0A8 ;   # capital SH
+    D9  D0A9 ;   # capital SHCH
+    DA  D0AA ;   # capital hard sign
+    DB  D0AB ;   # capital Y
+    DC  D0AC ;   # capital soft sign
+    DD  D0AD ;   # capital E
+    DE  D0AE ;   # capital YU
+    DF  D0AF ;   # capital YA
+
+    E0  D0B0 ;   # small a
+    E1  D0B1 ;   # small b
+    E2  D0B2 ;   # small v
+    E3  D0B3 ;   # small g
+    E4  D0B4 ;   # small d
+    E5  D0B5 ;   # small ye
+    E6  D0B6 ;   # small zh
+    E7  D0B7 ;   # small z
+    E8  D0B8 ;   # small i
+    E9  D0B9 ;   # small j
+    EA  D0BA ;   # small k
+    EB  D0BB ;   # small l
+    EC  D0BC ;   # small m
+    ED  D0BD ;   # small n
+    EE  D0BE ;   # small o
+    EF  D0BF ;   # small p
+
+    F0  D180 ;   # small r
+    F1  D181 ;   # small s
+    F2  D182 ;   # small t
+    F3  D183 ;   # small u
+    F4  D184 ;   # small f
+    F5  D185 ;   # small kh
+    F6  D186 ;   # small ts
+    F7  D187 ;   # small ch
+    F8  D188 ;   # small sh
+    F9  D189 ;   # small shch
+    FA  D18A ;   # small hard sign
+    FB  D18B ;   # small y
+    FC  D18C ;   # small soft sign
+    FD  D18D ;   # small e
+    FE  D18E ;   # small yu
+    FF  D18F ;   # small ya
+}
new file mode 100644
--- /dev/null
+++ b/docs/html/50x.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+<title>The page is temporarily unavailable</title>
+<style>
+body { font-family: Tahoma, Verdana, Arial, sans-serif; }
+</style>
+</head>
+<body bgcolor="white" text="black">
+<p>
+The page you are looking for is temporarily unavailable.<br/>
+Please try again later.
+</p>
+</body>
+</html>
--- a/docs/xml/nginx/changes.xml
+++ b/docs/xml/nginx/changes.xml
@@ -9,6 +9,80 @@
 <title lang="en">nginx changelog</title>
 
 
+<changes ver="0.3.50" date="28.06.2006">
+
+<change type="change">
+<para lang="ru">
+директивы proxy_redirect_errors и fastcgi_redirect_errors
+переименованы соответственно в proxy_intercept_errors и
+fastcgi_intercept_errors.
+</para>
+<para lang="en">
+the "proxy_redirect_errors" and "fastcgi_redirect_errors" directives
+was renamed to the "proxy_intercept_errors" and
+"fastcgi_intercept_errors" directives.
+</para>
+</change>
+
+<change type="feature">
+<para lang="ru">
+модуль ngx_http_charset_module поддерживает перекодирование из
+однобайтных кодировок в UTF-8 и обратно.
+</para>
+<para lang="en">
+the ngx_http_charset_module supports the recoding from the single byte
+encodings to the UTF-8 encoding and back.
+</para>
+</change>
+
+<change type="feature">
+<para lang="ru">
+в режиме прокси и FastCGI поддерживается строка заголовка "X-Accel-Charset"
+в ответе бэкенда.
+</para>
+<para lang="en">
+the "X-Accel-Charset" response header line is supported in proxy
+and FastCGI mode.
+</para>
+</change>
+
+<change type="bugfix">
+<para lang="ru">
+символ "\" в парах "\"" и "\'" в SSI командах убирался только, если 
+также использовался символ "$".
+</para>
+<para lang="en">
+the "\" escape symbol in the "\"" and "\'" pairs in the SSI command
+was removed only if the command also has the "$" symbol.
+</para>
+</change>
+
+<change type="bugfix">
+<para lang="ru">
+при некоторых условиях в SSI после вставки могла быть добавлена
+строка "&lt;!--".
+</para>
+<para lang="en">
+the "&lt;!--" string might be added on some conditions
+in the SSI after inclusion.
+</para>
+</change>
+
+<change type="bugfix">
+<para lang="ru">
+если в заголовке ответа была строка <nobr>"Content-Length: 0",</nobr>
+при использовании небуферизированного проксировании не закрывалось соединение
+с клиентом.
+</para>
+<para lang="en">
+if the "Content-Length: 0" header line was in response, then
+in nonbuffered proxying mode the client connection was not closed.
+</para>
+</change>
+
+</changes>
+
+
 <changes ver="0.3.49" date="31.05.2006">
 
 <change type="bugfix">
@@ -3211,8 +3285,8 @@ the "limit_rate" directive is supported 
 в ответе бэкенда.
 </para>
 <para lang="en">
-the "X-Accel-Limit-Rate" response header line is supported in proxy and FastCGI
-mode.
+the "X-Accel-Limit-Rate" response header line is supported in proxy
+and FastCGI mode.
 </para>
 </change>
 
--- a/src/core/nginx.h
+++ b/src/core/nginx.h
@@ -8,7 +8,7 @@
 #define _NGINX_H_INCLUDED_
 
 
-#define NGINX_VER          "nginx/0.3.49"
+#define NGINX_VER          "nginx/0.3.50"
 
 #define NGINX_VAR          "NGINX"
 #define NGX_OLDPID_EXT     ".oldbin"
--- a/src/core/ngx_inet.h
+++ b/src/core/ngx_inet.h
@@ -99,7 +99,6 @@ typedef struct {
 
     unsigned      uri_part:1;
     unsigned      port_only:1;
-    unsigned      virtual:1;
 } ngx_inet_upstream_t;
 
 
--- a/src/core/ngx_string.c
+++ b/src/core/ngx_string.c
@@ -750,16 +750,82 @@ ngx_decode_base64(ngx_str_t *dst, ngx_st
 }
 
 
+/*
+ * ngx_utf_decode() decodes two and more bytes UTF sequences only
+ * the return values:
+ *    0x80 - 0x10ffff         valid character
+ *    0x10ffff - 0xfffffffd   invalid sequence
+ *    0xfffffffe              incomplete sequence
+ *    0xffffffff              error
+ */
+
+uint32_t
+ngx_utf_decode(u_char **p, size_t n)
+{
+    size_t    len;
+    uint32_t  u, i, valid;
+
+    u = **p;
+
+    if (u > 0xf0) {
+
+        u &= 0x07;
+        valid = 0xffff;
+        len = 3;
+
+    } else if (u > 0xe0) {
+
+        u &= 0x0f;
+        valid = 0x7ff;
+        len = 2;
+
+    } else if (u > 0xc0) {
+
+        u &= 0x1f;
+        valid = 0x7f;
+        len = 1;
+
+    } else {
+        (*p)++;
+        return 0xffffffff;
+    }
+
+    if (n - 1 < len) {
+        return 0xfffffffe;
+    }
+
+    (*p)++;
+
+    while (len) {
+        i = *(*p)++;
+
+        if (i < 0x80) {
+            return 0xffffffff;
+        }
+
+        u = (u << 6) | (i & 0x3f);
+
+        len--;
+    }
+
+    if (u > valid) {
+        return u;
+    }
+
+    return 0xffffffff;
+}
+
+
 size_t
-ngx_utf_length(ngx_str_t *utf)
+ngx_utf_length(u_char *p, size_t n)
 {
     u_char      c;
     size_t      len;
     ngx_uint_t  i;
 
-    for (len = 0, i = 0; i < utf->len; len++, i++) {
+    for (len = 0, i = 0; i < n; len++, i++) {
 
-        c = utf->data[i];
+        c = p[i];
 
         if (c < 0x80) {
             continue;
@@ -775,7 +841,7 @@ ngx_utf_length(ngx_str_t *utf)
 
         /* invalid utf */
 
-        return utf->len;
+        return n;
     }
 
     return len;
--- a/src/core/ngx_string.h
+++ b/src/core/ngx_string.h
@@ -146,7 +146,8 @@ void ngx_md5_text(u_char *text, u_char *
 void ngx_encode_base64(ngx_str_t *dst, ngx_str_t *src);
 ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);
 
-size_t ngx_utf_length(ngx_str_t *utf);
+uint32_t ngx_utf_decode(u_char **p, size_t n);
+size_t ngx_utf_length(u_char *p, size_t n);
 u_char * ngx_utf_cpystrn(u_char *dst, u_char *src, size_t n);
 
 
--- a/src/http/modules/ngx_http_autoindex_module.c
+++ b/src/http/modules/ngx_http_autoindex_module.c
@@ -319,7 +319,7 @@ ngx_http_autoindex_handler(ngx_http_requ
                                            NGX_ESCAPE_HTML);
 
         if (r->utf8) {
-            entry->utf_len = ngx_utf_length(&entry->name);
+            entry->utf_len = ngx_utf_length(entry->name.data, entry->name.len);
         } else {
             entry->utf_len = len;
         }
--- a/src/http/modules/ngx_http_charset_filter_module.c
+++ b/src/http/modules/ngx_http_charset_filter_module.c
@@ -9,56 +9,94 @@
 #include <ngx_http.h>
 
 
-#define NGX_HTTP_NO_CHARSET  -2
+#define NGX_HTTP_NO_CHARSET    -2
+
+/* 1 byte length and up to 3 bytes for the UTF-8 encoding of the UCS-2 */
+#define NGX_UTF_LEN             4
+
+#define NGX_HTML_ENTITY_LEN     (sizeof("&#1114111;") - 1)
 
 
 typedef struct {
-    u_char     **tables;
-    ngx_str_t     name;
+    u_char                    **tables;
+    ngx_str_t                   name;
 
-    ngx_uint_t    utf8;   /* unsigned     utf8:1; */
+    unsigned                    length:16;
+    unsigned                    utf8:1;
 } ngx_http_charset_t;
 
 
 typedef struct {
-    ngx_int_t     src;
-    ngx_int_t     dst;
+    ngx_int_t                   src;
+    ngx_int_t                   dst;
 } ngx_http_charset_recode_t;
 
 
 typedef struct {
-    ngx_int_t     src;
-    ngx_int_t     dst;
-    u_char       *src2dst;
-    u_char       *dst2src;
+    ngx_int_t                   src;
+    ngx_int_t                   dst;
+    u_char                     *src2dst;
+    u_char                     *dst2src;
 } ngx_http_charset_tables_t;
 
 
 typedef struct {
-    ngx_array_t   charsets;               /* ngx_http_charset_t */
-    ngx_array_t   tables;                 /* ngx_http_charset_tables_t */
-    ngx_array_t   recodes;                /* ngx_http_charset_recode_t */
+    ngx_array_t                 charsets;       /* ngx_http_charset_t */
+    ngx_array_t                 tables;         /* ngx_http_charset_tables_t */
+    ngx_array_t                 recodes;        /* ngx_http_charset_recode_t */
 } ngx_http_charset_main_conf_t;
 
 
 typedef struct {
-    ngx_int_t     charset;
-    ngx_int_t     source_charset;
-    ngx_flag_t    override_charset;
+    ngx_int_t                   charset;
+    ngx_int_t                   source_charset;
+    ngx_flag_t                  override_charset;
 } ngx_http_charset_loc_conf_t;
 
 
 typedef struct {
-    u_char       *table;
-    ngx_int_t     charset;
+    u_char                     *table;
+    ngx_int_t                   charset;
+
+    ngx_chain_t                *busy;
+    ngx_chain_t                *free_bufs;
+    ngx_chain_t                *free_buffers;
+
+    size_t                      saved_len;
+    u_char                      saved[NGX_UTF_LEN];
+
+    unsigned                    length:16;
+    unsigned                    from_utf8:1;
+    unsigned                    to_utf8:1;
 } ngx_http_charset_ctx_t;
 
 
-static ngx_uint_t ngx_http_charset_recode(ngx_buf_t *b, u_char *table);
+typedef struct {
+    ngx_http_charset_tables_t  *table;
+    ngx_http_charset_t         *charset;
+    ngx_uint_t                  characters;
+} ngx_http_charset_conf_ctx_t;
+
 
-static char *ngx_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
+static ngx_int_t ngx_http_charset_get_charset(ngx_http_charset_t *charsets,
+    ngx_uint_t n, u_char *charset);
+static ngx_int_t ngx_http_charset_set_charset(ngx_http_request_t *r,
+    ngx_http_charset_t *charsets, ngx_int_t charset, ngx_int_t source_charset);
+static ngx_uint_t ngx_http_charset_recode(ngx_buf_t *b, u_char *table);
+static ngx_chain_t *ngx_http_charset_recode_from_utf8(ngx_pool_t *pool,
+    ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx);
+static ngx_chain_t *ngx_http_charset_recode_to_utf8(ngx_pool_t *pool,
+    ngx_buf_t *buf, ngx_http_charset_ctx_t *ctx);
+
+static ngx_chain_t *ngx_http_charset_get_buf(ngx_pool_t *pool,
+    ngx_http_charset_ctx_t *ctx);
+static ngx_chain_t *ngx_http_charset_get_buffer(ngx_pool_t *pool,
+    ngx_http_charset_ctx_t *ctx, size_t size);
+
+static char *ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
-static char *ngx_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf);
+static char *ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy,
+    void *conf);
 
 static char *ngx_http_set_charset_slot(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
@@ -101,7 +139,7 @@ static ngx_command_t  ngx_http_charset_f
 
     { ngx_string("charset_map"),
       NGX_HTTP_MAIN_CONF|NGX_CONF_BLOCK|NGX_CONF_TAKE2,
-      ngx_charset_map_block,
+      ngx_http_charset_map_block,
       NGX_HTTP_MAIN_CONF_OFFSET,
       0,
       NULL },
@@ -148,10 +186,10 @@ static ngx_http_output_body_filter_pt   
 static ngx_int_t
 ngx_http_charset_header_filter(ngx_http_request_t *r)
 {
-    size_t                         len;
-    u_char                        *p;
+    u_char                        *ct;
     ngx_int_t                      charset, source_charset;
-    ngx_uint_t                     i;
+    ngx_str_t                     *mc;
+    ngx_uint_t                     n;
     ngx_http_charset_t            *charsets;
     ngx_http_charset_ctx_t        *ctx;
     ngx_http_charset_loc_conf_t   *lcf, *mlcf;
@@ -159,112 +197,153 @@ ngx_http_charset_header_filter(ngx_http_
 
     mcf = ngx_http_get_module_main_conf(r, ngx_http_charset_filter_module);
 
-    ctx = ngx_http_get_module_ctx(r->main, ngx_http_charset_filter_module);
-
-    if (ctx == NULL) {
-        mlcf = ngx_http_get_module_loc_conf(r->main,
-                                            ngx_http_charset_filter_module);
-        charset = mlcf->charset;
+    charsets = mcf->charsets.elts;
+    n = mcf->charsets.nelts;
 
-        if (charset == NGX_HTTP_NO_CHARSET) {
-            return ngx_http_next_header_filter(r);
-        }
-
-    } else {
-        charset = ctx->charset;
-    }
-
-    charsets = mcf->charsets.elts;
+    /* destination charset */
 
     if (r == r->main) {
+
         if (r->headers_out.content_type.len == 0) {
             return ngx_http_next_header_filter(r);
         }
 
-        if (ngx_strncasecmp(r->headers_out.content_type.data, "text/", 5) != 0
-            && ngx_strncasecmp(r->headers_out.content_type.data,
-                               "application/x-javascript", 24) != 0)
+        if (r->headers_out.override_charset
+            && r->headers_out.override_charset->len)
         {
-            return ngx_http_next_header_filter(r);
+            charset = ngx_http_charset_get_charset(charsets, n,
+                                        r->headers_out.override_charset->data);
+
+            if (charset == NGX_HTTP_NO_CHARSET) {
+                ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                              "unknown charset \"%V\" to override",
+                              &r->headers_out.override_charset);
+
+                return ngx_http_next_header_filter(r);
+            }
+
+        } else {
+            mlcf = ngx_http_get_module_loc_conf(r,
+                                                ngx_http_charset_filter_module);
+            charset = mlcf->charset;
+
+            if (charset == NGX_HTTP_NO_CHARSET) {
+                return ngx_http_next_header_filter(r);
+            }
+
+            if (r->headers_out.charset.len) {
+                if (mlcf->override_charset == 0) {
+                    return ngx_http_next_header_filter(r);
+                }
+
+            } else {
+                ct = r->headers_out.content_type.data;
+
+                if (ngx_strncasecmp(ct, "text/", 5) != 0
+                    && ngx_strncasecmp(ct, "application/x-javascript", 24) != 0)
+                {
+                    return ngx_http_next_header_filter(r);
+                }
+            }
         }
 
     } else {
-        if (r->headers_out.content_type.len == 0) {
-            mlcf = ngx_http_get_module_loc_conf(r->main,
-                                                ngx_http_charset_filter_module);
-            source_charset = mlcf->source_charset;
+        ctx = ngx_http_get_module_ctx(r->main, ngx_http_charset_filter_module);
+
+        if (ctx == NULL) {
+
+            mc = &r->main->headers_out.charset;
+
+            if (mc->len == 0) {
+                return ngx_http_next_header_filter(r);
+            }
+
+            ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_charset_ctx_t));
+            if (ctx == NULL) {
+                return NGX_ERROR;
+            }
+
+            ngx_http_set_ctx(r->main, ctx, ngx_http_charset_filter_module);
 
-            goto found;
+            charset = ngx_http_charset_get_charset(charsets, n, mc->data);
+
+            ctx->charset = charset;
+
+            if (charset == NGX_HTTP_NO_CHARSET) {
+                ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                              "unknown charset \"%V\" of main request", mc);
+
+                return ngx_http_next_header_filter(r);
+            }
+        }
+
+        charset = ctx->charset;
+
+        if (charset == NGX_HTTP_NO_CHARSET) {
+            return ngx_http_next_header_filter(r);
         }
     }
 
-    lcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
-
-    len = 0;
+    /* source charset */
 
-    for (p = r->headers_out.content_type.data; *p; p++) {
-        if (*p == ';') {
-            len = p - r->headers_out.content_type.data;
-        }
-
-        if (ngx_strncasecmp(p, "charset=", 8) != 0) {
-            continue;
-        }
-
-        p += 8;
+    if (r->headers_out.charset.len == 0) {
+        lcf = ngx_http_get_module_loc_conf(r, ngx_http_charset_filter_module);
 
-        for (i = 0; i < mcf->charsets.nelts; i++) {
-
-            if (ngx_strcasecmp(p, charsets[i].name.data) == 0) {
+        return ngx_http_charset_set_charset(r, mcf->charsets.elts, charset,
+                                            lcf->source_charset);
+    }
 
-                if (r == r->main && lcf->override_charset == 0) {
-                    ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_charset_ctx_t));
-                    if (ctx == NULL) {
-                        return NGX_ERROR;
-                    }
-
-                    ngx_http_set_ctx(r, ctx, ngx_http_charset_filter_module);
-
-                    ctx->charset = i;
-
-                    return ngx_http_next_header_filter(r);
-                }
+    source_charset = ngx_http_charset_get_charset(charsets, n,
+                                                  r->headers_out.charset.data);
 
-                if (i != (ngx_uint_t) charset
-                    && (charsets[i].tables == NULL
-                        || charsets[i].tables[charset] == NULL))
-                {
-                    ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
-                                  "no \"charset_map\" between the charsets "
-                                  "\"%V\" and \"%V\"",
-                                  &charsets[i].name, &charsets[charset].name);
+    if (source_charset == NGX_HTTP_NO_CHARSET) {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "unknown source charset \"%V\"", &r->headers_out.charset);
 
-                    return ngx_http_next_header_filter(r);
-                }
-
-                r->headers_out.content_type.len = len;
+        return ngx_http_next_header_filter(r);
+    }
 
-                if (r->headers_out.status == NGX_HTTP_MOVED_PERMANENTLY
-                    || r->headers_out.status == NGX_HTTP_MOVED_TEMPORARILY)
-                {
-                    /*
-                     * do not set charset for the redirect because NN 4.x
-                     * uses this charset instead of the next page charset
-                     */
-
-                    r->headers_out.charset.len = 0;
-                    return ngx_http_next_header_filter(r);
-                }
-
-                source_charset = i;
-
-                goto found;
-            }
-        }
+    if (source_charset != charset
+        && (charsets[source_charset].tables == NULL
+            || charsets[source_charset].tables[charset] == NULL))
+    {
+        ngx_log_error(NGX_LOG_ALERT, r->connection->log, 0,
+                      "no \"charset_map\" between the charsets "
+                      "\"%V\" and \"%V\"",
+                      &charsets[source_charset].name, &charsets[charset].name);
 
         return ngx_http_next_header_filter(r);
     }
 
+    r->headers_out.content_type.len = r->headers_out.content_type_len;
+
+    return ngx_http_charset_set_charset(r, mcf->charsets.elts, charset,
+                                        source_charset);
+}
+
+
+static ngx_int_t
+ngx_http_charset_get_charset(ngx_http_charset_t *charsets, ngx_uint_t n,
+    u_char *charset)
+{
+    ngx_uint_t  i;
+
+    for (i = 0; i < n; i++) {
+        if (ngx_strcasecmp(charsets[i].name.data, charset) == 0) {
+            return i;
+        }
+    }
+
+    return NGX_HTTP_NO_CHARSET;
+}
+
+
+static ngx_int_t
+ngx_http_charset_set_charset(ngx_http_request_t *r,
+    ngx_http_charset_t *charsets, ngx_int_t charset, ngx_int_t source_charset)
+{
+    ngx_http_charset_ctx_t  *ctx;
+
     if (r->headers_out.status == NGX_HTTP_MOVED_PERMANENTLY
         || r->headers_out.status == NGX_HTTP_MOVED_TEMPORARILY)
     {
@@ -274,17 +353,10 @@ ngx_http_charset_header_filter(ngx_http_
          */
 
         r->headers_out.charset.len = 0;
+
         return ngx_http_next_header_filter(r);
     }
 
-    if (r->headers_out.charset.len) {
-        return ngx_http_next_header_filter(r);
-    }
-
-    source_charset = lcf->source_charset;
-
-found:
-
     r->headers_out.charset = charsets[charset].name;
     r->utf8 = charsets[charset].utf8;
 
@@ -301,6 +373,13 @@ found:
 
     ctx->table = charsets[source_charset].tables[charset];
     ctx->charset = charset;
+    ctx->length = charsets[charset].length;
+    ctx->from_utf8 = charsets[source_charset].utf8;
+    ctx->to_utf8 = charsets[charset].utf8;
+
+    if ((ctx->to_utf8 || ctx->from_utf8) && r == r->main) {
+        ngx_http_clear_content_length(r);
+    }
 
     r->filter_need_in_memory = 1;
 
@@ -311,7 +390,9 @@ found:
 static ngx_int_t
 ngx_http_charset_body_filter(ngx_http_request_t *r, ngx_chain_t *in)
 {
-    ngx_chain_t             *cl;
+    ngx_int_t                rc;
+    ngx_buf_t               *b;
+    ngx_chain_t             *cl, *out, **ll;
     ngx_http_charset_ctx_t  *ctx;
 
     ctx = ngx_http_get_module_ctx(r, ngx_http_charset_filter_module);
@@ -320,6 +401,84 @@ ngx_http_charset_body_filter(ngx_http_re
         return ngx_http_next_body_filter(r, in);
     }
 
+    if ((ctx->to_utf8 || ctx->from_utf8) || ctx->busy) {
+
+        out = NULL;
+        ll = &out;
+
+        for (cl = in; cl; cl = cl->next) {
+            b = cl->buf;
+
+            if (ngx_buf_size(b) == 0) {
+                continue;
+            }
+
+            if (ctx->to_utf8) {
+                *ll = ngx_http_charset_recode_to_utf8(r->pool, b, ctx);
+
+            } else {
+                *ll = ngx_http_charset_recode_from_utf8(r->pool, b, ctx);
+            }
+
+            if (*ll == NULL) {
+                return NGX_ERROR;
+            }
+
+            while (*ll) {
+                ll = &(*ll)->next;
+            }
+        }
+
+        rc = ngx_http_next_body_filter(r, out);
+
+        if (out) {
+            if (ctx->busy == NULL) {
+                ctx->busy = out;
+
+            } else {
+                for (cl = ctx->busy; cl->next; cl = cl->next) { /* void */ }
+                cl->next = out;
+            }
+        }
+
+        while (ctx->busy) {
+
+            cl = ctx->busy;
+            b = cl->buf;
+
+            if (ngx_buf_size(b) != 0) {
+                break;
+            }
+
+#if (NGX_HAVE_WRITE_ZEROCOPY)
+            if (b->zerocopy_busy) {
+                break;
+            }
+#endif
+
+            ctx->busy = cl->next;
+
+            if (b->tag != (ngx_buf_tag_t) &ngx_http_charset_filter_module) {
+                continue;
+            }
+
+            if (b->shadow) {
+                b->shadow->pos = b->shadow->last;
+            }
+
+            if (b->pos) {
+                cl->next = ctx->free_buffers;
+                ctx->free_buffers = cl;
+                continue;
+            }
+
+            cl->next = ctx->free_bufs;
+            ctx->free_bufs = cl;
+        }
+
+        return rc;
+    }
+
     for (cl = in; cl; cl = cl->next) {
         (void) ngx_http_charset_recode(cl->buf, ctx->table);
     }
@@ -353,17 +512,506 @@ ngx_http_charset_recode(ngx_buf_t *b, u_
 }
 
 
+static ngx_chain_t *
+ngx_http_charset_recode_from_utf8(ngx_pool_t *pool, ngx_buf_t *buf,
+    ngx_http_charset_ctx_t *ctx)
+{
+    size_t        len, size;
+    u_char        c, *p, *src, *dst, *saved, **table;
+    uint32_t      n;
+    ngx_buf_t    *b;
+    ngx_uint_t    i;
+    ngx_chain_t  *out, *cl, **ll;
+
+    src = buf->pos;
+
+    if (ctx->saved_len == 0) {
+
+        for ( /* void */ ; src < buf->last; src++) {
+
+            if (*src < 0x80) {
+                continue;
+            }
+
+            len = src - buf->pos;
+
+            if (len > 512) {
+                out = ngx_http_charset_get_buf(pool, ctx);
+                if (out == NULL) {
+                    return NULL;
+                }
+
+                b = out->buf;
+
+                b->temporary = buf->temporary;
+                b->memory = buf->memory;
+                b->mmap = buf->mmap;
+                b->flush = buf->flush;
+
+                b->pos = buf->pos;
+                b->last = src;
+
+                out->buf = b;
+                out->next = NULL;
+
+                size = buf->last - src;
+
+                saved = src;
+                n = ngx_utf_decode(&saved, size);
+
+                if (n == 0xfffffffe) {
+                    /* incomplete UTF-8 symbol */
+
+                    ngx_memcpy(ctx->saved, src, size);
+                    ctx->saved_len = size;
+
+                    b->shadow = buf;
+
+                    return out;
+                }
+
+            } else {
+                out = NULL;
+                size = len + buf->last - src;
+                src = buf->pos;
+            }
+
+            if (size < NGX_HTML_ENTITY_LEN) {
+                size += NGX_HTML_ENTITY_LEN;
+            }
+
+            cl = ngx_http_charset_get_buffer(pool, ctx, size);
+            if (cl == NULL) {
+                return NULL;
+            }
+
+            if (out) {
+                out->next = cl;
+
+            } else {
+                out = cl;
+            }
+
+            b = cl->buf;
+            dst = b->pos;
+
+            goto recode;
+        }
+
+        out = ngx_alloc_chain_link(pool);
+        if (out == NULL) {
+            return NULL;
+        }
+
+        out->buf = buf;
+        out->next = NULL;
+
+        return out;
+    }
+
+    /* process incomplete UTF sequence from previous buffer */
+
+    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+                   "http charset utf saved: %z", ctx->saved_len);
+
+    p = src;
+
+    for (i = ctx->saved_len; i < NGX_UTF_LEN; i++) {
+        ctx->saved[i] = *p++;
+
+        if (p == buf->last) {
+            break;
+        }
+    }
+
+    saved = ctx->saved;
+    n = ngx_utf_decode(&saved, i);
+
+    c = '\0';
+
+    if (n < 0x10000) {
+        table = (u_char **) ctx->table;
+        p = table[n >> 8];
+
+        if (p) {
+            c = p[n & 0xff];
+        }
+
+    } else if (n == 0xfffffffe) {
+
+        /* incomplete UTF-8 symbol */
+
+        if (i < NGX_UTF_LEN) {
+            out = ngx_http_charset_get_buf(pool, ctx);
+            if (out == NULL) {
+                return NULL;
+            }
+
+            b = out->buf;
+
+            b->pos = buf->pos;
+            b->last = buf->last;
+            b->sync = 1;
+            b->shadow = buf;
+
+            ngx_memcpy(&ctx->saved[ctx->saved_len], src, i);
+            ctx->saved_len += i;
+
+            return out;
+        }
+    }
+
+    size = buf->last - buf->pos;
+
+    if (size < NGX_HTML_ENTITY_LEN) {
+        size += NGX_HTML_ENTITY_LEN;
+    }
+
+    cl = ngx_http_charset_get_buffer(pool, ctx, size);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    out = cl;
+
+    b = cl->buf;
+    dst = b->pos;
+
+    if (c) {
+        *dst++ = c;
+
+    } else if (n == 0xfffffffe) {
+        *dst++ = '?';
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+                       "http charset invalid utf 0");
+
+        saved = &ctx->saved[NGX_UTF_LEN];
+
+    } else if (n > 0x10ffff) {
+        *dst++ = '?';
+
+        ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+                       "http charset invalid utf 1");
+
+    } else {
+        dst = ngx_sprintf(dst, "&#%uD;", n);
+    }
+
+    src += (saved - ctx->saved) - ctx->saved_len;
+    ctx->saved_len = 0;
+
+recode:
+
+    ll = &cl->next;
+
+    table = (u_char **) ctx->table;
+
+    while (src < buf->last) {
+
+        if ((size_t) (b->end - dst) < NGX_HTML_ENTITY_LEN) {
+            b->last = dst;
+
+            size = buf->last - src + NGX_HTML_ENTITY_LEN;
+
+            cl = ngx_http_charset_get_buffer(pool, ctx, size);
+            if (cl == NULL) {
+                return NULL;
+            }
+
+            *ll = cl;
+            ll = &cl->next;
+
+            b = cl->buf;
+            dst = b->pos;
+        }
+
+        if (*src < 0x80) {
+            *dst++ = *src++;
+            continue;
+        }
+
+        len = buf->last - src;
+
+        n = ngx_utf_decode(&src, len);
+
+        if (n < 0x10000) {
+
+            p = table[n >> 8];
+
+            if (p) {
+                c = p[n & 0xff];
+
+                if (c) {
+                    *dst++ = c;
+                    continue;
+                }
+            }
+
+            dst = ngx_sprintf(dst, "&#%uD;", n);
+
+            continue;
+        }
+
+        if (n == 0xfffffffe) {
+            /* incomplete UTF-8 symbol */
+
+            ngx_memcpy(ctx->saved, src, len);
+            ctx->saved_len = len;
+
+            if (b->pos == dst) {
+                b->sync = 1;
+                b->temporary = 0;
+            }
+
+            break;
+        }
+
+        if (n > 0x10ffff) {
+            *dst++ = '?';
+
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, pool->log, 0,
+                           "http charset invalid utf 2");
+
+            continue;
+        }
+
+        /* n > 0xffff */
+
+        dst = ngx_sprintf(dst, "&#%uD;", n);
+    }
+
+    b->last = dst;
+
+    b->last_buf = buf->last_buf;
+    b->last_in_chain = buf->last_in_chain;
+    b->flush = buf->flush;
+
+    b->shadow = buf;
+
+    return out;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_recode_to_utf8(ngx_pool_t *pool, ngx_buf_t *buf,
+    ngx_http_charset_ctx_t *ctx)
+{
+    size_t        len, size;
+    u_char       *p, *src, *dst, *table;
+    ngx_buf_t    *b;
+    ngx_chain_t  *out, *cl, **ll;
+
+    table = ctx->table;
+
+    for (src = buf->pos; src < buf->last; src++) {
+        if (table[*src * NGX_UTF_LEN] == '\1') {
+            continue;
+        }
+
+        goto recode;
+    }
+
+    out = ngx_alloc_chain_link(pool);
+    if (out == NULL) {
+        return NULL;
+    }
+
+    out->buf = buf;
+    out->next = NULL;
+
+    return out;
+
+recode:
+
+    /*
+     * we assume that there are about half of characters to be recoded,
+     * so we preallocate "size / 2 + size / 2 * ctx->length"
+     */
+
+    len = src - buf->pos;
+
+    if (len > 512) {
+        out = ngx_http_charset_get_buf(pool, ctx);
+        if (out == NULL) {
+            return NULL;
+        }
+
+        b = out->buf;
+
+        b->temporary = buf->temporary;
+        b->memory = buf->memory;
+        b->mmap = buf->mmap;
+        b->flush = buf->flush;
+
+        b->pos = buf->pos;
+        b->last = src;
+
+        out->buf = b;
+        out->next = NULL;
+
+        size = buf->last - src;
+        size = size / 2 + size / 2 * ctx->length;
+
+    } else {
+        out = NULL;
+
+        size = buf->last - src;
+        size = len + size / 2 + size / 2 * ctx->length;
+
+        src = buf->pos;
+    }
+
+    cl = ngx_http_charset_get_buffer(pool, ctx, size);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    if (out) {
+        out->next = cl;
+
+    } else {
+        out = cl;
+    }
+
+    ll = &cl->next;
+
+    b = cl->buf;
+    dst = b->pos;
+
+    while (src < buf->last) {
+
+        p = &table[*src++ * NGX_UTF_LEN];
+        len = *p++;
+
+        if ((size_t) (b->end - dst) < len) {
+            b->last = dst;
+
+            size = buf->last - src;
+            size = len + size / 2 + size / 2 * ctx->length;
+
+            cl = ngx_http_charset_get_buffer(pool, ctx, size);
+            if (cl == NULL) {
+                return NULL;
+            }
+
+            *ll = cl;
+            ll = &cl->next;
+
+            b = cl->buf;
+            dst = b->pos;
+        }
+
+        while (len) {
+            *dst++ = *p++;
+            len--;
+        }
+    }
+
+    b->last = dst;
+
+    b->last_buf = buf->last_buf;
+    b->last_in_chain = buf->last_in_chain;
+    b->flush = buf->flush;
+
+    b->shadow = buf;
+
+    return out;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_get_buf(ngx_pool_t *pool, ngx_http_charset_ctx_t *ctx)
+{
+    ngx_chain_t  *cl;
+
+    cl = ctx->free_bufs;
+
+    if (cl) {
+        ctx->free_bufs = cl->next;
+
+        cl->buf->shadow = NULL;
+        cl->next = NULL;
+
+        return cl;
+    }
+
+    cl = ngx_alloc_chain_link(pool);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    cl->buf = ngx_calloc_buf(pool);
+    if (cl->buf == NULL) {
+        return NULL;
+    }
+
+    cl->next = NULL;
+
+    cl->buf->tag = (ngx_buf_tag_t) &ngx_http_charset_filter_module;
+
+    return cl;
+}
+
+
+static ngx_chain_t *
+ngx_http_charset_get_buffer(ngx_pool_t *pool, ngx_http_charset_ctx_t *ctx,
+    size_t size)
+{
+    ngx_buf_t    *b;
+    ngx_chain_t  *cl, **ll;
+
+    for (ll = &ctx->free_buffers, cl = ctx->free_buffers;
+         cl;
+         ll = &cl->next, cl = cl->next)
+    {
+        b = cl->buf;
+
+        if ((size_t) (b->end - b->start) >= size) {
+            *ll = cl->next;
+            cl->next = NULL;
+
+            b->pos = b->start;
+            b->temporary = 1;
+            b->shadow = NULL;
+
+            return cl;
+        }
+    }
+
+    cl = ngx_alloc_chain_link(pool);
+    if (cl == NULL) {
+        return NULL;
+    }
+
+    cl->buf = ngx_create_temp_buf(pool, size);
+    if (cl->buf == NULL) {
+        return NULL;
+    }
+
+    cl->next = NULL;
+
+    cl->buf->temporary = 1;
+    cl->buf->tag = (ngx_buf_tag_t) &ngx_http_charset_filter_module;
+
+    return cl;
+}
+
+
 static char *
-ngx_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+ngx_http_charset_map_block(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     ngx_http_charset_main_conf_t  *mcf = conf;
 
-    char                       *rv;
-    ngx_int_t                   src, dst;
-    ngx_uint_t                  i;
-    ngx_str_t                  *value;
-    ngx_conf_t                  pvcf;
-    ngx_http_charset_tables_t  *table;
+    char                         *rv;
+    u_char                       *p, *dst2src, **pp;
+    ngx_int_t                     src, dst;
+    ngx_uint_t                    i, n;
+    ngx_str_t                    *value;
+    ngx_conf_t                    pvcf;
+    ngx_http_charset_t           *charset;
+    ngx_http_charset_tables_t    *table;
+    ngx_http_charset_conf_ctx_t   ctx;
 
     value = cf->args->elts;
 
@@ -404,45 +1052,98 @@ ngx_charset_map_block(ngx_conf_t *cf, ng
     table->src = src;
     table->dst = dst;
 
-    table->src2dst = ngx_palloc(cf->pool, 256);
-    if (table->src2dst == NULL) {
-        return NGX_CONF_ERROR;
-    }
+    if (ngx_strcasecmp(value[2].data, "utf-8") == 0) {
+        table->src2dst = ngx_pcalloc(cf->pool, 256 * NGX_UTF_LEN);
+        if (table->src2dst == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        table->dst2src = ngx_pcalloc(cf->pool, 256 * sizeof(void *));
+        if (table->dst2src == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        dst2src = ngx_pcalloc(cf->pool, 256);
+        if (dst2src == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        pp = (u_char **) &table->dst2src[0];
+        pp[0] = dst2src;
+
+        for (i = 0; i < 128; i++) {
+            p = &table->src2dst[i * NGX_UTF_LEN];
+            p[0] = '\1';
+            p[1] = (u_char) i;
+            dst2src[i] = (u_char) i;
+        }
 
-    table->dst2src = ngx_palloc(cf->pool, 256);
-    if (table->dst2src == NULL) {
-        return NGX_CONF_ERROR;
+        for (/* void */; i < 256; i++) {
+            p = &table->src2dst[i * NGX_UTF_LEN];
+            p[0] = '\1';
+            p[1] = '?';
+        }
+
+    } else {
+        table->src2dst = ngx_palloc(cf->pool, 256);
+        if (table->src2dst == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        table->dst2src = ngx_palloc(cf->pool, 256);
+        if (table->dst2src == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        for (i = 0; i < 128; i++) {
+            table->src2dst[i] = (u_char) i;
+            table->dst2src[i] = (u_char) i;
+        }
+
+        for (/* void */; i < 256; i++) {
+            table->src2dst[i] = '?';
+            table->dst2src[i] = '?';
+        }
     }
 
-    for (i = 0; i < 128; i++) {
-        table->src2dst[i] = (u_char) i;
-        table->dst2src[i] = (u_char) i;
-    }
+    charset = mcf->charsets.elts;
 
-    for (/* void */; i < 256; i++) {
-        table->src2dst[i] = '?';
-        table->dst2src[i] = '?';
-    }
+    ctx.table = table;
+    ctx.charset = &charset[dst];
+    ctx.characters = 0;
 
     pvcf = *cf;
-    cf->ctx = table;
-    cf->handler = ngx_charset_map;
+    cf->ctx = &ctx;
+    cf->handler = ngx_http_charset_map;
     cf->handler_conf = conf;
 
     rv = ngx_conf_parse(cf, NULL);
 
     *cf = pvcf;
 
+    if (ctx.characters) {
+        n = ctx.charset->length;
+        ctx.charset->length /= ctx.characters;
+
+        if (((n * 10) / ctx.characters) % 10 > 4) {
+            ctx.charset->length++;
+        }
+    }
+
     return rv;
 }
 
 
 static char *
-ngx_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
+ngx_http_charset_map(ngx_conf_t *cf, ngx_command_t *dummy, void *conf)
 {
-    ngx_int_t                   src, dst;
-    ngx_str_t                  *value;
-    ngx_http_charset_tables_t  *table;
+    u_char                       *p, *dst2src, **pp;
+    uint32_t                      n;
+    ngx_int_t                     src, dst;
+    ngx_str_t                    *value;
+    ngx_uint_t                    i;
+    ngx_http_charset_tables_t    *table;
+    ngx_http_charset_conf_ctx_t  *ctx;
 
     if (cf->args->nelts != 2) {
         ngx_conf_log_error(NGX_LOG_EMERG, cf, 0, "invalid parameters number");
@@ -458,18 +1159,67 @@ ngx_charset_map(ngx_conf_t *cf, ngx_comm
         return NGX_CONF_ERROR;
     }
 
-    dst = ngx_hextoi(value[1].data, value[1].len);
-    if (dst == NGX_ERROR || dst > 255) {
-        ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
-                           "invalid value \"%V\"", &value[1]);
-        return NGX_CONF_ERROR;
+    ctx = cf->ctx;
+    table = ctx->table;
+
+    if (ctx->charset->utf8) {
+        p = &table->src2dst[src * NGX_UTF_LEN];
+
+        *p++ = (u_char) (value[1].len / 2);
+
+        for (i = 0; i < value[1].len; i += 2) {
+            dst = ngx_hextoi(&value[1].data[i], 2);
+            if (dst == NGX_ERROR || dst > 255) {
+                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                                   "invalid value \"%V\"", &value[1]);
+                return NGX_CONF_ERROR;
+            }
+
+            *p++ = (u_char) dst;
+        }
+
+        i /= 2;
+
+        ctx->charset->length += i;
+        ctx->characters++;
+
+        p = &table->src2dst[src * NGX_UTF_LEN] + 1;
+
+        n = ngx_utf_decode(&p, i);
+
+        if (n > 0xffff) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        pp = (u_char **) &table->dst2src[0];
+
+        dst2src = pp[n >> 8];
+
+        if (dst2src == NULL) {
+            dst2src = ngx_pcalloc(cf->pool, 256);
+            if (dst2src == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            pp[n >> 8] = dst2src;
+        }
+
+        dst2src[n & 0xff] = (u_char) src;
+
+    } else {
+        dst = ngx_hextoi(value[1].data, value[1].len);
+        if (dst == NGX_ERROR || dst > 255) {
+            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
+                               "invalid value \"%V\"", &value[1]);
+            return NGX_CONF_ERROR;
+        }
+
+        table->src2dst[src] = (u_char) dst;
+        table->dst2src[dst] = (u_char) src;
     }
 
-    table = cf->ctx;
-
-    table->src2dst[src] = (u_char) dst;
-    table->dst2src[dst] = (u_char) src;
-
     return NGX_CONF_OK;
 }
 
@@ -538,6 +1288,7 @@ ngx_http_add_charset(ngx_array_t *charse
 
     c->tables = NULL;
     c->name = *name;
+    c->length = 0;
 
     if (ngx_strcasecmp(name->data, "utf-8") == 0) {
         c->utf8 = 1;
--- a/src/http/modules/ngx_http_fastcgi_module.c
+++ b/src/http/modules/ngx_http_fastcgi_module.c
@@ -156,6 +156,10 @@ static ngx_conf_deprecated_t  ngx_conf_d
     ngx_conf_deprecated, "fastcgi_header_buffer_size", "fastcgi_buffer_size"
 };
 
+static ngx_conf_deprecated_t  ngx_conf_deprecated_fastcgi_redirect_errors = {
+    ngx_conf_deprecated, "fastcgi_redirect_errors", "fastcgi_intercept_errors"
+};
+
 
 static ngx_conf_bitmask_t  ngx_http_fastcgi_next_upstream_masks[] = {
     { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
@@ -240,12 +244,19 @@ static ngx_command_t  ngx_http_fastcgi_c
       offsetof(ngx_http_fastcgi_loc_conf_t, upstream.pass_request_body),
       NULL },
 
+    { ngx_string("fastcgi_intercept_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_fastcgi_loc_conf_t, upstream.intercept_errors),
+      NULL },
+
     { ngx_string("fastcgi_redirect_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_fastcgi_loc_conf_t, upstream.redirect_errors),
-      NULL },
+      offsetof(ngx_http_fastcgi_loc_conf_t, upstream.intercept_errors),
+      &ngx_conf_deprecated_fastcgi_redirect_errors },
 
     { ngx_string("fastcgi_read_timeout"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
@@ -1534,7 +1545,7 @@ ngx_http_fastcgi_create_loc_conf(ngx_con
     conf->upstream.pass_request_headers = NGX_CONF_UNSET;
     conf->upstream.pass_request_body = NGX_CONF_UNSET;
 
-    conf->upstream.redirect_errors = NGX_CONF_UNSET;
+    conf->upstream.intercept_errors = NGX_CONF_UNSET;
 
     /* "fastcgi_cyclic_temp_file" is disabled */
     conf->upstream.cyclic_temp_file = 0;
@@ -1708,8 +1719,8 @@ ngx_http_fastcgi_merge_loc_conf(ngx_conf
     ngx_conf_merge_value(conf->upstream.pass_request_body,
                               prev->upstream.pass_request_body, 1);
 
-    ngx_conf_merge_value(conf->upstream.redirect_errors,
-                              prev->upstream.redirect_errors, 0);
+    ngx_conf_merge_value(conf->upstream.intercept_errors,
+                              prev->upstream.intercept_errors, 0);
 
 
     ngx_conf_merge_str_value(conf->index, prev->index, "");
--- a/src/http/modules/ngx_http_log_module.c
+++ b/src/http/modules/ngx_http_log_module.c
@@ -16,17 +16,20 @@ typedef struct {
     ngx_array_t                *ops;        /* array of ngx_http_log_op_t */
 } ngx_http_log_fmt_t;
 
+
 typedef struct {
     ngx_array_t                 formats;    /* array of ngx_http_log_fmt_t */
     ngx_uint_t                  combined_used; /* unsigned  combined_used:1 */
 } ngx_http_log_main_conf_t;
 
+
 typedef struct {
     ngx_open_file_t            *file;
     time_t                      disk_full_time;
     ngx_array_t                *ops;        /* array of ngx_http_log_op_t */
 } ngx_http_log_t;
 
+
 typedef struct {
     ngx_array_t                *logs;       /* array of ngx_http_log_t */
     ngx_uint_t                  off;        /* unsigned  off:1 */
--- a/src/http/modules/ngx_http_memcached_module.c
+++ b/src/http/modules/ngx_http_memcached_module.c
@@ -524,7 +524,7 @@ ngx_http_memcached_create_loc_conf(ngx_c
     conf->upstream.busy_buffers_size = 0;
     conf->upstream.max_temp_file_size = 0;
     conf->upstream.temp_file_write_size = 0;
-    conf->upstream.redirect_errors = 1;
+    conf->upstream.intercept_errors = 1;
     conf->upstream.redirect_404 = 1;
     conf->upstream.pass_request_headers = 0;
     conf->upstream.pass_request_body = 0;
--- a/src/http/modules/ngx_http_proxy_module.c
+++ b/src/http/modules/ngx_http_proxy_module.c
@@ -115,6 +115,10 @@ static ngx_conf_deprecated_t  ngx_conf_d
     ngx_conf_deprecated, "proxy_header_buffer_size", "proxy_buffer_size"
 };
 
+static ngx_conf_deprecated_t  ngx_conf_deprecated_proxy_redirect_errors = {
+    ngx_conf_deprecated, "proxy_redirect_errors", "proxy_intercept_errors"
+};
+
 
 static ngx_conf_bitmask_t  ngx_http_proxy_next_upstream_masks[] = {
     { ngx_string("error"), NGX_HTTP_UPSTREAM_FT_ERROR },
@@ -178,12 +182,19 @@ static ngx_command_t  ngx_http_proxy_com
       offsetof(ngx_http_proxy_loc_conf_t, upstream.send_lowat),
       &ngx_http_proxy_lowat_post },
 
+    { ngx_string("proxy_intercept_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_proxy_loc_conf_t, upstream.intercept_errors),
+      NULL },
+
     { ngx_string("proxy_redirect_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_proxy_loc_conf_t, upstream.redirect_errors),
-      NULL },
+      offsetof(ngx_http_proxy_loc_conf_t, upstream.intercept_errors),
+      &ngx_conf_deprecated_proxy_redirect_errors },
 
     { ngx_string("proxy_set_header"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_TAKE2,
@@ -1486,7 +1497,7 @@ ngx_http_proxy_create_loc_conf(ngx_conf_
     conf->upstream.pass_request_headers = NGX_CONF_UNSET;
     conf->upstream.pass_request_body = NGX_CONF_UNSET;
 
-    conf->upstream.redirect_errors = NGX_CONF_UNSET;
+    conf->upstream.intercept_errors = NGX_CONF_UNSET;
 
     /* "proxy_cyclic_temp_file" is disabled */
     conf->upstream.cyclic_temp_file = 0;
@@ -1670,8 +1681,8 @@ ngx_http_proxy_merge_loc_conf(ngx_conf_t
     ngx_conf_merge_value(conf->upstream.pass_request_body,
                               prev->upstream.pass_request_body, 1);
 
-    ngx_conf_merge_value(conf->upstream.redirect_errors,
-                              prev->upstream.redirect_errors, 0);
+    ngx_conf_merge_value(conf->upstream.intercept_errors,
+                              prev->upstream.intercept_errors, 0);
 
     ngx_conf_merge_value(conf->redirect, prev->redirect, 1);
 
--- a/src/http/modules/ngx_http_range_filter_module.c
+++ b/src/http/modules/ngx_http_range_filter_module.c
@@ -145,7 +145,6 @@ ngx_http_range_header_filter(ngx_http_re
         || r->headers_in.range->value.len < 7
         || ngx_strncasecmp(r->headers_in.range->value.data, "bytes=", 6) != 0)
     {
-
         r->headers_out.accept_ranges = ngx_list_push(&r->headers_out.headers);
         if (r->headers_out.accept_ranges == NULL) {
             return NGX_ERROR;
--- a/src/http/modules/ngx_http_ssi_filter_module.c
+++ b/src/http/modules/ngx_http_ssi_filter_module.c
@@ -788,7 +788,8 @@ ngx_http_ssi_output(ngx_http_request_t *
 
     while (ctx->busy) {
 
-        b = ctx->busy->buf;
+        cl = ctx->busy;
+        b = cl->buf;
 
         if (ngx_buf_size(b) != 0) {
             break;
@@ -804,7 +805,6 @@ ngx_http_ssi_output(ngx_http_request_t *
             b->shadow->pos = b->shadow->last;
         }
 
-        cl = ctx->busy;
         ctx->busy = cl->next;
 
         if (ngx_buf_in_memory(b) || b->in_file) {
@@ -942,9 +942,7 @@ ngx_http_ssi_parse(ngx_http_request_t *r
         case ssi_sharp_state:
             switch (ch) {
             case '#':
-                if (ctx->copy_start) {
-                    ctx->saved = 0;
-                }
+                ctx->saved = 0;
                 looked = 0;
                 state = ssi_precommand_state;
                 break;
@@ -1417,11 +1415,11 @@ ngx_http_ssi_evaluate_string(ngx_http_re
 
     if (n == 0) {
 
-        if (!(flags & NGX_HTTP_SSI_ADD_PREFIX)) {
-            return NGX_OK;
-        }
-
-        if (text->data[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;
@@ -1437,13 +1435,35 @@ ngx_http_ssi_evaluate_string(ngx_http_re
                 }
 
                 p = ngx_copy(data, r->uri.data, prefix);
-                ngx_memcpy(p, text->data, text->len);
-
-                text->len = len;
-                text->data = data;
             }
         }
 
+        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;
     }
 
@@ -2140,6 +2160,7 @@ ngx_http_ssi_date_gmt_local_variable(ngx
 
         v->len = ngx_sprintf(v->data, "%T", tp->sec + (gmt ? 0 : tp->gmtoff))
                  - v->data;
+
         return NGX_OK;
     }
 
--- a/src/http/modules/ngx_http_userid_filter_module.c
+++ b/src/http/modules/ngx_http_userid_filter_module.c
@@ -594,10 +594,6 @@ ngx_http_userid_variable(ngx_http_reques
     ngx_http_userid_ctx_t   *ctx;
     ngx_http_userid_conf_t  *conf;
 
-    v->valid = 1;
-    v->no_cachable = 0;
-    v->not_found = 0;
-
     ctx = ngx_http_get_module_ctx(r, ngx_http_userid_filter_module);
 
     uid = (uint32_t *) ((char *) ctx + data);
@@ -615,6 +611,10 @@ ngx_http_userid_variable(ngx_http_reques
         return NGX_ERROR;
     }
 
+    v->valid = 1;
+    v->no_cachable = 0;
+    v->not_found = 0;
+
     ngx_sprintf(v->data, "%V=%08XD%08XD%08XD%08XD",
                 &conf->name, uid[0], uid[1], uid[2], uid[3]);
 
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -519,6 +519,7 @@ static void
 ngx_http_core_run_phases(ngx_http_request_t *r)
 {
     ngx_int_t                   rc;
+    ngx_str_t                   path;
     ngx_http_handler_pt        *h;
     ngx_http_core_loc_conf_t   *clcf;
     ngx_http_core_main_conf_t  *cmcf;
@@ -642,11 +643,10 @@ ngx_http_core_run_phases(ngx_http_reques
 
     if (r->uri.data[r->uri.len - 1] == '/' && !r->zero_in_uri) {
 
-        clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
-
-        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
-                      "directory index of \"%V%V\" is forbidden",
-                      &clcf->root, &r->uri);
+        if (ngx_http_map_uri_to_path(r, &path, 0) != NULL) {
+            ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                          "directory index of \"%V\" is forbidden", &path);
+        }
 
         ngx_http_finalize_request(r, NGX_HTTP_FORBIDDEN);
         return;
@@ -960,11 +960,14 @@ ngx_http_set_content_type(ngx_http_reque
                              r->exten.data, r->exten.len);
 
         if (type) {
+            r->headers_out.content_type_len = type->len;
             r->headers_out.content_type = *type;
+
             return NGX_OK;
         }
     }
 
+    r->headers_out.content_type_len = clcf->default_type.len;
     r->headers_out.content_type = clcf->default_type;
 
     return NGX_OK;
@@ -1156,6 +1159,14 @@ ngx_http_subrequest(ngx_http_request_t *
     ngx_http_core_srv_conf_t      *cscf;
     ngx_http_postponed_request_t  *pr, *p;
 
+    r->main->subrequests--;
+
+    if (r->main->subrequests == 0) {
+        ngx_log_error(NGX_LOG_ERR, r->connection->log, 0,
+                      "subrequests cycle");
+        return NGX_ERROR;
+    }
+
     sr = ngx_pcalloc(r->pool, sizeof(ngx_http_request_t));
     if (sr == NULL) {
         return NGX_ERROR;
--- a/src/http/ngx_http_header_filter_module.c
+++ b/src/http/ngx_http_header_filter_module.c
@@ -247,7 +247,9 @@ ngx_http_header_filter(ngx_http_request_
         len += sizeof("Content-Type: ") - 1
                + r->headers_out.content_type.len + 2;
 
-        if (r->headers_out.charset.len) {
+        if (r->headers_out.content_type_len == r->headers_out.content_type.len
+            && r->headers_out.charset.len)
+        {
             len += sizeof("; charset=") - 1 + r->headers_out.charset.len;
         }
     }
@@ -380,7 +382,9 @@ ngx_http_header_filter(ngx_http_request_
         b->last = ngx_copy(b->last, r->headers_out.content_type.data,
                            r->headers_out.content_type.len);
 
-        if (r->headers_out.charset.len) {
+        if (r->headers_out.content_type_len == r->headers_out.content_type.len
+            && r->headers_out.charset.len)
+        {
             b->last = ngx_cpymem(b->last, "; charset=",
                                  sizeof("; charset=") - 1);
             b->last = ngx_copy(b->last, r->headers_out.charset.data,
--- a/src/http/ngx_http_parse.c
+++ b/src/http/ngx_http_parse.c
@@ -337,13 +337,13 @@ ngx_http_parse_request_line(ngx_http_req
                 r->quoted_uri = 1;
                 state = sw_uri;
                 break;
-            case '+':
-                r->plus_in_uri = 1;
-                break;
             case '?':
                 r->args_start = p + 1;
                 state = sw_uri;
                 break;
+            case '+':
+                r->plus_in_uri = 1;
+                break;
             case '\0':
                 r->zero_in_uri = 1;
                 break;
@@ -366,9 +366,6 @@ ngx_http_parse_request_line(ngx_http_req
                 r->uri_end = p;
                 r->http_minor = 9;
                 goto done;
-            case '+':
-                r->plus_in_uri = 1;
-                break;
             case '\0':
                 r->zero_in_uri = 1;
                 break;
@@ -828,6 +825,8 @@ ngx_http_parse_complex_uri(ngx_http_requ
                 r->uri_ext = u + 1;
                 *u++ = ch;
                 break;
+            case '+':
+                r->plus_in_uri = 1;
             default:
                 *u++ = ch;
                 break;
@@ -853,6 +852,8 @@ ngx_http_parse_complex_uri(ngx_http_requ
             case '?':
                 r->args_start = p;
                 goto done;
+            case '+':
+                r->plus_in_uri = 1;
             default:
                 state = sw_usual;
                 *u++ = ch;
@@ -881,6 +882,8 @@ ngx_http_parse_complex_uri(ngx_http_requ
             case '?':
                 r->args_start = p;
                 goto done;
+            case '+':
+                r->plus_in_uri = 1;
             default:
                 state = sw_usual;
                 *u++ = ch;
@@ -917,6 +920,8 @@ ngx_http_parse_complex_uri(ngx_http_requ
                 *u++ = ch;
                 break;
 #endif
+            case '+':
+                r->plus_in_uri = 1;
             default:
                 state = sw_usual;
                 *u++ = ch;
@@ -952,6 +957,8 @@ ngx_http_parse_complex_uri(ngx_http_requ
             case '?':
                 r->args_start = p;
                 goto done;
+            case '+':
+                r->plus_in_uri = 1;
             default:
                 state = sw_usual;
                 *u++ = ch;
@@ -992,8 +999,6 @@ ngx_http_parse_complex_uri(ngx_http_requ
 
                 if (ch == '\0') {
                     r->zero_in_uri = 1;
-                    *u++ = ch;
-                    ch = *p++;
                 }
 
                 state = quoted_state;
@@ -1003,10 +1008,15 @@ ngx_http_parse_complex_uri(ngx_http_requ
             c = (u_char) (ch | 0x20);
             if (c >= 'a' && c <= 'f') {
                 ch = (u_char) ((decoded << 4) + c - 'a' + 10);
+
                 if (ch == '?') {
                     *u++ = ch;
                     ch = *p++;
+
+                } else if (ch == '+') {
+                    r->plus_in_uri = 1;
                 }
+
                 state = quoted_state;
                 break;
             }
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -667,6 +667,7 @@ ngx_http_process_request_line(ngx_event_
                 r->read_event_handler = ngx_http_block_read;
 
                 r->uri_changes = NGX_HTTP_MAX_URI_CHANGES + 1;
+                r->subrequests = NGX_HTTP_MAX_SUBREQUESTS + 1;
 
                 ngx_http_handler(r);
 
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -9,6 +9,7 @@
 
 
 #define NGX_HTTP_MAX_URI_CHANGES           10
+#define NGX_HTTP_MAX_SUBREQUESTS           50
 
 /* must be 2^n */
 #define NGX_HTTP_LC_HEADER_LEN             32
@@ -228,10 +229,13 @@ typedef struct {
     ngx_table_elt_t                  *expires;
     ngx_table_elt_t                  *etag;
 
+    ngx_str_t                        *override_charset;
+
+    size_t                            content_type_len;
     ngx_str_t                         content_type;
     ngx_str_t                         charset;
+
     ngx_array_t                       ranges;
-
     ngx_array_t                       cache_control;
 
     off_t                             content_length_n;
@@ -446,6 +450,8 @@ struct ngx_http_request_s {
     unsigned                          stat_writing:1;
 #endif
 
+    unsigned                          subrequests:8;
+
     /* used to parse HTTP headers */
     ngx_uint_t                        state;
     u_char                           *uri_start;
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -49,6 +49,8 @@ static ngx_int_t ngx_http_upstream_proce
     ngx_table_elt_t *h, ngx_uint_t offset);
 static ngx_int_t ngx_http_upstream_process_buffering(ngx_http_request_t *r,
     ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_charset(ngx_http_request_t *r,
+    ngx_table_elt_t *h, ngx_uint_t offset);
 static ngx_int_t ngx_http_upstream_copy_header_line(ngx_http_request_t *r,
     ngx_table_elt_t *h, ngx_uint_t offset);
 static ngx_int_t
@@ -199,6 +201,10 @@ ngx_http_upstream_header_t  ngx_http_ups
                  ngx_http_upstream_process_buffering, 0,
                  ngx_http_upstream_ignore_header_line, 0, 0 },
 
+    { ngx_string("X-Accel-Charset"),
+                 ngx_http_upstream_process_charset, 0,
+                 ngx_http_upstream_ignore_header_line, 0, 0 },
+
 #if (NGX_HTTP_GZIP)
     { ngx_string("Content-Encoding"),
                  ngx_http_upstream_process_header_line,
@@ -1080,7 +1086,7 @@ ngx_http_upstream_process_header(ngx_eve
 
 
     if (u->headers_in.status_n >= NGX_HTTP_BAD_REQUEST
-        && u->conf->redirect_errors
+        && u->conf->intercept_errors
         && r->err_ctx == NULL)
     {
         clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
@@ -1515,7 +1521,7 @@ ngx_http_upstream_process_non_buffered_b
 
     clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);
 
-    do_write = ev->write;
+    do_write = ev->write || u->length == 0;
 
     for ( ;; ) {
 
@@ -1559,6 +1565,7 @@ ngx_http_upstream_process_non_buffered_b
         }
 
         if (size && u->peer.connection->read->ready) {
+
             n = u->peer.connection->recv(u->peer.connection, b->last, size);
 
             if (n == NGX_AGAIN) {
@@ -2126,6 +2133,16 @@ ngx_http_upstream_process_buffering(ngx_
 
 
 static ngx_int_t
+ngx_http_upstream_process_charset(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{
+    r->headers_out.override_charset = &h->value;
+
+    return NGX_OK;
+}
+
+
+static ngx_int_t
 ngx_http_upstream_copy_header_line(ngx_http_request_t *r, ngx_table_elt_t *h,
     ngx_uint_t offset)
 {
@@ -2184,8 +2201,33 @@ static ngx_int_t
 ngx_http_upstream_copy_content_type(ngx_http_request_t *r, ngx_table_elt_t *h,
     ngx_uint_t offset)
 {
+    u_char  *p, *last;
+
+    r->headers_out.content_type_len = h->value.len;
     r->headers_out.content_type = h->value;
 
+    for (p = h->value.data; *p; p++) {
+
+        if (*p != ';') {
+            continue;
+        }
+
+        last = p;
+
+        while (*++p == ' ') { /* void */ }
+
+        if (ngx_strncasecmp(p, "charset=", 8) != 0) {
+            continue;
+        }
+
+        p += 8;
+
+        r->headers_out.content_type_len = last - h->value.data;
+
+        r->headers_out.charset.len = h->value.data + h->value.len - p;
+        r->headers_out.charset.data = p;
+    }
+
     return NGX_OK;
 }
 
--- a/src/http/ngx_http_upstream.h
+++ b/src/http/ngx_http_upstream.h
@@ -90,7 +90,7 @@ typedef struct {
     ngx_flag_t                      pass_request_body;
 
     ngx_flag_t                      ignore_client_abort;
-    ngx_flag_t                      redirect_errors;
+    ngx_flag_t                      intercept_errors;
     ngx_flag_t                      cyclic_temp_file;
 
     ngx_path_t                     *temp_path;
--- a/src/http/ngx_http_variables.c
+++ b/src/http/ngx_http_variables.c
@@ -354,6 +354,9 @@ ngx_http_get_indexed_variable(ngx_http_r
         return &r->variables[index];
     }
 
+    r->variables[index].valid = 0;
+    r->variables[index].not_found = 1;
+
     return NULL;
 }