Created
December 2, 2010 23:55
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% | |
%% %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]). |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% | |
%% %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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
%% | |
%% %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