eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/hgweb/common.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # hgweb/common.py - Utility functions needed by hgweb_mod and hgwebdir_mod
       
     2 #
       
     3 # Copyright 21 May 2005 - (c) 2005 Jake Edge <jake@edge2.net>
       
     4 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
       
     5 #
       
     6 # This software may be used and distributed according to the terms of the
       
     7 # GNU General Public License version 2 or any later version.
       
     8 
       
     9 import errno, mimetypes, os
       
    10 
       
    11 HTTP_OK = 200
       
    12 HTTP_NOT_MODIFIED = 304
       
    13 HTTP_BAD_REQUEST = 400
       
    14 HTTP_UNAUTHORIZED = 401
       
    15 HTTP_FORBIDDEN = 403
       
    16 HTTP_NOT_FOUND = 404
       
    17 HTTP_METHOD_NOT_ALLOWED = 405
       
    18 HTTP_SERVER_ERROR = 500
       
    19 
       
    20 # Hooks for hgweb permission checks; extensions can add hooks here. Each hook
       
    21 # is invoked like this: hook(hgweb, request, operation), where operation is
       
    22 # either read, pull or push. Hooks should either raise an ErrorResponse
       
    23 # exception, or just return.
       
    24 # It is possible to do both authentication and authorization through this.
       
    25 permhooks = []
       
    26 
       
    27 def checkauthz(hgweb, req, op):
       
    28     '''Check permission for operation based on request data (including
       
    29     authentication info). Return if op allowed, else raise an ErrorResponse
       
    30     exception.'''
       
    31 
       
    32     user = req.env.get('REMOTE_USER')
       
    33 
       
    34     deny_read = hgweb.configlist('web', 'deny_read')
       
    35     if deny_read and (not user or deny_read == ['*'] or user in deny_read):
       
    36         raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
       
    37 
       
    38     allow_read = hgweb.configlist('web', 'allow_read')
       
    39     result = (not allow_read) or (allow_read == ['*'])
       
    40     if not (result or user in allow_read):
       
    41         raise ErrorResponse(HTTP_UNAUTHORIZED, 'read not authorized')
       
    42 
       
    43     if op == 'pull' and not hgweb.allowpull:
       
    44         raise ErrorResponse(HTTP_UNAUTHORIZED, 'pull not authorized')
       
    45     elif op == 'pull' or op is None: # op is None for interface requests
       
    46         return
       
    47 
       
    48     # enforce that you can only push using POST requests
       
    49     if req.env['REQUEST_METHOD'] != 'POST':
       
    50         msg = 'push requires POST request'
       
    51         raise ErrorResponse(HTTP_METHOD_NOT_ALLOWED, msg)
       
    52 
       
    53     # require ssl by default for pushing, auth info cannot be sniffed
       
    54     # and replayed
       
    55     scheme = req.env.get('wsgi.url_scheme')
       
    56     if hgweb.configbool('web', 'push_ssl', True) and scheme != 'https':
       
    57         raise ErrorResponse(HTTP_OK, 'ssl required')
       
    58 
       
    59     deny = hgweb.configlist('web', 'deny_push')
       
    60     if deny and (not user or deny == ['*'] or user in deny):
       
    61         raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
       
    62 
       
    63     allow = hgweb.configlist('web', 'allow_push')
       
    64     result = allow and (allow == ['*'] or user in allow)
       
    65     if not result:
       
    66         raise ErrorResponse(HTTP_UNAUTHORIZED, 'push not authorized')
       
    67 
       
    68 # Add the default permhook, which provides simple authorization.
       
    69 permhooks.append(checkauthz)
       
    70 
       
    71 
       
    72 class ErrorResponse(Exception):
       
    73     def __init__(self, code, message=None, headers=[]):
       
    74         Exception.__init__(self)
       
    75         self.code = code
       
    76         self.headers = headers
       
    77         if message is not None:
       
    78             self.message = message
       
    79         else:
       
    80             self.message = _statusmessage(code)
       
    81 
       
    82 def _statusmessage(code):
       
    83     from BaseHTTPServer import BaseHTTPRequestHandler
       
    84     responses = BaseHTTPRequestHandler.responses
       
    85     return responses.get(code, ('Error', 'Unknown error'))[0]
       
    86 
       
    87 def statusmessage(code, message=None):
       
    88     return '%d %s' % (code, message or _statusmessage(code))
       
    89 
       
    90 def get_mtime(spath):
       
    91     cl_path = os.path.join(spath, "00changelog.i")
       
    92     if os.path.exists(cl_path):
       
    93         return os.stat(cl_path).st_mtime
       
    94     else:
       
    95         return os.stat(spath).st_mtime
       
    96 
       
    97 def staticfile(directory, fname, req):
       
    98     """return a file inside directory with guessed Content-Type header
       
    99 
       
   100     fname always uses '/' as directory separator and isn't allowed to
       
   101     contain unusual path components.
       
   102     Content-Type is guessed using the mimetypes module.
       
   103     Return an empty string if fname is illegal or file not found.
       
   104 
       
   105     """
       
   106     parts = fname.split('/')
       
   107     for part in parts:
       
   108         if (part in ('', os.curdir, os.pardir) or
       
   109             os.sep in part or os.altsep is not None and os.altsep in part):
       
   110             return ""
       
   111     fpath = os.path.join(*parts)
       
   112     if isinstance(directory, str):
       
   113         directory = [directory]
       
   114     for d in directory:
       
   115         path = os.path.join(d, fpath)
       
   116         if os.path.exists(path):
       
   117             break
       
   118     try:
       
   119         os.stat(path)
       
   120         ct = mimetypes.guess_type(path)[0] or "text/plain"
       
   121         req.respond(HTTP_OK, ct, length = os.path.getsize(path))
       
   122         return open(path, 'rb').read()
       
   123     except TypeError:
       
   124         raise ErrorResponse(HTTP_SERVER_ERROR, 'illegal filename')
       
   125     except OSError, err:
       
   126         if err.errno == errno.ENOENT:
       
   127             raise ErrorResponse(HTTP_NOT_FOUND)
       
   128         else:
       
   129             raise ErrorResponse(HTTP_SERVER_ERROR, err.strerror)
       
   130 
       
   131 def paritygen(stripecount, offset=0):
       
   132     """count parity of horizontal stripes for easier reading"""
       
   133     if stripecount and offset:
       
   134         # account for offset, e.g. due to building the list in reverse
       
   135         count = (stripecount + offset) % stripecount
       
   136         parity = (stripecount + offset) / stripecount & 1
       
   137     else:
       
   138         count = 0
       
   139         parity = 0
       
   140     while True:
       
   141         yield parity
       
   142         count += 1
       
   143         if stripecount and count >= stripecount:
       
   144             parity = 1 - parity
       
   145             count = 0
       
   146 
       
   147 def get_contact(config):
       
   148     """Return repo contact information or empty string.
       
   149 
       
   150     web.contact is the primary source, but if that is not set, try
       
   151     ui.username or $EMAIL as a fallback to display something useful.
       
   152     """
       
   153     return (config("web", "contact") or
       
   154             config("ui", "username") or
       
   155             os.environ.get("EMAIL") or "")
       
   156 
       
   157 def caching(web, req):
       
   158     tag = str(web.mtime)
       
   159     if req.env.get('HTTP_IF_NONE_MATCH') == tag:
       
   160         raise ErrorResponse(HTTP_NOT_MODIFIED)
       
   161     req.headers.append(('ETag', tag))