%%% -*- erlang -*-
%%%
  This file is part of  released under the Apache 2 license .
%%% See the NOTICE for more information.
%%%
  Copyright ( c ) 2011 - 2012 ,   <  >
  Copyright ( c ) 2013 - 2014  
%%%
  @doc HTTP parser in pure Erlang
%%% This parser is able to parse HTTP responses and requests in a
%%% streaming fashion. If not set it will be autodetect the type of
%%% binary parsed, if it's a request or a response.
%%%
%%% Internally it is keeping a buffer for intermediary steps but don't
%%% keep any state in memory.
%%%
%%%
  The first time you initialise a parser using ` hackney_http : parser/0 '
%%% or `hackney_http:parser/1' you will receive an opaque record You can
%%% then process it using the function `hackney_http:execute/2'.
%%%
%%% Each steps will return the status, some data and the new parser that
%%% you can process later with `hackney_http:execute/2' when
%%% `{more, ...}'  is returnned or `hackney_http:execute/1' in other
%%% cases:
%%%
  - ` { response , http_version ( ) , status ( ) ,  ( ) , parser ( ) } ' :
  when the first line of a response is parsed
%%% - `{request, http_version(), http_method(), uri(), parser()}':
  when the first line of a request ( on servers ) is parsed
%%% - `{more, parser()}': when the parser need more
%%% data. The new data should be passed to `hackney_http:execute/2' with
%%% the new parser() state received.
%%% - `{header, {Name :: binary(), Value :: binary()}, parser()}':
%%% when an header has been parsed. To continue the parsing you must
%%% call the given `parser()' with `hackney_http:execute/1'.
%%% - `{headers_complete, parser()}' : when all headers have been parsed.
%%% To continue the parsing you must call the given `parser()' state
%%% with `hackney_http:execute/1'.
%%% - `{more, parser(), binary()}': on body, when
%%% the parser need more data. The new data should be passed to
%%% `hackney_http:execute/2' (with `parser()' ) when received. The binary at the end of the
%%% tuple correspond to the actual buffer of the parser. It may be used
%%% for other purpose, like start to parse a new request on pipeline
%%% connections, for a proxy...
%%% - `{ok, binary(), parser()}': on body, when a chunk has been
%%% parsed. To continue the parsing you must call
%%% `hackney_http:execute/1' with the given `parser()'.
%%% - `{done, binary()}': when the parsing is done. The binary
  given  to the non parsed part of the internal buffer .
%%% - `{error, term{}}': when an error happen

-module(hackney_http).

-export([parser/0, parser/1]).
-export([execute/1, execute/2]).
-export([get/2]).
-export([parse_response_version/2]).

-include("hackney_lib.hrl").


-opaque parser() :: #hparser{}.
-export_type([parser/0]).

-type http_version() :: {integer(), integer()}.
-type status() :: integer().
-type http_reason() :: binary().
-type http_method() :: binary().
-type uri() :: binary().
-type body_result() :: {more, parser(), binary()}
    | {ok, binary(), parser()}
    | {done, binary()}.

-type parser_result() ::
    {response, http_version(), status(), http_reason(), parser()}
    | {request, http_version(), http_method(), uri(), parser()}
    | {more, parser()}
    | body_result()
    | {error, term()}.

-type parser_option() :: request | response | auto
    | {max_empty_lines, integer()}
    | {max_line_length, integer()}.

-type parser_options() :: [parser_option()].


%% @doc Create a new HTTP parser. The parser will autodetect if the parded
%% binary is a response or a request.
-spec parser() -> parser().
parser() ->
    parser([]).

%% @doc create a new HTTP parser with options. By default the type of
%% parsed binary will be detected.
%%
%% Available options:
%% <ul>
    < li>`auto ' : autodetect if the binary parsed is a response or a
%%   request (default).</li>
%%   <li>`response': set the parser to parse a response</li>
%%   <li>`request': set the parser to parse a request (server)</li>
    < li>`{max_line_lenght ,  } ' : set the maximum size of a line parsed
