From 84adefa331c4159d432d22840663c38f155cd4c1 Mon Sep 17 00:00:00 2001 From: Erlang/OTP Date: Fri, 20 Nov 2009 14:54:40 +0000 Subject: The R13B03 release. --- lib/inets/src/http_lib/Makefile | 101 +++++++++ lib/inets/src/http_lib/http_chunk.erl | 291 ++++++++++++++++++++++++ lib/inets/src/http_lib/http_internal.hrl | 108 +++++++++ lib/inets/src/http_lib/http_request.erl | 281 ++++++++++++++++++++++++ lib/inets/src/http_lib/http_response.erl | 208 ++++++++++++++++++ lib/inets/src/http_lib/http_transport.erl | 353 ++++++++++++++++++++++++++++++ lib/inets/src/http_lib/http_util.erl | 148 +++++++++++++ 7 files changed, 1490 insertions(+) create mode 100644 lib/inets/src/http_lib/Makefile create mode 100644 lib/inets/src/http_lib/http_chunk.erl create mode 100644 lib/inets/src/http_lib/http_internal.hrl create mode 100644 lib/inets/src/http_lib/http_request.erl create mode 100644 lib/inets/src/http_lib/http_response.erl create mode 100644 lib/inets/src/http_lib/http_transport.erl create mode 100644 lib/inets/src/http_lib/http_util.erl (limited to 'lib/inets/src/http_lib') diff --git a/lib/inets/src/http_lib/Makefile b/lib/inets/src/http_lib/Makefile new file mode 100644 index 0000000000..27e7ee65c5 --- /dev/null +++ b/lib/inets/src/http_lib/Makefile @@ -0,0 +1,101 @@ +# +# %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% +# +# + +include $(ERL_TOP)/make/target.mk +EBIN = ../../ebin +include $(ERL_TOP)/make/$(TARGET)/otp.mk + +# ---------------------------------------------------- +# Application version +# ---------------------------------------------------- +include ../../vsn.mk + +VSN = $(INETS_VSN) + +# ---------------------------------------------------- +# Release directory specification +# ---------------------------------------------------- +RELSYSDIR = $(RELEASE_PATH)/lib/inets-$(VSN) + +# ---------------------------------------------------- +# Target Specs +# ---------------------------------------------------- +MODULES = \ + http_chunk \ + http_transport\ + http_util \ + http_request \ + http_response + +HRL_FILES = http_internal.hrl + +ERL_FILES = $(MODULES:%=%.erl) + +TARGET_FILES= $(MODULES:%=$(EBIN)/%.$(EMULATOR)) + +# ---------------------------------------------------- +# INETS FLAGS +# ---------------------------------------------------- +INETS_FLAGS = -D'SERVER_SOFTWARE="inets/$(VSN)"' \ + +# ---------------------------------------------------- +# FLAGS +# ---------------------------------------------------- +INETS_ERL_FLAGS += -I ../inets_app + +ifeq ($(WARN_UNUSED_WARS),true) +ERL_COMPILE_FLAGS += +warn_unused_vars +endif + +ERL_COMPILE_FLAGS += $(INETS_ERL_FLAGS) \ + $(INETS_FLAGS) \ + +'{parse_transform,sys_pre_attributes}' \ + +'{attribute,insert,app_vsn,$(APP_VSN)}' + + +# ---------------------------------------------------- +# Targets +# ---------------------------------------------------- + +debug opt: $(TARGET_FILES) + +clean: + rm -f $(TARGET_FILES) + rm -f core + +docs: + +# ---------------------------------------------------- +# Release Target +# ---------------------------------------------------- +include $(ERL_TOP)/make/otp_release_targets.mk + +release_spec: opt + $(INSTALL_DIR) $(RELSYSDIR)/src + $(INSTALL_DATA) $(HRL_FILES) $(ERL_FILES) $(RELSYSDIR)/src + $(INSTALL_DIR) $(RELSYSDIR)/ebin + $(INSTALL_DATA) $(TARGET_FILES) $(RELSYSDIR)/ebin + +release_docs_spec: + +info: + @echo "INETS_DEBUG = $(INETS_DEBUG)" + @echo "INETS_FLAGS = $(INETS_FLAGS)" + @echo "ERL_COMPILE_FLAGS = $(ERL_COMPILE_FLAGS)" diff --git a/lib/inets/src/http_lib/http_chunk.erl b/lib/inets/src/http_lib/http_chunk.erl new file mode 100644 index 0000000000..cd20dce9d5 --- /dev/null +++ b/lib/inets/src/http_lib/http_chunk.erl @@ -0,0 +1,291 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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% +%% +%% Description: Implements chunked transfer encoding see RFC2616 section +%% 3.6.1 +-module(http_chunk). + +-include("http_internal.hrl"). + +%% API +-export([decode/3, decode/4, encode/1, encode_last/0, handle_headers/2]). +%% Callback API - used for example if the chunkedbody is received a +%% little at a time on a socket. +-export([decode_size/1, ignore_extensions/1, decode_data/1, decode_trailer/1]). + +%%%========================================================================= +%%% API +%%%========================================================================= +%%------------------------------------------------------------------------- +%% decode(ChunkedBody, MaxBodySize, MaxHeaderSize, ) -> +%% {ok, {Headers, Body}} | {Module, Function, Args} +%% +%% Headers = ["Header:Value"] +%% ChunkedBody = binary() +%% MaxBodySize = integer() +%% MaxHeaderSize = integer() +%% Stream = {Code, Request} - if Request#request.stream =/= none +%% and Code == 200 the side effect of sending each decode chunk to the +%% client/file before the whole body is received will take place. +%% +%% Note: decode/4 should only be used from httpc_handler module. +%% Otherwhise use the side effect free decode/3. +%% +%% Description: Decodes a body encoded by the chunked transfer +%% encoding. If the ChunkedBody is not compleate it returns {Module, +%% Function, Args} so that decoding can be continued when more of the +%% data has been received by calling Module:Function([NewData | Args]). +%% +%% Note: In the case of pipelining a call to decode might contain data +%% that belongs to the next request/response and will be returned as +%% part of the body, hence functions calling http_chunk:decode must +%% look at the returned content-length header to make sure that they +%% split the actual body and data that possible should be passed along to +%% the next pass in the loop. +%%------------------------------------------------------------------------- +decode(ChunkedBody, MaxBodySize, MaxHeaderSize) -> + decode(ChunkedBody, MaxBodySize, MaxHeaderSize, false). + +decode(ChunkedBody, MaxBodySize, MaxHeaderSize, Stream) -> + %% Note decode_size will call decode_data. + decode_size([ChunkedBody, <<>>, [], + {MaxBodySize, <<>>, 0, MaxHeaderSize, Stream}]). + +%%------------------------------------------------------------------------- +%% encode(Chunk) -> EncodedChunk +%% +%% Chunked = binary() +%% EncodedChunk = binary() +%% +%% Description: Encodes a body part with the chunked transfer encoding. +%% Chunks are returned as lists or binaries depending on the +%% input format. When sending the data on the both formats +%% are accepted. +%%------------------------------------------------------------------------- +encode(Chunk) when is_binary(Chunk)-> + HEXSize = list_to_binary(http_util:integer_to_hexlist(size(Chunk))), + <>; + +encode(Chunk) when is_list(Chunk)-> + HEXSize = http_util:integer_to_hexlist(erlang:iolist_size(Chunk)), + [HEXSize, ?CR, ?LF, Chunk, ?CR, ?LF]. + +encode_last() -> + <<$0, ?CR, ?LF, ?CR, ?LF >>. + +%%------------------------------------------------------------------------- +%% handle_headers(HeaderRecord, ChunkedHeaders) -> NewHeaderRecord +%% +%% HeaderRecord = NewHeaderRecord = #http_request_h{} | #http_response_h{} +%% ChunkedHeaders = ["Header:Value"] as returnde by http_chunk:decode/3 +%% +%% Description: Removes chunked from the header as we now have decode +%% the body and adds a content-length header and any other headers +%% found in the chunked trail. +%%------------------------------------------------------------------------- +handle_headers(RequestHeaderRecord = #http_request_h{}, ChunkedHeaders) -> + NewHeaders = http_request:headers(ChunkedHeaders, RequestHeaderRecord), + TransferEncoding = + case NewHeaders#http_request_h.'transfer-encoding' -- "chunked" of + "" -> + undefined; + Other -> + Other + end, + NewHeaders#http_request_h{'transfer-encoding' = TransferEncoding}; + +handle_headers(ResponseHeaderRecord = #http_response_h{}, ChunkedHeaders) -> + NewHeaders = http_response:headers(ChunkedHeaders, ResponseHeaderRecord), + TransferEncoding = + case NewHeaders#http_response_h.'transfer-encoding' -- "chunked" of + "" -> + undefined; + Other -> + Other + end, + NewHeaders#http_response_h{'transfer-encoding' = TransferEncoding}. + +%% Functions that may be returned during the decoding process +%% if the input data is incompleate. +decode_size([Bin, Rest, HexList, Info]) -> + decode_size(<>, HexList, Info). + +ignore_extensions([Bin, Rest, NextFunction]) -> + ignore_extensions(<>, NextFunction). + +decode_data([Bin, ChunkSize, TotalChunk, Info]) -> + decode_data(ChunkSize, <>, Info). + +decode_trailer([Bin, Rest, Header, Headers, MaxHeaderSize, Body, + BodyLength]) -> + decode_trailer(<>, + Header, Headers, MaxHeaderSize, Body, BodyLength). + +%%%======================================================================== +%%% Internal functions +%%%======================================================================== +decode_size(<<>>, HexList, Info) -> + {?MODULE, decode_size, [<<>>, HexList, Info]}; +decode_size(Data = <>, HexList, + {MaxBodySize, Body, + AccLength, + MaxHeaderSize, Stream}) -> + ChunkSize = http_util:hexlist_to_integer(lists:reverse(HexList)), + case ChunkSize of + 0 -> % Last chunk, there was no data + ignore_extensions(Data, {?MODULE, decode_trailer, + [<<>>, [],[], MaxHeaderSize, + Body, + integer_to_list(AccLength)]}); + _ -> + %% Note decode_data may call decode_size again if there + %% is more than one chunk, hence here is where the last parameter + %% to this function comes in. + decode_data(ChunkSize, ChunkRest, {MaxBodySize, Body, + ChunkSize + AccLength , + MaxHeaderSize, Stream}) + end; +decode_size(<<";", Rest/binary>>, HexList, Info) -> + %% Note ignore_extensions will call decode_size/1 again when + %% it ignored all extensions. + ignore_extensions(Rest, {?MODULE, decode_size, [<<>>, HexList, Info]}); +decode_size(<> = Data, HexList, Info) -> + {?MODULE, decode_size, [Data, HexList, Info]}; +decode_size(<>, HexList, Info) -> + decode_size(Rest, [Octet | HexList], Info). + +%% "All applications MUST ignore chunk-extension extensions they +%% do not understand.", see RFC 2616 Section 3.6.1 We don't +%% understand any extension... +ignore_extensions(<<>>, NextFunction) -> + {?MODULE, ignore_extensions, [<<>>, NextFunction]}; +ignore_extensions(Data = <>, + {Module, Function, Args}) -> + Module:Function([Data | Args]); +ignore_extensions(<> = Data, NextFunction) -> + {?MODULE, ignore_extensions, [Data, NextFunction]}; +ignore_extensions(<<_Octet, Rest/binary>>, NextFunction) -> + ignore_extensions(Rest, NextFunction). + +decode_data(ChunkSize, TotalChunk, + Info = {MaxBodySize, BodySoFar, AccLength, MaxHeaderSize, Stream}) + when ChunkSize =< size(TotalChunk) -> + case TotalChunk of + %% Potential last chunk + <<_:ChunkSize/binary, ?CR, ?LF, "0">> -> + {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]}; + <<_:ChunkSize/binary, ?CR, ?LF, "0", ?CR>> -> + {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]}; + <<_:ChunkSize/binary, ?CR, ?LF>> -> + {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]}; + %% Last chunk + <> -> + %% Note ignore_extensions will call decode_trailer/1 + %% once it ignored all extensions. + {NewBody, _} = + stream(<>, Stream), + {?MODULE, ignore_extensions, + [<<>>, + {?MODULE, decode_trailer, [<<>>, [],[], MaxHeaderSize, + NewBody, + integer_to_list(AccLength)]}]}; + <> -> + %% Note ignore_extensions will call decode_trailer/1 + %% once it ignored all extensions. + {NewBody, _} = stream(<>, Stream), + ignore_extensions(Rest, {?MODULE, decode_trailer, + [<<>>, [],[], MaxHeaderSize, + NewBody, + integer_to_list(AccLength)]}); + <> -> + {NewBody, _} = stream(<>, Stream), + {?MODULE, decode_trailer, [<>, [],[], MaxHeaderSize, + NewBody, + integer_to_list(AccLength)]}; + <> -> + {NewBody,_}= stream(<>, Stream), + decode_trailer(<>, [],[], MaxHeaderSize, + NewBody, + integer_to_list(AccLength)); + %% There are more chunks, so here we go agin... + <> + when (AccLength < MaxBodySize) or (MaxBodySize == nolimit) -> + {NewBody, NewStream} = + stream(<>, Stream), + decode_size(Rest, [], + {MaxBodySize, NewBody, + AccLength, MaxHeaderSize, NewStream}); + <<_:ChunkSize/binary, ?CR, ?LF, _/binary>> -> + throw({error, body_too_big}); + _ -> + {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]} + end; +decode_data(ChunkSize, TotalChunk, Info) -> + {?MODULE, decode_data, [ChunkSize, TotalChunk, Info]}. + +decode_trailer(<<>>, Header, Headers, MaxHeaderSize, Body, BodyLength) -> + {?MODULE, decode_trailer, [<<>>, Header, Headers, MaxHeaderSize, Body, + BodyLength]}; + +%% Note: If Bin is not empty it is part of a pipelined request/response. +decode_trailer(<>, [], [], _, Body, BodyLength) -> + {ok, {["content-length:" ++ BodyLength], <>}}; +decode_trailer(<>, + Header, Headers, MaxHeaderSize, Body, BodyLength) -> + NewHeaders = case Header of + [] -> + Headers; + _ -> + [lists:reverse(Header) | Headers] + end, + Length = length(NewHeaders), + case Length > MaxHeaderSize of + true -> + throw({error, {header_too_long, MaxHeaderSize, + MaxHeaderSize-Length}}); + false -> + {ok, {["content-length:" ++ BodyLength | NewHeaders], + <>}} + end; +decode_trailer(<> = Data, Header, Headers, MaxHeaderSize, + Body, BodyLength) -> + {?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body, + BodyLength]}; +decode_trailer(<> = Data, Header, Headers, MaxHeaderSize, + Body, BodyLength) -> + {?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body, + BodyLength]}; +decode_trailer(<> = Data, Header, Headers, MaxHeaderSize, + Body, BodyLength) -> + {?MODULE, decode_trailer, [Data, Header, Headers, MaxHeaderSize, Body, + BodyLength]}; +decode_trailer(<>, Header, Headers, + MaxHeaderSize, Body, BodyLength) -> + decode_trailer(Rest, [], [lists:reverse(Header) | Headers], + MaxHeaderSize, Body, BodyLength); + +decode_trailer(<>, Header, Headers, MaxHeaderSize, Body, + BodyLength) -> + decode_trailer(Rest, [Octet | Header], Headers, MaxHeaderSize, + Body, BodyLength). + +stream(BodyPart, false) -> + {BodyPart, false}; +stream(BodyPart, {Code, Request}) -> + {NewBody, NewRequest} = httpc_handler:stream(BodyPart, Request, Code), + {NewBody, {Code, NewRequest}}. diff --git a/lib/inets/src/http_lib/http_internal.hrl b/lib/inets/src/http_lib/http_internal.hrl new file mode 100644 index 0000000000..bb2e831727 --- /dev/null +++ b/lib/inets/src/http_lib/http_internal.hrl @@ -0,0 +1,108 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2002-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% +%% +%% + +-include("inets_internal.hrl"). + +-define(HTTP_MAX_BODY_SIZE, nolimit). +-define(HTTP_MAX_HEADER_SIZE, 10240). +-define(HTTP_MAX_URI_SIZE, nolimit). + +%%% Response headers +-record(http_response_h,{ +%%% --- Standard "General" headers + 'cache-control', + connection, + date, + pragma, + trailer, + 'transfer-encoding', + upgrade, + via, + warning, +%%% --- Standard "Response" headers + 'accept-ranges', + age, + etag, + location, + 'proxy-authenticate', + 'retry-after', + server, + vary, + 'www-authenticate', +%%% --- Standard "Entity" headers + allow, + 'content-encoding', + 'content-language', + 'content-length' = "-1", + 'content-location', + 'content-md5', + 'content-range', + 'content-type', + expires, + 'last-modified', + other=[] % list() - Key/Value list with other headers + }). + + +%%% Request headers +-record(http_request_h,{ +%%% --- Standard "General" headers + 'cache-control', + connection = "keep-alive", + date, + pragma, + trailer, + 'transfer-encoding', + upgrade, + via, + warning, +%%% --- Standard "Request" headers + accept, + 'accept-charset', + 'accept-encoding', + 'accept-language', + authorization, + expect, + from, + host, + 'if-match', + 'if-modified-since', + 'if-none-match', + 'if-range', + 'if-unmodified-since', + 'max-forwards', + 'proxy-authorization', + range, + referer, + te, + 'user-agent', +%%% --- Standard "Entity" headers + allow, + 'content-encoding', + 'content-language', + 'content-length' = "0", + 'content-location', + 'content-md5', + 'content-range', + 'content-type', + expires, + 'last-modified', + other=[] % list() - Key/Value list with other headers + }). diff --git a/lib/inets/src/http_lib/http_request.erl b/lib/inets/src/http_lib/http_request.erl new file mode 100644 index 0000000000..c214aca4a4 --- /dev/null +++ b/lib/inets/src/http_lib/http_request.erl @@ -0,0 +1,281 @@ +%% +%% %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(Key, Value) -> + Key ++ ": " ++ Value ++ ?CRLF. + +headers_other([], Headers) -> + Headers; +headers_other([{Key,Value} | Rest], Headers) -> + Header = Key ++ ": " ++ Value ++ ?CRLF, + headers_other(Rest, [Header | Headers]). diff --git a/lib/inets/src/http_lib/http_response.erl b/lib/inets/src/http_lib/http_response.erl new file mode 100644 index 0000000000..b1e7f1e647 --- /dev/null +++ b/lib/inets/src/http_lib/http_response.erl @@ -0,0 +1,208 @@ +%% +%% %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_response). + +-include("http_internal.hrl"). + +-export([headers/2, header_list/1]). + +%%------------------------------------------------------------------------- +%% headers(HeaderList, #http_response_h{}) -> #http_response_h{} +%% HeaderList - ["HeaderField:Value"] +%% HeaderField - string() +%% Value - string() +%% +%% Description: Creates a http_response_h-record used internally to +%% handle http-headers. +%%------------------------------------------------------------------------- +headers([], Headers) -> + Headers; + +headers([Header | Tail], Headers) -> + {Key, [$: | Value]} = + lists:splitwith(fun($:) -> false; (_) -> true end, Header), + headers(Tail, headers(http_util:to_lower(string:strip(Key)), + string:strip(Value), Headers)). + +%%------------------------------------------------------------------------- +%% headers(#http_response_h{}) -> HeaderList +%% HeaderList - [{"HeaderField", Value"}] +%% HeaderField - string() +%% Value - string() +%% +%% Description: Creates a list of key value tuples from the #http_response_h +%% record, to be returned to the application programmer. We +%% do not wish to make the application programmer dependent on +%% our records. +%%------------------------------------------------------------------------- +header_list(Headers) -> + HeaderFields = record_info(fields, http_response_h) -- [other], + HeaderList = lists:foldl(fun(Key, Acc) -> + case key_value_tuple(Key, Headers) of + undefined -> + Acc; + Tuple -> + [Tuple | Acc] + end + end, + [], HeaderFields), + lists:reverse(HeaderList) ++ Headers#http_response_h.other. +%%%======================================================================== +%%% Internal functions +%%%======================================================================== +headers("cache-control", Value, Headers) -> + Headers#http_response_h{'cache-control'= Value}; +headers("connection", Value, Headers) -> + Headers#http_response_h{connection = Value}; +headers("date", Value, Headers) -> + Headers#http_response_h{date = Value}; +headers("pragma", Value, Headers) -> + Headers#http_response_h{pragma = Value}; +headers("trailer", Value, Headers) -> + Headers#http_response_h{trailer = Value}; +headers("transfer-encoding", Value, Headers) -> + Headers#http_response_h{'transfer-encoding' = Value}; +headers("upgrade", Value, Headers) -> + Headers#http_response_h{upgrade = Value}; +headers("via", Value, Headers) -> + Headers#http_response_h{via = Value}; +headers("warning", Value, Headers) -> + Headers#http_response_h{warning = Value}; +headers("accept-ranges", Value, Headers) -> + Headers#http_response_h{'accept-ranges' = Value}; +headers("age", Value, Headers) -> + Headers#http_response_h{age = Value}; +headers("etag", Value, Headers) -> + Headers#http_response_h{etag = Value}; +headers("location", Value, Headers) -> + Headers#http_response_h{location = Value}; +headers("proxy-authenticate", Value, Headers) -> + Headers#http_response_h{'proxy-authenticate' = Value}; +headers("retry-after", Value, Headers) -> + Headers#http_response_h{'retry-after' = Value}; +headers("server", Value, Headers) -> + Headers#http_response_h{server = Value}; +headers("vary", Value, Headers) -> + Headers#http_response_h{vary = Value}; +headers("www-authenticate", Value, Headers) -> + Headers#http_response_h{'www-authenticate' = Value}; +headers("allow", Value, Headers) -> + Headers#http_response_h{allow = Value}; +headers("content-encoding", Value, Headers) -> + Headers#http_response_h{'content-encoding' = Value}; +headers("content-language", Value, Headers) -> + Headers#http_response_h{'content-language' = Value}; +headers("content-length", Value, Headers) -> + Headers#http_response_h{'content-length' = Value}; +headers("content-location", Value, Headers) -> + Headers#http_response_h{'content-location' = Value}; +headers("content-md5", Value, Headers) -> + Headers#http_response_h{'content-md5' = Value}; +headers("content-range", Value, Headers) -> + Headers#http_response_h{'content-range' = Value}; +headers("content-type", Value, Headers) -> + Headers#http_response_h{'content-type' = Value}; +headers("expires", Value, Headers) -> + Headers#http_response_h{expires = Value}; +headers("last-modified", Value, Headers) -> + Headers#http_response_h{'last-modified' = Value}; +headers(Key, Value, Headers) -> + Headers#http_response_h{other= + [{Key, Value} | Headers#http_response_h.other]}. + + +key_value_tuple(Key = 'cache-control', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'cache-control'); +key_value_tuple(Key = connection, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.connection); +key_value_tuple(Key = date, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.date); +key_value_tuple(Key = pragma, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.pragma); +key_value_tuple(Key = trailer, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.trailer); +key_value_tuple(Key ='transfer-encoding', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'transfer-encoding'); +key_value_tuple(Key = upgrade, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.upgrade); +key_value_tuple(Key = via, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.via); +key_value_tuple(Key = warning, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.warning); +key_value_tuple(Key = 'accept-ranges', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'accept-ranges'); +key_value_tuple(Key = age, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.age); +key_value_tuple(Key = etag, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.etag); +key_value_tuple(Key = location, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.location); +key_value_tuple(Key = 'proxy-authenticate', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'proxy-authenticate'); +key_value_tuple(Key = 'retry-after', Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.'retry-after'); +key_value_tuple(Key = server, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.server); +key_value_tuple(Key = vary, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.vary); +key_value_tuple(Key = 'www-authenticate', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'www-authenticate'); +key_value_tuple(Key = allow, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.allow); +key_value_tuple(Key = 'content-encoding', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'content-encoding'); +key_value_tuple(Key = 'content-language', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'content-language'); +key_value_tuple(Key = 'content-length', Headers) -> + case Headers#http_response_h.'content-length' of + "-1" -> + undefined; + _ -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'content-length') + end; +key_value_tuple(Key = 'content-location', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'content-location'); +key_value_tuple(Key = 'content-md5', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'content-md5'); +key_value_tuple(Key = 'content-range', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'content-range'); +key_value_tuple(Key = 'content-type', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'content-type'); +key_value_tuple(Key = expires, Headers) -> + key_value_tuple(atom_to_list(Key), Headers#http_response_h.expires); +key_value_tuple(Key = 'last-modified', Headers) -> + key_value_tuple(atom_to_list(Key), + Headers#http_response_h.'last-modified'); +key_value_tuple(_, undefined) -> + undefined; +key_value_tuple(Key, Value) -> + {Key, Value}. diff --git a/lib/inets/src/http_lib/http_transport.erl b/lib/inets/src/http_lib/http_transport.erl new file mode 100644 index 0000000000..8100d7183a --- /dev/null +++ b/lib/inets/src/http_lib/http_transport.erl @@ -0,0 +1,353 @@ +%% +%% %CopyrightBegin% +%% +%% Copyright Ericsson AB 2004-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_transport). + +% Internal application API +-export([start/1, connect/3, connect/4, listen/2, listen/3, + accept/2, accept/3, close/2, + send/3, controlling_process/3, setopts/3, + peername/2, resolve/0]). + +-export([negotiate/3]). + + +%%%========================================================================= +%%% Internal application API +%%%========================================================================= + +%%------------------------------------------------------------------------- +%% start(SocketType) -> ok | {error, Reason} +%% SocketType = ip_comm | {ssl, _} +%% +%% Description: Makes sure inet_db or ssl is started. +%%------------------------------------------------------------------------- +start(ip_comm) -> + case inet_db:start() of + {ok, _} -> + ok; + {error, {already_started, _}} -> + ok; + Error -> + Error + end; +start({ssl, _}) -> + case ssl:start() of + ok -> + ok; + {error, {already_started,_}} -> + ok; + Error -> + Error + end. + + +%%------------------------------------------------------------------------- +%% connect(SocketType, Address, Options, Timeout) -> +%% {ok, Socket} | {error, Reason} +%% SocketType = ip_comm | {ssl, SslConfig} +%% Address = {Host, Port} +%% Options = [option()] +%% Socket = socket() +%% option() = ipfamily() | {ip, ip_address()} | {port, integer()} +%% ipfamily() = inet | inet6 +%% +%% Description: Connects to the Host and Port specified in HTTPRequest. +%%------------------------------------------------------------------------- + +connect(SocketType, Address, Opts) -> + connect(SocketType, Address, Opts, infinity). + +connect(ip_comm = _SocketType, {Host, Port}, Opts0, Timeout) + when is_list(Opts0) -> + Opts = [binary, {packet, 0}, {active, false}, {reuseaddr, true} | Opts0], + gen_tcp:connect(Host, Port, Opts, Timeout); + +connect({ssl, SslConfig}, {Host, Port}, _, Timeout) -> + Opts = [binary, {active, false}] ++ SslConfig, + ssl:connect(Host, Port, Opts, Timeout); + +connect({erl_ssl, SslConfig}, {Host, Port}, _, Timeout) -> + Opts = [binary, {active, false}, {ssl_imp, new}] ++ SslConfig, + ssl:connect(Host, Port, Opts, Timeout). + + +%%------------------------------------------------------------------------- +%% listen(SocketType, Port) -> {ok, Socket} | {error, Reason} +%% SocketType = ip_comm | {ssl, SSLConfig} +%% Port = integer() +%% Socket = socket() +%% +%% Description: Sets up socket to listen on the port Port on the local +%% host using either gen_tcp or ssl. In the gen_tcp case the port +%% might allready have been initiated by a wrapper-program and is +%% given as an Fd that can be retrieved by init:get_argument. The +%% reason for this to enable a HTTP-server not running as root to use +%% port 80. +%%------------------------------------------------------------------------- +listen(SocketType, Port) -> + listen(SocketType, undefined, Port). + +listen(ip_comm, Addr, Port) -> + case (catch listen_ip_comm(Addr, Port)) of + {'EXIT', Reason} -> + {error, {exit, Reason}}; + Else -> + Else + end; + +listen({ssl, SSLConfig} = Ssl, Addr, Port) -> + Opt = sock_opt(Ssl, Addr, SSLConfig), + ssl:listen(Port, Opt); + +listen({erl_ssl, SSLConfig} = Ssl, Addr, Port) -> + Opt = sock_opt(Ssl, Addr, SSLConfig), + ssl:listen(Port, [{ssl_imp, new} | Opt]). + + +listen_ip_comm(Addr, Port) -> + {NewPort, Opts, IpFamily} = get_socket_info(Addr, Port), + case IpFamily of + inet6fb4 -> + Opts2 = [inet6 | Opts], + case (catch gen_tcp:listen(NewPort, Opts2)) of + {error, Reason} when ((Reason =:= nxdomain) orelse + (Reason =:= eafnosupport)) -> + Opts3 = [inet | Opts], + gen_tcp:listen(NewPort, Opts3); + + %% This is when a given hostname has resolved to a + %% IPv4-address. The inet6-option together with a + %% {ip, IPv4} option results in badarg + {'EXIT', _} -> + Opts3 = [inet | Opts], + gen_tcp:listen(NewPort, Opts3); + + Other -> + Other + end; + _ -> + Opts2 = [IpFamily | Opts], + gen_tcp:listen(NewPort, Opts2) + end. + +ipfamily_default(Addr, Port) -> + httpd_conf:lookup(Addr, Port, ipfamily, inet6fb4). + +get_socket_info(Addr, Port) -> + Key = list_to_atom("httpd_" ++ integer_to_list(Port)), + BaseOpts = [{backlog, 128}, {reuseaddr, true}], + IpFamilyDefault = ipfamily_default(Addr, Port), + case init:get_argument(Key) of + {ok, [[Value]]} -> + {Fd, IpFamily} = + case string:tokens(Value, [$|]) of + [FdStr, IpFamilyStr] -> + Fd0 = fd_of(FdStr), + IpFamily0 = ip_family_of(IpFamilyStr), + {Fd0, IpFamily0}; + [FdStr] -> + {fd_of(FdStr), IpFamilyDefault}; + _ -> + throw({error, {bad_descriptor, Value}}) + end, + {0, sock_opt(ip_comm, Addr, [{fd, Fd} | BaseOpts]), IpFamily}; + error -> + {Port, sock_opt(ip_comm, Addr, BaseOpts), IpFamilyDefault} + end. + + +fd_of(FdStr) -> + case (catch list_to_integer(FdStr)) of + Fd when is_integer(Fd) -> + Fd; + _ -> + throw({error, {bad_descriptor, FdStr}}) + end. + +ip_family_of(IpFamilyStr) -> + IpFamily = list_to_atom(IpFamilyStr), + case lists:member(IpFamily, [inet, inet6, inet6fb4]) of + true -> + IpFamily; + false -> + throw({error, {bad_ipfamily, IpFamilyStr}}) + end. + + +%%------------------------------------------------------------------------- +%% accept(SocketType, ListenSocket) -> {ok, Socket} | {error, Reason} +%% accept(SocketType, ListenSocket, Timeout) -> ok | {error, Reason} +%% SocketType = ip_comm | {ssl, SSLConfig} +%% ListenSocket = socket() +%% Timeout = infinity | integer() >= 0 +%% Socket = socket() +%% +%% Description: Accepts an incoming connection request on a listen socket, +%% using either gen_tcp or ssl. +%%------------------------------------------------------------------------- +accept(SocketType, ListenSocket) -> + accept(SocketType, ListenSocket, infinity). +accept(ip_comm, ListenSocket, Timeout) -> + gen_tcp:accept(ListenSocket, Timeout); +accept({ssl,_SSLConfig}, ListenSocket, Timeout) -> + ssl:transport_accept(ListenSocket, Timeout). + +%%------------------------------------------------------------------------- +%% controlling_process(SocketType, Socket, NewOwner) -> ok | {error, Reason} +%% SocketType = ip_comm | {ssl, _} +%% Socket = socket() +%% NewOwner = pid() +%% +%% Description: Assigns a new controlling process to Socket. +%%------------------------------------------------------------------------- +controlling_process(ip_comm, Socket, NewOwner) -> + gen_tcp:controlling_process(Socket, NewOwner); +controlling_process({ssl, _}, Socket, NewOwner) -> + ssl:controlling_process(Socket, NewOwner). + +%%------------------------------------------------------------------------- +%% setopts(SocketType, Socket, Options) -> ok | {error, Reason} +%% SocketType = ip_comm | {ssl, _} +%% Socket = socket() +%% Options = list() +%% Description: Sets one or more options for a socket, using either +%% gen_tcp or ssl. +%%------------------------------------------------------------------------- +setopts(ip_comm, Socket, Options) -> + inet:setopts(Socket,Options); +setopts({ssl, _}, Socket, Options) -> + ssl:setopts(Socket, Options). + +%%------------------------------------------------------------------------- +%% send(RequestOrSocketType, Socket, Message) -> ok | {error, Reason} +%% SocketType = ip_comm | {ssl, _} +%% Socket = socket() +%% Message = list() | binary() +%% Description: Sends a packet on a socket, using either gen_tcp or ssl. +%%------------------------------------------------------------------------- +send(ip_comm, Socket, Message) -> + gen_tcp:send(Socket, Message); +send({ssl, _}, Socket, Message) -> + ssl:send(Socket, Message). + +%%------------------------------------------------------------------------- +%% close(SocketType, Socket) -> ok | {error, Reason} +%% SocketType = ip_comm | {ssl, _} +%% Socket = socket() +%% +%% Description: Closes a socket, using either gen_tcp or ssl. +%%------------------------------------------------------------------------- +close(ip_comm, Socket) -> + gen_tcp:close(Socket); +close({ssl, _}, Socket) -> + ssl:close(Socket). + +%%------------------------------------------------------------------------- +%% peername(SocketType, Socket) -> ok | {error, Reason} +%% SocketType = ip_comm | {ssl, _} +%% Socket = socket() +%% +%% Description: Returns the address and port for the other end of a +%% connection, usning either gen_tcp or ssl. +%%------------------------------------------------------------------------- +peername(ip_comm, Socket) -> + case inet:peername(Socket) of + {ok,{{A, B, C, D}, Port}} -> + PeerName = integer_to_list(A)++"."++integer_to_list(B)++"."++ + integer_to_list(C)++"."++integer_to_list(D), + {Port, PeerName}; + {ok,{{A, B, C, D, E, F, G, H}, Port}} -> + PeerName = http_util:integer_to_hexlist(A) ++ ":"++ + http_util:integer_to_hexlist(B) ++ ":" ++ + http_util:integer_to_hexlist(C) ++ ":" ++ + http_util:integer_to_hexlist(D) ++ ":" ++ + http_util:integer_to_hexlist(E) ++ ":" ++ + http_util:integer_to_hexlist(F) ++ ":" ++ + http_util:integer_to_hexlist(G) ++":"++ + http_util:integer_to_hexlist(H), + {Port, PeerName}; + {error, _} -> + {-1, "unknown"} + end; + +peername({ssl, _}, Socket) -> + case ssl:peername(Socket) of + {ok,{{A, B, C, D}, Port}} -> + PeerName = integer_to_list(A)++"."++integer_to_list(B)++"."++ + integer_to_list(C)++"."++integer_to_list(D), + {Port, PeerName}; + {error, _} -> + {-1, "unknown"} + end. + +%%------------------------------------------------------------------------- +%% resolve() -> HostName +%% HostName = string() +%% +%% Description: Returns the local hostname. +%%------------------------------------------------------------------------- +resolve() -> + {ok, Name} = inet:gethostname(), + Name. + + +%%%======================================================================== +%%% Internal functions +%%%======================================================================== + +%% Address any comes from directive: BindAddress "*" +sock_opt(ip_comm, any = Addr, Opts) -> + sock_opt2([{ip, Addr} | Opts]); +sock_opt(ip_comm, undefined, Opts) -> + sock_opt2(Opts); +sock_opt(_, any = _Addr, Opts) -> + sock_opt2(Opts); +sock_opt(_, undefined = _Addr, Opts) -> + sock_opt2(Opts); +sock_opt(_, {_,_,_,_} = Addr, Opts) -> + sock_opt2([{ip, Addr} | Opts]); +sock_opt(ip_comm, Addr, Opts) -> + sock_opt2([{ip, Addr} | Opts]); +sock_opt(_, Addr, Opts) -> + sock_opt2([{ip, Addr} | Opts]). + +sock_opt2(Opts) -> + [{packet, 0}, {active, false} | Opts]. + +negotiate(ip_comm,_,_) -> + ok; +negotiate({ssl,_},Socket,Timeout) -> + negotiate(Socket, Timeout); +negotiate({erl_ssl, _}, Socket, Timeout) -> + negotiate(Socket, Timeout). + +negotiate(Socket, Timeout) -> + case ssl:ssl_accept(Socket, Timeout) of + ok -> + ok; + {error, Error} -> + case lists:member(Error, + [timeout,econnreset,esslaccept,esslerrssl]) of + true -> + {error,normal}; + false -> + {error, Error} + end + end. diff --git a/lib/inets/src/http_lib/http_util.erl b/lib/inets/src/http_lib/http_util.erl new file mode 100644 index 0000000000..b03b780cf8 --- /dev/null +++ b/lib/inets/src/http_lib/http_util.erl @@ -0,0 +1,148 @@ +%% +%% %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_util). + +-export([to_upper/1, to_lower/1, convert_netscapecookie_date/1, + hexlist_to_integer/1, integer_to_hexlist/1, + convert_month/1, is_hostname/1]). + +%%%========================================================================= +%%% Internal application API +%%%========================================================================= +to_upper(Str) -> + string:to_upper(Str). + +to_lower(Str) -> + string:to_lower(Str). + +convert_netscapecookie_date([_D,_A,_Y, $,, _SP, + D1,D2,_DA, + M,O,N,_DA, + Y1,Y2,Y3,Y4,_SP, + H1,H2,_Col, + M1,M2,_Col, + S1,S2|_Rest]) -> + Year=list_to_integer([Y1,Y2,Y3,Y4]), + Day=list_to_integer([D1,D2]), + Month=convert_month([M,O,N]), + Hour=list_to_integer([H1,H2]), + Min=list_to_integer([M1,M2]), + Sec=list_to_integer([S1,S2]), + {{Year,Month,Day},{Hour,Min,Sec}}; + +convert_netscapecookie_date([_D,_A,_Y, _SP, + D1,D2,_DA, + M,O,N,_DA, + Y1,Y2,Y3,Y4,_SP, + H1,H2,_Col, + M1,M2,_Col, + S1,S2|_Rest]) -> + Year=list_to_integer([Y1,Y2,Y3,Y4]), + Day=list_to_integer([D1,D2]), + Month=convert_month([M,O,N]), + Hour=list_to_integer([H1,H2]), + Min=list_to_integer([M1,M2]), + Sec=list_to_integer([S1,S2]), + {{Year,Month,Day},{Hour,Min,Sec}}. + +hexlist_to_integer([])-> + empty; +%%When the string only contains one value its eaasy done. +%% 0-9 +hexlist_to_integer([Size]) when Size >= 48 , Size =< 57 -> + Size - 48; +%% A-F +hexlist_to_integer([Size]) when Size >= 65 , Size =< 70 -> + Size - 55; +%% a-f +hexlist_to_integer([Size]) when Size >= 97 , Size =< 102 -> + Size - 87; +hexlist_to_integer([_Size]) -> + not_a_num; + +hexlist_to_integer(Size) -> + Len = string:span(Size, "1234567890abcdefABCDEF"), + hexlist_to_integer2(Size, 16 bsl (4 *(Len-2)),0). + +integer_to_hexlist(Num)-> + integer_to_hexlist(Num, get_size(Num), []). + +convert_month("Jan") -> 1; +convert_month("Feb") -> 2; +convert_month("Mar") -> 3; +convert_month("Apr") -> 4; +convert_month("May") -> 5; +convert_month("Jun") -> 6; +convert_month("Jul") -> 7; +convert_month("Aug") -> 8; +convert_month("Sep") -> 9; +convert_month("Oct") -> 10; +convert_month("Nov") -> 11; +convert_month("Dec") -> 12. + +is_hostname(Dest) -> + inet_parse:domain(Dest). + +%%%======================================================================== +%%% Internal functions +%%%======================================================================== +hexlist_to_integer2([],_Pos,Sum)-> + Sum; +hexlist_to_integer2([HexVal | HexString], Pos, Sum) + when HexVal >= 48, HexVal =< 57 -> + hexlist_to_integer2(HexString, Pos bsr 4, Sum + ((HexVal-48) * Pos)); + +hexlist_to_integer2([HexVal | HexString], Pos, Sum) + when HexVal >= 65, HexVal =<70 -> + hexlist_to_integer2(HexString, Pos bsr 4, Sum + ((HexVal-55) * Pos)); + +hexlist_to_integer2([HexVal | HexString], Pos, Sum) + when HexVal>=97, HexVal=<102 -> + hexlist_to_integer2(HexString, Pos bsr 4, Sum + ((HexVal-87) * Pos)); + +hexlist_to_integer2(_AfterHexString, _Pos, Sum)-> + Sum. + +integer_to_hexlist(Num, Pot, Res) when Pot<0 -> + convert_to_ascii([Num | Res]); + +integer_to_hexlist(Num,Pot,Res) -> + Position = (16 bsl (Pot*4)), + PosVal = Num div Position, + integer_to_hexlist(Num - (PosVal*Position), Pot-1, [PosVal | Res]). + +get_size(Num)-> + get_size(Num, 0). + +get_size(Num, Pot) when Num < (16 bsl(Pot *4)) -> + Pot-1; + +get_size(Num, Pot) -> + get_size(Num, Pot+1). + +convert_to_ascii(RevesedNum) -> + convert_to_ascii(RevesedNum, []). + +convert_to_ascii([], Num)-> + Num; +convert_to_ascii([Num | Reversed], Number) when Num > -1, Num < 10 -> + convert_to_ascii(Reversed, [Num + 48 | Number]); +convert_to_ascii([Num | Reversed], Number) when Num > 9, Num < 16 -> + convert_to_ascii(Reversed, [Num + 55 | Number]). -- cgit v1.2.3