diff options
author | Christophe Romain <christophe.romain@process-one.net> | 2012-09-11 15:45:59 +0200 |
---|---|---|
committer | Christophe Romain <christophe.romain@process-one.net> | 2012-09-11 15:45:59 +0200 |
commit | 011535f0de1a14d6f5f411035bff9eeafec1c612 (patch) | |
tree | e60951904fbdc14dc126450c4d7515f51188d4b7 /src/web/mod_http_fileserver.erl | |
parent | Merge branch '2.1.x' into 2.2.x (diff) |
binary refactoring
Diffstat (limited to 'src/web/mod_http_fileserver.erl')
-rw-r--r-- | src/web/mod_http_fileserver.erl | 417 |
1 files changed, 224 insertions, 193 deletions
diff --git a/src/web/mod_http_fileserver.erl b/src/web/mod_http_fileserver.erl index b7c16f1b5..b47d8c34d 100644 --- a/src/web/mod_http_fileserver.erl +++ b/src/web/mod_http_fileserver.erl @@ -25,8 +25,11 @@ %%%---------------------------------------------------------------------- -module(mod_http_fileserver). + -author('mmirra@process-one.net'). + -author('ecestari@process-one.net'). + -behaviour(gen_mod). %% gen_mod callbacks @@ -36,113 +39,119 @@ -export([process/2]). -include("ejabberd.hrl"). + -include("jlib.hrl"). + -include_lib("kernel/include/file.hrl"). -include("ejabberd_http.hrl"). --ifdef(SSL40). --define(STRING2LOWER, string). --else. --ifdef(SSL39). --define(STRING2LOWER, string). --else. --define(STRING2LOWER, httpd_util). --endif. --endif. - -%% Response is {DataSize, Code, [{HeaderKey, HeaderValue}], Data} --define(HTTP_ERR_FILE_NOT_FOUND, {-1, 404, [], "Not found"}). --define(HTTP_ERR_FORBIDDEN, {-1, 403, [], "Forbidden"}). - --define(DEFAULT_CONTENT_TYPE, "application/octet-stream"). --define(DEFAULT_CONTENT_TYPES, [{".css", "text/css"}, - {".gif", "image/gif"}, - {".html", "text/html"}, - {".jar", "application/java-archive"}, - {".jpeg", "image/jpeg"}, - {".jpg", "image/jpeg"}, - {".js", "text/javascript"}, - {".png", "image/png"}, - {".svg", "image/svg+xml"}, - {".txt", "text/plain"}, - {".xml", "application/xml"}, - {".xpi", "application/x-xpinstall"}, - {".xul", "application/vnd.mozilla.xul+xml"}]). - --compile(export_all). +-define(HTTP_ERR_FILE_NOT_FOUND, + {-1, 404, [], <<"Not found">>}). + +-define(HTTP_ERR_FORBIDDEN, + {-1, 403, [], <<"Forbidden">>}). +-define(DEFAULT_CONTENT_TYPE, + <<"application/octet-stream">>). + +-define(DEFAULT_CONTENT_TYPES, + [{<<".css">>, <<"text/css">>}, + {<<".gif">>, <<"image/gif">>}, + {<<".html">>, <<"text/html">>}, + {<<".jar">>, <<"application/java-archive">>}, + {<<".jpeg">>, <<"image/jpeg">>}, + {<<".jpg">>, <<"image/jpeg">>}, + {<<".js">>, <<"text/javascript">>}, + {<<".png">>, <<"image/png">>}, + {<<".svg">>, <<"image/svg+xml">>}, + {<<".txt">>, <<"text/plain">>}, + {<<".xml">>, <<"application/xml">>}, + {<<".xpi">>, <<"application/x-xpinstall">>}, + {<<".xul">>, <<"application/vnd.mozilla.xul+xml">>}]). start(Host, Opts) -> - DocRoot = gen_mod:get_opt(docroot, Opts, undefined), + DocRoot = gen_mod:get_opt(docroot, Opts, + fun iolist_to_binary/1, + undefined), set_default_host(Host, Opts), conf_store(Host, docroot, DocRoot), check_docroot_defined(DocRoot, Host), DRInfo = check_docroot_exists(DocRoot), check_docroot_is_dir(DRInfo, DocRoot), check_docroot_is_readable(DRInfo, DocRoot), - AccessLog = gen_mod:get_opt(accesslog, Opts, undefined), + AccessLog = gen_mod:get_opt(accesslog, Opts, + fun iolist_to_binary/1, + undefined), start_log(Host, AccessLog), - DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, []), + DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, + fun(L) when is_list(L) -> L end, + []), conf_store(Host, directory_indices, DirectoryIndices), - ServeStaticGzip = gen_mod:get_opt(serve_gzip, Opts, false), + ServeStaticGzip = gen_mod:get_opt(serve_gzip, Opts, + fun(B) when is_boolean(B) -> B end, + false), conf_store(Host, serve_gzip, ServeStaticGzip), - CustomHeaders = gen_mod:get_opt(custom_headers, Opts, []), + CustomHeaders = gen_mod:get_opt(custom_headers, Opts, + fun(L) when is_list(L) -> L end, + []), conf_store(Host, custom_headers, CustomHeaders), - DefaultContentType = gen_mod:get_opt(default_content_type, Opts, - ?DEFAULT_CONTENT_TYPE), - conf_store(Host, default_content_type, DefaultContentType), - ContentTypes = build_list_content_types(gen_mod:get_opt(content_types, Opts, []), ?DEFAULT_CONTENT_TYPES), + DefaultContentType = + gen_mod:get_opt(default_content_type, Opts, + fun iolist_to_binary/1, + ?DEFAULT_CONTENT_TYPE), + conf_store(Host, default_content_type, + DefaultContentType), + ContentTypes = build_list_content_types( + gen_mod:get_opt(content_types, Opts, + fun(L) when is_list(L) -> L end, + []), + ?DEFAULT_CONTENT_TYPES), conf_store(Host, content_types, ContentTypes), ?INFO_MSG("initialize: ~n ~p", [ContentTypes]), ok. -% Defines host that will answer request if hostname is not recognized. -% The first configured host will be used. -set_default_host(Host, _Opts)-> +set_default_host(Host, _Opts) -> case mochiglobal:get(http_default_host) of - undefined -> - ?DEBUG("Setting default host to ~p", [Host]), - mochiglobal:put(http_default_host, Host); - _ -> - ok + undefined -> + ?DEBUG("Setting default host to ~p", [Host]), + mochiglobal:put(http_default_host, Host); + _ -> ok end. -% Returns current host if it exists or default host -get_host(Host)-> + +get_host(Host) -> DCT = mochiglobal:get(default_content_type), case lists:keymember(Host, 1, DCT) of - true -> Host; - false -> mochiglobal:get(http_default_host) + true -> Host; + false -> mochiglobal:get(http_default_host) end. - -conf_store(Host, Key, Value)-> + +conf_store(Host, Key, Value) -> R = case mochiglobal:get(Key) of - undefined -> [{Host, Value}]; - A -> - case lists:keymember(Host, 1, A) of - true -> lists:keyreplace(Host, 1, A,{Host, Value}); - false -> [{Host, Value}|A] - end - end, + undefined -> [{Host, Value}]; + A -> + case lists:keymember(Host, 1, A) of + true -> lists:keyreplace(Host, 1, A, {Host, Value}); + false -> [{Host, Value} | A] + end + end, mochiglobal:put(Key, R). - + conf_get(Host, Key) -> case mochiglobal:get(Key) of - undefined-> undefined; - A -> - case lists:keyfind(Host, 1, A) of - {Host, Val} -> Val; - false -> - case mochiglobal:get(http_default_host) of - Host -> % stop recursion here - undefined; - DefaultHost -> - conf_get(DefaultHost, Key) - end - end + undefined -> undefined; + A -> + case lists:keyfind(Host, 1, A) of + {Host, Val} -> Val; + false -> + case mochiglobal:get(http_default_host) of + Host -> % stop recursion here + undefined; + DefaultHost -> conf_get(DefaultHost, Key) + end + end end. - %% @spec (AdminCTs::[CT], Default::[CT]) -> [CT] %% where CT = {Extension::string(), Value} %% Value = string() | undefined @@ -150,45 +159,47 @@ conf_get(Host, Key) -> %% Elements of AdminCTs have more priority. %% If a CT is declared as 'undefined', then it is not included in the result. -start_log(_Host, undefined)-> - ok; +start_log(_Host, undefined) -> ok; start_log(Host, FileName) -> mod_http_fileserver_log:start(Host, FileName). - -build_list_content_types(AdminCTsUnsorted, DefaultCTsUnsorted) -> + +build_list_content_types(AdminCTsUnsorted, + DefaultCTsUnsorted) -> AdminCTs = lists:ukeysort(1, AdminCTsUnsorted), DefaultCTs = lists:ukeysort(1, DefaultCTsUnsorted), - CTsUnfiltered = lists:ukeymerge(1, AdminCTs, DefaultCTs), - [{Extension, Value} || {Extension, Value} <- CTsUnfiltered, Value /= undefined]. + CTsUnfiltered = lists:ukeymerge(1, AdminCTs, + DefaultCTs), + [{Extension, Value} + || {Extension, Value} <- CTsUnfiltered, + Value /= undefined]. check_docroot_defined(DocRoot, Host) -> case DocRoot of - undefined -> throw({undefined_docroot_option, Host}); - _ -> ok + undefined -> throw({undefined_docroot_option, Host}); + _ -> ok end. check_docroot_exists(DocRoot) -> case file:read_file_info(DocRoot) of - {error, Reason} -> throw({error_access_docroot, DocRoot, Reason}); - {ok, FI} -> FI + {error, Reason} -> + throw({error_access_docroot, DocRoot, Reason}); + {ok, FI} -> FI end. check_docroot_is_dir(DRInfo, DocRoot) -> case DRInfo#file_info.type of - directory -> ok; - _ -> throw({docroot_not_directory, DocRoot}) + directory -> ok; + _ -> throw({docroot_not_directory, DocRoot}) end. check_docroot_is_readable(DRInfo, DocRoot) -> case DRInfo#file_info.access of - read -> ok; - read_write -> ok; - _ -> throw({docroot_not_readable, DocRoot}) + read -> ok; + read_write -> ok; + _ -> throw({docroot_not_readable, DocRoot}) end. - -stop(_Host) -> - ok. +stop(_Host) -> ok. %%==================================================================== %% request_handlers callbacks @@ -205,141 +216,161 @@ process(LocalPath, Request) -> ClientHeaders = Request#request.headers, DirectoryIndices = conf_get(Host, directory_indices), CustomHeaders = conf_get(Host, custom_headers), - DefaultContentType = conf_get(Host, default_content_type), + DefaultContentType = conf_get(Host, + default_content_type), ContentTypes = conf_get(Host, content_types), Encoding = conf_get(Host, serve_gzip), Static = select_encoding(ClientHeaders, Encoding), DocRoot = conf_get(Host, docroot), - FileName = filename:join(filename:split(DocRoot) ++ LocalPath), - {FileSize, Code, Headers, Contents} = case file:read_file_info(FileName) of - {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND; - {error, eacces} -> ?HTTP_ERR_FORBIDDEN; - {ok, #file_info{type = directory}} -> serve_index(FileName, - DirectoryIndices, - CustomHeaders, - DefaultContentType, - ContentTypes, Static); - {ok, FileInfo} -> - case should_serve(FileInfo, ClientHeaders) of - true ->serve_file(FileInfo, FileName, - CustomHeaders, - DefaultContentType, - ContentTypes, Static); - false -> - {0, 304, [], []} - end - end, - mod_http_fileserver_log:add_to_log(Host,FileSize, Code, Request), + FileName = filename:join(filename:split(DocRoot) ++ + LocalPath), + {FileSize, Code, Headers, Contents} = case + file:read_file_info(FileName) + of + {error, enoent} -> + ?HTTP_ERR_FILE_NOT_FOUND; + {error, eacces} -> + ?HTTP_ERR_FORBIDDEN; + {ok, + #file_info{type = directory}} -> + serve_index(FileName, + DirectoryIndices, + CustomHeaders, + DefaultContentType, + ContentTypes, + Static); + {ok, FileInfo} -> + case should_serve(FileInfo, + ClientHeaders) + of + true -> + serve_file(FileInfo, + FileName, + CustomHeaders, + DefaultContentType, + ContentTypes, + Static); + false -> {0, 304, [], []} + end + end, + mod_http_fileserver_log:add_to_log(Host, FileSize, Code, + Request), {Code, Headers, Contents}. should_serve(FileInfo, Headers) -> - lists:foldl(fun({Header, Fun}, Acc)-> - case lists:keyfind(Header, 1, Headers) of - {_, Val} -> - Fun(FileInfo,Val); - _O -> - Acc - end - end, true, [{'If-None-Match',fun etag/2} - ]). -etag(FileInfo, Etag)-> + lists:foldl(fun ({Header, Fun}, Acc) -> + case lists:keyfind(Header, 1, Headers) of + {_, Val} -> Fun(FileInfo, Val); + _O -> Acc + end + end, + true, [{'If-None-Match', fun etag/2}]). + +etag(FileInfo, Etag) -> case httpd_util:create_etag(FileInfo) of - Etag -> - false; - _ -> - true + Etag -> false; + _ -> true end. -modified(FileInfo, LastModified)-> - AfterDate = calendar:datetime_to_gregorian_seconds( - httpd_util:convert_request_date(LastModified)), - Mtime = calendar:datetime_to_gregorian_seconds(FileInfo#file_info.mtime), - ?DEBUG("Modified : ~p > ~p (serving: ~p)", [Mtime, AfterDate,Mtime > AfterDate]), - Mtime > AfterDate. - -select_encoding(_Headers, false)-> - false; -select_encoding(Headers, Configuration)-> - Value = find_header('Accept-Encoding', Headers, ""), - Schemes = string:tokens(Value, ","), - case lists:member("gzip",Schemes) of - true -> Configuration; - false -> false + +select_encoding(_Headers, false) -> false; +select_encoding(Headers, Configuration) -> + Value = find_header('Accept-Encoding', Headers, <<"">>), + Schemes = str:tokens(Value, <<",">>), + case lists:member(<<"gzip">>, Schemes) of + true -> Configuration; + false -> false end. -%% Troll through the directory indices attempting to find one which -%% works, if none can be found, return a 404. -serve_index(_FileName, [], _CH, _DefaultContentType, _ContentTypes, _Static) -> +serve_index(_FileName, [], _CH, _DefaultContentType, + _ContentTypes, _Static) -> ?HTTP_ERR_FILE_NOT_FOUND; -serve_index(FileName, [Index | T], CH, DefaultContentType, ContentTypes, Static) -> +serve_index(FileName, [Index | T], CH, + DefaultContentType, ContentTypes, Static) -> IndexFileName = filename:join([FileName] ++ [Index]), case file:read_file_info(IndexFileName) of - {error, _Error} -> serve_index(FileName, T, CH, DefaultContentType, ContentTypes, Static); - {ok, #file_info{type = directory}} -> serve_index(FileName, T, CH, DefaultContentType, ContentTypes, Static); - {ok, FileInfo} -> serve_file(FileInfo, IndexFileName, CH, DefaultContentType, ContentTypes, Static) + {error, _Error} -> + serve_index(FileName, T, CH, DefaultContentType, + ContentTypes, Static); + {ok, #file_info{type = directory}} -> + serve_index(FileName, T, CH, DefaultContentType, + ContentTypes, Static); + {ok, FileInfo} -> + serve_file(FileInfo, IndexFileName, CH, + DefaultContentType, ContentTypes, Static) end. %% Assume the file exists if we got this far and attempt to read it in %% and serve it up. - -serve_file(FileInfo, FileName, CustomHeaders, DefaultContentType, ContentTypes, false) -> + +serve_file(FileInfo, FileName, CustomHeaders, + DefaultContentType, ContentTypes, false) -> ?DEBUG("Delivering: ~s", [FileName]), - ContentType = content_type(FileName, DefaultContentType, ContentTypes), + ContentType = content_type(FileName, DefaultContentType, + ContentTypes), {ok, FileContents} = file:read_file(FileName), - {FileInfo#file_info.size, - 200, [{"Server", "ejabberd"}, - {"Last-Modified", last_modified(FileInfo)}, - {"Content-Type", ContentType} | CustomHeaders], + {FileInfo#file_info.size, 200, + [{<<"Server">>, <<"ejabberd">>}, + {<<"Last-Modified">>, last_modified(FileInfo)}, + {<<"Content-Type">>, ContentType} + | CustomHeaders], FileContents}; - -serve_file(FileInfo, FileName, CustomHeaders, DefaultContentType, ContentTypes, Gzip) -> +serve_file(FileInfo, FileName, CustomHeaders, + DefaultContentType, ContentTypes, Gzip) -> ?DEBUG("Delivering: ~s", [FileName]), - ContentType = content_type(FileName, DefaultContentType, ContentTypes), - CompressedFileName = FileName ++ ".gz", + ContentType = content_type(FileName, DefaultContentType, + ContentTypes), + CompressedFileName = <<FileName/binary, ".gz">>, case file:read_file_info(CompressedFileName) of - {ok, FileInfoCompressed} -> %Found compressed - ?INFO_MSG("Found compressed: ~s", [FileName]), - {ok, FileContents} = file:read_file(CompressedFileName), - {FileInfoCompressed#file_info.size, - 200, [{"Server", "ejabberd"}, - {"Last-Modified", last_modified(FileInfoCompressed)}, - {"Content-Type", ContentType}, - {"Etag", httpd_util:create_etag(FileInfoCompressed)}, - {"Content-Encoding", "gzip"} | CustomHeaders], - FileContents}; - {error, _} -> - {FileContents, Size} = case Gzip of - static -> - {ok, Content} = file:read_file(FileName), - {Content, FileInfo#file_info.size}; - always -> - {ok, Content} = file:read_file(FileName), - Compressed = zlib:gzip(Content), - {Compressed, size(Compressed)} - end, - {Size, - 200, [{"Server", "ejabberd"}, - {"Last-Modified", last_modified(FileInfo)}, - {"Etag", httpd_util:create_etag(FileInfo)}, - {"Content-Type", ContentType}, - {"Content-Encoding", "gzip"} | CustomHeaders], - FileContents} + {ok, FileInfoCompressed} -> + ?INFO_MSG("Found compressed: ~s", [FileName]), + {ok, FileContents} = file:read_file(CompressedFileName), + {FileInfoCompressed#file_info.size, 200, + [{<<"Server">>, <<"ejabberd">>}, + {<<"Last-Modified">>, + last_modified(FileInfoCompressed)}, + {<<"Content-Type">>, ContentType}, + {<<"Etag">>, + httpd_util:create_etag(FileInfoCompressed)}, + {<<"Content-Encoding">>, <<"gzip">>} + | CustomHeaders], + FileContents}; + {error, _} -> + {FileContents, Size} = case Gzip of + static -> + {ok, Content} = file:read_file(FileName), + {Content, FileInfo#file_info.size}; + always -> + {ok, Content} = file:read_file(FileName), + Compressed = zlib:gzip(Content), + {Compressed, byte_size(Compressed)} + end, + {Size, 200, + [{<<"Server">>, <<"ejabberd">>}, + {<<"Last-Modified">>, last_modified(FileInfo)}, + {<<"Etag">>, httpd_util:create_etag(FileInfo)}, + {<<"Content-Type">>, ContentType}, + {<<"Content-Encoding">>, <<"gzip">>} + | CustomHeaders], + FileContents} end. find_header(Header, Headers, Default) -> case lists:keysearch(Header, 1, Headers) of - {value, {_, Value}} -> Value; - false -> Default + {value, {_, Value}} -> Value; + false -> Default end. %%---------------------------------------------------------------------- %% Utilities %%---------------------------------------------------------------------- -content_type(Filename, DefaultContentType, ContentTypes) -> - Extension = ?STRING2LOWER:to_lower(filename:extension(Filename)), +content_type(Filename, DefaultContentType, + ContentTypes) -> + Extension = + str:to_lower(filename:extension(Filename)), case lists:keysearch(Extension, 1, ContentTypes) of - {value, {_, ContentType}} -> ContentType; - false -> DefaultContentType + {value, {_, ContentType}} -> ContentType; + false -> DefaultContentType end. last_modified(FileInfo) -> |