home *** CD-ROM | disk | FTP | other *** search
/ PC Professionell 2004 December / PCpro_2004_12.ISO / files / webserver / tsw / TSW_3.4.0.exe / Apache2 / python / util.py < prev    next >
Encoding:
Python Source  |  2004-02-16  |  12.1 KB  |  386 lines

  1.  #
  2.  # Copyright 2004 Apache Software Foundation 
  3.  # 
  4.  # Licensed under the Apache License, Version 2.0 (the "License"); you
  5.  # may not use this file except in compliance with the License.  You
  6.  # may obtain a copy of the License at
  7.  #
  8.  #      http://www.apache.org/licenses/LICENSE-2.0
  9.  #
  10.  # Unless required by applicable law or agreed to in writing, software
  11.  # distributed under the License is distributed on an "AS IS" BASIS,
  12.  # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
  13.  # implied.  See the License for the specific language governing
  14.  # permissions and limitations under the License.
  15.  #
  16.  # Originally developed by Gregory Trubetskoy.
  17.  #
  18.  # $Id: util.py,v 1.21 2004/02/16 19:47:27 grisha Exp $
  19.  
  20. import _apache
  21. import apache
  22. import cStringIO
  23. import tempfile
  24.  
  25. from types import *
  26. from exceptions import *
  27.  
  28. parse_qs = _apache.parse_qs
  29. parse_qsl = _apache.parse_qsl
  30.  
  31. """ The classes below are a (almost) a drop-in replacement for the
  32.     standard cgi.py FieldStorage class. They should have pretty much the
  33.     same functionality.
  34.  
  35.     These classes differ in that unlike cgi.FieldStorage, they are not
  36.     recursive. The class FieldStorage contains a list of instances of
  37.     Field class. Field class is incapable of storing anything in it.
  38.  
  39.     These objects should be considerably faster than the ones in cgi.py
  40.     because they do not expect CGI environment, and are
  41.     optimized specifically for Apache and mod_python.
  42. """
  43.  
  44. class Field:
  45.  
  46.     filename = None
  47.     headers = {}
  48.  
  49.     def __init__(self, name, file, ctype, type_options,
  50.                  disp, disp_options):
  51.         self.name = name
  52.         self.file = file
  53.         self.type = ctype
  54.         self.type_options = type_options
  55.         self.disposition = disp
  56.         self.disposition_options = disp_options
  57.  
  58.     def __repr__(self):
  59.         """Return printable representation."""
  60.         return "Field(%s, %s)" % (`self.name`, `self.value`)
  61.  
  62.     def __getattr__(self, name):
  63.         if name != 'value':
  64.             raise AttributeError, name
  65.         if self.file:
  66.             self.file.seek(0)
  67.             value = self.file.read()
  68.             self.file.seek(0)
  69.         else:
  70.             value = None
  71.         return value
  72.  
  73.     def __del__(self):
  74.         self.file.close()
  75.  
  76. class StringField(str):
  77.     """ This class is basically a string with
  78.     a value attribute for compatibility with std lib cgi.py
  79.     """
  80.     
  81.     def __init__(self, str=""):
  82.         str.__init__(self, str)
  83.         self.value = self.__str__()
  84.  
  85. class FieldStorage:
  86.  
  87.     def __init__(self, req, keep_blank_values=0, strict_parsing=0):
  88.  
  89.         self.list = []
  90.  
  91.         # always process GET-style parameters
  92.         if req.args:
  93.             pairs = parse_qsl(req.args, keep_blank_values)
  94.             for pair in pairs:
  95.                 file = cStringIO.StringIO(pair[1])
  96.                 self.list.append(Field(pair[0], file, "text/plain", {},
  97.                                        None, {}))
  98.  
  99.         if req.method == "POST":
  100.  
  101.             try:
  102.                 clen = int(req.headers_in["content-length"])
  103.             except (KeyError, ValueError):
  104.                 # absent content-length is not acceptable
  105.                 raise apache.SERVER_RETURN, apache.HTTP_LENGTH_REQUIRED
  106.  
  107.             if not req.headers_in.has_key("content-type"):
  108.                 ctype = "application/x-www-form-urlencoded"
  109.             else:
  110.                 ctype = req.headers_in["content-type"]
  111.  
  112.             if ctype == "application/x-www-form-urlencoded":
  113.                 
  114.                 pairs = parse_qsl(req.read(clen), keep_blank_values)
  115.                 for pair in pairs:
  116.                     file = cStringIO.StringIO(pair[1])
  117.                     self.list.append(Field(pair[0], file, "text/plain",
  118.                                      {}, None, {}))
  119.  
  120.             elif ctype[:10] == "multipart/":
  121.  
  122.                 # figure out boundary
  123.                 try:
  124.                     i = ctype.lower().rindex("boundary=")
  125.                     boundary = ctype[i+9:]
  126.                     if len(boundary) >= 2 and boundary[0] == boundary[-1] == '"':
  127.                         boundary = boundary[1:-1]
  128.                     boundary = "--" + boundary
  129.                 except ValueError:
  130.                     raise apache.SERVER_RETURN, apache.HTTP_BAD_REQUEST
  131.  
  132.                 #read until boundary
  133.                 line = req.readline()
  134.                 sline = line.strip()
  135.                 while line and sline != boundary:
  136.                     line = req.readline()
  137.                     sline = line.strip()
  138.  
  139.                 while 1:
  140.  
  141.                     ## parse headers
  142.                     
  143.                     ctype, type_options = "text/plain", {}
  144.                     disp, disp_options = None, {}
  145.                     headers = apache.make_table()
  146.  
  147.                     line = req.readline()
  148.                     sline = line.strip()
  149.                     if not line or sline == (boundary + "--"):
  150.                         break
  151.                     
  152.                     while line and line not in ["\n", "\r\n"]:
  153.                         h, v = line.split(":", 1)
  154.                         headers.add(h, v)
  155.                         h = h.lower()
  156.                         if h == "content-disposition":
  157.                             disp, disp_options = parse_header(v)
  158.                         elif h == "content-type":
  159.                             ctype, type_options = parse_header(v)
  160.                         line = req.readline()
  161.                         sline = line.strip()
  162.  
  163.                     if disp_options.has_key("name"):
  164.                         name = disp_options["name"]
  165.                     else:
  166.                         name = None
  167.  
  168.                     # is this a file?
  169.                     if disp_options.has_key("filename"):
  170.                         file = self.make_file()
  171.                     else:
  172.                         file = cStringIO.StringIO()
  173.  
  174.                     # read it in
  175.                     self.read_to_boundary(req, boundary, file)
  176.                     file.seek(0)
  177.  
  178.                     # make a Field
  179.                     field = Field(name, file, ctype, type_options,
  180.                                   disp, disp_options)
  181.                     field.headers = headers
  182.                     if disp_options.has_key("filename"):
  183.                         field.filename = disp_options["filename"]
  184.  
  185.                     self.list.append(field)
  186.  
  187.             else:
  188.                 # we don't understand this content-type
  189.                 raise apache.SERVER_RETURN, apache.HTTP_NOT_IMPLEMENTED
  190.  
  191.  
  192.     def make_file(self):
  193.         return tempfile.TemporaryFile("w+b")
  194.  
  195.     def skip_to_boundary(self, req, boundary):
  196.         line = req.readline()
  197.         sline = line.strip()
  198.         last_bound = boundary + "--"
  199.         while line and sline != boundary and sline != last_bound:
  200.             line = req.readline()
  201.             sline = line.strip()
  202.  
  203.     def read_to_boundary(self, req, boundary, file):
  204.         delim = ""
  205.         line = req.readline()
  206.         sline = line.strip()
  207.         last_bound = boundary + "--"
  208.         while line and sline != boundary and sline != last_bound:
  209.             odelim = delim
  210.             if line[-2:] == "\r\n":
  211.                 delim = "\r\n"
  212.                 line = line[:-2]
  213.             elif line[-1:] == "\n":
  214.                 delim = "\n"
  215.                 line = line[:-1]
  216.             file.write(odelim + line)
  217.             line = req.readline()
  218.             sline = line.strip()
  219.  
  220.     def __getitem__(self, key):
  221.         """Dictionary style indexing."""
  222.         if self.list is None:
  223.             raise TypeError, "not indexable"
  224.         found = []
  225.         for item in self.list:
  226.             if item.name == key:
  227.                 if isinstance(item.file, FileType):
  228.                     found.append(item)
  229.                 else:
  230.                     found.append(StringField(item.value))
  231.         if not found:
  232.             raise KeyError, key
  233.         if len(found) == 1:
  234.             return found[0]
  235.         else:
  236.             return found
  237.  
  238.     def get(self, key, default):
  239.         try:
  240.             return self.__getitem__(key)
  241.         except KeyError:
  242.             return default
  243.  
  244.     def keys(self):
  245.         """Dictionary style keys() method."""
  246.         if self.list is None:
  247.             raise TypeError, "not indexable"
  248.         keys = []
  249.         for item in self.list:
  250.             if item.name not in keys: keys.append(item.name)
  251.         return keys
  252.  
  253.     def has_key(self, key):
  254.         """Dictionary style has_key() method."""
  255.         if self.list is None:
  256.             raise TypeError, "not indexable"
  257.         for item in self.list:
  258.             if item.name == key: return 1
  259.         return 0
  260.  
  261.     __contains__ = has_key
  262.  
  263.     def __len__(self):
  264.         """Dictionary style len(x) support."""
  265.         return len(self.keys())
  266.  
  267.     def getfirst(self, key, default=None):
  268.         """ return the first value received """
  269.         for item in self.list:
  270.             if item.name == key:
  271.                 if isinstance(item.file, FileType):
  272.                     return item
  273.                 else:
  274.                     return StringField(item.value)
  275.         return default
  276.                                                                     
  277.     def getlist(self, key):
  278.         """ return a list of received values """
  279.         if self.list is None:
  280.             raise TypeError, "not indexable"
  281.         found = []
  282.         for item in self.list:
  283.             if item.name == key:
  284.                 if isinstance(item.file, FileType):
  285.                     found.append(item)
  286.                 else:
  287.                     found.append(StringField(item.value))
  288.         return found
  289.  
  290. def parse_header(line):
  291.     """Parse a Content-type like header.
  292.  
  293.     Return the main content-type and a dictionary of options.
  294.  
  295.     """
  296.     
  297.     plist = map(lambda a: a.strip(), line.split(';'))
  298.     key = plist[0].lower()
  299.     del plist[0]
  300.     pdict = {}
  301.     for p in plist:
  302.         i = p.find('=')
  303.         if i >= 0:
  304.             name = p[:i].strip().lower()
  305.             value = p[i+1:].strip()
  306.             if len(value) >= 2 and value[0] == value[-1] == '"':
  307.                 value = value[1:-1]
  308.             pdict[name] = value
  309.     return key, pdict
  310.  
  311. def apply_fs_data(object, fs, **args):
  312.     """
  313.     Apply FieldStorage data to an object - the object must be
  314.     callable. Examine the args, and match then with fs data,
  315.     then call the object, return the result.
  316.     """
  317.  
  318.     # add form data to args
  319.     for field in fs.list:
  320.         if field.filename:
  321.             val = field
  322.         else:
  323.             val = field.value
  324.         args.setdefault(field.name, []).append(val)
  325.  
  326.     # replace lists with single values
  327.     for arg in args:
  328.         if ((type(args[arg]) is ListType) and
  329.             (len(args[arg]) == 1)):
  330.             args[arg] = args[arg][0]
  331.  
  332.     # we need to weed out unexpected keyword arguments
  333.     # and for that we need to get a list of them. There
  334.     # are a few options for callable objects here:
  335.  
  336.     if type(object) is InstanceType:
  337.         # instances are callable when they have __call__()
  338.         object = object.__call__
  339.  
  340.     expected = []
  341.     if hasattr(object, "func_code"):
  342.         # function
  343.         fc = object.func_code
  344.         expected = fc.co_varnames[0:fc.co_argcount]
  345.     elif hasattr(object, 'im_func'):
  346.         # method
  347.         fc = object.im_func.func_code
  348.         expected = fc.co_varnames[1:fc.co_argcount]
  349.     elif type(object) is ClassType:
  350.         # class
  351.         fc = object.__init__.im_func.func_code
  352.         expected = fc.co_varnames[1:fc.co_argcount]
  353.  
  354.     # remove unexpected args unless co_flags & 0x08,
  355.     # meaning function accepts **kw syntax
  356.     if not (fc.co_flags & 0x08):
  357.         for name in args.keys():
  358.             if name not in expected:
  359.                 del args[name]
  360.  
  361.     return object(**args)
  362.  
  363. def redirect(req, location, permanent=0, text=None):
  364.     """
  365.     A convenience function to provide redirection
  366.     """
  367.  
  368.     if req.sent_bodyct:
  369.         raise IOError, "Cannot redirect after headers have already been sent."
  370.  
  371.     req.err_headers_out["Location"] = location
  372.     if permanent:
  373.         req.status = apache.HTTP_MOVED_PERMANENTLY
  374.     else:
  375.         req.status = apache.HTTP_MOVED_TEMPORARILY
  376.  
  377.     if text is None:
  378.         req.write('<p>The document has moved' 
  379.                   ' <a href="%s">here</a></p>\n'
  380.                   % location)
  381.     else:
  382.         req.write(text)
  383.  
  384.     raise apache.SERVER_RETURN, apache.OK
  385.  
  386.