Nginx 400 error when username included in the uri

Hello,

Currently when nginx encounters a Request-URI of the form
http://[email protected]/ it returns error 400. This appears to be
proper per RFC 2616 since the username@ is not recognized by the RFC for
HTTP URIs. However, this is causing problems for us when dealing with
some clients because Apache silently discards the username:password@
portion of the URI. Users with clients expecting to see the same
behavior (e.g. Apple’s XCode) receive an error instead.

We realize that accepting this would be non-standard behavior and
understand if incorporating the changes is not possible. However, it
would be useful to us if we could have the option to let nginx be as
permissive as Apache while proxying to it. Either way, we would
appreciate any feedback on the patch in terms unintended side-effects,
etc.

Something like the following should allow nginx to ignore the
username:password:

*** nginx-0.6.32/src/http/ngx_http_parse.c 2008-03-16
11:47:16.000000000 -0500
— nginx-0.6.32-new/src/http/ngx_http_parse.c 2008-08-19
13:07:50.000000000 -0500


*** 355,360 ****
— 355,363 ----
r->uri_end = r->schema_end + 2;
state = sw_http_09;
break;

  •         case '@':
    
  •             r->host_start = p + 1;
    
  •             break;
            default:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
    

For reference, in APR’s apr_uri_parse method used by Apache, it
information is silently discarded from what I can tell:

/* If there’s a username:password@host:port, the @ we want is the last
@…

  • too bad there’s no memrchr()… For the C purists, note that hostinfo
  • is definately not the first character of the original uri so therefore
  • &hostinfo[-1] < &hostinfo[0] … and this loop is valid C.
    */
    do {
    –s;
    } while (s >= hostinfo && *s != ‘@’);

Thank you,
Michael Ching

On Tue, Aug 19, 2008 at 05:35:52PM -0700, Michael Ching wrote:

would be useful to us if we could have the option to let nginx be as
13:07:50.000000000 -0500
return NGX_HTTP_PARSE_INVALID_REQUEST;
*/
do {
–s;
} while (s >= hostinfo && *s != ‘@’);

This patch ignores user only in “http://user@host”.
Should password in “http://user:password@host” be ignored too ?

Igor S. wrote:

This patch ignores user only in “http://user@host”.
Should password in “http://user:password@host” be ignored too ?

Thank you for taking a look. You raise a good point. More generally,
it would also error if the username included any characters invalid for
a hostname. Working on a more complete fix.

isn’t http://user:password@host now deprecated?

only IE supports that now pre-SP2 / patched right?

(not that i agree with it, but afaik it is not in RFC spec)

originally i had thought there was a generic URI RFC spec

scheme://user:password@host:/dirs

where scheme can be telnet, ssh, http, https, scp, fastcgi, whatever
the programs require…

mike wrote:

where scheme can be telnet, ssh, http, https, scp, fastcgi, whatever
the programs require…

While I am not sure the specifics on whether the syntax is declared
deprecated or not, it is pretty widely used by a browser or client which
accepts the scheme://user:pass@host syntax then converts this to the
appropriate login mechanism for the service actually being accessed
(USER/PASS commands in FTP, Authorization:/Credentials: headers in HTTP,
etc).

For example, you cannot simply pass a request to an http server such as:
GET http://user:pass@host/

However, most browsers will accept the URI and when doing the actual
request to the server send:
GET http://host/
Authorization: Basic …
Credentials: user:pass

The issue is that some broken clients are sending both:
GET http://user:pass@host/
Authorization: Basic …
Credentials: user:pass

Apache ignores the extra info in the GET portion of the request and uses
the proper authorization information passed later, but nginx returns an
error 400 as soon as it detects the characters which should be illegal
in a host name.

Michael Ching wrote:

Igor S. wrote:

This patch ignores user only in “http://user@host”.
Should password in “http://user:password@host” be ignored too ?
We modified the patch we are using so that usernames with characters
illegal in a hostname are allowed (for example GET
http://[email protected]@example.com/), however we decided against
trying to extend the code to handle username:password to avoid breaking
anything related to parsing of the port number.

If we see one illegal character, instead of returning an error right
away we go to a new state. Then, if we encounter an “@” we assume
everything prior was a username, and move the host_start pointer forward
and go back to the sw_host state. Otherwise, if we encounter anything
that would normally indicate the end of the sw_host state before seeing
an “@”, we return the default 400 error.

The downside to this patch is that parsing becomes a lot more permissive
instead of having only explicitly allowed characters in the state
machine. Hopefully this is helpful for anyone else proxying SVN for
Xcode.

*** nginx-0.5.37/src/http/ngx_http_parse.c 2008-01-08
11:23:25.000000000 -0600
— nginx-0.5.37-new/src/http/ngx_http_parse.c 2008-08-23
04:47:37.000000000 -0500


*** 111,116 ****
— 111,117 ----
sw_schema_slash,
sw_schema_slash_slash,
sw_host,

  •     sw_host_illegal_character,
        sw_port,
        sw_after_slash_in_uri,
        sw_check_uri,
    

*** 355,361 ****
— 356,385 ----
r->uri_end = r->schema_end + 2;
state = sw_http_09;
break;

  •         case '@':
    
  •             r->host_start = p + 1;
    
  •             break;
            default:
    
  •             state = sw_host_illegal_character;
    
  •             break;
    
  •         }
    
  •         break;
    
  •     case sw_host_illegal_character:
    
  •         /*
    
  •          * if we see an @ somewhere before the hostname we can 
    

assume

  •          * the illegal characters were part of a username and 
    

ignore

  •          */
    
  •         switch (ch) {
    
  •         case '@':
    
  •             r->host_start = p + 1;
    
  •             state = sw_host;
    
  •             break;
    
  •         case '/':
    
  •         case ' ':
    
  •         case CR:
    
  •         case LF:
                return NGX_HTTP_PARSE_INVALID_REQUEST;
            }
            break;