[Medusa-dev] Autoindex

SergioFernández sfmunoz@teleline.es
Sun Dec 1 12:56:03 2002


Hello:

I've added autoindex support to Medusa Web Server. Autoindex shows you the
content of the directory you are looking for in a web server when there's
no index.html/default.html file in that directory.

Medusa returned a '404 Not Found' code when there were no
index.html/default.html file in the directory. I didn't like it and I
wanted to see what content that directory had. So I added autoindex feature
to Medusa.

Medusa autoindex features:

    - Very similar to that produced by default Apache autoindex feature
      (although this patch doesn't produce graphic icons).
    - It shows file name, last modified, size and type fields.
    - You can order (asc/desc) the list using any displayed field (name,
      last modified, size or type).

I've only changed default_handler.py file. I've tried to avoid the
modification of current code as far as I've been able to (in order to avoid
the introduction of bugs in working code). Changes I have made are:

    - Functions added to default_handler class:
        __get_params
        __format_size
        __format_entry
        __pad_entry
        __get_index_order
        __sort_output_entry_list
        autoindex
    - Substituted 'request.error(404) # Not Found' by
      'self.autoindex(request.path)' when index.html/default.html is not
      found (handle_request method in default_handler class). This is the
      link where autoindex is called.
    - 'import time' has been added because I needed time conversion tools.
    - request['Location'] calculation has been changed. This hasn't got
      anything to do with autoindex feature, but I prefer this new way to
      calculate it (read the end of the patch to understand the change).

I've developed this feature in SuSE 8.0/Python 2.2. I didn't tried it in
other platforms. If someone tries it, please tell me how it worked.

I've thinking about writting a derived class (autoindex_handler, for
example) to introduce autoindex feature.

Question to Medusa developers/maintainers: In order to include autoindex
feature in Medusa code (if you like it, of course)... do you want me to
write a derived class (autoindex_handler) or do you like the way I did
it (default_handler modification)? What do you prefer? What's better?

If this code is included in Medusa code, it lacks one think. Perhaps you
should include a boolean parameter in __init__ method (default_handler
class): autoindex, for example. If true, autoindex works. If false,
default_handler works as usual (returns 404).

Finally, here comes the patch.
Regards
Sergio

diff -urN medusa-cvs-26-11-2002.orig/default_handler.py medusa-cvs-26-11-2002/default_handler.py
--- medusa-cvs-26-11-2002.orig/default_handler.py	Thu Aug  1 20:15:45 2002
+++ medusa-cvs-26-11-2002/default_handler.py	Sun Dec  1 13:10:42 2002
@@ -18,6 +18,7 @@
 import http_server
 import status_handler
 import producers
+import time
 
 unquote = http_server.unquote
 
@@ -72,6 +73,285 @@
     def match (self, request):
         return 1
 
