Skip to content

Instantly share code, notes, and snippets.

@kellymclaughlin
Created December 2, 2010 23:55
Show Gist options
  • Save kellymclaughlin/726340 to your computer and use it in GitHub Desktop.
Save kellymclaughlin/726340 to your computer and use it in GitHub Desktop.
Instructions on adding http digest support to Erlang R14B01 with Inets 5.5.
These are source files to add http digest authentication to erlang's inet-5.5 application. To install do the following:
1. Copy httpc_request.erl and httpc.erl to /usr/lib/erlang/lib/inets-5.5/src/http_client/.
2. Copy http_request.erl to /usr/lib/erlang/lib/inets-5.5/src/http_lib/.
3. cd /usr/lib/erlang/lib/inets-5.5/src
3. sudo erlc -W -o ../ebin http_lib/http_request.erl http_client/httpc.erl http_client/httpc_request.erl
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2005-2009. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(http_request).
-include("http_internal.hrl").
-export([headers/2, http_headers/1, is_absolut_uri/1]).
%%-------------------------------------------------------------------------
%% headers(HeaderList, #http_request_h{}) -> #http_request_h{}
%% HeaderList - ["HeaderField:Value"]
%% HeaderField - string()
%% Value - string()
%%
%% Description: Creates a http_request_h-record used internally to
%% handle http-headers.
%%-------------------------------------------------------------------------
headers([], Headers) ->
Headers;
headers([Header | Tail], Headers) ->
case lists:splitwith(fun($:) -> false; (_) -> true end, Header) of
{Key, [$: | Value]} ->
headers(Tail, headers(http_util:to_lower(string:strip(Key)),
string:strip(Value), Headers));
{_, []} ->
Report = io_lib:format("Ignored invalid HTTP-header: ~p~n",
[Header]),
error_logger:error_report(Report),
headers(Tail, Headers)
end.
%%-------------------------------------------------------------------------
%% headers(#http_request_h{}) -> HeaderList
%% HeaderList - ["HeaderField:Value"]
%% HeaderField - string()
%% Value - string()
%%
%% Description: Creates a HTTP header string.
%%-------------------------------------------------------------------------
http_headers(Headers = #http_request_h{other = Other}) ->
HeaderFields = record_info(fields, http_request_h) -- [other],
HeaderStr = lists:foldl(fun(Key, Acc) ->
case key_value_str(Key, Headers) of
undefined ->
Acc;
Str ->
[Str | Acc]
end
end,
[], HeaderFields),
lists:flatten([HeaderStr | headers_other(Other, [])]).
%%-------------------------------------------------------------------------
%% is_absolut_uri(URI) -> true | false
%% URI - string()
%%
%% Description: Checks if an URI is absolute or relative
%%-------------------------------------------------------------------------
is_absolut_uri("http://" ++ _) ->
true;
is_absolut_uri("https://" ++ _) ->
true;
is_absolut_uri(_) ->
false.
%%%========================================================================
%%% Internal functions
%%%========================================================================
%%% --- Request headers
headers("accept", Value, Headers) ->
Headers#http_request_h{accept = Value};
headers("accept-charset", Value, Headers) ->
Headers#http_request_h{'accept-charset' = Value};
headers("accept-encoding", Value, Headers) ->
Headers#http_request_h{'accept-encoding' = Value};
headers("accept-language", Value, Headers) ->
Headers#http_request_h{'accept-language' = Value};
headers("authorization", Value, Headers) ->
Headers#http_request_h{authorization = Value};
headers("expect", Value, Headers) ->
Headers#http_request_h{expect = Value};
headers("from", Value, Headers) ->
Headers#http_request_h{from = Value};
headers("host", Value, Headers) ->
Headers#http_request_h{host = Value};
headers("if-match", Value, Headers) ->
Headers#http_request_h{'if-match' = Value};
headers("if-modified-since", Value, Headers) ->
Headers#http_request_h{'if-modified-since' = Value};
headers("if-none-match", Value, Headers) ->
Headers#http_request_h{'if-none-match' = Value};
headers("if-range", Value, Headers) ->
Headers#http_request_h{'if-range' = Value};
headers("if-unmodified-since", Value, Headers) ->
Headers#http_request_h{'if-unmodified-since' = Value};
headers("max-forwards", Value, Headers) ->
Headers#http_request_h{'max-forwards' = Value};
headers("proxy-authorization", Value, Headers) ->
Headers#http_request_h{'proxy-authorization' = Value};
headers("range", Value, Headers) ->
Headers#http_request_h{range = Value};
headers("referer", Value, Headers) ->
Headers#http_request_h{referer = Value};
headers("te", Value, Headers) ->
Headers#http_request_h{te = Value};
headers("user-agent", Value, Headers) ->
Headers#http_request_h{'user-agent' = Value};
%% General-Headers
headers("cache-control", Value, Headers) ->
Headers#http_request_h{'cache-control' = Value};
headers("connection", Value, Headers) ->
Headers#http_request_h{connection = Value};
headers("date", Value, Headers) ->
Headers#http_request_h{date = Value};
headers("pragma", Value, Headers) ->
Headers#http_request_h{pragma = Value};
headers("trailer", Value, Headers) ->
Headers#http_request_h{trailer = Value};
headers("transfer-encoding", Value, Headers) ->
Headers#http_request_h{'transfer-encoding' = Value};
headers("upgrade", Value, Headers) ->
Headers#http_request_h{upgrade = Value};
headers("via", Value, Headers) ->
Headers#http_request_h{via = Value};
headers("warning", Value, Headers) ->
Headers#http_request_h{warning = Value};
%% Entity header
headers("allow", Value, Headers) ->
Headers#http_request_h{allow = Value};
headers("content-encoding", Value, Headers) ->
Headers#http_request_h{'content-encoding' = Value};
headers("content-language", Value, Headers) ->
Headers#http_request_h{'content-language' = Value};
headers("content-length", Value, Headers) ->
Headers#http_request_h{'content-length' = Value};
headers("content-location", Value, Headers) ->
Headers#http_request_h{'content-location' = Value};
headers("content-md5", Value, Headers) ->
Headers#http_request_h{'content-md5' = Value};
headers("content-range", Value, Headers) ->
Headers#http_request_h{'content-range' = Value};
headers("content-type", Value, Headers) ->
Headers#http_request_h{'content-type' = Value};
headers("expires", Value, Headers) ->
Headers#http_request_h{expires = Value};
headers("last-modified", Value, Headers) ->
Headers#http_request_h{'last-modified' = Value};
headers(Key, Value, Headers) ->
Headers#http_request_h{other=
[{Key, Value} | Headers#http_request_h.other]}.
key_value_str(Key = 'cache-control', Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.'cache-control');
key_value_str(Key = connection, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.connection);
key_value_str(Key = date, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.date);
key_value_str(Key = pragma, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.pragma);
key_value_str(Key = trailer, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.trailer);
key_value_str(Key = 'transfer-encoding', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'transfer-encoding');
key_value_str(Key = upgrade, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.upgrade);
key_value_str(Key = via, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.via);
key_value_str(Key = warning, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.warning);
key_value_str(Key = accept, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.accept);
key_value_str(Key = 'accept-charset', Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.'accept-charset');
key_value_str(Key = 'accept-encoding', Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.'accept-encoding');
key_value_str(Key = 'accept-language', Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.'accept-language');
key_value_str(Key = authorization, Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.authorization);
key_value_str(Key = expect, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.expect);
key_value_str(Key = from, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.from);
key_value_str(Key = host, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.host);
key_value_str(Key = 'if-match', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'if-match');
key_value_str(Key = 'if-modified-since', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'if-modified-since');
key_value_str(Key = 'if-none-match', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'if-none-match');
key_value_str(Key = 'if-range', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'if-range');
key_value_str(Key = 'if-unmodified-since', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'if-unmodified-since');
key_value_str(Key = 'max-forwards', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'max-forwards');
key_value_str(Key = 'proxy-authorization', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'proxy-authorization');
key_value_str(Key = range, Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.range);
key_value_str(Key = referer, Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.referer);
key_value_str(Key = te, Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.te);
key_value_str(Key = 'user-agent', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'user-agent');
key_value_str(Key = allow, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.allow);
key_value_str(Key = 'content-encoding', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'content-encoding');
key_value_str(Key = 'content-language', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'content-language');
key_value_str(Key = 'content-length', Headers) ->
case Headers#http_request_h.'content-length' of
"0" ->
undefined;
_ ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'content-length')
end;
key_value_str(Key = 'content-location', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'content-location');
key_value_str(Key = 'content-md5', Headers) ->
key_value_str(atom_to_list(Key),
Headers#http_request_h.'content-md5');
key_value_str(Key = 'content-range', Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.'content-range');
key_value_str(Key = 'content-type', Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.'content-type');
key_value_str(Key = expires, Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.expires);
key_value_str(Key = 'last-modified', Headers) ->
key_value_str(atom_to_list(Key), Headers#http_request_h.'last-modified');
key_value_str(_, undefined) ->
undefined;
key_value_str("authorization", []) ->
undefined;
key_value_str(Key, Value) ->
Key ++ ": " ++ Value ++ ?CRLF.
headers_other([], Headers) ->
Headers;
headers_other([{Key,Value} | Rest], Headers) ->
Header = Key ++ ": " ++ Value ++ ?CRLF,
headers_other(Rest, [Header | Headers]).
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2009-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
%%
%% Description:
%%% This version of the HTTP/1.1 client supports:
%%% - RFC 2616 HTTP 1.1 client part
%%% - RFC 2818 HTTP Over TLS
-module(httpc).
-behaviour(inets_service).
%% API
-export([
request/1, request/2, request/4, request/5,
cancel_request/1, cancel_request/2,
set_option/2, set_option/3,
set_options/1, set_options/2,
store_cookies/2, store_cookies/3,
cookie_header/1, cookie_header/2,
which_cookies/0, which_cookies/1,
reset_cookies/0, reset_cookies/1,
stream_next/1,
default_profile/0,
profile_name/1, profile_name/2,
info/0, info/1
]).
%% Behavior callbacks
-export([start_standalone/1, start_service/1,
stop_service/1,
services/0, service_info/1]).
-include_lib("inets/src/http_lib/http_internal.hrl").
-include("httpc_internal.hrl").
-define(DEFAULT_PROFILE, default).
%%%=========================================================================
%%% API
%%%=========================================================================
default_profile() ->
?DEFAULT_PROFILE.
profile_name(?DEFAULT_PROFILE) ->
httpc_manager;
profile_name(Profile) ->
profile_name("httpc_manager_", Profile).
profile_name(Prefix, Profile) when is_atom(Profile) ->
list_to_atom(Prefix ++ atom_to_list(Profile));
profile_name(Prefix, Profile) when is_pid(Profile) ->
ProfileStr0 =
string:strip(string:strip(erlang:pid_to_list(Profile), left, $<), right, $>),
F = fun($.) -> $_; (X) -> X end,
ProfileStr = [F(C) || C <- ProfileStr0],
list_to_atom(Prefix ++ "pid_" ++ ProfileStr).
%%--------------------------------------------------------------------------
%% request(Url) -> {ok, {StatusLine, Headers, Body}} | {error,Reason}
%% request(Url Profile) ->
%% {ok, {StatusLine, Headers, Body}} | {error,Reason}
%%
%% Url - string()
%% Description: Calls request/4 with default values.
%%--------------------------------------------------------------------------
request(Url) ->
request(Url, default_profile()).
request(Url, Profile) ->
request(get, {Url, []}, [], [], Profile).
%%--------------------------------------------------------------------------
%% request(Method, Request, HTTPOptions, Options [, Profile]) ->
%% {ok, {StatusLine, Headers, Body}} | {ok, {Status, Body}} |
%% {ok, RequestId} | {error,Reason} | {ok, {saved_as, FilePath}
%%
%% Method - atom() = head | get | put | post | trace | options| delete
%% Request - {Url, Headers} | {Url, Headers, ContentType, Body}
%% Url - string()
%% HTTPOptions - [HttpOption]
%% HTTPOption - {timeout, Time} | {connect_timeout, Time} |
%% {ssl, SSLOptions} | {proxy_auth, {User, Password}}
%% Ssloptions = ssl_options() |
%% {ssl, ssl_options()} |
%% {ossl, ssl_options()} |
%% {essl, ssl_options()}
%% ssl_options() = [ssl_option()]
%% ssl_option() = {verify, code()} |
%% {depth, depth()} |
%% {certfile, path()} |
%% {keyfile, path()} | {password, string()} | {cacertfile, path()} |
%% {ciphers, string()}
%% Options - [Option]
%% Option - {sync, Boolean} | {body_format, BodyFormat} |
%% {full_result, Boolean} | {stream, To} |
%% {headers_as_is, Boolean}
%% StatusLine = {HTTPVersion, StatusCode, ReasonPhrase}</v>
%% HTTPVersion = string()
%% StatusCode = integer()
%% ReasonPhrase = string()
%% Headers = [Header]
%% Header = {Field, Value}
%% Field = string()
%% Value = string()
%% Body = string() | binary() - HTLM-code
%%
%% Description: Sends a HTTP-request. The function can be both
%% syncronus and asynchronous in the later case the function will
%% return {ok, RequestId} and later on a message will be sent to the
%% calling process on the format {http, {RequestId, {StatusLine,
%% Headers, Body}}} or {http, {RequestId, {error, Reason}}}
%%--------------------------------------------------------------------------
request(Method, Request, HttpOptions, Options) ->
request(Method, Request, HttpOptions, Options, default_profile()).
request(Method, {Url, Headers}, HTTPOptions, Options, Profile)
when (Method =:= options) orelse
(Method =:= get) orelse
(Method =:= head) orelse
(Method =:= delete) orelse
(Method =:= trace) andalso
(is_atom(Profile) orelse is_pid(Profile)) ->
?hcrt("request", [{method, Method},
{url, Url},
{headers, Headers},
{http_options, HTTPOptions},
{options, Options},
{profile, Profile}]),
case http_uri:parse(Url) of
{error, Reason} ->
{error, Reason};
ParsedUrl ->
handle_request(Method, Url, ParsedUrl, Headers, [], [],
HTTPOptions, Options, Profile)
end;
request(Method, {Url,Headers,ContentType,Body}, HTTPOptions, Options, Profile)
when ((Method =:= post) orelse (Method =:= put)) andalso
(is_atom(Profile) orelse is_pid(Profile)) ->
?hcrt("request", [{method, Method},
{url, Url},
{headers, Headers},
{content_type, ContentType},
{body, Body},
{http_options, HTTPOptions},
{options, Options},
{profile, Profile}]),
case http_uri:parse(Url) of
{error, Reason} ->
{error, Reason};
ParsedUrl ->
handle_request(Method, Url,
ParsedUrl, Headers, ContentType, Body,
HTTPOptions, Options, Profile)
end.
%%--------------------------------------------------------------------------
%% cancel_request(RequestId) -> ok
%% cancel_request(RequestId, Profile) -> ok
%% RequestId - As returned by request/4
%%
%% Description: Cancels a HTTP-request.
%%-------------------------------------------------------------------------
cancel_request(RequestId) ->
cancel_request(RequestId, default_profile()).
cancel_request(RequestId, Profile)
when is_atom(Profile) orelse is_pid(Profile) ->
?hcrt("cancel request", [{request_id, RequestId}, {profile, Profile}]),
ok = httpc_manager:cancel_request(RequestId, profile_name(Profile)),
receive
%% If the request was already fulfilled throw away the
%% answer as the request has been canceled.
{http, {RequestId, _}} ->
ok
after 0 ->
ok
end.
%%--------------------------------------------------------------------------
%% set_options(Options) -> ok | {error, Reason}
%% set_options(Options, Profile) -> ok | {error, Reason}
%% Options - [Option]
%% Profile - atom()
%% Option - {proxy, {Proxy, NoProxy}} | {max_sessions, MaxSessions} |
%% {max_pipeline_length, MaxPipeline} |
%% {pipeline_timeout, PipelineTimeout} | {cookies, CookieMode} |
%% {ipfamily, IpFamily}
%% Proxy - {Host, Port}
%% NoProxy - [Domain | HostName | IPAddress]
%% MaxSessions, MaxPipeline, PipelineTimeout = integer()
%% CookieMode - enabled | disabled | verify
%% IpFamily - inet | inet6 | inet6fb4
%% Description: Informs the httpc_manager of the new settings.
%%-------------------------------------------------------------------------
set_options(Options) ->
set_options(Options, default_profile()).
set_options(Options, Profile) when is_atom(Profile) orelse is_pid(Profile) ->
?hcrt("set cookies", [{options, Options}, {profile, Profile}]),
case validate_options(Options) of
{ok, Opts} ->
try
begin
httpc_manager:set_options(Opts, profile_name(Profile))
end
catch
exit:{noproc, _} ->
{error, inets_not_started}
end;
{error, Reason} ->
{error, Reason}
end.
set_option(Key, Value) ->
set_option(Key, Value, default_profile()).
set_option(Key, Value, Profile) ->
set_options([{Key, Value}], Profile).
%%--------------------------------------------------------------------------
%% store_cookies(SetCookieHeaders, Url [, Profile]) -> ok | {error, reason}
%%
%%
%% Description: Store the cookies from <SetCookieHeaders>
%% in the cookie database
%% for the profile <Profile>. This function shall be used when the option
%% cookie is set to verify.
%%-------------------------------------------------------------------------
store_cookies(SetCookieHeaders, Url) ->
store_cookies(SetCookieHeaders, Url, default_profile()).
store_cookies(SetCookieHeaders, Url, Profile)
when is_atom(Profile) orelse is_pid(Profile) ->
?hcrt("store cookies", [{set_cookie_headers, SetCookieHeaders},
{url, Url},
{profile, Profile}]),
try
begin
{_, _, Host, Port, Path, _} = http_uri:parse(Url),
Address = {Host, Port},
ProfileName = profile_name(Profile),
Cookies = httpc_cookie:cookies(SetCookieHeaders, Path, Host),
httpc_manager:store_cookies(Cookies, Address, ProfileName),
ok
end
catch
exit:{noproc, _} ->
{error, {not_started, Profile}};
error:{badmatch, Bad} ->
{error, {parse_failed, Bad}}
end.
%%--------------------------------------------------------------------------
%% cookie_header(Url [, Profile]) -> Header | {error, Reason}
%%
%% Description: Returns the cookie header that would be sent when making
%% a request to <Url>.
%%-------------------------------------------------------------------------
cookie_header(Url) ->
cookie_header(Url, default_profile()).
cookie_header(Url, Profile) ->
?hcrt("cookie header", [{url, Url},
{profile, Profile}]),
try
begin
httpc_manager:which_cookies(Url, profile_name(Profile))
end
catch
exit:{noproc, _} ->
{error, {not_started, Profile}}
end.
%%--------------------------------------------------------------------------
%% which_cookies() -> [cookie()]
%% which_cookies(Profile) -> [cookie()]
%%
%% Description: Debug function, dumping the cookie database
%%-------------------------------------------------------------------------
which_cookies() ->
which_cookies(default_profile()).
which_cookies(Profile) ->
?hcrt("which cookies", [{profile, Profile}]),
try
begin
httpc_manager:which_cookies(profile_name(Profile))
end
catch
exit:{noproc, _} ->
{error, {not_started, Profile}}
end.
%%--------------------------------------------------------------------------
%% info() -> list()
%% info(Profile) -> list()
%%
%% Description: Debug function, retreive info about the profile
%%-------------------------------------------------------------------------
info() ->
info(default_profile()).
info(Profile) ->
?hcrt("info", [{profile, Profile}]),
try
begin
httpc_manager:info(profile_name(Profile))
end
catch
exit:{noproc, _} ->
{error, {not_started, Profile}}
end.
%%--------------------------------------------------------------------------
%% reset_cookies() -> void()
%% reset_cookies(Profile) -> void()
%%
%% Description: Debug function, reset the cookie database
%%-------------------------------------------------------------------------
reset_cookies() ->
reset_cookies(default_profile()).
reset_cookies(Profile) ->
?hcrt("reset cookies", [{profile, Profile}]),
try
begin
httpc_manager:reset_cookies(profile_name(Profile))
end
catch
exit:{noproc, _} ->
{error, {not_started, Profile}}
end.
%%--------------------------------------------------------------------------
%% stream_next(Pid) -> Header | {error, Reason}
%%
%% Description: Triggers the next message to be streamed, e.i.
%% same behavior as active once for sockets.
%%-------------------------------------------------------------------------
stream_next(Pid) ->
?hcrt("stream next", [{handler, Pid}]),
httpc_handler:stream_next(Pid).
%%%========================================================================
%%% Behaviour callbacks
%%%========================================================================
start_standalone(PropList) ->
?hcrt("start standalone", [{proplist, PropList}]),
case proplists:get_value(profile, PropList) of
undefined ->
{error, no_profile};
Profile ->
Dir =
proplists:get_value(data_dir, PropList, only_session_cookies),
httpc_manager:start_link(Profile, Dir, stand_alone)
end.
start_service(Config) ->
?hcrt("start service", [{config, Config}]),
httpc_profile_sup:start_child(Config).
stop_service(Profile) when is_atom(Profile) ->
?hcrt("stop service", [{profile, Profile}]),
httpc_profile_sup:stop_child(Profile);
stop_service(Pid) when is_pid(Pid) ->
?hcrt("stop service", [{pid, Pid}]),
case service_info(Pid) of
{ok, [{profile, Profile}]} ->
stop_service(Profile);
Error ->
Error
end.
services() ->
[{httpc, Pid} || {_, Pid, _, _} <-
supervisor:which_children(httpc_profile_sup)].
service_info(Pid) ->
try [{ChildName, ChildPid} ||
{ChildName, ChildPid, _, _} <-
supervisor:which_children(httpc_profile_sup)] of
Children ->
child_name2info(child_name(Pid, Children))
catch
exit:{noproc, _} ->
{error, service_not_available}
end.
%%%========================================================================
%%% Internal functions
%%%========================================================================
handle_request(Method, Url,
{Scheme, UserInfo, Host, Port, Path, Query},
Headers, ContentType, Body,
HTTPOptions0, Options0, Profile) ->
Started = http_util:timestamp(),
AuthCheck = fun({Key, Val}) ->
case string:to_lower(Key) of
"authorization" -> {"authorization", string:to_lower(Val)};
LowKey -> {LowKey, Val}
end
end,
NewHeaders = [AuthCheck({Key, Val}) || {Key, Val} <- Headers],
try
begin
HTTPOptions = http_options(HTTPOptions0),
Options = request_options(Options0),
Sync = proplists:get_value(sync, Options),
Stream = proplists:get_value(stream, Options),
Host2 = header_host(Scheme, Host, Port),
HeadersRecord = header_record(NewHeaders, Host2, HTTPOptions),
Receiver = proplists:get_value(receiver, Options),
SocketOpts = proplists:get_value(socket_opts, Options),
Request = #request{from = Receiver,
scheme = Scheme,
address = {Host, Port},
path = Path,
pquery = Query,
method = Method,
headers = HeadersRecord,
content = {ContentType, Body},
settings = HTTPOptions,
abs_uri = Url,
userinfo = UserInfo,
stream = Stream,
headers_as_is = headers_as_is(Headers, Options),
socket_opts = SocketOpts,
started = Started},
case HeadersRecord#http_request_h.authorization of
"digest" ->
ChallengeUrl = [atom_to_list(Scheme),"://", Host, ":", integer_to_list(Port), Path, Query],
ChallengeRequest = #request{from = self(),
scheme = Scheme, address = {Host,Port},
path = Path, pquery = Query, method = Method,
headers = HeadersRecord#http_request_h{'authorization'=""},
content = {ContentType,Body},
settings =
HTTPOptions,
abs_uri = ChallengeUrl, userinfo = "",
stream = Stream,
headers_as_is =
headers_as_is(Headers, Options),
started = Started},
case httpc_manager:request(ChallengeRequest, profile_name(Profile)) of
{ok, RequestId} ->
{ok, {{_, 401, _}, ChallengeHeaders, _}} = handle_answer(RequestId, Sync, Options),
%Parse challenge response
HeaderDict = dict:from_list(ChallengeHeaders),
{ok, AuthenticateHeader} = dict:find("www-authenticate", HeaderDict),
%Strip the authorization type from the string
AdjustedAuthHeader = string:substr(AuthenticateHeader, 8, string:len(AuthenticateHeader)-7),
AuthFields = string:tokens(AdjustedAuthHeader, ","),
Fun = fun(AuthField) ->
[Key, Value] = string:tokens(AuthField, "="),
case Value of
[$\"|_] ->
{string:strip(Key,both), string:strip(string:strip(Value, both, $\"), both)};
_ ->
{string:strip(Key,both), string:strip(Value, both)}
end
end,
AuthPairs = [Fun(AuthField) || AuthField <- AuthFields],
AuthDict = dict:from_list(AuthPairs),
{ok, Nonce} = dict:find("nonce", AuthDict),
{ok, Realm} = dict:find("realm", AuthDict),
{ok, Algorithm} = dict:find("algorithm", AuthDict),
{ok, Qop} = dict:find("qop", AuthDict),
{ok, Opaque} = dict:find("opaque", AuthDict),
%Calculate H(A1)
Cnonce = get_cnonce(),
NonceCount = "00000001",
[User, Passwd] = string:tokens(UserInfo, ":"),
HA1 = hexdigest([hexdigest([User , ":" , Realm , ":" , Passwd]), ":",Nonce,":",Cnonce]),
%Calculate H(A2)
HA2 = hexdigest([string:to_upper(atom_to_list(Method)),":",Path++Query]),
%Calculate the digest response
ChallengeResponse = hexdigest([HA1,":",Nonce,":",NonceCount,":",Cnonce,":",Qop,":",HA2]),
%Build the Authorization header value
AuthValue = lists:flatten(["Digest username=\"", User, "\", realm=\"", Realm, "\", nonce=\"", Nonce, "\", uri=\"", Path, Query, "\", cnonce=\"", Cnonce, "\", nc=", NonceCount, ", qop=\"", Qop, "\", response=\"", ChallengeResponse, "\", opaque=\"", Opaque, "\", algorithm=\"", Algorithm, "\""]),
%Send request again with challenge response included
case httpc_manager:request(Request#request{id=RequestId, headers=HeadersRecord#http_request_h{authorization=AuthValue}}, profile_name(Profile)) of
{ok, RequestId2} ->
handle_answer(RequestId2, Sync, Options);
{error, Reason2} ->
{error, Reason2}
end;
{error, Reason} ->
{error, Reason}
end;
_ ->
case httpc_manager:request(Request, profile_name(Profile)) of
{ok, RequestId} ->
handle_answer(RequestId, Sync, Options);
{error, Reason} ->
{error, Reason}
end
end
end
catch
error:{noproc, _} ->
{error, {not_started, Profile}};
throw:Error ->
Error
end.
handle_answer(RequestId, false, _) ->
{ok, RequestId};
handle_answer(RequestId, true, Options) ->
receive
{http, {RequestId, saved_to_file}} ->
{ok, saved_to_file};
{http, {RequestId, {_,_,_} = Result}} ->
return_answer(Options, Result);
{http, {RequestId, {error, Reason}}} ->
{error, Reason}
end.
return_answer(Options, {{"HTTP/0.9",_,_}, _, BinBody}) ->
Body = maybe_format_body(BinBody, Options),
{ok, Body};
return_answer(Options, {StatusLine, Headers, BinBody}) ->
Body = maybe_format_body(BinBody, Options),
case proplists:get_value(full_result, Options, true) of
true ->
{ok, {StatusLine, Headers, Body}};
false ->
{_, Status, _} = StatusLine,
{ok, {Status, Body}}
end.
maybe_format_body(BinBody, Options) ->
case proplists:get_value(body_format, Options, string) of
string ->
binary_to_list(BinBody);
_ ->
BinBody
end.
%% This options is a workaround for http servers that do not follow the
%% http standard and have case sensative header parsing. Should only be
%% used if there is no other way to communicate with the server or for
%% testing purpose.
headers_as_is(Headers, Options) ->
case proplists:get_value(headers_as_is, Options, false) of
false ->
[];
true ->
Headers
end.
http_options(HttpOptions) ->
HttpOptionsDefault = http_options_default(),
http_options(HttpOptionsDefault, HttpOptions, #http_options{}).
http_options([], [], Acc) ->
Acc;
http_options([], HttpOptions, Acc) ->
Fun = fun(BadOption) ->
Report = io_lib:format("Invalid option ~p ignored ~n",
[BadOption]),
error_logger:info_report(Report)
end,
lists:foreach(Fun, HttpOptions),
Acc;
http_options([{Tag, Default, Idx, Post} | Defaults], HttpOptions, Acc) ->
case lists:keysearch(Tag, 1, HttpOptions) of
{value, {Tag, Val0}} ->
case Post(Val0) of
{ok, Val} ->
Acc2 = setelement(Idx, Acc, Val),
HttpOptions2 = lists:keydelete(Tag, 1, HttpOptions),
http_options(Defaults, HttpOptions2, Acc2);
error ->
Report = io_lib:format("Invalid option ~p:~p ignored ~n",
[Tag, Val0]),
error_logger:info_report(Report),
HttpOptions2 = lists:keydelete(Tag, 1, HttpOptions),
http_options(Defaults, HttpOptions2, Acc)
end;
false ->
DefaultVal =
case Default of
{value, Val} ->
Val;
{field, DefaultIdx} ->
element(DefaultIdx, Acc)
end,
Acc2 = setelement(Idx, Acc, DefaultVal),
http_options(Defaults, HttpOptions, Acc2)
end.
http_options_default() ->
VersionPost =
fun(Value) when is_atom(Value) ->
{ok, http_util:to_upper(atom_to_list(Value))};
(Value) when is_list(Value) ->
{ok, http_util:to_upper(Value)};
(_) ->
error
end,
TimeoutPost = fun(Value) when is_integer(Value) andalso (Value >= 0) ->
{ok, Value};
(infinity = Value) ->
{ok, Value};
(_) ->
error
end,
AutoRedirectPost = fun(Value) when (Value =:= true) orelse
(Value =:= false) ->
{ok, Value};
(_) ->
error
end,
SslPost = fun(Value) when is_list(Value) ->
{ok, {?HTTP_DEFAULT_SSL_KIND, Value}};
({ssl, SslOptions}) when is_list(SslOptions) ->
{ok, {?HTTP_DEFAULT_SSL_KIND, SslOptions}};
({ossl, SslOptions}) when is_list(SslOptions) ->
{ok, {ossl, SslOptions}};
({essl, SslOptions}) when is_list(SslOptions) ->
{ok, {essl, SslOptions}};
(_) ->
error
end,
ProxyAuthPost = fun({User, Passwd} = Value) when is_list(User) andalso
is_list(Passwd) ->
{ok, Value};
(_) ->
error
end,
RelaxedPost = fun(Value) when (Value =:= true) orelse
(Value =:= false) ->
{ok, Value};
(_) ->
error
end,
ConnTimeoutPost =
fun(Value) when is_integer(Value) andalso (Value >= 0) ->
{ok, Value};
(infinity = Value) ->
{ok, Value};
(_) ->
error
end,
[
{version, {value, "HTTP/1.1"}, #http_options.version, VersionPost},
{timeout, {value, ?HTTP_REQUEST_TIMEOUT}, #http_options.timeout, TimeoutPost},
{autoredirect, {value, true}, #http_options.autoredirect, AutoRedirectPost},
{ssl, {value, {?HTTP_DEFAULT_SSL_KIND, []}}, #http_options.ssl, SslPost},
{proxy_auth, {value, undefined}, #http_options.proxy_auth, ProxyAuthPost},
{relaxed, {value, false}, #http_options.relaxed, RelaxedPost},
%% this field has to be *after* the timeout option (as that field is used for the default value)
{connect_timeout, {field, #http_options.timeout}, #http_options.connect_timeout, ConnTimeoutPost}
].
request_options_defaults() ->
VerifyBoolean =
fun(Value) when ((Value =:= true) orelse (Value =:= false)) ->
ok;
(_) ->
error
end,
VerifySync = VerifyBoolean,
VerifyStream =
fun(none = _Value) ->
ok;
(self = _Value) ->
ok;
({self, once} = _Value) ->
ok;
(Value) when is_list(Value) ->
ok;
(_) ->
error
end,
VerifyBodyFormat =
fun(string = _Value) ->
ok;
(binary = _Value) ->
ok;
(_) ->
error
end,
VerifyFullResult = VerifyBoolean,
VerifyHeaderAsIs = VerifyBoolean,
VerifyReceiver =
fun(Value) when is_pid(Value) ->
ok;
({M, F, A}) when (is_atom(M) andalso
is_atom(F) andalso
is_list(A)) ->
ok;
(Value) when is_function(Value, 1) ->
ok;
(_) ->
error
end,
VerifySocketOpts =
fun([]) ->
{ok, undefined};
(Value) when is_list(Value) ->
ok;
(_) ->
error
end,
[
{sync, true, VerifySync},
{stream, none, VerifyStream},
{body_format, string, VerifyBodyFormat},
{full_result, true, VerifyFullResult},
{headers_as_is, false, VerifyHeaderAsIs},
{receiver, self(), VerifyReceiver},
{socket_opts, undefined, VerifySocketOpts}
].
request_options(Options) ->
Defaults = request_options_defaults(),
request_options(Defaults, Options, []).
request_options([], [], Acc) ->
request_options_sanity_check(Acc),
lists:reverse(Acc);
request_options([], Options, Acc) ->
Fun = fun(BadOption) ->
Report = io_lib:format("Invalid option ~p ignored ~n",
[BadOption]),
error_logger:info_report(Report)
end,
lists:foreach(Fun, Options),
Acc;
request_options([{Key, DefaultVal, Verify} | Defaults], Options, Acc) ->
case lists:keysearch(Key, 1, Options) of
{value, {Key, Value}} ->
case Verify(Value) of
ok ->
Options2 = lists:keydelete(Key, 1, Options),
request_options(Defaults, Options2, [{Key, Value} | Acc]);
{ok, Value2} ->
Options2 = lists:keydelete(Key, 1, Options),
request_options(Defaults, Options2, [{Key, Value2} | Acc]);
error ->
Report = io_lib:format("Invalid option ~p:~p ignored ~n",
[Key, Value]),
error_logger:info_report(Report),
Options2 = lists:keydelete(Key, 1, Options),
request_options(Defaults, Options2, Acc)
end;
false ->
request_options(Defaults, Options, [{Key, DefaultVal} | Acc])
end.
request_options_sanity_check(Opts) ->
case proplists:get_value(sync, Opts) of
Sync when (Sync =:= true) ->
case proplists:get_value(receiver, Opts) of
Pid when is_pid(Pid) andalso (Pid =:= self()) ->
ok;
BadReceiver ->
throw({error, {bad_options_combo,
[{sync, true}, {receiver, BadReceiver}]}})
end,
case proplists:get_value(stream, Opts) of
Stream when (Stream =:= self) orelse
(Stream =:= {self, once}) ->
throw({error, streaming_error});
_ ->
ok
end;
_ ->
ok
end,
ok.
validate_options(Options) ->
(catch validate_options(Options, [])).
validate_options([], ValidateOptions) ->
{ok, lists:reverse(ValidateOptions)};
validate_options([{proxy, Proxy} = Opt| Tail], Acc) ->
validate_proxy(Proxy),
validate_options(Tail, [Opt | Acc]);
validate_options([{max_sessions, Value} = Opt| Tail], Acc) ->
validate_max_sessions(Value),
validate_options(Tail, [Opt | Acc]);
validate_options([{keep_alive_timeout, Value} = Opt| Tail], Acc) ->
validate_keep_alive_timeout(Value),
validate_options(Tail, [Opt | Acc]);
validate_options([{max_keep_alive_length, Value} = Opt| Tail], Acc) ->
validate_max_keep_alive_length(Value),
validate_options(Tail, [Opt | Acc]);
validate_options([{pipeline_timeout, Value} = Opt| Tail], Acc) ->
validate_pipeline_timeout(Value),
validate_options(Tail, [Opt | Acc]);
validate_options([{max_pipeline_length, Value} = Opt| Tail], Acc) ->
validate_max_pipeline_length(Value),
validate_options(Tail, [Opt | Acc]);
validate_options([{cookies, Value} = Opt| Tail], Acc) ->
validate_cookies(Value),
validate_options(Tail, [Opt | Acc]);
validate_options([{ipfamily, Value} = Opt| Tail], Acc) ->
validate_ipfamily(Value),
validate_options(Tail, [Opt | Acc]);
%% For backward compatibillity
validate_options([{ipv6, Value}| Tail], Acc) ->
NewValue = validate_ipv6(Value),
Opt = {ipfamily, NewValue},
validate_options(Tail, [Opt | Acc]);
validate_options([{ip, Value} = Opt| Tail], Acc) ->
validate_ip(Value),
validate_options(Tail, [Opt | Acc]);
validate_options([{port, Value} = Opt| Tail], Acc) ->
validate_port(Value),
validate_options(Tail, [Opt | Acc]);
validate_options([{socket_opts, Value} = Opt| Tail], Acc) ->
validate_socket_opts(Value),
validate_options(Tail, [Opt | Acc]);
validate_options([{verbose, Value} = Opt| Tail], Acc) ->
validate_verbose(Value),
validate_options(Tail, [Opt | Acc]);
validate_options([{_, _} = Opt| _], _Acc) ->
{error, {not_an_option, Opt}}.
validate_proxy({{ProxyHost, ProxyPort}, NoProxy} = Proxy)
when is_list(ProxyHost) andalso
is_integer(ProxyPort) andalso
is_list(NoProxy) ->
Proxy;
validate_proxy(BadProxy) ->
bad_option(proxy, BadProxy).
validate_max_sessions(Value) when is_integer(Value) andalso (Value >= 0) ->
Value;
validate_max_sessions(BadValue) ->
bad_option(max_sessions, BadValue).
validate_keep_alive_timeout(Value) when is_integer(Value) andalso (Value >= 0) ->
Value;
validate_keep_alive_timeout(infinity = Value) ->
Value;
validate_keep_alive_timeout(BadValue) ->
bad_option(keep_alive_timeout, BadValue).
validate_max_keep_alive_length(Value) when is_integer(Value) andalso (Value >= 0) ->
Value;
validate_max_keep_alive_length(BadValue) ->
bad_option(max_keep_alive_length, BadValue).
validate_pipeline_timeout(Value) when is_integer(Value) ->
Value;
validate_pipeline_timeout(infinity = Value) ->
Value;
validate_pipeline_timeout(BadValue) ->
bad_option(pipeline_timeout, BadValue).
validate_max_pipeline_length(Value) when is_integer(Value) ->
Value;
validate_max_pipeline_length(BadValue) ->
bad_option(max_pipeline_length, BadValue).
validate_cookies(Value)
when ((Value =:= enabled) orelse
(Value =:= disabled) orelse
(Value =:= verify)) ->
Value;
validate_cookies(BadValue) ->
bad_option(cookies, BadValue).
validate_ipv6(Value) when (Value =:= enabled) orelse (Value =:= disabled) ->
case Value of
enabled ->
inet6fb4;
disabled ->
inet
end;
validate_ipv6(BadValue) ->
bad_option(ipv6, BadValue).
validate_ipfamily(Value)
when (Value =:= inet) orelse (Value =:= inet6) orelse (Value =:= inet6fb4) ->
Value;
validate_ipfamily(BadValue) ->
bad_option(ipfamily, BadValue).
validate_ip(Value)
when is_tuple(Value) andalso ((size(Value) =:= 4) orelse (size(Value) =:= 8)) ->
Value;
validate_ip(BadValue) ->
bad_option(ip, BadValue).
validate_port(Value) when is_integer(Value) ->
Value;
validate_port(BadValue) ->
bad_option(port, BadValue).
validate_socket_opts(Value) when is_list(Value) ->
Value;
validate_socket_opts(BadValue) ->
bad_option(socket_opts, BadValue).
validate_verbose(Value)
when ((Value =:= false) orelse
(Value =:= verbose) orelse
(Value =:= debug) orelse
(Value =:= trace)) ->
ok;
validate_verbose(BadValue) ->
bad_option(verbose, BadValue).
bad_option(Option, BadValue) ->
throw({error, {bad_option, Option, BadValue}}).
header_host(https, Host, 443 = _Port) ->
Host;
header_host(http, Host, 80 = _Port) ->
Host;
header_host(_Scheme, Host, Port) ->
Host ++ ":" ++ integer_to_list(Port).
header_record(NewHeaders, Host, #http_options{version = Version}) ->
header_record(NewHeaders, #http_request_h{}, Host, Version).
header_record([], RequestHeaders, Host, Version) ->
validate_headers(RequestHeaders, Host, Version);
header_record([{"cache-control", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'cache-control' = Val},
Host, Version);
header_record([{"connection", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{connection = Val}, Host,
Version);
header_record([{"date", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{date = Val}, Host,
Version);
header_record([{"pragma", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{pragma = Val}, Host,
Version);
header_record([{"trailer", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{trailer = Val}, Host,
Version);
header_record([{"transfer-encoding", Val} | Rest], RequestHeaders, Host,
Version) ->
header_record(Rest,
RequestHeaders#http_request_h{'transfer-encoding' = Val},
Host, Version);
header_record([{"upgrade", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{upgrade = Val}, Host,
Version);
header_record([{"via", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{via = Val}, Host,
Version);
header_record([{"warning", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{warning = Val}, Host,
Version);
header_record([{"accept", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{accept = Val}, Host,
Version);
header_record([{"accept-charset", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'accept-charset' = Val},
Host, Version);
header_record([{"accept-encoding", Val} | Rest], RequestHeaders, Host,
Version) ->
header_record(Rest, RequestHeaders#http_request_h{'accept-encoding' = Val},
Host, Version);
header_record([{"accept-language", Val} | Rest], RequestHeaders, Host,
Version) ->
header_record(Rest, RequestHeaders#http_request_h{'accept-language' = Val},
Host, Version);
header_record([{"authorization", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{authorization = Val},
Host, Version);
header_record([{"expect", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{expect = Val}, Host,
Version);
header_record([{"from", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{from = Val}, Host,
Version);
header_record([{"host", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{host = Val}, Host,
Version);
header_record([{"if-match", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'if-match' = Val},
Host, Version);
header_record([{"if-modified-since", Val} | Rest], RequestHeaders, Host,
Version) ->
header_record(Rest,
RequestHeaders#http_request_h{'if-modified-since' = Val},
Host, Version);
header_record([{"if-none-match", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'if-none-match' = Val},
Host, Version);
header_record([{"if-range", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'if-range' = Val},
Host, Version);
header_record([{"if-unmodified-since", Val} | Rest], RequestHeaders, Host,
Version) ->
header_record(Rest, RequestHeaders#http_request_h{'if-unmodified-since'
= Val}, Host, Version);
header_record([{"max-forwards", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'max-forwards' = Val},
Host, Version);
header_record([{"proxy-authorization", Val} | Rest], RequestHeaders, Host,
Version) ->
header_record(Rest, RequestHeaders#http_request_h{'proxy-authorization'
= Val}, Host, Version);
header_record([{"range", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{range = Val}, Host,
Version);
header_record([{"referer", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{referer = Val}, Host,
Version);
header_record([{"te", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{te = Val}, Host,
Version);
header_record([{"user-agent", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'user-agent' = Val},
Host, Version);
header_record([{"allow", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{allow = Val}, Host,
Version);
header_record([{"content-encoding", Val} | Rest], RequestHeaders, Host,
Version) ->
header_record(Rest,
RequestHeaders#http_request_h{'content-encoding' = Val},
Host, Version);
header_record([{"content-language", Val} | Rest], RequestHeaders,
Host, Version) ->
header_record(Rest,
RequestHeaders#http_request_h{'content-language' = Val},
Host, Version);
header_record([{"content-length", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'content-length' = Val},
Host, Version);
header_record([{"content-location", Val} | Rest], RequestHeaders,
Host, Version) ->
header_record(Rest,
RequestHeaders#http_request_h{'content-location' = Val},
Host, Version);
header_record([{"content-md5", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'content-md5' = Val},
Host, Version);
header_record([{"content-range", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'content-range' = Val},
Host, Version);
header_record([{"content-type", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'content-type' = Val},
Host, Version);
header_record([{"expires", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{expires = Val}, Host,
Version);
header_record([{"last-modified", Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{'last-modified' = Val},
Host, Version);
header_record([{Key, Val} | Rest], RequestHeaders, Host, Version) ->
header_record(Rest, RequestHeaders#http_request_h{
other = [{Key, Val} |
RequestHeaders#http_request_h.other]},
Host, Version).
validate_headers(RequestHeaders = #http_request_h{te = undefined}, Host,
"HTTP/1.1" = Version) ->
validate_headers(RequestHeaders#http_request_h{te = ""}, Host,
"HTTP/1.1" = Version);
validate_headers(RequestHeaders = #http_request_h{host = undefined},
Host, "HTTP/1.1" = Version) ->
validate_headers(RequestHeaders#http_request_h{host = Host}, Host, Version);
validate_headers(RequestHeaders, _, _) ->
RequestHeaders.
child_name2info(undefined) ->
{error, no_such_service};
child_name2info(httpc_manager) ->
{ok, [{profile, default}]};
child_name2info({httpc, Profile}) ->
{ok, [{profile, Profile}]}.
child_name(_, []) ->
undefined;
child_name(Pid, [{Name, Pid} | _]) ->
Name;
child_name(Pid, [_ | Children]) ->
child_name(Pid, Children).
hexdigest(String) ->
lists:flatten(lists:map(fun(V) ->
string:to_lower(integer_to_hexlist(V)) end,
binary_to_list(erlang:md5(String))
)).
integer_to_hexlist(X) when X < 16 ->
"0" ++ httpd_util:integer_to_hexlist(X);
integer_to_hexlist(X) ->
httpd_util:integer_to_hexlist(X).
get_cnonce() ->
{A1,A2,A3} = now(),
random:seed(A1,A2,A3),
base64:encode_to_string(generate_cnonce(8)).
generate_cnonce({0, Cnonce}) ->
[random:uniform(255) | Cnonce];
generate_cnonce({Len, Cnonce}) ->
generate_cnonce({Len-1,[random:uniform(255) | Cnonce]});
generate_cnonce(Len) ->
generate_cnonce({Len-1,[random:uniform(255)]}).
%% d(F) ->
%% d(F, []).
%% d(F, A) ->
%% d(get(dbg), F, A).
%% d(true, F, A) ->
%% io:format(user, "~w:~w:" ++ F ++ "~n", [self(), ?MODULE | A]);
%% d(_, _, _) ->
%% ok.
%%
%% %CopyrightBegin%
%%
%% Copyright Ericsson AB 2004-2010. All Rights Reserved.
%%
%% The contents of this file are subject to the Erlang Public License,
%% Version 1.1, (the "License"); you may not use this file except in
%% compliance with the License. You should have received a copy of the
%% Erlang Public License along with this software. If not, it can be
%% retrieved online at http://www.erlang.org/.
%%
%% Software distributed under the License is distributed on an "AS IS"
%% basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
%% the License for the specific language governing rights and limitations
%% under the License.
%%
%% %CopyrightEnd%
%%
-module(httpc_request).
-include_lib("inets/src/http_lib/http_internal.hrl").
-include("httpc_internal.hrl").
%%% Internal API
-export([send/3, is_idempotent/1, is_client_closing/1]).
%%%=========================================================================
%%% Internal application API
%%%=========================================================================
%%-------------------------------------------------------------------------
%% send(MaybeProxy, Request) ->
%% MaybeProxy - {Host, Port}
%% Host = string()
%% Port = integer()
%% Request - #request{}
%% Socket - socket()
%% CookieSupport - enabled | disabled | verify
%%
%% Description: Composes and sends a HTTP-request.
%%-------------------------------------------------------------------------
send(SendAddr, #session{socket = Socket, socket_type = SocketType},
#request{socket_opts = SocketOpts} = Request)
when is_list(SocketOpts) ->
case http_transport:setopts(SocketType, Socket, SocketOpts) of
ok ->
send(SendAddr, Socket, SocketType,
Request#request{socket_opts = undefined});
{error, Reason} ->
{error, {setopts_failed, Reason}}
end;
send(SendAddr, #session{socket = Socket, socket_type = SocketType}, Request) ->
send(SendAddr, Socket, SocketType, Request).
send(SendAddr, Socket, SocketType,
#request{method = Method,
path = Path,
pquery = Query,
headers = Headers,
content = Content,
address = Address,
abs_uri = AbsUri,
headers_as_is = HeadersAsIs,
settings = HttpOptions,
userinfo = UserInfo}) ->
?hcrt("send",
[{send_addr, SendAddr},
{socket, Socket},
{method, Method},
{path, Path},
{pquery, Query},
{headers, Headers},
{content, Content},
{address, Address},
{abs_uri, AbsUri},
{headers_as_is, HeadersAsIs},
{settings, HttpOptions},
{userinfo, UserInfo}]),
TmpHeaders = handle_user_info(UserInfo, Headers),
{TmpHeaders2, Body} =
post_data(Method, TmpHeaders, Content, HeadersAsIs),
{NewHeaders, Uri} = case Address of
SendAddr ->
{TmpHeaders2, Path ++ Query};
_Proxy ->
TmpHeaders3 =
handle_proxy(HttpOptions, TmpHeaders2),
{TmpHeaders3, AbsUri}
end,
FinalHeaders = case NewHeaders of
HeaderList when is_list(HeaderList) ->
http_headers(HeaderList, []);
_ ->
http_request:http_headers(NewHeaders)
end,
Version = HttpOptions#http_options.version,
Message = [method(Method), " ", Uri, " ",
version(Version), ?CRLF,
headers(FinalHeaders, Version), ?CRLF, Body],
?hcrd("send", [{message, Message}]),
http_transport:send(SocketType, Socket, lists:append(Message)).
%%-------------------------------------------------------------------------
%% is_idempotent(Method) ->
%% Method = atom()
%%
%% Description: Checks if Method is considered idempotent.
%%-------------------------------------------------------------------------
%% In particular, the convention has been established that the GET and
%% HEAD methods SHOULD NOT have the significance of taking an action
%% other than retrieval. These methods ought to be considered "safe".
is_idempotent(head) ->
true;
is_idempotent(get) ->
true;
%% Methods can also have the property of "idempotence" in that (aside
%% from error or expiration issues) the side-effects of N > 0
%% identical requests is the same as for a single request.
is_idempotent(put) ->
true;
is_idempotent(delete) ->
true;
%% Also, the methods OPTIONS and TRACE SHOULD NOT have side effects,
%% and so are inherently idempotent.
is_idempotent(trace) ->
true;
is_idempotent(options) ->
true;
is_idempotent(_) ->
false.
%%-------------------------------------------------------------------------
%% is_client_closing(Headers) ->
%% Headers = #http_request_h{}
%%
%% Description: Checks if the client has supplied a "Connection:
%% close" header.
%%-------------------------------------------------------------------------
is_client_closing(Headers) ->
case Headers#http_request_h.connection of
"close" ->
true;
_ ->
false
end.
%%%========================================================================
%%% Internal functions
%%%========================================================================
post_data(Method, Headers, {ContentType, Body}, HeadersAsIs)
when (Method =:= post) orelse (Method =:= put) ->
ContentLength = body_length(Body),
NewBody = case Headers#http_request_h.expect of
"100-continue" ->
"";
_ ->
Body
end,
NewHeaders = case HeadersAsIs of
[] ->
Headers#http_request_h{'content-type' =
ContentType,
'content-length' =
ContentLength};
_ ->
HeadersAsIs
end,
{NewHeaders, NewBody};
post_data(_, Headers, _, []) ->
{Headers, ""};
post_data(_, _, _, HeadersAsIs = [_|_]) ->
{HeadersAsIs, ""}.
body_length(Body) when is_binary(Body) ->
integer_to_list(size(Body));
body_length(Body) when is_list(Body) ->
integer_to_list(length(Body)).
method(Method) ->
http_util:to_upper(atom_to_list(Method)).
version("HTTP/0.9") ->
"";
version(Version) ->
Version.
headers(_, "HTTP/0.9") ->
"";
%% HTTP 1.1 headers not present in HTTP 1.0 should be
%% consider as unknown extension headers that should be
%% ignored.
headers(Headers, _) ->
Headers.
http_headers([], Headers) ->
lists:flatten(Headers);
http_headers([{Key,Value} | Rest], Headers) ->
Header = Key ++ ": " ++ Value ++ ?CRLF,
http_headers(Rest, [Header | Headers]).
handle_proxy(_, Headers) when is_list(Headers) ->
Headers; %% Headers as is option was specified
handle_proxy(HttpOptions, Headers) ->
case HttpOptions#http_options.proxy_auth of
undefined ->
Headers;
{User, Password} ->
UserPasswd = base64:encode_to_string(User ++ ":" ++ Password),
Headers#http_request_h{'proxy-authorization' =
"Basic " ++ UserPasswd}
end.
handle_user_info([], Headers) ->
Headers;
handle_user_info(UserInfo, Headers) ->
case string:tokens(UserInfo, ":") of
[User, Passwd] ->
case string:substr(Headers#http_request_h.authorization,1,6) of
"Digest" ->
Headers;
_ ->
UserPasswd = base64:encode_to_string(User ++ ":" ++ Passwd),
Headers#http_request_h{authorization = "Basic " ++ UserPasswd}
end;
[User] ->
UserPasswd = base64:encode_to_string(User ++ ":"),
Headers#http_request_h{authorization = "Basic " ++ UserPasswd};
_ ->
Headers
end.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment