mirror of
https://github.com/processone/ejabberd.git
synced 2024-11-26 16:26:24 +01:00
Allow content types to be configured in ejabberd.cfg (EJAB-975)(thanks to Brian Cully)
SVN Revision: 2377
This commit is contained in:
parent
338af10aaf
commit
97ee31e751
@ -2102,17 +2102,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: ‘undefined’.
|
||||||
|
</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 ‘application/octet-stream’.
|
||||||
</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"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
...
|
...
|
||||||
|
@ -2775,11 +2775,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,
|
||||||
@ -2787,8 +2797,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"}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
...
|
...
|
||||||
|
@ -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
|
||||||
@ -183,7 +216,8 @@ try_open_log(FN, Host) ->
|
|||||||
?ERROR_MSG("Cannot open access log file: ~p~nReason: ~p", [FN, Reason]),
|
?ERROR_MSG("Cannot open access log file: ~p~nReason: ~p", [FN, Reason]),
|
||||||
undefined
|
undefined
|
||||||
end,
|
end,
|
||||||
ejabberd_hooks:add(reopen_log_hook, Host, ?MODULE, reopen_log, 50),
|
HostB = list_to_binary(Host),
|
||||||
|
ejabberd_hooks:add(reopen_log_hook, HostB, ?MODULE, reopen_log, 50),
|
||||||
FD.
|
FD.
|
||||||
|
|
||||||
%%--------------------------------------------------------------------
|
%%--------------------------------------------------------------------
|
||||||
@ -196,7 +230,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 +298,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 +410,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) ->
|
||||||
|
Loading…
Reference in New Issue
Block a user