+    # get a dictionary from query parameters
+    def __get_params(self,request):
+        [path, params, query, fragment] = request.split_uri()
+        if query == None:
+            return {}
+        out = {}
+        query = string.split(query[1:],"&")
+        for input in query:
+            [key,value] = string.split(input,"=")
+            out[key] = value
+        return out
+
+    # format size to get a pretty output
+    def __format_size(self,input):
+        K = 1024.0
+        M = 1048576.0
+        T = 1073741824.0
+        unit = " "
+        if input >= T:
+            out = input/T
+            unit = "T"
+        elif input >= M:
+            out = input/M
+            unit = "M"
+        elif input >= K:
+            out = input/K
+            unit = "K"
+        else:
+            out = input
+            unit = " "
+
+        if out < 0.1:
+            output = "  0 "
+        elif out < 10:
+            output = "%1.1f%s" % (out,unit)
+        else:
+            output = "%3.0f%s" % (out,unit)
+
+        return output
+
+    # make entry sorter if it's so long.
+    def __format_entry (self,entry,size):
+        if len(entry) > size+3:
+            new_entry = entry[:size] + '..>'
+        else:
+            new_entry = entry[:]
+        return new_entry
+
+    # pad entry with spaces.
+    def __pad_entry (self,entry,size):
+        return ' '*(size-len(entry)+3)
+
+    # calculate what order user wants from input params.
+    def __get_index_order(self,request):
+        params = self.__get_params(request)
+        order = { \
+            'name' : 0, \
+            'last_modified' : 0, \
+            'size' : 0, \
+            'type' : 0 \
+        }
+
+        if params.has_key('C') and params.has_key('O'):
+            if params['C'] == 'N':
+                if params['O'] == 'A':
+                    order['name'] = 1
+                elif params['O'] == 'D':
+                    order['name'] = -1
+            elif params['C'] == 'M':
+                if params['O'] == 'A':
+                    order['last_modified'] = 1
+                elif params['O'] == 'D':
+                    order['last_modified'] = -1
+            elif params['C'] == 'S':
+                if params['O'] == 'A':
+                    order['size'] = 1
+                elif params['O'] == 'D':
+                    order['size'] = -1
+            elif params['C'] == 'T':
+                if params['O'] == 'A':
+                    order['type'] = 1
+                elif params['O'] == 'D':
+                    order['type'] = -1
+        else:
+            order['name'] = 1
+
+        return order
+
+    # sort output entry list (as order says)
+    def __sort_output_entry_list(self,list,order):
+        key_list = []
+        output_list = []
+        entry_dict = {}
+        for entry in list:
+            if order['name'] != 0:
+                key = "%s %20d %20d %s" % (
+                    entry['name'], \
+                    entry['last_modified'], \
+                    entry['size'], \
+                    entry['type'] \
+                )
+            elif order['last_modified'] != 0:
+                key = "%20d %s %20d %s" % ( \
+                    entry['last_modified'], \
+                    entry['name'], \
+                    entry['size'], \
+                    entry['type'] \
+                )
+            elif order['size'] != 0:
+                key = "%20d %2d %s %20d %s" % ( \
+                    entry['size'], \
+                    1-entry['is_dir'], \
+                    entry['name'], \
+                    entry['last_modified'], \
+                    entry['type'] \
+                )
+            elif order['type'] != 0:
+                key = "%s %s %20d %20d" % ( \
+                    entry['type'], \
+                    entry['name'], \
+                    entry['size'], \
+                    entry['last_modified'] \
+                )
+            else:
+                key = "%s %20d %20d %s" % ( \
+                    entry['name'], \
+                    entry['last_modified'], \
+                    entry['size'], \
+                    entry['type'] \
+                )
+
+            key_list.append(key)
+            entry_dict[key] = entry
+
+        key_list.sort()
+
+        order_sum = 0
+        for key in order.keys():
+            order_sum = order_sum + order[key]
+        if order_sum < 0:
+            key_list.reverse()
+
+        for key in key_list:
+            output_list.append(entry_dict[key])
+        return output_list
+
+    # autoindex composition
+    def autoindex (self, request, path):
+        if path and path[-1] == '/':
+            spath = path[:-1]
+        else:
+            spath = path[:]
+        order = self.__get_index_order(request)
+        NAME_SIZE = 20
+        TIME_FORMAT = "%d-%b-%Y %H:%M"
+        input_entry_list = self.filesystem.listdir(spath).list[:]
+        output_entry_list = []
+
+        for input_entry in input_entry_list:
+            full_input_entry = "%s/%s" % (spath,input_entry)
+            stat_input_entry = self.filesystem.stat(full_input_entry)
+
+            output_entry = {}
+
+            if self.filesystem.isdir(full_input_entry):
+                output_entry['is_dir'] = 1
+            else:
+                output_entry['is_dir'] = 0
+
+            output_entry['name'] = input_entry
+
+            output_entry['last_modified'] = stat_input_entry[stat.ST_MTIME]
+
+            if output_entry['is_dir']:
+                output_entry['size'] = 0
+            else:
+                output_entry['size'] = int(stat_input_entry[stat.ST_SIZE])
+
+            if output_entry['is_dir']:
+                output_entry['type'] = '                     '
+            else:
+                typ, encoding = mimetypes.guess_type(full_input_entry)
+                if typ is None:
+                    typ = 'text/plain'
+                output_entry['type'] = typ
+
+            output_entry_list.append(output_entry)
+
+        output_entry_list = self.__sort_output_entry_list(
+            output_entry_list,
+            order
+        )
+
+        request['Content-Type'] = 'text/html'
+        request.push("<html>\n")
+        request.push("<head>\n")
+        request.push("<title>Index of /%s</title>\n" % spath)
+        request.push("</head>\n")
+        request.push("<body>\n")
+        request.push("<h1>Index of /%s</h1>\n" % spath)
+        request.push("<pre>\n")
+        request.push("    ")
+        if order['name'] == 1:
+            request.push("<a href=\"?C=N&O=D\">")
+        else:
+            request.push("<a href=\"?C=N&O=A\">")
+        request.push("Name</a> ")
+        request.push(self.__pad_entry("Name",NAME_SIZE))
+        if order['last_modified'] == 1:
+            request.push("<a href=\"?C=M&O=D\">")
+        else:
+            request.push("<a href=\"?C=M&O=A\">")
+        request.push("Last modified</a>      ")
+        if order['size'] == 1:
+            request.push("<a href=\"?C=S&O=D\">")
+        else:
+            request.push("<a href=\"?C=S&O=A\">")
+        request.push("Size</a>  ")
+        if order['type'] == 1:
+            request.push("<a href=\"?C=T&O=D\">")
+        else:
+            request.push("<a href=\"?C=T&O=A\">")
+        request.push("Type</a>")
+        request.push("\n")
+        request.push("<hr>\n")
+        if spath:
+            request.push("    <a href=\"..\">Parent directory</a>\n")
+
+        for output_entry in output_entry_list:
+            if output_entry['is_dir']:
+                output_entry_is_dir = "[D] "
+            else:
+                output_entry_is_dir = "    "
+
+            if output_entry['is_dir']:
+                output_entry_name_link = "%s/" % output_entry['name']
+            else:
+                output_entry_name_link = "%s" % output_entry['name']
+
+            output_entry_name_view = self.__format_entry( \
+                output_entry_name_link, \
+                NAME_SIZE \
+            )
+
+            output_entry_last_modified = time.strftime( \
+                TIME_FORMAT, \
+                time.localtime(output_entry['last_modified']) \
+            )
+
+            if output_entry['is_dir']:
+                output_entry_size = '  - '
+            else:
+                output_entry_size = "%s" % self.__format_size(output_entry['size'])
+
+            if output_entry['is_dir']:
+                output_entry_type = '  - '
+            else:
+                output_entry_type = output_entry['type']
+
+            request.push ( \
+                "%s<a href=\"%s\">%s</a>%s %s  %s  %s\n" % ( \
+                    output_entry_is_dir, \
+                    output_entry_name_link, \
+                    output_entry_name_view, \
+                    self.__pad_entry(output_entry_name_view,NAME_SIZE), \
+                    output_entry_last_modified, \
+                    output_entry_size, \
+                    output_entry_type \
+                ) \
+            )
+
+        request.push("<hr>\n")
+        request.push("</pre>\n")
+        request.push("<address>Medusa/%s Server</address>\n" % http_server.VERSION_STRING)
+        request.push("</body>\n")
+        request.push("</html>\n")
+        request.done()
+        return
+
     # handle a file request, with caching.
 
     def handle_request (self, request):
@@ -93,8 +373,7 @@
 
         if self.filesystem.isdir (path):
             if path and path[-1] != '/':
-                request['Location'] = 'http://%s/%s/' % (
-                        request.channel.server.server_name,
+                request['Location'] = '/%s/' % (
                         path
                         )
                 request.error (301)
@@ -113,7 +392,7 @@
                     found = 1
                     break
             if not found:
-                request.error (404) # Not Found
+                self.autoindex(request,path)
                 return
 
         elif not self.filesystem.isfile (path):




More information about the Medusa-dev mailing list