From 89ae3c8cadc6f1ce0a9b66ba5c2e3aa4834d4440 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 14 Sep 2011 01:41:03 +0200 Subject: 'Host' header is optional in HTTP/1.0 Krishnamurthy, Kristol, Mogul: "Key Differences between HTTP/1.0 and HTTP/1.1", "Internet address conservation". http://www8.org/w8-papers/5c-protocols/key/key.html Fixes issue #35 reported by Alex Kropivny. --- src/cowboy_http_protocol.erl | 26 +++++++++++++++++--------- test/http_SUITE.erl | 27 +++++++++++++++++++++------ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/src/cowboy_http_protocol.erl b/src/cowboy_http_protocol.erl index 35a0471..63c971d 100644 --- a/src/cowboy_http_protocol.erl +++ b/src/cowboy_http_protocol.erl @@ -148,10 +148,12 @@ header({http_header, _I, 'Host', _R, RawHost}, Req=#http_req{ case catch cowboy_dispatcher:split_host(RawHost2) of {Host, RawHost3, undefined} -> Port = default_port(Transport:name()), - dispatch(Req#http_req{host=Host, raw_host=RawHost3, port=Port, + dispatch(fun parse_header/2, Req#http_req{ + host=Host, raw_host=RawHost3, port=Port, headers=[{'Host', RawHost3}|Req#http_req.headers]}, State); {Host, RawHost3, Port} -> - dispatch(Req#http_req{host=Host, raw_host=RawHost3, port=Port, + dispatch(fun parse_header/2, Req#http_req{ + host=Host, raw_host=RawHost3, port=Port, headers=[{'Host', RawHost3}|Req#http_req.headers]}, State); {'EXIT', _Reason} -> error_terminate(400, State) @@ -168,24 +170,30 @@ header({http_header, _I, Field, _R, Value}, Req, State) -> Field2 = format_header(Field), parse_header(Req#http_req{headers=[{Field2, Value}|Req#http_req.headers]}, State); -%% The Host header is required. -header(http_eoh, #http_req{host=undefined}, State) -> +%% The Host header is required in HTTP/1.1. +header(http_eoh, #http_req{version={1, 1}, host=undefined}, State) -> error_terminate(400, State); +%% It is however optional in HTTP/1.0. +header(http_eoh, Req=#http_req{version={1, 0}, transport=Transport, + host=undefined}, State=#state{buffer=Buffer}) -> + Port = default_port(Transport:name()), + dispatch(fun handler_init/2, Req#http_req{host=[], raw_host= <<>>, + port=Port, buffer=Buffer}, State#state{buffer= <<>>}); header(http_eoh, Req, State=#state{buffer=Buffer}) -> handler_init(Req#http_req{buffer=Buffer}, State#state{buffer= <<>>}); header({http_error, _Bin}, _Req, State) -> error_terminate(500, State). --spec dispatch(#http_req{}, #state{}) -> ok. -dispatch(Req=#http_req{host=Host, path=Path}, +-spec dispatch(fun((#http_req{}, #state{}) -> ok), + #http_req{}, #state{}) -> ok. +dispatch(Next, Req=#http_req{host=Host, path=Path}, State=#state{dispatch=Dispatch}) -> %% @todo We probably want to filter the Host and Path here to allow %% things like url rewriting. case cowboy_dispatcher:match(Host, Path, Dispatch) of {ok, Handler, Opts, Binds, HostInfo, PathInfo} -> - parse_header(Req#http_req{host_info=HostInfo, path_info=PathInfo, - bindings=Binds}, - State#state{handler={Handler, Opts}}); + Next(Req#http_req{host_info=HostInfo, path_info=PathInfo, + bindings=Binds}, State#state{handler={Handler, Opts}}); {error, notfound, host} -> error_terminate(400, State); {error, notfound, path} -> diff --git a/test/http_SUITE.erl b/test/http_SUITE.erl index d4d99e8..8708824 100644 --- a/test/http_SUITE.erl +++ b/test/http_SUITE.erl @@ -21,17 +21,18 @@ -export([chunked_response/1, headers_dupe/1, headers_huge/1, keepalive_nl/1, nc_rand/1, pipeline/1, raw/1, ws0/1, ws8/1]). %% http. -export([http_200/1, http_404/1]). %% http and https. +-export([http_10_hostless/1]). %% misc. %% ct. all() -> - [{group, http}, {group, https}]. + [{group, http}, {group, https}, {group, misc}]. groups() -> BaseTests = [http_200, http_404], [{http, [], [chunked_response, headers_dupe, headers_huge, keepalive_nl, nc_rand, pipeline, raw, ws0, ws8] ++ BaseTests}, - {https, [], BaseTests}]. + {https, [], BaseTests}, {misc, [], [http_10_hostless]}]. init_per_suite(Config) -> application:start(inets), @@ -62,16 +63,24 @@ init_per_group(https, Config) -> {keyfile, DataDir ++ "key.pem"}, {password, "cowboy"}], cowboy_http_protocol, [{dispatch, init_https_dispatch()}] ), - [{scheme, "https"}, {port, Port}|Config]. + [{scheme, "https"}, {port, Port}|Config]; +init_per_group(misc, Config) -> + Port = 33082, + cowboy:start_listener(misc, 100, + cowboy_tcp_transport, [{port, Port}], + cowboy_http_protocol, [{dispatch, [{'_', [ + {[], http_handler, []} + ]}]}]), + [{port, Port}|Config]. -end_per_group(http, _Config) -> - cowboy:stop_listener(http), - ok; end_per_group(https, _Config) -> cowboy:stop_listener(https), application:stop(ssl), application:stop(public_key), application:stop(crypto), + ok; +end_per_group(Listener, _Config) -> + cowboy:stop_listener(Listener), ok. %% Dispatch configuration. @@ -309,3 +318,9 @@ http_200(Config) -> http_404(Config) -> {ok, {{"HTTP/1.1", 404, "Not Found"}, _Headers, _Body}} = httpc:request(build_url("/not/found", Config)). + +%% misc. + +http_10_hostless(Config) -> + Packet = "GET / HTTP/1.0\r\n\r\n", + {Packet, 200} = raw_req(Packet, Config). -- cgit v1.2.3