From 2f35af5b0a67f591dde5ae4305a9587988a37c0c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lo=C3=AFc=20Hoguin?= Date: Wed, 4 Sep 2013 18:13:39 +0200 Subject: Introduce cow_http support module This module contains parsing functions for the basic HTTP items, basically request-line, response-line and host. --- Makefile | 1 + src/cow_http.erl | 128 +++++++++++++++++++++++++++++++++++++++++++++++++++ test/eunit_SUITE.erl | 31 +++++++++++++ 3 files changed, 160 insertions(+) create mode 100644 src/cow_http.erl create mode 100644 test/eunit_SUITE.erl diff --git a/Makefile b/Makefile index 3b44c01..29a2f7e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,6 @@ # See LICENSE for licensing information. PROJECT = cowlib +CT_SUITES = eunit include erlang.mk diff --git a/src/cow_http.erl b/src/cow_http.erl new file mode 100644 index 0000000..704933b --- /dev/null +++ b/src/cow_http.erl @@ -0,0 +1,128 @@ +%% Copyright (c) 2013, Loïc Hoguin +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(cow_http). + +-export([parse_fullhost/1]). +-export([parse_fullpath/1]). +-export([parse_version/1]). + +%% @doc Extract host and port from a binary. +%% +%% Because the hostname is case insensitive it is converted +%% to lowercase. + +-spec parse_fullhost(binary()) -> {binary(), undefined | non_neg_integer()}. +parse_fullhost(Fullhost) -> + parse_fullhost(Fullhost, false, <<>>). + +parse_fullhost(<< $[, Rest/bits >>, false, <<>>) -> + parse_fullhost(Rest, true, << $[ >>); +parse_fullhost(<<>>, false, Acc) -> + {Acc, undefined}; +parse_fullhost(<< $:, Rest/bits >>, false, Acc) -> + {Acc, list_to_integer(binary_to_list(Rest))}; +parse_fullhost(<< $], Rest/bits >>, true, Acc) -> + parse_fullhost(Rest, false, << Acc/binary, $] >>); +parse_fullhost(<< C, Rest/bits >>, E, Acc) -> + case C of + $A -> parse_fullhost(Rest, E, << Acc/binary, $a >>); + $B -> parse_fullhost(Rest, E, << Acc/binary, $b >>); + $C -> parse_fullhost(Rest, E, << Acc/binary, $c >>); + $D -> parse_fullhost(Rest, E, << Acc/binary, $d >>); + $E -> parse_fullhost(Rest, E, << Acc/binary, $e >>); + $F -> parse_fullhost(Rest, E, << Acc/binary, $f >>); + $G -> parse_fullhost(Rest, E, << Acc/binary, $g >>); + $H -> parse_fullhost(Rest, E, << Acc/binary, $h >>); + $I -> parse_fullhost(Rest, E, << Acc/binary, $i >>); + $J -> parse_fullhost(Rest, E, << Acc/binary, $j >>); + $K -> parse_fullhost(Rest, E, << Acc/binary, $k >>); + $L -> parse_fullhost(Rest, E, << Acc/binary, $l >>); + $M -> parse_fullhost(Rest, E, << Acc/binary, $m >>); + $N -> parse_fullhost(Rest, E, << Acc/binary, $n >>); + $O -> parse_fullhost(Rest, E, << Acc/binary, $o >>); + $P -> parse_fullhost(Rest, E, << Acc/binary, $p >>); + $Q -> parse_fullhost(Rest, E, << Acc/binary, $q >>); + $R -> parse_fullhost(Rest, E, << Acc/binary, $r >>); + $S -> parse_fullhost(Rest, E, << Acc/binary, $s >>); + $T -> parse_fullhost(Rest, E, << Acc/binary, $t >>); + $U -> parse_fullhost(Rest, E, << Acc/binary, $u >>); + $V -> parse_fullhost(Rest, E, << Acc/binary, $v >>); + $W -> parse_fullhost(Rest, E, << Acc/binary, $w >>); + $X -> parse_fullhost(Rest, E, << Acc/binary, $x >>); + $Y -> parse_fullhost(Rest, E, << Acc/binary, $y >>); + $Z -> parse_fullhost(Rest, E, << Acc/binary, $z >>); + _ -> parse_fullhost(Rest, E, << Acc/binary, C >>) + end. + +-ifdef(TEST). +parse_fullhost_test() -> + {<<"example.org">>, 8080} = parse_fullhost(<<"example.org:8080">>), + {<<"example.org">>, undefined} = parse_fullhost(<<"example.org">>), + {<<"192.0.2.1">>, 8080} = parse_fullhost(<<"192.0.2.1:8080">>), + {<<"192.0.2.1">>, undefined} = parse_fullhost(<<"192.0.2.1">>), + {<<"[2001:db8::1]">>, 8080} = parse_fullhost(<<"[2001:db8::1]:8080">>), + {<<"[2001:db8::1]">>, undefined} = parse_fullhost(<<"[2001:db8::1]">>), + {<<"[::ffff:192.0.2.1]">>, 8080} + = parse_fullhost(<<"[::ffff:192.0.2.1]:8080">>), + {<<"[::ffff:192.0.2.1]">>, undefined} + = parse_fullhost(<<"[::ffff:192.0.2.1]">>), + ok. +-endif. + +%% @doc Extract path and query string from a binary. + +-spec parse_fullpath(binary()) -> {binary(), binary()}. +parse_fullpath(Fullpath) -> + parse_fullpath(Fullpath, <<>>). + +parse_fullpath(<<>>, Path) -> + {Path, <<>>}; +parse_fullpath(<< $?, Rest/binary >>, Path) -> + parse_fullpath_qs(Rest, Path, <<>>); +parse_fullpath(<< C, Rest/binary >>, SoFar) -> + parse_fullpath(Rest, << SoFar/binary, C >>). + +parse_fullpath_qs(<<>>, Path, Qs) -> + {Path, Qs}; +parse_fullpath_qs(<< C, Rest/binary >>, Path, SoFar) -> + parse_fullpath_qs(Rest, Path, << SoFar/binary, C >>). + +-ifdef(TEST). +parse_fullpath_test() -> + {<<"*">>, <<>>} = parse_fullpath(<<"*">>), + {<<"/">>, <<>>} = parse_fullpath(<<"/">>), + {<<"/path/to/resource">>, <<>>} = parse_fullpath(<<"/path/to/resource">>), + {<<"/">>, <<>>} = parse_fullpath(<<"/?">>), + {<<"/">>, <<"q=cowboy">>} = parse_fullpath(<<"/?q=cowboy">>), + {<<"/path/to/resource">>, <<"q=cowboy">>} + = parse_fullpath(<<"/path/to/resource?q=cowboy">>), + ok. +-endif. + +%% @doc Convert an HTTP version to atom. + +-spec parse_version(binary()) -> 'HTTP/1.1' | 'HTTP/1.0'. +parse_version(<<"HTTP/1.1">>) -> + 'HTTP/1.1'; +parse_version(<<"HTTP/1.0">>) -> + 'HTTP/1.0'. + +-ifdef(TEST). +parse_version_test() -> + 'HTTP/1.1' = parse_version(<<"HTTP/1.1">>), + 'HTTP/1.0' = parse_version(<<"HTTP/1.0">>), + {'EXIT', _} = (catch parse_version(<<"HTTP/1.2">>)), + ok. +-endif. diff --git a/test/eunit_SUITE.erl b/test/eunit_SUITE.erl new file mode 100644 index 0000000..e9d9282 --- /dev/null +++ b/test/eunit_SUITE.erl @@ -0,0 +1,31 @@ +%% Copyright (c) 2013, Loïc Hoguin +%% +%% Permission to use, copy, modify, and/or distribute this software for any +%% purpose with or without fee is hereby granted, provided that the above +%% copyright notice and this permission notice appear in all copies. +%% +%% THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES +%% WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF +%% MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR +%% ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +%% WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN +%% ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF +%% OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + +-module(eunit_SUITE). + +-include_lib("common_test/include/ct.hrl"). + +%% ct. +-export([all/0]). + +%% Tests. +-export([eunit/1]). + +%% ct. + +all() -> + [eunit]. + +eunit(_) -> + ok = eunit:test({application, cowlib}). -- cgit v1.2.3