diff --git a/doc/guide.html b/doc/guide.html index 2d1fb8eaa..13e104e96 100644 --- a/doc/guide.html +++ b/doc/guide.html @@ -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. +
content_types
+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’. +
default_content_type
+Specify the content type to use for unknown extensions. +Default value is ‘application/octet-stream’.

This example configuration will serve the files from the local directory /var/www in the address http://example.org:5280/pub/archive/. +In this example a new content type ogg is defined, +png is redefined, and jpg definition is deleted. To use this module you must enable it:

{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"}
                         ]
   },
   ...
diff --git a/doc/guide.tex b/doc/guide.tex
index 7c8466eed..ffbc24181 100644
--- a/doc/guide.tex
+++ b/doc/guide.tex
@@ -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"}
                         ]
   },
   ...
diff --git a/src/web/mod_http_fileserver.erl b/src/web/mod_http_fileserver.erl
index 5d5eebb7f..21dd6fddf 100644
--- a/src/web/mod_http_fileserver.erl
+++ b/src/web/mod_http_fileserver.erl
@@ -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) ->