changeset 527:7fa11e5c6e96 release-0.1.38

nginx-0.1.38-RELEASE import *) Feature: the "limit_rate" directive is supported in in proxy and FastCGI mode. *) Feature: the "X-Accel-Limit-Rate" response header line is supported in proxy and FastCGI mode. *) Feature: the "break" directive. *) Feature: the "log_not_found" directive. *) Bugfix: the response status code was not changed when request was redirected by the ""X-Accel-Redirect" header line. *) Bugfix: the variables set by the "set" directive could not be used in SSI. *) Bugfix: the segmentation fault may occurred if the SSI page has more than one remote subrequest. *) Bugfix: nginx treated the backend response as invalid if the status line in the header was transferred in two packets; the bug had appeared in 0.1.29. *) Feature: the "ssi_types" directive. *) Feature: the "autoindex_exact_size" directive. *) Bugfix: the ngx_http_autoindex_module did not support the long file names in UTF-8. *) Feature: the IMAP/POP3 proxy.
author Igor Sysoev <igor@sysoev.ru>
date Fri, 08 Jul 2005 14:34:20 +0000
parents e31ce4d8b8e6
children 155de1720e97
files conf/nginx.conf docs/xml/nginx/changes.xml src/core/nginx.h src/core/ngx_connection.h src/core/ngx_palloc.c src/core/ngx_string.c src/core/ngx_string.h src/event/modules/ngx_rtsig_module.c src/event/ngx_event_pipe.c src/http/modules/ngx_http_autoindex_module.c src/http/modules/ngx_http_gzip_filter_module.c src/http/modules/ngx_http_proxy_module.c src/http/modules/ngx_http_rewrite_module.c src/http/modules/ngx_http_ssi_filter_module.c src/http/modules/ngx_http_static_module.c src/http/ngx_http_copy_filter_module.c src/http/ngx_http_core_module.c src/http/ngx_http_core_module.h src/http/ngx_http_postpone_filter_module.c src/http/ngx_http_request.c src/http/ngx_http_request.h src/http/ngx_http_request_body.c src/http/ngx_http_script.c src/http/ngx_http_script.h src/http/ngx_http_special_response.c src/http/ngx_http_upstream.c src/http/ngx_http_upstream.h src/http/ngx_http_variables.c src/http/ngx_http_write_filter_module.c src/imap/ngx_imap.h src/imap/ngx_imap_auth_http_module.c src/imap/ngx_imap_core_module.c src/imap/ngx_imap_handler.c src/imap/ngx_imap_parse.c src/imap/ngx_imap_proxy_module.c src/os/unix/ngx_linux_config.h src/os/unix/ngx_user.c
diffstat 37 files changed, 2339 insertions(+), 367 deletions(-) [+]
line wrap: on
line diff
--- a/conf/nginx.conf
+++ b/conf/nginx.conf
@@ -37,7 +37,7 @@ http {
 
         # deny access to .htaccess files
         #
-        #location ~ \.ht {
+        #location ~ /\.ht {
         #    deny  all;
         #}
     }
--- a/docs/xml/nginx/changes.xml
+++ b/docs/xml/nginx/changes.xml
@@ -9,6 +9,129 @@
 <title lang="en">nginx changelog</title>
 
 
+<changes ver="0.1.38" date="08.07.2005">
+
+<change type="feature">
+<para lang="ru">
+директива limit_rate поддерживается в режиме прокси и FastCGI.
+</para>
+<para lang="en">
+the "limit_rate" directive is supported in in proxy and FastCGI mode.
+</para>
+</change>
+
+<change type="feature">
+<para lang="ru">
+в режиме прокси и FastCGI поддерживается строка заголовка "X-Accel-Limit-Rate"
+в ответе бэкенда.
+</para>
+<para lang="en">
+the "X-Accel-Limit-Rate" response header line is supported in proxy and FastCGI
+mode.
+</para>
+</change>
+
+<change type="feature">
+<para lang="ru">
+директива break.
+</para>
+<para lang="en">
+the "break" directive.
+</para>
+</change>
+
+<change type="feature">
+<para lang="ru">
+директива log_not_found.
+</para>
+<para lang="en">
+the "log_not_found" directive.
+</para>
+</change>
+
+<change type="bugfix">
+<para lang="ru">
+при перенаправлении запроса с помощью строки заголовка "X-Accel-Redirect"
+не изменялся код ответа.
+</para>
+<para lang="en">
+the response status code was not changed when request was redirected
+by the ""X-Accel-Redirect" header line.
+</para>
+</change>
+
+<change type="bugfix">
+<para lang="ru">
+переменные, установленные директивой set не могли использоваться в SSI.
+</para>
+<para lang="en">
+the variables set by the "set" directive could not be used in SSI.
+</para>
+</change>
+
+<change type="bugfix">
+<para lang="ru">
+при включении в SSI более одного удалённого подзапроса
+мог произойти segmentation fault.
+</para>
+<para lang="en">
+the segmentation fault may occurred if the SSI page has more than one
+remote subrequest.
+</para>
+</change>
+
+<change type="bugfix">
+<para lang="ru">
+если статусная строка в ответе бэкенда передавалась в двух пакетах, то
+nginx считал ответ неверным;
+ошибка появилась в 0.1.29.
+</para>
+<para lang="en">
+nginx treated the backend response as invalid if the status line in the
+header was transferred in two packets;
+bug appeared in 0.1.29.
+</para>
+</change>
+
+<change type="feature">
+<para lang="ru">
+директива ssi_types.
+</para>
+<para lang="en">
+the "ssi_types" directive.
+</para>
+</change>
+
+<change type="feature">
+<para lang="ru">
+директива autoindex_exact_size.
+</para>
+<para lang="en">
+the "autoindex_exact_size" directive.
+</para>
+</change>
+
+<change type="bugfix">
+<para lang="ru">
+модуль ngx_http_autoindex_module не поддерживал длинные имена файлов в UTF-8.
+</para>
+<para lang="en">
+the ngx_http_autoindex_module did not support the long file names in UTF-8.
+</para>
+</change>
+
+<change type="feature">
+<para lang="ru">
+IMAP/POP3 прокси.
+</para>
+<para lang="en">
+the IMAP/POP3 proxy.
+</para>
+</change>
+
+</changes>
+
+
 <changes ver="0.1.37" date="23.06.2005">
 
 <change type="change">
@@ -316,7 +439,7 @@ expressions.
 
 <change type="feature">
 <para lang="ru">
-в режиме прокси и FastCGI поддерживается строка заголовка X-Accel-Redirect
+в режиме прокси и FastCGI поддерживается строка заголовка "X-Accel-Redirect"
 в ответе бэкенда.
 </para>
 <para lang="en">
--- a/src/core/nginx.h
+++ b/src/core/nginx.h
@@ -8,7 +8,7 @@
 #define _NGINX_H_INCLUDED_
 
 
-#define NGINX_VER          "nginx/0.1.37"
+#define NGINX_VER          "nginx/0.1.38"
 
 #define NGINX_VAR          "NGINX"
 #define NGX_NEWPID_EXT     ".newbin"
--- a/src/core/ngx_connection.h
+++ b/src/core/ngx_connection.h
@@ -128,6 +128,7 @@ struct ngx_connection_s {
     unsigned            single_connection:1;
     unsigned            unexpected_eof:1;
     unsigned            timedout:1;
+    unsigned            closed:1;
 
     unsigned            sendfile:1;
     unsigned            sndlowat:1;
--- a/src/core/ngx_palloc.c
+++ b/src/core/ngx_palloc.c
@@ -15,7 +15,7 @@ ngx_create_pool(size_t size, ngx_log_t *
 
     p = ngx_alloc(size, log);
     if (p == NULL) {
-       return NULL;
+        return NULL;
     }
 
     p->last = (u_char *) p + sizeof(ngx_pool_t);
--- a/src/core/ngx_string.c
+++ b/src/core/ngx_string.c
@@ -753,20 +753,62 @@ ngx_utf_length(ngx_str_t *utf)
             continue;
         }
 
-        if (c < 0xC0) {
-            /* invalid utf */
-            return utf->len;
+        if (c >= 0xc0) {
+            for (c <<= 1; c & 0x80; c <<= 1) {
+                i++;
+            }
+
+            continue;
         }
 
-        for (c <<= 1; c & 0x80; c <<= 1) {
-            i++;
-        }
+        /* invalid utf */
+
+        return utf->len;
     }
 
     return len;
 }
 
 
