You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@couchdb.apache.org by wi...@apache.org on 2020/01/13 19:42:29 UTC
[couchdb-mochiweb] 30/37: Run erl_tidy on modified source files
This is an automated email from the ASF dual-hosted git repository.
willholley pushed a commit to branch upstream
in repository https://gitbox.apache.org/repos/asf/couchdb-mochiweb.git
commit 3d3f929a99abcbcd75f2e32e6eb1ee2c8c3b075f
Author: Bob Ippolito <bo...@redivi.com>
AuthorDate: Tue Mar 12 03:49:26 2019 +0000
Run erl_tidy on modified source files
```erlang
lists:foreach(
fun (F) -> erl_tidy:file(F, [{backups, false}, keep_unused]) end,
string:split(
string:trim(
os:cmd("git diff --name-only origin/master | grep \".erl$\"")
),
"\n",
all
)
).
```
---
examples/hmac_api/hmac_api_lib.erl | 354 +++---
examples/https/https_store.erl | 142 +--
examples/keepalive/keepalive.erl | 72 +-
src/mochifmt.erl | 405 ++++---
src/mochiweb_acceptor.erl | 61 +-
src/mochiweb_http.erl | 366 +++---
src/mochiweb_multipart.erl | 1237 ++++++++++---------
src/mochiweb_request.erl | 1280 ++++++++++++--------
src/mochiweb_response.erl | 37 +-
src/mochiweb_websocket.erl | 287 +++--
.../mochiwebapp_skel/src/mochiapp_web.erl | 79 +-
test/mochiweb_http_tests.erl | 50 +-
test/mochiweb_request_tests.erl | 292 +++--
test/mochiweb_tests.erl | 244 ++--
test/mochiweb_websocket_tests.erl | 189 +--
15 files changed, 2741 insertions(+), 2354 deletions(-)
diff --git a/examples/hmac_api/hmac_api_lib.erl b/examples/hmac_api/hmac_api_lib.erl
index 4c26f2f..1dae62d 100644
--- a/examples/hmac_api/hmac_api_lib.erl
+++ b/examples/hmac_api/hmac_api_lib.erl
@@ -1,6 +1,7 @@
-module(hmac_api_lib).
-include("hmac_api.hrl").
+
-include_lib("eunit/include/eunit.hrl").
-author("Hypernumbers Ltd <go...@hypernumbers.com>").
@@ -21,11 +22,8 @@
%%%
%%% THE AMAZON API MUNGES HOSTNAME AND PATHS IN A CUSTOM WAY
%%% THIS IMPLEMENTATION DOESN'T
--export([
- authorize_request/1,
- sign/5,
- get_api_keypair/0
- ]).
+-export([authorize_request/1, get_api_keypair/0,
+ sign/5]).
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% %%%
@@ -34,27 +32,27 @@
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
authorize_request(Req) ->
- Method = mochiweb_request:get(method, Req),
- Path = mochiweb_request:get(path, Req),
- Headers = normalise(mochiweb_headers:to_list(mochiweb_request:get(headers, Req))),
- ContentMD5 = get_header(Headers, "content-md5"),
+ Method = mochiweb_request:get(method, Req),
+ Path = mochiweb_request:get(path, Req),
+ Headers =
+ normalise(mochiweb_headers:to_list(mochiweb_request:get(headers,
+ Req))),
+ ContentMD5 = get_header(Headers, "content-md5"),
ContentType = get_header(Headers, "content-type"),
- Date = get_header(Headers, "date"),
- IncAuth = get_header(Headers, "authorization"),
+ Date = get_header(Headers, "date"),
+ IncAuth = get_header(Headers, "authorization"),
{_Schema, _PublicKey, _Sig} = breakout(IncAuth),
%% normally you would use the public key to look up the private key
- PrivateKey = ?privatekey,
+ PrivateKey = (?privatekey),
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = Path},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = Path},
Signed = sign_data(PrivateKey, Signature),
{_, AuthHeader} = make_HTTPAuth_header(Signed),
case AuthHeader of
- IncAuth -> "match";
- _ -> "no_match"
+ IncAuth -> "match";
+ _ -> "no_match"
end.
sign(PrivateKey, Method, URL, Headers, ContentType) ->
@@ -62,15 +60,12 @@ sign(PrivateKey, Method, URL, Headers, ContentType) ->
ContentMD5 = get_header(Headers2, "content-md5"),
Date = get_header(Headers2, "date"),
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
SignedSig = sign_data(PrivateKey, Signature),
make_HTTPAuth_header(SignedSig).
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% %%%
%%% Internal Functions %%%
@@ -83,22 +78,30 @@ breakout(Header) ->
{Schema, PublicKey, Signature}.
get_api_keypair() ->
- Public = mochihex:to_hex(binary_to_list(crypto:strong_rand_bytes(16))),
- Private = mochihex:to_hex(binary_to_list(crypto:strong_rand_bytes(16))),
+ Public =
+ mochihex:to_hex(binary_to_list(crypto:strong_rand_bytes(16))),
+ Private =
+ mochihex:to_hex(binary_to_list(crypto:strong_rand_bytes(16))),
{Public, Private}.
make_HTTPAuth_header(Signature) ->
- {"Authorization", ?schema ++ " "
- ++ ?publickey ++ ":" ++ Signature}.
+ {"Authorization",
+ (?schema) ++ " " ++ (?publickey) ++ ":" ++ Signature}.
make_signature_string(#hmac_signature{} = S) ->
- Date = get_date(S#hmac_signature.headers, S#hmac_signature.date),
- string:to_upper(atom_to_list(S#hmac_signature.method)) ++ "\n"
- ++ S#hmac_signature.contentmd5 ++ "\n"
- ++ S#hmac_signature.contenttype ++ "\n"
- ++ Date ++ "\n"
- ++ canonicalise_headers(S#hmac_signature.headers)
- ++ canonicalise_resource(S#hmac_signature.resource).
+ Date = get_date(S#hmac_signature.headers,
+ S#hmac_signature.date),
+ string:to_upper(atom_to_list(S#hmac_signature.method))
+ ++
+ "\n" ++
+ S#hmac_signature.contentmd5 ++
+ "\n" ++
+ S#hmac_signature.contenttype ++
+ "\n" ++
+ Date ++
+ "\n" ++
+ canonicalise_headers(S#hmac_signature.headers) ++
+ canonicalise_resource(S#hmac_signature.resource).
sign_data(PrivateKey, #hmac_signature{} = Signature) ->
Str = make_signature_string(Signature),
@@ -109,35 +112,41 @@ sign_data(PrivateKey, #hmac_signature{} = Signature) ->
%% yer Donald is well and truly Ducked so ye may as weel test it...
sign2(PrivateKey, Str) ->
Sign = xmerl_ucs:to_utf8(Str),
- binary_to_list(base64:encode(crypto:sha_mac(PrivateKey, Sign))).
+ binary_to_list(base64:encode(crypto:sha_mac(PrivateKey,
+ Sign))).
canonicalise_headers([]) -> "\n";
canonicalise_headers(List) when is_list(List) ->
- List2 = [{string:to_lower(K), V} || {K, V} <- lists:sort(List)],
+ List2 = [{string:to_lower(K), V}
+ || {K, V} <- lists:sort(List)],
c_headers2(consolidate(List2, []), []).
-c_headers2([], Acc) -> string:join(Acc, "\n") ++ "\n";
-c_headers2([{?headerprefix ++ Rest, Key} | T], Acc) ->
- Hd = string:strip(?headerprefix ++ Rest) ++ ":" ++ string:strip(Key),
+c_headers2([], Acc) -> string:join(Acc, "\n") ++ "\n";
+c_headers2([{(?headerprefix) ++ Rest, Key} | T], Acc) ->
+ Hd = string:strip((?headerprefix) ++ Rest) ++
+ ":" ++ string:strip(Key),
c_headers2(T, [Hd | Acc]);
c_headers2([_H | T], Acc) -> c_headers2(T, Acc).
-consolidate([H | []], Acc) -> [H | Acc];
+consolidate([H], Acc) -> [H | Acc];
consolidate([{H, K1}, {H, K2} | Rest], Acc) ->
consolidate([{H, join(K1, K2)} | Rest], Acc);
consolidate([{H1, K1}, {H2, K2} | Rest], Acc) ->
- consolidate([{rectify(H2), rectify(K2)} | Rest], [{H1, K1} | Acc]).
+ consolidate([{rectify(H2), rectify(K2)} | Rest],
+ [{H1, K1} | Acc]).
join(A, B) -> string:strip(A) ++ ";" ++ string:strip(B).
%% removes line spacing as per RFC 2616 Section 4.2
rectify(String) ->
- Re = "[\x20* | \t*]+",
+ Re = "[ * | \t*]+",
re:replace(String, Re, " ", [{return, list}, global]).
-canonicalise_resource("http://" ++ Rest) -> c_res2(Rest);
-canonicalise_resource("https://" ++ Rest) -> c_res2(Rest);
-canonicalise_resource(X) -> c_res3(X).
+canonicalise_resource("http://" ++ Rest) ->
+ c_res2(Rest);
+canonicalise_resource("https://" ++ Rest) ->
+ c_res2(Rest);
+canonicalise_resource(X) -> c_res3(X).
c_res2(Rest) ->
N = string:str(Rest, "/"),
@@ -146,15 +155,15 @@ c_res2(Rest) ->
c_res3(Tail) ->
URL = case string:str(Tail, "#") of
- 0 -> Tail;
- N -> {U, _Anchor} = lists:split(N, Tail),
- U
- end,
+ 0 -> Tail;
+ N -> {U, _Anchor} = lists:split(N, Tail), U
+ end,
U3 = case string:str(URL, "?") of
- 0 -> URL;
- N2 -> {U2, Q} = lists:split(N2, URL),
- U2 ++ canonicalise_query(Q)
- end,
+ 0 -> URL;
+ N2 ->
+ {U2, Q} = lists:split(N2, URL),
+ U2 ++ canonicalise_query(Q)
+ end,
string:to_lower(U3).
canonicalise_query(List) ->
@@ -163,11 +172,12 @@ canonicalise_query(List) ->
string:join(lists:sort(List2), "&").
%% if there's a header date take it and ditch the date
-get_date([], Date) -> Date;
-get_date([{K, _V} | T], Date) -> case string:to_lower(K) of
- ?dateheader -> [];
- _ -> get_date(T, Date)
- end.
+get_date([], Date) -> Date;
+get_date([{K, _V} | T], Date) ->
+ case string:to_lower(K) of
+ ?dateheader -> [];
+ _ -> get_date(T, Date)
+ end.
normalise(List) -> norm2(List, []).
@@ -178,11 +188,10 @@ norm2([H | T], Acc) -> norm2(T, [H | Acc]).
get_header(Headers, Type) ->
case lists:keyfind(Type, 1, Headers) of
- false -> [];
- {_K, V} -> V
+ false -> [];
+ {_K, V} -> V
end.
-
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%%% %%%
%%% Unit Tests %%%
@@ -190,10 +199,13 @@ get_header(Headers, Type) ->
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
% taken from Amazon docs
+
%% http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAuthentication.html
hash_test1(_) ->
- Sig = "DELETE\n\n\n\nx-amz-date:Tue, 27 Mar 2007 21:20:26 +0000\n/johnsmith/photos/puppy.jpg",
- Key = ?privatekey,
+ Sig = "DELETE\n\n\n\nx-amz-date:Tue, 27 Mar "
+ "2007 21:20:26 +0000\n/johnsmith/photos/puppy."
+ "jpg",
+ Key = (?privatekey),
Hash = sign2(Key, Sig),
Expected = "k3nL7gH3+PadhTEVn5Ip83xlYzk=",
?assertEqual(Expected, Hash).
@@ -201,7 +213,8 @@ hash_test1(_) ->
%% taken from Amazon docs
%% http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAuthentication.html
hash_test2(_) ->
- Sig = "GET\n\n\nTue, 27 Mar 2007 19:44:46 +0000\n/johnsmith/?acl",
+ Sig = "GET\n\n\nTue, 27 Mar 2007 19:44:46 +0000\n/jo"
+ "hnsmith/?acl",
Key = "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o",
Hash = sign2(Key, Sig),
Expected = "thdUi9VAkzhkniLj96JIrOPGi0g=",
@@ -210,8 +223,9 @@ hash_test2(_) ->
%% taken from Amazon docs
%% http://docs.amazonwebservices.com/AmazonS3/latest/dev/index.html?RESTAuthentication.html
hash_test3(_) ->
- Sig = "GET\n\n\nWed, 28 Mar 2007 01:49:49 +0000\n/dictionary/"
- ++ "fran%C3%A7ais/pr%c3%a9f%c3%a8re",
+ Sig = "GET\n\n\nWed, 28 Mar 2007 01:49:49 +0000\n/di"
+ "ctionary/"
+ ++ "fran%C3%A7ais/pr%c3%a9f%c3%a8re",
Key = "uV3F3YluFJax1cknvbcGwgjvx4QpvB+leU8dUj2o",
Hash = sign2(Key, Sig),
Expected = "dxhSBHoI6eVSPcXJqEghlUzZMnY=",
@@ -225,13 +239,12 @@ signature_test1(_) ->
Date = "Sun, 10 Jul 2011 05:07:19 UTC",
Headers = [],
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
Sig = make_signature_string(Signature),
- Expected = "POST\n\n\nSun, 10 Jul 2011 05:07:19 UTC\n\n/tongs/ya/bas",
+ Expected = "POST\n\n\nSun, 10 Jul 2011 05:07:19 "
+ "UTC\n\n/tongs/ya/bas",
?assertEqual(Expected, Sig).
signature_test2(_) ->
@@ -242,13 +255,13 @@ signature_test2(_) ->
Date = "Sun, 10 Jul 2011 05:07:19 UTC",
Headers = [{"x-amz-acl", "public-read"}],
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
Sig = make_signature_string(Signature),
- Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz-acl:public-read\n/tongs/ya/bas",
+ Expected =
+ "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz"
+ "-acl:public-read\n/tongs/ya/bas",
?assertEqual(Expected, Sig).
signature_test3(_) ->
@@ -258,17 +271,17 @@ signature_test3(_) ->
ContentType = "",
Date = "Sun, 10 Jul 2011 05:07:19 UTC",
Headers = [{"x-amz-acl", "public-read"},
- {"yantze", "blast-off"},
- {"x-amz-doobie", "bongwater"},
- {"x-amz-acl", "public-write"}],
+ {"yantze", "blast-off"}, {"x-amz-doobie", "bongwater"},
+ {"x-amz-acl", "public-write"}],
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
Sig = make_signature_string(Signature),
- Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz-acl:public-read;public-write\nx-amz-doobie:bongwater\n/tongs/ya/bas",
+ Expected =
+ "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz"
+ "-acl:public-read;public-write\nx-amz-doobie:b"
+ "ongwater\n/tongs/ya/bas",
?assertEqual(Expected, Sig).
signature_test4(_) ->
@@ -278,17 +291,18 @@ signature_test4(_) ->
ContentType = "",
Date = "Sun, 10 Jul 2011 05:07:19 UTC",
Headers = [{"x-amz-acl", "public-read"},
- {"yantze", "blast-off"},
- {"x-amz-doobie oobie \t boobie ", "bongwater"},
- {"x-amz-acl", "public-write"}],
+ {"yantze", "blast-off"},
+ {"x-amz-doobie oobie \t boobie ", "bongwater"},
+ {"x-amz-acl", "public-write"}],
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
Sig = make_signature_string(Signature),
- Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz-acl:public-read;public-write\nx-amz-doobie oobie boobie:bongwater\n/tongs/ya/bas",
+ Expected =
+ "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz"
+ "-acl:public-read;public-write\nx-amz-doobie "
+ "oobie boobie:bongwater\n/tongs/ya/bas",
?assertEqual(Expected, Sig).
signature_test5(_) ->
@@ -298,138 +312,122 @@ signature_test5(_) ->
ContentType = "",
Date = "Sun, 10 Jul 2011 05:07:19 UTC",
Headers = [{"x-amz-acl", "public-Read"},
- {"yantze", "Blast-Off"},
- {"x-amz-doobie Oobie \t boobie ", "bongwater"},
- {"x-amz-acl", "public-write"}],
+ {"yantze", "Blast-Off"},
+ {"x-amz-doobie Oobie \t boobie ", "bongwater"},
+ {"x-amz-acl", "public-write"}],
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
Sig = make_signature_string(Signature),
- Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz-acl:public-Read;public-write\nx-amz-doobie oobie boobie:bongwater\n/tongs/ya/bas",
+ Expected =
+ "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\nx-amz"
+ "-acl:public-Read;public-write\nx-amz-doobie "
+ "oobie boobie:bongwater\n/tongs/ya/bas",
?assertEqual(Expected, Sig).
signature_test6(_) ->
- URL = "http://example.com:90/tongs/ya/bas/?andy&zbish=bash&bosh=burp",
+ URL = "http://example.com:90/tongs/ya/bas/?andy&zbis"
+ "h=bash&bosh=burp",
Method = get,
ContentMD5 = "",
ContentType = "",
Date = "Sun, 10 Jul 2011 05:07:19 UTC",
Headers = [],
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
Sig = make_signature_string(Signature),
Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\n\n"
- ++ "/tongs/ya/bas/?andy&bosh=burp&zbish=bash",
+ ++ "/tongs/ya/bas/?andy&bosh=burp&zbish=bash",
?assertEqual(Expected, Sig).
signature_test7(_) ->
- URL = "http://exAMPLE.Com:90/tONgs/ya/bas/?ANdy&ZBish=Bash&bOsh=burp",
+ URL = "http://exAMPLE.Com:90/tONgs/ya/bas/?ANdy&ZBis"
+ "h=Bash&bOsh=burp",
Method = get,
ContentMD5 = "",
ContentType = "",
Date = "Sun, 10 Jul 2011 05:07:19 UTC",
Headers = [],
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
Sig = make_signature_string(Signature),
Expected = "GET\n\n\nSun, 10 Jul 2011 05:07:19 UTC\n\n"
- ++"/tongs/ya/bas/?andy&bosh=burp&zbish=bash",
+ ++ "/tongs/ya/bas/?andy&bosh=burp&zbish=bash",
?assertEqual(Expected, Sig).
signature_test8(_) ->
- URL = "http://exAMPLE.Com:90/tONgs/ya/bas/?ANdy&ZBish=Bash&bOsh=burp",
+ URL = "http://exAMPLE.Com:90/tONgs/ya/bas/?ANdy&ZBis"
+ "h=Bash&bOsh=burp",
Method = get,
ContentMD5 = "",
ContentType = "",
Date = "",
- Headers = [{"x-aMz-daTe", "Tue, 27 Mar 2007 21:20:26 +0000"}],
+ Headers = [{"x-aMz-daTe",
+ "Tue, 27 Mar 2007 21:20:26 +0000"}],
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
Sig = make_signature_string(Signature),
- Expected = "GET\n\n\n\n"
- ++"x-amz-date:Tue, 27 Mar 2007 21:20:26 +0000\n"
- ++"/tongs/ya/bas/?andy&bosh=burp&zbish=bash",
+ Expected = "GET\n\n\n\n" ++
+ "x-amz-date:Tue, 27 Mar 2007 21:20:26 "
+ "+0000\n"
+ ++ "/tongs/ya/bas/?andy&bosh=burp&zbish=bash",
?assertEqual(Expected, Sig).
signature_test9(_) ->
- URL = "http://exAMPLE.Com:90/tONgs/ya/bas/?ANdy&ZBish=Bash&bOsh=burp",
+ URL = "http://exAMPLE.Com:90/tONgs/ya/bas/?ANdy&ZBis"
+ "h=Bash&bOsh=burp",
Method = get,
ContentMD5 = "",
ContentType = "",
Date = "Sun, 10 Jul 2011 05:07:19 UTC",
- Headers = [{"x-amz-date", "Tue, 27 Mar 2007 21:20:26 +0000"}],
+ Headers = [{"x-amz-date",
+ "Tue, 27 Mar 2007 21:20:26 +0000"}],
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
Sig = make_signature_string(Signature),
- Expected = "GET\n\n\n\n"
- ++"x-amz-date:Tue, 27 Mar 2007 21:20:26 +0000\n"
- ++"/tongs/ya/bas/?andy&bosh=burp&zbish=bash",
+ Expected = "GET\n\n\n\n" ++
+ "x-amz-date:Tue, 27 Mar 2007 21:20:26 "
+ "+0000\n"
+ ++ "/tongs/ya/bas/?andy&bosh=burp&zbish=bash",
?assertEqual(Expected, Sig).
amazon_test1(_) ->
- URL = "http://exAMPLE.Com:90/johnsmith/photos/puppy.jpg",
+ URL =
+ "http://exAMPLE.Com:90/johnsmith/photos/puppy.jpg",
Method = delete,
ContentMD5 = "",
ContentType = "",
Date = "",
- Headers = [{"x-amz-date", "Tue, 27 Mar 2007 21:20:26 +0000"}],
+ Headers = [{"x-amz-date",
+ "Tue, 27 Mar 2007 21:20:26 +0000"}],
Signature = #hmac_signature{method = Method,
- contentmd5 = ContentMD5,
- contenttype = ContentType,
- date = Date,
- headers = Headers,
- resource = URL},
+ contentmd5 = ContentMD5,
+ contenttype = ContentType, date = Date,
+ headers = Headers, resource = URL},
Sig = sign_data(?privatekey, Signature),
Expected = "k3nL7gH3+PadhTEVn5Ip83xlYzk=",
?assertEqual(Expected, Sig).
unit_test_() ->
- Setup = fun() -> ok end,
- Cleanup = fun(_) -> ok end,
-
- Series1 = [
- fun hash_test1/1,
- fun hash_test2/1,
- fun hash_test3/1
- ],
-
- Series2 = [
- fun signature_test1/1,
- fun signature_test2/1,
- fun signature_test3/1,
- fun signature_test4/1,
- fun signature_test5/1,
- fun signature_test6/1,
- fun signature_test7/1,
- fun signature_test8/1,
- fun signature_test9/1
- ],
-
- Series3 = [
- fun amazon_test1/1
- ],
-
- {setup, Setup, Cleanup, [
- {with, [], Series1},
- {with, [], Series2},
- {with, [], Series3}
- ]}.
+ Setup = fun () -> ok end,
+ Cleanup = fun (_) -> ok end,
+ Series1 = [fun hash_test1/1, fun hash_test2/1,
+ fun hash_test3/1],
+ Series2 = [fun signature_test1/1, fun signature_test2/1,
+ fun signature_test3/1, fun signature_test4/1,
+ fun signature_test5/1, fun signature_test6/1,
+ fun signature_test7/1, fun signature_test8/1,
+ fun signature_test9/1],
+ Series3 = [fun amazon_test1/1],
+ {setup, Setup, Cleanup,
+ [{with, [], Series1}, {with, [], Series2},
+ {with, [], Series3}]}.
diff --git a/examples/https/https_store.erl b/examples/https/https_store.erl
index 719f206..a9a4ec0 100644
--- a/examples/https/https_store.erl
+++ b/examples/https/https_store.erl
@@ -1,4 +1,3 @@
-
%% Trivial web storage app. It's available over both HTTP (port 8442)
%% and HTTPS (port 8443). You use a PUT to store items, a GET to
%% retrieve them and DELETE to delete them. The HTTP POST method is
@@ -34,113 +33,102 @@
-module(https_store).
--export([start/0,
- stop/0,
- dispatch/1,
- loop/1
- ]).
+-export([dispatch/1, loop/1, start/0, stop/0]).
--define(HTTP_OPTS, [
- {loop, {?MODULE, dispatch}},
- {port, 8442},
- {name, http_8442}
- ]).
+-define(HTTP_OPTS,
+ [{loop, {?MODULE, dispatch}}, {port, 8442},
+ {name, http_8442}]).
--define(HTTPS_OPTS, [
- {loop, {?MODULE, dispatch}},
- {port, 8443},
- {name, https_8443},
- {ssl, true},
- {ssl_opts, [
- {certfile, "server_cert.pem"},
- {keyfile, "server_key.pem"}]}
- ]).
+-define(HTTPS_OPTS,
+ [{loop, {?MODULE, dispatch}}, {port, 8443},
+ {name, https_8443}, {ssl, true},
+ {ssl_opts,
+ [{certfile, "server_cert.pem"},
+ {keyfile, "server_key.pem"}]}]).
-record(sd, {http, https}).
+
-record(resource, {type, data}).
start() ->
{ok, Http} = mochiweb_http:start(?HTTP_OPTS),
{ok, Https} = mochiweb_http:start(?HTTPS_OPTS),
- SD = #sd{http=Http, https=Https},
- Pid = spawn_link(fun() ->
- ets:new(?MODULE, [named_table]),
- loop(SD)
- end),
+ SD = #sd{http = Http, https = Https},
+ Pid = spawn_link(fun () ->
+ ets:new(?MODULE, [named_table]), loop(SD)
+ end),
register(http_store, Pid),
ok.
-stop() ->
- http_store ! stop,
- ok.
+stop() -> http_store ! stop, ok.
dispatch(Req) ->
case mochiweb_request:get(method, Req) of
- 'GET' ->
- get_resource(Req);
- 'PUT' ->
- put_resource(Req);
- 'DELETE' ->
- delete_resource(Req);
- _ ->
- Headers = [{"Allow", "GET,PUT,DELETE"}],
- mochiweb_request:respond({405, Headers, "405 Method Not Allowed\r\n"}, Req)
+ 'GET' -> get_resource(Req);
+ 'PUT' -> put_resource(Req);
+ 'DELETE' -> delete_resource(Req);
+ _ ->
+ Headers = [{"Allow", "GET,PUT,DELETE"}],
+ mochiweb_request:respond({405, Headers,
+ "405 Method Not Allowed\r\n"},
+ Req)
end.
get_resource(Req) ->
Path = mochiweb_request:get(path, Req),
case ets:lookup(?MODULE, Path) of
- [{Path, #resource{type=Type, data=Data}}] ->
- mochiweb_request:ok({Type, Data}, Req);
- [] ->
- mochiweb_request:respond({404, [], "404 Not Found\r\n"}, Req)
+ [{Path, #resource{type = Type, data = Data}}] ->
+ mochiweb_request:ok({Type, Data}, Req);
+ [] ->
+ mochiweb_request:respond({404, [], "404 Not Found\r\n"},
+ Req)
end.
put_resource(Req) ->
- ContentType = case mochiweb_request:get_header_value("Content-Type", Req) of
- undefined ->
- "application/octet-stream";
- S ->
- S
- end,
- Resource = #resource{type=ContentType, data=mochiweb_request:recv_body(Req)},
- http_store ! {self(), {put, mochiweb_request:get(path, Req), Resource}},
+ ContentType = case
+ mochiweb_request:get_header_value("Content-Type", Req)
+ of
+ undefined -> "application/octet-stream";
+ S -> S
+ end,
+ Resource = #resource{type = ContentType,
+ data = mochiweb_request:recv_body(Req)},
+ http_store !
+ {self(),
+ {put, mochiweb_request:get(path, Req), Resource}},
Pid = whereis(http_store),
receive
- {Pid, created} ->
- mochiweb_request:respond({201, [], "201 Created\r\n"}, Req);
- {Pid, updated} ->
- mochiweb_request:respond({200, [], "200 OK\r\n"}, Req)
+ {Pid, created} ->
+ mochiweb_request:respond({201, [], "201 Created\r\n"},
+ Req);
+ {Pid, updated} ->
+ mochiweb_request:respond({200, [], "200 OK\r\n"}, Req)
end.
delete_resource(Req) ->
- http_store ! {self(), {delete, mochiweb_request:get(path, Req)}},
+ http_store !
+ {self(), {delete, mochiweb_request:get(path, Req)}},
Pid = whereis(http_store),
receive
- {Pid, ok} ->
- mochiweb_request:respond({200, [], "200 OK\r\n"}, Req)
+ {Pid, ok} ->
+ mochiweb_request:respond({200, [], "200 OK\r\n"}, Req)
end.
-loop(#sd{http=Http, https=Https} = SD) ->
+loop(#sd{http = Http, https = Https} = SD) ->
receive
- stop ->
- ok = mochiweb_http:stop(Http),
- ok = mochiweb_http:stop(Https),
- exit(normal);
- {From, {put, Key, Val}} ->
- Exists = ets:member(?MODULE, Key),
- ets:insert(?MODULE, {Key, Val}),
- case Exists of
- true ->
- From ! {self(), updated};
- false ->
- From ! {self(), created}
- end;
- {From, {delete, Key}} ->
- ets:delete(?MODULE, Key),
- From ! {self(), ok};
- _ ->
- ignore
+ stop ->
+ ok = mochiweb_http:stop(Http),
+ ok = mochiweb_http:stop(Https),
+ exit(normal);
+ {From, {put, Key, Val}} ->
+ Exists = ets:member(?MODULE, Key),
+ ets:insert(?MODULE, {Key, Val}),
+ case Exists of
+ true -> From ! {self(), updated};
+ false -> From ! {self(), created}
+ end;
+ {From, {delete, Key}} ->
+ ets:delete(?MODULE, Key), From ! {self(), ok};
+ _ -> ignore
end,
- ?MODULE:loop(SD).
-
+ (?MODULE):loop(SD).
diff --git a/examples/keepalive/keepalive.erl b/examples/keepalive/keepalive.erl
index 51a7c3a..f6f4e53 100644
--- a/examples/keepalive/keepalive.erl
+++ b/examples/keepalive/keepalive.erl
@@ -18,64 +18,62 @@
%% response. if you do that then control flow returns to the proper place,
%% and keep alives work like they would if you hadn't hibernated.
--export([ start/1, loop/1
- ]).
+-export([loop/1, start/1]).
%% internal export (so hibernate can reach it)
--export([ resume/3
- ]).
+-export([resume/3]).
-define(LOOP, {?MODULE, loop}).
start(Options = [{port, _Port}]) ->
- mochiweb_http:start([{name, ?MODULE}, {loop, ?LOOP} | Options]).
+ mochiweb_http:start([{name, ?MODULE}, {loop, ?LOOP}
+ | Options]).
loop(Req) ->
Path = mochiweb_request:get(path, Req),
case string:tokens(Path, "/") of
- ["longpoll" | RestOfPath] ->
- %% the "reentry" is a continuation -- what @mochiweb_http@
- %% needs to do to start its loop back at the top
- Reentry = mochiweb_http:reentry(?LOOP),
-
- %% here we could send a message to some other process and hope
- %% to get an interesting message back after a while. for
- %% simplicity let's just send ourselves a message after a few
- %% seconds
- erlang:send_after(2000, self(), "honk honk"),
-
- %% since we expect to wait for a long time before getting a
- %% reply, let's hibernate. memory usage will be minimized, so
- %% we won't be wasting memory just sitting in a @receive@
- proc_lib:hibernate(?MODULE, resume, [Req, RestOfPath, Reentry]),
-
- %% we'll never reach this point, and this function @loop/1@
- %% won't ever return control to @mochiweb_http@. luckily
- %% @resume/3@ will take care of that.
- io:format("not gonna happen~n", []);
-
- _ ->
- ok(Req, io_lib:format("some other page: ~p", [Path]))
+ ["longpoll" | RestOfPath] ->
+ %% the "reentry" is a continuation -- what @mochiweb_http@
+ %% needs to do to start its loop back at the top
+ Reentry = mochiweb_http:reentry(?LOOP),
+ %% here we could send a message to some other process and hope
+ %% to get an interesting message back after a while. for
+ %% simplicity let's just send ourselves a message after a few
+ %% seconds
+ erlang:send_after(2000, self(), "honk honk"),
+ %% since we expect to wait for a long time before getting a
+ %% reply, let's hibernate. memory usage will be minimized, so
+ %% we won't be wasting memory just sitting in a @receive@
+ proc_lib:hibernate(?MODULE, resume,
+ [Req, RestOfPath, Reentry]),
+ %% we'll never reach this point, and this function @loop/1@
+ %% won't ever return control to @mochiweb_http@. luckily
+ %% @resume/3@ will take care of that.
+ io:format("not gonna happen~n", []);
+ _ ->
+ ok(Req, io_lib:format("some other page: ~p", [Path]))
end,
-
io:format("restarting loop normally in ~p~n", [Path]),
ok.
%% this is the function that's called when a message arrives.
resume(Req, RestOfPath, Reentry) ->
receive
- Msg ->
- Text = io_lib:format("wake up message: ~p~nrest of path: ~p", [Msg, RestOfPath]),
- ok(Req, Text)
+ Msg ->
+ Text =
+ io_lib:format("wake up message: ~p~nrest of path: ~p",
+ [Msg, RestOfPath]),
+ ok(Req, Text)
end,
-
%% if we didn't call @Reentry@ here then the function would finish and the
%% process would exit. calling @Reentry@ takes care of returning control
%% to @mochiweb_http@
- io:format("reentering loop via continuation in ~p~n", [mochiweb_request:get(path, Req)]),
+ io:format("reentering loop via continuation in "
+ "~p~n",
+ [mochiweb_request:get(path, Req)]),
Reentry(Req).
ok(Req, Response) ->
- mochiweb_request:ok(
- {_ContentType = "text/plain", _Headers = [], Response},
- Req).
+ mochiweb_request:ok({_ContentType = "text/plain",
+ _Headers = [], Response},
+ Req).
diff --git a/src/mochifmt.erl b/src/mochifmt.erl
index 1aa01c3..e7b9b59 100644
--- a/src/mochifmt.erl
+++ b/src/mochifmt.erl
@@ -23,28 +23,31 @@
%% (<a href="http://www.python.org/dev/peps/pep-3101/">PEP 3101</a>).
%%
-module(mochifmt).
+
-author('bob@mochimedia.com').
--export([format/2, format_field/2, convert_field/2, get_value/2, get_field/2]).
--export([tokenize/1, format/3, get_field/3, format_field/3]).
+-export([convert_field/2, format/2, format_field/2,
+ get_field/2, get_value/2]).
+
+-export([format/3, format_field/3, get_field/3,
+ tokenize/1]).
+
-export([bformat/2, bformat/3]).
+
-export([f/2, f/3]).
--record(conversion, {length, precision, ctype, align, fill_char, sign}).
+-record(conversion,
+ {length, precision, ctype, align, fill_char, sign}).
%% @spec tokenize(S::string()) -> tokens()
%% @doc Tokenize a format string into mochifmt's internal format.
-tokenize(S) ->
- {?MODULE, tokenize(S, "", [])}.
+tokenize(S) -> {?MODULE, tokenize(S, "", [])}.
%% @spec convert_field(Arg, Conversion::conversion()) -> term()
%% @doc Process Arg according to the given explicit conversion specifier.
-convert_field(Arg, "") ->
- Arg;
-convert_field(Arg, "r") ->
- repr(Arg);
-convert_field(Arg, "s") ->
- str(Arg).
+convert_field(Arg, "") -> Arg;
+convert_field(Arg, "r") -> repr(Arg);
+convert_field(Arg, "s") -> str(Arg).
%% @spec get_value(Key::string(), Args::args()) -> term()
%% @doc Get the Key from Args. If Args is a tuple then convert Key to
@@ -55,47 +58,43 @@ convert_field(Arg, "s") ->
get_value(Key, Args) when is_tuple(Args) ->
element(1 + list_to_integer(Key), Args);
get_value(Key, Args) when is_list(Args) ->
- try lists:nth(1 + list_to_integer(Key), Args)
- catch error:_ ->
- {_K, V} = proplist_lookup(Key, Args),
- V
+ try lists:nth(1 + list_to_integer(Key), Args) catch
+ error:_ -> {_K, V} = proplist_lookup(Key, Args), V
end.
%% @spec get_field(Key::string(), Args) -> term()
%% @doc Consecutively call get_value/2 on parts of Key delimited by ".",
%% replacing Args with the result of the previous get_value. This
%% is used to implement formats such as {0.0}.
-get_field(Key, Args) ->
- get_field(Key, Args, ?MODULE).
+get_field(Key, Args) -> get_field(Key, Args, ?MODULE).
%% @spec get_field(Key::string(), Args, Module) -> term()
%% @doc Consecutively call Module:get_value/2 on parts of Key delimited by ".",
%% replacing Args with the result of the previous get_value. This
%% is used to implement formats such as {0.0}.
get_field(Key, Args, Module) ->
- {Name, Next} = lists:splitwith(fun (C) -> C =/= $. end, Key),
+ {Name, Next} = lists:splitwith(fun (C) -> C =/= $. end,
+ Key),
Res = mod_get_value(Name, Args, Module),
case Next of
- "" ->
- Res;
- "." ++ S1 ->
- get_field(S1, Res, Module)
+ "" -> Res;
+ "." ++ S1 -> get_field(S1, Res, Module)
end.
mod_get_value(Name, Args, Module) ->
- try tuple_apply(Module, get_value, [Name, Args])
- catch error:undef -> get_value(Name, Args)
+ try tuple_apply(Module, get_value, [Name, Args]) catch
+ error:undef -> get_value(Name, Args)
end.
tuple_apply(Module, F, Args) when is_atom(Module) ->
erlang:apply(Module, F, Args);
-tuple_apply(Module, F, Args) when is_tuple(Module), is_atom(element(1, Module)) ->
+tuple_apply(Module, F, Args)
+ when is_tuple(Module), is_atom(element(1, Module)) ->
erlang:apply(element(1, Module), F, Args ++ [Module]).
%% @spec format(Format::string(), Args) -> iolist()
%% @doc Format Args with Format.
-format(Format, Args) ->
- format(Format, Args, ?MODULE).
+format(Format, Args) -> format(Format, Args, ?MODULE).
%% @spec format(Format::string(), Args, Module) -> iolist()
%% @doc Format Args with Format using Module.
@@ -117,17 +116,14 @@ format_field(Arg, Format, _Module) ->
%% @spec f(Format::string(), Args) -> string()
%% @doc Format Args with Format and return a string().
-f(Format, Args) ->
- f(Format, Args, ?MODULE).
+f(Format, Args) -> f(Format, Args, ?MODULE).
%% @spec f(Format::string(), Args, Module) -> string()
%% @doc Format Args with Format using Module and return a string().
f(Format, Args, Module) ->
case lists:member(${, Format) of
- true ->
- binary_to_list(bformat(Format, Args, Module));
- false ->
- Format
+ true -> binary_to_list(bformat(Format, Args, Module));
+ false -> Format
end.
%% @spec bformat(Format::string(), Args) -> binary()
@@ -142,25 +138,22 @@ bformat(Format, Args, Module) ->
%% Internal API
-add_raw("", Acc) ->
- Acc;
-add_raw(S, Acc) ->
- [{raw, lists:reverse(S)} | Acc].
+add_raw("", Acc) -> Acc;
+add_raw(S, Acc) -> [{raw, lists:reverse(S)} | Acc].
-tokenize([], S, Acc) ->
- lists:reverse(add_raw(S, Acc));
+tokenize([], S, Acc) -> lists:reverse(add_raw(S, Acc));
tokenize("{{" ++ Rest, S, Acc) ->
tokenize(Rest, "{" ++ S, Acc);
tokenize("{" ++ Rest, S, Acc) ->
{Format, Rest1} = tokenize_format(Rest),
- tokenize(Rest1, "", [{format, make_format(Format)} | add_raw(S, Acc)]);
+ tokenize(Rest1, "",
+ [{format, make_format(Format)} | add_raw(S, Acc)]);
tokenize("}}" ++ Rest, S, Acc) ->
tokenize(Rest, "}" ++ S, Acc);
tokenize([C | Rest], S, Acc) ->
tokenize(Rest, [C | S], Acc).
-tokenize_format(S) ->
- tokenize_format(S, 1, []).
+tokenize_format(S) -> tokenize_format(S, 1, []).
tokenize_format("}" ++ Rest, 1, Acc) ->
{lists:reverse(Acc), Rest};
@@ -172,90 +165,96 @@ tokenize_format([C | Rest], N, Acc) ->
tokenize_format(Rest, N, [C | Acc]).
make_format(S) ->
- {Name0, Spec} = case lists:splitwith(fun (C) -> C =/= $: end, S) of
- {_, ""} ->
- {S, ""};
- {SN, ":" ++ SS} ->
- {SN, SS}
- end,
- {Name, Transform} = case lists:splitwith(fun (C) -> C =/= $! end, Name0) of
- {_, ""} ->
- {Name0, ""};
- {TN, "!" ++ TT} ->
- {TN, TT}
- end,
+ {Name0, Spec} = case lists:splitwith(fun (C) -> C =/= $:
+ end,
+ S)
+ of
+ {_, ""} -> {S, ""};
+ {SN, ":" ++ SS} -> {SN, SS}
+ end,
+ {Name, Transform} = case lists:splitwith(fun (C) ->
+ C =/= $!
+ end,
+ Name0)
+ of
+ {_, ""} -> {Name0, ""};
+ {TN, "!" ++ TT} -> {TN, TT}
+ end,
{Name, Transform, Spec}.
proplist_lookup(S, P) ->
- A = try list_to_existing_atom(S)
- catch error:_ -> make_ref() end,
- B = try list_to_binary(S)
- catch error:_ -> make_ref() end,
+ A = try list_to_existing_atom(S) catch
+ error:_ -> make_ref()
+ end,
+ B = try list_to_binary(S) catch
+ error:_ -> make_ref()
+ end,
proplist_lookup2({S, A, B}, P).
proplist_lookup2({KS, KA, KB}, [{K, V} | _])
- when KS =:= K orelse KA =:= K orelse KB =:= K ->
+ when KS =:= K orelse KA =:= K orelse KB =:= K ->
{K, V};
proplist_lookup2(Keys, [_ | Rest]) ->
proplist_lookup2(Keys, Rest).
-format2([], _Args, _Module, Acc) ->
- lists:reverse(Acc);
+format2([], _Args, _Module, Acc) -> lists:reverse(Acc);
format2([{raw, S} | Rest], Args, Module, Acc) ->
format2(Rest, Args, Module, [S | Acc]);
-format2([{format, {Key, Convert, Format0}} | Rest], Args, Module, Acc) ->
+format2([{format, {Key, Convert, Format0}} | Rest],
+ Args, Module, Acc) ->
Format = f(Format0, Args, Module),
V = case Module of
- ?MODULE ->
- V0 = get_field(Key, Args),
- V1 = convert_field(V0, Convert),
- format_field(V1, Format);
- _ ->
- V0 = try tuple_apply(Module, get_field, [Key, Args])
- catch error:undef -> get_field(Key, Args, Module) end,
- V1 = try tuple_apply(Module, convert_field, [V0, Convert])
- catch error:undef -> convert_field(V0, Convert) end,
- try tuple_apply(Module, format_field, [V1, Format])
- catch error:undef -> format_field(V1, Format, Module) end
- end,
+ ?MODULE ->
+ V0 = get_field(Key, Args),
+ V1 = convert_field(V0, Convert),
+ format_field(V1, Format);
+ _ ->
+ V0 = try tuple_apply(Module, get_field, [Key, Args])
+ catch
+ error:undef -> get_field(Key, Args, Module)
+ end,
+ V1 = try tuple_apply(Module, convert_field,
+ [V0, Convert])
+ catch
+ error:undef -> convert_field(V0, Convert)
+ end,
+ try tuple_apply(Module, format_field, [V1, Format])
+ catch
+ error:undef -> format_field(V1, Format, Module)
+ end
+ end,
format2(Rest, Args, Module, [V | Acc]).
-default_ctype(_Arg, C=#conversion{ctype=N}) when N =/= undefined ->
+default_ctype(_Arg, C = #conversion{ctype = N})
+ when N =/= undefined ->
C;
default_ctype(Arg, C) when is_integer(Arg) ->
- C#conversion{ctype=decimal};
+ C#conversion{ctype = decimal};
default_ctype(Arg, C) when is_float(Arg) ->
- C#conversion{ctype=general};
-default_ctype(_Arg, C) ->
- C#conversion{ctype=string}.
+ C#conversion{ctype = general};
+default_ctype(_Arg, C) -> C#conversion{ctype = string}.
-fix_padding(Arg, #conversion{length=undefined}) ->
+fix_padding(Arg, #conversion{length = undefined}) ->
Arg;
-fix_padding(Arg, F=#conversion{length=Length, fill_char=Fill0, align=Align0,
- ctype=Type}) ->
+fix_padding(Arg,
+ F = #conversion{length = Length, fill_char = Fill0,
+ align = Align0, ctype = Type}) ->
Padding = Length - iolist_size(Arg),
Fill = case Fill0 of
- undefined ->
- $\s;
- _ ->
- Fill0
- end,
+ undefined -> $\s;
+ _ -> Fill0
+ end,
Align = case Align0 of
- undefined ->
- case Type of
- string ->
- left;
- _ ->
- right
- end;
- _ ->
- Align0
- end,
+ undefined ->
+ case Type of
+ string -> left;
+ _ -> right
+ end;
+ _ -> Align0
+ end,
case Padding > 0 of
- true ->
- do_padding(Arg, Padding, Fill, Align, F);
- false ->
- Arg
+ true -> do_padding(Arg, Padding, Fill, Align, F);
+ false -> Arg
end.
do_padding(Arg, Padding, Fill, right, _F) ->
@@ -263,31 +262,31 @@ do_padding(Arg, Padding, Fill, right, _F) ->
do_padding(Arg, Padding, Fill, center, _F) ->
LPadding = lists:duplicate(Padding div 2, Fill),
RPadding = case Padding band 1 of
- 1 ->
- [Fill | LPadding];
- _ ->
- LPadding
- end,
+ 1 -> [Fill | LPadding];
+ _ -> LPadding
+ end,
[LPadding, Arg, RPadding];
do_padding([$- | Arg], Padding, Fill, sign_right, _F) ->
[[$- | lists:duplicate(Padding, Fill)], Arg];
-do_padding(Arg, Padding, Fill, sign_right, #conversion{sign=$-}) ->
+do_padding(Arg, Padding, Fill, sign_right,
+ #conversion{sign = $-}) ->
[lists:duplicate(Padding, Fill), Arg];
-do_padding([S | Arg], Padding, Fill, sign_right, #conversion{sign=S}) ->
+do_padding([S | Arg], Padding, Fill, sign_right,
+ #conversion{sign = S}) ->
[[S | lists:duplicate(Padding, Fill)], Arg];
-do_padding(Arg, Padding, Fill, sign_right, #conversion{sign=undefined}) ->
+do_padding(Arg, Padding, Fill, sign_right,
+ #conversion{sign = undefined}) ->
[lists:duplicate(Padding, Fill), Arg];
do_padding(Arg, Padding, Fill, left, _F) ->
[Arg | lists:duplicate(Padding, Fill)].
-fix_sign(Arg, #conversion{sign=$+}) when Arg >= 0 ->
+fix_sign(Arg, #conversion{sign = $+}) when Arg >= 0 ->
[$+, Arg];
-fix_sign(Arg, #conversion{sign=$\s}) when Arg >= 0 ->
+fix_sign(Arg, #conversion{sign = $\s}) when Arg >= 0 ->
[$\s, Arg];
-fix_sign(Arg, _F) ->
- Arg.
+fix_sign(Arg, _F) -> Arg.
-ctype($\%) -> percent;
+ctype($%) -> percent;
ctype($s) -> string;
ctype($b) -> bin;
ctype($o) -> oct;
@@ -304,107 +303,123 @@ align($>) -> right;
align($^) -> center;
align($=) -> sign_right.
-convert2(Arg, F=#conversion{ctype=percent}) ->
- [convert2(100.0 * Arg, F#conversion{ctype=fixed}), $\%];
-convert2(Arg, #conversion{ctype=string}) ->
- str(Arg);
-convert2(Arg, #conversion{ctype=bin}) ->
+convert2(Arg, F = #conversion{ctype = percent}) ->
+ [convert2(1.0e+2 * Arg, F#conversion{ctype = fixed}),
+ $%];
+convert2(Arg, #conversion{ctype = string}) -> str(Arg);
+convert2(Arg, #conversion{ctype = bin}) ->
erlang:integer_to_list(Arg, 2);
-convert2(Arg, #conversion{ctype=oct}) ->
+convert2(Arg, #conversion{ctype = oct}) ->
erlang:integer_to_list(Arg, 8);
-convert2(Arg, #conversion{ctype=upper_hex}) ->
+convert2(Arg, #conversion{ctype = upper_hex}) ->
erlang:integer_to_list(Arg, 16);
-convert2(Arg, #conversion{ctype=hex}) ->
+convert2(Arg, #conversion{ctype = hex}) ->
string:to_lower(erlang:integer_to_list(Arg, 16));
-convert2(Arg, #conversion{ctype=char}) when Arg < 16#80 ->
+convert2(Arg, #conversion{ctype = char})
+ when Arg < 128 ->
[Arg];
-convert2(Arg, #conversion{ctype=char}) ->
+convert2(Arg, #conversion{ctype = char}) ->
xmerl_ucs:to_utf8(Arg);
-convert2(Arg, #conversion{ctype=decimal}) ->
+convert2(Arg, #conversion{ctype = decimal}) ->
integer_to_list(Arg);
-convert2(Arg, #conversion{ctype=general, precision=undefined}) ->
- try mochinum:digits(Arg)
- catch error:undef -> io_lib:format("~g", [Arg]) end;
-convert2(Arg, #conversion{ctype=fixed, precision=undefined}) ->
+convert2(Arg,
+ #conversion{ctype = general, precision = undefined}) ->
+ try mochinum:digits(Arg) catch
+ error:undef -> io_lib:format("~g", [Arg])
+ end;
+convert2(Arg,
+ #conversion{ctype = fixed, precision = undefined}) ->
io_lib:format("~f", [Arg]);
-convert2(Arg, #conversion{ctype=exp, precision=undefined}) ->
+convert2(Arg,
+ #conversion{ctype = exp, precision = undefined}) ->
io_lib:format("~e", [Arg]);
-convert2(Arg, #conversion{ctype=general, precision=P}) ->
+convert2(Arg,
+ #conversion{ctype = general, precision = P}) ->
io_lib:format("~." ++ integer_to_list(P) ++ "g", [Arg]);
-convert2(Arg, #conversion{ctype=fixed, precision=P}) ->
+convert2(Arg,
+ #conversion{ctype = fixed, precision = P}) ->
io_lib:format("~." ++ integer_to_list(P) ++ "f", [Arg]);
-convert2(Arg, #conversion{ctype=exp, precision=P}) ->
+convert2(Arg,
+ #conversion{ctype = exp, precision = P}) ->
io_lib:format("~." ++ integer_to_list(P) ++ "e", [Arg]).
-str(A) when is_atom(A) ->
- atom_to_list(A);
-str(I) when is_integer(I) ->
- integer_to_list(I);
+str(A) when is_atom(A) -> atom_to_list(A);
+str(I) when is_integer(I) -> integer_to_list(I);
str(F) when is_float(F) ->
- try mochinum:digits(F)
- catch error:undef -> io_lib:format("~g", [F]) end;
-str(L) when is_list(L) ->
- L;
-str(B) when is_binary(B) ->
- B;
-str(P) ->
- repr(P).
+ try mochinum:digits(F) catch
+ error:undef -> io_lib:format("~g", [F])
+ end;
+str(L) when is_list(L) -> L;
+str(B) when is_binary(B) -> B;
+str(P) -> repr(P).
repr(P) when is_float(P) ->
- try mochinum:digits(P)
- catch error:undef -> float_to_list(P) end;
-repr(P) ->
- io_lib:format("~p", [P]).
+ try mochinum:digits(P) catch
+ error:undef -> float_to_list(P)
+ end;
+repr(P) -> io_lib:format("~p", [P]).
parse_std_conversion(S) ->
parse_std_conversion(S, #conversion{}).
-parse_std_conversion("", Acc) ->
- Acc;
+parse_std_conversion("", Acc) -> Acc;
parse_std_conversion([Fill, Align | Spec], Acc)
- when Align =:= $< orelse Align =:= $> orelse Align =:= $= orelse Align =:= $^ ->
- parse_std_conversion(Spec, Acc#conversion{fill_char=Fill,
- align=align(Align)});
+ when Align =:= $< orelse
+ Align =:= $> orelse Align =:= $= orelse Align =:= $^ ->
+ parse_std_conversion(Spec,
+ Acc#conversion{fill_char = Fill,
+ align = align(Align)});
parse_std_conversion([Align | Spec], Acc)
- when Align =:= $< orelse Align =:= $> orelse Align =:= $= orelse Align =:= $^ ->
- parse_std_conversion(Spec, Acc#conversion{align=align(Align)});
+ when Align =:= $< orelse
+ Align =:= $> orelse Align =:= $= orelse Align =:= $^ ->
+ parse_std_conversion(Spec,
+ Acc#conversion{align = align(Align)});
parse_std_conversion([Sign | Spec], Acc)
- when Sign =:= $+ orelse Sign =:= $- orelse Sign =:= $\s ->
- parse_std_conversion(Spec, Acc#conversion{sign=Sign});
+ when Sign =:= $+ orelse
+ Sign =:= $- orelse Sign =:= $\s ->
+ parse_std_conversion(Spec, Acc#conversion{sign = Sign});
parse_std_conversion("0" ++ Spec, Acc) ->
Align = case Acc#conversion.align of
- undefined ->
- sign_right;
- A ->
- A
- end,
- parse_std_conversion(Spec, Acc#conversion{fill_char=$0, align=Align});
-parse_std_conversion(Spec=[D|_], Acc) when D >= $0 andalso D =< $9 ->
- {W, Spec1} = lists:splitwith(fun (C) -> C >= $0 andalso C =< $9 end, Spec),
- parse_std_conversion(Spec1, Acc#conversion{length=list_to_integer(W)});
+ undefined -> sign_right;
+ A -> A
+ end,
+ parse_std_conversion(Spec,
+ Acc#conversion{fill_char = $0, align = Align});
+parse_std_conversion(Spec = [D | _], Acc)
+ when D >= $0 andalso D =< $9 ->
+ {W, Spec1} = lists:splitwith(fun (C) ->
+ C >= $0 andalso C =< $9
+ end,
+ Spec),
+ parse_std_conversion(Spec1,
+ Acc#conversion{length = list_to_integer(W)});
parse_std_conversion([$. | Spec], Acc) ->
- case lists:splitwith(fun (C) -> C >= $0 andalso C =< $9 end, Spec) of
- {"", Spec1} ->
- parse_std_conversion(Spec1, Acc);
- {P, Spec1} ->
- parse_std_conversion(Spec1,
- Acc#conversion{precision=list_to_integer(P)})
+ case lists:splitwith(fun (C) -> C >= $0 andalso C =< $9
+ end,
+ Spec)
+ of
+ {"", Spec1} -> parse_std_conversion(Spec1, Acc);
+ {P, Spec1} ->
+ parse_std_conversion(Spec1,
+ Acc#conversion{precision = list_to_integer(P)})
end;
parse_std_conversion([Type], Acc) ->
- parse_std_conversion("", Acc#conversion{ctype=ctype(Type)}).
-
+ parse_std_conversion("",
+ Acc#conversion{ctype = ctype(Type)}).
%%
%% Tests
%%
-ifdef(TEST).
+
-include_lib("eunit/include/eunit.hrl").
tokenize_test() ->
{?MODULE, [{raw, "ABC"}]} = tokenize("ABC"),
{?MODULE, [{format, {"0", "", ""}}]} = tokenize("{0}"),
- {?MODULE, [{raw, "ABC"}, {format, {"1", "", ""}}, {raw, "DEF"}]} =
- tokenize("ABC{1}DEF"),
+ {?MODULE,
+ [{raw, "ABC"}, {format, {"1", "", ""}}, {raw, "DEF"}]} =
+ tokenize("ABC{1}DEF"),
ok.
format_test() ->
@@ -413,26 +428,31 @@ format_test() ->
<<" 4">> = bformat("{0:{0}}", [4]),
<<"4 ">> = bformat("{0:4}", ["4"]),
<<"4 ">> = bformat("{0:{0}}", ["4"]),
- <<"1.2yoDEF">> = bformat("{2}{0}{1}{3}", {yo, "DE", 1.2, <<"F">>}),
- <<"cafebabe">> = bformat("{0:x}", {16#cafebabe}),
- <<"CAFEBABE">> = bformat("{0:X}", {16#cafebabe}),
- <<"CAFEBABE">> = bformat("{0:X}", {16#cafebabe}),
- <<"755">> = bformat("{0:o}", {8#755}),
+ <<"1.2yoDEF">> = bformat("{2}{0}{1}{3}",
+ {yo, "DE", 1.19999999999999995559, <<"F">>}),
+ <<"cafebabe">> = bformat("{0:x}", {3405691582}),
+ <<"CAFEBABE">> = bformat("{0:X}", {3405691582}),
+ <<"CAFEBABE">> = bformat("{0:X}", {3405691582}),
+ <<"755">> = bformat("{0:o}", {493}),
<<"a">> = bformat("{0:c}", {97}),
%% Horizontal ellipsis
- <<226, 128, 166>> = bformat("{0:c}", {16#2026}),
+ <<226, 128, 166>> = bformat("{0:c}", {8230}),
<<"11">> = bformat("{0:b}", {3}),
<<"11">> = bformat("{0:b}", [3]),
<<"11">> = bformat("{three:b}", [{three, 3}]),
<<"11">> = bformat("{three:b}", [{"three", 3}]),
<<"11">> = bformat("{three:b}", [{<<"three">>, 3}]),
<<"\"foo\"">> = bformat("{0!r}", {"foo"}),
- <<"2008-5-4">> = bformat("{0.0}-{0.1}-{0.2}", {{2008,5,4}}),
- <<"2008-05-04">> = bformat("{0.0:04}-{0.1:02}-{0.2:02}", {{2008,5,4}}),
+ <<"2008-5-4">> = bformat("{0.0}-{0.1}-{0.2}",
+ {{2008, 5, 4}}),
+ <<"2008-05-04">> = bformat("{0.0:04}-{0.1:02}-{0.2:02}",
+ {{2008, 5, 4}}),
<<"foo6bar-6">> = bformat("foo{1}{0}-{1}", {bar, 6}),
- <<"-'atom test'-">> = bformat("-{arg!r}-", [{arg, 'atom test'}]),
- <<"2008-05-04">> = bformat("{0.0:0{1.0}}-{0.1:0{1.1}}-{0.2:0{1.2}}",
- {{2008,5,4}, {4, 2, 2}}),
+ <<"-'atom test'-">> = bformat("-{arg!r}-",
+ [{arg, 'atom test'}]),
+ <<"2008-05-04">> =
+ bformat("{0.0:0{1.0}}-{0.1:0{1.1}}-{0.2:0{1.2}}",
+ {{2008, 5, 4}, {4, 2, 2}}),
ok.
std_test() ->
@@ -441,13 +461,16 @@ std_test() ->
ok.
records_test() ->
- M = mochifmt_records:new([{conversion, record_info(fields, conversion)}]),
- R = #conversion{length=long, precision=hard, sign=peace},
+ M = mochifmt_records:new([{conversion,
+ record_info(fields, conversion)}]),
+ R = #conversion{length = long, precision = hard,
+ sign = peace},
long = mochifmt_records:get_value("length", R, M),
hard = mochifmt_records:get_value("precision", R, M),
peace = mochifmt_records:get_value("sign", R, M),
<<"long hard">> = bformat("{length} {precision}", R, M),
- <<"long hard">> = bformat("{0.length} {0.precision}", [R], M),
+ <<"long hard">> = bformat("{0.length} {0.precision}",
+ [R], M),
ok.
-endif.
diff --git a/src/mochiweb_acceptor.erl b/src/mochiweb_acceptor.erl
index 1b53552..2fed6bd 100644
--- a/src/mochiweb_acceptor.erl
+++ b/src/mochiweb_acceptor.erl
@@ -22,11 +22,12 @@
%% @doc MochiWeb acceptor.
-module(mochiweb_acceptor).
+
-author('bob@mochimedia.com').
-include("internal.hrl").
--export([start_link/3, start_link/4, init/4]).
+-export([init/4, start_link/3, start_link/4]).
-define(EMFILE_SLEEP_MSEC, 100).
@@ -34,43 +35,40 @@ start_link(Server, Listen, Loop) ->
start_link(Server, Listen, Loop, []).
start_link(Server, Listen, Loop, Opts) ->
- proc_lib:spawn_link(?MODULE, init, [Server, Listen, Loop, Opts]).
+ proc_lib:spawn_link(?MODULE, init,
+ [Server, Listen, Loop, Opts]).
do_accept(Server, Listen) ->
T1 = os:timestamp(),
case mochiweb_socket:transport_accept(Listen) of
- {ok, Socket} ->
- gen_server:cast(Server, {accepted, self(), timer:now_diff(os:timestamp(), T1)}),
- mochiweb_socket:finish_accept(Socket);
- Other ->
- Other
+ {ok, Socket} ->
+ gen_server:cast(Server,
+ {accepted, self(),
+ timer:now_diff(os:timestamp(), T1)}),
+ mochiweb_socket:finish_accept(Socket);
+ Other -> Other
end.
init(Server, Listen, Loop, Opts) ->
case catch do_accept(Server, Listen) of
- {ok, Socket} ->
- call_loop(Loop, Socket, Opts);
- {error, Err} when Err =:= closed orelse
- Err =:= esslaccept orelse
- Err =:= timeout ->
- exit(normal);
- Other ->
- %% Mitigate out of file descriptor scenario by sleeping for a
- %% short time to slow error rate
- case Other of
- {error, emfile} ->
- receive
- after ?EMFILE_SLEEP_MSEC ->
- ok
- end;
- _ ->
- ok
- end,
- error_logger:error_report(
- [{application, mochiweb},
- "Accept failed error",
- lists:flatten(io_lib:format("~p", [Other]))]),
- exit({error, accept_failed})
+ {ok, Socket} -> call_loop(Loop, Socket, Opts);
+ {error, Err}
+ when Err =:= closed orelse
+ Err =:= esslaccept orelse Err =:= timeout ->
+ exit(normal);
+ Other ->
+ %% Mitigate out of file descriptor scenario by sleeping for a
+ %% short time to slow error rate
+ case Other of
+ {error, emfile} ->
+ receive after ?EMFILE_SLEEP_MSEC -> ok end;
+ _ -> ok
+ end,
+ error_logger:error_report([{application, mochiweb},
+ "Accept failed error",
+ lists:flatten(io_lib:format("~p",
+ [Other]))]),
+ exit({error, accept_failed})
end.
call_loop({M, F}, Socket, Opts) when is_atom(M) ->
@@ -79,5 +77,4 @@ call_loop({M, F, [A1]}, Socket, Opts) when is_atom(M) ->
M:F(Socket, Opts, A1);
call_loop({M, F, A}, Socket, Opts) when is_atom(M) ->
erlang:apply(M, F, [Socket, Opts | A]);
-call_loop(Loop, Socket, Opts) ->
- Loop(Socket, Opts).
+call_loop(Loop, Socket, Opts) -> Loop(Socket, Opts).
diff --git a/src/mochiweb_http.erl b/src/mochiweb_http.erl
index 6854b6a..aff95aa 100644
--- a/src/mochiweb_http.erl
+++ b/src/mochiweb_http.erl
@@ -22,38 +22,47 @@
%% @doc HTTP server.
-module(mochiweb_http).
+
-author('bob@mochimedia.com').
+
-export([start/1, start_link/1, stop/0, stop/1]).
+
-export([loop/3]).
+
-export([after_response/2, reentry/1]).
+
-export([parse_range_request/1, range_skip_length/2]).
--define(REQUEST_RECV_TIMEOUT, 300000). %% timeout waiting for request line
--define(HEADERS_RECV_TIMEOUT, 30000). %% timeout waiting for headers
+-define(REQUEST_RECV_TIMEOUT,
+ 300000). %% timeout waiting for request line
+
+-define(HEADERS_RECV_TIMEOUT,
+ 30000). %% timeout waiting for headers
-define(MAX_HEADERS, 1000).
--define(DEFAULTS, [{name, ?MODULE},
- {port, 8888}]).
+
+-define(DEFAULTS, [{name, ?MODULE}, {port, 8888}]).
-ifdef(gen_tcp_r15b_workaround).
-r15b_workaround() -> true.
--else.
+
r15b_workaround() -> false.
--endif.
+-else.
+
+r15b_workaround() -> false.
+-endif.
parse_options(Options) ->
{loop, HttpLoop} = proplists:lookup(loop, Options),
Loop = {?MODULE, loop, [HttpLoop]},
- Options1 = [{loop, Loop} | proplists:delete(loop, Options)],
+ Options1 = [{loop, Loop} | proplists:delete(loop,
+ Options)],
mochilists:set_defaults(?DEFAULTS, Options1).
-stop() ->
- mochiweb_socket_server:stop(?MODULE).
+stop() -> mochiweb_socket_server:stop(?MODULE).
-stop(Name) ->
- mochiweb_socket_server:stop(Name).
+stop(Name) -> mochiweb_socket_server:stop(Name).
%% @spec start(Options) -> ServerRet
%% Options = [option()]
@@ -76,240 +85,263 @@ start_link(Options) ->
ensure_started(M) when is_atom(M) ->
case M:start() of
- {ok, _Pid} ->
- ok;
- {error, {already_started, _Pid}} ->
- ok
+ {ok, _Pid} -> ok;
+ {error, {already_started, _Pid}} -> ok
end.
loop(Socket, Opts, Body) ->
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, http}])),
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{packet,
+ http}])),
request(Socket, Opts, Body).
request(Socket, Opts, Body) ->
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{active, once}])),
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{active,
+ once}])),
receive
- {Protocol, _, {http_request, Method, Path, Version}} when Protocol == http orelse Protocol == ssl ->
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, httph}])),
- headers(Socket, Opts, {Method, Path, Version}, [], Body, 0);
- {Protocol, _, {http_error, "\r\n"}} when Protocol == http orelse Protocol == ssl ->
- request(Socket, Opts, Body);
- {Protocol, _, {http_error, "\n"}} when Protocol == http orelse Protocol == ssl ->
- request(Socket, Opts, Body);
- {tcp_closed, _} ->
- mochiweb_socket:close(Socket),
- exit(normal);
- {tcp_error, _, emsgsize} = Other ->
- handle_invalid_msg_request(Other, Socket, Opts);
- {ssl_closed, _} ->
- mochiweb_socket:close(Socket),
- exit(normal)
- after ?REQUEST_RECV_TIMEOUT ->
- mochiweb_socket:close(Socket),
- exit(normal)
+ {Protocol, _, {http_request, Method, Path, Version}}
+ when Protocol == http orelse Protocol == ssl ->
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{packet,
+ httph}])),
+ headers(Socket, Opts, {Method, Path, Version}, [], Body,
+ 0);
+ {Protocol, _, {http_error, "\r\n"}}
+ when Protocol == http orelse Protocol == ssl ->
+ request(Socket, Opts, Body);
+ {Protocol, _, {http_error, "\n"}}
+ when Protocol == http orelse Protocol == ssl ->
+ request(Socket, Opts, Body);
+ {tcp_closed, _} ->
+ mochiweb_socket:close(Socket), exit(normal);
+ {tcp_error, _, emsgsize} = Other ->
+ handle_invalid_msg_request(Other, Socket, Opts);
+ {ssl_closed, _} ->
+ mochiweb_socket:close(Socket), exit(normal)
+ after ?REQUEST_RECV_TIMEOUT ->
+ mochiweb_socket:close(Socket), exit(normal)
end.
reentry(Body) ->
- fun (Req) ->
- ?MODULE:after_response(Body, Req)
- end.
+ fun (Req) -> (?MODULE):after_response(Body, Req) end.
-headers(Socket, Opts, Request, Headers, _Body, ?MAX_HEADERS) ->
+headers(Socket, Opts, Request, Headers, _Body,
+ ?MAX_HEADERS) ->
%% Too many headers sent, bad request.
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{packet,
+ raw}])),
handle_invalid_request(Socket, Opts, Request, Headers);
-headers(Socket, Opts, Request, Headers, Body, HeaderCount) ->
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{active, once}])),
+headers(Socket, Opts, Request, Headers, Body,
+ HeaderCount) ->
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{active,
+ once}])),
receive
- {Protocol, _, http_eoh} when Protocol == http orelse Protocol == ssl ->
- Req = new_request(Socket, Opts, Request, Headers),
- call_body(Body, Req),
- ?MODULE:after_response(Body, Req);
- {Protocol, _, {http_header, _, Name, _, Value}} when Protocol == http orelse Protocol == ssl ->
- headers(Socket, Opts, Request, [{Name, Value} | Headers], Body,
- 1 + HeaderCount);
- {tcp_closed, _} ->
- mochiweb_socket:close(Socket),
- exit(normal);
- {tcp_error, _, emsgsize} = Other ->
- handle_invalid_msg_request(Other, Socket, Opts, Request, Headers)
- after ?HEADERS_RECV_TIMEOUT ->
- mochiweb_socket:close(Socket),
- exit(normal)
+ {Protocol, _, http_eoh}
+ when Protocol == http orelse Protocol == ssl ->
+ Req = new_request(Socket, Opts, Request, Headers),
+ call_body(Body, Req),
+ (?MODULE):after_response(Body, Req);
+ {Protocol, _, {http_header, _, Name, _, Value}}
+ when Protocol == http orelse Protocol == ssl ->
+ headers(Socket, Opts, Request,
+ [{Name, Value} | Headers], Body, 1 + HeaderCount);
+ {tcp_closed, _} ->
+ mochiweb_socket:close(Socket), exit(normal);
+ {tcp_error, _, emsgsize} = Other ->
+ handle_invalid_msg_request(Other, Socket, Opts, Request,
+ Headers)
+ after ?HEADERS_RECV_TIMEOUT ->
+ mochiweb_socket:close(Socket), exit(normal)
end.
call_body({M, F, A}, Req) when is_atom(M) ->
erlang:apply(M, F, [Req | A]);
-call_body({M, F}, Req) when is_atom(M) ->
- M:F(Req);
-call_body(Body, Req) ->
- Body(Req).
+call_body({M, F}, Req) when is_atom(M) -> M:F(Req);
+call_body(Body, Req) -> Body(Req).
+
+-spec handle_invalid_msg_request(term(), term(),
+ term()) -> no_return().
--spec handle_invalid_msg_request(term(), term(), term()) -> no_return().
handle_invalid_msg_request(Msg, Socket, Opts) ->
- handle_invalid_msg_request(Msg, Socket, Opts, {'GET', {abs_path, "/"}, {0,9}}, []).
+ handle_invalid_msg_request(Msg, Socket, Opts,
+ {'GET', {abs_path, "/"}, {0, 9}}, []).
+
+-spec handle_invalid_msg_request(term(), term(), term(),
+ term(), term()) -> no_return().
--spec handle_invalid_msg_request(term(), term(), term(), term(), term()) -> no_return().
-handle_invalid_msg_request(Msg, Socket, Opts, Request, RevHeaders) ->
+handle_invalid_msg_request(Msg, Socket, Opts, Request,
+ RevHeaders) ->
case {Msg, r15b_workaround()} of
- {{tcp_error,_,emsgsize}, true} ->
- %% R15B02 returns this then closes the socket, so close and exit
- mochiweb_socket:close(Socket),
- exit(normal);
- _ ->
- handle_invalid_request(Socket, Opts, Request, RevHeaders)
+ {{tcp_error, _, emsgsize}, true} ->
+ %% R15B02 returns this then closes the socket, so close and exit
+ mochiweb_socket:close(Socket),
+ exit(normal);
+ _ ->
+ handle_invalid_request(Socket, Opts, Request,
+ RevHeaders)
end.
--spec handle_invalid_request(term(), term(), term(), term()) -> no_return().
-handle_invalid_request(Socket, Opts, Request, RevHeaders) ->
- {ReqM, _} = Req = new_request(Socket, Opts, Request, RevHeaders),
+-spec handle_invalid_request(term(), term(), term(),
+ term()) -> no_return().
+
+handle_invalid_request(Socket, Opts, Request,
+ RevHeaders) ->
+ {ReqM, _} = Req = new_request(Socket, Opts, Request,
+ RevHeaders),
ReqM:respond({400, [], []}, Req),
mochiweb_socket:close(Socket),
exit(normal).
new_request(Socket, Opts, Request, RevHeaders) ->
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
- mochiweb:new_request({Socket, Opts, Request, lists:reverse(RevHeaders)}).
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{packet,
+ raw}])),
+ mochiweb:new_request({Socket, Opts, Request,
+ lists:reverse(RevHeaders)}).
after_response(Body, {ReqM, _} = Req) ->
Socket = ReqM:get(socket, Req),
case ReqM:should_close(Req) of
- true ->
- mochiweb_socket:close(Socket),
- exit(normal);
- false ->
- ReqM:cleanup(Req),
- erlang:garbage_collect(),
- ?MODULE:loop(Socket, mochiweb_request:get(opts, Req), Body)
+ true -> mochiweb_socket:close(Socket), exit(normal);
+ false ->
+ ReqM:cleanup(Req),
+ erlang:garbage_collect(),
+ (?MODULE):loop(Socket, mochiweb_request:get(opts, Req),
+ Body)
end.
parse_range_request(RawRange) when is_list(RawRange) ->
- try
- "bytes=" ++ RangeString = RawRange,
- RangeTokens = [string:strip(R) || R <- string:tokens(RangeString, ",")],
- Ranges = [R || R <- RangeTokens, string:len(R) > 0],
- lists:map(fun ("-" ++ V) ->
- {none, list_to_integer(V)};
- (R) ->
- case string:tokens(R, "-") of
- [S1, S2] ->
- {list_to_integer(S1), list_to_integer(S2)};
- [S] ->
- {list_to_integer(S), none}
- end
- end,
- Ranges)
+ try "bytes=" ++ RangeString = RawRange,
+ RangeTokens = [string:strip(R)
+ || R <- string:tokens(RangeString, ",")],
+ Ranges = [R || R <- RangeTokens, string:len(R) > 0],
+ [parse_range_request_1(V1) || V1 <- Ranges]
catch
- _:_ ->
- fail
+ _:_ -> fail
+ end.
+
+parse_range_request_1("-" ++ V) ->
+ {none, list_to_integer(V)};
+parse_range_request_1(R) ->
+ case string:tokens(R, "-") of
+ [S1, S2] -> {list_to_integer(S1), list_to_integer(S2)};
+ [S] -> {list_to_integer(S), none}
end.
range_skip_length(Spec, Size) ->
case Spec of
- {none, R} when R =< Size, R >= 0 ->
- {Size - R, R};
- {none, _OutOfRange} ->
- {0, Size};
- {R, none} when R >= 0, R < Size ->
- {R, Size - R};
- {_OutOfRange, none} ->
- invalid_range;
- {Start, End} when Start >= 0, Start < Size, Start =< End ->
- {Start, erlang:min(End + 1, Size) - Start};
- {_InvalidStart, _InvalidEnd} ->
- invalid_range
+ {none, R} when R =< Size, R >= 0 -> {Size - R, R};
+ {none, _OutOfRange} -> {0, Size};
+ {R, none} when R >= 0, R < Size -> {R, Size - R};
+ {_OutOfRange, none} -> invalid_range;
+ {Start, End}
+ when Start >= 0, Start < Size, Start =< End ->
+ {Start, erlang:min(End + 1, Size) - Start};
+ {_InvalidStart, _InvalidEnd} -> invalid_range
end.
%%
%% Tests
%%
-ifdef(TEST).
+
-include_lib("eunit/include/eunit.hrl").
range_test() ->
%% valid, single ranges
- ?assertEqual([{20, 30}], parse_range_request("bytes=20-30")),
- ?assertEqual([{20, none}], parse_range_request("bytes=20-")),
- ?assertEqual([{none, 20}], parse_range_request("bytes=-20")),
-
+ ?assertEqual([{20, 30}],
+ (parse_range_request("bytes=20-30"))),
+ ?assertEqual([{20, none}],
+ (parse_range_request("bytes=20-"))),
+ ?assertEqual([{none, 20}],
+ (parse_range_request("bytes=-20"))),
%% trivial single range
- ?assertEqual([{0, none}], parse_range_request("bytes=0-")),
-
+ ?assertEqual([{0, none}],
+ (parse_range_request("bytes=0-"))),
%% invalid, single ranges
- ?assertEqual(fail, parse_range_request("")),
- ?assertEqual(fail, parse_range_request("garbage")),
- ?assertEqual(fail, parse_range_request("bytes=-20-30")),
-
+ ?assertEqual(fail, (parse_range_request(""))),
+ ?assertEqual(fail, (parse_range_request("garbage"))),
+ ?assertEqual(fail,
+ (parse_range_request("bytes=-20-30"))),
%% valid, multiple range
- ?assertEqual(
- [{20, 30}, {50, 100}, {110, 200}],
- parse_range_request("bytes=20-30,50-100,110-200")),
- ?assertEqual(
- [{20, none}, {50, 100}, {none, 200}],
- parse_range_request("bytes=20-,50-100,-200")),
-
+ ?assertEqual([{20, 30}, {50, 100}, {110, 200}],
+ (parse_range_request("bytes=20-30,50-100,110-200"))),
+ ?assertEqual([{20, none}, {50, 100}, {none, 200}],
+ (parse_range_request("bytes=20-,50-100,-200"))),
%% valid, multiple range with whitespace
- ?assertEqual(
- [{20, 30}, {50, 100}, {110, 200}],
- parse_range_request("bytes=20-30, 50-100 , 110-200")),
-
+ ?assertEqual([{20, 30}, {50, 100}, {110, 200}],
+ (parse_range_request("bytes=20-30, 50-100 , 110-200"))),
%% valid, multiple range with extra commas
- ?assertEqual(
- [{20, 30}, {50, 100}, {110, 200}],
- parse_range_request("bytes=20-30,,50-100,110-200")),
- ?assertEqual(
- [{20, 30}, {50, 100}, {110, 200}],
- parse_range_request("bytes=20-30, ,50-100,,,110-200")),
-
+ ?assertEqual([{20, 30}, {50, 100}, {110, 200}],
+ (parse_range_request("bytes=20-30,,50-100,110-200"))),
+ ?assertEqual([{20, 30}, {50, 100}, {110, 200}],
+ (parse_range_request("bytes=20-30, ,50-100,,,110-200"))),
%% no ranges
- ?assertEqual([], parse_range_request("bytes=")),
+ ?assertEqual([], (parse_range_request("bytes="))),
ok.
range_skip_length_test() ->
- Body = <<"012345678901234567890123456789012345678901234567890123456789">>,
+ Body = <<"012345678901234567890123456789012345678901234"
+ "567890123456789">>,
BodySize = byte_size(Body), %% 60
BodySize = 60,
-
%% these values assume BodySize =:= 60
- ?assertEqual({1,9}, range_skip_length({1,9}, BodySize)), %% 1-9
- ?assertEqual({10,10}, range_skip_length({10,19}, BodySize)), %% 10-19
- ?assertEqual({40, 20}, range_skip_length({none, 20}, BodySize)), %% -20
- ?assertEqual({30, 30}, range_skip_length({30, none}, BodySize)), %% 30-
-
+ ?assertEqual({1, 9},
+ (range_skip_length({1, 9}, BodySize))), %% 1-9
+ ?assertEqual({10, 10},
+ (range_skip_length({10, 19}, BodySize))), %% 10-19
+ ?assertEqual({40, 20},
+ (range_skip_length({none, 20}, BodySize))), %% -20
+ ?assertEqual({30, 30},
+ (range_skip_length({30, none}, BodySize))), %% 30-
%% valid edge cases for range_skip_length
- ?assertEqual({BodySize, 0}, range_skip_length({none, 0}, BodySize)),
- ?assertEqual({0, BodySize}, range_skip_length({none, BodySize}, BodySize)),
- ?assertEqual({0, BodySize}, range_skip_length({0, none}, BodySize)),
- ?assertEqual({0, BodySize}, range_skip_length({0, BodySize + 1}, BodySize)),
+ ?assertEqual({BodySize, 0},
+ (range_skip_length({none, 0}, BodySize))),
+ ?assertEqual({0, BodySize},
+ (range_skip_length({none, BodySize}, BodySize))),
+ ?assertEqual({0, BodySize},
+ (range_skip_length({0, none}, BodySize))),
+ ?assertEqual({0, BodySize},
+ (range_skip_length({0, BodySize + 1}, BodySize))),
BodySizeLess1 = BodySize - 1,
?assertEqual({BodySizeLess1, 1},
- range_skip_length({BodySize - 1, none}, BodySize)),
+ (range_skip_length({BodySize - 1, none}, BodySize))),
?assertEqual({BodySizeLess1, 1},
- range_skip_length({BodySize - 1, BodySize+5}, BodySize)),
+ (range_skip_length({BodySize - 1, BodySize + 5},
+ BodySize))),
?assertEqual({BodySizeLess1, 1},
- range_skip_length({BodySize - 1, BodySize}, BodySize)),
-
+ (range_skip_length({BodySize - 1, BodySize},
+ BodySize))),
%% out of range, return whole thing
?assertEqual({0, BodySize},
- range_skip_length({none, BodySize + 1}, BodySize)),
+ (range_skip_length({none, BodySize + 1}, BodySize))),
?assertEqual({0, BodySize},
- range_skip_length({none, -1}, BodySize)),
+ (range_skip_length({none, -1}, BodySize))),
?assertEqual({0, BodySize},
- range_skip_length({0, BodySize + 1}, BodySize)),
-
+ (range_skip_length({0, BodySize + 1}, BodySize))),
%% invalid ranges
?assertEqual(invalid_range,
- range_skip_length({-1, 30}, BodySize)),
+ (range_skip_length({-1, 30}, BodySize))),
?assertEqual(invalid_range,
- range_skip_length({-1, BodySize + 1}, BodySize)),
+ (range_skip_length({-1, BodySize + 1}, BodySize))),
?assertEqual(invalid_range,
- range_skip_length({BodySize, 40}, BodySize)),
+ (range_skip_length({BodySize, 40}, BodySize))),
?assertEqual(invalid_range,
- range_skip_length({-1, none}, BodySize)),
+ (range_skip_length({-1, none}, BodySize))),
?assertEqual(invalid_range,
- range_skip_length({BodySize, none}, BodySize)),
+ (range_skip_length({BodySize, none}, BodySize))),
?assertEqual(invalid_range,
- range_skip_length({BodySize + 1, BodySize + 5}, BodySize)),
+ (range_skip_length({BodySize + 1, BodySize + 5},
+ BodySize))),
ok.
-endif.
diff --git a/src/mochiweb_multipart.erl b/src/mochiweb_multipart.erl
index 46b7090..21b23bb 100644
--- a/src/mochiweb_multipart.erl
+++ b/src/mochiweb_multipart.erl
@@ -22,16 +22,21 @@
%% @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}).
+-record(mp,
+ {state, boundary, length, buffer, callback, req}).
%% TODO: DOCUMENT THIS MODULE.
%% @type key() = atom() | string() | binary().
@@ -47,28 +52,30 @@
%% 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) ->
+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)]}],
+ {"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_body(BodyList, ContentType, Size)
+ when is_list(BodyList) ->
parts_to_multipart_body(BodyList, ContentType, Size,
- mochihex:to_hex(crypto:strong_rand_bytes(8))).
+ mochihex:to_hex(crypto:strong_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) ->
+parts_to_multipart_body(BodyList, ContentType, Size,
+ Boundary) ->
HeaderList = [{"Content-Type",
- ["multipart/byteranges; ",
- "boundary=", Boundary]}],
- MultiPartBody = multipart_body(BodyList, ContentType, Boundary, Size),
-
+ ["multipart/byteranges; ", "boundary=", Boundary]}],
+ MultiPartBody = multipart_body(BodyList, ContentType,
+ Boundary, Size),
{HeaderList, MultiPartBody}.
%% @spec multipart_body([bodypart()], ContentType::string(),
@@ -76,14 +83,15 @@ parts_to_multipart_body(BodyList, ContentType, Size, Boundary) ->
%% @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)].
+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
@@ -94,168 +102,196 @@ parse_form(Req) ->
%% @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,
+ 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(eof, _, Acc) -> lists:reverse(Acc);
parse_form_outer({headers, H}, FileHandler, State) ->
- {"form-data", H1} = proplists:get_value("content-disposition", H),
+ {"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
+ 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))),
+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) ->
+ 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.
+ fun (Next) ->
+ parse_form_value(Next, {Name, Acc1}, FileHandler, State)
+ end.
-parse_form_file(body_end, {Name, Handler}, FileHandler, State) ->
+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) ->
+ 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.
+ 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])
+ 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({ReqM, _} = Req, Callback) ->
%% TODO: Support chunked?
- Length = list_to_integer(ReqM:get_combined_header_value("content-length", Req)),
- Boundary = iolist_to_binary(
- get_boundary(ReqM:get_header_value("content-type", Req))),
+ Length =
+ list_to_integer(ReqM:get_combined_header_value("content-length",
+ Req)),
+ Boundary =
+ iolist_to_binary(get_boundary(ReqM:get_header_value("content-type",
+ Req))),
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, []).
+ <<"--", 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])
+ {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)),
+ {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({ReqM, _} = Req, Length) when Length > 0 ->
case Length of
- Length when Length < ?CHUNKSIZE ->
- ReqM:recv(Length, Req);
- _ ->
- ReqM:recv(?CHUNKSIZE, Req)
+ Length when Length < (?CHUNKSIZE) ->
+ ReqM:recv(Length, Req);
+ _ -> ReqM:recv(?CHUNKSIZE, Req)
end.
-read_more(State=#mp{length=Length, buffer=Buffer, req=Req}) ->
+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 =
+ Length - byte_size(Data),
+ buffer = Buffer1}).
-flash_multipart_hack(State=#mp{length=0, buffer=Buffer, boundary=Prefix}) ->
+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
+ 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}) ->
+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_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),
+ {"multipart/form-data", Opts} =
+ mochiweb_util:parse_header(ContentType),
case proplists:get_value("boundary", Opts) of
- S when is_list(S) ->
- S
+ S when is_list(S) -> S
end.
%% @spec find_in_binary(Pattern::binary(), Data::binary()) ->
@@ -265,323 +301,332 @@ find_in_binary(P, Data) when size(P) > 0 ->
PS = size(P),
DS = size(Data),
case DS - PS of
- Last when Last < 0 ->
- partial_find(P, Data, 0, DS);
- Last ->
- case binary:match(Data, P) of
- {Pos, _} -> {exact, Pos};
- nomatch -> partial_find(P, Data, Last+1, PS-1)
- end
+ Last when Last < 0 -> partial_find(P, Data, 0, DS);
+ Last ->
+ case binary:match(Data, P) of
+ {Pos, _} -> {exact, Pos};
+ nomatch -> partial_find(P, Data, Last + 1, PS - 1)
+ end
end.
-partial_find(_B, _D, _N, 0) ->
- not_found;
+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)
+ <<_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
+ {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
%%
-ifdef(TEST).
+
-include_lib("eunit/include/eunit.hrl").
ssl_cert_opts() ->
EbinDir = filename:dirname(code:which(?MODULE)),
- CertDir = filename:join([EbinDir, "..", "support", "test-materials"]),
+ 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}],
+ 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_link(ServerOpts),
+ plain -> ServerOpts0;
+ ssl ->
+ ServerOpts0 ++
+ [{ssl, true}, {ssl_opts, ssl_cert_opts()}]
+ end,
+ {ok, Server} =
+ mochiweb_socket_server:start_link(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 = mochiweb_test_util:ssl_client_opts(ClientOpts),
- {ok, SslSocket} = ssl:connect("127.0.0.1", Port, ClientOpts1),
- {ok, {ssl, SslSocket}}
- end,
+ plain -> gen_tcp:connect("127.0.0.1", Port, ClientOpts);
+ ssl ->
+ ClientOpts1 =
+ mochiweb_test_util:ssl_client_opts(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 | _]) ->
+ 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 ->
+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;
+ 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
+ [] -> ok;
+ _ -> fun (Next) -> test_callback(Next, Rest) end
end.
-parse3_http_test() ->
- parse3(plain).
+parse3_http_test() -> parse3(plain).
-parse3_https_test() ->
- parse3(ssl).
+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">>,
+ ContentType =
+ "multipart/form-data; boundary=---------------"
+ "------------7386909285754635891697677882",
+ BinContent =
+ <<"-----------------------------7386909285754635"
+ "891697677882\r\nContent-Disposition: "
+ "form-data; name=\"hidden\"\r\n\r\nmultipart "
+ "message\r\n-----------------------------73869"
+ "09285754635891697677882\r\nContent-Dispositio"
+ "n: form-data; name=\"file\"; filename=\"test_"
+ "file.txt\"\r\nContent-Type: text/plain\r\n\r\n"
+ "Woo multiline text file\n\nLa la la\r\n------"
+ "-----------------------7386909285754635891697"
+ "677882--\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,
+ [{"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, _Opts) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
+ 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),
+ 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_http_test() -> parse2(plain).
-parse2_https_test() ->
- parse2(ssl).
+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">>,
+ ContentType =
+ "multipart/form-data; boundary=---------------"
+ "------------6072231407570234361599764024",
+ BinContent =
+ <<"-----------------------------6072231407570234"
+ "361599764024\r\nContent-Disposition: "
+ "form-data; name=\"hidden\"\r\n\r\nmultipart "
+ "message\r\n-----------------------------60722"
+ "31407570234361599764024\r\nContent-Dispositio"
+ "n: form-data; name=\"file\"; filename=\"\"\r\n"
+ "Content-Type: application/octet-stream\r\n\r\n\r\n"
+ "-----------------------------6072231407570234"
+ "361599764024--\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,
+ [{"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, _Opts) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
+ 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),
+ 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_http_test() -> do_parse_form(plain).
-parse_form_https_test() ->
- do_parse_form(ssl).
+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"),
+ 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, _Opts) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
+ 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),
+ 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_http_test() -> do_parse(plain).
-parse_https_test() ->
- do_parse(ssl).
+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"),
+ 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,
+ [{"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, _Opts) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
+ 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),
+ 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(plain).
parse_partial_body_boundary_https_test() ->
- parse_partial_body_boundary(ssl).
+ 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"),
+ 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,
+ [{"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, _Opts) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
+ 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),
+ 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() ->
@@ -593,64 +638,69 @@ parse_large_header_https_test() ->
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"),
+ 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,
+ [{"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, _Opts) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
+ 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),
+ 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">>),
+ {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>>,
+ P = <<"\r\n-----------------------------160374543510"
+ "82272548568224146">>,
+ 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.
@@ -660,231 +710,262 @@ find_in_binary_test() ->
{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">>),
+ {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_http_test() -> flash_parse(plain).
-flash_parse_https_test() ->
- flash_parse(ssl).
+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-Dis [...]
+ ContentType =
+ "multipart/form-data; boundary=----------ei4GI"
+ "3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
+ "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" =
+ get_boundary(ContentType),
+ BinContent =
+ <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\n"
+ "Content-Disposition: form-data; name=\"Filena"
+ "me\"\r\n\r\nhello.txt\r\n------------ei4GI3GI"
+ "3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition"
+ ": form-data; name=\"success_action_status\"\r\n\r\n"
+ "201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei"
+ "4Ij5\r\nContent-Disposition: form-data; "
+ "name=\"file\"; filename=\"hello.txt\"\r\nCont"
+ "ent-Type: application/octet-stream\r\n\r\nhel"
+ "lo\n\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5e"
+ "i4Ij5\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,
+ [{"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, _Opts) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
+ 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),
+ 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_http_test() -> flash_parse2(plain).
-flash_parse2_https_test() ->
- flash_parse2(ssl).
+flash_parse2_https_test() -> flash_parse2(ssl).
flash_parse2(Transport) ->
- ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
- "----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5" = get_boundary(ContentType),
+ ContentType =
+ "multipart/form-data; boundary=----------ei4GI"
+ "3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
+ "----------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\n [...]
+ BinContent =
+ <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\n"
+ "Content-Disposition: form-data; name=\"Filena"
+ "me\"\r\n\r\nhello.txt\r\n------------ei4GI3GI"
+ "3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition"
+ ": form-data; name=\"success_action_status\"\r\n\r\n"
+ "201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei"
+ "4Ij5\r\nContent-Disposition: form-data; "
+ "name=\"file\"; filename=\"hello.txt\"\r\nCont"
+ "ent-Type: application/octet-stream\r\n\r\n",
+ Chunk/binary,
+ "\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij"
+ "5\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,
+ [{"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, _Opts) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
+ 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),
+ 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(<<>>)).
+ ?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)).
+ 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)),
+ "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)),
+ {[{"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)),
+ {[{"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))),
+ ?assertEqual(<<"--BOUNDARY--\r\n">>,
+ (iolist_to_binary(multipart_body([], "text/plain",
+ "BOUNDARY", 0)))),
+ ?assertEqual(<<"--BOUNDARY\r\nContent-Type: text/plain\r\nCon"
+ "tent-Range: bytes 0-5/10\r\n\r\n01234\r\n--BO"
+ "UNDARY\r\nContent-Type: text/plain\r\nContent"
+ "-Range: bytes 5-10/10\r\n\r\n56789\r\n--BOUND"
+ "ARY--\r\n">>,
+ (iolist_to_binary(multipart_body([{0, 5, <<"01234">>},
+ {5, 10, <<"56789">>}],
+ "text/plain", "BOUNDARY",
+ 10)))),
ok.
%% @todo Move somewhere more appropriate than in the test suite
multipart_parsing_benchmark_test() ->
- run_multipart_parsing_benchmark(1).
+ run_multipart_parsing_benchmark(1).
run_multipart_parsing_benchmark(0) -> ok;
run_multipart_parsing_benchmark(N) ->
- multipart_parsing_benchmark(),
- run_multipart_parsing_benchmark(N-1).
+ multipart_parsing_benchmark(),
+ run_multipart_parsing_benchmark(N - 1).
multipart_parsing_benchmark() ->
- ContentType = "multipart/form-data; boundary=----------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
- Chunk = binary:copy(<<"This Is_%Some=Quite0Long4String2Used9For7BenchmarKing.5">>, 102400),
- 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\n [...]
+ ContentType =
+ "multipart/form-data; boundary=----------ei4GI"
+ "3GI3Ij5Ef1ae0KM7Ij5ei4Ij5",
+ Chunk =
+ binary:copy(<<"This Is_%Some=Quite0Long4String2Used9For7Benc"
+ "hmarKing.5">>,
+ 102400),
+ BinContent =
+ <<"------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij5\r\n"
+ "Content-Disposition: form-data; name=\"Filena"
+ "me\"\r\n\r\nhello.txt\r\n------------ei4GI3GI"
+ "3Ij5Ef1ae0KM7Ij5ei4Ij5\r\nContent-Disposition"
+ ": form-data; name=\"success_action_status\"\r\n\r\n"
+ "201\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei"
+ "4Ij5\r\nContent-Disposition: form-data; "
+ "name=\"file\"; filename=\"hello.txt\"\r\nCont"
+ "ent-Type: application/octet-stream\r\n\r\n",
+ Chunk/binary,
+ "\r\n------------ei4GI3GI3Ij5Ef1ae0KM7Ij5ei4Ij"
+ "5\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,
+ [{"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, _Opts) ->
- ok = mochiweb_socket:send(Socket, BinContent),
- exit(normal)
- end,
+ 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,
+ Req = fake_request(Socket, ContentType,
+ byte_size(BinContent)),
+ Res = parse_multipart_request(Req, TestCallback),
+ {0, <<>>, ok} = Res,
+ ok
+ end,
ok = with_socket_server(plain, ServerFun, ClientFun),
ok.
+
-endif.
diff --git a/src/mochiweb_request.erl b/src/mochiweb_request.erl
index 3889d33..a1d2d6e 100644
--- a/src/mochiweb_request.erl
+++ b/src/mochiweb_request.erl
@@ -22,33 +22,57 @@
%% @doc MochiWeb HTTP Request abstraction.
-module(mochiweb_request).
+
-author('bob@mochimedia.com').
-include_lib("kernel/include/file.hrl").
+
-include("internal.hrl").
-define(QUIP, "Any of you quaids got a smint?").
-export([new/5, new/6]).
--export([get_header_value/2, get_primary_header_value/2, get_combined_header_value/2, get/2, dump/1]).
--export([send/2, recv/2, recv/3, recv_body/1, recv_body/2, stream_body/4, stream_body/5]).
--export([start_response/2, start_response_length/2, start_raw_response/2]).
--export([respond/2, ok/2]).
+
+-export([dump/1, get/2, get_combined_header_value/2,
+ get_header_value/2, get_primary_header_value/2]).
+
+-export([recv/2, recv/3, recv_body/1, recv_body/2,
+ send/2, stream_body/4, stream_body/5]).
+
+-export([start_raw_response/2, start_response/2,
+ start_response_length/2]).
+
+-export([ok/2, respond/2]).
+
-export([not_found/1, not_found/2]).
+
-export([parse_post/1, parse_qs/1]).
--export([should_close/1, cleanup/1]).
--export([parse_cookie/1, get_cookie_value/2]).
+
+-export([cleanup/1, should_close/1]).
+
+-export([get_cookie_value/2, parse_cookie/1]).
+
-export([serve_file/3, serve_file/4]).
+
-export([accepted_encodings/2]).
--export([accepts_content_type/2, accepted_content_types/2]).
+
+-export([accepted_content_types/2,
+ accepts_content_type/2]).
-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 key() = atom() | string() | binary()
@@ -62,7 +86,7 @@
-define(IDLE_TIMEOUT, 300000).
% Maximum recv_body() length of 1MB
--define(MAX_RECV_BODY, (1024*1024)).
+-define(MAX_RECV_BODY, 1024 * 1024).
%% @spec new(Socket, Method, RawPath, Version, headers()) -> request()
%% @doc Create a new request instance.
@@ -72,17 +96,27 @@ new(Socket, Method, RawPath, Version, Headers) ->
%% @spec new(Socket, Opts, Method, RawPath, Version, headers()) -> request()
%% @doc Create a new request instance.
new(Socket, Opts, Method, RawPath, Version, Headers) ->
- {?MODULE, [Socket, Opts, Method, RawPath, Version, Headers]}.
+ {?MODULE,
+ [Socket, Opts, Method, RawPath, Version, Headers]}.
%% @spec get_header_value(K, request()) -> undefined | Value
%% @doc Get the value of a given request header.
-get_header_value(K, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, Headers]}) ->
+get_header_value(K,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ Headers]}) ->
mochiweb_headers:get_value(K, Headers).
-get_primary_header_value(K, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, Headers]}) ->
+get_primary_header_value(K,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ Headers]}) ->
mochiweb_headers:get_primary_value(K, Headers).
-get_combined_header_value(K, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, Headers]}) ->
+get_combined_header_value(K,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ Headers]}) ->
mochiweb_headers:get_combined_value(K, Headers).
%% @type field() = socket | scheme | method | raw_path | version | headers | peer | path | body_length | range
@@ -93,246 +127,303 @@ get_combined_header_value(K, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Vers
%% 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, {?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
+get(socket,
+ {?MODULE,
+ [Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]}) ->
Socket;
-get(scheme, {?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
+get(scheme,
+ {?MODULE,
+ [Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]}) ->
case mochiweb_socket:type(Socket) of
- plain ->
- http;
- ssl ->
- https
+ plain -> http;
+ ssl -> https
end;
-get(method, {?MODULE, [_Socket, _Opts, Method, _RawPath, _Version, _Headers]}) ->
+get(method,
+ {?MODULE,
+ [_Socket, _Opts, Method, _RawPath, _Version,
+ _Headers]}) ->
Method;
-get(raw_path, {?MODULE, [_Socket, _Opts, _Method, RawPath, _Version, _Headers]}) ->
+get(raw_path,
+ {?MODULE,
+ [_Socket, _Opts, _Method, RawPath, _Version,
+ _Headers]}) ->
RawPath;
-get(version, {?MODULE, [_Socket, _Opts, _Method, _RawPath, Version, _Headers]}) ->
+get(version,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, Version,
+ _Headers]}) ->
Version;
-get(headers, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, Headers]}) ->
+get(headers,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ Headers]}) ->
Headers;
-get(peer, {?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+get(peer,
+ {?MODULE,
+ [Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
case mochiweb_socket:peername(Socket) of
- {ok, {Addr={10, _, _, _}, _Port}} ->
- case get_header_value("x-forwarded-for", THIS) of
- undefined ->
- inet_parse:ntoa(Addr);
- Hosts ->
- string:strip(lists:last(string:tokens(Hosts, ",")))
- end;
- %% Copied this syntax from webmachine contributor Steve Vinoski
- {ok, {Addr={172, Second, _, _}, _Port}} when (Second > 15) andalso (Second < 32) ->
- case get_header_value("x-forwarded-for", THIS) of
- undefined ->
- inet_parse:ntoa(Addr);
- Hosts ->
- string:strip(lists:last(string:tokens(Hosts, ",")))
- end;
- %% According to RFC 6598, contributor Gerald Xv
- {ok, {Addr={100, Second, _, _}, _Port}} when (Second > 63) andalso (Second < 128) ->
- case get_header_value("x-forwarded-for", THIS) of
- undefined ->
- inet_parse:ntoa(Addr);
- Hosts ->
- string:strip(lists:last(string:tokens(Hosts, ",")))
- end;
- {ok, {Addr={192, 168, _, _}, _Port}} ->
- case get_header_value("x-forwarded-for", THIS) 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", THIS) 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)
+ {ok, {Addr = {10, _, _, _}, _Port}} ->
+ case get_header_value("x-forwarded-for", THIS) of
+ undefined -> inet_parse:ntoa(Addr);
+ Hosts ->
+ string:strip(lists:last(string:tokens(Hosts, ",")))
+ end;
+ %% Copied this syntax from webmachine contributor Steve Vinoski
+ {ok, {Addr = {172, Second, _, _}, _Port}}
+ when Second > 15 andalso Second < 32 ->
+ case get_header_value("x-forwarded-for", THIS) of
+ undefined -> inet_parse:ntoa(Addr);
+ Hosts ->
+ string:strip(lists:last(string:tokens(Hosts, ",")))
+ end;
+ %% According to RFC 6598, contributor Gerald Xv
+ {ok, {Addr = {100, Second, _, _}, _Port}}
+ when Second > 63 andalso Second < 128 ->
+ case get_header_value("x-forwarded-for", THIS) of
+ undefined -> inet_parse:ntoa(Addr);
+ Hosts ->
+ string:strip(lists:last(string:tokens(Hosts, ",")))
+ end;
+ {ok, {Addr = {192, 168, _, _}, _Port}} ->
+ case get_header_value("x-forwarded-for", THIS) 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", THIS) 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, {?MODULE, [_Socket, _Opts, _Method, RawPath, _Version, _Headers]}) ->
+get(path,
+ {?MODULE,
+ [_Socket, _Opts, _Method, RawPath, _Version,
+ _Headers]}) ->
case erlang:get(?SAVE_PATH) of
- undefined ->
- {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath),
- Path = mochiweb_util:normalize_path(mochiweb_util:unquote(Path0)),
- put(?SAVE_PATH, Path),
- Path;
- Cached ->
- Cached
+ undefined ->
+ {Path0, _, _} = mochiweb_util:urlsplit_path(RawPath),
+ Path =
+ mochiweb_util:normalize_path(mochiweb_util:unquote(Path0)),
+ put(?SAVE_PATH, Path),
+ Path;
+ Cached -> Cached
end;
-get(body_length, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+get(body_length,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
case erlang:get(?SAVE_BODY_LENGTH) of
- undefined ->
- BodyLength = body_length(THIS),
- put(?SAVE_BODY_LENGTH, {cached, BodyLength}),
- BodyLength;
- {cached, Cached} ->
- Cached
+ undefined ->
+ BodyLength = body_length(THIS),
+ put(?SAVE_BODY_LENGTH, {cached, BodyLength}),
+ BodyLength;
+ {cached, Cached} -> Cached
end;
-get(range, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+get(range,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
case get_header_value(range, THIS) of
- undefined ->
- undefined;
- RawRange ->
- mochiweb_http:parse_range_request(RawRange)
+ undefined -> undefined;
+ RawRange -> mochiweb_http:parse_range_request(RawRange)
end;
-get(opts, {?MODULE, [_Socket, Opts, _Method, _RawPath, _Version, _Headers]}) ->
+get(opts,
+ {?MODULE,
+ [_Socket, Opts, _Method, _RawPath, _Version,
+ _Headers]}) ->
Opts.
%% @spec dump(request()) -> {mochiweb_request, [{atom(), term()}]}
%% @doc Dump the internal representation to a "human readable" set of terms
%% for debugging/inspection purposes.
-dump({?MODULE, [_Socket, Opts, Method, RawPath, Version, Headers]}) ->
- {?MODULE, [{method, Method},
- {version, Version},
- {raw_path, RawPath},
- {opts, Opts},
- {headers, mochiweb_headers:to_list(Headers)}]}.
+dump({?MODULE,
+ [_Socket, Opts, Method, RawPath, Version, Headers]}) ->
+ {?MODULE,
+ [{method, Method}, {version, Version},
+ {raw_path, RawPath}, {opts, Opts},
+ {headers, mochiweb_headers:to_list(Headers)}]}.
%% @spec send(iodata(), request()) -> ok
%% @doc Send data over the socket.
-send(Data, {?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
+send(Data,
+ {?MODULE,
+ [Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]}) ->
case mochiweb_socket:send(Socket, Data) of
- ok ->
- ok;
- _ ->
- exit(normal)
+ ok -> ok;
+ _ -> exit(normal)
end.
%% @spec recv(integer(), request()) -> binary()
%% @doc Receive Length bytes from the client as a binary, with the default
%% idle timeout.
-recv(Length, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+recv(Length,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
recv(Length, ?IDLE_TIMEOUT, THIS).
%% @spec recv(integer(), integer(), request()) -> binary()
%% @doc Receive Length bytes from the client as a binary, with the given
%% Timeout in msec.
-recv(Length, Timeout, {?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
+recv(Length, Timeout,
+ {?MODULE,
+ [Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]}) ->
case mochiweb_socket:recv(Socket, Length, Timeout) of
- {ok, Data} ->
- put(?SAVE_RECV, true),
- Data;
- _ ->
- exit(normal)
+ {ok, Data} -> put(?SAVE_RECV, true), Data;
+ _ -> exit(normal)
end.
%% @spec body_length(request()) -> undefined | chunked | unknown_transfer_encoding | integer()
%% @doc Infer body length from transfer-encoding and content-length headers.
-body_length({?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+body_length({?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
case get_header_value("transfer-encoding", THIS) of
- undefined ->
- case get_combined_header_value("content-length", THIS) of
- undefined ->
- undefined;
- Length ->
- list_to_integer(Length)
- end;
- "chunked" ->
- chunked;
- Unknown ->
- {unknown_transfer_encoding, Unknown}
+ undefined ->
+ case get_combined_header_value("content-length", THIS)
+ of
+ undefined -> undefined;
+ Length -> list_to_integer(Length)
+ end;
+ "chunked" -> chunked;
+ Unknown -> {unknown_transfer_encoding, Unknown}
end.
-
%% @spec recv_body(request()) -> 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({?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+recv_body({?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
recv_body(?MAX_RECV_BODY, THIS).
%% @spec recv_body(integer(), request()) -> binary()
%% @doc Receive the body of the HTTP request (defined by Content-Length).
%% Will receive up to MaxBody bytes.
-recv_body(MaxBody, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+recv_body(MaxBody,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
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, THIS),
- put(?SAVE_BODY, Body),
- Body;
- Cached -> Cached
+ 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, THIS),
+ put(?SAVE_BODY, Body),
+ Body;
+ Cached -> Cached
end.
-stream_body(MaxChunkSize, ChunkFun, FunState, {?MODULE,[_Socket,_Opts,_Method,_RawPath,_Version,_Headers]}=THIS) ->
- stream_body(MaxChunkSize, ChunkFun, FunState, undefined, THIS).
-
-stream_body(MaxChunkSize, ChunkFun, FunState, MaxBodyLength,
- {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+stream_body(MaxChunkSize, ChunkFun, FunState,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
+ stream_body(MaxChunkSize, ChunkFun, FunState, undefined,
+ THIS).
+
+stream_body(MaxChunkSize, ChunkFun, FunState,
+ MaxBodyLength,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
Expect = case get_header_value("expect", THIS) of
- undefined ->
- undefined;
- Value when is_list(Value) ->
- string:to_lower(Value)
- end,
+ undefined -> undefined;
+ Value when is_list(Value) -> string:to_lower(Value)
+ end,
case Expect of
- "100-continue" ->
- _ = start_raw_response({100, gb_trees:empty()}, THIS),
- ok;
- _Else ->
- ok
+ "100-continue" ->
+ _ = start_raw_response({100, gb_trees:empty()}, THIS),
+ ok;
+ _Else -> ok
end,
case body_length(THIS) 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, THIS);
- 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(MaxChunkSize,Length, ChunkFun, FunState, THIS)
- end
+ 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,
+ THIS);
+ 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(MaxChunkSize, Length, ChunkFun,
+ FunState, THIS)
+ end
end.
-
%% @spec start_response({integer(), ioheaders()}, request()) -> 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}, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+start_response({Code, ResponseHeaders},
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
start_raw_response({Code, ResponseHeaders}, THIS).
%% @spec start_raw_response({integer(), headers()}, request()) -> response()
%% @doc Start the HTTP response by sending the Code HTTP response and
%% ResponseHeaders.
-start_raw_response({Code, ResponseHeaders}, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
- {Header, Response} = format_response_header({Code, ResponseHeaders}, THIS),
+start_raw_response({Code, ResponseHeaders},
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
+ {Header, Response} = format_response_header({Code,
+ ResponseHeaders},
+ THIS),
send(Header, THIS),
Response.
-
%% @spec start_response_length({integer(), ioheaders(), integer()}, request()) -> 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},
- {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
HResponse = mochiweb_headers:make(ResponseHeaders),
- HResponse1 = mochiweb_headers:enter("Content-Length", Length, HResponse),
+ HResponse1 = mochiweb_headers:enter("Content-Length",
+ Length, HResponse),
start_response({Code, HResponse1}, THIS).
%% @spec format_response_header({integer(), ioheaders()} | {integer(), ioheaders(), integer()}, request()) -> iolist()
@@ -340,23 +431,37 @@ start_response_length({Code, ResponseHeaders, Length},
%% ResponseHeaders including an optional Content-Length of Length. The server
%% will set header defaults such as Server
%% and Date if not present in ResponseHeaders.
-format_response_header({Code, ResponseHeaders}, {?MODULE, [_Socket, _Opts, _Method, _RawPath, Version, _Headers]}=THIS) ->
+format_response_header({Code, ResponseHeaders},
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, Version,
+ _Headers]} =
+ THIS) ->
HResponse = mochiweb_headers:make(ResponseHeaders),
- HResponse1 = mochiweb_headers:default_from_list(server_headers(), HResponse),
+ HResponse1 =
+ mochiweb_headers:default_from_list(server_headers(),
+ HResponse),
HResponse2 = case should_close(THIS) of
- true ->
- mochiweb_headers:enter("Connection", "close", HResponse1);
- false ->
- HResponse1
- end,
- End = [[mochiweb_util:make_io(K), <<": ">>, V, <<"\r\n">>]
- || {K, V} <- mochiweb_headers:to_list(HResponse2)],
- Response = mochiweb:new_response({THIS, Code, HResponse2}),
- {[make_version(Version), make_code(Code), <<"\r\n">> | [End, <<"\r\n">>]], Response};
+ true ->
+ mochiweb_headers:enter("Connection", "close",
+ HResponse1);
+ false -> HResponse1
+ end,
+ End = [[mochiweb_util:make_io(K), <<": ">>, V,
+ <<"\r\n">>]
+ || {K, V} <- mochiweb_headers:to_list(HResponse2)],
+ Response = mochiweb:new_response({THIS, Code,
+ HResponse2}),
+ {[make_version(Version), make_code(Code), <<"\r\n">>,
+ End, <<"\r\n">>],
+ Response};
format_response_header({Code, ResponseHeaders, Length},
- {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
HResponse = mochiweb_headers:make(ResponseHeaders),
- HResponse1 = mochiweb_headers:enter("Content-Length", Length, HResponse),
+ HResponse1 = mochiweb_headers:enter("Content-Length",
+ Length, HResponse),
format_response_header({Code, HResponse1}, THIS).
%% @spec respond({integer(), ioheaders(), iodata() | chunked | {file, IoDevice}}, request()) -> response()
@@ -365,293 +470,391 @@ format_response_header({Code, ResponseHeaders, Length},
%% will be set by the Body length, and the server will insert header
%% defaults.
respond({Code, ResponseHeaders, {file, IoDevice}},
- {?MODULE, [_Socket, _Opts, Method, _RawPath, _Version, _Headers]}=THIS) ->
+ {?MODULE,
+ [_Socket, _Opts, Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
Length = mochiweb_io:iodevice_size(IoDevice),
- Response = start_response_length({Code, ResponseHeaders, Length}, THIS),
+ Response = start_response_length({Code, ResponseHeaders,
+ Length},
+ THIS),
case Method of
- 'HEAD' ->
- ok;
- _ ->
- mochiweb_io:iodevice_stream(
- fun (Body) -> send(Body, THIS) end,
- IoDevice)
+ 'HEAD' -> ok;
+ _ ->
+ mochiweb_io:iodevice_stream(fun (Body) ->
+ send(Body, THIS)
+ end,
+ IoDevice)
end,
Response;
-respond({Code, ResponseHeaders, chunked}, {?MODULE, [_Socket, _Opts, Method, _RawPath, Version, _Headers]}=THIS) ->
+respond({Code, ResponseHeaders, chunked},
+ {?MODULE,
+ [_Socket, _Opts, Method, _RawPath, Version, _Headers]} =
+ THIS) ->
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,
+ '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}, THIS);
-respond({Code, ResponseHeaders, Body}, {?MODULE, [_Socket, _Opts, Method, _RawPath, _Version, _Headers]}=THIS) ->
- {Header, Response} = format_response_header({Code, ResponseHeaders, iolist_size(Body)}, THIS),
+respond({Code, ResponseHeaders, Body},
+ {?MODULE,
+ [_Socket, _Opts, Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
+ {Header, Response} = format_response_header({Code,
+ ResponseHeaders,
+ iolist_size(Body)},
+ THIS),
case Method of
- 'HEAD' -> send(Header, THIS);
- _ -> send([Header, Body], THIS)
+ 'HEAD' -> send(Header, THIS);
+ _ -> send([Header, Body], THIS)
end,
Response.
%% @spec not_found(request()) -> response()
%% @doc Alias for <code>not_found([])</code>.
-not_found({?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+not_found({?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
not_found([], THIS).
%% @spec not_found(ExtraHeaders, request()) -> response()
%% @doc Alias for <code>respond({404, [{"Content-Type", "text/plain"}
%% | ExtraHeaders], <<"Not found.">>})</code>.
-not_found(ExtraHeaders, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
- respond({404, [{"Content-Type", "text/plain"} | ExtraHeaders],
- <<"Not found.">>}, THIS).
+not_found(ExtraHeaders,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
+ respond({404,
+ [{"Content-Type", "text/plain"} | ExtraHeaders],
+ <<"Not found.">>},
+ THIS).
%% @spec ok({value(), iodata()} | {value(), ioheaders(), iodata() | {file, IoDevice}}, request()) ->
%% response()
%% @doc respond({200, [{"Content-Type", ContentType} | Headers], Body}).
-ok({ContentType, Body}, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ok({ContentType, Body},
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
ok({ContentType, [], Body}, THIS);
-ok({ContentType, ResponseHeaders, Body}, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ok({ContentType, ResponseHeaders, Body},
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
HResponse = mochiweb_headers:make(ResponseHeaders),
case get(range, THIS) 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}, THIS);
- 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}, THIS);
- 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}, THIS)
- end
+ 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}, THIS);
+ 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}, THIS);
+ 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}, THIS)
+ end
end.
%% @spec should_close(request()) -> bool()
%% @doc Return true if the connection must be closed. If false, using
%% Keep-Alive should be safe.
-should_close({?MODULE, [_Socket, _Opts, _Method, _RawPath, Version, _Headers]}=THIS) ->
- ForceClose = erlang:get(?SAVE_FORCE_CLOSE) =/= undefined,
+should_close({?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, Version,
+ _Headers]} =
+ THIS) ->
+ ForceClose = erlang:get(?SAVE_FORCE_CLOSE) =/=
+ undefined,
DidNotRecv = erlang:get(?SAVE_RECV) =:= undefined,
- ForceClose orelse Version < {1, 0}
- %% Connection: close
- orelse is_close(get_header_value("connection", THIS))
- %% HTTP 1.0 requires Connection: Keep-Alive
- orelse (Version =:= {1, 0}
- andalso get_header_value("connection", THIS) =/= "Keep-Alive")
- %% unread data left on the socket, can't safely continue
- orelse (DidNotRecv
- andalso get_combined_header_value("content-length", THIS) =/= undefined
- andalso list_to_integer(get_combined_header_value("content-length", THIS)) > 0)
- orelse (DidNotRecv
- andalso get_header_value("transfer-encoding", THIS) =:= "chunked").
-
-is_close("close") ->
- true;
-is_close(S=[_C, _L, _O, _S, _E]) ->
+ ForceClose orelse
+ Version < {1, 0}
+ %% Connection: close
+ orelse
+ is_close(get_header_value("connection", THIS))
+ %% HTTP 1.0 requires Connection: Keep-Alive
+ orelse
+ Version =:= {1, 0} andalso
+ get_header_value("connection", THIS) =/= "Keep-Alive"
+ %% unread data left on the socket, can't safely continue
+ orelse
+ DidNotRecv andalso
+ get_combined_header_value("content-length", THIS) =/=
+ undefined
+ andalso
+ list_to_integer(get_combined_header_value("content-length",
+ THIS))
+ > 0
+ orelse
+ DidNotRecv andalso
+ get_header_value("transfer-encoding", THIS) =:=
+ "chunked".
+
+is_close("close") -> true;
+is_close(S = [_C, _L, _O, _S, _E]) ->
string:to_lower(S) =:= "close";
-is_close(_) ->
- false.
+is_close(_) -> false.
%% @spec cleanup(request()) -> ok
%% @doc Clean up any junk in the process dictionary, required before continuing
%% a Keep-Alive request.
-cleanup({?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
- L = [?SAVE_QS, ?SAVE_PATH, ?SAVE_RECV, ?SAVE_BODY, ?SAVE_BODY_LENGTH,
- ?SAVE_POST, ?SAVE_COOKIE, ?SAVE_FORCE_CLOSE],
- lists:foreach(fun(K) ->
- erase(K)
- end, L),
+cleanup({?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]}) ->
+ L = [?SAVE_QS, ?SAVE_PATH, ?SAVE_RECV, ?SAVE_BODY,
+ ?SAVE_BODY_LENGTH, ?SAVE_POST, ?SAVE_COOKIE,
+ ?SAVE_FORCE_CLOSE],
+ lists:foreach(fun (K) -> erase(K) end, L),
ok.
%% @spec parse_qs(request()) -> [{Key::string(), Value::string()}]
%% @doc Parse the query string of the URL.
-parse_qs({?MODULE, [_Socket, _Opts, _Method, RawPath, _Version, _Headers]}) ->
+parse_qs({?MODULE,
+ [_Socket, _Opts, _Method, RawPath, _Version,
+ _Headers]}) ->
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
+ 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, request()) -> string() | undefined
%% @doc Get the value of the given cookie.
-get_cookie_value(Key, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+get_cookie_value(Key,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
proplists:get_value(Key, parse_cookie(THIS)).
%% @spec parse_cookie(request()) -> [{Key::string(), Value::string()}]
%% @doc Parse the cookie header.
-parse_cookie({?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+parse_cookie({?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
case erlang:get(?SAVE_COOKIE) of
- undefined ->
- Cookies = case get_header_value("cookie", THIS) of
- undefined ->
- [];
- Value ->
- mochiweb_cookies:parse_cookie(Value)
- end,
- put(?SAVE_COOKIE, Cookies),
- Cookies;
- Cached ->
- Cached
+ undefined ->
+ Cookies = case get_header_value("cookie", THIS) of
+ undefined -> [];
+ Value -> mochiweb_cookies:parse_cookie(Value)
+ end,
+ put(?SAVE_COOKIE, Cookies),
+ Cookies;
+ Cached -> Cached
end.
%% @spec parse_post(request()) -> [{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({?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+parse_post({?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
case erlang:get(?SAVE_POST) of
- undefined ->
- Parsed = case recv_body(THIS) of
- undefined ->
- [];
- Binary ->
- case get_primary_header_value("content-type",THIS) of
- "application/x-www-form-urlencoded" ++ _ ->
- mochiweb_util:parse_qs(Binary);
- _ ->
- []
- end
- end,
- put(?SAVE_POST, Parsed),
- Parsed;
- Cached ->
- Cached
+ undefined ->
+ Parsed = case recv_body(THIS) of
+ undefined -> [];
+ Binary ->
+ case get_primary_header_value("content-type", THIS) 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(), request()) -> term()
%% @doc The function is called for each chunk.
%% Used internally by read_chunked_body.
stream_chunked_body(MaxChunkSize, Fun, FunState,
- {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
case read_chunk_length(THIS) of
- 0 ->
- Fun({0, read_chunk(0, THIS)}, FunState);
- Length when Length > MaxChunkSize ->
- NewState = read_sub_chunks(Length, MaxChunkSize, Fun, FunState, THIS),
- stream_chunked_body(MaxChunkSize, Fun, NewState, THIS);
- Length ->
- NewState = Fun({Length, read_chunk(Length, THIS)}, FunState),
- stream_chunked_body(MaxChunkSize, Fun, NewState, THIS)
+ 0 -> Fun({0, read_chunk(0, THIS)}, FunState);
+ Length when Length > MaxChunkSize ->
+ NewState = read_sub_chunks(Length, MaxChunkSize, Fun,
+ FunState, THIS),
+ stream_chunked_body(MaxChunkSize, Fun, NewState, THIS);
+ Length ->
+ NewState = Fun({Length, read_chunk(Length, THIS)},
+ FunState),
+ stream_chunked_body(MaxChunkSize, Fun, NewState, THIS)
end.
-stream_unchunked_body(_MaxChunkSize, 0, Fun, FunState, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
+stream_unchunked_body(_MaxChunkSize, 0, Fun, FunState,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]}) ->
Fun({0, <<>>}, FunState);
-stream_unchunked_body(MaxChunkSize, Length, Fun, FunState,
- {?MODULE, [_Socket, Opts, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > 0 ->
- RecBuf = case mochilists:get_value(recbuf, Opts, ?RECBUF_SIZE) of
- undefined -> %os controlled buffer size
- MaxChunkSize;
- Val ->
- Val
- end,
- PktSize=min(Length,RecBuf),
+stream_unchunked_body(MaxChunkSize, Length, Fun,
+ FunState,
+ {?MODULE,
+ [_Socket, Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS)
+ when Length > 0 ->
+ RecBuf = case mochilists:get_value(recbuf, Opts,
+ ?RECBUF_SIZE)
+ of
+ undefined -> %os controlled buffer size
+ MaxChunkSize;
+ Val -> Val
+ end,
+ PktSize = min(Length, RecBuf),
Bin = recv(PktSize, THIS),
NewState = Fun({PktSize, Bin}, FunState),
- stream_unchunked_body(MaxChunkSize, Length - PktSize, Fun, NewState, THIS).
+ stream_unchunked_body(MaxChunkSize, Length - PktSize,
+ Fun, NewState, THIS).
%% @spec read_chunk_length(request()) -> integer()
%% @doc Read the length of the next HTTP chunk.
-read_chunk_length({?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, line}])),
+read_chunk_length({?MODULE,
+ [Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]}) ->
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{packet,
+ line}])),
case mochiweb_socket:recv(Socket, 0, ?IDLE_TIMEOUT) of
- {ok, Header} ->
- ok = mochiweb_socket:exit_if_closed(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)
+ {ok, Header} ->
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{packet,
+ raw}])),
+ Splitter = fun (C) ->
+ C =/= $\r andalso C =/= $\n andalso C =/= $\n
+ end,
+ {Hex, _Rest} = lists:splitwith(Splitter,
+ binary_to_list(Header)),
+ mochihex:to_int(Hex);
+ _ -> exit(normal)
end.
%% @spec read_chunk(integer(), request()) -> 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, {?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, line}])),
+read_chunk(0,
+ {?MODULE,
+ [Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]}) ->
+ ok =
+ mochiweb_socket:exit_if_closed(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,
+ 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, []),
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, raw}])),
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{packet,
+ raw}])),
put(?SAVE_RECV, true),
Footers;
-read_chunk(Length, {?MODULE, [Socket, _Opts, _Method, _RawPath, _Version, _Headers]}) ->
- case mochiweb_socket:recv(Socket, 2 + Length, ?IDLE_TIMEOUT) of
- {ok, <<Chunk:Length/binary, "\r\n">>} ->
- Chunk;
- _ ->
- exit(normal)
+read_chunk(Length,
+ {?MODULE,
+ [Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]}) ->
+ 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,
- {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) when Length > MaxChunkSize ->
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS)
+ when Length > MaxChunkSize ->
Bin = recv(MaxChunkSize, THIS),
NewState = Fun({size(Bin), Bin}, FunState),
- read_sub_chunks(Length - MaxChunkSize, MaxChunkSize, Fun, NewState, THIS);
-
+ read_sub_chunks(Length - MaxChunkSize, MaxChunkSize,
+ Fun, NewState, THIS);
read_sub_chunks(Length, _MaxChunkSize, Fun, FunState,
- {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
Fun({Length, read_chunk(Length, THIS)}, FunState).
%% @spec serve_file(Path, DocRoot, request()) -> Response
%% @doc Serve a file relative to DocRoot.
-serve_file(Path, DocRoot, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+serve_file(Path, DocRoot,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
serve_file(Path, DocRoot, [], THIS).
%% @spec serve_file(Path, DocRoot, ExtraHeaders, request()) -> Response
%% @doc Serve a file relative to DocRoot.
-serve_file(Path, DocRoot, ExtraHeaders, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+serve_file(Path, DocRoot, ExtraHeaders,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
case mochiweb_util:safe_relative_path(Path) of
- undefined ->
- not_found(ExtraHeaders, THIS);
- RelPath ->
- FullPath = filename:join([DocRoot, RelPath]),
- case filelib:is_dir(FullPath) of
- true ->
- maybe_redirect(RelPath, FullPath, ExtraHeaders, THIS);
- false ->
- maybe_serve_file(FullPath, ExtraHeaders, THIS)
- end
+ undefined -> not_found(ExtraHeaders, THIS);
+ RelPath ->
+ FullPath = filename:join([DocRoot, RelPath]),
+ case filelib:is_dir(FullPath) of
+ true ->
+ maybe_redirect(RelPath, FullPath, ExtraHeaders, THIS);
+ false -> maybe_serve_file(FullPath, ExtraHeaders, THIS)
+ end
end.
%% Internal API
@@ -660,104 +863,111 @@ serve_file(Path, DocRoot, ExtraHeaders, {?MODULE, [_Socket, _Opts, _Method, _Raw
directory_index(FullPath) ->
filename:join([FullPath, "index.html"]).
-maybe_redirect([], FullPath, ExtraHeaders, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
- maybe_serve_file(directory_index(FullPath), ExtraHeaders, THIS);
-
+maybe_redirect([], FullPath, ExtraHeaders,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
+ maybe_serve_file(directory_index(FullPath),
+ ExtraHeaders, THIS);
maybe_redirect(RelPath, FullPath, ExtraHeaders,
- {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, Headers]}=THIS) ->
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ Headers]} =
+ THIS) ->
case string:right(RelPath, 1) of
- "/" ->
- maybe_serve_file(directory_index(FullPath), ExtraHeaders, THIS);
- _ ->
- 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}, THIS)
+ "/" ->
+ maybe_serve_file(directory_index(FullPath),
+ ExtraHeaders, THIS);
+ _ ->
+ 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>Mov"
+ "ed 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}, THIS)
end.
-maybe_serve_file(File, ExtraHeaders, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+maybe_serve_file(File, ExtraHeaders,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
case file:read_file_info(File) of
- {ok, FileInfo} ->
- LastModified = httpd_util:rfc1123_date(FileInfo#file_info.mtime),
- case get_header_value("if-modified-since", THIS) of
- LastModified ->
- respond({304, ExtraHeaders, ""}, THIS);
- _ ->
- case file:open(File, [raw, binary]) of
- {ok, IoDevice} ->
- ContentType = mochiweb_util:guess_mime(File),
- Res = ok({ContentType,
- [{"last-modified", LastModified}
- | ExtraHeaders],
- {file, IoDevice}}, THIS),
- ok = file:close(IoDevice),
- Res;
- _ ->
- not_found(ExtraHeaders, THIS)
- end
- end;
- {error, _} ->
- not_found(ExtraHeaders, THIS)
+ {ok, FileInfo} ->
+ LastModified =
+ httpd_util:rfc1123_date(FileInfo#file_info.mtime),
+ case get_header_value("if-modified-since", THIS) of
+ LastModified -> respond({304, ExtraHeaders, ""}, THIS);
+ _ ->
+ case file:open(File, [raw, binary]) of
+ {ok, IoDevice} ->
+ ContentType = mochiweb_util:guess_mime(File),
+ Res = ok({ContentType,
+ [{"last-modified", LastModified}
+ | ExtraHeaders],
+ {file, IoDevice}},
+ THIS),
+ ok = file:close(IoDevice),
+ Res;
+ _ -> not_found(ExtraHeaders, THIS)
+ end
+ end;
+ {error, _} -> not_found(ExtraHeaders, THIS)
end.
server_headers() ->
- [{"Server", "MochiWeb/1.0 (" ++ ?QUIP ++ ")"},
+ [{"Server", "MochiWeb/1.0 (" ++ (?QUIP) ++ ")"},
{"Date", mochiweb_clock:rfc1123()}].
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.
+ [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 ">>.
+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,
+ 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) ->
- case Length of
- 0 ->
- {Skip, Skip, <<>>};
- _ ->
- {Skip, Skip + Length - 1, PartialBody}
- end
- end,
- LocNums, Data),
+ Bodies = lists:zipwith(fun ({Skip, Length},
+ PartialBody) ->
+ case Length of
+ 0 -> {Skip, Skip, <<>>};
+ _ -> {Skip, Skip + Length - 1, PartialBody}
+ end
+ 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,
+ 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()], request()) -> [encoding()] | bad_accept_encoding_value
@@ -784,20 +994,23 @@ range_parts(Body0, Ranges) ->
%% accepted_encodings(["gzip", "deflate", "identity"]) ->
%% ["deflate", "gzip", "identity"]
%%
-accepted_encodings(SupportedEncodings, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
- AcceptEncodingHeader = case get_header_value("Accept-Encoding", THIS) 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"
- )
+accepted_encodings(SupportedEncodings,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
+ AcceptEncodingHeader = case
+ get_header_value("Accept-Encoding", THIS)
+ 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(), request()) -> boolean() | bad_accept_header
@@ -822,27 +1035,28 @@ accepted_encodings(SupportedEncodings, {?MODULE, [_Socket, _Opts, _Method, _RawP
%% 5) For an "Accept" header with value "text/*; q=0.0, */*":
%% accepts_content_type("text/plain") -> false
%%
-accepts_content_type(ContentType1, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
- ContentType = re:replace(ContentType1, "\\s", "", [global, {return, list}]),
+accepts_content_type(ContentType1,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
+ ContentType = re:replace(ContentType1, "\\s", "",
+ [global, {return, list}]),
AcceptHeader = accept_header(THIS),
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))
+ 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.
%% @spec accepted_content_types([string() | binary()], request()) -> [string()] | bad_accept_header
@@ -871,57 +1085,67 @@ accepts_content_type(ContentType1, {?MODULE, [_Socket, _Opts, _Method, _RawPath,
%% accepts_content_types(["application/json", "text/html"]) ->
%% ["text/html", "application/json"]
%%
-accepted_content_types(Types1, {?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
- Types = lists:map(
- fun(T) -> re:replace(T, "\\s", "", [global, {return, list}]) end,
- Types1),
+accepted_content_types(Types1,
+ {?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
+ Types = [accepted_content_types_1(V1) || V1 <- Types1],
AcceptHeader = accept_header(THIS),
case mochiweb_util:parse_qvalues(AcceptHeader) of
- invalid_qvalue_string ->
- bad_accept_header;
- QList ->
- TypesQ = lists:foldr(
- fun(T, Acc) ->
- case proplists:get_value(T, QList) of
- undefined ->
- [MainType, _SubType] = string:tokens(T, "/"),
- case proplists:get_value(MainType ++ "/*", QList) of
- undefined ->
- case proplists:get_value("*/*", QList) of
- Q when is_float(Q), Q > 0.0 ->
- [{Q, T} | Acc];
- _ ->
- Acc
- end;
- Q when Q > 0.0 ->
- [{Q, T} | Acc];
- _ ->
- Acc
- end;
- Q when Q > 0.0 ->
- [{Q, T} | Acc];
- _ ->
- Acc
- end
- end,
- [], Types),
- % Note: Stable sort. If 2 types have the same Q value we leave them in the
- % same order as in the input list.
- SortFun = fun({Q1, _}, {Q2, _}) -> Q1 >= Q2 end,
- [Type || {_Q, Type} <- lists:sort(SortFun, TypesQ)]
+ invalid_qvalue_string -> bad_accept_header;
+ QList ->
+ TypesQ = lists:foldr(fun (T, Acc) ->
+ case proplists:get_value(T, QList) of
+ undefined ->
+ [MainType, _SubType] =
+ string:tokens(T, "/"),
+ case proplists:get_value(MainType
+ ++ "/*",
+ QList)
+ of
+ undefined ->
+ case
+ proplists:get_value("*/*",
+ QList)
+ of
+ Q
+ when is_float(Q),
+ Q > 0.0 ->
+ [{Q, T} | Acc];
+ _ -> Acc
+ end;
+ Q when Q > 0.0 -> [{Q, T} | Acc];
+ _ -> Acc
+ end;
+ Q when Q > 0.0 -> [{Q, T} | Acc];
+ _ -> Acc
+ end
+ end,
+ [], Types),
+ % Note: Stable sort. If 2 types have the same Q value we leave them in the
+ % same order as in the input list.
+ SortFun = fun ({Q1, _}, {Q2, _}) -> Q1 >= Q2 end,
+ [Type || {_Q, Type} <- lists:sort(SortFun, TypesQ)]
end.
-accept_header({?MODULE, [_Socket, _Opts, _Method, _RawPath, _Version, _Headers]}=THIS) ->
+accepted_content_types_1(T) ->
+ re:replace(T, "\\s", "", [global, {return, list}]).
+
+accept_header({?MODULE,
+ [_Socket, _Opts, _Method, _RawPath, _Version,
+ _Headers]} =
+ THIS) ->
case get_header_value("Accept", THIS) of
- undefined ->
- "*/*";
- Value ->
- Value
+ undefined -> "*/*";
+ Value -> Value
end.
%%
%% Tests
%%
-ifdef(TEST).
+
-include_lib("eunit/include/eunit.hrl").
+
-endif.
diff --git a/src/mochiweb_response.erl b/src/mochiweb_response.erl
index 81325b5..2f5b544 100644
--- a/src/mochiweb_response.erl
+++ b/src/mochiweb_response.erl
@@ -22,11 +22,13 @@
%% @doc Response abstraction.
-module(mochiweb_response).
+
-author('bob@mochimedia.com').
-define(QUIP, "Any of you quaids got a smint?").
--export([new/3, get_header_value/2, get/2, dump/1]).
+-export([dump/1, get/2, get_header_value/2, new/3]).
+
-export([send/2, write_chunk/2]).
%% @type response(). A mochiweb_response parameterized module instance.
@@ -39,7 +41,8 @@ new(Request, Code, Headers) ->
%% @spec get_header_value(string() | atom() | binary(), response()) ->
%% string() | undefined
%% @doc Get the value of the given response header.
-get_header_value(K, {?MODULE, [_Request, _Code, Headers]}) ->
+get_header_value(K,
+ {?MODULE, [_Request, _Code, Headers]}) ->
mochiweb_headers:get_value(K, Headers).
%% @spec get(request | code | headers, response()) -> term()
@@ -55,36 +58,38 @@ get(headers, {?MODULE, [_Request, _Code, Headers]}) ->
%% @doc Dump the internal representation to a "human readable" set of terms
%% for debugging/inspection purposes.
dump({?MODULE, [{ReqM, _} = Request, Code, Headers]}) ->
- [{request, ReqM:dump(Request)},
- {code, Code},
+ [{request, ReqM:dump(Request)}, {code, Code},
{headers, mochiweb_headers:to_list(Headers)}].
%% @spec send(iodata(), response()) -> ok
%% @doc Send data over the socket if the method is not HEAD.
-send(Data, {?MODULE, [{ReqM, _} = Request, _Code, _Headers]}) ->
+send(Data,
+ {?MODULE, [{ReqM, _} = Request, _Code, _Headers]}) ->
case ReqM:get(method, Request) of
- 'HEAD' ->
- ok;
- _ ->
- ReqM:send(Data, Request)
+ 'HEAD' -> ok;
+ _ -> ReqM:send(Data, Request)
end.
%% @spec write_chunk(iodata(), response()) -> 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, {?MODULE, [{ReqM, _} = Request, _Code, _Headers]}=THIS) ->
+write_chunk(Data,
+ {?MODULE, [{ReqM, _} = Request, _Code, _Headers]} =
+ THIS) ->
case ReqM:get(version, Request) of
- Version when Version >= {1, 1} ->
- Length = iolist_size(Data),
- send([io_lib:format("~.16b\r\n", [Length]), Data, <<"\r\n">>], THIS);
- _ ->
- send(Data, THIS)
+ Version when Version >= {1, 1} ->
+ Length = iolist_size(Data),
+ send([io_lib:format("~.16b\r\n", [Length]), Data,
+ <<"\r\n">>],
+ THIS);
+ _ -> send(Data, THIS)
end.
-
%%
%% Tests
%%
-ifdef(TEST).
+
-include_lib("eunit/include/eunit.hrl").
+
-endif.
diff --git a/src/mochiweb_websocket.erl b/src/mochiweb_websocket.erl
index c162980..c2e2ab6 100644
--- a/src/mochiweb_websocket.erl
+++ b/src/mochiweb_websocket.erl
@@ -1,4 +1,5 @@
-module(mochiweb_websocket).
+
-author('lukasz.lalik@zadane.pl').
%% The MIT License (MIT)
@@ -25,43 +26,45 @@
%% @doc Websockets module for Mochiweb. Based on Misultin websockets module.
--export([loop/5, upgrade_connection/2, request/5]).
+-export([loop/5, request/5, upgrade_connection/2]).
+
-export([send/3]).
+
-ifdef(TEST).
--export([make_handshake/1, hixie_handshake/7, parse_hybi_frames/3, parse_hixie_frames/2]).
+
+-export([hixie_handshake/7, make_handshake/1,
+ parse_hixie_frames/2, parse_hybi_frames/3]).
+
-endif.
loop(Socket, Body, State, WsVersion, ReplyChannel) ->
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, 0}, {active, once}])),
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{packet, 0},
+ {active,
+ once}])),
proc_lib:hibernate(?MODULE, request,
- [Socket, Body, State, WsVersion, ReplyChannel]).
+ [Socket, Body, State, WsVersion, ReplyChannel]).
request(Socket, Body, State, WsVersion, ReplyChannel) ->
receive
- {tcp_closed, _} ->
- mochiweb_socket:close(Socket),
- exit(normal);
- {ssl_closed, _} ->
- mochiweb_socket:close(Socket),
- exit(normal);
- {tcp_error, _, _} ->
- mochiweb_socket:close(Socket),
- exit(normal);
- {Proto, _, WsFrames} when Proto =:= tcp orelse Proto =:= ssl ->
- case parse_frames(WsVersion, WsFrames, Socket) of
- close ->
- mochiweb_socket:close(Socket),
- exit(normal);
- error ->
- mochiweb_socket:close(Socket),
- exit(normal);
- Payload ->
- NewState = call_body(Body, Payload, State, ReplyChannel),
- loop(Socket, Body, NewState, WsVersion, ReplyChannel)
- end;
- _ ->
- mochiweb_socket:close(Socket),
- exit(normal)
+ {tcp_closed, _} ->
+ mochiweb_socket:close(Socket), exit(normal);
+ {ssl_closed, _} ->
+ mochiweb_socket:close(Socket), exit(normal);
+ {tcp_error, _, _} ->
+ mochiweb_socket:close(Socket), exit(normal);
+ {Proto, _, WsFrames}
+ when Proto =:= tcp orelse Proto =:= ssl ->
+ case parse_frames(WsVersion, WsFrames, Socket) of
+ close -> mochiweb_socket:close(Socket), exit(normal);
+ error -> mochiweb_socket:close(Socket), exit(normal);
+ Payload ->
+ NewState = call_body(Body, Payload, State,
+ ReplyChannel),
+ loop(Socket, Body, NewState, WsVersion, ReplyChannel)
+ end;
+ _ -> mochiweb_socket:close(Socket), exit(normal)
end.
call_body({M, F, A}, Payload, State, ReplyChannel) ->
@@ -72,63 +75,68 @@ call_body(Body, Payload, State, ReplyChannel) ->
Body(Payload, State, ReplyChannel).
send(Socket, Payload, hybi) ->
- Prefix = <<1:1, 0:3, 1:4, (payload_length(iolist_size(Payload)))/binary>>,
+ Prefix = <<1:1, 0:3, 1:4,
+ (payload_length(iolist_size(Payload)))/binary>>,
mochiweb_socket:send(Socket, [Prefix, Payload]);
send(Socket, Payload, hixie) ->
mochiweb_socket:send(Socket, [0, Payload, 255]).
upgrade_connection({ReqM, _} = Req, Body) ->
case make_handshake(Req) of
- {Version, Response} ->
- ReqM:respond(Response, Req),
- Socket = ReqM:get(socket, Req),
- ReplyChannel = fun (Payload) ->
- ?MODULE:send(Socket, Payload, Version)
- end,
- Reentry = fun (State) ->
- ?MODULE:loop(Socket, Body, State, Version, ReplyChannel)
- end,
- {Reentry, ReplyChannel};
- _ ->
- mochiweb_socket:close(ReqM:get(socket, Req)),
- exit(normal)
+ {Version, Response} ->
+ ReqM:respond(Response, Req),
+ Socket = ReqM:get(socket, Req),
+ ReplyChannel = fun (Payload) ->
+ (?MODULE):send(Socket, Payload, Version)
+ end,
+ Reentry = fun (State) ->
+ (?MODULE):loop(Socket, Body, State, Version,
+ ReplyChannel)
+ end,
+ {Reentry, ReplyChannel};
+ _ ->
+ mochiweb_socket:close(ReqM:get(socket, Req)),
+ exit(normal)
end.
make_handshake({ReqM, _} = Req) ->
- SecKey = ReqM:get_header_value("sec-websocket-key", Req),
- Sec1Key = ReqM:get_header_value("Sec-WebSocket-Key1", Req),
- Sec2Key = ReqM:get_header_value("Sec-WebSocket-Key2", Req),
+ SecKey = ReqM:get_header_value("sec-websocket-key",
+ Req),
+ Sec1Key = ReqM:get_header_value("Sec-WebSocket-Key1",
+ Req),
+ Sec2Key = ReqM:get_header_value("Sec-WebSocket-Key2",
+ Req),
Origin = ReqM:get_header_value(origin, Req),
- if SecKey =/= undefined ->
- hybi_handshake(SecKey);
+ if SecKey =/= undefined -> hybi_handshake(SecKey);
Sec1Key =/= undefined andalso Sec2Key =/= undefined ->
- Host = ReqM:get_header_value("Host", Req),
- Path = ReqM:get(path, Req),
- Body = ReqM:recv(8, Req),
- Scheme = scheme(Req),
- hixie_handshake(Scheme, Host, Path, Sec1Key, Sec2Key, Body, Origin);
- true ->
- error
+ Host = ReqM:get_header_value("Host", Req),
+ Path = ReqM:get(path, Req),
+ Body = ReqM:recv(8, Req),
+ Scheme = scheme(Req),
+ hixie_handshake(Scheme, Host, Path, Sec1Key, Sec2Key,
+ Body, Origin);
+ true -> error
end.
hybi_handshake(SecKey) ->
BinKey = list_to_binary(SecKey),
- Bin = <<BinKey/binary, "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>,
+ Bin = <<BinKey/binary,
+ "258EAFA5-E914-47DA-95CA-C5AB0DC85B11">>,
Challenge = base64:encode(crypto:hash(sha, Bin)),
- Response = {101, [{"Connection", "Upgrade"},
- {"Upgrade", "websocket"},
- {"Sec-Websocket-Accept", Challenge}], ""},
+ Response = {101,
+ [{"Connection", "Upgrade"}, {"Upgrade", "websocket"},
+ {"Sec-Websocket-Accept", Challenge}],
+ ""},
{hybi, Response}.
scheme(Req) ->
case mochiweb_request:get(scheme, Req) of
- http ->
- "ws://";
- https ->
- "wss://"
+ http -> "ws://";
+ https -> "wss://"
end.
-hixie_handshake(Scheme, Host, Path, Key1, Key2, Body, Origin) ->
+hixie_handshake(Scheme, Host, Path, Key1, Key2, Body,
+ Origin) ->
Ikey1 = [D || D <- Key1, $0 =< D, D =< $9],
Ikey2 = [D || D <- Key2, $0 =< D, D =< $9],
Blank1 = length([D || D <- Key1, D =:= 32]),
@@ -136,116 +144,93 @@ hixie_handshake(Scheme, Host, Path, Key1, Key2, Body, Origin) ->
Part1 = erlang:list_to_integer(Ikey1) div Blank1,
Part2 = erlang:list_to_integer(Ikey2) div Blank2,
Ckey = <<Part1:4/big-unsigned-integer-unit:8,
- Part2:4/big-unsigned-integer-unit:8,
- Body/binary>>,
+ Part2:4/big-unsigned-integer-unit:8, Body/binary>>,
Challenge = erlang:md5(Ckey),
Location = lists:concat([Scheme, Host, Path]),
- Response = {101, [{"Upgrade", "WebSocket"},
- {"Connection", "Upgrade"},
- {"Sec-WebSocket-Origin", Origin},
- {"Sec-WebSocket-Location", Location}],
- Challenge},
+ Response = {101,
+ [{"Upgrade", "WebSocket"}, {"Connection", "Upgrade"},
+ {"Sec-WebSocket-Origin", Origin},
+ {"Sec-WebSocket-Location", Location}],
+ Challenge},
{hixie, Response}.
parse_frames(hybi, Frames, Socket) ->
try parse_hybi_frames(Socket, Frames, []) of
- Parsed -> process_frames(Parsed, [])
+ Parsed -> process_frames(Parsed, [])
catch
- _:_ -> error
+ _:_ -> error
end;
parse_frames(hixie, Frames, _Socket) ->
try parse_hixie_frames(Frames, []) of
- Payload -> Payload
+ Payload -> Payload
catch
- _:_ -> error
+ _:_ -> error
end.
%%
%% Websockets internal functions for RFC6455 and hybi draft
%%
-process_frames([], Acc) ->
- lists:reverse(Acc);
+process_frames([], Acc) -> lists:reverse(Acc);
process_frames([{Opcode, Payload} | Rest], Acc) ->
case Opcode of
- 8 -> close;
- _ ->
- process_frames(Rest, [Payload | Acc])
+ 8 -> close;
+ _ -> process_frames(Rest, [Payload | Acc])
end.
-parse_hybi_frames(_, <<>>, Acc) ->
- lists:reverse(Acc);
-parse_hybi_frames(S, <<_Fin:1,
- _Rsv:3,
- Opcode:4,
- _Mask:1,
- PayloadLen:7,
- MaskKey:4/binary,
- Payload:PayloadLen/binary-unit:8,
- Rest/binary>>,
- Acc) when PayloadLen < 126 ->
+parse_hybi_frames(_, <<>>, Acc) -> lists:reverse(Acc);
+parse_hybi_frames(S,
+ <<_Fin:1, _Rsv:3, Opcode:4, _Mask:1, PayloadLen:7,
+ MaskKey:4/binary, Payload:PayloadLen/binary-unit:8,
+ Rest/binary>>,
+ Acc)
+ when PayloadLen < 126 ->
Payload2 = hybi_unmask(Payload, MaskKey, <<>>),
parse_hybi_frames(S, Rest, [{Opcode, Payload2} | Acc]);
-parse_hybi_frames(S, <<_Fin:1,
- _Rsv:3,
- Opcode:4,
- _Mask:1,
- 126:7,
- PayloadLen:16,
- MaskKey:4/binary,
- Payload:PayloadLen/binary-unit:8,
- Rest/binary>>,
- Acc) ->
+parse_hybi_frames(S,
+ <<_Fin:1, _Rsv:3, Opcode:4, _Mask:1, 126:7,
+ PayloadLen:16, MaskKey:4/binary,
+ Payload:PayloadLen/binary-unit:8, Rest/binary>>,
+ Acc) ->
Payload2 = hybi_unmask(Payload, MaskKey, <<>>),
parse_hybi_frames(S, Rest, [{Opcode, Payload2} | Acc]);
-parse_hybi_frames(Socket, <<_Fin:1,
- _Rsv:3,
- _Opcode:4,
- _Mask:1,
- 126:7,
- _PayloadLen:16,
- _MaskKey:4/binary,
- _/binary-unit:8>> = PartFrame,
- Acc) ->
- ok = mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket, [{packet, 0}, {active, once}])),
+parse_hybi_frames(Socket,
+ <<_Fin:1, _Rsv:3, _Opcode:4, _Mask:1, 126:7,
+ _PayloadLen:16, _MaskKey:4/binary, _/binary-unit:8>> =
+ PartFrame,
+ Acc) ->
+ ok =
+ mochiweb_socket:exit_if_closed(mochiweb_socket:setopts(Socket,
+ [{packet, 0},
+ {active,
+ once}])),
receive
- {tcp_closed, _} ->
- mochiweb_socket:close(Socket),
- exit(normal);
- {ssl_closed, _} ->
- mochiweb_socket:close(Socket),
- exit(normal);
- {tcp_error, _, _} ->
- mochiweb_socket:close(Socket),
- exit(normal);
- {Proto, _, Continuation} when Proto =:= tcp orelse Proto =:= ssl ->
- parse_hybi_frames(Socket, <<PartFrame/binary, Continuation/binary>>,
- Acc);
- _ ->
- mochiweb_socket:close(Socket),
- exit(normal)
- after
- 5000 ->
- mochiweb_socket:close(Socket),
- exit(normal)
+ {tcp_closed, _} ->
+ mochiweb_socket:close(Socket), exit(normal);
+ {ssl_closed, _} ->
+ mochiweb_socket:close(Socket), exit(normal);
+ {tcp_error, _, _} ->
+ mochiweb_socket:close(Socket), exit(normal);
+ {Proto, _, Continuation}
+ when Proto =:= tcp orelse Proto =:= ssl ->
+ parse_hybi_frames(Socket,
+ <<PartFrame/binary, Continuation/binary>>, Acc);
+ _ -> mochiweb_socket:close(Socket), exit(normal)
+ after 5000 ->
+ mochiweb_socket:close(Socket), exit(normal)
end;
-parse_hybi_frames(S, <<_Fin:1,
- _Rsv:3,
- Opcode:4,
- _Mask:1,
- 127:7,
- 0:1,
- PayloadLen:63,
- MaskKey:4/binary,
- Payload:PayloadLen/binary-unit:8,
- Rest/binary>>,
- Acc) ->
+parse_hybi_frames(S,
+ <<_Fin:1, _Rsv:3, Opcode:4, _Mask:1, 127:7, 0:1,
+ PayloadLen:63, MaskKey:4/binary,
+ Payload:PayloadLen/binary-unit:8, Rest/binary>>,
+ Acc) ->
Payload2 = hybi_unmask(Payload, MaskKey, <<>>),
parse_hybi_frames(S, Rest, [{Opcode, Payload2} | Acc]).
%% Unmasks RFC 6455 message
hybi_unmask(<<O:32, Rest/bits>>, MaskKey, Acc) ->
<<MaskKey2:32>> = MaskKey,
- hybi_unmask(Rest, MaskKey, <<Acc/binary, (O bxor MaskKey2):32>>);
+ hybi_unmask(Rest, MaskKey,
+ <<Acc/binary, (O bxor MaskKey2):32>>);
hybi_unmask(<<O:24>>, MaskKey, Acc) ->
<<MaskKey2:24, _:8>> = MaskKey,
<<Acc/binary, (O bxor MaskKey2):24>>;
@@ -255,27 +240,25 @@ hybi_unmask(<<O:16>>, MaskKey, Acc) ->
hybi_unmask(<<O:8>>, MaskKey, Acc) ->
<<MaskKey2:8, _:24>> = MaskKey,
<<Acc/binary, (O bxor MaskKey2):8>>;
-hybi_unmask(<<>>, _MaskKey, Acc) ->
- Acc.
+hybi_unmask(<<>>, _MaskKey, Acc) -> Acc.
payload_length(N) ->
case N of
- N when N =< 125 -> << N >>;
- N when N =< 16#ffff -> << 126, N:16 >>;
- N when N =< 16#7fffffffffffffff -> << 127, N:64 >>
+ N when N =< 125 -> <<N>>;
+ N when N =< 65535 -> <<126, N:16>>;
+ N when N =< 9223372036854775807 -> <<127, N:64>>
end.
-
%%
%% Websockets internal functions for hixie-76 websocket version
%%
parse_hixie_frames(<<>>, Frames) ->
- lists:reverse(Frames);
+ lists:reverse(Frames);
parse_hixie_frames(<<0, T/binary>>, Frames) ->
- {Frame, Rest} = parse_hixie(T, <<>>),
- parse_hixie_frames(Rest, [Frame | Frames]).
+ {Frame, Rest} = parse_hixie(T, <<>>),
+ parse_hixie_frames(Rest, [Frame | Frames]).
parse_hixie(<<255, Rest/binary>>, Buffer) ->
- {Buffer, Rest};
+ {Buffer, Rest};
parse_hixie(<<H, T/binary>>, Buffer) ->
- parse_hixie(T, <<Buffer/binary, H>>).
+ parse_hixie(T, <<Buffer/binary, H>>).
diff --git a/support/templates/mochiwebapp_skel/src/mochiapp_web.erl b/support/templates/mochiwebapp_skel/src/mochiapp_web.erl
index ecd99c5..0b586b0 100644
--- a/support/templates/mochiwebapp_skel/src/mochiapp_web.erl
+++ b/support/templates/mochiwebapp_skel/src/mochiapp_web.erl
@@ -3,90 +3,69 @@
%% @doc Web server for {{appid}}.
--module({{appid}}_web).
+-module('{{appid}}_web').
+
-author("{{author}}").
--export([start/1, stop/0, loop/2]).
+-export([loop/2, start/1, stop/0]).
%% External API
start(Options) ->
{DocRoot, Options1} = get_option(docroot, Options),
- Loop = fun (Req) ->
- ?MODULE:loop(Req, DocRoot)
- end,
- mochiweb_http:start([{name, ?MODULE}, {loop, Loop} | Options1]).
-
-stop() ->
- mochiweb_http:stop(?MODULE).
+ Loop = fun (Req) -> (?MODULE):loop(Req, DocRoot) end,
+ mochiweb_http:start([{name, ?MODULE}, {loop, Loop}
+ | Options1]).
+stop() -> mochiweb_http:stop(?MODULE).
%% OTP 21 is the first to define OTP_RELEASE and the first to support
%% EEP-0047 direct stack trace capture.
-ifdef(OTP_RELEASE).
--if(?OTP_RELEASE >= 21).
+
+-if((?OTP_RELEASE) >= 21).
+
-define(HAS_DIRECT_STACKTRACE, true).
+
-endif.
+
-endif.
-ifdef(HAS_DIRECT_STACKTRACE).
--define(CAPTURE_EXC_PRE(Type, What, Trace), Type:What:Trace).
+
+- define ( CAPTURE_EXC_PRE ( Type , What , Trace ) , Type : What : Trace ) .
+
+
-define(CAPTURE_EXC_GET(Trace), Trace).
+
-else.
+
-define(CAPTURE_EXC_PRE(Type, What, Trace), Type:What).
--define(CAPTURE_EXC_GET(Trace), erlang:get_stacktrace()).
+
+-define(CAPTURE_EXC_GET(Trace),
+ erlang:get_stacktrace()).
+
-endif.
-loop(Req, DocRoot) ->
- "/" ++ Path = mochiweb_request:get(path, Req),
- try
- case mochiweb_request:get(method, Req) of
- Method when Method =:= 'GET'; Method =:= 'HEAD' ->
- case Path of
- "hello_world" ->
- mochiweb_request:respond(
- {200, [{"Content-Type", "text/plain"}], "Hello world!\n"},
- Req
- );
- _ ->
- mochiweb_request:serve_file(Path, DocRoot, Req)
- end;
- 'POST' ->
- case Path of
- _ ->
- mochiweb_request:not_found(Req)
- end;
- _ ->
- mochiweb_request:respond({501, [], []}, Req)
- end
- catch
- ?CAPTURE_EXC_PRE(Type, What, Trace) ->
- Report = ["web request failed",
- {path, Path},
- {type, Type}, {what, What},
- {trace, ?CAPTURE_EXC_GET(Trace)}],
- error_logger:error_report(Report),
- mochiweb_request:respond(
- {500, [{"Content-Type", "text/plain"}], "request failed, sorry\n"},
- Req
- )
- end.
+loop ( Req , DocRoot ) -> "/" ++ Path = mochiweb_request : get ( path , Req ) , try case mochiweb_request : get ( method , Req ) of Method when Method =:= 'GET' ; Method =:= 'HEAD' -> case Path of "hello_world" -> mochiweb_request : respond ( { 200 , [ { "Content-Type" , "text/plain" } ] , "Hello world!\n" } , Req ) ; _ -> mochiweb_request : serve_file ( Path , DocRoot , Req ) end ; 'POST' -> case Path of _ -> mochiweb_request : not_found ( Req ) end ; _ -> mochiweb_request : respond ( { [...]
+
%% Internal API
get_option(Option, Options) ->
- {proplists:get_value(Option, Options), proplists:delete(Option, Options)}.
+ {proplists:get_value(Option, Options),
+ proplists:delete(Option, Options)}.
%%
%% Tests
%%
-ifdef(TEST).
+
-include_lib("eunit/include/eunit.hrl").
you_should_write_a_test() ->
- ?assertEqual(
- "No, but I will!",
- "Have you written any tests?"),
+ ?assertEqual("No, but I will!",
+ "Have you written any tests?"),
ok.
-endif.
diff --git a/test/mochiweb_http_tests.erl b/test/mochiweb_http_tests.erl
index 8fbf0f3..d58971a 100644
--- a/test/mochiweb_http_tests.erl
+++ b/test/mochiweb_http_tests.erl
@@ -1,49 +1,51 @@
-module(mochiweb_http_tests).
+
-include_lib("eunit/include/eunit.hrl").
-ifdef(gen_tcp_r15b_workaround).
+
-define(SHOULD_HAVE_BUG, true).
+
-else.
+
-define(SHOULD_HAVE_BUG, false).
+
-endif.
has_acceptor_bug_test_() ->
- {setup,
- fun start_server/0,
- fun mochiweb_http:stop/1,
+ {setup, fun start_server/0, fun mochiweb_http:stop/1,
fun has_acceptor_bug_tests/1}.
start_server() ->
application:start(inets),
{ok, Pid} = mochiweb_http:start_link([{port, 0},
- {loop, fun responder/1}]),
+ {loop, fun responder/1}]),
Pid.
has_acceptor_bug_tests(Server) ->
Port = mochiweb_socket_server:get(Server, port),
[{"1000 should be fine even with the bug",
- ?_assertEqual(false, has_bug(Port, 1000))},
+ ?_assertEqual(false, (has_bug(Port, 1000)))},
{"10000 should trigger the bug if present",
- ?_assertEqual(?SHOULD_HAVE_BUG, has_bug(Port, 10000))}].
+ ?_assertEqual((?SHOULD_HAVE_BUG),
+ (has_bug(Port, 10000)))}].
responder(Req) ->
- mochiweb_request:respond(
- {
- 200,
- [{"Content-Type", "text/html"}],
- ["<html><body>Hello</body></html>"]
- },
- Req).
+ mochiweb_request:respond({200,
+ [{"Content-Type", "text/html"}],
+ ["<html><body>Hello</body></html>"]},
+ Req).
has_bug(Port, Len) ->
- case
- httpc:request(get, {"http://127.0.0.1:" ++ integer_to_list(Port) ++ "/",
- [{"X-Random", lists:duplicate(Len, $a)}]}, [], [])
- of
- {error, socket_closed_remotely} ->
- true;
- {ok, {{"HTTP/1.1", 200, "OK"}, _, "<html><body>Hello</body></html>"}} ->
- false;
- {ok, {{"HTTP/1.1", 400, "Bad Request"}, _, []}} ->
- false
- end.
+ case httpc:request(get,
+ {"http://127.0.0.1:" ++ integer_to_list(Port) ++ "/",
+ [{"X-Random", lists:duplicate(Len, $a)}]},
+ [], [])
+ of
+ {error, socket_closed_remotely} -> true;
+ {ok,
+ {{"HTTP/1.1", 200, "OK"}, _,
+ "<html><body>Hello</body></html>"}} ->
+ false;
+ {ok, {{"HTTP/1.1", 400, "Bad Request"}, _, []}} -> false
+ end.
diff --git a/test/mochiweb_request_tests.erl b/test/mochiweb_request_tests.erl
index 47fe7ee..119fc81 100644
--- a/test/mochiweb_request_tests.erl
+++ b/test/mochiweb_request_tests.erl
@@ -1,182 +1,238 @@
-module(mochiweb_request_tests).
-ifdef(TEST).
+
-include_lib("eunit/include/eunit.hrl").
accepts_content_type_test() ->
Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "multipart/related"}])),
- ?assertEqual(true, mochiweb_request:accepts_content_type("multipart/related", Req1)),
- ?assertEqual(true, mochiweb_request:accepts_content_type(<<"multipart/related">>, Req1)),
-
+ mochiweb_headers:make([{"Accept",
+ "multipart/related"}])),
+ ?assertEqual(true,
+ (mochiweb_request:accepts_content_type("multipart/related",
+ Req1))),
+ ?assertEqual(true,
+ (mochiweb_request:accepts_content_type(<<"multipart/related">>,
+ Req1))),
Req2 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html"}])),
- ?assertEqual(false, mochiweb_request:accepts_content_type("multipart/related", Req2)),
-
+ mochiweb_headers:make([{"Accept",
+ "text/html"}])),
+ ?assertEqual(false,
+ (mochiweb_request:accepts_content_type("multipart/related",
+ Req2))),
Req3 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html, multipart/*"}])),
- ?assertEqual(true, mochiweb_request:accepts_content_type("multipart/related", Req3)),
-
+ mochiweb_headers:make([{"Accept",
+ "text/html, multipart/*"}])),
+ ?assertEqual(true,
+ (mochiweb_request:accepts_content_type("multipart/related",
+ Req3))),
Req4 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html, multipart/*; q=0.0"}])),
- ?assertEqual(false, mochiweb_request:accepts_content_type("multipart/related", Req4)),
-
+ mochiweb_headers:make([{"Accept",
+ "text/html, multipart/*; q=0.0"}])),
+ ?assertEqual(false,
+ (mochiweb_request:accepts_content_type("multipart/related",
+ Req4))),
Req5 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html, multipart/*; q=0"}])),
- ?assertEqual(false, mochiweb_request:accepts_content_type("multipart/related", Req5)),
-
+ mochiweb_headers:make([{"Accept",
+ "text/html, multipart/*; q=0"}])),
+ ?assertEqual(false,
+ (mochiweb_request:accepts_content_type("multipart/related",
+ Req5))),
Req6 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html, */*; q=0.0"}])),
- ?assertEqual(false, mochiweb_request:accepts_content_type("multipart/related", Req6)),
-
+ mochiweb_headers:make([{"Accept",
+ "text/html, */*; q=0.0"}])),
+ ?assertEqual(false,
+ (mochiweb_request:accepts_content_type("multipart/related",
+ Req6))),
Req7 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "multipart/*; q=0.0, */*"}])),
- ?assertEqual(false, mochiweb_request:accepts_content_type("multipart/related", Req7)),
-
+ mochiweb_headers:make([{"Accept",
+ "multipart/*; q=0.0, */*"}])),
+ ?assertEqual(false,
+ (mochiweb_request:accepts_content_type("multipart/related",
+ Req7))),
Req8 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "*/*; q=0.0, multipart/*"}])),
- ?assertEqual(true, mochiweb_request:accepts_content_type("multipart/related", Req8)),
-
+ mochiweb_headers:make([{"Accept",
+ "*/*; q=0.0, multipart/*"}])),
+ ?assertEqual(true,
+ (mochiweb_request:accepts_content_type("multipart/related",
+ Req8))),
Req9 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "*/*; q=0.0, multipart/related"}])),
- ?assertEqual(true, mochiweb_request:accepts_content_type("multipart/related", Req9)),
-
+ mochiweb_headers:make([{"Accept",
+ "*/*; q=0.0, multipart/related"}])),
+ ?assertEqual(true,
+ (mochiweb_request:accepts_content_type("multipart/related",
+ Req9))),
Req10 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html; level=1"}])),
- ?assertEqual(true, mochiweb_request:accepts_content_type("text/html;level=1", Req10)),
-
+ mochiweb_headers:make([{"Accept",
+ "text/html; level=1"}])),
+ ?assertEqual(true,
+ (mochiweb_request:accepts_content_type("text/html;level=1",
+ Req10))),
Req11 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html; level=1, text/html"}])),
- ?assertEqual(true, mochiweb_request:accepts_content_type("text/html", Req11)),
-
+ mochiweb_headers:make([{"Accept",
+ "text/html; level=1, text/html"}])),
+ ?assertEqual(true,
+ (mochiweb_request:accepts_content_type("text/html",
+ Req11))),
Req12 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html; level=1; q=0.0, text/html"}])),
- ?assertEqual(false, mochiweb_request:accepts_content_type("text/html;level=1", Req12)),
-
+ mochiweb_headers:make([{"Accept",
+ "text/html; level=1; q=0.0, text/html"}])),
+ ?assertEqual(false,
+ (mochiweb_request:accepts_content_type("text/html;level=1",
+ Req12))),
Req13 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html; level=1; q=0.0, text/html"}])),
- ?assertEqual(false, mochiweb_request:accepts_content_type("text/html; level=1", Req13)),
-
+ mochiweb_headers:make([{"Accept",
+ "text/html; level=1; q=0.0, text/html"}])),
+ ?assertEqual(false,
+ (mochiweb_request:accepts_content_type("text/html; level=1",
+ Req13))),
Req14 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html;level=1;q=0.1, text/html"}])),
- ?assertEqual(true, mochiweb_request:accepts_content_type("text/html; level=1", Req14)).
+ mochiweb_headers:make([{"Accept",
+ "text/html;level=1;q=0.1, text/html"}])),
+ ?assertEqual(true,
+ (mochiweb_request:accepts_content_type("text/html; level=1",
+ Req14))).
accepted_encodings_test() ->
Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([])),
+ mochiweb_headers:make([])),
?assertEqual(["identity"],
- mochiweb_request:accepted_encodings(["gzip", "identity"], Req1)),
-
+ (mochiweb_request:accepted_encodings(["gzip",
+ "identity"],
+ Req1))),
Req2 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept-Encoding", "gzip, deflate"}])),
+ mochiweb_headers:make([{"Accept-Encoding",
+ "gzip, deflate"}])),
?assertEqual(["gzip", "identity"],
- mochiweb_request:accepted_encodings(["gzip", "identity"], Req2)),
-
+ (mochiweb_request:accepted_encodings(["gzip",
+ "identity"],
+ Req2))),
Req3 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept-Encoding", "gzip;q=0.5, deflate"}])),
+ mochiweb_headers:make([{"Accept-Encoding",
+ "gzip;q=0.5, deflate"}])),
?assertEqual(["deflate", "gzip", "identity"],
- mochiweb_request:accepted_encodings(["gzip", "deflate", "identity"], Req3)),
-
+ (mochiweb_request:accepted_encodings(["gzip", "deflate",
+ "identity"],
+ Req3))),
Req4 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept-Encoding", "identity, *;q=0"}])),
+ mochiweb_headers:make([{"Accept-Encoding",
+ "identity, *;q=0"}])),
?assertEqual(["identity"],
- mochiweb_request:accepted_encodings(["gzip", "deflate", "identity"], Req4)),
-
+ (mochiweb_request:accepted_encodings(["gzip", "deflate",
+ "identity"],
+ Req4))),
Req5 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept-Encoding", "gzip; q=0.1, *;q=0"}])),
+ mochiweb_headers:make([{"Accept-Encoding",
+ "gzip; q=0.1, *;q=0"}])),
?assertEqual(["gzip"],
- mochiweb_request:accepted_encodings(["gzip", "deflate", "identity"], Req5)),
-
+ (mochiweb_request:accepted_encodings(["gzip", "deflate",
+ "identity"],
+ Req5))),
Req6 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept-Encoding", "gzip; q=, *;q=0"}])),
+ mochiweb_headers:make([{"Accept-Encoding",
+ "gzip; q=, *;q=0"}])),
?assertEqual(bad_accept_encoding_value,
- mochiweb_request:accepted_encodings(["gzip", "deflate", "identity"], Req6)),
-
+ (mochiweb_request:accepted_encodings(["gzip", "deflate",
+ "identity"],
+ Req6))),
Req7 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept-Encoding", "gzip;q=2.0, *;q=0"}])),
+ mochiweb_headers:make([{"Accept-Encoding",
+ "gzip;q=2.0, *;q=0"}])),
?assertEqual(bad_accept_encoding_value,
- mochiweb_request:accepted_encodings(["gzip", "identity"], Req7)),
-
+ (mochiweb_request:accepted_encodings(["gzip",
+ "identity"],
+ Req7))),
Req8 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept-Encoding", "deflate, *;q=0.0"}])),
+ mochiweb_headers:make([{"Accept-Encoding",
+ "deflate, *;q=0.0"}])),
?assertEqual([],
- mochiweb_request:accepted_encodings(["gzip", "identity"], Req8)).
+ (mochiweb_request:accepted_encodings(["gzip",
+ "identity"],
+ Req8))).
accepted_content_types_test() ->
Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html"}])),
+ mochiweb_headers:make([{"Accept",
+ "text/html"}])),
?assertEqual(["text/html"],
- mochiweb_request:accepted_content_types(["text/html", "application/json"], Req1)),
-
+ (mochiweb_request:accepted_content_types(["text/html",
+ "application/json"],
+ Req1))),
Req2 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html, */*;q=0"}])),
+ mochiweb_headers:make([{"Accept",
+ "text/html, */*;q=0"}])),
?assertEqual(["text/html"],
- mochiweb_request:accepted_content_types(["text/html", "application/json"], Req2)),
-
+ (mochiweb_request:accepted_content_types(["text/html",
+ "application/json"],
+ Req2))),
Req3 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/*, */*;q=0"}])),
+ mochiweb_headers:make([{"Accept",
+ "text/*, */*;q=0"}])),
?assertEqual(["text/html"],
- mochiweb_request:accepted_content_types(["text/html", "application/json"], Req3)),
-
+ (mochiweb_request:accepted_content_types(["text/html",
+ "application/json"],
+ Req3))),
Req4 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/*;q=0.8, */*;q=0.5"}])),
+ mochiweb_headers:make([{"Accept",
+ "text/*;q=0.8, */*;q=0.5"}])),
?assertEqual(["text/html", "application/json"],
- mochiweb_request:accepted_content_types(["application/json", "text/html"], Req4)),
-
+ (mochiweb_request:accepted_content_types(["application/json",
+ "text/html"],
+ Req4))),
Req5 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/*;q=0.8, */*;q=0.5"}])),
+ mochiweb_headers:make([{"Accept",
+ "text/*;q=0.8, */*;q=0.5"}])),
?assertEqual(["text/html", "application/json"],
- mochiweb_request:accepted_content_types(["text/html", "application/json"], Req5)),
-
+ (mochiweb_request:accepted_content_types(["text/html",
+ "application/json"],
+ Req5))),
Req6 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/*;q=0.5, */*;q=0.5"}])),
+ mochiweb_headers:make([{"Accept",
+ "text/*;q=0.5, */*;q=0.5"}])),
?assertEqual(["application/json", "text/html"],
- mochiweb_request:accepted_content_types(["application/json", "text/html"], Req6)),
-
+ (mochiweb_request:accepted_content_types(["application/json",
+ "text/html"],
+ Req6))),
Req7 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make(
- [{"Accept", "text/html;q=0.5, application/json;q=0.5"}])),
+ mochiweb_headers:make([{"Accept",
+ "text/html;q=0.5, application/json;q=0.5"}])),
?assertEqual(["application/json", "text/html"],
- mochiweb_request:accepted_content_types(["application/json", "text/html"], Req7)),
-
+ (mochiweb_request:accepted_content_types(["application/json",
+ "text/html"],
+ Req7))),
Req8 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/html"}])),
+ mochiweb_headers:make([{"Accept",
+ "text/html"}])),
?assertEqual([],
- mochiweb_request:accepted_content_types(["application/json"], Req8)),
-
+ (mochiweb_request:accepted_content_types(["application/json"],
+ Req8))),
Req9 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Accept", "text/*;q=0.9, text/html;q=0.5, */*;q=0.7"}])),
+ mochiweb_headers:make([{"Accept",
+ "text/*;q=0.9, text/html;q=0.5, */*;q=0.7"}])),
?assertEqual(["application/json", "text/html"],
- mochiweb_request:accepted_content_types(["text/html", "application/json"], Req9)).
+ (mochiweb_request:accepted_content_types(["text/html",
+ "application/json"],
+ Req9))).
should_close_test() ->
F = fun (V, H) ->
- mochiweb_request:should_close(mochiweb_request:new(
- nil, 'GET', "/", V,
- mochiweb_headers:make(H)
- ))
- end,
- ?assertEqual(
- true,
- F({1, 1}, [{"Connection", "close"}])),
- ?assertEqual(
- true,
- F({1, 0}, [{"Connection", "close"}])),
- ?assertEqual(
- true,
- F({1, 1}, [{"Connection", "ClOSe"}])),
- ?assertEqual(
- false,
- F({1, 1}, [{"Connection", "closer"}])),
- ?assertEqual(
- false,
- F({1, 1}, [])),
- ?assertEqual(
- true,
- F({1, 0}, [])),
- ?assertEqual(
- false,
- F({1, 0}, [{"Connection", "Keep-Alive"}])),
+ mochiweb_request:should_close(mochiweb_request:new(nil,
+ 'GET', "/",
+ V,
+ mochiweb_headers:make(H)))
+ end,
+ ?assertEqual(true,
+ (F({1, 1}, [{"Connection", "close"}]))),
+ ?assertEqual(true,
+ (F({1, 0}, [{"Connection", "close"}]))),
+ ?assertEqual(true,
+ (F({1, 1}, [{"Connection", "ClOSe"}]))),
+ ?assertEqual(false,
+ (F({1, 1}, [{"Connection", "closer"}]))),
+ ?assertEqual(false, (F({1, 1}, []))),
+ ?assertEqual(true, (F({1, 0}, []))),
+ ?assertEqual(false,
+ (F({1, 0}, [{"Connection", "Keep-Alive"}]))),
ok.
-endif.
diff --git a/test/mochiweb_tests.erl b/test/mochiweb_tests.erl
index ae88bda..ef688f1 100644
--- a/test/mochiweb_tests.erl
+++ b/test/mochiweb_tests.erl
@@ -1,130 +1,149 @@
-module(mochiweb_tests).
+
-include_lib("eunit/include/eunit.hrl").
+
-include("mochiweb_test_util.hrl").
with_server(Transport, ServerFun, ClientFun) ->
- mochiweb_test_util:with_server(Transport, ServerFun, ClientFun).
+ mochiweb_test_util:with_server(Transport, ServerFun,
+ ClientFun).
request_test() ->
- R = mochiweb_request:new(z, z, "//foo///bar/baz%20wibble+quux?qs=2", z, []),
- "/foo/bar/baz wibble quux" = mochiweb_request:get(path, R),
+ R = mochiweb_request:new(z, z,
+ "//foo///bar/baz%20wibble+quux?qs=2", z, []),
+ "/foo/bar/baz wibble quux" = mochiweb_request:get(path,
+ R),
ok.
-define(LARGE_TIMEOUT, 60).
-single_http_GET_test() ->
- do_GET(plain, 1).
+single_http_GET_test() -> do_GET(plain, 1).
-single_https_GET_test() ->
- do_GET(ssl, 1).
+single_https_GET_test() -> do_GET(ssl, 1).
-multiple_http_GET_test() ->
- do_GET(plain, 3).
+multiple_http_GET_test() -> do_GET(plain, 3).
-multiple_https_GET_test() ->
- do_GET(ssl, 3).
+multiple_https_GET_test() -> do_GET(ssl, 3).
-hundred_http_GET_test_() -> % note the underscore
+hundred_http_GET_test_() ->
+ % note the underscore
{timeout, ?LARGE_TIMEOUT,
- fun() -> ?assertEqual(ok, do_GET(plain,100)) end}.
+ fun () -> ?assertEqual(ok, (do_GET(plain, 100))) end}.
-hundred_https_GET_test_() -> % note the underscore
+hundred_https_GET_test_() ->
+ % note the underscore
{timeout, ?LARGE_TIMEOUT,
- fun() -> ?assertEqual(ok, do_GET(ssl,100)) end}.
+ fun () -> ?assertEqual(ok, (do_GET(ssl, 100))) end}.
-single_128_http_POST_test() ->
- do_POST(plain, 128, 1).
+single_128_http_POST_test() -> do_POST(plain, 128, 1).
-single_128_https_POST_test() ->
- do_POST(ssl, 128, 1).
+single_128_https_POST_test() -> do_POST(ssl, 128, 1).
-single_2k_http_POST_test() ->
- do_POST(plain, 2048, 1).
+single_2k_http_POST_test() -> do_POST(plain, 2048, 1).
-single_2k_https_POST_test() ->
- do_POST(ssl, 2048, 1).
+single_2k_https_POST_test() -> do_POST(ssl, 2048, 1).
-single_100k_http_POST_test_() -> % note the underscore
+single_100k_http_POST_test_() ->
+ % note the underscore
{timeout, ?LARGE_TIMEOUT,
- fun() -> ?assertEqual(ok, do_POST(plain, 102400, 1)) end}.
+ fun () -> ?assertEqual(ok, (do_POST(plain, 102400, 1)))
+ end}.
-single_100k_https_POST_test_() -> % note the underscore
+single_100k_https_POST_test_() ->
+ % note the underscore
{timeout, ?LARGE_TIMEOUT,
- fun() -> ?assertEqual(ok, do_POST(ssl, 102400, 1)) end}.
+ fun () -> ?assertEqual(ok, (do_POST(ssl, 102400, 1)))
+ end}.
multiple_100k_http_POST_test() ->
{timeout, ?LARGE_TIMEOUT,
- fun() -> ?assertEqual(ok, do_POST(plain, 102400, 3)) end}.
+ fun () -> ?assertEqual(ok, (do_POST(plain, 102400, 3)))
+ end}.
multiple_100K_https_POST_test() ->
{timeout, ?LARGE_TIMEOUT,
- fun() -> ?assertEqual(ok, do_POST(ssl, 102400, 3)) end}.
+ fun () -> ?assertEqual(ok, (do_POST(ssl, 102400, 3)))
+ end}.
-hundred_128_http_POST_test_() -> % note the underscore
+hundred_128_http_POST_test_() ->
+ % note the underscore
{timeout, ?LARGE_TIMEOUT,
- fun() -> ?assertEqual(ok, do_POST(plain, 128, 100)) end}.
+ fun () -> ?assertEqual(ok, (do_POST(plain, 128, 100)))
+ end}.
-hundred_128_https_POST_test_() -> % note the underscore
+hundred_128_https_POST_test_() ->
+ % note the underscore
{timeout, ?LARGE_TIMEOUT,
- fun() -> ?assertEqual(ok, do_POST(ssl, 128, 100)) end}.
+ fun () -> ?assertEqual(ok, (do_POST(ssl, 128, 100)))
+ end}.
single_GET_scheme_test_() ->
- [{"ssl", ?_assertEqual(ok, do_GET("derp", ssl, 1))},
- {"plain", ?_assertEqual(ok, do_GET("derp", plain, 1))}].
+ [{"ssl", ?_assertEqual(ok, (do_GET("derp", ssl, 1)))},
+ {"plain",
+ ?_assertEqual(ok, (do_GET("derp", plain, 1)))}].
single_GET_absoluteURI_test_() ->
Uri = "https://example.com:123/x/",
ServerFun = fun (Req) ->
- mochiweb_request:ok({"text/plain", mochiweb_request:get(path, Req)}, Req)
- end,
+ mochiweb_request:ok({"text/plain",
+ mochiweb_request:get(path, Req)},
+ Req)
+ end,
%% Note that all the scheme/host/port information is discarded from path
- ClientFun = new_client_fun('GET', [#treq{path = Uri, xreply = <<"/x/">>}]),
+ ClientFun = new_client_fun('GET',
+ [#treq{path = Uri, xreply = <<"/x/">>}]),
[{atom_to_list(Transport),
- ?_assertEqual(ok, with_server(Transport, ServerFun, ClientFun))}
+ ?_assertEqual(ok,
+ (with_server(Transport, ServerFun, ClientFun)))}
|| Transport <- [ssl, plain]].
single_CONNECT_test_() ->
- [{"ssl", ?_assertEqual(ok, do_CONNECT(ssl, 1))},
- {"plain", ?_assertEqual(ok, do_CONNECT(plain, 1))}].
+ [{"ssl", ?_assertEqual(ok, (do_CONNECT(ssl, 1)))},
+ {"plain", ?_assertEqual(ok, (do_CONNECT(plain, 1)))}].
single_GET_any_test_() ->
ServerFun = fun (Req) ->
- mochiweb_request:ok({"text/plain", mochiweb_request:get(path, Req)}, Req)
- end,
- ClientFun = new_client_fun('GET', [#treq{path = "*", xreply = <<"*">>}]),
+ mochiweb_request:ok({"text/plain",
+ mochiweb_request:get(path, Req)},
+ Req)
+ end,
+ ClientFun = new_client_fun('GET',
+ [#treq{path = "*", xreply = <<"*">>}]),
[{atom_to_list(Transport),
- ?_assertEqual(ok, with_server(Transport, ServerFun, ClientFun))}
+ ?_assertEqual(ok,
+ (with_server(Transport, ServerFun, ClientFun)))}
|| Transport <- [ssl, plain]].
-
cookie_header_test() ->
ReplyPrefix = "You requested: ",
ExHeaders = [{"Set-Cookie", "foo=bar"},
- {"Set-Cookie", "foo=baz"}],
+ {"Set-Cookie", "foo=baz"}],
ServerFun = fun (Req) ->
- Reply = ReplyPrefix ++ mochiweb_request:get(path, Req),
- mochiweb_request:ok({"text/plain", ExHeaders, Reply}, Req)
- end,
+ Reply = ReplyPrefix ++ mochiweb_request:get(path, Req),
+ mochiweb_request:ok({"text/plain", ExHeaders, Reply},
+ Req)
+ end,
Path = "cookie_header",
ExpectedReply = list_to_binary(ReplyPrefix ++ Path),
- TestReqs = [#treq{path=Path, xreply=ExpectedReply, xheaders=ExHeaders}],
+ TestReqs = [#treq{path = Path, xreply = ExpectedReply,
+ xheaders = ExHeaders}],
ClientFun = new_client_fun('GET', TestReqs),
ok = with_server(plain, ServerFun, ClientFun),
ok.
-
do_CONNECT(Transport, Times) ->
PathPrefix = "example.com:",
ReplyPrefix = "You requested: ",
ServerFun = fun (Req) ->
- Reply = ReplyPrefix ++ mochiweb_request:get(path, Req),
- mochiweb_request:ok({"text/plain", Reply}, Req)
- end,
+ Reply = ReplyPrefix ++ mochiweb_request:get(path, Req),
+ mochiweb_request:ok({"text/plain", Reply}, Req)
+ end,
TestReqs = [begin
- Path = PathPrefix ++ integer_to_list(N),
- ExpectedReply = list_to_binary(ReplyPrefix ++ Path),
- #treq{path=Path, xreply=ExpectedReply}
- end || N <- lists:seq(1, Times)],
+ Path = PathPrefix ++ integer_to_list(N),
+ ExpectedReply = list_to_binary(ReplyPrefix ++ Path),
+ #treq{path = Path, xreply = ExpectedReply}
+ end
+ || N <- lists:seq(1, Times)],
ClientFun = new_client_fun('CONNECT', TestReqs),
ok = with_server(Transport, ServerFun, ClientFun),
ok.
@@ -135,86 +154,85 @@ do_GET(Transport, Times) ->
do_GET(PathPrefix, Transport, Times) ->
ReplyPrefix = "You requested: ",
ServerFun = fun (Req) ->
- Reply = ReplyPrefix ++ mochiweb_request:get(path, Req),
- mochiweb_request:ok({"text/plain", Reply}, Req)
- end,
+ Reply = ReplyPrefix ++ mochiweb_request:get(path, Req),
+ mochiweb_request:ok({"text/plain", Reply}, Req)
+ end,
TestReqs = [begin
- Path = PathPrefix ++ integer_to_list(N),
- ExpectedReply = list_to_binary(ReplyPrefix ++ Path),
- #treq{path=Path, xreply=ExpectedReply}
- end || N <- lists:seq(1, Times)],
+ Path = PathPrefix ++ integer_to_list(N),
+ ExpectedReply = list_to_binary(ReplyPrefix ++ Path),
+ #treq{path = Path, xreply = ExpectedReply}
+ end
+ || N <- lists:seq(1, Times)],
ClientFun = new_client_fun('GET', TestReqs),
ok = with_server(Transport, ServerFun, ClientFun),
ok.
do_POST(Transport, Size, Times) ->
ServerFun = fun (Req) ->
- Body = mochiweb_request:recv_body(Req),
- Headers = [{"Content-Type", "application/octet-stream"}],
- mochiweb_request:respond({201, Headers, Body}, Req)
- end,
+ Body = mochiweb_request:recv_body(Req),
+ Headers = [{"Content-Type",
+ "application/octet-stream"}],
+ mochiweb_request:respond({201, Headers, Body}, Req)
+ end,
TestReqs = [begin
- Path = "/stuff/" ++ integer_to_list(N),
- Body = crypto:strong_rand_bytes(Size),
- #treq{path=Path, body=Body, xreply=Body}
- end || N <- lists:seq(1, Times)],
+ Path = "/stuff/" ++ integer_to_list(N),
+ Body = crypto:strong_rand_bytes(Size),
+ #treq{path = Path, body = Body, xreply = Body}
+ end
+ || N <- lists:seq(1, Times)],
ClientFun = new_client_fun('POST', TestReqs),
ok = with_server(Transport, ServerFun, ClientFun),
ok.
new_client_fun(Method, TestReqs) ->
fun (Transport, Port) ->
- mochiweb_test_util:client_request(Transport, Port, Method, TestReqs)
+ mochiweb_test_util:client_request(Transport, Port,
+ Method, TestReqs)
end.
close_on_unread_data_test() ->
- ok = with_server(
- plain,
- fun mochiweb_request:not_found/1,
- fun close_on_unread_data_client/2).
+ ok = with_server(plain,
+ fun mochiweb_request:not_found/1,
+ fun close_on_unread_data_client/2).
close_on_unread_data_client(Transport, Port) ->
SockFun = mochiweb_test_util:sock_fun(Transport, Port),
%% A normal GET request should not trigger this behavior
- Request0 = string:join(
- ["GET / HTTP/1.1",
- "Host: localhost",
- "",
- ""],
- "\r\n"),
+ Request0 = string:join(["GET / HTTP/1.1",
+ "Host: localhost", "", ""],
+ "\r\n"),
ok = SockFun({setopts, [{packet, http}]}),
ok = SockFun({send, Request0}),
- ?assertMatch(
- {ok, {http_response, {1, 1}, 404, _}},
- SockFun(recv)),
- Headers0 = mochiweb_test_util:read_server_headers(SockFun),
- ?assertEqual(
- undefined,
- mochiweb_headers:get_value("Connection", Headers0)),
- Len0 = list_to_integer(
- mochiweb_headers:get_value("Content-Length", Headers0)),
- _Body0 = mochiweb_test_util:drain_reply(SockFun, Len0, <<>>),
+ ?assertMatch({ok, {http_response, {1, 1}, 404, _}},
+ (SockFun(recv))),
+ Headers0 =
+ mochiweb_test_util:read_server_headers(SockFun),
+ ?assertEqual(undefined,
+ (mochiweb_headers:get_value("Connection", Headers0))),
+ Len0 =
+ list_to_integer(mochiweb_headers:get_value("Content-Length",
+ Headers0)),
+ _Body0 = mochiweb_test_util:drain_reply(SockFun, Len0,
+ <<>>),
%% Re-use same socket
- Request = string:join(
- ["POST / HTTP/1.1",
- "Host: localhost",
- "Content-Type: application/json",
- "Content-Length: 2",
- "",
- "{}"],
- "\r\n"),
+ Request = string:join(["POST / HTTP/1.1",
+ "Host: localhost", "Content-Type: application/json",
+ "Content-Length: 2", "", "{}"],
+ "\r\n"),
ok = SockFun({setopts, [{packet, http}]}),
ok = SockFun({send, Request}),
- ?assertMatch(
- {ok, {http_response, {1, 1}, 404, _}},
- SockFun(recv)),
- Headers = mochiweb_test_util:read_server_headers(SockFun),
+ ?assertMatch({ok, {http_response, {1, 1}, 404, _}},
+ (SockFun(recv))),
+ Headers =
+ mochiweb_test_util:read_server_headers(SockFun),
%% Expect to see a Connection: close header when we know the
%% server will close the connection re #146
- ?assertEqual(
- "close",
- mochiweb_headers:get_value("Connection", Headers)),
- Len = list_to_integer(mochiweb_headers:get_value("Content-Length", Headers)),
- _Body = mochiweb_test_util:drain_reply(SockFun, Len, <<>>),
- ?assertEqual({error, closed}, SockFun(recv)),
+ ?assertEqual("close",
+ (mochiweb_headers:get_value("Connection", Headers))),
+ Len =
+ list_to_integer(mochiweb_headers:get_value("Content-Length",
+ Headers)),
+ _Body = mochiweb_test_util:drain_reply(SockFun, Len,
+ <<>>),
+ ?assertEqual({error, closed}, (SockFun(recv))),
ok.
diff --git a/test/mochiweb_websocket_tests.erl b/test/mochiweb_websocket_tests.erl
index 0af29b7..5fd6273 100644
--- a/test/mochiweb_websocket_tests.erl
+++ b/test/mochiweb_websocket_tests.erl
@@ -1,4 +1,5 @@
-module(mochiweb_websocket_tests).
+
-author('lukasz.lalik@zadane.pl').
%% The MIT License (MIT)
@@ -27,76 +28,81 @@
make_handshake_for_correct_client_test() ->
%% Hybi handshake
- Req1 = mochiweb_request:new(
- nil, 'GET', "/foo", {1, 1},
- mochiweb_headers:make([{"Sec-WebSocket-Key",
- "Xn3fdKyc3qEXPuj2A3O+ZA=="}])),
-
- {Version1,
- {HttpCode1, Headers1, _}} = mochiweb_websocket:make_handshake(Req1),
+ Req1 = mochiweb_request:new(nil, 'GET', "/foo", {1, 1},
+ mochiweb_headers:make([{"Sec-WebSocket-Key",
+ "Xn3fdKyc3qEXPuj2A3O+ZA=="}])),
+ {Version1, {HttpCode1, Headers1, _}} =
+ mochiweb_websocket:make_handshake(Req1),
?assertEqual(hybi, Version1),
?assertEqual(101, HttpCode1),
- ?assertEqual("Upgrade", proplists:get_value("Connection", Headers1)),
+ ?assertEqual("Upgrade",
+ (proplists:get_value("Connection", Headers1))),
?assertEqual(<<"BIFTHkJk4r5t8kuud82tZJaQsCE=">>,
- proplists:get_value("Sec-Websocket-Accept", Headers1)),
-
+ (proplists:get_value("Sec-Websocket-Accept",
+ Headers1))),
%% Hixie handshake
{Version2, {HttpCode2, Headers2, Body2}} =
- mochiweb_websocket:hixie_handshake(
- "ws://",
- "localhost", "/",
- "33j284 9 z63 e 9 7",
- "TF'3|6D12659H 7 70",
- <<175,181,191,215,128,195,144,120>>,
- "null"),
+ mochiweb_websocket:hixie_handshake("ws://", "localhost",
+ "/", "33j284 9 z63 e 9 7",
+ "TF'3|6D12659H 7 70",
+ <<175, 181, 191, 215, 128, 195, 144,
+ 120>>,
+ "null"),
?assertEqual(hixie, Version2),
?assertEqual(101, HttpCode2),
- ?assertEqual("null", proplists:get_value("Sec-WebSocket-Origin", Headers2)),
+ ?assertEqual("null",
+ (proplists:get_value("Sec-WebSocket-Origin",
+ Headers2))),
?assertEqual("ws://localhost/",
- proplists:get_value("Sec-WebSocket-Location", Headers2)),
- ?assertEqual(
- <<230,144,237,94,84,214,41,69,244,150,134,167,221,103,239,246>>,
- Body2).
+ (proplists:get_value("Sec-WebSocket-Location",
+ Headers2))),
+ ?assertEqual(<<230, 144, 237, 94, 84, 214, 41, 69, 244,
+ 150, 134, 167, 221, 103, 239, 246>>,
+ Body2).
hybi_frames_decode_test() ->
- ?assertEqual(
- [{1, <<"foo">>}],
- mochiweb_websocket:parse_hybi_frames(
- nil, <<129,131,118,21,153,58,16,122,246>>, [])),
- ?assertEqual(
- [{1, <<"foo">>}, {1, <<"bar">>}],
- mochiweb_websocket:parse_hybi_frames(
- nil,
- <<129,131,1,225,201,42,103,142,166,129,131,93,222,214,66,63,191,164>>,
- [])).
+ ?assertEqual([{1, <<"foo">>}],
+ (mochiweb_websocket:parse_hybi_frames(nil,
+ <<129, 131, 118, 21, 153,
+ 58, 16, 122, 246>>,
+ []))),
+ ?assertEqual([{1, <<"foo">>}, {1, <<"bar">>}],
+ (mochiweb_websocket:parse_hybi_frames(nil,
+ <<129, 131, 1, 225, 201,
+ 42, 103, 142, 166, 129,
+ 131, 93, 222, 214, 66,
+ 63, 191, 164>>,
+ []))).
hixie_frames_decode_test() ->
- ?assertEqual(
- [],
- mochiweb_websocket:parse_hixie_frames(<<>>, [])),
- ?assertEqual(
- [<<"foo">>],
- mochiweb_websocket:parse_hixie_frames(<<0,102,111,111,255>>, [])),
- ?assertEqual(
- [<<"foo">>, <<"bar">>],
- mochiweb_websocket:parse_hixie_frames(
- <<0,102,111,111,255,0,98,97,114,255>>,
- [])).
+ ?assertEqual([],
+ (mochiweb_websocket:parse_hixie_frames(<<>>, []))),
+ ?assertEqual([<<"foo">>],
+ (mochiweb_websocket:parse_hixie_frames(<<0, 102, 111,
+ 111, 255>>,
+ []))),
+ ?assertEqual([<<"foo">>, <<"bar">>],
+ (mochiweb_websocket:parse_hixie_frames(<<0, 102, 111,
+ 111, 255, 0, 98, 97,
+ 114, 255>>,
+ []))).
end_to_end_test_factory(ServerTransport) ->
- mochiweb_test_util:with_server(
- ServerTransport,
- fun end_to_end_server/1,
- fun (Transport, Port) ->
- end_to_end_client(mochiweb_test_util:sock_fun(Transport, Port))
- end).
+ mochiweb_test_util:with_server(ServerTransport,
+ fun end_to_end_server/1,
+ fun (Transport, Port) ->
+ end_to_end_client(mochiweb_test_util:sock_fun(Transport,
+ Port))
+ end).
end_to_end_server(Req) ->
- ?assertEqual("Upgrade", mochiweb_request:get_header_value("connection", Req)),
- ?assertEqual("websocket", mochiweb_request:get_header_value("upgrade", Req)),
- {ReentryWs, _ReplyChannel} = mochiweb_websocket:upgrade_connection(
- Req,
- fun end_to_end_ws_loop/3),
+ ?assertEqual("Upgrade",
+ (mochiweb_request:get_header_value("connection", Req))),
+ ?assertEqual("websocket",
+ (mochiweb_request:get_header_value("upgrade", Req))),
+ {ReentryWs, _ReplyChannel} =
+ mochiweb_websocket:upgrade_connection(Req,
+ fun end_to_end_ws_loop/3),
ReentryWs(ok).
end_to_end_ws_loop(Payload, State, ReplyChannel) ->
@@ -106,55 +112,52 @@ end_to_end_ws_loop(Payload, State, ReplyChannel) ->
end_to_end_client(S) ->
%% Key and Accept per https://tools.ietf.org/html/rfc6455
- UpgradeReq = string:join(
- ["GET / HTTP/1.1",
- "Host: localhost",
- "Upgrade: websocket",
- "Connection: Upgrade",
- "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==",
- "",
- ""], "\r\n"),
+ UpgradeReq = string:join(["GET / HTTP/1.1",
+ "Host: localhost", "Upgrade: websocket",
+ "Connection: Upgrade",
+ "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==", "",
+ ""],
+ "\r\n"),
ok = S({send, UpgradeReq}),
- {ok, {http_response, {1,1}, 101, _}} = S(recv),
- read_expected_headers(
- S,
- [{'Upgrade', "websocket"},
- {'Connection', "Upgrade"},
- {'Content-Length', "0"},
- {"Sec-Websocket-Accept", "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}]),
+ {ok, {http_response, {1, 1}, 101, _}} = S(recv),
+ read_expected_headers(S,
+ [{'Upgrade', "websocket"}, {'Connection', "Upgrade"},
+ {'Content-Length', "0"},
+ {"Sec-Websocket-Accept",
+ "s3pPLMBiTxaQ9kYGzzhZRbK+xOo="}]),
%% The first message sent over telegraph :)
SmallMessage = <<"What hath God wrought?">>,
ok = S({send,
- << 1:1, %% Fin
- 0:1, %% Rsv1
- 0:1, %% Rsv2
- 0:1, %% Rsv3
- 2:4, %% Opcode, 1 = text frame
- 1:1, %% Mask on
- (byte_size(SmallMessage)):7, %% Length, <125 case
- 0:32, %% Mask (trivial)
- SmallMessage/binary >>}),
+ <<1:1, %% Fin
+ 0:1, %% Rsv1
+ 0:1, %% Rsv2
+ 0:1, %% Rsv3
+ 2:4, %% Opcode, 1 = text frame
+ 1:1, %% Mask on
+ (byte_size(SmallMessage)):7, %% Length, <125 case
+ 0:32, %% Mask (trivial)
+ SmallMessage/binary>>}),
{ok, WsFrames} = S(recv),
- << 1:1, %% Fin
- 0:1, %% Rsv1
- 0:1, %% Rsv2
- 0:1, %% Rsv3
- 1:4, %% Opcode, text frame (all mochiweb suports for now)
- MsgSize:8, %% Expecting small size
- SmallMessage/binary >> = WsFrames,
- ?assertEqual(MsgSize, byte_size(SmallMessage)),
+ <<1:1, %% Fin
+ 0:1, %% Rsv1
+ 0:1, %% Rsv2
+ 0:1, %% Rsv3
+ 1:4, %% Opcode, text frame (all mochiweb suports for now)
+ MsgSize:8, %% Expecting small size
+ SmallMessage/binary>> =
+ WsFrames,
+ ?assertEqual(MsgSize, (byte_size(SmallMessage))),
ok.
read_expected_headers(S, D) ->
Headers = mochiweb_test_util:read_server_headers(S),
- lists:foreach(
- fun ({K, V}) ->
- ?assertEqual(V, mochiweb_headers:get_value(K, Headers))
- end,
- D).
+ lists:foreach(fun ({K, V}) ->
+ ?assertEqual(V,
+ (mochiweb_headers:get_value(K, Headers)))
+ end,
+ D).
end_to_end_http_test() ->
end_to_end_test_factory(plain).
-end_to_end_https_test() ->
- end_to_end_test_factory(ssl).
+end_to_end_https_test() -> end_to_end_test_factory(ssl).