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
instead of a regular file, those directory indices are looked in
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
the local directory <CODE>/var/www</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:
</P><PRE CLASS="verbatim">{modules,
[
...
{mod_http_fileserver, [
{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
instead of a regular file, those directory indices are looked in
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}
This example configuration will serve the files from
the local directory \verb|/var/www|
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:
\begin{verbatim}
{modules,
@ -2800,8 +2810,13 @@ To use this module you must enable it:
...
{mod_http_fileserver, [
{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).
-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).
@ -80,6 +81,19 @@
-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"},
{".txt", "text/plain"},
{".xpi", "application/x-xpinstall"},
{".xul", "application/vnd.mozilla.xul+xml"}]).
-compile(export_all).
%%====================================================================
@ -91,7 +105,7 @@ start(Host, Opts) ->
ChildSpec =
{Proc,
{?MODULE, start_link, [Host, Opts]},
temporary,
transient, % if process crashes abruptly, it gets restarted
1000,
worker,
[?MODULE]},
@ -126,12 +140,15 @@ start_link(Host, Opts) ->
%%--------------------------------------------------------------------
init([Host, Opts]) ->
try initialize(Host, Opts) of
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices} ->
{DocRoot, AccessLog, AccessLogFD, DirectoryIndices,
DefaultContentType, ContentTypes} ->
{ok, #state{host = Host,
accesslog = AccessLog,
accesslogfd = AccessLogFD,
docroot = DocRoot,
directory_indices = DirectoryIndices}}
directory_indices = DirectoryIndices,
default_content_type = DefaultContentType,
content_types = ContentTypes}}
catch
throw:Reason ->
{stop, Reason}
@ -146,7 +163,23 @@ initialize(Host, Opts) ->
AccessLog = gen_mod:get_opt(accesslog, Opts, undefined),
AccessLogFD = try_open_log(AccessLog, Host),
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) ->
case DocRoot of
@ -196,7 +229,8 @@ try_open_log(FN, Host) ->
%% Description: Handling call messages
%%--------------------------------------------------------------------
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};
handle_call(_Request, _From, State) ->
{reply, ok, State}.
@ -263,36 +297,42 @@ process(LocalPath, Request) ->
ejabberd_web:error(not_found)
end.
serve(LocalPath, DocRoot, DirectoryIndices) ->
serve(LocalPath, DocRoot, DirectoryIndices, DefaultContentType, ContentTypes) ->
FileName = filename:join(filename:split(DocRoot) ++ LocalPath),
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);
{ok, FileInfo} -> serve_file(FileInfo, FileName)
{ok, #file_info{type = directory}} -> serve_index(FileName,
DirectoryIndices,
DefaultContentType,
ContentTypes);
{ok, FileInfo} -> serve_file(FileInfo, FileName,
DefaultContentType,
ContentTypes)
end.
%% Troll through the directory indices attempting to find one which
%% works, if none can be found, return a 404.
serve_index(_FileName, []) ->
serve_index(_FileName, [], _DefaultContentType, _ContentTypes) ->
?HTTP_ERR_FILE_NOT_FOUND;
serve_index(FileName, [Index | T]) ->
serve_index(FileName, [Index | T], DefaultContentType, ContentTypes) ->
IndexFileName = filename:join([FileName] ++ [Index]),
case file:read_file_info(IndexFileName) of
{error, _Error} -> serve_index(FileName, T);
{ok, #file_info{type = directory}} -> serve_index(FileName, T);
{ok, FileInfo} -> serve_file(FileInfo, IndexFileName)
{error, _Error} -> serve_index(FileName, T, DefaultContentType, ContentTypes);
{ok, #file_info{type = directory}} -> serve_index(FileName, T, DefaultContentType, ContentTypes);
{ok, FileInfo} -> serve_file(FileInfo, IndexFileName, DefaultContentType, ContentTypes)
end.
%% Assume the file exists if we got this far and attempt to read it in
%% and serve it up.
serve_file(FileInfo, FileName) ->
serve_file(FileInfo, FileName, DefaultContentType, ContentTypes) ->
?DEBUG("Delivering: ~s", [FileName]),
{ok, FileContents} = file:read_file(FileName),
ContentType = content_type(FileName, DefaultContentType, ContentTypes),
{FileInfo#file_info.size,
200, [{"Server", "ejabberd"},
{"Last-Modified", last_modified(FileInfo)},
{"Content-Type", content_type(FileName)}],
{"Content-Type", ContentType}],
FileContents}.
%%----------------------------------------------------------------------
@ -369,20 +409,11 @@ join([E], _) ->
join([H | T], Separator) ->
lists:foldl(fun(E, Acc) -> lists:concat([Acc, Separator, E]) end, H, T).
content_type(Filename) ->
case ?STRING2LOWER:to_lower(filename:extension(Filename)) of
".jpg" -> "image/jpeg";
".jpeg" -> "image/jpeg";
".gif" -> "image/gif";
".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"
content_type(Filename, DefaultContentType, ContentTypes) ->
Extension = ?STRING2LOWER:to_lower(filename:extension(Filename)),
case lists:keysearch(Extension, 1, ContentTypes) of
{value, {_, ContentType}} -> ContentType;
false -> DefaultContentType
end.
last_modified(FileInfo) ->