+u_char *
+ngx_utf_cpystrn(u_char *dst, u_char *src, size_t n)
+{
+    u_char  c;
+
+    if (n == 0) {
+        return dst;
+    }
+
+    for ( /* void */ ; --n; dst++, src++) {
+
+        c = *src;
+        *dst = c;
+
+        if (c < 0x80) {
+            if (*dst != '\0') {
+                continue;
+            }
+
+            return dst;
+        }
+
+        if (c >= 0xc0) {
+            for (c <<= 1; c & 0x80; c <<= 1) {
+               *++dst = *++src;
+            }
+
+            continue;
+        }
+
+        /* invalid utf */
+    }
+
+    *dst = '\0';
+
+    return dst;
+}
+
+
 uintptr_t
 ngx_escape_uri(u_char *dst, u_char *src, size_t size, ngx_uint_t type)
 {
--- a/src/core/ngx_string.h
+++ b/src/core/ngx_string.h
@@ -97,6 +97,8 @@ void ngx_encode_base64(ngx_str_t *dst, n
 ngx_int_t ngx_decode_base64(ngx_str_t *dst, ngx_str_t *src);
 
 size_t ngx_utf_length(ngx_str_t *utf);
+u_char * ngx_utf_cpystrn(u_char *dst, u_char *src, size_t n);
+
 
 #define NGX_ESCAPE_URI   0
 #define NGX_ESCAPE_ARGS  1
--- a/src/event/modules/ngx_rtsig_module.c
+++ b/src/event/modules/ngx_rtsig_module.c
@@ -761,7 +761,7 @@ ngx_rtsig_process_overflow(ngx_cycle_t *
         }
     }
 
-    ngx_log_error(NGX_LOG_INFO, cycle->log, 0,
+    ngx_log_error(NGX_LOG_ALERT, cycle->log, 0,
                   "rt signal queue overflow recovered");
 
     overflow = 0;
--- a/src/event/ngx_event_pipe.c
+++ b/src/event/ngx_event_pipe.c
@@ -182,7 +182,8 @@ static ngx_int_t ngx_event_pipe_read_ups
 
             } else if (!p->cachable
                        && p->downstream->data == p->output_ctx
-                       && p->downstream->write->ready)
+                       && p->downstream->write->ready
+                       && !p->downstream->write->delayed)
             {
                 /*
                  * if the bufs are not needed to be saved in a cache and
@@ -461,7 +462,8 @@ static ngx_int_t ngx_event_pipe_write_to
         }
 
         if (p->downstream->data != p->output_ctx
-            || !p->downstream->write->ready)
+            || !p->downstream->write->ready
+            || p->downstream->write->delayed)
         {
             break;
         }
--- a/src/http/modules/ngx_http_autoindex_module.c
+++ b/src/http/modules/ngx_http_autoindex_module.c
@@ -35,6 +35,7 @@ typedef struct {
 typedef struct {
     ngx_flag_t     enable;
     ngx_flag_t     localtime;
+    ngx_flag_t     exact_size;
 } ngx_http_autoindex_loc_conf_t;
 
 
@@ -67,6 +68,13 @@ static ngx_command_t  ngx_http_autoindex
       offsetof(ngx_http_autoindex_loc_conf_t, localtime),
       NULL },
 
+    { ngx_string("autoindex_exact_size"),
+      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_autoindex_loc_conf_t, exact_size),
+      NULL },
+
       ngx_null_command
 };
 
@@ -117,10 +125,11 @@ static u_char tail[] =
 static ngx_int_t
 ngx_http_autoindex_handler(ngx_http_request_t *r)
 {
-    u_char                         *last;
-    size_t                          len;
+    u_char                         *last, scale;
+    off_t                           length;
+    size_t                          len, copy;
     ngx_tm_t                        tm;
-    ngx_int_t                       rc;
+    ngx_int_t                       rc, size;
     ngx_uint_t                      i, level;
     ngx_err_t                       err;
     ngx_buf_t                      *b;
@@ -351,7 +360,7 @@ ngx_http_autoindex_handler(ngx_http_requ
             + NGX_HTTP_AUTOINDEX_NAME_LEN + sizeof("&gt;") - 2
             + sizeof("</a>") - 1
             + sizeof(" 28-Sep-1970 12:00 ") - 1
-            + 19
+            + 20
             + 2;
     }
 
@@ -396,14 +405,27 @@ ngx_http_autoindex_handler(ngx_http_requ
         *b->last++ = '"';
         *b->last++ = '>';
 
-        b->last = ngx_cpystrn(b->last, entry[i].name.data,
-                              NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
-
         len = entry[i].utf_len;
 
+        if (len) {
+            if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
+                copy = NGX_HTTP_AUTOINDEX_NAME_LEN - 3 + 1;
+
+            } else {
+                copy = NGX_HTTP_AUTOINDEX_NAME_LEN + 1;
+            }
+
+            b->last = ngx_utf_cpystrn(b->last, entry[i].name.data, copy);
+            last = b->last;
+
+        } else {
+            b->last = ngx_cpystrn(b->last, entry[i].name.data,
+                                  NGX_HTTP_AUTOINDEX_NAME_LEN + 1);
+            last = b->last - 3;
+        }
+
         if (len > NGX_HTTP_AUTOINDEX_NAME_LEN) {
-            b->last = ngx_cpymem(b->last - 3, "..&gt;</a>",
-                                 sizeof("..&gt;</a>") - 1);
+            b->last = ngx_cpymem(last, "..&gt;</a>", sizeof("..&gt;</a>") - 1);
 
         } else {
             if (entry[i].dir && NGX_HTTP_AUTOINDEX_NAME_LEN - len > 0) {
@@ -427,12 +449,55 @@ ngx_http_autoindex_handler(ngx_http_requ
                               tm.ngx_tm_hour,
                               tm.ngx_tm_min);
 
-        if (entry[i].dir) {
-            b->last = ngx_cpymem(b->last,  "                  -",
-                                 sizeof("                  -") - 1);
+        if (alcf->exact_size) {
+            if (entry[i].dir) {
+                b->last = ngx_cpymem(b->last,  "                  -",
+                                     sizeof("                  -") - 1);
+            } else {
+                b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
+            } 
 
         } else {
-            b->last = ngx_sprintf(b->last, "%19O", entry[i].size);
+            if (entry[i].dir) {
+                b->last = ngx_cpymem(b->last,  "     -", sizeof("     -") - 1);
+
+            } else {
+                length = entry[i].size;
+
+                if (length > 1024 * 1024 * 1024 - 1) {
+                    size = (ngx_int_t) (length / (1024 * 1024 * 1024));
+                    if ((length % (1024 * 1024 * 1024))
+                                                > (1024 * 1024 * 1024 / 2 - 1))
+                    { 
+                        size++;
+                    }
+                    scale = 'G';
+
+                } else if (length > 1024 * 1024 - 1) {
+                    size = (ngx_int_t) (length / (1024 * 1024));
+                    if ((length % (1024 * 1024)) > (1024 * 1024 / 2 - 1)) {
+                        size++;
+                    }
+                    scale = 'M';
+
+                } else if (length > 9999) {
+                    size = (ngx_int_t) (length / 1024);
+                    if (length % 1024 > 511) {
+                        size++;
+                    }
+                    scale = 'K';
+
+                } else {
+                    size = (ngx_int_t) length;
+                    scale = ' ';
+                }
+
+                b->last = ngx_sprintf(b->last, "%6i", size);
+
+                if (scale != ' ') {
+                    *b->last++ = scale;
+                }
+            }
         }
 
         *b->last++ = CR;
@@ -559,6 +624,7 @@ ngx_http_autoindex_create_loc_conf(ngx_c
 
     conf->enable = NGX_CONF_UNSET;
     conf->localtime = NGX_CONF_UNSET;
+    conf->exact_size = NGX_CONF_UNSET;
 
     return conf;
 }
@@ -572,6 +638,7 @@ ngx_http_autoindex_merge_loc_conf(ngx_co
 
     ngx_conf_merge_value(conf->enable, prev->enable, 0);
     ngx_conf_merge_value(conf->localtime, prev->localtime, 0);
+    ngx_conf_merge_value(conf->exact_size, prev->exact_size, 1);
 
     return NGX_CONF_OK;
 }
--- a/src/http/modules/ngx_http_gzip_filter_module.c
+++ b/src/http/modules/ngx_http_gzip_filter_module.c
@@ -15,7 +15,7 @@ typedef struct {
     ngx_flag_t           enable;
     ngx_flag_t           no_buffer;
 
-    ngx_array_t         *types;     /* array of ngx_http_gzip_type_t */
+    ngx_array_t         *types;     /* array of ngx_str_t */
 
     ngx_bufs_t           bufs;
 
@@ -29,12 +29,6 @@ typedef struct {
 } ngx_http_gzip_conf_t;
 
 
-typedef struct {
-    ngx_str_t            name;
-    ngx_uint_t           enable;
-} ngx_http_gzip_type_t;
-
-
 #define NGX_HTTP_GZIP_PROXIED_OFF       0x0002
 #define NGX_HTTP_GZIP_PROXIED_EXPIRED   0x0004
 #define NGX_HTTP_GZIP_PROXIED_NO_CACHE  0x0008
@@ -91,20 +85,18 @@ static ngx_int_t ngx_http_gzip_filter_in
 static void *ngx_http_gzip_create_conf(ngx_conf_t *cf);
 static char *ngx_http_gzip_merge_conf(ngx_conf_t *cf,
     void *parent, void *child);
-static char *ngx_http_gzip_set_types(ngx_conf_t *cf, ngx_command_t *cmd,
+static char *ngx_http_gzip_types(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
-static char *ngx_http_gzip_set_window(ngx_conf_t *cf, void *post, void *data);
-static char *ngx_http_gzip_set_hash(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data);
+static char *ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data);
 
 
 static ngx_conf_num_bounds_t  ngx_http_gzip_comp_level_bounds = {
     ngx_conf_check_num_bounds, 1, 9
 };
 
-static ngx_conf_post_handler_pt  ngx_http_gzip_set_window_p =
-                                                      ngx_http_gzip_set_window;
-static ngx_conf_post_handler_pt  ngx_http_gzip_set_hash_p =
-                                                        ngx_http_gzip_set_hash;
+static ngx_conf_post_handler_pt  ngx_http_gzip_window_p = ngx_http_gzip_window;
+static ngx_conf_post_handler_pt  ngx_http_gzip_hash_p = ngx_http_gzip_hash;
 
 
 
@@ -148,7 +140,7 @@ static ngx_command_t  ngx_http_gzip_filt
 
     { ngx_string("gzip_types"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
-      ngx_http_gzip_set_types,
+      ngx_http_gzip_types,
       NGX_HTTP_LOC_CONF_OFFSET,
       0,
       NULL },
@@ -165,14 +157,14 @@ static ngx_command_t  ngx_http_gzip_filt
       ngx_conf_set_size_slot,
       NGX_HTTP_LOC_CONF_OFFSET,
       offsetof(ngx_http_gzip_conf_t, wbits),
-      &ngx_http_gzip_set_window_p },
+      &ngx_http_gzip_window_p },
 
     { ngx_string("gzip_hash"),
       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_gzip_conf_t, memlevel),
-      &ngx_http_gzip_set_hash_p },
+      &ngx_http_gzip_hash_p },
 
     { ngx_string("gzip_no_buffer"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_FLAG,
@@ -270,10 +262,10 @@ static ngx_http_output_body_filter_pt   
 static ngx_int_t
 ngx_http_gzip_header_filter(ngx_http_request_t *r)
 {
-    ngx_uint_t             i, found;
+    ngx_str_t             *type;
+    ngx_uint_t             i;
     ngx_http_gzip_ctx_t   *ctx;
     ngx_http_gzip_conf_t  *conf;
-    ngx_http_gzip_type_t  *type;
 
     conf = ngx_http_get_module_loc_conf(r, ngx_http_gzip_filter_module);
 
@@ -297,24 +289,21 @@ ngx_http_gzip_header_filter(ngx_http_req
     }
 
 
-    found = 0;
     type = conf->types->elts;
-
     for (i = 0; i < conf->types->nelts; i++) {
-        if (r->headers_out.content_type.len >= type[i].name.len
+        if (r->headers_out.content_type.len >= type[i].len
             && ngx_strncasecmp(r->headers_out.content_type.data, 
-                               type[i].name.data, type[i].name.len) == 0)
+                               type[i].data, type[i].len) == 0)
         {
-            found = 1;
-            break;
+            goto found;
         }
     }
 
-    if (!found) {
-        return ngx_http_next_header_filter(r);
-    }
+    return ngx_http_next_header_filter(r);
 
 
+found:
+
     if (r->headers_in.via) {
         if (conf->proxied & NGX_HTTP_GZIP_PROXIED_OFF) {
             return ngx_http_next_header_filter(r);
@@ -1031,7 +1020,7 @@ ngx_http_gzip_merge_conf(ngx_conf_t *cf,
     ngx_http_gzip_conf_t *prev = parent;
     ngx_http_gzip_conf_t *conf = child;
 
-    ngx_http_gzip_type_t  *type;
+    ngx_str_t  *type;
 
     ngx_conf_merge_value(conf->enable, prev->enable, 0);
 
@@ -1051,8 +1040,7 @@ ngx_http_gzip_merge_conf(ngx_conf_t *cf,
 
     if (conf->types == NULL) {
         if (prev->types == NULL) {
-            conf->types = ngx_array_create(cf->pool, 1,
-                                           sizeof(ngx_http_gzip_type_t));
+            conf->types = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t));
             if (conf->types == NULL) {
                 return NGX_CONF_ERROR;
             }
@@ -1062,9 +1050,8 @@ ngx_http_gzip_merge_conf(ngx_conf_t *cf,
                 return NGX_CONF_ERROR;
             }
 
-            type->name.len = sizeof("text/html") - 1;
-            type->name.data = (u_char *) "text/html";
-            type->enable = 1;
+            type->len = sizeof("text/html") - 1;
+            type->data = (u_char *) "text/html";
 
         } else {
             conf->types = prev->types;
@@ -1076,17 +1063,15 @@ ngx_http_gzip_merge_conf(ngx_conf_t *cf,
 
 
 static char *
-ngx_http_gzip_set_types(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+ngx_http_gzip_types(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     ngx_http_gzip_conf_t *gcf = conf;
 
-    ngx_str_t             *value;
-    ngx_uint_t             i;
-    ngx_http_gzip_type_t  *type;
+    ngx_str_t   *value, *type;
+    ngx_uint_t   i;
 
     if (gcf->types == NULL) {
-        gcf->types = ngx_array_create(cf->pool, 4,
-                                      sizeof(ngx_http_gzip_type_t));
+        gcf->types = ngx_array_create(cf->pool, 4, sizeof(ngx_str_t));
         if (gcf->types == NULL) {
             return NGX_CONF_ERROR;
         }
@@ -1096,9 +1081,8 @@ ngx_http_gzip_set_types(ngx_conf_t *cf, 
             return NGX_CONF_ERROR;
         }
 
-        type->name.len = sizeof("text/html") - 1;
-        type->name.data = (u_char *) "text/html";
-        type->enable = 1;
+        type->len = sizeof("text/html") - 1;
+        type->data = (u_char *) "text/html";
     }
 
     value = cf->args->elts;
@@ -1114,14 +1098,14 @@ ngx_http_gzip_set_types(ngx_conf_t *cf, 
             return NGX_CONF_ERROR;
         }
 
-        type->name.len = value[i].len;
+        type->len = value[i].len;
 
-        type->name.data = ngx_palloc(cf->pool, type->name.len + 1);
-        if (type->name.data == NULL) {
+        type->data = ngx_palloc(cf->pool, type->len + 1);
+        if (type->data == NULL) {
             return NGX_CONF_ERROR;
         }
 
-        ngx_cpystrn(type->name.data, value[i].data, type->name.len + 1);
+        ngx_cpystrn(type->data, value[i].data, type->len + 1);
     }
 
     return NGX_CONF_OK;
@@ -1129,7 +1113,7 @@ ngx_http_gzip_set_types(ngx_conf_t *cf, 
 
 
 static char *
-ngx_http_gzip_set_window(ngx_conf_t *cf, void *post, void *data)
+ngx_http_gzip_window(ngx_conf_t *cf, void *post, void *data)
 {
     int *np = data;
 
@@ -1153,7 +1137,7 @@ ngx_http_gzip_set_window(ngx_conf_t *cf,
 
 
 static char *
-ngx_http_gzip_set_hash(ngx_conf_t *cf, void *post, void *data)
+ngx_http_gzip_hash(ngx_conf_t *cf, void *post, void *data)
 {
     int *np = data;
 
--- a/src/http/modules/ngx_http_proxy_module.c
+++ b/src/http/modules/ngx_http_proxy_module.c
@@ -914,7 +914,7 @@ ngx_http_proxy_parse_status_line(ngx_htt
             }
             break;
 
-        /* end of request line */
+        /* end of status line */
         case sw_almost_done:
             p->status_end = pos - 1;
             switch (ch) {
@@ -926,7 +926,7 @@ ngx_http_proxy_parse_status_line(ngx_htt
         }
     }
 
-    u->header_in.pos = pos + 1;
+    u->header_in.pos = pos;
     r->state = state;
 
     return NGX_AGAIN;
@@ -1803,7 +1803,7 @@ ngx_http_proxy_pass(ngx_conf_t *cf, ngx_
         }
 
         for (i = 0; i < plcf->peers->number; i++) {
-            plcf->peers->peer[i].uri_separator = ":";
+            plcf->peers->peer[i].uri_separator = "";
         }
 
         plcf->host_header = inet_upstream.host_header;
--- a/src/http/modules/ngx_http_rewrite_module.c
+++ b/src/http/modules/ngx_http_rewrite_module.c
@@ -36,6 +36,8 @@ static ngx_int_t ngx_http_rewrite_init(n
 static char *ngx_http_rewrite(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
 static char *ngx_http_rewrite_return(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
+static char *ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
 static char *ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
 static char * ngx_http_rewrite_if_condition(ngx_conf_t *cf,
@@ -66,6 +68,14 @@ static ngx_command_t  ngx_http_rewrite_c
       0,
       NULL },
 
+    { ngx_string("break"),
+      NGX_HTTP_SRV_CONF|NGX_HTTP_SIF_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
+                       |NGX_CONF_NOARGS,
+      ngx_http_rewrite_break,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
     { ngx_string("if"),
       NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_BLOCK|NGX_CONF_1MORE,
       ngx_http_rewrite_if,
@@ -601,6 +611,24 @@ ngx_http_rewrite_return(ngx_conf_t *cf, 
 
 
 static char *
+ngx_http_rewrite_break(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_rewrite_loc_conf_t *lcf = conf;
+
+    ngx_http_script_code_pt  *code;
+
+    code = ngx_http_script_start_code(cf->pool, &lcf->codes, sizeof(uintptr_t));
+    if (code == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    *code = ngx_http_script_break_code;
+
+    return NGX_CONF_OK;
+}
+
+
+static char *
 ngx_http_rewrite_if(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
 {
     ngx_http_rewrite_loc_conf_t *lcf = conf;
--- a/src/http/modules/ngx_http_ssi_filter_module.c
+++ b/src/http/modules/ngx_http_ssi_filter_module.c
@@ -24,6 +24,8 @@ typedef struct {
     ngx_flag_t        silent_errors;
     ngx_flag_t        ignore_recycled_buffers;
 
+    ngx_array_t      *types;     /* array of ngx_str_t */
+
     size_t            min_file_chunk;
     size_t            value_len;
 } ngx_http_ssi_conf_t;
@@ -127,6 +129,8 @@ static ngx_int_t ngx_http_ssi_endif(ngx_
 static ngx_http_variable_value_t *
     ngx_http_ssi_date_gmt_local_variable(ngx_http_request_t *r, uintptr_t gmt);
 
+static char *ngx_http_ssi_types(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
+
 static ngx_int_t ngx_http_ssi_add_variables(ngx_conf_t *cf);
 static void *ngx_http_ssi_create_conf(ngx_conf_t *cf);
 static char *ngx_http_ssi_merge_conf(ngx_conf_t *cf,
@@ -164,6 +168,13 @@ static ngx_command_t  ngx_http_ssi_filte
       offsetof(ngx_http_ssi_conf_t, min_file_chunk),
       NULL },
 
+    { ngx_string("ssi_types"),
+      NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_CONF_1MORE,
+      ngx_http_ssi_types,
+      NGX_HTTP_LOC_CONF_OFFSET,
+      0,
+      NULL },
+
       ngx_null_command
 };
 
@@ -201,7 +212,7 @@ static ngx_int_t (*ngx_http_next_body_fi
 
 static u_char ngx_http_ssi_string[] = "<!--";
 static u_char ngx_http_ssi_error_string[] =
-                          "[an error occurred while processing the directive]";
+    "[an error occurred while processing the directive]";
 
 static ngx_str_t ngx_http_ssi_none = ngx_string("(none)");
 
@@ -280,25 +291,35 @@ static ngx_http_variable_t  ngx_http_ssi
 static ngx_int_t
 ngx_http_ssi_header_filter(ngx_http_request_t *r)
 {
+    ngx_uint_t            i;
+    ngx_str_t            *type;
     ngx_http_ssi_ctx_t   *ctx;
     ngx_http_ssi_conf_t  *conf;
 
     conf = ngx_http_get_module_loc_conf(r, ngx_http_ssi_filter_module);
 
-    if (!conf->enable) {
-        return ngx_http_next_header_filter(r);
-    }
-
-    /* TODO: "text/html" -> custom types */
-
-    if (r->headers_out.content_type.len == 0
-        || ngx_strncasecmp(r->headers_out.content_type.data, "text/html", 5)
-           != 0)
+    if (!conf->enable
+        || r->headers_out.content_type.len == 0)
     {
         return ngx_http_next_header_filter(r);
     }
 
 
+    type = conf->types->elts;
+    for (i = 0; i < conf->types->nelts; i++) {
+        if (r->headers_out.content_type.len >= type[i].len
+            && ngx_strncasecmp(r->headers_out.content_type.data,
+                               type[i].data, type[i].len) == 0)
+        {
+            goto found;
+        }
+    }
+
+    return ngx_http_next_header_filter(r);
+
+
+found:
+
     ctx = ngx_pcalloc(r->pool, sizeof(ngx_http_ssi_ctx_t));
     if (ctx == NULL) {
         return NGX_ERROR;
@@ -1632,6 +1653,56 @@ ngx_http_ssi_date_gmt_local_variable(ngx
 }
 
 
+static char *
+ngx_http_ssi_types(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    ngx_http_ssi_conf_t *scf = conf;
+
+    ngx_str_t   *value, *type;
+    ngx_uint_t   i;
+
+    if (scf->types == NULL) {
+        scf->types = ngx_array_create(cf->pool, 4, sizeof(ngx_str_t));
+        if (scf->types == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        type = ngx_array_push(scf->types);
+        if (type == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        type->len = sizeof("text/html") - 1;
+        type->data = (u_char *) "text/html";
+    }
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+
+        if (ngx_strcmp(value[i].data, "text/html") == 0) {
+            continue;
+        }
+
+        type = ngx_array_push(scf->types);
+        if (type == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        type->len = value[i].len;
+
+        type->data = ngx_palloc(cf->pool, type->len + 1);
+        if (type->data == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        ngx_cpystrn(type->data, value[i].data, type->len + 1);
+    }
+
+    return NGX_CONF_OK;
+}
+
+
 static ngx_int_t
 ngx_http_ssi_add_variables(ngx_conf_t *cf)
 {
@@ -1661,6 +1732,12 @@ ngx_http_ssi_create_conf(ngx_conf_t *cf)
         return NGX_CONF_ERROR;
     }
 
+    /*
+     * set by ngx_pcalloc():
+     *
+     *     conf->types = NULL;
+     */
+
     conf->enable = NGX_CONF_UNSET;
     conf->silent_errors = NGX_CONF_UNSET;
     conf->ignore_recycled_buffers = NGX_CONF_UNSET;
@@ -1678,6 +1755,8 @@ ngx_http_ssi_merge_conf(ngx_conf_t *cf, 
     ngx_http_ssi_conf_t *prev = parent;
     ngx_http_ssi_conf_t *conf = child;
 
+    ngx_str_t  *type;
+
     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,
@@ -1686,6 +1765,26 @@ ngx_http_ssi_merge_conf(ngx_conf_t *cf, 
     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, 256);
 
+    if (conf->types == NULL) {
+        if (prev->types == NULL) {
+            conf->types = ngx_array_create(cf->pool, 1, sizeof(ngx_str_t));
+            if (conf->types == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            type = ngx_array_push(conf->types);
+            if (type == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            type->len = sizeof("text/html") - 1;
+            type->data = (u_char *) "text/html";
+
+        } else {
+            conf->types = prev->types;
+        }
+    }
+
     return NGX_CONF_OK;
 }
 
--- a/src/http/modules/ngx_http_static_module.c
+++ b/src/http/modules/ngx_http_static_module.c
@@ -210,8 +210,10 @@ ngx_http_static_handler(ngx_http_request
             rc = NGX_HTTP_INTERNAL_SERVER_ERROR;
         }
 
-        ngx_log_error(level, log, err,
-                      ngx_open_file_n " \"%s\" failed", name.data);
+        if (rc != NGX_HTTP_NOT_FOUND || clcf->log_not_found) {
+            ngx_log_error(level, log, err,
+                          ngx_open_file_n " \"%s\" failed", name.data);
+        }
 
         return rc;
     }
--- a/src/http/ngx_http_copy_filter_module.c
+++ b/src/http/ngx_http_copy_filter_module.c
@@ -68,13 +68,17 @@ ngx_http_copy_filter(ngx_http_request_t 
     ngx_output_chain_ctx_t       *ctx;
     ngx_http_copy_filter_conf_t  *conf;
 
-    if (r->connection->write->error) {
-        return NGX_ERROR;
-    }
-
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "copy filter: \"%V\"", &r->uri);
 
+    if (r->connection->closed) {
+        rc = ngx_http_next_filter(r, in);
+
+        ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "copy closed filter: %i \"%V\"", rc, &r->uri);
+        return rc;
+    }
+
     ctx = ngx_http_get_module_ctx(r, ngx_http_copy_filter_module);
 
     if (ctx == NULL) {
--- a/src/http/ngx_http_core_module.c
+++ b/src/http/ngx_http_core_module.c
@@ -326,6 +326,13 @@ static ngx_command_t  ngx_http_core_comm
       offsetof(ngx_http_core_loc_conf_t, msie_padding),
       NULL },
 
+    { ngx_string("log_not_found"),
+      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_core_loc_conf_t, log_not_found),
+      NULL },
+
     { ngx_string("error_page"),
       NGX_HTTP_MAIN_CONF|NGX_HTTP_SRV_CONF|NGX_HTTP_LOC_CONF|NGX_HTTP_LIF_CONF
                         |NGX_CONF_2MORE,
@@ -633,6 +640,8 @@ ngx_http_find_location_config(ngx_http_r
         return NGX_HTTP_MOVED_PERMANENTLY;
     }
 
+    r->limit_rate = clcf->limit_rate;
+
     if (clcf->handler) {
         r->content_handler = clcf->handler;
     }
@@ -862,10 +871,6 @@ ngx_http_output_filter(ngx_http_request_
 {
     ngx_int_t  rc;
 
-    if (r->connection->write->error) {
-        return NGX_ERROR;
-    }
-
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
                    "http output filter \"%V\"", &r->uri);
 
@@ -873,7 +878,7 @@ ngx_http_output_filter(ngx_http_request_
 
     if (rc == NGX_ERROR) {
         /* NGX_ERROR may be returned by any filter */
-        r->connection->write->error = 1;
+        r->connection->closed = 1;
     }
 
     return rc;
@@ -1727,6 +1732,7 @@ ngx_http_core_create_loc_conf(ngx_conf_t
     lcf->reset_timedout_connection = NGX_CONF_UNSET;
     lcf->port_in_redirect = NGX_CONF_UNSET;
     lcf->msie_padding = NGX_CONF_UNSET;
+    lcf->log_not_found = NGX_CONF_UNSET;
 
     return lcf;
 }
@@ -1841,6 +1847,7 @@ ngx_http_core_merge_loc_conf(ngx_conf_t 
                               prev->reset_timedout_connection, 0);
     ngx_conf_merge_value(conf->port_in_redirect, prev->port_in_redirect, 1);
     ngx_conf_merge_value(conf->msie_padding, prev->msie_padding, 1);
+    ngx_conf_merge_value(conf->log_not_found, prev->log_not_found, 1);
 
     if (conf->open_files == NULL) {
         conf->open_files = prev->open_files;
--- a/src/http/ngx_http_core_module.h
+++ b/src/http/ngx_http_core_module.h
@@ -224,6 +224,7 @@ struct ngx_http_core_loc_conf_s {
     ngx_flag_t    reset_timedout_connection; /* reset_timedout_connection */
     ngx_flag_t    port_in_redirect;        /* port_in_redirect */
     ngx_flag_t    msie_padding;            /* msie_padding */
+    ngx_flag_t    log_not_found;           /* log_not_found */
 
     ngx_array_t  *error_pages;             /* error_page */
 
--- a/src/http/ngx_http_postpone_filter_module.c
+++ b/src/http/ngx_http_postpone_filter_module.c
@@ -48,13 +48,18 @@ ngx_http_postpone_filter(ngx_http_reques
     ngx_http_request_t            *mr;
     ngx_http_postponed_request_t  *pr, **ppr;
 
-    if (r->connection->write->error) {
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http postpone filter \"%V\" %p", &r->uri, in);
+
+    if (r->connection->closed) {
+
+        if (r->postponed) {
+            r->postponed = r->postponed->next;
+        }
+
         return NGX_ERROR;
     }
 
-    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                   "http postpone filter \"%V\" %p", &r->uri, in);
-
     if (r != r->connection->data || (r->postponed && in)) {
 
         if (r->postponed) {
@@ -112,7 +117,7 @@ ngx_http_postpone_filter(ngx_http_reques
 
     if (rc == NGX_ERROR) {
         /* NGX_ERROR may be returned by any filter */
-        r->connection->write->error = 1;
+        r->connection->closed = 1;
     }
 
     return rc;
--- a/src/http/ngx_http_request.c
+++ b/src/http/ngx_http_request.c
@@ -90,11 +90,6 @@ ngx_http_header_t  ngx_http_headers_in[]
     { ngx_string("Range"), offsetof(ngx_http_headers_in_t, range),
                  ngx_http_process_header_line },
 
-#if 0
-    { ngx_string("If-Range"), offsetof(ngx_http_headers_in_t, if_range),
-                 ngx_http_process_header_line },
-#endif
-
 #if (NGX_HTTP_GZIP)
     { ngx_string("Accept-Encoding"),
                  offsetof(ngx_http_headers_in_t, accept_encoding),
@@ -1441,6 +1436,8 @@ ngx_http_finalize_request(ngx_http_reque
     r->done = 1;
 
     if (r != r->connection->data) {
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http finalize non-active request: \"%V\"", &r->uri);
         return;
     }
 
@@ -1448,12 +1445,18 @@ ngx_http_finalize_request(ngx_http_reque
 
         pr = r->parent;
 
+        ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                       "http parent request: \"%V\"", &pr->uri);
+
         if (rc != NGX_AGAIN) {
             pr->connection->data = pr;
         }
 
         if (pr->postponed) {
 
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http request: \"%V\" has postponed", &pr->uri);
+
             if (rc != NGX_AGAIN && pr->postponed->request == r) {
                 pr->postponed = pr->postponed->next;
 
@@ -1462,9 +1465,13 @@ ngx_http_finalize_request(ngx_http_reque
                 }
             }
 
+            ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                           "http request: \"%V\" still has postponed",
+                           &pr->uri);
+
             if (pr->done) {
                 ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                               "http wake request: \"%V\"", &pr->uri);
+                               "http wake parent request: \"%V\"", &pr->uri);
 
                 pr->write_event_handler(pr);
             }
@@ -1483,7 +1490,7 @@ ngx_http_finalize_request(ngx_http_reque
             ngx_del_timer(r->connection->write);
         }
 
-        if (rc == NGX_HTTP_CLIENT_CLOSED_REQUEST || r->closed) {
+        if (r->connection->closed) {
             ngx_http_close_request(r, 0);
             ngx_http_close_connection(r->connection);
             return;
@@ -1492,13 +1499,15 @@ ngx_http_finalize_request(ngx_http_reque
         ngx_http_finalize_request(r, ngx_http_special_response_handler(r, rc));
 
         return;
-
-    } else if (rc == NGX_ERROR) {
+    }
+
+    if (rc == NGX_ERROR || r->connection->closed) {
         ngx_http_close_request(r, 0);
         ngx_http_close_connection(r->connection);
         return;
-
-    } else if (rc == NGX_AGAIN || r->out) {
+    }
+
+    if (rc == NGX_AGAIN || r->out) {
         (void) ngx_http_set_write_handler(r);
         return;
     }
@@ -1553,6 +1562,10 @@ ngx_http_set_write_handler(ngx_http_requ
 
     r->write_event_handler = ngx_http_writer;
 
+    if (r->connection->closed) {
+        return NGX_OK;
+    }
+
     wev = r->connection->write;
 
     if (wev->ready && wev->delayed) {
@@ -1673,6 +1686,9 @@ static ngx_int_t
 ngx_http_postponed_handler(ngx_http_request_t *r)
 {
     ngx_int_t                      rc;
+#if 0
+    ngx_http_request_t            *mr;
+#endif
     ngx_http_postponed_request_t  *pr;
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
@@ -1687,20 +1703,16 @@ ngx_http_postponed_handler(ngx_http_requ
         rc = ngx_http_output_filter(r, NULL);
 
         ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                           "http postponed output filter: %d", rc);
+                       "http postponed output filter: %d", rc);
 
         if (rc == NGX_AGAIN) {
             return rc;
         }
 
-        if (rc == NGX_ERROR) {
-            /* NGX_ERROR may be returned by any filter */
-            r->connection->write->error = 1;
-
-            ngx_http_finalize_request(r, rc);
-
-            return NGX_DONE;
-        }
+        /*
+         * we treat NGX_ERROR as NGX_OK, because we need to complete
+         * all postponed requests
+         */
 
         pr = r->postponed;
 
@@ -1713,7 +1725,7 @@ ngx_http_postponed_handler(ngx_http_requ
     r->connection->data = r;
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
-                   "http postponed request \"%V\"", &r->uri);
+                   "http wake child request \"%V\"", &r->uri);
 
     r->write_event_handler(r);
 
@@ -1833,7 +1845,7 @@ ngx_http_read_discarded_body(ngx_http_re
 
     if (n == NGX_ERROR) {
 
-        r->closed = 1;
+        r->connection->closed = 1;
 
         /*
          * if a client request body is discarded then we already set
--- a/src/http/ngx_http_request.h
+++ b/src/http/ngx_http_request.h
@@ -310,6 +310,8 @@ struct ngx_http_request_s {
 
     ngx_http_variable_value_t       **variables;
 
+    size_t                            limit_rate;
+
     /* used to learn the Apache compatible response length without a header */
     size_t                            header_size;
 
@@ -361,7 +363,6 @@ struct ngx_http_request_s {
     unsigned                          keepalive:1;
     unsigned                          lingering_close:1;
     unsigned                          internal:1;
-    unsigned                          closed:1;
     unsigned                          done:1;
     unsigned                          utf8:1;
 
--- a/src/http/ngx_http_request_body.c
+++ b/src/http/ngx_http_request_body.c
@@ -236,7 +236,7 @@ ngx_http_do_read_client_request_body(ngx
         }
 
         if (n == 0 || n == NGX_ERROR) {
-            r->closed = 1;
+            c->closed = 1;
             return NGX_HTTP_BAD_REQUEST;
         }
 
--- a/src/http/ngx_http_script.c
+++ b/src/http/ngx_http_script.c
@@ -558,6 +558,7 @@ ngx_http_script_regex_start_code(ngx_htt
 
         if (code->break_cycle) {
             r->valid_location = 0;
+            r->uri_changed = 0;
 
         } else {
             r->uri_changed = 1;
@@ -713,6 +714,15 @@ ngx_http_script_return_code(ngx_http_scr
 
 
 void
+ngx_http_script_break_code(ngx_http_script_engine_t *e)
+{
+    e->request->uri_changed = 0;
+
+    e->ip = ngx_http_script_exit;
+}
+
+
+void
 ngx_http_script_if_code(ngx_http_script_engine_t *e)
 {
     ngx_http_script_if_code_t  *code;
--- a/src/http/ngx_http_script.h
+++ b/src/http/ngx_http_script.h
@@ -166,6 +166,7 @@ void ngx_http_script_regex_start_code(ng
 void ngx_http_script_regex_end_code(ngx_http_script_engine_t *e);
 #endif
 void ngx_http_script_return_code(ngx_http_script_engine_t *e);
+void ngx_http_script_break_code(ngx_http_script_engine_t *e);
 void ngx_http_script_if_code(ngx_http_script_engine_t *e);
 void ngx_http_script_complex_value_code(ngx_http_script_engine_t *e);
 void ngx_http_script_value_code(ngx_http_script_engine_t *e);
--- a/src/http/ngx_http_special_response.c
+++ b/src/http/ngx_http_special_response.c
@@ -244,6 +244,9 @@ ngx_http_special_response_handler(ngx_ht
     ngx_http_err_page_t       *err_page;
     ngx_http_core_loc_conf_t  *clcf;
 
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, r->connection->log, 0,
+                   "http special response: %d, \"%V\"", error, &r->uri);
+
     rc = ngx_http_discard_body(r);
 
     if (rc == NGX_HTTP_INTERNAL_SERVER_ERROR) {
--- a/src/http/ngx_http_upstream.c
+++ b/src/http/ngx_http_upstream.c
@@ -39,6 +39,8 @@ static ngx_int_t
     ngx_table_elt_t *h, ngx_uint_t offset);
 static ngx_int_t ngx_http_upstream_ignore_header_line(ngx_http_request_t *r,
     ngx_table_elt_t *h, ngx_uint_t offset);
+static ngx_int_t ngx_http_upstream_process_limit_rate(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
@@ -143,6 +145,10 @@ ngx_http_upstream_header_t  ngx_http_ups
                  offsetof(ngx_http_upstream_headers_in_t, x_accel_redirect),
                  ngx_http_upstream_ignore_header_line, 0, 0 },
 
+    { ngx_string("X-Accel-Limit-Rate"),
+                 ngx_http_upstream_process_limit_rate, 0,
+                 ngx_http_upstream_ignore_header_line, 0, 0 },
+
 #if (NGX_HTTP_GZIP)
     { ngx_string("Content-Encoding"),
                  ngx_http_upstream_process_header_line,
@@ -299,12 +305,19 @@ ngx_http_upstream_check_broken_connectio
     ngx_connection_t     *c;
     ngx_http_upstream_t  *u;
 
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ev->log, 0,
-                   "http upstream check client, write event:%d", ev->write);
+    ngx_log_debug2(NGX_LOG_DEBUG_HTTP, ev->log, 0,
+                   "http upstream check client, write event:%d, \"%V\"",
+                   ev->write, &r->uri);
 
     c = r->connection;
     u = r->upstream;
 
+    if (c->closed) {
+        ngx_http_upstream_finalize_request(r, u,
+                                           NGX_HTTP_CLIENT_CLOSED_REQUEST);
+        return;
+    }
+
     if (u->peer.connection == NULL) {
         return;
     }
@@ -318,6 +331,7 @@ ngx_http_upstream_check_broken_connectio
         }
 
         ev->eof = 1;
+        c->closed = 1;
 
         if (ev->kq_errno) {
             ev->error = 1;
@@ -325,9 +339,8 @@ ngx_http_upstream_check_broken_connectio
 
         if (!u->cachable && u->peer.connection) {
             ngx_log_error(NGX_LOG_INFO, ev->log, ev->kq_errno,
-                          "kevent() reported that client closed "
-                          "prematurely connection, "
-                          "so upstream connection is closed too");
+                          "kevent() reported that client closed prematurely "
+                          "connection, so upstream connection is closed too");
             ngx_http_upstream_finalize_request(r, u,
                                                NGX_HTTP_CLIENT_CLOSED_REQUEST);
             return;
@@ -374,6 +387,7 @@ ngx_http_upstream_check_broken_connectio
     }
 
     ev->eof = 1;
+    c->closed = 1;
 
     if (n == -1) {
         if (err == NGX_EAGAIN) {
@@ -924,6 +938,8 @@ ngx_http_upstream_process_header(ngx_eve
             }
         }
 
+        r->headers_out.status_line.len = 0;
+
         ngx_http_internal_redirect(r,
                               &r->upstream->headers_in.x_accel_redirect->value,
                               NULL);
@@ -1155,9 +1171,33 @@ ngx_http_upstream_process_body(ngx_event
 
     if (ev->timedout) {
         if (ev->write) {
-            p->downstream_error = 1;
-            ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT,
-                          "client timed out");
+            if (ev->delayed) {
+
+                ev->timedout = 0;
+                ev->delayed = 0;
+
+                if (!ev->ready) {
+                    ngx_add_timer(ev, p->send_timeout);
+
+                    if (ngx_handle_write_event(ev, p->send_lowat) == NGX_ERROR)
+                    {
+                        ngx_http_upstream_finalize_request(r, u, 0);
+                        return;
+                    }
+
+                    return;
+                }
+
+                if (ngx_event_pipe(p, ev->write) == NGX_ABORT) {
+                    ngx_http_upstream_finalize_request(r, u, 0);
+                    return;
+                }
+
+            } else {
+                p->downstream_error = 1;
+                ngx_log_error(NGX_LOG_ERR, c->log, NGX_ETIMEDOUT,
+                              "client timed out");
+            }
 
         } else {
             p->upstream_error = 1; 
@@ -1166,6 +1206,17 @@ ngx_http_upstream_process_body(ngx_event
         }
 
     } else {
+        if (ev->write && ev->delayed) {
+            ngx_log_debug0(NGX_LOG_DEBUG_HTTP, c->log, 0,
+                           "http downstream delayed");
+
+            if (ngx_handle_write_event(ev, p->send_lowat) == NGX_ERROR) {
+                return;
+            }
+
+            return;
+        }
+
         if (ngx_event_pipe(p, ev->write) == NGX_ABORT) {
             ngx_http_upstream_finalize_request(r, u, 0);
             return;
@@ -1281,6 +1332,7 @@ ngx_http_upstream_next(ngx_http_request_
     }
 
     if (r->connection->write->eof) {
+        r->connection->closed = 1;
         ngx_http_upstream_finalize_request(r, u,
                                            NGX_HTTP_CLIENT_CLOSED_REQUEST);
         return;
@@ -1432,6 +1484,24 @@ ngx_http_upstream_ignore_header_line(ngx
 
 
 static ngx_int_t
+ngx_http_upstream_process_limit_rate(ngx_http_request_t *r, ngx_table_elt_t *h,
+    ngx_uint_t offset)
+{   
+    ngx_int_t  n;
+
+    r->upstream->headers_in.x_accel_limit_rate = h;
+
+    n = ngx_atoi(h->value.data, h->value.len);
+
+    if (n != NGX_ERROR) {
+        r->limit_rate = (size_t) n;
+    }
+
+    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)
 {
--- a/src/http/ngx_http_upstream.h
+++ b/src/http/ngx_http_upstream.h
@@ -48,6 +48,7 @@ typedef struct {
     ngx_msec_t                      connect_timeout;
     ngx_msec_t                      send_timeout;
     ngx_msec_t                      read_timeout;
+    ngx_msec_t                      timeout;
 
     size_t                          send_lowat;
     size_t                          header_buffer_size;
@@ -103,6 +104,7 @@ typedef struct {
     ngx_table_elt_t                *etag;
     ngx_table_elt_t                *x_accel_expires;
     ngx_table_elt_t                *x_accel_redirect;
+    ngx_table_elt_t                *x_accel_limit_rate;
 
     ngx_table_elt_t                *content_type;
     ngx_table_elt_t                *content_length;
@@ -120,8 +122,6 @@ typedef struct {
 
 
 struct ngx_http_upstream_s {
-    ngx_http_request_t             *request;
-
     ngx_peer_connection_t           peer;
 
     ngx_event_pipe_t                pipe;
@@ -146,6 +146,8 @@ struct ngx_http_upstream_s {
     ngx_int_t                     (*rewrite_redirect)(ngx_http_request_t *r,
                                         ngx_table_elt_t *h, size_t prefix);
 
+    ngx_msec_t                      timeout;
+
     ngx_uint_t                      method;
 
     ngx_http_log_handler_pt         saved_log_handler;
--- a/src/http/ngx_http_variables.c
+++ b/src/http/ngx_http_variables.c
@@ -706,7 +706,9 @@ ngx_http_variables_init_vars(ngx_conf_t 
             {
                 v[i].handler = av[n].handler;
                 v[i].data = av[n].data;
-                v[i].flags = av[n].flags | NGX_HTTP_VAR_INDEXED;
+
+                av[n].flags |= NGX_HTTP_VAR_INDEXED;
+                v[i].flags = av[n].flags;
 
                 goto next;
             }
--- a/src/http/ngx_http_write_filter_module.c
+++ b/src/http/ngx_http_write_filter_module.c
@@ -47,6 +47,12 @@ ngx_http_write_filter(ngx_http_request_t
     ngx_connection_t          *c;
     ngx_http_core_loc_conf_t  *clcf;
 
+    c = r->connection;
+
+    if (c->closed) {
+        return NGX_ERROR;
+    }
+
     size = 0;
     flush = 0;
     last = 0;
@@ -151,8 +157,6 @@ ngx_http_write_filter(ngx_http_request_t
 
     *ll = NULL;
 
-    c = r->connection;
-
     ngx_log_debug3(NGX_LOG_DEBUG_HTTP, c->log, 0,
                    "http write filter: l:%d f:%d s:%O", last, flush, size);
 
@@ -197,19 +201,20 @@ ngx_http_write_filter(ngx_http_request_t
 
     sent = c->sent;
 
-    chain = c->send_chain(c, r->out, clcf->limit_rate);
+    chain = c->send_chain(c, r->out, r->limit_rate);
 
     ngx_log_debug1(NGX_LOG_DEBUG_HTTP, c->log, 0,
                    "http write filter %p", chain);
 
-    if (clcf->limit_rate) {
+    if (r->limit_rate) {
         sent = c->sent - sent;
         c->write->delayed = 1;
         ngx_add_timer(r->connection->write,
-                      (ngx_msec_t) (sent * 1000 / clcf->limit_rate));
+                      (ngx_msec_t) (sent * 1000 / r->limit_rate));
     }
 
     if (chain == NGX_CHAIN_ERROR) {
+        c->closed = 1;
         return NGX_ERROR;
     }
 
--- a/src/imap/ngx_imap.h
+++ b/src/imap/ngx_imap.h
@@ -36,6 +36,12 @@ typedef struct {
 
     ngx_uint_t            protocol;
 
+    ngx_buf_t            *pop3_capability;
+    ngx_buf_t            *imap_capability;
+
+    ngx_array_t           pop3_capabilities;
+    ngx_array_t           imap_capabilities;
+
     /* server ctx */
     ngx_imap_conf_ctx_t  *ctx;
 } ngx_imap_core_srv_conf_t;
@@ -51,9 +57,18 @@ typedef struct {
 
 
 typedef enum {
+    ngx_imap_start = 0,
+    ngx_imap_login,
+    ngx_imap_user,
+    ngx_imap_passwd,
+} ngx_imap_state_e;
+
+
+typedef enum {
     ngx_pop3_start = 0,
-    ngx_pop3_user
-} ngx_imap_state_e;
+    ngx_pop3_user,
+    ngx_pop3_passwd
+} ngx_po3_state_e;
 
 
 typedef struct {
@@ -68,6 +83,7 @@ typedef struct {
     ngx_connection_t       *connection;
 
     ngx_buf_t              *buffer;
+    ngx_str_t               out;
 
     void                  **ctx;
     void                  **main_conf;
@@ -75,39 +91,55 @@ typedef struct {
 
     ngx_imap_proxy_ctx_t   *proxy;
 
-    ngx_imap_state_e        imap_state;
+    ngx_uint_t              imap_state;
 
     unsigned                protocol:1;
+    unsigned                quoted:1;
 
     ngx_str_t               login;
     ngx_str_t               passwd;
 
+    ngx_str_t               tag;
+
     ngx_uint_t              command;
     ngx_array_t             args;
 
+    ngx_uint_t              login_attempt;
+
     /* used to parse IMAP/POP3 command */
 
     ngx_uint_t              state;
+    u_char                 *cmd_start;
     u_char                 *arg_start;
     u_char                 *arg_end;
+    ngx_uint_t              literal_len;
 } ngx_imap_session_t;
 
 
 #define NGX_POP3_USER       1
 #define NGX_POP3_PASS       2
-#define NGX_POP3_APOP       3
-#define NGX_POP3_STAT       4
-#define NGX_POP3_LIST       5
-#define NGX_POP3_RETR       6
-#define NGX_POP3_DELE       7
-#define NGX_POP3_NOOP       8
-#define NGX_POP3_RSET       9
-#define NGX_POP3_TOP        10
-#define NGX_POP3_UIDL       11
-#define NGX_POP3_QUIT       12
+#define NGX_POP3_CAPA       3
+#define NGX_POP3_QUIT       4
+#define NGX_POP3_NOOP       5
+#define NGX_POP3_APOP       6
+#define NGX_POP3_STAT       7
+#define NGX_POP3_LIST       8
+#define NGX_POP3_RETR       9
+#define NGX_POP3_DELE       10
+#define NGX_POP3_RSET       11
+#define NGX_POP3_TOP        12
+#define NGX_POP3_UIDL       13
 
 
-#define NGX_IMAP_PARSE_INVALID_COMMAND  10
+#define NGX_IMAP_LOGIN      1
+#define NGX_IMAP_LOGOUT     2
+#define NGX_IMAP_CAPABILITY 3
+#define NGX_IMAP_NOOP       4
+
+#define NGX_IMAP_NEXT       5
+
+
+#define NGX_IMAP_PARSE_INVALID_COMMAND  20
 
 
 #define NGX_IMAP_PROXY_INVALID  10
@@ -135,9 +167,12 @@ typedef struct {
 
 
 void ngx_imap_init_connection(ngx_connection_t *c);
+void ngx_imap_auth_state(ngx_event_t *rev);
+void ngx_pop3_auth_state(ngx_event_t *rev);
 void ngx_imap_close_connection(ngx_connection_t *c);
 void ngx_imap_session_internal_server_error(ngx_imap_session_t *s);
 
+ngx_int_t ngx_imap_parse_command(ngx_imap_session_t *s);
 ngx_int_t ngx_pop3_parse_command(ngx_imap_session_t *s);
 
 
--- a/src/imap/ngx_imap_auth_http_module.c
+++ b/src/imap/ngx_imap_auth_http_module.c
@@ -12,24 +12,54 @@
 
 
 typedef struct {
-    ngx_peers_t            *peers;
+    ngx_peers_t                    *peers;
 
-    ngx_msec_t              timeout;
+    ngx_msec_t                      timeout;
 
-    ngx_str_t               host_header;
-    ngx_str_t               uri;
+    ngx_str_t                       host_header;
+    ngx_str_t                       uri;
 } ngx_imap_auth_http_conf_t;
 
 
-typedef struct {
-    ngx_buf_t              *request;
-    ngx_buf_t              *response;
-    ngx_peer_connection_t   peer;
-} ngx_imap_auth_http_ctx_t;
+typedef struct ngx_imap_auth_http_ctx_s  ngx_imap_auth_http_ctx_t;
+
+typedef void (*ngx_imap_auth_http_handler_pt)(ngx_imap_session_t *s,
+    ngx_imap_auth_http_ctx_t *ctx);
+
+struct ngx_imap_auth_http_ctx_s {
+    ngx_buf_t                      *request;
+    ngx_buf_t                      *response;
+    ngx_peer_connection_t           peer;
+
+    ngx_imap_auth_http_handler_pt   handler;
+
+    ngx_uint_t                      state;
+    ngx_uint_t                      hash;   /* no needed ? */
+
+    u_char                         *header_name_start;
+    u_char                         *header_name_end;
+    u_char                         *header_start;
+    u_char                         *header_end;
+
+    ngx_str_t                       addr;
+    ngx_str_t                       port;
+    ngx_str_t                       err;
+
+    ngx_msec_t                      sleep;
+
+    ngx_peers_t                    *peers;
+};
 
 
 static void ngx_imap_auth_http_write_handler(ngx_event_t *wev);
 static void ngx_imap_auth_http_read_handler(ngx_event_t *rev);
+static void ngx_imap_auth_http_ignore_status_line(ngx_imap_session_t *s,
+    ngx_imap_auth_http_ctx_t *ctx);
+static void ngx_imap_auth_http_process_headers(ngx_imap_session_t *s,
+    ngx_imap_auth_http_ctx_t *ctx);
+static void ngx_imap_auth_sleep_handler(ngx_event_t *rev);
+static ngx_int_t ngx_imap_auth_http_parse_header_line(ngx_imap_session_t *s,
+    ngx_imap_auth_http_ctx_t *ctx);
 static void ngx_imap_auth_http_block_read(ngx_event_t *rev);
 static void ngx_imap_auth_http_dummy_handler(ngx_event_t *ev);
 static ngx_buf_t *ngx_imap_auth_http_create_request(ngx_imap_session_t *s,
@@ -124,6 +154,8 @@ ngx_imap_auth_http_init(ngx_imap_session
     ctx->peer.connection->read->handler = ngx_imap_auth_http_read_handler;
     ctx->peer.connection->write->handler = ngx_imap_auth_http_write_handler;
 
+    ctx->handler = ngx_imap_auth_http_ignore_status_line;
+
     if (rc == NGX_OK) {
         ngx_imap_auth_http_write_handler(ctx->peer.connection->write);
         return;
@@ -194,7 +226,6 @@ static void
 ngx_imap_auth_http_read_handler(ngx_event_t *rev)
 {
     ssize_t                     n, size;
-    ngx_peers_t                *peers;
     ngx_connection_t          *c;
     ngx_imap_session_t        *s;
     ngx_imap_auth_http_ctx_t  *ctx;
@@ -224,24 +255,604 @@ ngx_imap_auth_http_read_handler(ngx_even
         }
     }
 
-    size = ctx->response->last - ctx->response->pos;
+    size = ctx->response->end - ctx->response->last;
 
     n = ngx_recv(c, ctx->response->pos, size);
 
-    if (n == NGX_ERROR || n == 0) {
-        ngx_close_connection(ctx->peer.connection);
-        ngx_imap_session_internal_server_error(s);
+    if (n > 0) {
+        ctx->response->last += n;
+
+        ctx->handler(s, ctx);
+        return;
+    }
+
+    if (n == NGX_AGAIN) {
         return;
     }
 
+    ngx_close_connection(ctx->peer.connection);
+    ngx_imap_session_internal_server_error(s);
+}
 
 
+static void
+ngx_imap_auth_http_ignore_status_line(ngx_imap_session_t *s,
+    ngx_imap_auth_http_ctx_t *ctx)
+{
+    u_char  *p, ch;
+    enum  {
+        sw_start = 0,
+        sw_H,
+        sw_HT,
+        sw_HTT,
+        sw_HTTP,
+        sw_skip,
+        sw_almost_done
+    } state;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_IMAP, s->connection->log, 0,
+                   "imap auth http process status line");
+
+    state = ctx->state;
+
+    for (p = ctx->response->pos; p < ctx->response->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* "HTTP/" */
+        case sw_start:
+            if (ch == 'H') {
+                state = sw_H;
+                break;
+            }
+            goto next;
+
+        case sw_H:
+            if (ch == 'T') {
+                state = sw_HT;
+                break;
+            }
+            goto next;
+
+        case sw_HT:
+            if (ch == 'T') {
+                state = sw_HTT;
+                break;
+            }
+            goto next;
+
+        case sw_HTT:
+            if (ch == 'P') {
+                state = sw_HTTP;
+                break;
+            }
+            goto next;
+
+        case sw_HTTP:
+            if (ch == '/') {
+                state = sw_skip;
+                break;
+            }
+            goto next;
+
+        /* any text until end of line */
+        case sw_skip:
+            switch (ch) {
+            case CR:
+                state = sw_almost_done;
+
+                break;
+            case LF: 
+                goto done;
+            }
+            break;
+
+        /* end of status line */
+        case sw_almost_done:
+            if (ch == LF) {
+                goto done;
+            }
+
+            ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                          "auth http server sent invalid response");
+            ngx_close_connection(ctx->peer.connection);
+            ngx_imap_session_internal_server_error(s);
+            return;
+        }
+    }
+
+    ctx->response->pos = p;
+    ctx->state = state;
+
+    return;
+
+next:
+
+    p = ctx->response->start - 1;
+
+done:
+
+    ctx->response->pos = p + 1;
+    ctx->state = 0;
+    ctx->handler = ngx_imap_auth_http_process_headers;
+    ctx->handler(s, ctx);
+}
 
 
+static void
+ngx_imap_auth_http_process_headers(ngx_imap_session_t *s,
+    ngx_imap_auth_http_ctx_t *ctx)
+{
+    u_char              *p;
+    size_t               len, size;
+    ngx_int_t            rc, port, n;
+    struct sockaddr_in  *sin;
 
-    peers = NULL;
+    ngx_log_debug0(NGX_LOG_DEBUG_IMAP, s->connection->log, 0,
+                   "imap auth http process headers");
+
+    for ( ;; ) {
+        rc = ngx_imap_auth_http_parse_header_line(s, ctx);
+
+        if (rc == NGX_OK) {
+
+#if (NGX_DEBUG)
+            {
+            ngx_str_t  key, value;
+
+            key.len = ctx->header_name_end - ctx->header_name_start;
+            key.data = ctx->header_name_start;
+            value.len = ctx->header_end - ctx->header_start;
+            value.data = ctx->header_start;
+
+            ngx_log_debug2(NGX_LOG_DEBUG_IMAP, s->connection->log, 0,
+                           "auth http header: \"%V: %V\"",
+                           &key, &value);
+            }
+#endif
+
+            len = ctx->header_name_end - ctx->header_name_start;
+
+            if (len == sizeof("Auth-Status") - 1
+                && ngx_strncasecmp(ctx->header_name_start, "Auth-Status",
+                                   sizeof("Auth-Status") - 1) == 0)
+            {
+                len = ctx->header_end - ctx->header_start;
+
+                if (len == 2
+                    && ctx->header_start[0] == 'O'
+                    && ctx->header_start[1] == 'K')
+                {
+                    continue;
+                }
+
+                if (s->protocol == NGX_IMAP_POP3_PROTOCOL) {
+                    size = sizeof("-ERR") - 1 + len + sizeof(CRLF) - 1;
+
+                } else {
+                    size = s->tag.len + sizeof("NO") - 1 + len
+                           + sizeof(CRLF) - 1;
+                }
+
+                p = ngx_pcalloc(s->connection->pool, size);
+                if (p == NULL) {
+                    ngx_imap_session_internal_server_error(s);
+                    return;
+                }
+
+                ctx->err.data = p;
+
+                if (s->protocol == NGX_IMAP_POP3_PROTOCOL) {
+                    *p++ = '-'; *p++ = 'E'; *p++ = 'R'; *p++ = 'R';
+
+                } else {
+                    p = ngx_cpymem(p, s->tag.data, s->tag.len);
+                    *p++ = 'N'; *p++ = 'O';
+                }
+
+                *p++ = ' ';
+                p = ngx_cpymem(p, ctx->header_start, len);
+                *p++ = CR; *p++ = LF;
+
+                ctx->err.len = p - ctx->err.data;
+
+                continue;
+            }
+
+            if (len == sizeof("Auth-Server") - 1
+                && ngx_strncasecmp(ctx->header_name_start, "Auth-Server",
+                                   sizeof("Auth-Server") - 1) == 0)
+            {
+                ctx->addr.len = ctx->header_end - ctx->header_start;
+                ctx->addr.data = ctx->header_start;
+
+                continue;
+            }
+
+            if (len == sizeof("Auth-Port") - 1
+                && ngx_strncasecmp(ctx->header_name_start, "Auth-Port",
+                                   sizeof("Auth-Port") - 1) == 0)
+            {
+                ctx->port.len = ctx->header_end - ctx->header_start;
+                ctx->port.data = ctx->header_start;
+
+                continue;
+            }
+
+            if (len == sizeof("Auth-User") - 1
+                && ngx_strncasecmp(ctx->header_name_start, "Auth-User",
+                                   sizeof("Auth-User") - 1) == 0)
+            {
+                s->login.len = ctx->header_end - ctx->header_start;
+                s->login.data = ctx->header_start;
+
+                continue;
+            }
+
+            if (len == sizeof("Auth-Wait") - 1
+                && ngx_strncasecmp(ctx->header_name_start, "Auth-Wait",
+                                   sizeof("Auth-Wait") - 1) == 0)
+            {
+                n = ngx_atoi(ctx->header_start,
+                             ctx->header_end - ctx->header_start);
+
+                if (n != NGX_ERROR) {
+                    ctx->sleep = n;
+                }
+
+                continue;
+            }
+
+            /* ignore other headers */
+
+            continue;
+        }
+
+        if (rc == NGX_DONE) {
+            ngx_log_debug0(NGX_LOG_DEBUG_IMAP, s->connection->log, 0,
+                           "auth http header done");
+
+            ngx_close_connection(ctx->peer.connection);
+
+            if (ctx->err.len) {
+                (void) ngx_send(s->connection, ctx->err.data, ctx->err.len);
+
+                if (ctx->sleep == 0) {
+                    ngx_imap_close_connection(s->connection);
+                    return;
+                }
+
+                ngx_add_timer(s->connection->read, ctx->sleep * 1000);
+
+                s->connection->read->handler = ngx_imap_auth_sleep_handler;
+
+                return;
+            }
+
+            if (ctx->addr.len == 0 || ctx->port.len == 0) {
+                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                              "auth http server did not send server or port");
+                ngx_imap_session_internal_server_error(s);
+                return;
+            }
+
+            ctx->peers = ngx_pcalloc(s->connection->pool, sizeof(ngx_peers_t));
+            if (ctx->peers == NULL) {
+                ngx_imap_session_internal_server_error(s);
+                return;
+            }
+
+            sin = ngx_pcalloc(s->connection->pool, sizeof(struct sockaddr_in));
+            if (sin == NULL) {
+                ngx_imap_session_internal_server_error(s);
+                return;
+            }
+
+            sin->sin_family = AF_INET;
+
+            port = ngx_atoi(ctx->port.data, ctx->port.len);
+            if (port == NGX_ERROR || port < 1 || port > 65536) {
+                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                              "auth http server sent invalid server "
+                              "port:\"%V\"", &ctx->port);
+                ngx_imap_session_internal_server_error(s);
+                return;
+            }
+
+            sin->sin_port = htons((in_port_t) port);
+
+            ctx->addr.data[ctx->addr.len] = '\0';
+            sin->sin_addr.s_addr = inet_addr((char *) ctx->addr.data);
+            if (sin->sin_addr.s_addr == INADDR_NONE) {
+                ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                              "auth http server sent invalid server "
+                              "address:\"%V\"", &ctx->addr);
+                ngx_imap_session_internal_server_error(s);
+                return;
+            }
+
+            ctx->peers->number = 1;
+
+            ctx->peers->peer[0].sockaddr = (struct sockaddr *) sin;
+            ctx->peers->peer[0].socklen = sizeof(struct sockaddr_in);
+
+            len = ctx->addr.len + 1 + ctx->port.len;
+
+            ctx->peers->peer[0].name.len = len;
+
+            ctx->peers->peer[0].name.data = ngx_palloc(s->connection->pool,
+                                                       len);
+            if (ctx->peers->peer[0].name.data == NULL) {
+                ngx_imap_session_internal_server_error(s);
+                return;
+            }
+
+            len = ctx->addr.len;
+
+            ngx_memcpy(ctx->peers->peer[0].name.data, ctx->addr.data, len);
+
+            ctx->peers->peer[0].name.data[len++] = ':';
+
+            ngx_memcpy(ctx->peers->peer[0].name.data + len,
+                       ctx->port.data, ctx->port.len);
+
+            ctx->peers->peer[0].uri_separator = "";
+
+            ngx_imap_proxy_init(s, ctx->peers);
+
+            return;
+        }
+
+        if (rc == NGX_AGAIN ) {
+            return;
+        }
+
+        /* rc == NGX_ERROR */
+
+        ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
+                      "auth http server sent invalid header in response");
+        ngx_close_connection(ctx->peer.connection);
+        ngx_imap_session_internal_server_error(s);
+
+        return;
+    }
+}
+
 
-    ngx_imap_proxy_init(s, peers);
+static void
+ngx_imap_auth_sleep_handler(ngx_event_t *rev)
+{
+    ngx_connection_t    *c;
+    ngx_imap_session_t  *s;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap auth sleep handler");
+
+    c = rev->data;
+    s = c->data;
+
+    if (rev->timedout) {
+
+        rev->timedout = 0;
+
+        if (s->protocol == NGX_IMAP_POP3_PROTOCOL) {
+            s->imap_state = ngx_pop3_start;
+            s->connection->read->handler = ngx_pop3_auth_state;
+
+        } else {
+            s->imap_state = ngx_imap_start;
+            s->connection->read->handler = ngx_imap_auth_state;
+        }
+
+        if (rev->ready) {
+            s->connection->read->handler(rev);
+            return;
+        }
+
+        if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
+            ngx_imap_close_connection(s->connection);
+        }
+
+        return;
+    }
+
+    if (rev->active) {
+        if (ngx_handle_read_event(rev, 0) == NGX_ERROR) {
+            ngx_imap_close_connection(s->connection);
+        }
+    }
+}
+
+
+static ngx_int_t
+ngx_imap_auth_http_parse_header_line(ngx_imap_session_t *s,
+    ngx_imap_auth_http_ctx_t *ctx)
+{
+    u_char      c, ch, *p;
+    ngx_uint_t  hash;
+    enum {
+        sw_start = 0,
+        sw_name,
+        sw_space_before_value,
+        sw_value,
+        sw_space_after_value,
+        sw_almost_done, 
+        sw_header_almost_done
+    } state;
+
+    state = ctx->state; 
+    hash = ctx->hash;
+
+    for (p = ctx->response->pos; p < ctx->response->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* first char */
+        case sw_start:
+
+            switch (ch) {
+            case CR:
+                ctx->header_end = p; 
+                state = sw_header_almost_done;
+                break;
+            case LF: 
+                ctx->header_end = p;
+                goto header_done;
+            default:
+                state = sw_name;
+                ctx->header_name_start = p;
+
+                c = (u_char) (ch | 0x20);
+                if (c >= 'a' && c <= 'z') {
+                    hash = c;
+                    break;
+                }
+
+                if (ch >= '0' && ch <= '9') {
+                    hash = ch;
+                    break;
+                }
+
+                return NGX_ERROR;
+            }
+            break;
+
+        /* header name */
+        case sw_name:
+            c = (u_char) (ch | 0x20);
+            if (c >= 'a' && c <= 'z') {
+                hash += c;
+                break;
+            }
+
+            if (ch == ':') {
+                ctx->header_name_end = p;
+                state = sw_space_before_value;
+                break;
+            }
+
+            if (ch == '-') {
+                hash += ch;
+                break;
+            }
+
+            if (ch >= '0' && ch <= '9') {
+                hash += ch;
+                break;
+            }
+
+            if (ch == CR) {
+                ctx->header_name_end = p;
+                ctx->header_start = p;
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            }
+
+            if (ch == LF) {
+                ctx->header_name_end = p;
+                ctx->header_start = p;
+                ctx->header_end = p;
+                goto done;
+            }
+
+            return NGX_ERROR;
+
+        /* space* before header value */
+        case sw_space_before_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                ctx->header_start = p;
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                ctx->header_start = p;
+                ctx->header_end = p;
+                goto done;
+            default:
+                ctx->header_start = p;
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* header value */
+        case sw_value:
+            switch (ch) {
+            case ' ':
+                ctx->header_end = p;
+                state = sw_space_after_value;
+                break;
+            case CR:
+                ctx->header_end = p;
+                state = sw_almost_done;
+                break;
+            case LF:
+                ctx->header_end = p;
+                goto done;
+            }
+            break;
+
+        /* space* before end of header line */
+        case sw_space_after_value:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                state = sw_value;
+                break;
+            }
+            break;
+
+        /* end of header line */
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                return NGX_ERROR;
+            }
+
+        /* end of header */
+        case sw_header_almost_done:
+            switch (ch) {
+            case LF:
+                goto header_done;
+            default:
+                return NGX_ERROR;
+            }
+        }
+    }
+
+    ctx->response->pos = p;
+    ctx->state = state;
+    ctx->hash = hash;
+
+    return NGX_AGAIN;
+
+done:
+
+    ctx->response->pos = p + 1;
+    ctx->state = sw_start;
+    ctx->hash = hash;
+
+    return NGX_OK;
+
+header_done:
+
+    ctx->response->pos = p + 1;
+    ctx->state = sw_start;
+
+    return NGX_DONE;
 }
 
 
@@ -288,6 +899,8 @@ ngx_imap_auth_http_create_request(ngx_im
           + sizeof("Auth-User: ") - 1 + s->login.len + sizeof(CRLF) - 1
           + sizeof("Auth-Pass: ") - 1 + s->passwd.len + sizeof(CRLF) - 1
           + sizeof("Auth-Protocol: imap" CRLF) - 1
+          + sizeof("Auth-Login-Attempt: ") - 1 + NGX_INT_T_LEN
+                + sizeof(CRLF) - 1
           + sizeof("Client-IP: ") - 1 + s->connection->addr_text.len
                 + sizeof(CRLF) - 1
           + sizeof(CRLF) - 1;
@@ -324,6 +937,9 @@ ngx_imap_auth_http_create_request(ngx_im
                          sizeof("imap") - 1);
     *b->last++ = CR; *b->last++ = LF;
 
+    b->last = ngx_sprintf(b->last, "Auth-Login-Attempt: %ui" CRLF,
+                          s->login_attempt);
+
     b->last = ngx_cpymem(b->last, "Client-IP: ", sizeof("Client-IP: ") - 1);
     b->last = ngx_cpymem(b->last, s->connection->addr_text.data,
                          s->connection->addr_text.len);
@@ -338,7 +954,7 @@ ngx_imap_auth_http_create_request(ngx_im
 
     l.len = b->last - b->pos;
     l.data = b->pos;
-    ngx_log_debug1(NGX_LOG_DEBUG_HTTP, s->connection->log, 0,
+    ngx_log_debug1(NGX_LOG_DEBUG_IMAP, s->connection->log, 0,
                    "imap auth http header:\n\"%V\"", &l);
     }
 #endif
@@ -440,7 +1056,7 @@ ngx_imap_auth_http(ngx_conf_t *cf, ngx_c
         }
 
         for (i = 0; i < ahcf->peers->number; i++) {
-            ahcf->peers->peer[i].uri_separator = ":";
+            ahcf->peers->peer[i].uri_separator = "";
         }
 
         ahcf->host_header = inet_upstream.host_header;
--- a/src/imap/ngx_imap_core_module.c
+++ b/src/imap/ngx_imap_core_module.c
@@ -18,6 +18,8 @@ static char *ngx_imap_core_server(ngx_co
     void *conf);
 static char *ngx_imap_core_listen(ngx_conf_t *cf, ngx_command_t *cmd,
     void *conf);
+static char *ngx_imap_core_capability(ngx_conf_t *cf, ngx_command_t *cmd,
+    void *conf);
 
 
 static ngx_conf_enum_t  ngx_imap_core_procotol[] = {
@@ -27,6 +29,22 @@ static ngx_conf_enum_t  ngx_imap_core_pr
 };
 
 
+static ngx_str_t  ngx_pop3_default_capabilities[] = {
+    ngx_string("TOP"),
+    ngx_string("USER"),
+    ngx_string("UIDL"),
+    ngx_null_string
+};
+
+
+static ngx_str_t  ngx_imap_default_capabilities[] = {
+    ngx_string("IMAP4"),
+    ngx_string("IMAP4rev1"),
+    ngx_string("UIDPLUS"),
+    ngx_null_string
+};
+
+
 static ngx_command_t  ngx_imap_core_commands[] = {
 
     { ngx_string("server"),
@@ -71,6 +89,20 @@ static ngx_command_t  ngx_imap_core_comm
       offsetof(ngx_imap_core_srv_conf_t, timeout),
       NULL },
 
+    { ngx_string("pop3_capabilities"),
+      NGX_IMAP_MAIN_CONF|NGX_IMAP_SRV_CONF|NGX_CONF_1MORE,
+      ngx_imap_core_capability,
+      NGX_IMAP_SRV_CONF_OFFSET,
+      offsetof(ngx_imap_core_srv_conf_t, pop3_capabilities),
+      NULL },
+
+    { ngx_string("imap_capabilities"),
+      NGX_IMAP_MAIN_CONF|NGX_IMAP_SRV_CONF|NGX_CONF_1MORE,
+      ngx_imap_core_capability,
+      NGX_IMAP_SRV_CONF_OFFSET,
+      offsetof(ngx_imap_core_srv_conf_t, imap_capabilities),
+      NULL },
+
       ngx_null_command
 };
 
@@ -121,7 +153,7 @@ ngx_imap_core_create_srv_conf(ngx_conf_t
             
     cscf = ngx_pcalloc(cf->pool, sizeof(ngx_imap_core_srv_conf_t));
     if (cscf == NULL) {
-        return NGX_CONF_ERROR;
+        return NULL;
     }
 
     cscf->imap_client_buffer_size = NGX_CONF_UNSET_SIZE;
@@ -129,6 +161,18 @@ ngx_imap_core_create_srv_conf(ngx_conf_t
     cscf->timeout = NGX_CONF_UNSET_MSEC;
     cscf->protocol = NGX_CONF_UNSET_UINT;
 
+    if (ngx_array_init(&cscf->pop3_capabilities, cf->pool, 4, sizeof(ngx_str_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
+    if (ngx_array_init(&cscf->imap_capabilities, cf->pool, 4, sizeof(ngx_str_t))
+        != NGX_OK)
+    {
+        return NULL;
+    }
+
     return cscf;
 }
 
@@ -139,6 +183,11 @@ ngx_imap_core_merge_srv_conf(ngx_conf_t 
     ngx_imap_core_srv_conf_t *prev = parent;
     ngx_imap_core_srv_conf_t *conf = child;
 
+    size_t       size;
+    ngx_buf_t   *b;
+    ngx_str_t   *c, *d;
+    ngx_uint_t   i;
+
     ngx_conf_merge_size_value(conf->imap_client_buffer_size,
                               prev->imap_client_buffer_size,
                               (size_t) ngx_pagesize);
@@ -148,6 +197,88 @@ ngx_imap_core_merge_srv_conf(ngx_conf_t 
     ngx_conf_merge_unsigned_value(conf->protocol, prev->protocol,
                               NGX_IMAP_IMAP_PROTOCOL);
 
+
+    if (conf->pop3_capabilities.nelts == 0) {
+        conf->pop3_capabilities = prev->pop3_capabilities;
+    }
+
+    if (conf->pop3_capabilities.nelts == 0) {
+
+        for (d = ngx_pop3_default_capabilities; d->len; d++) {
+            c = ngx_array_push(&conf->pop3_capabilities);
+            if (c == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            *c = *d;
+        }
+    }
+
+    size = sizeof("+OK Capability list follows" CRLF) - 1
+           + sizeof("." CRLF) - 1;
+
+    c = conf->pop3_capabilities.elts;
+    for (i = 0; i < conf->pop3_capabilities.nelts; i++) {
+        size += c[i].len + sizeof(CRLF) - 1;
+    }
+
+    b = ngx_create_temp_buf(cf->pool, size);
+    if (b == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    b->last = ngx_cpymem(b->last, "+OK Capability list follows" CRLF,
+                         sizeof("+OK Capability list follows" CRLF) - 1);
+
+    for (i = 0; i < conf->pop3_capabilities.nelts; i++) {
+        b->last = ngx_cpymem(b->last, c[i].data, c[i].len);
+        *b->last++ = CR; *b->last++ = LF;
+    }
+
+    *b->last++ = '.'; *b->last++ = CR; *b->last++ = LF;
+
+    conf->pop3_capability = b;
+
+
+    if (conf->imap_capabilities.nelts == 0) {
+        conf->imap_capabilities = prev->imap_capabilities;
+    }
+
+    if (conf->imap_capabilities.nelts == 0) {
+
+        for (d = ngx_imap_default_capabilities; d->len; d++) {
+            c = ngx_array_push(&conf->imap_capabilities);
+            if (c == NULL) {
+                return NGX_CONF_ERROR;
+            }
+
+            *c = *d;
+        }
+    }
+
+    size = sizeof("* CAPABILITY") - 1 + sizeof(CRLF) - 1;
+
+    c = conf->imap_capabilities.elts;
+    for (i = 0; i < conf->imap_capabilities.nelts; i++) {
+        size += 1 + c[i].len;
+    }
+
+    b = ngx_create_temp_buf(cf->pool, size);
+    if (b == NULL) {
+        return NGX_CONF_ERROR;
+    }
+
+    b->last = ngx_cpymem(b->last, "* CAPABILITY", sizeof("* CAPABILITY") - 1);
+
+    for (i = 0; i < conf->imap_capabilities.nelts; i++) {
+        *b->last++ = ' ';
+        b->last = ngx_cpymem(b->last, c[i].data, c[i].len);
+    }
+
+    *b->last++ = CR; *b->last++ = LF;
+
+    conf->imap_capability = b;
+
     return NGX_CONF_OK;
 }
 
@@ -296,3 +427,29 @@ ngx_imap_core_listen(ngx_conf_t *cf, ngx
 
     return NGX_CONF_OK;
 }
+
+
+static char *
+ngx_imap_core_capability(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
+{
+    char  *p = conf;
+
+    ngx_str_t    *c, *value;
+    ngx_uint_t    i;
+    ngx_array_t  *a;
+
+    a = (ngx_array_t *) (p + cmd->offset);
+
+    value = cf->args->elts;
+
+    for (i = 1; i < cf->args->nelts; i++) {
+        c = ngx_array_push(a);
+        if (c == NULL) {
+            return NGX_CONF_ERROR;
+        }
+
+        *c = value[i];
+    }
+
+    return NGX_CONF_OK;
+}
--- a/src/imap/ngx_imap_handler.c
+++ b/src/imap/ngx_imap_handler.c
@@ -8,20 +8,15 @@
 #include <ngx_core.h>
 #include <ngx_event.h>
 #include <ngx_imap.h>
-#include <nginx.h>
 
 
 static void ngx_imap_init_session(ngx_event_t *rev);
-
-static void ngx_pop3_auth_state(ngx_event_t *rev);
-static ngx_int_t ngx_pop3_read_command(ngx_imap_session_t *s);
-
-static void ngx_imap_auth_state(ngx_event_t *rev);
+static ngx_int_t ngx_imap_read_command(ngx_imap_session_t *s);
 
 
 static ngx_str_t  greetings[] = {
-   ngx_string("+OK " NGINX_VER " ready" CRLF),
-   ngx_string("* OK " NGINX_VER " ready" CRLF)
+   ngx_string("+OK POP3 ready" CRLF),
+   ngx_string("* OK IMAP ready" CRLF)
 };
 
 static ngx_str_t  internal_server_errors[] = {
@@ -32,6 +27,11 @@ static ngx_str_t  internal_server_errors
 static u_char  pop3_ok[] = "+OK" CRLF;
 static u_char  pop3_invalid_command[] = "-ERR invalid command" CRLF;
 
+static u_char  imap_ok[] = "OK" CRLF;
+static u_char  imap_next[] = "+ OK" CRLF;
+static u_char  imap_bye[] = "* BYE" CRLF;
+static u_char  imap_invalid_command[] = "BAD invalid command" CRLF;
+
 
 void
 ngx_imap_init_connection(ngx_connection_t *c)
@@ -87,7 +87,7 @@ ngx_imap_init_session(ngx_event_t *rev)
 
     s = ngx_pcalloc(c->pool, sizeof(ngx_imap_session_t));
     if (s == NULL) {
-        ngx_imap_close_connection(c);
+        ngx_imap_session_internal_server_error(s);
         return;
     }
 
@@ -96,7 +96,7 @@ ngx_imap_init_session(ngx_event_t *rev)
 
     s->ctx = ngx_pcalloc(c->pool, sizeof(void *) * ngx_imap_max_module);
     if (s->ctx == NULL) {
-        ngx_imap_close_connection(c);
+        ngx_imap_session_internal_server_error(s);
         return;
     }
 
@@ -105,7 +105,7 @@ ngx_imap_init_session(ngx_event_t *rev)
     s->srv_conf = ctx->srv_conf;
 
     if (ngx_array_init(&s->args, c->pool, 2, sizeof(ngx_str_t)) == NGX_ERROR) {
-        ngx_imap_close_connection(c);
+        ngx_imap_session_internal_server_error(s);
         return;
     }
 
@@ -115,16 +115,18 @@ ngx_imap_init_session(ngx_event_t *rev)
 
     if (cscf->protocol == NGX_IMAP_POP3_PROTOCOL) {
         size = 128;
+        s->imap_state = ngx_pop3_start;
         c->read->handler = ngx_pop3_auth_state;
 
     } else {
         size = cscf->imap_client_buffer_size;
+        s->imap_state = ngx_imap_start;
         c->read->handler = ngx_imap_auth_state;
     }
 
     s->buffer = ngx_create_temp_buf(c->pool, size);
     if (s->buffer == NULL) {
-        ngx_imap_close_connection(c);
+        ngx_imap_session_internal_server_error(s);
         return;
     }
 
@@ -132,27 +134,194 @@ ngx_imap_init_session(ngx_event_t *rev)
 }
 
 
-static void
+void
 ngx_imap_auth_state(ngx_event_t *rev)
 {
-    ngx_connection_t  *c;
+    u_char                    *text, *last, *out, *p;
+    ssize_t                    size, text_len, last_len;
+    ngx_str_t                 *arg;
+    ngx_int_t                  rc;
+    ngx_uint_t                 quit, tag;
+    ngx_connection_t          *c;
+    ngx_imap_session_t        *s;
+    ngx_imap_core_srv_conf_t  *cscf;
 
     c = rev->data;
+    s = c->data;
 
-    ngx_imap_close_connection(c);
+    ngx_log_debug0(NGX_LOG_DEBUG_IMAP, c->log, 0, "imap auth state");
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
+        ngx_imap_close_connection(c);
+        return;
+    }
+
+    rc = ngx_imap_read_command(s);
+
+    ngx_log_debug1(NGX_LOG_DEBUG_IMAP, c->log, 0, "imap auth: %i", rc);
+
+    if (rc == NGX_AGAIN || rc == NGX_ERROR) {
+        return;
+    }
+
+    quit = 0;
+    tag = 1;
+
+    text = NULL;
+    text_len = 0;
+
+    last = imap_ok;
+    last_len = sizeof(imap_ok) - 1;
+
+    if (rc == NGX_OK) {
+
+        ngx_log_debug1(NGX_LOG_DEBUG_IMAP, c->log, 0, "imap auth command: %i",
+                       s->command);
+
+        switch (s->command) {
+
+        case NGX_IMAP_LOGIN:
+            if (s->args.nelts == 2) {
+
+                arg = s->args.elts;
+
+                s->login.len = arg[0].len;
+                s->login.data = ngx_palloc(c->pool, s->login.len);
+                if (s->login.data == NULL) {
+                    ngx_imap_session_internal_server_error(s);
+                    return;
+                }
+
+                ngx_memcpy(s->login.data, arg[0].data, s->login.len);
+
+                s->passwd.len = arg[1].len;
+                s->passwd.data = ngx_palloc(c->pool, s->passwd.len);
+                if (s->passwd.data == NULL) {
+                    ngx_imap_session_internal_server_error(s);
+                    return;
+                }
+
+                ngx_memcpy(s->passwd.data, arg[1].data, s->passwd.len);
+
+                ngx_log_debug2(NGX_LOG_DEBUG_IMAP, c->log, 0,
+                               "imap login:\"%V\" passwd:\"%V\"",
+                               &s->login, &s->passwd);
+
+                s->args.nelts = 0;
+                s->buffer->pos = s->buffer->start;
+                s->buffer->last = s->buffer->start;
+
+                if (rev->timer_set) {
+                    ngx_del_timer(rev);
+                }
+
+                s->login_attempt++;
+
+                ngx_imap_auth_http_init(s);
+
+                return;
+
+            } else {
+                rc = NGX_IMAP_PARSE_INVALID_COMMAND;
+            }
+
+            break;
+
+        case NGX_IMAP_CAPABILITY:
+            cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module);
+            text = cscf->imap_capability->pos;
+            text_len = cscf->imap_capability->last - cscf->imap_capability->pos;
+            break;
+
+        case NGX_IMAP_LOGOUT:
+            text = imap_bye;
+            text_len = sizeof(imap_bye) - 1;
+            quit = 1;
+            break;
+
+        case NGX_IMAP_NOOP:
+            break;
+
+        default:
+            rc = NGX_IMAP_PARSE_INVALID_COMMAND;
+            break;
+        }
+
+    } else if (rc == NGX_IMAP_NEXT) {
+        last = imap_next;
+        last_len = sizeof(imap_next) - 1;
+        tag = 0;
+    }
+
+    if (rc == NGX_IMAP_PARSE_INVALID_COMMAND) {
+        last = imap_invalid_command;
+        last_len = sizeof(imap_invalid_command) - 1;
+    }
+
+    if (tag) {
+        if (s->out.len < text_len + s->tag.len + last_len) {
+
+            s->out.len = text_len + s->tag.len + last_len;
+            s->out.data = ngx_palloc(c->pool, s->out.len);
+            if (s->out.data == NULL) {
+                ngx_imap_close_connection(c);
+                return;
+            }
+        }
+
+        out = s->out.data;
+        p = out;
+
+        if (text) {
+            p = ngx_cpymem(p, text, text_len);
+        }
+        p = ngx_cpymem(p, s->tag.data, s->tag.len);
+        ngx_memcpy(p, last, last_len);
+
+        size = text_len + s->tag.len + last_len;
+
+    } else {
+        out = last;
+        size = last_len;
+    }
+
+    if (ngx_send(c, out, size) < size) {
+        /*
+         * we treat the incomplete sending as NGX_ERROR
+         * because it is very strange here
+         */
+        ngx_imap_close_connection(c);
+        return;
+    }
+
+    if (rc == NGX_IMAP_NEXT) {
+        return;
+    }
+
+    if (quit) {
+        ngx_imap_close_connection(c);
+        return;
+    }
+
+    s->args.nelts = 0;
+    s->buffer->pos = s->buffer->start;
+    s->buffer->last = s->buffer->start;
+    s->tag.len = 0;
 }
 
 
-static void
+void
 ngx_pop3_auth_state(ngx_event_t *rev)
 {
-    u_char              *text;
-    ssize_t              size;
-    ngx_int_t            rc;
-    ngx_uint_t           quit;
-    ngx_str_t           *arg;
-    ngx_connection_t    *c;
-    ngx_imap_session_t  *s;
+    u_char                    *text;
+    ssize_t                    size;
+    ngx_int_t                  rc;
+    ngx_uint_t                 quit;
+    ngx_str_t                 *arg;
+    ngx_connection_t          *c;
+    ngx_imap_session_t        *s;
+    ngx_imap_core_srv_conf_t  *cscf;
 
     c = rev->data;
     s = c->data;
@@ -165,7 +334,7 @@ ngx_pop3_auth_state(ngx_event_t *rev)
         return;
     }
 
-    rc = ngx_pop3_read_command(s);
+    rc = ngx_imap_read_command(s);
 
     if (rc == NGX_AGAIN || rc == NGX_ERROR) {
         return;
@@ -188,16 +357,16 @@ ngx_pop3_auth_state(ngx_event_t *rev)
 
                     arg = s->args.elts;
                     s->login.len = arg[0].len;
-                    s->login.data = ngx_palloc(c->pool, s->login.len + 1);
+                    s->login.data = ngx_palloc(c->pool, s->login.len);
                     if (s->login.data == NULL) {
-                        ngx_imap_close_connection(c);
+                        ngx_imap_session_internal_server_error(s);
                         return;
                     }
 
-                    ngx_cpystrn(s->login.data, arg[0].data, s->login.len + 1);
+                    ngx_memcpy(s->login.data, arg[0].data, s->login.len);
 
                     ngx_log_debug1(NGX_LOG_DEBUG_IMAP, c->log, 0,
-                                   "pop3 login: \"%s\"", s->login.data);
+                                   "pop3 login: \"%V\"", &s->login);
 
                 } else {
                     rc = NGX_IMAP_PARSE_INVALID_COMMAND;
@@ -205,10 +374,19 @@ ngx_pop3_auth_state(ngx_event_t *rev)
 
                 break;
 
+            case NGX_POP3_CAPA:
+                cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module);
+                text = cscf->pop3_capability->pos;
+                size = cscf->pop3_capability->last - cscf->pop3_capability->pos;
+                break;
+
             case NGX_POP3_QUIT:
                 quit = 1;
                 break;
 
+            case NGX_POP3_NOOP:
+                break;
+
             default:
                 s->imap_state = ngx_pop3_start;
                 rc = NGX_IMAP_PARSE_INVALID_COMMAND;
@@ -227,20 +405,25 @@ ngx_pop3_auth_state(ngx_event_t *rev)
 
                     arg = s->args.elts;
                     s->passwd.len = arg[0].len;
-                    s->passwd.data = ngx_palloc(c->pool, s->passwd.len + 1);
+                    s->passwd.data = ngx_palloc(c->pool, s->passwd.len);
                     if (s->passwd.data == NULL) {
-                        ngx_imap_close_connection(c);
+                        ngx_imap_session_internal_server_error(s);
                         return;
                     }
 
-                    ngx_cpystrn(s->passwd.data, arg[0].data, s->passwd.len + 1);
+                    ngx_memcpy(s->passwd.data, arg[0].data, s->passwd.len);
 
                     ngx_log_debug1(NGX_LOG_DEBUG_IMAP, c->log, 0,
-                                   "pop3 passwd: \"%s\"", s->passwd.data);
+                                   "pop3 passwd: \"%V\"", &s->passwd);
 
+                    s->args.nelts = 0;
                     s->buffer->pos = s->buffer->start;
                     s->buffer->last = s->buffer->start;
 
+                    if (rev->timer_set) {
+                        ngx_del_timer(rev);
+                    }
+
                     ngx_imap_auth_http_init(s);
 
                     return;
@@ -251,10 +434,19 @@ ngx_pop3_auth_state(ngx_event_t *rev)
 
                 break;
 
+            case NGX_POP3_CAPA:
+                cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module);
+                text = cscf->pop3_capability->pos;
+                size = cscf->pop3_capability->last - cscf->pop3_capability->pos;
+                break;
+
             case NGX_POP3_QUIT:
                 quit = 1;
                 break;
 
+            case NGX_POP3_NOOP:
+                break;
+
             default:
                 s->imap_state = ngx_pop3_start;
                 rc = NGX_IMAP_PARSE_INVALID_COMMAND;
@@ -262,6 +454,10 @@ ngx_pop3_auth_state(ngx_event_t *rev)
             }
 
             break;
+
+        /* suppress warinings */
+        case ngx_pop3_passwd:
+            break;
         }
     }
 
@@ -291,7 +487,7 @@ ngx_pop3_auth_state(ngx_event_t *rev)
 
 
 static ngx_int_t
-ngx_pop3_read_command(ngx_imap_session_t *s)
+ngx_imap_read_command(ngx_imap_session_t *s)
 {
     ssize_t    n;
     ngx_int_t  rc;
@@ -310,16 +506,23 @@ ngx_pop3_read_command(ngx_imap_session_t
 
     if (n == NGX_AGAIN) {
         if (ngx_handle_read_event(s->connection->read, 0) == NGX_ERROR) {
-            ngx_imap_close_connection(s->connection);
+            ngx_imap_session_internal_server_error(s);
             return NGX_ERROR;
         }
 
         return NGX_AGAIN;
     }
 
-    rc = ngx_pop3_parse_command(s);
+    if (s->protocol == NGX_IMAP_POP3_PROTOCOL) {
+        rc = ngx_pop3_parse_command(s);
+    } else {
+        rc = ngx_imap_parse_command(s);
+    }
 
-    if (rc == NGX_AGAIN || rc == NGX_IMAP_PARSE_INVALID_COMMAND) {
+    if (rc == NGX_AGAIN
+        || rc == NGX_IMAP_NEXT
+        || rc == NGX_IMAP_PARSE_INVALID_COMMAND)
+    {
         return rc;
     }
 
@@ -332,20 +535,6 @@ ngx_pop3_read_command(ngx_imap_session_t
 }
 
 
-#if 0
-
-void
-ngx_imap_close_session(ngx_imap_session_t *s)
-{
-    ngx_log_debug0(NGX_LOG_DEBUG_IMAP, s->connection->log, 0,
-                   "close imap session");
-
-    ngx_imap_close_connection(s->connection);
-}
-
-#endif
-
-
 void
 ngx_imap_session_internal_server_error(ngx_imap_session_t *s)
 {
--- a/src/imap/ngx_imap_parse.c
+++ b/src/imap/ngx_imap_parse.c
@@ -10,64 +10,134 @@
 #include <ngx_imap.h>
 
 
-ngx_int_t ngx_pop3_parse_command(ngx_imap_session_t *s)
+ngx_int_t ngx_imap_parse_command(ngx_imap_session_t *s)
 {
     u_char      ch, *p, *c;
     ngx_str_t  *arg;
     enum {
         sw_start = 0,
+        sw_spaces_before_command,
+        sw_command,
         sw_spaces_before_argument,
         sw_argument,
-        sw_almost_done,
-        sw_done
+        sw_literal,
+        sw_start_literal_argument,
+        sw_literal_argument,
+        sw_end_literal_argument,
+        sw_almost_done
     } state;
 
     state = s->state;
-    p = s->buffer->pos;
 
-    while (p < s->buffer->last && state < sw_done) {
-        ch = *p++;
+    for (p = s->buffer->pos; p < s->buffer->last; p++) {
+        ch = *p;
 
         switch (state) {
 
-        /* POP3 command */
-
+        /* IMAP tag */
         case sw_start:
-            if (ch == ' ' || ch == CR || ch == LF) {
-                c = s->buffer->start;
-
-                if (p - 1 - c == 4) {
-
-                    if (c[0] == 'U' && c[1] == 'S'
-                        && c[2] == 'E' && c[3] == 'R')
-                    {
-                        s->command = NGX_POP3_USER;
+            switch (ch) {
+            case ' ':
+                s->tag.len = p - s->buffer->start + 1;
+                s->tag.data = s->buffer->start;
+                state = sw_spaces_before_command;
+                break;
+            case CR:
+                s->state = sw_start;
+                return NGX_IMAP_PARSE_INVALID_COMMAND;
+            case LF:
+                s->state = sw_start;
+                return NGX_IMAP_PARSE_INVALID_COMMAND;
+            }
+            break;
 
-                    } else if (c[0] == 'P' && c[1] == 'A'
-                               && c[2] == 'S' && c[3] == 'S')
-                    {
-                        s->command = NGX_POP3_PASS;
+        case sw_spaces_before_command:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                s->state = sw_start;
+                return NGX_IMAP_PARSE_INVALID_COMMAND;
+            case LF:
+                s->state = sw_start;
+                return NGX_IMAP_PARSE_INVALID_COMMAND;
+            default:
+                s->cmd_start = p;
+                state = sw_command;
+                break;
+            }
+            break;
 
-                    } else if (c[0] == 'Q' && c[1] == 'U'
-                               && c[2] == 'I' && c[3] == 'T')
+        case sw_command:
+            if (ch == ' ' || ch == CR || ch == LF) {
+
+                c = s->cmd_start;
+
+                switch (p - c) {
+
+                case 4:
+                    if ((c[0] == 'N' || c[0] == 'n')
+                        && (c[1] == 'O'|| c[1] == 'o')
+                        && (c[2] == 'O'|| c[2] == 'o')
+                        && (c[3] == 'P'|| c[3] == 'p'))
                     {
-                        s->command = NGX_POP3_QUIT;
-
-#if 0
-                    } else if (c[0] == 'N' && c[1] == 'O'
-                               && c[2] == 'O' && c[3] == 'P')
-                    {
-                        s->command = NGX_POP3_NOOP;
-#endif
+                        s->command = NGX_IMAP_NOOP;
 
                     } else {
-                        s->state = sw_start;
-                        return NGX_IMAP_PARSE_INVALID_COMMAND;
+                        goto invalid;
                     }
+                    break;
+
+                case 5:
+                    if ((c[0] == 'L'|| c[0] == 'l')
+                        && (c[1] == 'O'|| c[1] == 'o')
+                        && (c[2] == 'G'|| c[2] == 'g')
+                        && (c[3] == 'I'|| c[3] == 'i')
+                        && (c[4] == 'N'|| c[4] == 'n'))
+                    {
+                        s->command = NGX_IMAP_LOGIN;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
 
-                } else {
-                    s->state = sw_start;
-                    return NGX_IMAP_PARSE_INVALID_COMMAND;
+                case 6:
+                    if ((c[0] == 'L'|| c[0] == 'l')
+                        && (c[1] == 'O'|| c[1] == 'o')
+                        && (c[2] == 'G'|| c[2] == 'g')
+                        && (c[3] == 'O'|| c[3] == 'o')
+                        && (c[4] == 'U'|| c[4] == 'u')
+                        && (c[5] == 'T'|| c[5] == 't'))
+                    {
+                        s->command = NGX_IMAP_LOGOUT;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+                case 10:
+                    if ((c[0] == 'C'|| c[0] == 'c')
+                        && (c[1] == 'A'|| c[1] == 'a')
+                        && (c[2] == 'P'|| c[2] == 'p')
+                        && (c[3] == 'A'|| c[3] == 'a')
+                        && (c[4] == 'B'|| c[4] == 'b')
+                        && (c[5] == 'I'|| c[5] == 'i')
+                        && (c[6] == 'L'|| c[6] == 'l')
+                        && (c[7] == 'I'|| c[7] == 'i')
+                        && (c[8] == 'T'|| c[8] == 't')
+                        && (c[9] == 'Y'|| c[9] == 'y'))
+                    {
+                        s->command = NGX_IMAP_CAPABILITY;
+
+                    } else {
+                        goto invalid;
+                    }
+                    break;
+
+                default:
+                    goto invalid;
                 }
 
                 switch (ch) {
@@ -78,45 +148,287 @@ ngx_int_t ngx_pop3_parse_command(ngx_ima
                     state = sw_almost_done;
                     break;
                 case LF:
-                    state = sw_done;
-                    break;
+                    goto done;
                 }
                 break;
             }
 
-            if (ch < 'A' || ch > 'Z') {
-                s->state = sw_start;
-                return NGX_IMAP_PARSE_INVALID_COMMAND;
+            if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+                goto invalid;
             }
 
             break;
 
-        /* the spaces before the argument */
         case sw_spaces_before_argument:
             switch (ch) {
             case ' ':
                 break;
             case CR:
                 state = sw_almost_done;
-                s->arg_end = p - 1;
+                s->arg_end = p;
                 break;
             case LF:
-                state = sw_done;
-                s->arg_end = p - 1;
-                break;
+                s->arg_end = p;
+                goto done;
+            case '"':
+                if (s->args.nelts <= 2) {
+                    s->quoted = 1;
+                    s->arg_start = p + 1;
+                    state = sw_argument;
+                    break;
+                }
+                goto invalid;
+            case '{':
+                if (s->args.nelts <= 2) {
+                    state = sw_literal;
+                    break;
+                }
+                goto invalid;
             default:
-                if (s->args.nelts > 2) {
-                    s->state = sw_start;
-                    return NGX_IMAP_PARSE_INVALID_COMMAND;
+                if (s->args.nelts <= 2) {
+                    s->arg_start = p;
+                    state = sw_argument;
+                    break;
+                }
+                goto invalid;
+            }
+            break;
+
+        case sw_argument:
+            switch (ch) {
+            case '"':
+                if (!s->quoted) {
+                    break;
                 }
+                s->quoted = 0;
+                /* fall through */
+            case ' ':
+            case CR:
+            case LF:
+                arg = ngx_array_push(&s->args);
+                if (arg == NULL) {
+                    return NGX_ERROR;
+                }
+                arg->len = p - s->arg_start;
+                arg->data = s->arg_start;
+                s->arg_start = NULL;
 
-                state = sw_argument;
-                s->arg_start = p - 1;
+                switch (ch) {
+                case '"':
+                case ' ':
+                    state = sw_spaces_before_argument;
+                    break;
+                case CR:
+                    state = sw_almost_done;
+                    break;
+                case LF:
+                    goto done;
+                }
                 break;
             }
             break;
 
-        /* the argument */
+        case sw_literal:
+            if (ch >= '0' && ch <= '9') {
+                s->literal_len = s->literal_len * 10 + (ch - '0');
+                break;
+            }
+            if (ch == '}') {
+                state = sw_start_literal_argument;
+                break;
+            }
+            goto invalid;
+
+        case sw_start_literal_argument:
+            switch (ch) {
+            case CR:
+                break;
+            case LF:
+                s->buffer->pos = p + 1;
+                s->arg_start = p + 1;
+                s->state = sw_literal_argument;
+                return NGX_IMAP_NEXT;
+            }
+            goto invalid;
+
+        case sw_literal_argument:
+            if (--s->literal_len) {
+                break;
+            }
+
+            arg = ngx_array_push(&s->args);
+            if (arg == NULL) {
+                return NGX_ERROR;
+            }
+            arg->len = p + 1 - s->arg_start;
+            arg->data = s->arg_start;
+            s->arg_start = NULL;
+            state = sw_end_literal_argument;
+
+            break;
+
+        case sw_end_literal_argument:
+            switch (ch) {
+            case '{':
+                if (s->args.nelts <= 2) {
+                    state = sw_literal;
+                    break;
+                }
+                goto invalid;
+            case CR:
+                state = sw_almost_done;
+                break;
+            case LF:
+                goto done;
+            default:
+                goto invalid;
+            }
+            break;
+
+        case sw_almost_done:
+            switch (ch) {
+            case LF:
+                goto done;
+            default:
+                goto invalid;
+            }
+        }
+    }
+
+    s->buffer->pos = p;
+    s->state = state;
+
+    return NGX_AGAIN;
+
+done:
+
+    s->buffer->pos = p + 1;
+
+    if (s->arg_start) {
+        arg = ngx_array_push(&s->args);
+        if (arg == NULL) {
+            return NGX_ERROR;
+        }
+        arg->len = s->arg_end - s->arg_start;
+        arg->data = s->arg_start;
+        s->arg_start = NULL;
+        s->cmd_start = NULL;
+        s->quoted = 0;
+        s->literal_len = 0;
+    }
+
+    s->state = sw_start;
+
+    return NGX_OK;
+
+invalid:
+
+    s->state = sw_start;
+    s->quoted = 0;
+    s->literal_len = 0;
+
+    return NGX_IMAP_PARSE_INVALID_COMMAND;
+}
+
+
+ngx_int_t ngx_pop3_parse_command(ngx_imap_session_t *s)
+{
+    u_char      ch, *p, *c, c0, c1, c2, c3;
+    ngx_str_t  *arg;
+    enum {
+        sw_start = 0,
+        sw_spaces_before_argument,
+        sw_argument,
+        sw_almost_done
+    } state;
+
+    state = s->state;
+
+    for (p = s->buffer->pos; p < s->buffer->last; p++) {
+        ch = *p;
+
+        switch (state) {
+
+        /* POP3 command */
+        case sw_start:
+            if (ch == ' ' || ch == CR || ch == LF) {
+                c = s->buffer->start;
+
+                if (p - c == 4) {
+
+                    c0 = ngx_toupper(c[0]);
+                    c1 = ngx_toupper(c[1]);
+                    c2 = ngx_toupper(c[2]);
+                    c3 = ngx_toupper(c[3]);
+
+                    if (c0 == 'U' && c1 == 'S' && c2 == 'E' && c3 == 'R')
+                    {
+                        s->command = NGX_POP3_USER;
+
+                    } else if (c0 == 'P' && c1 == 'A' && c2 == 'S' && c3 == 'S')
+                    {
+                        s->command = NGX_POP3_PASS;
+
+                    } else if (c0 == 'Q' && c1 == 'U' && c2 == 'I' && c3 == 'T')
+                    {
+                        s->command = NGX_POP3_QUIT;
+
+                    } else if (c0 == 'C' && c1 == 'A' && c2 == 'P' && c3 == 'A')
+                    {
+                        s->command = NGX_POP3_CAPA;
+
+                    } else if (c0 == 'N' && c1 == 'O' && c2 == 'O' && c3 == 'P')
+                    {
+                        s->command = NGX_POP3_NOOP;
+
+                    } else {
+                        goto invalid;
+                    }
+
+                } else {
+                    goto invalid;
+                }
+
+                switch (ch) {
+                case ' ':
+                    state = sw_spaces_before_argument;
+                    break;
+                case CR:
+                    state = sw_almost_done;
+                    break;
+                case LF:
+                    goto done;
+                }
+                break;
+            }
+
+            if ((ch < 'A' || ch > 'Z') && (ch < 'a' || ch > 'z')) {
+                goto invalid;
+            }
+
+            break;
+
+        case sw_spaces_before_argument:
+            switch (ch) {
+            case ' ':
+                break;
+            case CR:
+                state = sw_almost_done;
+                s->arg_end = p;
+                break;
+            case LF:
+                s->arg_end = p;
+                goto done;
+            default:
+                if (s->args.nelts <= 2) {
+                    state = sw_argument;
+                    s->arg_start = p;
+                    break;
+                }
+                goto invalid;
+            }
+            break;
+
         case sw_argument:
             switch (ch) {
             case ' ':
@@ -126,7 +438,7 @@ ngx_int_t ngx_pop3_parse_command(ngx_ima
                 if (arg == NULL) {
                     return NGX_ERROR;
                 }
-                arg->len = p - 1 - s->arg_start;
+                arg->len = p - s->arg_start;
                 arg->data = s->arg_start;
                 s->arg_start = NULL;
 
@@ -138,8 +450,7 @@ ngx_int_t ngx_pop3_parse_command(ngx_ima
                     state = sw_almost_done;
                     break;
                 case LF:
-                    state = sw_done;
-                    break;
+                    goto done;
                 }
                 break;
 
@@ -148,42 +459,42 @@ ngx_int_t ngx_pop3_parse_command(ngx_ima
             }
             break;
 
-        /* end of request line */
         case sw_almost_done:
             switch (ch) {
             case LF:
-                state = sw_done;
-                break;
+                goto done;
             default:
-                s->state = sw_start;
-                return NGX_IMAP_PARSE_INVALID_COMMAND;
+                goto invalid;
             }
-            break;
-
-        /* suppress warning */
-        case sw_done:
-            break;
         }
     }
 
     s->buffer->pos = p;
+    s->state = state;
 
-    if (state == sw_done) {
-        if (s->arg_start) {
-            arg = ngx_array_push(&s->args);
-            if (arg == NULL) {
-                return NGX_ERROR;
-            }
-            arg->len = s->arg_end - s->arg_start;
-            arg->data = s->arg_start;
-            s->arg_start = NULL;
+    return NGX_AGAIN;
+
+done:
+
+    s->buffer->pos = p + 1;
+
+    if (s->arg_start) {
+        arg = ngx_array_push(&s->args);
+        if (arg == NULL) {
+            return NGX_ERROR;
         }
+        arg->len = s->arg_end - s->arg_start;
+        arg->data = s->arg_start;
+        s->arg_start = NULL;
+    }
 
-        s->state = sw_start;
-        return NGX_OK;
+    s->state = sw_start;
 
-    } else {
-        s->state = state;
-        return NGX_AGAIN;
-    }
+    return NGX_OK;
+
+invalid:
+
+    s->state = sw_start;
+
+    return NGX_IMAP_PARSE_INVALID_COMMAND;
 }
--- a/src/imap/ngx_imap_proxy_module.c
+++ b/src/imap/ngx_imap_proxy_module.c
@@ -17,16 +17,23 @@ typedef struct {
 
 
 static void ngx_imap_proxy_block_read(ngx_event_t *rev);
-static void ngx_imap_proxy_auth_handler(ngx_event_t *rev);
+static void ngx_imap_proxy_imap_handler(ngx_event_t *rev);
+static void ngx_imap_proxy_pop3_handler(ngx_event_t *rev);
 static void ngx_imap_proxy_dummy_handler(ngx_event_t *ev);
-static ngx_int_t ngx_imap_proxy_read_response(ngx_imap_session_t *s);
+static ngx_int_t ngx_imap_proxy_read_response(ngx_imap_session_t *s,
+    ngx_uint_t what);
 static void ngx_imap_proxy_handler(ngx_event_t *ev);
+static void ngx_imap_proxy_internal_server_error(ngx_imap_session_t *s);
 static void ngx_imap_proxy_close_session(ngx_imap_session_t *s);
 static void *ngx_imap_proxy_create_conf(ngx_conf_t *cf);
 static char *ngx_imap_proxy_merge_conf(ngx_conf_t *cf, void *parent,
     void *child);
 
 
+#define NGX_IMAP_WAIT_OK    0
+#define NGX_IMAP_WAIT_NEXT  1
+
+
 static ngx_command_t  ngx_imap_proxy_commands[] = {
     { ngx_string("proxy"),
       NGX_IMAP_MAIN_CONF|NGX_IMAP_SRV_CONF|NGX_CONF_FLAG,
@@ -61,12 +68,13 @@ ngx_module_t  ngx_imap_proxy_module = {
 void
 ngx_imap_proxy_init(ngx_imap_session_t *s, ngx_peers_t *peers)
 {
-    ngx_int_t              rc;
-    ngx_imap_proxy_ctx_t  *p;
+    ngx_int_t                  rc;
+    ngx_imap_proxy_ctx_t      *p;
+    ngx_imap_core_srv_conf_t  *cscf;
 
     p = ngx_pcalloc(s->connection->pool, sizeof(ngx_imap_proxy_ctx_t));
     if (p == NULL) {
-        ngx_imap_close_connection(s->connection);
+        ngx_imap_session_internal_server_error(s);
         return;
     }
 
@@ -79,16 +87,27 @@ ngx_imap_proxy_init(ngx_imap_session_t *
     rc = ngx_event_connect_peer(&p->upstream);
 
     if (rc == NGX_ERROR) {
-        ngx_imap_proxy_close_session(s);
+        ngx_imap_session_internal_server_error(s);
         return;
     }
 
+    cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module);
+    ngx_add_timer(p->upstream.connection->read, cscf->timeout);
+
     p->upstream.connection->data = s;
     p->upstream.connection->pool = s->connection->pool;
 
     s->connection->read->handler = ngx_imap_proxy_block_read;
-    p->upstream.connection->read->handler = ngx_imap_proxy_auth_handler;
     p->upstream.connection->write->handler = ngx_imap_proxy_dummy_handler;
+
+    if (s->protocol == NGX_IMAP_POP3_PROTOCOL) {
+        p->upstream.connection->read->handler = ngx_imap_proxy_pop3_handler;
+        s->imap_state = ngx_pop3_start;
+
+    } else {
+        p->upstream.connection->read->handler = ngx_imap_proxy_imap_handler;
+        s->imap_state = ngx_imap_start;
+    }
 }
 
 
@@ -110,53 +129,189 @@ ngx_imap_proxy_block_read(ngx_event_t *r
 
 
 static void
-ngx_imap_proxy_auth_handler(ngx_event_t *rev)
+ngx_imap_proxy_imap_handler(ngx_event_t *rev)
 {
-    u_char              *p;
-    ngx_int_t            rc;
-    ngx_str_t            line;
-    ngx_connection_t    *c;
-    ngx_imap_session_t  *s;
+    u_char                    *p;
+    ngx_int_t                  rc;
+    ngx_str_t                  line;
+    ngx_connection_t          *c;
+    ngx_imap_session_t        *s;
+    ngx_imap_core_srv_conf_t  *cscf;
 
-    ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap proxy auth handler");
+    ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0,
+                   "imap proxy imap auth handler");
 
     c = rev->data;
     s = c->data;
 
     if (rev->timedout) {
-        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT, "client timed out");
-        ngx_imap_proxy_close_session(s);
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                      "upstream timed out");
+        ngx_imap_proxy_internal_server_error(s);
         return;
     }
 
     if (s->proxy->buffer == NULL) {
-        s->proxy->buffer = ngx_create_temp_buf(c->pool, /* STUB */ 4096);
+        cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module);
+
+        s->proxy->buffer = ngx_create_temp_buf(c->pool,
+                                               cscf->proxy_buffer_size);
         if (s->proxy->buffer == NULL) {
-            ngx_imap_proxy_close_session(s);
+            ngx_imap_proxy_internal_server_error(s);
             return;
         }
     }
 
-    rc = ngx_imap_proxy_read_response(s);
+    rc = ngx_imap_proxy_read_response(s, s->imap_state == ngx_imap_start ?
+                                      NGX_IMAP_WAIT_OK : NGX_IMAP_WAIT_NEXT);
 
     if (rc == NGX_AGAIN) {
         return;
     }
 
     if (rc == NGX_ERROR) {
-        /* TODO: ngx_imap_proxy_finalize_session(s, NGX_IMAP_INTERNAL_ERROR) */
-        ngx_imap_proxy_close_session(s);
+        ngx_imap_proxy_internal_server_error(s);
         return;
     }
 
-    if (s->imap_state == ngx_pop3_start) {
+    switch (s->imap_state) {
+
+    case ngx_imap_start:
+        ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0,
+                       "imap proxy send login");
 
+        line.len = s->tag.len + sizeof("LOGIN ") - 1
+                   + 1 + NGX_SIZE_T_LEN + 1 + 2;
+        line.data = ngx_palloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_imap_proxy_internal_server_error(s);
+            return;
+        }
+
+        line.len = ngx_sprintf(line.data, "%VLOGIN {%uz}" CRLF,
+                               &s->tag, s->login.len)
+                   - line.data;
+
+        s->imap_state = ngx_imap_login;
+        break;
+
+    case ngx_imap_login:
         ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap proxy send user");
 
-        line.len = sizeof("USER ") + s->login.len - 1 + 2;
+        line.len = s->login.len + 1 + NGX_SIZE_T_LEN + 1 + 2;
+        line.data = ngx_palloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_imap_proxy_internal_server_error(s);
+            return;
+        }
+
+        line.len = ngx_sprintf(line.data, "%V{%uz}" CRLF,
+                               &s->login, s->passwd.len)
+                   - line.data;
+
+        s->imap_state = ngx_imap_user;
+        break;
+
+    case ngx_imap_user:
+        ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0,
+                       "imap proxy send passwd");
+
+        line.len = s->passwd.len + 2;
         line.data = ngx_palloc(c->pool, line.len);
         if (line.data == NULL) {
-            ngx_imap_proxy_close_session(s);
+            ngx_imap_proxy_internal_server_error(s);
+            return;
+        }
+
+        p = ngx_cpymem(line.data, s->passwd.data, s->passwd.len);
+        *p++ = CR; *p = LF;
+
+        s->imap_state = ngx_imap_passwd;
+        break;
+
+    default:
+#if (NGX_SUPPRESS_WARN)
+        line.len = 0;
+        line.data = NULL;
+#endif
+        break;
+    }
+
+    if (ngx_send(c, line.data, line.len) < (ssize_t) line.len) {
+        /*
+         * we treat the incomplete sending as NGX_ERROR
+         * because it is very strange here
+         */
+        ngx_imap_proxy_internal_server_error(s);
+        return;
+    }
+
+    s->proxy->buffer->pos = s->proxy->buffer->start;
+    s->proxy->buffer->last = s->proxy->buffer->start;
+
+    if (s->imap_state == ngx_imap_passwd) {
+        s->connection->read->handler = ngx_imap_proxy_handler;
+        s->connection->write->handler = ngx_imap_proxy_handler;
+        rev->handler = ngx_imap_proxy_handler;
+        c->write->handler = ngx_imap_proxy_handler;
+    }
+}
+
+
+static void
+ngx_imap_proxy_pop3_handler(ngx_event_t *rev)
+{
+    u_char                    *p;
+    ngx_int_t                  rc;
+    ngx_str_t                  line;
+    ngx_connection_t          *c;
+    ngx_imap_session_t        *s;
+    ngx_imap_core_srv_conf_t  *cscf;
+
+    ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0,
+                   "imap proxy pop3 auth handler");
+
+    c = rev->data;
+    s = c->data;
+
+    if (rev->timedout) {
+        ngx_log_error(NGX_LOG_INFO, c->log, NGX_ETIMEDOUT,
+                      "upstream timed out");
+        ngx_imap_proxy_internal_server_error(s);
+        return;
+    }
+
+    if (s->proxy->buffer == NULL) {
+        cscf = ngx_imap_get_module_srv_conf(s, ngx_imap_core_module);
+
+        s->proxy->buffer = ngx_create_temp_buf(c->pool,
+                                               cscf->proxy_buffer_size);
+        if (s->proxy->buffer == NULL) {
+            ngx_imap_proxy_internal_server_error(s);
+            return;
+        }
+    }
+
+    rc = ngx_imap_proxy_read_response(s, NGX_IMAP_WAIT_OK);
+
+    if (rc == NGX_AGAIN) {
+        return;
+    }
+
+    if (rc == NGX_ERROR) {
+        ngx_imap_proxy_internal_server_error(s);
+        return;
+    }
+
+    switch (s->imap_state) {
+
+    case ngx_pop3_start:
+        ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap proxy send user");
+
+        line.len = sizeof("USER ")  - 1 + s->login.len + 2;
+        line.data = ngx_palloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_imap_proxy_internal_server_error(s);
             return;
         }
 
@@ -164,52 +319,52 @@ ngx_imap_proxy_auth_handler(ngx_event_t 
         p = ngx_cpymem(p, s->login.data, s->login.len);
         *p++ = CR; *p = LF;
 
-        if (ngx_send(c, line.data, line.len) < (ssize_t) line.len) {
-            /*
-             * we treat the incomplete sending as NGX_ERROR
-             * because it is very strange here
-             */
-            ngx_imap_close_connection(c);
+        s->imap_state = ngx_pop3_user;
+        break;
+
+    case ngx_pop3_user:
+        ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap proxy send pass");
+
+        line.len = sizeof("PASS ")  - 1 + s->passwd.len + 2;
+        line.data = ngx_palloc(c->pool, line.len);
+        if (line.data == NULL) {
+            ngx_imap_proxy_internal_server_error(s);
             return;
         }
 
-        s->imap_state = ngx_pop3_user;
-
-        s->proxy->buffer->pos = s->proxy->buffer->start;
-        s->proxy->buffer->last = s->proxy->buffer->start;
+        p = ngx_cpymem(line.data, "PASS ", sizeof("PASS ") - 1);
+        p = ngx_cpymem(p, s->passwd.data, s->passwd.len);
+        *p++ = CR; *p = LF;
 
-        return;
-    }
-
-    ngx_log_debug0(NGX_LOG_DEBUG_IMAP, rev->log, 0, "imap proxy send pass");
+        s->imap_state = ngx_pop3_passwd;
+        break;
 
-    line.len = sizeof("PASS ") + s->passwd.len - 1 + 2;
-    line.data = ngx_palloc(c->pool, line.len);
-    if (line.data == NULL) {
-        ngx_imap_proxy_close_session(s);
-        return;
+    default:
+#if (NGX_SUPPRESS_WARN)
+        line.len = 0;
+        line.data = NULL;
+#endif
+        break;
     }
 
-    p = ngx_cpymem(line.data, "PASS ", sizeof("PASS ") - 1);
-    p = ngx_cpymem(p, s->passwd.data, s->passwd.len);
-    *p++ = CR; *p = LF;
-
     if (ngx_send(c, line.data, line.len) < (ssize_t) line.len) {
         /*
          * we treat the incomplete sending as NGX_ERROR
          * because it is very strange here
          */
-        ngx_imap_close_connection(c);
+        ngx_imap_proxy_internal_server_error(s);
         return;
     }
 
     s->proxy->buffer->pos = s->proxy->buffer->start;
     s->proxy->buffer->last = s->proxy->buffer->start;
 
-    s->connection->read->handler = ngx_imap_proxy_handler;
-    s->connection->write->handler = ngx_imap_proxy_handler;
-    rev->handler = ngx_imap_proxy_handler;
-    c->write->handler = ngx_imap_proxy_handler;
+    if (s->imap_state == ngx_pop3_passwd) {
+        s->connection->read->handler = ngx_imap_proxy_handler;
+        s->connection->write->handler = ngx_imap_proxy_handler;
+        rev->handler = ngx_imap_proxy_handler;
+        c->write->handler = ngx_imap_proxy_handler;
+    }
 }
 
 
@@ -221,7 +376,7 @@ ngx_imap_proxy_dummy_handler(ngx_event_t
 
 
 static ngx_int_t
-ngx_imap_proxy_read_response(ngx_imap_session_t *s)
+ngx_imap_proxy_read_response(ngx_imap_session_t *s, ngx_uint_t what)
 {
     u_char     *p;
     ssize_t     n;
@@ -259,17 +414,31 @@ ngx_imap_proxy_read_response(ngx_imap_se
 
     p = b->pos;
 
-    if (p[0] == '+' && p[1] == 'O' && p[2] == 'K') {
-        return NGX_OK;
-    }
+    if (s->protocol == NGX_IMAP_POP3_PROTOCOL) {
+        if (p[0] == '+' && p[1] == 'O' && p[2] == 'K') {
+            return NGX_OK;
+        }
+
+        if (p[0] == '-' && p[1] == 'E' && p[2] == 'R' && p[3] == 'R') {
+            return NGX_IMAP_PROXY_ERROR;
+        }
 
-    if (p[0] == '-' && p[1] == 'E' && p[2] == 'R' && p[3] == 'R') {
-        return NGX_IMAP_PROXY_ERROR;
+    } else {
+        if (what == NGX_IMAP_WAIT_OK) {
+            if (p[0] == '*' && p[1] == ' ' && p[2] == 'O' && p[3] == 'K') {
+                return NGX_OK;
+            }
+
+        } else {
+            if (p[0] == '+' && p[1] == ' ' && p[2] == 'O' && p[3] == 'K') {
+                return NGX_OK;
+            }
+        }
     }
 
     *(b->last - 2) = '\0';
     ngx_log_error(NGX_LOG_ERR, s->connection->log, 0,
-                  "upstream sent invalid greeting line: \"%s\"", p);
+                  "upstream sent invalid response: \"%s\"", p);
 
     return NGX_IMAP_PROXY_INVALID;
 }
@@ -397,6 +566,21 @@ ngx_imap_proxy_handler(ngx_event_t *ev)
 
 
 static void
+ngx_imap_proxy_internal_server_error(ngx_imap_session_t *s)
+{
+    if (s->proxy->upstream.connection) {
+        ngx_log_debug1(NGX_LOG_DEBUG_IMAP, s->connection->log, 0,
+                       "close imap proxy connection: %d",
+                       s->proxy->upstream.connection->fd);
+
+        ngx_close_connection(s->proxy->upstream.connection);
+    }
+
+    ngx_imap_session_internal_server_error(s);
+}
+
+
+static void
 ngx_imap_proxy_close_session(ngx_imap_session_t *s)
 {
     if (s->proxy->upstream.connection) {
--- a/src/os/unix/ngx_linux_config.h
+++ b/src/os/unix/ngx_linux_config.h
@@ -88,6 +88,11 @@ extern ssize_t sendfile(int s, int fd, i
 #endif
 
 
+#ifndef NGX_HAVE_GNU_CRYPT_R
+#define NGX_HAVE_GNU_CRYPT_R         1
+#endif
+
+
 #ifndef NGX_HAVE_INHERITED_NONBLOCK
 #define NGX_HAVE_INHERITED_NONBLOCK  0
 #endif
--- a/src/os/unix/ngx_user.c
+++ b/src/os/unix/ngx_user.c
@@ -20,7 +20,7 @@
 
 #if (NGX_CRYPT)
 
-#if (NGX_LINUX)
+#if (NGX_HAVE_GNU_CRYPT_R)
 
 ngx_int_t
 ngx_crypt(ngx_pool_t *pool, u_char *key, u_char *salt, u_char **encrypted)
@@ -33,6 +33,8 @@ ngx_crypt(ngx_pool_t *pool, u_char *key,
     ngx_set_errno(0);
 
     cd.initialized = 0;
+    /* work around the glibc-2.2.5 bug */
+    cd.current_saltbits = 0;
 
     value = crypt_r((char *) key, (char *) salt, &cd);