You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by ja...@apache.org on 2012/11/12 21:48:52 UTC
[5/12] update mochiweb to 2.3.2
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6fdb9e07/src/mochiweb/mochiweb_multipart.erl
----------------------------------------------------------------------
diff --git a/src/mochiweb/mochiweb_multipart.erl b/src/mochiweb/mochiweb_multipart.erl
deleted file mode 100644
index 3069cf4..0000000
--- a/src/mochiweb/mochiweb_multipart.erl
+++ /dev/null
@@ -1,824 +0,0 @@
-%% @author Bob Ippolito <bo...@mochimedia.com>
-%% @copyright 2007 Mochi Media, Inc.
-
-%% @doc Utilities for parsing multipart/form-data.
-
--module(mochiweb_multipart).
--author('bob@mochimedia.com').
-
--export([parse_form/1, parse_form/2]).
--export([parse_multipart_request/2]).
--export([parts_to_body/3, parts_to_multipart_body/4]).
--export([default_file_handler/2]).
-
--define(CHUNKSIZE, 4096).
-
--record(mp, {state, boundary, length, buffer, callback, req}).
-
-%% TODO: DOCUMENT THIS MODULE.
-%% @type key() = atom() | string() | binary().
-%% @type value() = atom() | iolist() | integer().
-%% @type header() = {key(), value()}.
-%% @type bodypart() = {Start::integer(), End::integer(), Body::iolist()}.
-%% @type formfile() = {Name::string(), ContentType::string(), Content::binary()}.
-%% @type request().
-%% @type file_handler() = (Filename::string(), ContentType::string()) -> file_handler_callback().
-%% @type file_handler_callback() = (binary() | eof) -> file_handler_callback() | term().
-
-%% @spec parts_to_body([bodypart()], ContentType::string(),
-%% Size::integer()) -> {[header()], iolist()}
-%% @doc Return {[header()], iolist()} representing the body for the given
-%% parts, may be a single part or multipart.
-parts_to_body([{Start, End, Body}], ContentType, Size) ->
- HeaderList = [{"Content-Type", ContentType},
- {"Content-Range",
- ["bytes ",
- mochiweb_util:make_io(Start), "-", mochiweb_util:make_io(End),
- "/", mochiweb_util:make_io(Size)]}],
- {HeaderList, Body};
-parts_to_body(BodyList, ContentType, Size) when is_list(BodyList) ->
- parts_to_multipart_body(BodyList, ContentType, Size,
- mochihex:to_hex(crypto:rand_bytes(8))).
-
-%% @spec parts_to_multipart_body([bodypart()], ContentType::string(),
-%% Size::integer(), Boundary::string()) ->
-%% {[header()], iolist()}
-%% @doc Return {[header()], iolist()} representing the body for the given
-%% parts, always a multipart response.
-parts_to_multipart_body(BodyList, ContentType, Size, Boundary) ->
- HeaderList = [{"Content-Type",
- ["multipart/byteranges; ",
- "boundary=", Boundary]}],
- MultiPartBody = multipart_body(BodyList, ContentType, Boundary, Size),
-
- {HeaderList, MultiPartBody}.
-
-%% @spec multipart_body([bodypart()], ContentType::string(),
-%% Boundary::string(), Size::integer()) -> iolist()
-%% @doc Return the representation of a multipart body for the given [bodypart()].
-multipart_body([], _ContentType, Boundary, _Size) ->
- ["--", Boundary, "--\r\n"];
-multipart_body([{Start, End, Body} | BodyList], ContentType, Boundary, Size) ->
- ["--", Boundary, "\r\n",
- "Content-Type: ", ContentType, "\r\n",
- "Content-Range: ",
- "bytes ", mochiweb_util:make_io(Start), "-", mochiweb_util:make_io(End),
- "/", mochiweb_util:make_io(Size), "\r\n\r\n",
- Body, "\r\n"
- | multipart_body(BodyList, ContentType, Boundary, Size)].
-
-%% @spec parse_form(request()) -> [{string(), string() | formfile()}]
-%% @doc Parse a multipart form from the given request using the in-memory
-%% default_file_handler/2.
-parse_form(Req) ->
- parse_form(Req, fun default_file_handler/2).
-
-%% @spec parse_form(request(), F::file_handler()) -> [{string(), string() | term()}]
-%% @doc Parse a multipart form from the given request using the given file_handler().
-parse_form(Req, FileHandler) ->
- Callback = fun (Next) -> parse_form_outer(Next, FileHandler, []) end,
- {_, _, Res} = parse_multipart_request(Req, Callback),
- Res.
-
-parse_form_outer(eof, _, Acc) ->
- lists:reverse(Acc);
-parse_form_outer({headers, H}, FileHandler, State) ->
- {"form-data", H1} = proplists:get_value("content-disposition", H),
- Name = proplists:get_value("name", H1),
- Filename = proplists:get_value("filename", H1),
- case Filename of
- undefined ->
- fun (Next) ->
- parse_form_value(Next, {Name, []}, FileHandler, State)
- end;
- _ ->
- ContentType = proplists:get_value("content-type", H),
- Handler = FileHandler(Filename, ContentType),
- fun (Next) ->
- parse_form_file(Next, {Name, Handler}, FileHandler, State)
- end
- end.
-
-parse_form_value(body_end, {Name, Acc}, FileHandler, State) ->
- Value = binary_to_list(iolist_to_binary(lists:reverse(Acc))),
- State1 = [{Name, Value} | State],
- fun (Next) -> parse_form_outer(Next, FileHandler, State1) end;
-parse_form_value({body, Data}, {Name, Acc}, FileHandler, State) ->
- Acc1 = [Data | Acc],
- fun (Next) -> parse_form_value(Next, {Name, Acc1}, FileHandler, State) end.
-
-parse_form_file(body_end, {Name, Handler}, FileHandler, State) ->
- Value = Handler(eof),
- State1 = [{Name, Value} | State],
- fun (Next) -> parse_form_outer(Next, FileHandler, State1) end;
-parse_form_file({body, Data}, {Name, Handler}, FileHandler, State) ->
- H1 = Handler(Data),
- fun (Next) -> parse_form_file(Next, {Name, H1}, FileHandler, State) end.
-
-default_file_handler(Filename, ContentType) ->
- default_file_handler_1(Filename, ContentType, []).
-
-default_file_handler_1(Filename, ContentType, Acc) ->
- fun(eof) ->
- Value = iolist_to_binary(lists:reverse(Acc)),
- {Filename, ContentType, Value};
- (Next) ->
- default_file_handler_1(Filename, ContentType, [Next | Acc])
- end.
-
-parse_multipart_request(Req, Callback) ->
- %% TODO: Support chunked?
- Length = list_to_integer(Req:get_header_value("content-length")),
- Boundary = iolist_to_binary(
- get_boundary(Req:get_header_value("content-type"))),
- Prefix = <<"\r\n--", Boundary/binary>>,
- BS = byte_size(Boundary),
- Chunk = read_chunk(Req, Length),
- Length1 = Length - byte_size(Chunk),
- <<"--", Boundary:BS/binary, "\r\n", Rest/binary>> = Chunk,
- feed_mp(headers, flash_multipart_hack(#mp{boundary=Prefix,
- length=Length1,
- buffer=Rest,
- callback=Callback,
- req=Req})).
-
-parse_headers(<<>>) ->
- [];
-parse_headers(Binary) ->
- parse_headers(Binary, []).
-
-parse_headers(Binary, Acc) ->
- case find_in_binary(<<"\r\n">>, Binary) of
- {exact, N} ->
- <<Line:N/binary, "\r\n", Rest/binary>> = Binary,
- parse_headers(Rest, [split_header(Line) | Acc]);
- not_found ->
- lists:reverse([split_header(Binary) | Acc])
- end.
-
-split_header(Line) ->
- {Name, [$: | Value]} = lists:splitwith(fun (C) -> C =/= $: end,
- binary_to_list(Line)),
- {string:to_lower(string:strip(Name)),
- mochiweb_util:parse_header(Value)}.
-
-read_chunk(Req, Length) when Length > 0 ->
- case Length of
- Length when Length < ?CHUNKSIZE ->
- Req:recv(Length);
- _ ->
- Req:recv(?CHUNKSIZE)
- end.
-
-read_more(State=#mp{length=Length, buffer=Buffer, req=Req}) ->
- Data = read_chunk(Req, Length),
- Buffer1 = <<Buffer/binary, Data/binary>>,
- flash_multipart_hack(State#mp{length=Length - byte_size(Data),
- buffer=Buffer1}).
-
-flash_multipart_hack(State=#mp{length=0, buffer=Buffer, boundary=Prefix}) ->
- %% http://code.google.com/p/mochiweb/issues/detail?id=22
- %% Flash doesn't terminate multipart with \r\n properly so we fix it up here
- PrefixSize = size(Prefix),
- case size(Buffer) - (2 + PrefixSize) of
- Seek when Seek >= 0 ->
- case Buffer of
- <<_:Seek/binary, Prefix:PrefixSize/binary, "--">> ->
- Buffer1 = <<Buffer/binary, "\r\n">>,
- State#mp{buffer=Buffer1};
- _ ->
- State
- end;
- _ ->
- State
- end;
-flash_multipart_hack(State) ->
- State.
-
-feed_mp(headers, State=#mp{buffer=Buffer, callback=Callback}) ->
- {State1, P} = case find_in_binary(<<"\r\n\r\n">>, Buffer) of
- {exact, N} ->
- {State, N};
- _ ->
- S1 = read_more(State),
- %% Assume headers must be less than ?CHUNKSIZE
- {exact, N} = find_in_binary(<<"\r\n\r\n">>,
- S1#mp.buffer),
- {S1, N}
- end,
- <<Headers:P/binary, "\r\n\r\n", Rest/binary>> = State1#mp.buffer,
- NextCallback = Callback({headers, parse_headers(Headers)}),
- feed_mp(body, State1#mp{buffer=Rest,
- callback=NextCallback});
-feed_mp(body, State=#mp{boundary=Prefix, buffer=Buffer, callback=Callback}) ->
- Boundary = find_boundary(Prefix, Buffer),
- case Boundary of
- {end_boundary, Start, Skip} ->
- <<Data:Start/binary, _:Skip/binary, Rest/binary>> = Buffer,
- C1 = Callback({body, Data}),
- C2 = C1(body_end),
- {State#mp.length, Rest, C2(eof)};
- {next_boundary, Start, Skip} ->
- <<Data:Start/binary, _:Skip/binary, Rest/binary>> = Buffer,
- C1 = Callback({body, Data}),
- feed_mp(headers, State#mp{callback=C1(body_end),
- buffer=Rest});
- {maybe, Start} ->
- <<Data:Start/binary, Rest/binary>> = Buffer,
- feed_mp(body, read_more(State#mp{callback=Callback({body, Data}),
- buffer=Rest}));
- not_found ->
- {Data, Rest} = {Buffer, <<>>},
- feed_mp(body, read_more(State#mp{callback=Callback({body, Data}),
- buffer=Rest}))
- end.
-
-get_boundary(ContentType) ->
- {"multipart/form-data", Opts} = mochiweb_util:parse_header(ContentType),
- case proplists:get_value("boundary", Opts) of
- S when is_list(S) ->
- S
- end.
-
-find_in_binary(B, Data) when size(B) > 0 ->
- case size(Data) - size(B) of
- Last when Last < 0 ->
- partial_find(B, Data, 0, size(Data));
- Last ->
- find_in_binary(B, size(B), Data, 0, Last)
- end.
-
-find_in_binary(B, BS, D, N, Last) when N =< Last->
- case D of
- <<_:N/binary, B:BS/binary, _/binary>> ->
- {exact, N};
- _ ->
- find_in_binary(B, BS, D, 1 + N, Last)
- end;
-find_in_binary(B, BS, D, N, Last) when N =:= 1 + Last ->
- partial_find(B, D, N, BS - 1).
-
-partial_find(_B, _D, _N, 0) ->
- not_found;
-partial_find(B, D, N, K) ->
- <<B1:K/binary, _/binary>> = B,
- case D of
- <<_Skip:N/binary, B1:K/binary>> ->
- {partial, N, K};
- _ ->
- partial_find(B, D, 1 + N, K - 1)
- end.
-
-find_boundary(Prefix, Data) ->
- case find_in_binary(Prefix, Data) of
- {exact, Skip} ->
- PrefixSkip = Skip + size(Prefix),
- case Data of
- <<_:PrefixSkip/binary, "\r\n", _/binary>> ->
- {next_boundary, Skip, size(Prefix) + 2};
- <<_:PrefixSkip/binary, "--\r\n", _/binary>> ->
- {end_boundary, Skip, size(Prefix) + 4};
- _ when size(Data) < PrefixSkip + 4 ->
- %% Underflow
- {maybe, Skip};
- _ ->
- %% False positive
- not_found
- end;
- {partial, Skip, Length} when (Skip + Length) =:= size(Data) ->
- %% Underflow
- {maybe, Skip};
- _ ->
- not_found
- end.
-
-%%
-%% Tests
-%%
--include_lib("eunit/include/eunit.hrl").
--ifdef(TEST).
-
-ssl_cert_opts() ->
- EbinDir = filename:dirname(code:which(?MODULE)),
- CertDir = filename:join([EbinDir, "..", "support", "test-materials"]),
- CertFile = filename:join(CertDir, "test_ssl_cert.pem"),
- KeyFile = filename:join(CertDir, "test_ssl_key.pem"),
- [{certfile, CertFile}, {keyfile, KeyFile}].
-
-with_socket_server(Transport, ServerFun, ClientFun) ->
- ServerOpts0 = [{ip, "127.0.0.1"}, {port, 0}, {loop, ServerFun}],
- ServerOpts = case Transport of
- plain ->
- ServerOpts0;
- ssl ->
- ServerOpts0 ++ [{ssl, true}, {ssl_opts, ssl_cert_opts()}]
- end,
- {ok, Server} = mochiweb_socket_server:start(ServerOpts),
- Port = mochiweb_socket_server:get(Server, port),
- ClientOpts = [binary, {active, false}],
- {ok, Client} = case Transport of
- plain ->
- gen_tcp:connect("127.0.0.1", Port, ClientOpts);
- ssl ->
- ClientOpts1 = [{ssl_imp, new} | ClientOpts],
- {ok, SslSocket} = ssl:connect("127.0.0.1", Port, ClientOpts1),
- {ok, {ssl, SslSocket}}
- end,
- Res = (catch ClientFun(Client)),
- mochiweb_socket_server:stop(Server),
- Res.
-
-fake_request(Socket, ContentType, Length) ->
- mochiweb_request:new(Socket,
- 'POST',
- "/multipart",
- {1,1},
- mochiweb_headers:make(
- [{"content-type", ContentType},
- {"content-length", Length}])).
-
-test_callback({body, <<>>}, Rest=[body_end | _]) ->
- %% When expecting the body_end we might get an empty binary
- fun (Next) -> test_callback(Next, Rest) end;
-test_callback({body, Got}, [{body, Expect} | Rest]) when Got =/= Expect ->
- %% Partial response
- GotSize = size(Got),
- <<Got:GotSize/binary, Expect1/binary>> = Expect,
- fun (Next) -> test_callback(Next, [{body, Expect1} | Rest]) end;
-test_callback(Got, [Expect | Rest]) ->
- ?assertEqual(Got, Expect),
- case Rest of
- [] ->
- ok;
- _ ->
- fun (Next) -> test_callback(Next, Rest) end
- end.
-
-parse3_http_test() ->
- parse3(plain).
-
-parse3_https_test() ->
- parse3(ssl).
-
-parse3(Transport) ->
- ContentType = "multipart/form-data; boundary=---------------------------7386909285754635891697677882",
- BinContent = <<"-----------------------------7386909285754635891697677882\r\nContent-Disposition: form-data; name=\"hidden\"\r\n\r\nmultipart message\r\n-----------------------------7386909285754635891697677882\r\nContent-Disposition: form-data; name=\"file\"; filename=\"test_file.txt\"\r\nContent-Type: text/plain\r\n\r\nWoo multiline text file\n\nLa la la\r\n-----------------------------7386909285754635891697677882--\r\n">>,
- Expect = [{headers,
- [{"content-disposition",
- {"form-data", [{"name", "hidden"}]}}]},
- {body, <<"multipart message">>},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "file"}, {"filename", "test_file.txt"}]}},
- {"content-type", {"text/plain", []}}]},
- {body, <<"Woo multiline text file\n\nLa la la">>},
- body_end,
- eof],
- TestCallback = fun (Next) -> test_callback(Next, Expect) end,
- ServerFun = fun (Socket) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
- ClientFun = fun (Socket) ->
- Req = fake_request(Socket, ContentType,
- byte_size(BinContent)),
- Res = parse_multipart_request(Req, TestCallback),
- {0, <<>>, ok} = Res,
- ok
- end,
- ok = with_socket_server(Transport, ServerFun, ClientFun),
- ok.
-
-parse2_http_test() ->
- parse2(plain).
-
-parse2_https_test() ->
- parse2(ssl).
-
-parse2(Transport) ->
- ContentType = "multipart/form-data; boundary=---------------------------6072231407570234361599764024",
- BinContent = <<"-----------------------------6072231407570234361599764024\r\nContent-Disposition: form-data; name=\"hidden\"\r\n\r\nmultipart message\r\n-----------------------------6072231407570234361599764024\r\nContent-Disposition: form-data; name=\"file\"; filename=\"\"\r\nContent-Type: application/octet-stream\r\n\r\n\r\n-----------------------------6072231407570234361599764024--\r\n">>,
- Expect = [{headers,
- [{"content-disposition",
- {"form-data", [{"name", "hidden"}]}}]},
- {body, <<"multipart message">>},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "file"}, {"filename", ""}]}},
- {"content-type", {"application/octet-stream", []}}]},
- {body, <<>>},
- body_end,
- eof],
- TestCallback = fun (Next) -> test_callback(Next, Expect) end,
- ServerFun = fun (Socket) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
- ClientFun = fun (Socket) ->
- Req = fake_request(Socket, ContentType,
- byte_size(BinContent)),
- Res = parse_multipart_request(Req, TestCallback),
- {0, <<>>, ok} = Res,
- ok
- end,
- ok = with_socket_server(Transport, ServerFun, ClientFun),
- ok.
-
-parse_form_http_test() ->
- do_parse_form(plain).
-
-parse_form_https_test() ->
- do_parse_form(ssl).
-
-do_parse_form(Transport) ->
- ContentType = "multipart/form-data; boundary=AaB03x",
- "AaB03x" = get_boundary(ContentType),
- Content = mochiweb_util:join(
- ["--AaB03x",
- "Content-Disposition: form-data; name=\"submit-name\"",
- "",
- "Larry",
- "--AaB03x",
- "Content-Disposition: form-data; name=\"files\";"
- ++ "filename=\"file1.txt\"",
- "Content-Type: text/plain",
- "",
- "... contents of file1.txt ...",
- "--AaB03x--",
- ""], "\r\n"),
- BinContent = iolist_to_binary(Content),
- ServerFun = fun (Socket) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
- ClientFun = fun (Socket) ->
- Req = fake_request(Socket, ContentType,
- byte_size(BinContent)),
- Res = parse_form(Req),
- [{"submit-name", "Larry"},
- {"files", {"file1.txt", {"text/plain",[]},
- <<"... contents of file1.txt ...">>}
- }] = Res,
- ok
- end,
- ok = with_socket_server(Transport, ServerFun, ClientFun),
- ok.
-
-parse_http_test() ->
- do_parse(plain).
-
-parse_https_test() ->
- do_parse(ssl).
-
-do_parse(Transport) ->
- ContentType = "multipart/form-data; boundary=AaB03x",
- "AaB03x" = get_boundary(ContentType),
- Content = mochiweb_util:join(
- ["--AaB03x",
- "Content-Disposition: form-data; name=\"submit-name\"",
- "",
- "Larry",
- "--AaB03x",
- "Content-Disposition: form-data; name=\"files\";"
- ++ "filename=\"file1.txt\"",
- "Content-Type: text/plain",
- "",
- "... contents of file1.txt ...",
- "--AaB03x--",
- ""], "\r\n"),
- BinContent = iolist_to_binary(Content),
- Expect = [{headers,
- [{"content-disposition",
- {"form-data", [{"name", "submit-name"}]}}]},
- {body, <<"Larry">>},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}},
- {"content-type", {"text/plain", []}}]},
- {body, <<"... contents of file1.txt ...">>},
- body_end,
- eof],
- TestCallback = fun (Next) -> test_callback(Next, Expect) end,
- ServerFun = fun (Socket) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
- ClientFun = fun (Socket) ->
- Req = fake_request(Socket, ContentType,
- byte_size(BinContent)),
- Res = parse_multipart_request(Req, TestCallback),
- {0, <<>>, ok} = Res,
- ok
- end,
- ok = with_socket_server(Transport, ServerFun, ClientFun),
- ok.
-
-parse_partial_body_boundary_http_test() ->
- parse_partial_body_boundary(plain).
-
-parse_partial_body_boundary_https_test() ->
- parse_partial_body_boundary(ssl).
-
-parse_partial_body_boundary(Transport) ->
- Boundary = string:copies("$", 2048),
- ContentType = "multipart/form-data; boundary=" ++ Boundary,
- ?assertEqual(Boundary, get_boundary(ContentType)),
- Content = mochiweb_util:join(
- ["--" ++ Boundary,
- "Content-Disposition: form-data; name=\"submit-name\"",
- "",
- "Larry",
- "--" ++ Boundary,
- "Content-Disposition: form-data; name=\"files\";"
- ++ "filename=\"file1.txt\"",
- "Content-Type: text/plain",
- "",
- "... contents of file1.txt ...",
- "--" ++ Boundary ++ "--",
- ""], "\r\n"),
- BinContent = iolist_to_binary(Content),
- Expect = [{headers,
- [{"content-disposition",
- {"form-data", [{"name", "submit-name"}]}}]},
- {body, <<"Larry">>},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}},
- {"content-type", {"text/plain", []}}
- ]},
- {body, <<"... contents of file1.txt ...">>},
- body_end,
- eof],
- TestCallback = fun (Next) -> test_callback(Next, Expect) end,
- ServerFun = fun (Socket) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
- ClientFun = fun (Socket) ->
- Req = fake_request(Socket, ContentType,
- byte_size(BinContent)),
- Res = parse_multipart_request(Req, TestCallback),
- {0, <<>>, ok} = Res,
- ok
- end,
- ok = with_socket_server(Transport, ServerFun, ClientFun),
- ok.
-
-parse_large_header_http_test() ->
- parse_large_header(plain).
-
-parse_large_header_https_test() ->
- parse_large_header(ssl).
-
-parse_large_header(Transport) ->
- ContentType = "multipart/form-data; boundary=AaB03x",
- "AaB03x" = get_boundary(ContentType),
- Content = mochiweb_util:join(
- ["--AaB03x",
- "Content-Disposition: form-data; name=\"submit-name\"",
- "",
- "Larry",
- "--AaB03x",
- "Content-Disposition: form-data; name=\"files\";"
- ++ "filename=\"file1.txt\"",
- "Content-Type: text/plain",
- "x-large-header: " ++ string:copies("%", 4096),
- "",
- "... contents of file1.txt ...",
- "--AaB03x--",
- ""], "\r\n"),
- BinContent = iolist_to_binary(Content),
- Expect = [{headers,
- [{"content-disposition",
- {"form-data", [{"name", "submit-name"}]}}]},
- {body, <<"Larry">>},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "files"}, {"filename", "file1.txt"}]}},
- {"content-type", {"text/plain", []}},
- {"x-large-header", {string:copies("%", 4096), []}}
- ]},
- {body, <<"... contents of file1.txt ...">>},
- body_end,
- eof],
- TestCallback = fun (Next) -> test_callback(Next, Expect) end,
- ServerFun = fun (Socket) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
- ClientFun = fun (Socket) ->
- Req = fake_request(Socket, ContentType,
- byte_size(BinContent)),
- Res = parse_multipart_request(Req, TestCallback),
- {0, <<>>, ok} = Res,
- ok
- end,
- ok = with_socket_server(Transport, ServerFun, ClientFun),
- ok.
-
-find_boundary_test() ->
- B = <<"\r\n--X">>,
- {next_boundary, 0, 7} = find_boundary(B, <<"\r\n--X\r\nRest">>),
- {next_boundary, 1, 7} = find_boundary(B, <<"!\r\n--X\r\nRest">>),
- {end_boundary, 0, 9} = find_boundary(B, <<"\r\n--X--\r\nRest">>),
- {end_boundary, 1, 9} = find_boundary(B, <<"!\r\n--X--\r\nRest">>),
- not_found = find_boundary(B, <<"--X\r\nRest">>),
- {maybe, 0} = find_boundary(B, <<"\r\n--X\r">>),
- {maybe, 1} = find_boundary(B, <<"!\r\n--X\r">>),
- P = <<"\r\n-----------------------------16037454351082272548568224146">>,
- B0 = <<55,212,131,77,206,23,216,198,35,87,252,118,252,8,25,211,132,229,
- 182,42,29,188,62,175,247,243,4,4,0,59, 13,10,45,45,45,45,45,45,45,
- 45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,45,
- 49,54,48,51,55,52,53,52,51,53,49>>,
- {maybe, 30} = find_boundary(P, B0),
- not_found = find_boundary(B, <<"\r\n--XJOPKE">>),
- ok.
-
-find_in_binary_test() ->
- {exact, 0} = find_in_binary(<<"foo">>, <<"foobarbaz">>),
- {exact, 1} = find_in_binary(<<"oo">>, <<"foobarbaz">>),
- {exact, 8} = find_in_binary(<<"z">>, <<"foobarbaz">>),
- not_found = find_in_binary(<<"q">>, <<"foobarbaz">>),
- {partial, 7, 2} = find_in_binary(<<"azul">>, <<"foobarbaz">>),
- {exact, 0} = find_in_binary(<<"foobarbaz">>, <<"foobarbaz">>),
- {partial, 0, 3} = find_in_binary(<<"foobar">>, <<"foo">>),
- {partial, 1, 3} = find_in_binary(<<"foobar">>, <<"afoo">>),
- ok.
-
-flash_parse_http_test() ->
- flash_parse(plain).
-
-flash_parse_https_test() ->
- flash_parse(ssl).
-
-flash_parse(Transport) ->
- ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
- "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType),
- BinContent = <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\nhello.txt\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\nContent-Type: application/octet-stream\r\n\r\nhello\n\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Upload\"\r\n\r\nSubmit Query\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5--">>,
- Expect = [{headers,
- [{"content-disposition",
- {"form-data", [{"name", "Filename"}]}}]},
- {body, <<"hello.txt">>},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "success_action_status"}]}}]},
- {body, <<"201">>},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}},
- {"content-type", {"application/octet-stream", []}}]},
- {body, <<"hello\n">>},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "Upload"}]}}]},
- {body, <<"Submit Query">>},
- body_end,
- eof],
- TestCallback = fun (Next) -> test_callback(Next, Expect) end,
- ServerFun = fun (Socket) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
- ClientFun = fun (Socket) ->
- Req = fake_request(Socket, ContentType,
- byte_size(BinContent)),
- Res = parse_multipart_request(Req, TestCallback),
- {0, <<>>, ok} = Res,
- ok
- end,
- ok = with_socket_server(Transport, ServerFun, ClientFun),
- ok.
-
-flash_parse2_http_test() ->
- flash_parse2(plain).
-
-flash_parse2_https_test() ->
- flash_parse2(ssl).
-
-flash_parse2(Transport) ->
- ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
- "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType),
- Chunk = iolist_to_binary(string:copies("%", 4096)),
- BinContent = <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Filename\"\r\n\r\nhello.txt\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"success_action_status\"\r\n\r\n201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"file\"; filename=\"hello.txt\"\r\nContent-Type: application/octet-stream\r\n\r\n", Chunk/binary, "\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition: form-data; name=\"Upload\"\r\n\r\nSubmit Query\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5--">>,
- Expect = [{headers,
- [{"content-disposition",
- {"form-data", [{"name", "Filename"}]}}]},
- {body, <<"hello.txt">>},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "success_action_status"}]}}]},
- {body, <<"201">>},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "file"}, {"filename", "hello.txt"}]}},
- {"content-type", {"application/octet-stream", []}}]},
- {body, Chunk},
- body_end,
- {headers,
- [{"content-disposition",
- {"form-data", [{"name", "Upload"}]}}]},
- {body, <<"Submit Query">>},
- body_end,
- eof],
- TestCallback = fun (Next) -> test_callback(Next, Expect) end,
- ServerFun = fun (Socket) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
- ClientFun = fun (Socket) ->
- Req = fake_request(Socket, ContentType,
- byte_size(BinContent)),
- Res = parse_multipart_request(Req, TestCallback),
- {0, <<>>, ok} = Res,
- ok
- end,
- ok = with_socket_server(Transport, ServerFun, ClientFun),
- ok.
-
-parse_headers_test() ->
- ?assertEqual([], parse_headers(<<>>)).
-
-flash_multipart_hack_test() ->
- Buffer = <<"prefix-">>,
- Prefix = <<"prefix">>,
- State = #mp{length=0, buffer=Buffer, boundary=Prefix},
- ?assertEqual(State,
- flash_multipart_hack(State)).
-
-parts_to_body_single_test() ->
- {HL, B} = parts_to_body([{0, 5, <<"01234">>}],
- "text/plain",
- 10),
- [{"Content-Range", Range},
- {"Content-Type", Type}] = lists:sort(HL),
- ?assertEqual(
- <<"bytes 0-5/10">>,
- iolist_to_binary(Range)),
- ?assertEqual(
- <<"text/plain">>,
- iolist_to_binary(Type)),
- ?assertEqual(
- <<"01234">>,
- iolist_to_binary(B)),
- ok.
-
-parts_to_body_multi_test() ->
- {[{"Content-Type", Type}],
- _B} = parts_to_body([{0, 5, <<"01234">>}, {5, 10, <<"56789">>}],
- "text/plain",
- 10),
- ?assertMatch(
- <<"multipart/byteranges; boundary=", _/binary>>,
- iolist_to_binary(Type)),
- ok.
-
-parts_to_multipart_body_test() ->
- {[{"Content-Type", V}], B} = parts_to_multipart_body(
- [{0, 5, <<"01234">>}, {5, 10, <<"56789">>}],
- "text/plain",
- 10,
- "BOUNDARY"),
- MB = multipart_body(
- [{0, 5, <<"01234">>}, {5, 10, <<"56789">>}],
- "text/plain",
- "BOUNDARY",
- 10),
- ?assertEqual(
- <<"multipart/byteranges; boundary=BOUNDARY">>,
- iolist_to_binary(V)),
- ?assertEqual(
- iolist_to_binary(MB),
- iolist_to_binary(B)),
- ok.
-
-multipart_body_test() ->
- ?assertEqual(
- <<"--BOUNDARY--\r\n">>,
- iolist_to_binary(multipart_body([], "text/plain", "BOUNDARY", 0))),
- ?assertEqual(
- <<"--BOUNDARY\r\n"
- "Content-Type: text/plain\r\n"
- "Content-Range: bytes 0-5/10\r\n\r\n"
- "01234\r\n"
- "--BOUNDARY\r\n"
- "Content-Type: text/plain\r\n"
- "Content-Range: bytes 5-10/10\r\n\r\n"
- "56789\r\n"
- "--BOUNDARY--\r\n">>,
- iolist_to_binary(multipart_body([{0, 5, <<"01234">>}, {5, 10, <<"56789">>}],
- "text/plain",
- "BOUNDARY",
- 10))),
- ok.
-
--endif.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6fdb9e07/src/mochiweb/mochiweb_request.erl
----------------------------------------------------------------------
diff --git a/src/mochiweb/mochiweb_request.erl b/src/mochiweb/mochiweb_request.erl
deleted file mode 100644
index 980f5ad..0000000
--- a/src/mochiweb/mochiweb_request.erl
+++ /dev/null
@@ -1,788 +0,0 @@
-%% @author Bob Ippolito <bo...@mochimedia.com>
-%% @copyright 2007 Mochi Media, Inc.
-
-%% @doc MochiWeb HTTP Request abstraction.
-
--module(mochiweb_request, [Socket, Method, RawPath, Version, Headers]).
--author('bob@mochimedia.com').
-
--include_lib("kernel/include/file.hrl").
--include("internal.hrl").
-
--define(QUIP, "Any of you quaids got a smint?").
-
--export([get_header_value/1, get_primary_header_value/1, get/1, dump/0]).
--export([send/1, recv/1, recv/2, recv_body/0, recv_body/1, stream_body/3]).
--export([start_response/1, start_response_length/1, start_raw_response/1]).
--export([respond/1, ok/1]).
--export([not_found/0, not_found/1]).
--export([parse_post/0, parse_qs/0]).
--export([should_close/0, cleanup/0]).
--export([parse_cookie/0, get_cookie_value/1]).
--export([serve_file/2, serve_file/3]).
--export([accepted_encodings/1]).
--export([accepts_content_type/1]).
-
--define(SAVE_QS, mochiweb_request_qs).
--define(SAVE_PATH, mochiweb_request_path).
--define(SAVE_RECV, mochiweb_request_recv).
--define(SAVE_BODY, mochiweb_request_body).
--define(SAVE_BODY_LENGTH, mochiweb_request_body_length).
--define(SAVE_POST, mochiweb_request_post).
--define(SAVE_COOKIE, mochiweb_request_cookie).
--define(SAVE_FORCE_CLOSE, mochiweb_request_force_close).
-
-%% @type iolist() = [iolist() | binary() | char()].
-%% @type iodata() = binary() | iolist().
-%% @type key() = atom() | string() | binary()
-%% @type value() = atom() | string() | binary() | integer()
-%% @type headers(). A mochiweb_headers structure.
-%% @type response(). A mochiweb_response parameterized module instance.
-%% @type ioheaders() = headers() | [{key(), value()}].
-
-% 5 minute default idle timeout
--define(IDLE_TIMEOUT, 300000).
-
-% Maximum recv_body() length of 1MB
--define(MAX_RECV_BODY, (1024*1024)).
-
-%% @spec get_header_value(K) -> undefined | Value
-%% @doc Get the value of a given request header.
-get_header_value(K) ->
- mochiweb_headers:get_value(K, Headers).
-
-get_primary_header_value(K) ->
- mochiweb_headers:get_primary_value(K, Headers).
-
-%% @type field() = socket | scheme | method | raw_path | version | headers | peer | path | body_length | range
-
-%% @spec get(field()) -> term()
-%% @doc Return the internal representation of the given field. If
-%% <code>socket</code> is requested on a HTTPS connection, then
-%% an ssl socket will be returned as <code>{ssl, SslSocket}</code>.
-%% You can use <code>SslSocket</code> with the <code>ssl</code>
-%% application, eg: <code>ssl:peercert(SslSocket)</code>.
-get(socket) ->
- Socket;
-get(scheme) ->
- case mochiweb_socket:type(Socket) of
- plain ->
- http;
- ssl ->
- https
- end;
-get(method) ->
- Method;
-get(raw_path) ->
- RawPath;
-get(version) ->
- Version;
-get(headers) ->
- Headers;
-get(peer) ->
- case mochiweb_socket:peername(Socket) of
- {ok, {Addr={10, _, _, _}, _Port}} ->
- case get_header_value("x-forwarded-for") of
- undefined ->
- inet_parse:ntoa(Addr);
- Hosts ->
- string:strip(lists:last(string:tokens(Hosts, ",")))
- end;
- {ok, {{127, 0, 0, 1}, _Port}} ->
- case get_header_value("x-forwarded-for") of
- undefined ->
- "127.0.0.1";
- Hosts ->
- string:strip(lists:last(string:tokens(Hosts, ",")))
- end;
- {ok, {Addr, _Port}} ->
- inet_parse:ntoa(Addr);
- {error, enotconn} ->
- exit(normal)
- end;
-get(path) ->
- case erlang:get(?SAVE_PATH) of
- undefined ->
- {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath),
- Path = mochiweb_util:unquote(Path0),
- put(?SAVE_PATH, Path),
- Path;
- Cached ->
- Cached
- end;
-get(body_length) ->
- case erlang:get(?SAVE_BODY_LENGTH) of
- undefined ->
- BodyLength = body_length(),
- put(?SAVE_BODY_LENGTH, {cached, BodyLength}),
- BodyLength;
- {cached, Cached} ->
- Cached
- end;
-get(range) ->
- case get_header_value(range) of
- undefined ->
- undefined;
- RawRange ->
- mochiweb_http:parse_range_request(RawRange)
- end.
-
-%% @spec dump() -> {mochiweb_request, [{atom(), term()}]}
-%% @doc Dump the internal representation to a "human readable" set of terms
-%% for debugging/inspection purposes.
-dump() ->
- {?MODULE, [{method, Method},
- {version, Version},
- {raw_path, RawPath},
- {headers, mochiweb_headers:to_list(Headers)}]}.
-
-%% @spec send(iodata()) -> ok
-%% @doc Send data over the socket.
-send(Data) ->
- case mochiweb_socket:send(Socket, Data) of
- ok ->
- ok;
- _ ->
- exit(normal)
- end.
-
-%% @spec recv(integer()) -> binary()
-%% @doc Receive Length bytes from the client as a binary, with the default
-%% idle timeout.
-recv(Length) ->
- recv(Length, ?IDLE_TIMEOUT).
-
-%% @spec recv(integer(), integer()) -> binary()
-%% @doc Receive Length bytes from the client as a binary, with the given
-%% Timeout in msec.
-recv(Length, Timeout) ->
- case mochiweb_socket:recv(Socket, Length, Timeout) of
- {ok, Data} ->
- put(?SAVE_RECV, true),
- Data;
- _ ->
- exit(normal)
- end.
-
-%% @spec body_length() -> undefined | chunked | unknown_transfer_encoding | integer()
-%% @doc Infer body length from transfer-encoding and content-length headers.
-body_length() ->
- case get_header_value("transfer-encoding") of
- undefined ->
- case get_header_value("content-length") of
- undefined ->
- undefined;
- Length ->
- list_to_integer(Length)
- end;
- "chunked" ->
- chunked;
- Unknown ->
- {unknown_transfer_encoding, Unknown}
- end.
-
-
-%% @spec recv_body() -> binary()
-%% @doc Receive the body of the HTTP request (defined by Content-Length).
-%% Will only receive up to the default max-body length of 1MB.
-recv_body() ->
- recv_body(?MAX_RECV_BODY).
-
-%% @spec recv_body(integer()) -> binary()
-%% @doc Receive the body of the HTTP request (defined by Content-Length).
-%% Will receive up to MaxBody bytes.
-recv_body(MaxBody) ->
- case erlang:get(?SAVE_BODY) of
- undefined ->
- % we could use a sane constant for max chunk size
- Body = stream_body(?MAX_RECV_BODY, fun
- ({0, _ChunkedFooter}, {_LengthAcc, BinAcc}) ->
- iolist_to_binary(lists:reverse(BinAcc));
- ({Length, Bin}, {LengthAcc, BinAcc}) ->
- NewLength = Length + LengthAcc,
- if NewLength > MaxBody ->
- exit({body_too_large, chunked});
- true ->
- {NewLength, [Bin | BinAcc]}
- end
- end, {0, []}, MaxBody),
- put(?SAVE_BODY, Body),
- Body;
- Cached -> Cached
- end.
-
-stream_body(MaxChunkSize, ChunkFun, FunState) ->
- stream_body(MaxChunkSize, ChunkFun, FunState, undefined).
-
-stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength) ->
- Expect = case get_header_value("expect") of
- undefined ->
- undefined;
- Value when is_list(Value) ->
- string:to_lower(Value)
- end,
- case Expect of
- "100-continue" ->
- start_raw_response({100, gb_trees:empty()});
- _Else ->
- ok
- end,
- case body_length() of
- undefined ->
- undefined;
- {unknown_transfer_encoding, Unknown} ->
- exit({unknown_transfer_encoding, Unknown});
- chunked ->
- % In this case the MaxBody is actually used to
- % determine the maximum allowed size of a single
- % chunk.
- stream_chunked_body(MaxChunkSize, ChunkFun, FunState);
- 0 ->
- <<>>;
- Length when is_integer(Length) ->
- case MaxBodyLength of
- MaxBodyLength when is_integer(MaxBodyLength), MaxBodyLength < Length ->
- exit({body_too_large, content_length});
- _ ->
- stream_unchunked_body(Length, ChunkFun, FunState)
- end;
- Length ->
- exit({length_not_integer, Length})
- end.
-
-
-%% @spec start_response({integer(), ioheaders()}) -> response()
-%% @doc Start the HTTP response by sending the Code HTTP response and
-%% ResponseHeaders. The server will set header defaults such as Server
-%% and Date if not present in ResponseHeaders.
-start_response({Code, ResponseHeaders}) ->
- HResponse = mochiweb_headers:make(ResponseHeaders),
- HResponse1 = mochiweb_headers:default_from_list(server_headers(),
- HResponse),
- start_raw_response({Code, HResponse1}).
-
-%% @spec start_raw_response({integer(), headers()}) -> response()
-%% @doc Start the HTTP response by sending the Code HTTP response and
-%% ResponseHeaders.
-start_raw_response({Code, ResponseHeaders}) ->
- F = fun ({K, V}, Acc) ->
- [mochiweb_util:make_io(K), <<": ">>, V, <<"\r\n">> | Acc]
- end,
- End = lists:foldl(F, [<<"\r\n">>],
- mochiweb_headers:to_list(ResponseHeaders)),
- send([make_version(Version), make_code(Code), <<"\r\n">> | End]),
- mochiweb:new_response({THIS, Code, ResponseHeaders}).
-
-
-%% @spec start_response_length({integer(), ioheaders(), integer()}) -> response()
-%% @doc Start the HTTP response by sending the Code HTTP response and
-%% ResponseHeaders including a Content-Length of Length. The server
-%% will set header defaults such as Server
-%% and Date if not present in ResponseHeaders.
-start_response_length({Code, ResponseHeaders, Length}) ->
- HResponse = mochiweb_headers:make(ResponseHeaders),
- HResponse1 = mochiweb_headers:enter("Content-Length", Length, HResponse),
- start_response({Code, HResponse1}).
-
-%% @spec respond({integer(), ioheaders(), iodata() | chunked | {file, IoDevice}}) -> response()
-%% @doc Start the HTTP response with start_response, and send Body to the
-%% client (if the get(method) /= 'HEAD'). The Content-Length header
-%% will be set by the Body length, and the server will insert header
-%% defaults.
-respond({Code, ResponseHeaders, {file, IoDevice}}) ->
- Length = mochiweb_io:iodevice_size(IoDevice),
- Response = start_response_length({Code, ResponseHeaders, Length}),
- case Method of
- 'HEAD' ->
- ok;
- _ ->
- mochiweb_io:iodevice_stream(fun send/1, IoDevice)
- end,
- Response;
-respond({Code, ResponseHeaders, chunked}) ->
- HResponse = mochiweb_headers:make(ResponseHeaders),
- HResponse1 = case Method of
- 'HEAD' ->
- %% This is what Google does, http://www.google.com/
- %% is chunked but HEAD gets Content-Length: 0.
- %% The RFC is ambiguous so emulating Google is smart.
- mochiweb_headers:enter("Content-Length", "0",
- HResponse);
- _ when Version >= {1, 1} ->
- %% Only use chunked encoding for HTTP/1.1
- mochiweb_headers:enter("Transfer-Encoding", "chunked",
- HResponse);
- _ ->
- %% For pre-1.1 clients we send the data as-is
- %% without a Content-Length header and without
- %% chunk delimiters. Since the end of the document
- %% is now ambiguous we must force a close.
- put(?SAVE_FORCE_CLOSE, true),
- HResponse
- end,
- start_response({Code, HResponse1});
-respond({Code, ResponseHeaders, Body}) ->
- Response = start_response_length({Code, ResponseHeaders, iolist_size(Body)}),
- case Method of
- 'HEAD' ->
- ok;
- _ ->
- send(Body)
- end,
- Response.
-
-%% @spec not_found() -> response()
-%% @doc Alias for <code>not_found([])</code>.
-not_found() ->
- not_found([]).
-
-%% @spec not_found(ExtraHeaders) -> response()
-%% @doc Alias for <code>respond({404, [{"Content-Type", "text/plain"}
-%% | ExtraHeaders], <<"Not found.">>})</code>.
-not_found(ExtraHeaders) ->
- respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders],
- <<"Not found.">>}).
-
-%% @spec ok({value(), iodata()} | {value(), ioheaders(), iodata() | {file, IoDevice}}) ->
-%% response()
-%% @doc respond({200, [{"Content-Type", ContentType} | Headers], Body}).
-ok({ContentType, Body}) ->
- ok({ContentType, [], Body});
-ok({ContentType, ResponseHeaders, Body}) ->
- HResponse = mochiweb_headers:make(ResponseHeaders),
- case THIS:get(range) of
- X when (X =:= undefined orelse X =:= fail) orelse Body =:= chunked ->
- %% http://code.google.com/p/mochiweb/issues/detail?id=54
- %% Range header not supported when chunked, return 200 and provide
- %% full response.
- HResponse1 = mochiweb_headers:enter("Content-Type", ContentType,
- HResponse),
- respond({200, HResponse1, Body});
- Ranges ->
- {PartList, Size} = range_parts(Body, Ranges),
- case PartList of
- [] -> %% no valid ranges
- HResponse1 = mochiweb_headers:enter("Content-Type",
- ContentType,
- HResponse),
- %% could be 416, for now we'll just return 200
- respond({200, HResponse1, Body});
- PartList ->
- {RangeHeaders, RangeBody} =
- mochiweb_multipart:parts_to_body(PartList, ContentType, Size),
- HResponse1 = mochiweb_headers:enter_from_list(
- [{"Accept-Ranges", "bytes"} |
- RangeHeaders],
- HResponse),
- respond({206, HResponse1, RangeBody})
- end
- end.
-
-%% @spec should_close() -> bool()
-%% @doc Return true if the connection must be closed. If false, using
-%% Keep-Alive should be safe.
-should_close() ->
- ForceClose = erlang:get(?SAVE_FORCE_CLOSE) =/= undefined,
- DidNotRecv = erlang:get(?SAVE_RECV) =:= undefined,
- ForceClose orelse Version < {1, 0}
- %% Connection: close
- orelse get_header_value("connection") =:= "close"
- %% HTTP 1.0 requires Connection: Keep-Alive
- orelse (Version =:= {1, 0}
- andalso get_header_value("connection") =/= "Keep-Alive")
- %% unread data left on the socket, can't safely continue
- orelse (DidNotRecv
- andalso get_header_value("content-length") =/= undefined
- andalso list_to_integer(get_header_value("content-length")) > 0)
- orelse (DidNotRecv
- andalso get_header_value("transfer-encoding") =:= "chunked").
-
-%% @spec cleanup() -> ok
-%% @doc Clean up any junk in the process dictionary, required before continuing
-%% a Keep-Alive request.
-cleanup() ->
- [erase(K) || K <- [?SAVE_QS,
- ?SAVE_PATH,
- ?SAVE_RECV,
- ?SAVE_BODY,
- ?SAVE_BODY_LENGTH,
- ?SAVE_POST,
- ?SAVE_COOKIE,
- ?SAVE_FORCE_CLOSE]],
- ok.
-
-%% @spec parse_qs() -> [{Key::string(), Value::string()}]
-%% @doc Parse the query string of the URL.
-parse_qs() ->
- case erlang:get(?SAVE_QS) of
- undefined ->
- {_, QueryString, _} = mochiweb_util:urlsplit_path(RawPath),
- Parsed = mochiweb_util:parse_qs(QueryString),
- put(?SAVE_QS, Parsed),
- Parsed;
- Cached ->
- Cached
- end.
-
-%% @spec get_cookie_value(Key::string) -> string() | undefined
-%% @doc Get the value of the given cookie.
-get_cookie_value(Key) ->
- proplists:get_value(Key, parse_cookie()).
-
-%% @spec parse_cookie() -> [{Key::string(), Value::string()}]
-%% @doc Parse the cookie header.
-parse_cookie() ->
- case erlang:get(?SAVE_COOKIE) of
- undefined ->
- Cookies = case get_header_value("cookie") of
- undefined ->
- [];
- Value ->
- mochiweb_cookies:parse_cookie(Value)
- end,
- put(?SAVE_COOKIE, Cookies),
- Cookies;
- Cached ->
- Cached
- end.
-
-%% @spec parse_post() -> [{Key::string(), Value::string()}]
-%% @doc Parse an application/x-www-form-urlencoded form POST. This
-%% has the side-effect of calling recv_body().
-parse_post() ->
- case erlang:get(?SAVE_POST) of
- undefined ->
- Parsed = case recv_body() of
- undefined ->
- [];
- Binary ->
- case get_primary_header_value("content-type") of
- "application/x-www-form-urlencoded" ++ _ ->
- mochiweb_util:parse_qs(Binary);
- _ ->
- []
- end
- end,
- put(?SAVE_POST, Parsed),
- Parsed;
- Cached ->
- Cached
- end.
-
-%% @spec stream_chunked_body(integer(), fun(), term()) -> term()
-%% @doc The function is called for each chunk.
-%% Used internally by read_chunked_body.
-stream_chunked_body(MaxChunkSize, Fun, FunState) ->
- case read_chunk_length() of
- 0 ->
- Fun({0, read_chunk(0)}, FunState);
- Length when Length > MaxChunkSize ->
- NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState),
- stream_chunked_body(MaxChunkSize, Fun, NewState);
- Length ->
- NewState = Fun({Length, read_chunk(Length)}, FunState),
- stream_chunked_body(MaxChunkSize, Fun, NewState)
- end.
-
-stream_unchunked_body(0, Fun, FunState) ->
- Fun({0, <<>>}, FunState);
-stream_unchunked_body(Length, Fun, FunState) when Length > 0 ->
- PktSize = case Length > ?RECBUF_SIZE of
- true ->
- ?RECBUF_SIZE;
- false ->
- Length
- end,
- Bin = recv(PktSize),
- NewState = Fun({PktSize, Bin}, FunState),
- stream_unchunked_body(Length - PktSize, Fun, NewState).
-
-%% @spec read_chunk_length() -> integer()
-%% @doc Read the length of the next HTTP chunk.
-read_chunk_length() ->
- mochiweb_socket:setopts(Socket, [{packet, line}]),
- case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
- {ok, Header} ->
- mochiweb_socket:setopts(Socket, [{packet, raw}]),
- Splitter = fun (C) ->
- C =/= $\r andalso C =/= $\n andalso C =/= $
- end,
- {Hex, _Rest} = lists:splitwith(Splitter, binary_to_list(Header)),
- mochihex:to_int(Hex);
- _ ->
- exit(normal)
- end.
-
-%% @spec read_chunk(integer()) -> Chunk::binary() | [Footer::binary()]
-%% @doc Read in a HTTP chunk of the given length. If Length is 0, then read the
-%% HTTP footers (as a list of binaries, since they're nominal).
-read_chunk(0) ->
- mochiweb_socket:setopts(Socket, [{packet, line}]),
- F = fun (F1, Acc) ->
- case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
- {ok, <<"\r\n">>} ->
- Acc;
- {ok, Footer} ->
- F1(F1, [Footer | Acc]);
- _ ->
- exit(normal)
- end
- end,
- Footers = F(F, []),
- mochiweb_socket:setopts(Socket, [{packet, raw}]),
- put(?SAVE_RECV, true),
- Footers;
-read_chunk(Length) ->
- case mochiweb_socket:recv(Socket, 2 + Length, ?IDLE_TIMEOUT) of
- {ok, <<Chunk:Length/binary, "\r\n">>} ->
- Chunk;
- _ ->
- exit(normal)
- end.
-
-read_sub_chunks(Length, MaxChunkSize, Fun, FunState) when Length > MaxChunkSize ->
- Bin = recv(MaxChunkSize),
- NewState = Fun({size(Bin), Bin}, FunState),
- read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState);
-
-read_sub_chunks(Length, _MaxChunkSize, Fun, FunState) ->
- Fun({Length, read_chunk(Length)}, FunState).
-
-%% @spec serve_file(Path, DocRoot) -> Response
-%% @doc Serve a file relative to DocRoot.
-serve_file(Path, DocRoot) ->
- serve_file(Path, DocRoot, []).
-
-%% @spec serve_file(Path, DocRoot, ExtraHeaders) -> Response
-%% @doc Serve a file relative to DocRoot.
-serve_file(Path, DocRoot, ExtraHeaders) ->
- case mochiweb_util:safe_relative_path(Path) of
- undefined ->
- not_found(ExtraHeaders);
- RelPath ->
- FullPath = filename:join([DocRoot, RelPath]),
- case filelib:is_dir(FullPath) of
- true ->
- maybe_redirect(RelPath, FullPath, ExtraHeaders);
- false ->
- maybe_serve_file(FullPath, ExtraHeaders)
- end
- end.
-
-%% Internal API
-
-%% This has the same effect as the DirectoryIndex directive in httpd
-directory_index(FullPath) ->
- filename:join([FullPath, "index.html"]).
-
-maybe_redirect([], FullPath, ExtraHeaders) ->
- maybe_serve_file(directory_index(FullPath), ExtraHeaders);
-
-maybe_redirect(RelPath, FullPath, ExtraHeaders) ->
- case string:right(RelPath, 1) of
- "/" ->
- maybe_serve_file(directory_index(FullPath), ExtraHeaders);
- _ ->
- Host = mochiweb_headers:get_value("host", Headers),
- Location = "http://" ++ Host ++ "/" ++ RelPath ++ "/",
- LocationBin = list_to_binary(Location),
- MoreHeaders = [{"Location", Location},
- {"Content-Type", "text/html"} | ExtraHeaders],
- Top = <<"<!DOCTYPE HTML PUBLIC \"-//IETF//DTD HTML 2.0//EN\">"
- "<html><head>"
- "<title>301 Moved Permanently</title>"
- "</head><body>"
- "<h1>Moved Permanently</h1>"
- "<p>The document has moved <a href=\"">>,
- Bottom = <<">here</a>.</p></body></html>\n">>,
- Body = <<Top/binary, LocationBin/binary, Bottom/binary>>,
- respond({301, MoreHeaders, Body})
- end.
-
-maybe_serve_file(File, ExtraHeaders) ->
- case read_file_info(File) of
- {ok, FileInfo} ->
- LastModified = couch_util:rfc1123_date(FileInfo#file_info.mtime),
- case get_header_value("if-modified-since") of
- LastModified ->
- respond({304, ExtraHeaders, ""});
- _ ->
- case file:open(File, [raw, binary]) of
- {ok, IoDevice} ->
- ContentType = mochiweb_util:guess_mime(File),
- Res = ok({ContentType,
- [{"last-modified", LastModified}
- | ExtraHeaders],
- {file, IoDevice}}),
- file:close(IoDevice),
- Res;
- _ ->
- not_found(ExtraHeaders)
- end
- end;
- {error, _} ->
- not_found(ExtraHeaders)
- end.
-
-read_file_info(File) ->
- try
- file:read_file_info(File, [{time, universal}])
- catch error:undef ->
- case file:read_file_info(File) of
- {ok, FileInfo} ->
- {ok, FileInfo#file_info{
- atime=to_universal(FileInfo#file_info.atime),
- mtime=to_universal(FileInfo#file_info.mtime),
- ctime=to_universal(FileInfo#file_info.ctime)
- }};
- Else ->
- Else
- end
- end.
-
-to_universal(LocalTime) ->
- erlang:localtime_to_universaltime(LocalTime).
-
-server_headers() ->
- [{"Server", "MochiWeb/1.0 (" ++ ?QUIP ++ ")"},
- {"Date", couch_util:rfc1123_date()}].
-
-make_code(X) when is_integer(X) ->
- [integer_to_list(X), [" " | httpd_util:reason_phrase(X)]];
-make_code(Io) when is_list(Io); is_binary(Io) ->
- Io.
-
-make_version({1, 0}) ->
- <<"HTTP/1.0 ">>;
-make_version(_) ->
- <<"HTTP/1.1 ">>.
-
-range_parts({file, IoDevice}, Ranges) ->
- Size = mochiweb_io:iodevice_size(IoDevice),
- F = fun (Spec, Acc) ->
- case mochiweb_http:range_skip_length(Spec, Size) of
- invalid_range ->
- Acc;
- V ->
- [V | Acc]
- end
- end,
- LocNums = lists:foldr(F, [], Ranges),
- {ok, Data} = file:pread(IoDevice, LocNums),
- Bodies = lists:zipwith(fun ({Skip, Length}, PartialBody) ->
- {Skip, Skip + Length - 1, PartialBody}
- end,
- LocNums, Data),
- {Bodies, Size};
-range_parts(Body0, Ranges) ->
- Body = iolist_to_binary(Body0),
- Size = size(Body),
- F = fun(Spec, Acc) ->
- case mochiweb_http:range_skip_length(Spec, Size) of
- invalid_range ->
- Acc;
- {Skip, Length} ->
- <<_:Skip/binary, PartialBody:Length/binary, _/binary>> = Body,
- [{Skip, Skip + Length - 1, PartialBody} | Acc]
- end
- end,
- {lists:foldr(F, [], Ranges), Size}.
-
-%% @spec accepted_encodings([encoding()]) -> [encoding()] | bad_accept_encoding_value
-%% @type encoding() = string().
-%%
-%% @doc Returns a list of encodings accepted by a request. Encodings that are
-%% not supported by the server will not be included in the return list.
-%% This list is computed from the "Accept-Encoding" header and
-%% its elements are ordered, descendingly, according to their Q values.
-%%
-%% Section 14.3 of the RFC 2616 (HTTP 1.1) describes the "Accept-Encoding"
-%% header and the process of determining which server supported encodings
-%% can be used for encoding the body for the request's response.
-%%
-%% Examples
-%%
-%% 1) For a missing "Accept-Encoding" header:
-%% accepted_encodings(["gzip", "identity"]) -> ["identity"]
-%%
-%% 2) For an "Accept-Encoding" header with value "gzip, deflate":
-%% accepted_encodings(["gzip", "identity"]) -> ["gzip", "identity"]
-%%
-%% 3) For an "Accept-Encoding" header with value "gzip;q=0.5, deflate":
-%% accepted_encodings(["gzip", "deflate", "identity"]) ->
-%% ["deflate", "gzip", "identity"]
-%%
-accepted_encodings(SupportedEncodings) ->
- AcceptEncodingHeader = case get_header_value("Accept-Encoding") of
- undefined ->
- "";
- Value ->
- Value
- end,
- case mochiweb_util:parse_qvalues(AcceptEncodingHeader) of
- invalid_qvalue_string ->
- bad_accept_encoding_value;
- QList ->
- mochiweb_util:pick_accepted_encodings(
- QList, SupportedEncodings, "identity"
- )
- end.
-
-%% @spec accepts_content_type(string() | binary()) -> boolean() | bad_accept_header
-%%
-%% @doc Determines whether a request accepts a given media type by analyzing its
-%% "Accept" header.
-%%
-%% Examples
-%%
-%% 1) For a missing "Accept" header:
-%% accepts_content_type("application/json") -> true
-%%
-%% 2) For an "Accept" header with value "text/plain, application/*":
-%% accepts_content_type("application/json") -> true
-%%
-%% 3) For an "Accept" header with value "text/plain, */*; q=0.0":
-%% accepts_content_type("application/json") -> false
-%%
-%% 4) For an "Accept" header with value "text/plain; q=0.5, */*; q=0.1":
-%% accepts_content_type("application/json") -> true
-%%
-%% 5) For an "Accept" header with value "text/*; q=0.0, */*":
-%% accepts_content_type("text/plain") -> false
-%%
-accepts_content_type(ContentType) when is_binary(ContentType) ->
- accepts_content_type(binary_to_list(ContentType));
-accepts_content_type(ContentType1) ->
- ContentType = re:replace(ContentType1, "\\s", "", [global, {return, list}]),
- AcceptHeader = case get_header_value("Accept") of
- undefined ->
- "*/*";
- Value ->
- Value
- end,
- case mochiweb_util:parse_qvalues(AcceptHeader) of
- invalid_qvalue_string ->
- bad_accept_header;
- QList ->
- [MainType, _SubType] = string:tokens(ContentType, "/"),
- SuperType = MainType ++ "/*",
- lists:any(
- fun({"*/*", Q}) when Q > 0.0 ->
- true;
- ({Type, Q}) when Q > 0.0 ->
- Type =:= ContentType orelse Type =:= SuperType;
- (_) ->
- false
- end,
- QList
- ) andalso
- (not lists:member({ContentType, 0.0}, QList)) andalso
- (not lists:member({SuperType, 0.0}, QList))
- end.
-
-%%
-%% Tests
-%%
--include_lib("eunit/include/eunit.hrl").
--ifdef(TEST).
--endif.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6fdb9e07/src/mochiweb/mochiweb_request_tests.erl
----------------------------------------------------------------------
diff --git a/src/mochiweb/mochiweb_request_tests.erl b/src/mochiweb/mochiweb_request_tests.erl
deleted file mode 100644
index b61a583..0000000
--- a/src/mochiweb/mochiweb_request_tests.erl
+++ /dev/null
@@ -1,63 +0,0 @@
--module(mochiweb_request_tests).
-
--include_lib("eunit/include/eunit.hrl").
--ifdef(TEST).
-
-accepts_content_type_test() ->
- Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "multipart/related"}])),
- ?assertEqual(true, Req1:accepts_content_type("multipart/related")),
-
- Req2 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html"}])),
- ?assertEqual(false, Req2:accepts_content_type("multipart/related")),
-
- Req3 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html, multipart/*"}])),
- ?assertEqual(true, Req3:accepts_content_type("multipart/related")),
-
- Req4 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html, multipart/*; q=0.0"}])),
- ?assertEqual(false, Req4:accepts_content_type("multipart/related")),
-
- Req5 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html, multipart/*; q=0"}])),
- ?assertEqual(false, Req5:accepts_content_type("multipart/related")),
-
- Req6 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html, */*; q=0.0"}])),
- ?assertEqual(false, Req6:accepts_content_type("multipart/related")),
-
- Req7 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "multipart/*; q=0.0, */*"}])),
- ?assertEqual(false, Req7:accepts_content_type("multipart/related")),
-
- Req8 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "*/*; q=0.0, multipart/*"}])),
- ?assertEqual(true, Req8:accepts_content_type("multipart/related")),
-
- Req9 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "*/*; q=0.0, multipart/related"}])),
- ?assertEqual(true, Req9:accepts_content_type("multipart/related")),
-
- Req10 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html; level=1"}])),
- ?assertEqual(true, Req10:accepts_content_type("text/html;level=1")),
-
- Req11 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html; level=1, text/html"}])),
- ?assertEqual(true, Req11:accepts_content_type("text/html")),
-
- Req12 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html; level=1; q=0.0, text/html"}])),
- ?assertEqual(false, Req12:accepts_content_type("text/html;level=1")),
-
- Req13 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html; level=1; q=0.0, text/html"}])),
- ?assertEqual(false, Req13:accepts_content_type("text/html; level=1")),
-
- Req14 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html;level=1;q=0.1, text/html"}])),
- ?assertEqual(true, Req14:accepts_content_type("text/html; level=1")).
-
--endif.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6fdb9e07/src/mochiweb/mochiweb_response.erl
----------------------------------------------------------------------
diff --git a/src/mochiweb/mochiweb_response.erl b/src/mochiweb/mochiweb_response.erl
deleted file mode 100644
index ab8ee61..0000000
--- a/src/mochiweb/mochiweb_response.erl
+++ /dev/null
@@ -1,64 +0,0 @@
-%% @author Bob Ippolito <bo...@mochimedia.com>
-%% @copyright 2007 Mochi Media, Inc.
-
-%% @doc Response abstraction.
-
--module(mochiweb_response, [Request, Code, Headers]).
--author('bob@mochimedia.com').
-
--define(QUIP, "Any of you quaids got a smint?").
-
--export([get_header_value/1, get/1, dump/0]).
--export([send/1, write_chunk/1]).
-
-%% @spec get_header_value(string() | atom() | binary()) -> string() | undefined
-%% @doc Get the value of the given response header.
-get_header_value(K) ->
- mochiweb_headers:get_value(K, Headers).
-
-%% @spec get(request | code | headers) -> term()
-%% @doc Return the internal representation of the given field.
-get(request) ->
- Request;
-get(code) ->
- Code;
-get(headers) ->
- Headers.
-
-%% @spec dump() -> {mochiweb_request, [{atom(), term()}]}
-%% @doc Dump the internal representation to a "human readable" set of terms
-%% for debugging/inspection purposes.
-dump() ->
- [{request, Request:dump()},
- {code, Code},
- {headers, mochiweb_headers:to_list(Headers)}].
-
-%% @spec send(iodata()) -> ok
-%% @doc Send data over the socket if the method is not HEAD.
-send(Data) ->
- case Request:get(method) of
- 'HEAD' ->
- ok;
- _ ->
- Request:send(Data)
- end.
-
-%% @spec write_chunk(iodata()) -> ok
-%% @doc Write a chunk of a HTTP chunked response. If Data is zero length,
-%% then the chunked response will be finished.
-write_chunk(Data) ->
- case Request:get(version) of
- Version when Version >= {1, 1} ->
- Length = iolist_size(Data),
- send([io_lib:format("~.16b\r\n", [Length]), Data, <<"\r\n">>]);
- _ ->
- send(Data)
- end.
-
-
-%%
-%% Tests
-%%
--include_lib("eunit/include/eunit.hrl").
--ifdef(TEST).
--endif.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6fdb9e07/src/mochiweb/mochiweb_skel.erl
----------------------------------------------------------------------
diff --git a/src/mochiweb/mochiweb_skel.erl b/src/mochiweb/mochiweb_skel.erl
deleted file mode 100644
index 76eefa6..0000000
--- a/src/mochiweb/mochiweb_skel.erl
+++ /dev/null
@@ -1,86 +0,0 @@
--module(mochiweb_skel).
--export([skelcopy/2]).
-
--include_lib("kernel/include/file.hrl").
-
-%% External API
-
-skelcopy(DestDir, Name) ->
- ok = ensuredir(DestDir),
- LDst = case length(filename:dirname(DestDir)) of
- 1 -> %% handle case when dirname returns "/"
- 0;
- N ->
- N + 1
- end,
- skelcopy(src(), DestDir, Name, LDst),
- DestLink = filename:join([DestDir, Name, "deps", "mochiweb-src"]),
- ok = filelib:ensure_dir(DestLink),
- ok = file:make_symlink(
- filename:join(filename:dirname(code:which(?MODULE)), ".."),
- DestLink).
-
-%% Internal API
-
-src() ->
- Dir = filename:dirname(code:which(?MODULE)),
- filename:join(Dir, "../priv/skel").
-
-skel() ->
- "skel".
-
-skelcopy(Src, DestDir, Name, LDst) ->
- Dest = re:replace(filename:basename(Src), skel(), Name,
- [global, {return, list}]),
- case file:read_file_info(Src) of
- {ok, #file_info{type=directory, mode=Mode}} ->
- Dir = DestDir ++ "/" ++ Dest,
- EDst = lists:nthtail(LDst, Dir),
- ok = ensuredir(Dir),
- ok = file:write_file_info(Dir, #file_info{mode=Mode}),
- case filename:basename(Src) of
- "ebin" ->
- ok;
- _ ->
- {ok, Files} = file:list_dir(Src),
- io:format("~s/~n", [EDst]),
- lists:foreach(fun ("." ++ _) -> ok;
- (F) ->
- skelcopy(filename:join(Src, F),
- Dir,
- Name,
- LDst)
- end,
- Files),
- ok
- end;
- {ok, #file_info{type=regular, mode=Mode}} ->
- OutFile = filename:join(DestDir, Dest),
- {ok, B} = file:read_file(Src),
- S = re:replace(binary_to_list(B), skel(), Name,
- [{return, list}, global]),
- ok = file:write_file(OutFile, list_to_binary(S)),
- ok = file:write_file_info(OutFile, #file_info{mode=Mode}),
- io:format(" ~s~n", [filename:basename(Src)]),
- ok;
- {ok, _} ->
- io:format("ignored source file: ~p~n", [Src]),
- ok
- end.
-
-ensuredir(Dir) ->
- case file:make_dir(Dir) of
- ok ->
- ok;
- {error, eexist} ->
- ok;
- E ->
- E
- end.
-
-%%
-%% Tests
-%%
--include_lib("eunit/include/eunit.hrl").
--ifdef(TEST).
--endif.
http://git-wip-us.apache.org/repos/asf/couchdb/blob/6fdb9e07/src/mochiweb/mochiweb_socket.erl
----------------------------------------------------------------------
diff --git a/src/mochiweb/mochiweb_socket.erl b/src/mochiweb/mochiweb_socket.erl
deleted file mode 100644
index ad27204..0000000
--- a/src/mochiweb/mochiweb_socket.erl
+++ /dev/null
@@ -1,83 +0,0 @@
-%% @copyright 2010 Mochi Media, Inc.
-
-%% @doc MochiWeb socket - wrapper for plain and ssl sockets.
-
--module(mochiweb_socket).
-
--export([listen/4, accept/1, after_accept/1, recv/3, send/2, close/1, port/1, peername/1,
- setopts/2, type/1]).
-
--define(ACCEPT_TIMEOUT, 2000).
--define(SSL_ACCEPT_TIMEOUT, 30000).
-
-listen(Ssl, Port, Opts, SslOpts) ->
- case Ssl of
- true ->
- case ssl:listen(Port, Opts ++ SslOpts) of
- {ok, ListenSocket} ->
- {ok, {ssl, ListenSocket}};
- {error, _} = Err ->
- Err
- end;
- false ->
- gen_tcp:listen(Port, Opts)
- end.
-
-accept({ssl, ListenSocket}) ->
- % There's a bug in ssl:transport_accept/2 at the moment, which is the
- % reason for the try...catch block. Should be fixed in OTP R14.
- try ssl:transport_accept(ListenSocket, ?ACCEPT_TIMEOUT) of
- {ok, Socket} ->
- {ok, {ssl, Socket}};
- {error, _} = Err ->
- Err
- catch
- error:{badmatch, {error, Reason}} ->
- {error, Reason}
- end;
-accept(ListenSocket) ->
- gen_tcp:accept(ListenSocket, ?ACCEPT_TIMEOUT).
-
-after_accept({ssl, Socket}) -> ssl:ssl_accept(Socket, ?SSL_ACCEPT_TIMEOUT);
-after_accept(_Socket) -> ok.
-
-recv({ssl, Socket}, Length, Timeout) ->
- ssl:recv(Socket, Length, Timeout);
-recv(Socket, Length, Timeout) ->
- gen_tcp:recv(Socket, Length, Timeout).
-
-send({ssl, Socket}, Data) ->
- ssl:send(Socket, Data);
-send(Socket, Data) ->
- gen_tcp:send(Socket, Data).
-
-close({ssl, Socket}) ->
- ssl:close(Socket);
-close(Socket) ->
- gen_tcp:close(Socket).
-
-port({ssl, Socket}) ->
- case ssl:sockname(Socket) of
- {ok, {_, Port}} ->
- {ok, Port};
- {error, _} = Err ->
- Err
- end;
-port(Socket) ->
- inet:port(Socket).
-
-peername({ssl, Socket}) ->
- ssl:peername(Socket);
-peername(Socket) ->
- inet:peername(Socket).
-
-setopts({ssl, Socket}, Opts) ->
- ssl:setopts(Socket, Opts);
-setopts(Socket, Opts) ->
- inet:setopts(Socket, Opts).
-
-type({ssl, _}) ->
- ssl;
-type(_) ->
- plain.
-