|
|
@ -1,267 +0,0 @@ |
|
|
|
-module(http_lib). |
|
|
|
-export([etag/1, |
|
|
|
local_time_to_rfc1123/1, rfc1123_to_date_time/1, mime_type/1, |
|
|
|
mime_type/2, extension/1, is_compressible/1, month_to_list/1, |
|
|
|
url_decode/1, chunk/1, |
|
|
|
accept/1, recv/2, recv/3, send/2, setopts/2, close/1, peername/1, |
|
|
|
dir/1, is_idempotent/1, is_modified/2, |
|
|
|
reason_phrase/1]). |
|
|
|
|
|
|
|
-include_lib("kernel/include/file.hrl"). |
|
|
|
-include_lib("eunit/include/eunit.hrl"). |
|
|
|
|
|
|
|
%% -------------------------------------------------------------------- |
|
|
|
%% Macros |
|
|
|
%% -------------------------------------------------------------------- |
|
|
|
-define(CS_DEBUG(F, D), io:format(lists:concat(["D(", ?MODULE, ":", ?LINE, ") :", F, "~n"]), D)). |
|
|
|
|
|
|
|
any_to_list(A) when is_atom(A) -> |
|
|
|
atom_to_list(A); |
|
|
|
any_to_list(I) when is_integer(I) -> |
|
|
|
integer_to_list(I); |
|
|
|
any_to_list(L) when is_list(L) -> |
|
|
|
L. |
|
|
|
|
|
|
|
chunk(IoList) -> |
|
|
|
[erlang:integer_to_list(iolist_size(IoList), 16), "\r\n", IoList, "\r\n"]. |
|
|
|
|
|
|
|
local_time_to_rfc1123(LocalTime) -> |
|
|
|
[{{YYYY, MM, DD}, {H, M, S}} | _] = |
|
|
|
calendar:local_time_to_universal_time_dst(LocalTime), |
|
|
|
Day = calendar:day_of_the_week({YYYY, MM, DD}), |
|
|
|
lists:flatten( |
|
|
|
io_lib:format("~s, ~2.2.0w ~3.s ~4.4.0w ~2.2.0w:~2.2.0w:~2.2.0w GMT", |
|
|
|
[day(Day), DD, month_to_list(MM), YYYY, H, M, S])). |
|
|
|
|
|
|
|
rfc1123_to_date_time([_D, _A, _Y, $,, $ , D1, D2, $ , M, O, N, $ , Y1, Y2, Y3, Y4, $ , |
|
|
|
H1, H2, $:, M1, M2, $:, S1, S2 | _]) -> |
|
|
|
Year = list_to_integer([Y1, Y2, Y3, Y4]), |
|
|
|
Day = list_to_integer([D1, D2]), |
|
|
|
Month = list_to_month([M, O, N]), |
|
|
|
Hour = list_to_integer([H1, H2]), |
|
|
|
Min = list_to_integer([M1, M2]), |
|
|
|
Sec = list_to_integer([S1, S2]), |
|
|
|
{{Year, Month, Day}, {Hour, Min, Sec}}. |
|
|
|
|
|
|
|
day(1) -> "Mon"; |
|
|
|
day(2) -> "Tue"; |
|
|
|
day(3) -> "Wed"; |
|
|
|
day(4) -> "Thu"; |
|
|
|
day(5) -> "Fri"; |
|
|
|
day(6) -> "Sat"; |
|
|
|
day(7) -> "Sun". |
|
|
|
|
|
|
|
month_to_list(1) -> "Jan"; |
|
|
|
month_to_list(2) -> "Feb"; |
|
|
|
month_to_list(3) -> "Mar"; |
|
|
|
month_to_list(4) -> "Apr"; |
|
|
|
month_to_list(5) -> "May"; |
|
|
|
month_to_list(6) -> "Jun"; |
|
|
|
month_to_list(7) -> "Jul"; |
|
|
|
month_to_list(8) -> "Aug"; |
|
|
|
month_to_list(9) -> "Sep"; |
|
|
|
month_to_list(10) -> "Oct"; |
|
|
|
month_to_list(11) -> "Nov"; |
|
|
|
month_to_list(12) -> "Dec". |
|
|
|
|
|
|
|
list_to_month("Jan") -> 1; |
|
|
|
list_to_month("Feb") -> 2; |
|
|
|
list_to_month("Mar") -> 3; |
|
|
|
list_to_month("Apr") -> 4; |
|
|
|
list_to_month("May") -> 5; |
|
|
|
list_to_month("Jun") -> 6; |
|
|
|
list_to_month("Jul") -> 7; |
|
|
|
list_to_month("Aug") -> 8; |
|
|
|
list_to_month("Sep") -> 9; |
|
|
|
list_to_month("Oct") -> 10; |
|
|
|
list_to_month("Nov") -> 11; |
|
|
|
list_to_month("Dec") -> 12. |
|
|
|
|
|
|
|
is_modified(#file_info{mtime = Mtime}, Since) -> |
|
|
|
erlang:localtime_to_universaltime(Mtime) > rfc1123_to_date_time(Since). |
|
|
|
|
|
|
|
is_idempotent('HEAD') -> true; |
|
|
|
is_idempotent('GET') -> true; |
|
|
|
is_idempotent('PUT') -> true; |
|
|
|
is_idempotent('DELETE') -> true; |
|
|
|
is_idempotent('TRACE') -> true; |
|
|
|
is_idempotent('OPTIONS') -> true; |
|
|
|
is_idempotent(_) -> false. |
|
|
|
|
|
|
|
url_decode(URL) -> |
|
|
|
url_decode(URL, []). |
|
|
|
|
|
|
|
url_decode([], Acc) -> |
|
|
|
lists:reverse(Acc); |
|
|
|
url_decode([37, H, L | T], Acc) -> |
|
|
|
url_decode(T, [erlang:list_to_integer([H, L], 16) | Acc]); |
|
|
|
url_decode([$+ | T], Acc) -> |
|
|
|
url_decode(T, [32 | Acc]); |
|
|
|
url_decode([H | T], Acc) -> |
|
|
|
url_decode(T, [H | Acc]). |
|
|
|
|
|
|
|
url_decode_test() -> |
|
|
|
?assertEqual("I am decoded", url_decode("I+am+decoded")), |
|
|
|
?assertEqual("#$%&+=<> /", url_decode("%23%24%25%26%2B%3D%3C%3D%20%2F")). |
|
|
|
|
|
|
|
%% @doc Generates Etag based on file modification time and size. |
|
|
|
%% @spec etag(#file_info{}) -> string() |
|
|
|
etag(#file_info{mtime = MTime, size = Size}) -> |
|
|
|
{{Year, Month, Day}, {Hour, Min, Sec}} = MTime, |
|
|
|
F = fun(X) when X =< 25 -> X + $A; |
|
|
|
(X) when X =< 50 -> X - 26 + $a; |
|
|
|
(X) -> X - 3 |
|
|
|
end, |
|
|
|
SizeStr = integer_to_list(Size), |
|
|
|
lists:map(F, [X rem 60 || X <- [Year, Month, Day, Hour, Min, Sec]]) ++ SizeStr. |
|
|
|
|
|
|
|
mime_type(Path) -> |
|
|
|
mime_type(Path, undefined). |
|
|
|
|
|
|
|
mime_type(Path, Default) -> |
|
|
|
Ext = extension(Path), |
|
|
|
case ets:lookup(http_mime_types, Ext) of |
|
|
|
[{mime_type, Ext, Type, _Compressible} | _] -> |
|
|
|
Type; |
|
|
|
[] -> |
|
|
|
Default |
|
|
|
end. |
|
|
|
|
|
|
|
extension(Path) -> |
|
|
|
case filename:extension(Path) of |
|
|
|
[] -> |
|
|
|
[]; |
|
|
|
Extension -> |
|
|
|
tl(Extension) |
|
|
|
end. |
|
|
|
|
|
|
|
is_compressible(Path) -> |
|
|
|
Ext = extension(Path), |
|
|
|
case ets:lookup(http_mime_types, Ext) of |
|
|
|
[{mime_type, Ext, _Type, Compressible} | _] -> |
|
|
|
Compressible; |
|
|
|
[] -> |
|
|
|
true |
|
|
|
end. |
|
|
|
|
|
|
|
dir(priv_dir) -> |
|
|
|
priv_dir(http); |
|
|
|
dir({priv_dir, Module}) -> |
|
|
|
priv_dir(Module); |
|
|
|
dir({priv_dir, Module, SubDir}) -> |
|
|
|
filename:join([priv_dir(Module), any_to_list(SubDir)]); |
|
|
|
dir({lib_dir, SubDir}) -> |
|
|
|
code:lib_dir(http, SubDir); |
|
|
|
dir({lib_dir, App, SubDir}) -> |
|
|
|
code:lib_dir(App, SubDir); |
|
|
|
dir(Path) when is_list(Path) -> |
|
|
|
Path. |
|
|
|
|
|
|
|
priv_dir(Module) -> |
|
|
|
filename:join(get_base_dir(Module), "priv"). |
|
|
|
|
|
|
|
get_base_dir(Module) -> |
|
|
|
{file, Here} = code:is_loaded(Module), |
|
|
|
filename:dirname(filename:dirname(Here)). |
|
|
|
|
|
|
|
accept(Socket) when element(1, Socket) == sslsocket -> |
|
|
|
case ssl:transport_accept(Socket) of |
|
|
|
{ok, SSLSocket} -> |
|
|
|
case ssl:ssl_accept(SSLSocket) of |
|
|
|
ok -> |
|
|
|
{ok, SSLSocket}; |
|
|
|
Else -> |
|
|
|
Else |
|
|
|
end; |
|
|
|
Else -> |
|
|
|
Else |
|
|
|
end; |
|
|
|
accept(Socket) -> |
|
|
|
gen_tcp:accept(Socket). |
|
|
|
|
|
|
|
recv(Socket, Length) when element(1, Socket) == sslsocket -> |
|
|
|
ssl:recv(Socket, Length); |
|
|
|
recv(Socket, Length) -> |
|
|
|
gen_tcp:recv(Socket, Length). |
|
|
|
|
|
|
|
recv(Socket, Length, Timeout) when element(1, Socket) == sslsocket -> |
|
|
|
ssl:recv(Socket, Length, Timeout); |
|
|
|
recv(Socket, Length, Timeout) -> |
|
|
|
gen_tcp:recv(Socket, Length, Timeout). |
|
|
|
|
|
|
|
send(Socket, Data) when element(1, Socket) == sslsocket -> |
|
|
|
ssl:send(Socket, Data); |
|
|
|
send(Socket, Data) -> |
|
|
|
gen_tcp:send(Socket, Data). |
|
|
|
|
|
|
|
setopts(Socket, Options) when element(1, Socket) == sslsocket -> |
|
|
|
ssl:setopts(Socket, Options); |
|
|
|
setopts(Socket, Options) -> |
|
|
|
inet:setopts(Socket, Options). |
|
|
|
|
|
|
|
close(Socket) when element(1, Socket) == sslsocket -> |
|
|
|
ssl:close(Socket); |
|
|
|
close(Socket) -> |
|
|
|
gen_tcp:close(Socket). |
|
|
|
|
|
|
|
peername(Socket) when element(1, Socket) == sslsocket -> |
|
|
|
ssl:peername(Socket); |
|
|
|
peername(Socket) -> |
|
|
|
gen_tcp:peername(Socket). |
|
|
|
|
|
|
|
%% RFC 2616, HTTP 1.1 Status codes |
|
|
|
reason_phrase(100) -> "Continue"; |
|
|
|
reason_phrase(101) -> "Switching Protocols"; |
|
|
|
reason_phrase(200) -> "OK"; |
|
|
|
reason_phrase(201) -> "Created"; |
|
|
|
reason_phrase(202) -> "Accepted"; |
|
|
|
reason_phrase(203) -> "Non-Authoritative Information"; |
|
|
|
reason_phrase(204) -> "No Content"; |
|
|
|
reason_phrase(205) -> "Reset Content"; |
|
|
|
reason_phrase(206) -> "Partial Content"; |
|
|
|
reason_phrase(300) -> "Multiple Choices"; |
|
|
|
reason_phrase(301) -> "Moved Permanently"; |
|
|
|
reason_phrase(302) -> "Moved Temporarily"; |
|
|
|
reason_phrase(303) -> "See Other"; |
|
|
|
reason_phrase(304) -> "Not Modified"; |
|
|
|
reason_phrase(305) -> "Use Proxy"; |
|
|
|
reason_phrase(306) -> "(unused)"; |
|
|
|
reason_phrase(307) -> "Temporary Redirect"; |
|
|
|
reason_phrase(400) -> "Bad Request"; |
|
|
|
reason_phrase(401) -> "Unauthorized"; |
|
|
|
reason_phrase(402) -> "Payment Required"; |
|
|
|
reason_phrase(403) -> "Forbidden"; |
|
|
|
reason_phrase(404) -> "Object Not Found"; |
|
|
|
reason_phrase(405) -> "Method Not Allowed"; |
|
|
|
reason_phrase(406) -> "Not Acceptable"; |
|
|
|
reason_phrase(407) -> "Proxy Authentication Required"; |
|
|
|
reason_phrase(408) -> "Request Time-out"; |
|
|
|
reason_phrase(409) -> "Conflict"; |
|
|
|
reason_phrase(410) -> "Gone"; |
|
|
|
reason_phrase(411) -> "Length Required"; |
|
|
|
reason_phrase(412) -> "Precondition Failed"; |
|
|
|
reason_phrase(413) -> "Request Entity Too Large"; |
|
|
|
reason_phrase(414) -> "Request-URI Too Large"; |
|
|
|
reason_phrase(415) -> "Unsupported Media Type"; |
|
|
|
reason_phrase(416) -> "Requested Range Not Satisfiable"; |
|
|
|
reason_phrase(417) -> "Expectation Failed"; |
|
|
|
reason_phrase(500) -> "Internal Server Error"; |
|
|
|
reason_phrase(501) -> "Not Implemented"; |
|
|
|
reason_phrase(502) -> "Bad Gateway"; |
|
|
|
reason_phrase(503) -> "Service Unavailable"; |
|
|
|
reason_phrase(504) -> "Gateway Time-out"; |
|
|
|
reason_phrase(505) -> "HTTP Version not supported"; |
|
|
|
%% RFC 2518, HTTP Extensions for Distributed Authoring -- WEBDAV |
|
|
|
reason_phrase(102) -> "Processing"; |
|
|
|
reason_phrase(207) -> "Multi-Status"; |
|
|
|
reason_phrase(422) -> "Unprocessable Entity"; |
|
|
|
reason_phrase(423) -> "Locked"; |
|
|
|
reason_phrase(424) -> "Failed Dependency"; |
|
|
|
reason_phrase(507) -> "Insufficient Storage"; |
|
|
|
%% (Work in Progress) WebDAV Advanced Collections |
|
|
|
reason_phrase(425) -> ""; |
|
|
|
%% RFC 2817, HTTP Upgrade to TLS |
|
|
|
reason_phrase(426) -> "Upgrade Required"; |
|
|
|
%% RFC 3229, Delta encoding in HTTP |
|
|
|
reason_phrase(226) -> "IM Used"; |
|
|
|
reason_phrase(_) -> "Internal Server Error". |