%%   before we give up.</li>
    < li>`{max_lines_empty ,  } ' : the maximum number of empty line we
    accept before the first line happen</li >
%% </ul>
-spec parser(parser_options()) -> parser().
parser(Options) ->
    parse_options(Options, #hparser{}).

%% @doc retrieve a parser property.
%% Properties are:
%%  - `buffer': internal buffer of the parser (non parsed)
   - ` state ' : the current state ( on_status , on_header , on_body , done )
%%  - `version': HTTP version
   - ` content_length ' : content length header if any
%%  - `transfer_encoding': transfer encoding header if any
%%  - `content_type': content type header if any
%%  - `location': location header if any
%%  - `connection': connection header if any.
-spec get(parser(), atom() | [atom()]) -> any().
get(Parser, Props) when is_list(Props) ->
    [get_property(P, Parser) || P <- Props];
get(Parser, Prop) ->
    get_property(Prop, Parser).

%% @doc Execute the parser with the current buffer.
-spec execute(#hparser{}) -> parser_result().
execute(St) ->
    execute(St, <<>>).

%% @doc Execute the parser with the new buffer
-spec execute(#hparser{}, binary()) -> parser_result().
execute(#hparser{state=Status, buffer=Buffer}=St, Bin) ->
    %% update the state with the new buffer
    NBuffer = << Buffer/binary, Bin/binary >>,
    St1 = St#hparser{buffer=NBuffer},

    %% process the right state.
    case Status of
        done -> done;
        on_first_line -> parse_first_line(NBuffer, St1, 0);
        on_header -> parse_headers(St1);
        on_body -> parse_body(St1)
    end.

%% Empty lines must be using \r\n.
parse_first_line(<< $\n, _/binary >>, _St, _) ->
    {error, badarg};
  We limit the length of the first - line to MaxLength to avoid endlessly
%% reading from the socket and eventually crashing.
parse_first_line(Buffer, St=#hparser{type=Type,
                                     max_line_length=MaxLength,
                                     max_empty_lines=MaxEmpty}, Empty) ->
    case match_eol(Buffer, 0) of
        nomatch when byte_size(Buffer) > MaxLength ->
            {error, line_too_long};
        nomatch ->
            {more, St#hparser{empty_lines=Empty}};
        1 when Empty =:= MaxEmpty ->
            {error, bad_request};
        1 ->
            << _:16, Rest/binary >> = Buffer,
            parse_first_line(Rest, St, Empty + 1);
        _ when Type =:= auto ->
            case parse_request_line(St) of
                {error, bad_request} ->
                    case parse_response_line(St) of
                        {error, bad_request} = Error ->
                            Error;
                        OK ->
                            OK
                    end;
                OK ->
                    OK
            end;
        _ when Type =:= response ->
            parse_response_line(St);
        _ when Type =:= request ->
            parse_request_line(St)
    end.

match_eol(<< $\n, _/bits >>, N) ->
    N;
match_eol(<< _, Rest/bits >>, N) ->
    match_eol(Rest, N + 1);
match_eol(_, _) ->
    nomatch.

%% @doc parse status
parse_response_line(#hparser{buffer=Buf}=St) ->
    case binary:split(Buf, <<"\r\n">>) of
        [Line, Rest] ->
            parse_response_version(Line, St#hparser{buffer=Rest});
        _ ->
            {error, bad_request}
    end.


parse_response_version(<< "HTTP/", High, ".", Low, $\s, Rest/binary >>, St)
        when High >= $0, High =< $9, Low >= $0, Low =< $9 ->
    Version = { High -$0, Low - $0},
    parse_status(Rest, St, Version, <<>>);
parse_response_version(_, _) ->
     {error, bad_request}.

parse_status(<< C, Rest/bits >>, St, Version, Acc) ->
    case C of
        $\r ->  {error, bad_request};
        $\s -> parse_reason(Rest, St, Version, Acc);
        _ -> parse_status(Rest, St, Version, << Acc/binary, C >>)
    end.

parse_reason(Reason, St, Version, StatusCode) ->
    StatusInt = list_to_integer(binary_to_list(StatusCode)),

    NState = St#hparser{type=response,
                        version=Version,
                        state=on_header,
                        partial_headers=[]},
    {response, Version, StatusInt, Reason, NState}.


parse_request_line(#hparser{buffer=Buf}=St) ->
    parse_method(Buf, St, <<>>).

parse_method(<< C, Rest/bits >>, St, Acc) ->
    case C of
        $\r ->  {error, bad_request};
        $\s -> parse_uri(Rest, St, Acc);
        _ -> parse_method(Rest, St, << Acc/binary, C >>)
    end.

parse_uri(<< $\r, _/bits >>, _St, _) ->
    {error, bad_request};
parse_uri(<< "* ", Rest/bits >>, St, Method) ->
    parse_version(Rest, St, Method, <<"*">>);
parse_uri(Buffer, St, Method) ->
    parse_uri_path(Buffer, St, Method, <<>>).

parse_uri_path(<< C, Rest/bits >>, St, Method, Acc) ->
    case C of
        $\r -> {error, bad_request};
        $\s -> parse_version(Rest, St, Method, Acc);
        _ -> parse_uri_path(Rest, St, Method, << Acc/binary, C >>)
    end.

parse_version(<< "HTTP/", High, ".", Low, Rest/binary >>, St, Method, URI)
        when High >= $0, High =< $9, Low >= $0, Low =< $9 ->
    Version = { High -$0, Low - $0},

    NState = St#hparser{type=request,
                        version=Version,
                        method=Method,
                        state=on_header,
                        buffer=Rest,
                        partial_headers=[]},
    {request, Method, URI, Version, NState};
parse_version(_, _, _, _) ->
     {error, bad_request}.

%% @doc fetch all headers
parse_headers(#hparser{partial_headers=Headers}=St) ->
    case parse_header(St) of
        {more, St2} ->
            {more, St2};
        {headers_complete, St2} ->
            {headers_complete, St2};
        {header, KV, St2} ->
            {header, KV, St2};
        {error, Reason, Acc} ->
            {error, {Reason, {Acc, Headers}}}
    end.


parse_header(#hparser{buffer=Buf}=St) ->
    case binary:split(Buf, <<"\r\n">>) of
        [<<>>, Rest] ->
            {headers_complete, St#hparser{buffer=Rest,
                                          state=on_body}};
        [<< " ", Line/binary >>, Rest] ->
            NewBuf = iolist_to_binary([Line, Rest]),
            parse_header(St#hparser{buffer=NewBuf});
        [<< "\t", Line/binary >>, Rest] ->
            NewBuf = iolist_to_binary([Line, Rest]),
            parse_header(St#hparser{buffer=NewBuf});
        [Line, Rest]->
            parse_header(Line, St#hparser{buffer=Rest});
        [Buf] ->
            {more, St}
    end.


parse_header(Line, St) ->
    [Key, Value] = case binary:split(Line, <<": ">>, [trim]) of
        [K] -> [K, <<>>];
        [K, V] -> [K, V]
    end,
    St1 = case hackney_bstr:to_lower(Key) of
        <<"content-length">> ->
            CLen = list_to_integer(binary_to_list(Value)),
            St#hparser{clen=CLen};
        <<"transfer-encoding">> ->
            St#hparser{te=hackney_bstr:to_lower(Value)};
        <<"connection">> ->
            St#hparser{connection=hackney_bstr:to_lower(Value)};
        <<"content-type">> ->
            St#hparser{ctype=hackney_bstr:to_lower(Value)};
        <<"location">> ->
            St#hparser{location=Value};
        _ ->
           St
    end,
    {header, {Key, Value}, St1}.


parse_trailers(St, Acc) ->
    case parse_headers(St) of
        {header, Header, St2} -> parse_trailers(St2, [Header | Acc]);
        {headers_complete, St2} -> {ok, lists:reverse(Acc), St2};
        _ -> error
    end.

parse_body(St=#hparser{body_state=waiting, te=TE, clen=Length,
                       method=Method, buffer=Buffer}) ->
	case TE of
		<<"chunked">> ->
			parse_body(St#hparser{body_state=
				{stream, fun te_chunked/2, {0, 0}, fun ce_identity/1}});
		_ when Length =:= 0 orelse Method =:= <<"HEAD">> ->
            {done, Buffer};
        _ ->
		    parse_body(St#hparser{body_state=
						{stream, fun te_identity/2, {0, Length},
						 fun ce_identity/1}})
	end;
parse_body(#hparser{body_state=done, buffer=Buffer}) ->
	{done, Buffer};
parse_body(St=#hparser{buffer=Buffer, body_state={stream, _, _, _}})
		when byte_size(Buffer) > 0 ->
	transfer_decode(Buffer, St#hparser{buffer= <<>>});
parse_body(St) ->
    {more, St, <<>>}.


-spec transfer_decode(binary(), #hparser{})
                     -> {ok, binary(), #hparser{}} | {error, atom()}.
transfer_decode(Data, St=#hparser{
                body_state={stream, TransferDecode,
                                    TransferState, ContentDecode},
                buffer=Buf}) ->
    case TransferDecode(Data, TransferState) of
        {ok, Data2, TransferState2} ->
            content_decode(ContentDecode, Data2,
                           St#hparser{body_state= {stream,
                                                   TransferDecode,
                                                   TransferState2,
                                                   ContentDecode}});
        {ok, Data2, Rest, TransferState2} ->
            content_decode(ContentDecode, Data2,
                           St#hparser{buffer=Rest,
                                      body_state={stream,
                                                  TransferDecode,
                                                  TransferState2,
                                                  ContentDecode}});
        {chunk_done, Rest} ->
            case parse_trailers(St#hparser{buffer=Rest}, []) of
                {ok, _Trailers, #hparser{buffer=Rest1}} ->
                    {done, Rest1};
                _ ->
                    {done, Rest}
            end;
        {chunk_ok, Chunk, Rest} ->
            {ok, Chunk, St#hparser{buffer=Rest}};
        more ->
            {more, St#hparser{buffer=Data}, Buf};
        {done, Rest} ->
            {done, Rest};
        {done, Data2, _Rest} ->
            content_decode(ContentDecode, Data2,
                           St#hparser{body_state=done});
        {done, Data2, _Length, Rest} ->
            content_decode(ContentDecode, Data2, St#hparser{buffer=Rest,
                                                            body_state=done});
        done ->
            {done, <<>>};
        {error, Reason} ->
            {error, Reason}
    end.


-spec content_decode(fun(), binary(), #hparser{})
	-> {ok, binary(), #hparser{}} | {error, atom()}.
content_decode(ContentDecode, Data, St) ->
	case ContentDecode(Data) of
        {ok, Data2} -> {ok, Data2, St};
		{error, Reason} -> {error, Reason}
	end.


%% @doc Decode a stream of chunks.
-spec te_chunked(binary(), any())
    -> more | {ok, binary(), {non_neg_integer(), non_neg_integer()}}
    | {ok, binary(), binary(),  {non_neg_integer(), non_neg_integer()}}
    | {done, non_neg_integer(), binary()} | {error, badarg}.
te_chunked(<<>>, _) ->
    done;
te_chunked(Data, _) ->
    case read_size(Data) of
        {ok, 0, Rest} ->
            {chunk_done, Rest};
        {ok, Size, Rest} ->
            case read_chunk(Rest, Size) of
                {ok, Chunk, Rest1} ->
                    {chunk_ok, Chunk, Rest1};
                eof ->
                    more
            end;
        eof ->
            more
    end.

%% @doc Decode an identity stream.
-spec te_identity(binary(), {non_neg_integer(), non_neg_integer()})
	-> {ok, binary(), {non_neg_integer(), non_neg_integer()}}
	| {done, binary(), non_neg_integer(), binary()}.
te_identity(Data, {Streamed, Total})
        when (Streamed + byte_size(Data)) < Total ->
	{ok, Data, {Streamed + byte_size(Data), Total}};
te_identity(Data, {Streamed, Total}) ->
	Size = Total - Streamed,
	<< Data2:Size/binary, Rest/binary >> = Data,
	{done, Data2, Total, Rest}.

%% @doc Decode an identity content.
-spec ce_identity(binary()) -> {ok, binary()}.
ce_identity(Data) ->
	{ok, Data}.

read_size(Data) ->
    case read_size(Data, [], true) of
        {ok, Line, Rest} ->
            case io_lib:fread("~16u", Line) of
                {ok, [Size], []} ->
                    {ok, Size, Rest};
                _ ->
                    {error, {poorly_formatted_size, Line}}
            end;
        Err ->
            Err
    end.

read_size(<<>>, _, _) ->
    eof;

read_size(<<"\r\n", Rest/binary>>, Acc, _) ->
    {ok, lists:reverse(Acc), Rest};

read_size(<<$;, Rest/binary>>, Acc, _) ->
    read_size(Rest, Acc, false);

read_size(<<C, Rest/binary>>, Acc, AddToAcc) ->
    case AddToAcc of
        true ->
            read_size(Rest, [C|Acc], AddToAcc);
        false ->
            read_size(Rest, Acc, AddToAcc)
    end.

read_chunk(Data, Size) ->
    case Data of
        <<Chunk:Size/binary, "\r\n", Rest/binary>> ->
            {ok, Chunk, Rest};
        <<_Chunk:Size/binary, _Rest/binary>> when size(_Rest) >= 2 ->
            {error, poorly_formatted_chunked_size};
        _ ->
            eof
    end.

  @private

parse_options([], St) ->
    St;
parse_options([auto | Rest], St) ->
    parse_options(Rest, St#hparser{type=auto});
parse_options([request | Rest], St) ->
    parse_options(Rest, St#hparser{type=request});
parse_options([response | Rest], St) ->
    parse_options(Rest, St#hparser{type=response});
parse_options([{max_line_length, MaxLength} | Rest], St) ->
    parse_options(Rest, St#hparser{max_line_length=MaxLength});
parse_options([{max_empty_lines, MaxEmptyLines} | Rest], St) ->
    parse_options(Rest, St#hparser{max_empty_lines=MaxEmptyLines});
parse_options([_ | Rest], St) ->
    parse_options(Rest, St).

get_property(buffer, #hparser{buffer=Buffer}) ->
    Buffer;
get_property(state, #hparser{state=State}) ->
    State;
get_property(version, #hparser{version=Version}) ->
    Version;
get_property(method, #hparser{method=Method}) ->
    Method;
get_property(transfer_encoding, #hparser{te=TE}) ->
    TE;
get_property(content_length, #hparser{clen=CLen}) ->
    CLen;
get_property(connection, #hparser{connection=Connection}) ->
    Connection;
get_property(content_type, #hparser{ctype=CType}) ->
    CType;
get_property(location, #hparser{location=Location}) ->
    Location.

 -*- erlang -*-

 See the NOTICE for more information.


 This parser is able to parse HTTP responses and requests in a
 streaming fashion. If not set it will be autodetect the type of
 binary parsed, if it's a request or a response.

 Internally it is keeping a buffer for intermediary steps but don't
 keep any state in memory.


 or `hackney_http:parser/1' you will receive an opaque record You can
 then process it using the function `hackney_http:execute/2'.

 Each steps will return the status, some data and the new parser that
 you can process later with `hackney_http:execute/2' when
 `{more, ...}'  is returnned or `hackney_http:execute/1' in other
 cases:

 - `{request, http_version(), http_method(), uri(), parser()}':
 - `{more, parser()}': when the parser need more
 data. The new data should be passed to `hackney_http:execute/2' with
 the new parser() state received.
 - `{header, {Name :: binary(), Value :: binary()}, parser()}':
 when an header has been parsed. To continue the parsing you must
 call the given `parser()' with `hackney_http:execute/1'.
 - `{headers_complete, parser()}' : when all headers have been parsed.
 To continue the parsing you must call the given `parser()' state
 with `hackney_http:execute/1'.
 - `{more, parser(), binary()}': on body, when
 the parser need more data. The new data should be passed to
 `hackney_http:execute/2' (with `parser()' ) when received. The binary at the end of the
 tuple correspond to the actual buffer of the parser. It may be used
 for other purpose, like start to parse a new request on pipeline
 connections, for a proxy...
 - `{ok, binary(), parser()}': on body, when a chunk has been
 parsed. To continue the parsing you must call
 `hackney_http:execute/1' with the given `parser()'.
 - `{done, binary()}': when the parsing is done. The binary
 - `{error, term{}}': when an error happen
 @doc Create a new HTTP parser. The parser will autodetect if the parded
 binary is a response or a request.
 @doc create a new HTTP parser with options. By default the type of
 parsed binary will be detected.

 Available options:
 <ul>
   request (default).</li>
   <li>`response': set the parser to parse a response</li>
   <li>`request': set the parser to parse a request (server)</li>
   before we give up.</li>
 </ul>
 @doc retrieve a parser property.
 Properties are:
  - `buffer': internal buffer of the parser (non parsed)
  - `version': HTTP version
  - `transfer_encoding': transfer encoding header if any
  - `content_type': content type header if any
  - `location': location header if any
  - `connection': connection header if any.
 @doc Execute the parser with the current buffer.
 @doc Execute the parser with the new buffer
 update the state with the new buffer
 process the right state.
 Empty lines must be using \r\n.
 reading from the socket and eventually crashing.
 @doc parse status
 @doc fetch all headers
 @doc Decode a stream of chunks.
 @doc Decode an identity stream.
 @doc Decode an identity content.
  This file is part of  released under the Apache 2 license .
  Copyright ( c ) 2011 - 2012 ,   <  >
  Copyright ( c ) 2013 - 2014  
  @doc HTTP parser in pure Erlang
  The first time you initialise a parser using ` hackney_http : parser/0 '
  - ` { response , http_version ( ) , status ( ) ,  ( ) , parser ( ) } ' :
  when the first line of a response is parsed
  when the first line of a request ( on servers ) is parsed
  given  to the non parsed part of the internal buffer .

