Allow content types to be configured in ejabberd.cfg (EJAB-975)(thanks to Brian Cully)

SVN Revision: 2376
This commit is contained in:
Badlop 2009-07-21 17:31:09 +00:00
parent 46304da5d7
commit 5346a7df02
3 changed files with 95 additions and 34 deletions

View File

@ -2111,17 +2111,32 @@ Indicate one or more directory index files, similarly to Apache’s
DirectoryIndex variable. When a web request hits a directory DirectoryIndex variable. When a web request hits a directory
instead of a regular file, those directory indices are looked in instead of a regular file, those directory indices are looked in
order, and the first one found is returned. order, and the first one found is returned.
</DD><DT CLASS="dt-description"><B><TT>content_types</TT></B></DT><DD CLASS="dd-description">
Specify a mapping of extensions to content types.
There are several content types already defined,
with this option you can add new definitions, modify or delete existing ones.
To delete an existing definition, simply define it with a value: &#X2018;undefined&#X2019;.
</DD><DT CLASS="dt-description"><B><TT>default_content_type</TT></B></DT><DD CLASS="dd-description">
Specify the content type to use for unknown extensions.
Default value is &#X2018;application/octet-stream&#X2019;.
</DD></DL><P>This example configuration will serve the files from </DD></DL><P>This example configuration will serve the files from
the local directory <CODE>/var/www</CODE> the local directory <CODE>/var/www</CODE>
in the address <CODE>http://example.org:5280/pub/archive/</CODE>. in the address <CODE>http://example.org:5280/pub/archive/</CODE>.
In this example a new content type <TT>ogg</TT> is defined,
<TT>png</TT> is redefined, and <TT>jpg</TT> definition is deleted.
To use this module you must enable it: To use this module you must enable it:
</P><PRE CLASS="verbatim">{modules, </P><PRE CLASS="verbatim">{modules,
[ [
... ...
{mod_http_fileserver, [ {mod_http_fileserver, [
{docroot, "/var/www"}, {docroot, "/var/www"},
{directory_indices, ["index.html", "main.htm"]}, {accesslog, "/var/log/ejabberd/access.log"},
{accesslog, "/var/log/ejabberd/access.log"} {directory_indices, ["index.html", "main.htm"]},
{content_types, [{".ogg", "audio/ogg"},
{".png", "image/png"},
{".jpg", undefined}
]},
{default_content_type, "text/html"}
] ]
}, },
... ...

View File

@ -2788,11 +2788,21 @@ Options:
DirectoryIndex variable. When a web request hits a directory DirectoryIndex variable. When a web request hits a directory
instead of a regular file, those directory indices are looked in instead of a regular file, those directory indices are looked in
order, and the first one found is returned. order, and the first one found is returned.
\titem{content\_types} \ind{options!contenttypes}
Specify a mapping of extensions to content types.
There are several content types already defined,
with this option you can add new definitions, modify or delete existing ones.
To delete an existing definition, simply define it with a value: `undefined'.
\titem{default\_content\_type} \ind{options!defaultcontenttype}
Specify the content type to use for unknown extensions.
Default value is `application/octet-stream'.
\end{description} \end{description}
This example configuration will serve the files from This example configuration will serve the files from
the local directory \verb|/var/www| the local directory \verb|/var/www|
in the address \verb|http://example.org:5280/pub/archive/|. in the address \verb|http://example.org:5280/pub/archive/|.
In this example a new content type \term{ogg} is defined,
\term{png} is redefined, and \term{jpg} definition is deleted.
To use this module you must enable it: To use this module you must enable it:
\begin{verbatim} \begin{verbatim}
{modules, {modules,
@ -2800,8 +2810,13 @@ To use this module you must enable it:
... ...
{mod_http_fileserver, [ {mod_http_fileserver, [
{docroot, "/var/www"}, {docroot, "/var/www"},
{directory_indices, ["index.html", "main.htm"]}, {accesslog, "/var/log/ejabberd/access.log"},
{accesslog, "/var/log/ejabberd/access.log"} {directory_indices, ["index.html", "main.htm"]},
{content_types, [{".ogg", "audio/ogg"},
{".png", "image/png"},
{".jpg", undefined}
]},
{default_content_type, "text/html"}
] ]
}, },
... ...

View File

@ -72,7 +72,8 @@
-define(STRING2LOWER, httpd_util). -define(STRING2LOWER, httpd_util).
-endif. -endif.
-record(state, {host, docroot, accesslog, accesslogfd, directory_indices}). -record(state, {host, docroot, accesslog, accesslogfd, directory_indices,
default_content_type, content_types = []}).
-define(PROCNAME, ejabberd_mod_http_fileserver). -define(PROCNAME, ejabberd_mod_http_fileserver).
@ -80,6 +81,19 @@
-define(HTTP_ERR_FILE_NOT_FOUND, {-1, 404, [], "Not found"}). -define(HTTP_ERR_FILE_NOT_FOUND, {-1, 404, [], "Not found"}).
-define(HTTP_ERR_FORBIDDEN, {-1, 403, [], "Forbidden"}). -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"},
{".txt", "text/plain"},
{".xpi", "application/x-xpinstall"},
{".xul", "application/vnd.mozilla.xul+xml"}]).
-compile(export_all). -compile(export_all).
%%==================================================================== %%====================================================================
@ -91,7 +105,7 @@ start(Host, Opts) ->
ChildSpec = ChildSpec =
{Proc, {Proc,
{?MODULE, start_link, [Host, Opts]}, {?MODULE, start_link, [Host, Opts]},
temporary, transient, % if process crashes abruptly, it gets restarted
1000, 1000,
worker, worker,
[?MODULE]}, [?MODULE]},
@ -126,12 +140,15 @@ start_link(Host, Opts) ->
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
init([Host, Opts]) -> init([Host, Opts]) ->
try initialize(Host, Opts) of try initialize(Host, Opts) of
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices} -> {DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
DefaultContentType, ContentTypes} ->
{ok, #state{host = Host, {ok, #state{host = Host,
accesslog = AccessLog, accesslog = AccessLog,
accesslogfd = AccessLogFD, accesslogfd = AccessLogFD,
docroot = DocRoot, docroot = DocRoot,
directory_indices = DirectoryIndices}} directory_indices = DirectoryIndices,
default_content_type = DefaultContentType,
content_types = ContentTypes}}
catch catch
throw:Reason -> throw:Reason ->
{stop, Reason} {stop, Reason}
@ -146,7 +163,23 @@ initialize(Host, Opts) ->
AccessLog = gen_mod:get_opt(accesslog, Opts, undefined), AccessLog = gen_mod:get_opt(accesslog, Opts, undefined),
AccessLogFD = try_open_log(AccessLog, Host), AccessLogFD = try_open_log(AccessLog, Host),
DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, []), DirectoryIndices = gen_mod:get_opt(directory_indices, Opts, []),
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices}. DefaultContentType = gen_mod:get_opt(default_content_type, Opts,
?DEFAULT_CONTENT_TYPE),
ContentTypes = build_list_content_types(gen_mod:get_opt(content_types, Opts, []), ?DEFAULT_CONTENT_TYPES),
?INFO_MSG("initialize: ~n ~p", [ContentTypes]),%+++
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
DefaultContentType, ContentTypes}.
%% @spec (AdminCTs::[CT], Default::[CT]) -> [CT]
%% where CT = {Extension::string(), Value}
%% Value = string() | undefined
%% Returns a unified list without duplicates where elements of AdminCTs have more priority.
%% If a CT is declared as 'undefined', then it is not included in the result.
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].
check_docroot_defined(DocRoot, Host) -> check_docroot_defined(DocRoot, Host) ->
case DocRoot of case DocRoot of
@ -196,7 +229,8 @@ try_open_log(FN, Host) ->
%% Description: Handling call messages %% Description: Handling call messages
%%-------------------------------------------------------------------- %%--------------------------------------------------------------------
handle_call({serve, LocalPath}, _From, State) -> handle_call({serve, LocalPath}, _From, State) ->
Reply = serve(LocalPath, State#state.docroot, State#state.directory_indices), Reply = serve(LocalPath, State#state.docroot, State#state.directory_indices,
State#state.default_content_type, State#state.content_types),
{reply, Reply, State}; {reply, Reply, State};
handle_call(_Request, _From, State) -> handle_call(_Request, _From, State) ->
{reply, ok, State}. {reply, ok, State}.
@ -263,36 +297,42 @@ process(LocalPath, Request) ->
ejabberd_web:error(not_found) ejabberd_web:error(not_found)
end. end.
serve(LocalPath, DocRoot, DirectoryIndices) -> serve(LocalPath, DocRoot, DirectoryIndices, DefaultContentType, ContentTypes) ->
FileName = filename:join(filename:split(DocRoot) ++ LocalPath), FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
case file:read_file_info(FileName) of case file:read_file_info(FileName) of
{error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND; {error, enoent} -> ?HTTP_ERR_FILE_NOT_FOUND;
{error, eacces} -> ?HTTP_ERR_FORBIDDEN; {error, eacces} -> ?HTTP_ERR_FORBIDDEN;
{ok, #file_info{type = directory}} -> serve_index(FileName, DirectoryIndices); {ok, #file_info{type = directory}} -> serve_index(FileName,
{ok, FileInfo} -> serve_file(FileInfo, FileName) DirectoryIndices,
DefaultContentType,
ContentTypes);
{ok, FileInfo} -> serve_file(FileInfo, FileName,
DefaultContentType,
ContentTypes)
end. end.
%% Troll through the directory indices attempting to find one which %% Troll through the directory indices attempting to find one which
%% works, if none can be found, return a 404. %% works, if none can be found, return a 404.
serve_index(_FileName, []) -> serve_index(_FileName, [], _DefaultContentType, _ContentTypes) ->
?HTTP_ERR_FILE_NOT_FOUND; ?HTTP_ERR_FILE_NOT_FOUND;
serve_index(FileName, [Index | T]) -> serve_index(FileName, [Index | T], DefaultContentType, ContentTypes) ->
IndexFileName = filename:join([FileName] ++ [Index]), IndexFileName = filename:join([FileName] ++ [Index]),
case file:read_file_info(IndexFileName) of case file:read_file_info(IndexFileName) of
{error, _Error} -> serve_index(FileName, T); {error, _Error} -> serve_index(FileName, T, DefaultContentType, ContentTypes);
{ok, #file_info{type = directory}} -> serve_index(FileName, T); {ok, #file_info{type = directory}} -> serve_index(FileName, T, DefaultContentType, ContentTypes);
{ok, FileInfo} -> serve_file(FileInfo, IndexFileName) {ok, FileInfo} -> serve_file(FileInfo, IndexFileName, DefaultContentType, ContentTypes)
end. end.
%% Assume the file exists if we got this far and attempt to read it in %% Assume the file exists if we got this far and attempt to read it in
%% and serve it up. %% and serve it up.
serve_file(FileInfo, FileName) -> serve_file(FileInfo, FileName, DefaultContentType, ContentTypes) ->
?DEBUG("Delivering: ~s", [FileName]), ?DEBUG("Delivering: ~s", [FileName]),
{ok, FileContents} = file:read_file(FileName), {ok, FileContents} = file:read_file(FileName),
ContentType = content_type(FileName, DefaultContentType, ContentTypes),
{FileInfo#file_info.size, {FileInfo#file_info.size,
200, [{"Server", "ejabberd"}, 200, [{"Server", "ejabberd"},
{"Last-Modified", last_modified(FileInfo)}, {"Last-Modified", last_modified(FileInfo)},
{"Content-Type", content_type(FileName)}], {"Content-Type", ContentType}],
FileContents}. FileContents}.
%%---------------------------------------------------------------------- %%----------------------------------------------------------------------
@ -369,20 +409,11 @@ join([E], _) ->
join([H | T], Separator) -> join([H | T], Separator) ->
lists:foldl(fun(E, Acc) -> lists:concat([Acc, Separator, E]) end, H, T). lists:foldl(fun(E, Acc) -> lists:concat([Acc, Separator, E]) end, H, T).
content_type(Filename) -> content_type(Filename, DefaultContentType, ContentTypes) ->
case ?STRING2LOWER:to_lower(filename:extension(Filename)) of Extension = ?STRING2LOWER:to_lower(filename:extension(Filename)),
".jpg" -> "image/jpeg"; case lists:keysearch(Extension, 1, ContentTypes) of
".jpeg" -> "image/jpeg"; {value, {_, ContentType}} -> ContentType;
".gif" -> "image/gif"; false -> DefaultContentType
".png" -> "image/png";
".html" -> "text/html";
".css" -> "text/css";
".txt" -> "text/plain";
".xul" -> "application/vnd.mozilla.xul+xml";
".jar" -> "application/java-archive";
".xpi" -> "application/x-xpinstall";
".js" -> "application/x-javascript";
_Else -> "application/octet-stream"
end. end.
last_modified(FileInfo) -> last_modified(FileInfo) ->