-module(hackney_http).

-export([parser/0, parser/1]).
-export([execute/1, execute/2]).
-export([get/2]).
-export([parse_response_version/2]).

-include("hackney_lib.hrl").


-opaque parser() :: #hparser{}.
-export_type([parser/0]).

-type http_version() :: {integer(), integer()}.
-type status() :: integer().
-type http_reason() :: binary().
-type http_method() :: binary().
-type uri() :: binary().
-type body_result() :: {more, parser(), binary()}
    | {ok, binary(), parser()}
    | {done, binary()}.

-type parser_result() ::
    {response, http_version(), status(), http_reason(), parser()}
    | {request, http_version(), http_method(), uri(), parser()}
    | {more, parser()}
    | body_result()
    | {error, term()}.

-type parser_option() :: request | response | auto
    | {max_empty_lines, integer()}
    | {max_line_length, integer()}.

-type parser_options() :: [parser_option()].


-spec parser() -> parser().
parser() ->
    parser([]).

    < li>`auto ' : autodetect if the binary parsed is a response or a
    < li>`{max_line_lenght ,  } ' : set the maximum size of a line parsed
    < li>`{max_lines_empty ,  } ' : the maximum number of empty line we
    accept before the first line happen</li >
-spec parser(parser_options()) -> parser().
parser(Options) ->
    parse_options(Options, #hparser{}).

   - ` state ' : the current state ( on_status , on_header , on_body , done )
   - ` content_length ' : content length header if any
-spec get(parser(), atom() | [atom()]) -> any().
get(Parser, Props) when is_list(Props) ->
    [get_property(P, Parser) || P <- Props];
get(Parser, Prop) ->
    get_property(Prop, Parser).

-spec execute(#hparser{}) -> parser_result().
execute(St) ->
    execute(St, <<>>).

-spec execute(#hparser{}, binary()) -> parser_result().
execute(#hparser{state=Status, buffer=Buffer}=St, Bin) ->
    NBuffer = << Buffer/binary, Bin/binary >>,
    St1 = St#hparser{buffer=NBuffer},

    case Status of
        done -> done;
        on_first_line -> parse_first_line(NBuffer, St1, 0);
        on_header -> parse_headers(St1);
        on_body -> parse_body(St1)
    end.

parse_first_line(<< $\n, _/binary >>, _St, _) ->
    {error, badarg};
  We limit the length of the first - line to MaxLength to avoid endlessly
parse_first_line(Buffer, St=#hparser{type=Type,
                                     max_line_length=MaxLength,
                                     max_empty_lines=MaxEmpty}, Empty) ->
    case match_eol(Buffer, 0) of
        nomatch when byte_size(Buffer) > MaxLength ->
            {error, line_too_long};
        nomatch ->
            {more, St#hparser{empty_lines=Empty}};
        1 when Empty =:= MaxEmpty ->
            {error, bad_request};
        1 ->
            << _:16, Rest/binary >> = Buffer,
            parse_first_line(Rest, St, Empty + 1);
        _ when Type =:= auto ->
            case parse_request_line(St) of
                {error, bad_request} ->
                    case parse_response_line(St) of
                        {error, bad_request} = Error ->
                            Error;
                        OK ->
                            OK
                    end;
                OK ->
                    OK
            end;
        _ when Type =:= response ->
            parse_response_line(St);
        _ when Type =:= request ->
            parse_request_line(St)
    end.

match_eol(<< $\n, _/bits >>, N) ->
    N;
match_eol(<< _, Rest/bits >>, N) ->
    match_eol(Rest, N + 1);
match_eol(_, _) ->
    nomatch.

parse_response_line(#hparser{buffer=Buf}=St) ->
    case binary:split(Buf, <<"\r\n">>) of
        [Line, Rest] ->
            parse_response_version(Line, St#hparser{buffer=Rest});
        _ ->
            {error, bad_request}
    end.


parse_response_version(<< "HTTP/", High, ".", Low, $\s, Rest/binary >>, St)
        when High >= $0, High =< $9, Low >= $0, Low =< $9 ->
    Version = { High -$0, Low - $0},
    parse_status(Rest, St, Version, <<>>);
parse_response_version(_, _) ->
     {error, bad_request}.

parse_status(<< C, Rest/bits >>, St, Version, Acc) ->
    case C of
        $\r ->  {error, bad_request};
        $\s -> parse_reason(Rest, St, Version, Acc);
        _ -> parse_status(Rest, St, Version, << Acc/binary, C >>)
    end.

parse_reason(Reason, St, Version, StatusCode) ->
    StatusInt = list_to_integer(binary_to_list(StatusCode)),

    NState = St#hparser{type=response,
                        version=Version,
                        state=on_header,
                        partial_headers=[]},
    {response, Version, StatusInt, Reason, NState}.


parse_request_line(#hparser{buffer=Buf}=St) ->
    parse_method(Buf, St, <<>>).

parse_method(<< C, Rest/bits >>, St, Acc) ->
    case C of
        $\r ->  {error, bad_request};
        $\s -> parse_uri(Rest, St, Acc);
        _ -> parse_method(Rest, St, << Acc/binary, C >>)
    end.

parse_uri(<< $\r, _/bits >>, _St, _) ->
    {error, bad_request};
parse_uri(<< "* ", Rest/bits >>, St, Method) ->
    parse_version(Rest, St, Method, <<"*">>);
parse_uri(Buffer, St, Method) ->
    parse_uri_path(Buffer, St, Method, <<>>).

parse_uri_path(<< C, Rest/bits >>, St, Method, Acc) ->
    case C of
        $\r -> {error, bad_request};
        $\s -> parse_version(Rest, St, Method, Acc);
        _ -> parse_uri_path(Rest, St, Method, << Acc/binary, C >>)
    end.

parse_version(<< "HTTP/", High, ".", Low, Rest/binary >>, St, Method, URI)
        when High >= $0, High =< $9, Low >= $0, Low =< $9 ->
    Version = { High -$0, Low - $0},

    NState = St#hparser{type=request,
                        version=Version,
                        method=Method,
                        state=on_header,
                        buffer=Rest,
                        partial_headers=[]},
    {request, Method, URI, Version, NState};
parse_version(_, _, _, _) ->
     {error, bad_request}.

parse_headers(#hparser{partial_headers=Headers}=St) ->
    case parse_header(St) of
        {more, St2} ->
            {more, St2};
        {headers_complete, St2} ->
            {headers_complete, St2};
        {header, KV, St2} ->
            {header, KV, St2};
        {error, Reason, Acc} ->
            {error, {Reason, {Acc, Headers}}}
    end.


parse_header(#hparser{buffer=Buf}=St) ->
    case binary:split(Buf, <<"\r\n">>) of
        [<<>>, Rest] ->
            {headers_complete, St#hparser{buffer=Rest,
                                          state=on_body}};
        [<< " ", Line/binary >>, Rest] ->
            NewBuf = iolist_to_binary([Line, Rest]),
            parse_header(St#hparser{buffer=NewBuf});
        [<< "\t", Line/binary >>, Rest] ->
            NewBuf = iolist_to_binary([Line, Rest]),
            parse_header(St#hparser{buffer=NewBuf});
        [Line, Rest]->
            parse_header(Line, St#hparser{buffer=Rest});
        [Buf] ->
            {more, St}
    end.


parse_header(Line, St) ->
    [Key, Value] = case binary:split(Line, <<": ">>, [trim]) of
        [K] -> [K, <<>>];
        [K, V] -> [K, V]
    end,
    St1 = case hackney_bstr:to_lower(Key) of
        <<"content-length">> ->
            CLen = list_to_integer(binary_to_list(Value)),
            St#hparser{clen=CLen};
        <<"transfer-encoding">> ->
            St#hparser{te=hackney_bstr:to_lower(Value)};
        <<"connection">> ->
            St#hparser{connection=hackney_bstr:to_lower(Value)};
        <<"content-type">> ->
            St#hparser{ctype=hackney_bstr:to_lower(Value)};
        <<"location">> ->
            St#hparser{location=Value};
        _ ->
           St
    end,
    {header, {Key, Value}, St1}.


parse_trailers(St, Acc) ->
    case parse_headers(St) of
        {header, Header, St2} -> parse_trailers(St2, [Header | Acc]);
        {headers_complete, St2} -> {ok, lists:reverse(Acc), St2};
        _ -> error
    end.

parse_body(St=#hparser{body_state=waiting, te=TE, clen=Length,
                       method=Method, buffer=Buffer}) ->
	case TE of
		<<"chunked">> ->
			parse_body(St#hparser{body_state=
				{stream, fun te_chunked/2, {0, 0}, fun ce_identity/1}});
		_ when Length =:= 0 orelse Method =:= <<"HEAD">> ->
            {done, Buffer};
        _ ->
		    parse_body(St#hparser{body_state=
						{stream, fun te_identity/2, {0, Length},
						 fun ce_identity/1}})
	end;
parse_body(#hparser{body_state=done, buffer=Buffer}) ->
	{done, Buffer};
parse_body(St=#hparser{buffer=Buffer, body_state={stream, _, _, _}})
		when byte_size(Buffer) > 0 ->
	transfer_decode(Buffer, St#hparser{buffer= <<>>});
parse_body(St) ->
    {more, St, <<>>}.


-spec transfer_decode(binary(), #hparser{})
                     -> {ok, binary(), #hparser{}} | {error, atom()}.
transfer_decode(Data, St=#hparser{
                body_state={stream, TransferDecode,
                                    TransferState, ContentDecode},
                buffer=Buf}) ->
    case TransferDecode(Data, TransferState) of
        {ok, Data2, TransferState2} ->
            content_decode(ContentDecode, Data2,
                           St#hparser{body_state= {stream,
                                                   TransferDecode,
                                                   TransferState2,
                                                   ContentDecode}});
        {ok, Data2, Rest, TransferState2} ->
            content_decode(ContentDecode, Data2,
                           St#hparser{buffer=Rest,
                                      body_state={stream,
                                                  TransferDecode,
                                                  TransferState2,
                                                  ContentDecode}});
        {chunk_done, Rest} ->
            case parse_trailers(St#hparser{buffer=Rest}, []) of
                {ok, _Trailers, #hparser{buffer=Rest1}} ->
                    {done, Rest1};
                _ ->
                    {done, Rest}
            end;
        {chunk_ok, Chunk, Rest} ->
            {ok, Chunk, St#hparser{buffer=Rest}};
        more ->
            {more, St#hparser{buffer=Data}, Buf};
        {done, Rest} ->
            {done, Rest};
        {done, Data2, _Rest} ->
            content_decode(ContentDecode, Data2,
                           St#hparser{body_state=done});
        {done, Data2, _Length, Rest} ->
            content_decode(ContentDecode, Data2, St#hparser{buffer=Rest,
                                                            body_state=done});
        done ->
            {done, <<>>};
        {error, Reason} ->
            {error, Reason}
    end.


-spec content_decode(fun(), binary(), #hparser{})
	-> {ok, binary(), #hparser{}} | {error, atom()}.
content_decode(ContentDecode, Data, St) ->
	case ContentDecode(Data) of
        {ok, Data2} -> {ok, Data2, St};
		{error, Reason} -> {error, Reason}
	end.


-spec te_chunked(binary(), any())
    -> more | {ok, binary(), {non_neg_integer(), non_neg_integer()}}
    | {ok, binary(), binary(),  {non_neg_integer(), non_neg_integer()}}
    | {done, non_neg_integer(), binary()} | {error, badarg}.
te_chunked(<<>>, _) ->
    done;
te_chunked(Data, _) ->
    case read_size(Data) of
        {ok, 0, Rest} ->
            {chunk_done, Rest};
        {ok, Size, Rest} ->
            case read_chunk(Rest, Size) of
                {ok, Chunk, Rest1} ->
                    {chunk_ok, Chunk, Rest1};
                eof ->
                    more
            end;
        eof ->
            more
    end.

-spec te_identity(binary(), {non_neg_integer(), non_neg_integer()})
	-> {ok, binary(), {non_neg_integer(), non_neg_integer()}}
	| {done, binary(), non_neg_integer(), binary()}.
te_identity(Data, {Streamed, Total})
        when (Streamed + byte_size(Data)) < Total ->
	{ok, Data, {Streamed + byte_size(Data), Total}};
te_identity(Data, {Streamed, Total}) ->
	Size = Total - Streamed,
	<< Data2:Size/binary, Rest/binary >> = Data,
	{done, Data2, Total, Rest}.

-spec ce_identity(binary()) -> {ok, binary()}.
ce_identity(Data) ->
	{ok, Data}.

read_size(Data) ->
    case read_size(Data, [], true) of
        {ok, Line, Rest} ->
            case io_lib:fread("~16u", Line) of
                {ok, [Size], []} ->
                    {ok, Size, Rest};
                _ ->
                    {error, {poorly_formatted_size, Line}}
            end;
        Err ->
            Err
    end.

read_size(<<>>, _, _) ->
    eof;

read_size(<<"\r\n", Rest/binary>>, Acc, _) ->
    {ok, lists:reverse(Acc), Rest};

read_size(<<$;, Rest/binary>>, Acc, _) ->
    read_size(Rest, Acc, false);

read_size(<<C, Rest/binary>>, Acc, AddToAcc) ->
    case AddToAcc of
        true ->
            read_size(Rest, [C|Acc], AddToAcc);
        false ->
            read_size(Rest, Acc, AddToAcc)
    end.

read_chunk(Data, Size) ->
    case Data of
        <<Chunk:Size/binary, "\r\n", Rest/binary>> ->
            {ok, Chunk, Rest};
        <<_Chunk:Size/binary, _Rest/binary>> when size(_Rest) >= 2 ->
            {error, poorly_formatted_chunked_size};
        _ ->
            eof
    end.

  @private

parse_options([], St) ->
    St;
parse_options([auto | Rest], St) ->
    parse_options(Rest, St#hparser{type=auto});
parse_options([request | Rest], St) ->
    parse_options(Rest, St#hparser{type=request});
parse_options([response | Rest], St) ->
    parse_options(Rest, St#hparser{type=response});
parse_options([{max_line_length, MaxLength} | Rest], St) ->
    parse_options(Rest, St#hparser{max_line_length=MaxLength});
parse_options([{max_empty_lines, MaxEmptyLines} | Rest], St) ->
    parse_options(Rest, St#hparser{max_empty_lines=MaxEmptyLines});
parse_options([_ | Rest], St) ->
    parse_options(Rest, St).

get_property(buffer, #hparser{buffer=Buffer}) ->
    Buffer;
get_property(state, #hparser{state=State}) ->
    State;
get_property(version, #hparser{version=Version}) ->
    Version;
get_property(method, #hparser{method=Method}) ->
    Method;
get_property(transfer_encoding, #hparser{te=TE}) ->
    TE;
get_property(content_length, #hparser{clen=CLen}) ->
    CLen;
get_property(connection, #hparser{connection=Connection}) ->
    Connection;
get_property(content_type, #hparser{ctype=CType}) ->
    CType;
get_property(location, #hparser{location=Location}) ->
    Location.
