eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/mercurial/httprepo.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # httprepo.py - HTTP repository proxy classes for mercurial
       
     2 #
       
     3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
       
     4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 from node import nullid
       
    10 from i18n import _
       
    11 import changegroup, statichttprepo, error, url, util, wireproto
       
    12 import os, urllib, urllib2, urlparse, zlib, httplib
       
    13 import errno, socket
       
    14 
       
    15 def zgenerator(f):
       
    16     zd = zlib.decompressobj()
       
    17     try:
       
    18         for chunk in util.filechunkiter(f):
       
    19             while chunk:
       
    20                 yield zd.decompress(chunk, 2**18)
       
    21                 chunk = zd.unconsumed_tail
       
    22     except httplib.HTTPException:
       
    23         raise IOError(None, _('connection ended unexpectedly'))
       
    24     yield zd.flush()
       
    25 
       
    26 class httprepository(wireproto.wirerepository):
       
    27     def __init__(self, ui, path):
       
    28         self.path = path
       
    29         self.caps = None
       
    30         self.handler = None
       
    31         scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
       
    32         if query or frag:
       
    33             raise util.Abort(_('unsupported URL component: "%s"') %
       
    34                              (query or frag))
       
    35 
       
    36         # urllib cannot handle URLs with embedded user or passwd
       
    37         self._url, authinfo = url.getauthinfo(path)
       
    38 
       
    39         self.ui = ui
       
    40         self.ui.debug('using %s\n' % self._url)
       
    41 
       
    42         self.urlopener = url.opener(ui, authinfo)
       
    43 
       
    44     def __del__(self):
       
    45         for h in self.urlopener.handlers:
       
    46             h.close()
       
    47             if hasattr(h, "close_all"):
       
    48                 h.close_all()
       
    49 
       
    50     def url(self):
       
    51         return self.path
       
    52 
       
    53     # look up capabilities only when needed
       
    54 
       
    55     def get_caps(self):
       
    56         if self.caps is None:
       
    57             try:
       
    58                 self.caps = set(self._call('capabilities').split())
       
    59             except error.RepoError:
       
    60                 self.caps = set()
       
    61             self.ui.debug('capabilities: %s\n' %
       
    62                           (' '.join(self.caps or ['none'])))
       
    63         return self.caps
       
    64 
       
    65     capabilities = property(get_caps)
       
    66 
       
    67     def lock(self):
       
    68         raise util.Abort(_('operation not supported over http'))
       
    69 
       
    70     def _callstream(self, cmd, **args):
       
    71         if cmd == 'pushkey':
       
    72             args['data'] = ''
       
    73         data = args.pop('data', None)
       
    74         headers = args.pop('headers', {})
       
    75         self.ui.debug("sending %s command\n" % cmd)
       
    76         q = {"cmd": cmd}
       
    77         q.update(args)
       
    78         qs = '?%s' % urllib.urlencode(q)
       
    79         cu = "%s%s" % (self._url, qs)
       
    80         req = urllib2.Request(cu, data, headers)
       
    81         if data is not None:
       
    82             # len(data) is broken if data doesn't fit into Py_ssize_t
       
    83             # add the header ourself to avoid OverflowError
       
    84             size = data.__len__()
       
    85             self.ui.debug("sending %s bytes\n" % size)
       
    86             req.add_unredirected_header('Content-Length', '%d' % size)
       
    87         try:
       
    88             resp = self.urlopener.open(req)
       
    89         except urllib2.HTTPError, inst:
       
    90             if inst.code == 401:
       
    91                 raise util.Abort(_('authorization failed'))
       
    92             raise
       
    93         except httplib.HTTPException, inst:
       
    94             self.ui.debug('http error while sending %s command\n' % cmd)
       
    95             self.ui.traceback()
       
    96             raise IOError(None, inst)
       
    97         except IndexError:
       
    98             # this only happens with Python 2.3, later versions raise URLError
       
    99             raise util.Abort(_('http error, possibly caused by proxy setting'))
       
   100         # record the url we got redirected to
       
   101         resp_url = resp.geturl()
       
   102         if resp_url.endswith(qs):
       
   103             resp_url = resp_url[:-len(qs)]
       
   104         if self._url.rstrip('/') != resp_url.rstrip('/'):
       
   105             self.ui.status(_('real URL is %s\n') % resp_url)
       
   106         self._url = resp_url
       
   107         try:
       
   108             proto = resp.getheader('content-type')
       
   109         except AttributeError:
       
   110             proto = resp.headers['content-type']
       
   111 
       
   112         safeurl = url.hidepassword(self._url)
       
   113         # accept old "text/plain" and "application/hg-changegroup" for now
       
   114         if not (proto.startswith('application/mercurial-') or
       
   115                 proto.startswith('text/plain') or
       
   116                 proto.startswith('application/hg-changegroup')):
       
   117             self.ui.debug("requested URL: '%s'\n" % url.hidepassword(cu))
       
   118             raise error.RepoError(
       
   119                 _("'%s' does not appear to be an hg repository:\n"
       
   120                   "---%%<--- (%s)\n%s\n---%%<---\n")
       
   121                 % (safeurl, proto, resp.read()))
       
   122 
       
   123         if proto.startswith('application/mercurial-'):
       
   124             try:
       
   125                 version = proto.split('-', 1)[1]
       
   126                 version_info = tuple([int(n) for n in version.split('.')])
       
   127             except ValueError:
       
   128                 raise error.RepoError(_("'%s' sent a broken Content-Type "
       
   129                                         "header (%s)") % (safeurl, proto))
       
   130             if version_info > (0, 1):
       
   131                 raise error.RepoError(_("'%s' uses newer protocol %s") %
       
   132                                       (safeurl, version))
       
   133 
       
   134         return resp
       
   135 
       
   136     def _call(self, cmd, **args):
       
   137         fp = self._callstream(cmd, **args)
       
   138         try:
       
   139             return fp.read()
       
   140         finally:
       
   141             # if using keepalive, allow connection to be reused
       
   142             fp.close()
       
   143 
       
   144     def _callpush(self, cmd, cg, **args):
       
   145         # have to stream bundle to a temp file because we do not have
       
   146         # http 1.1 chunked transfer.
       
   147 
       
   148         type = ""
       
   149         types = self.capable('unbundle')
       
   150         # servers older than d1b16a746db6 will send 'unbundle' as a
       
   151         # boolean capability
       
   152         try:
       
   153             types = types.split(',')
       
   154         except AttributeError:
       
   155             types = [""]
       
   156         if types:
       
   157             for x in types:
       
   158                 if x in changegroup.bundletypes:
       
   159                     type = x
       
   160                     break
       
   161 
       
   162         tempname = changegroup.writebundle(cg, None, type)
       
   163         fp = url.httpsendfile(tempname, "rb")
       
   164         headers = {'Content-Type': 'application/mercurial-0.1'}
       
   165 
       
   166         try:
       
   167             try:
       
   168                 r = self._call(cmd, data=fp, headers=headers, **args)
       
   169                 return r.split('\n', 1)
       
   170             except socket.error, err:
       
   171                 if err.args[0] in (errno.ECONNRESET, errno.EPIPE):
       
   172                     raise util.Abort(_('push failed: %s') % err.args[1])
       
   173                 raise util.Abort(err.args[1])
       
   174         finally:
       
   175             fp.close()
       
   176             os.unlink(tempname)
       
   177 
       
   178     def _abort(self, exception):
       
   179         raise exception
       
   180 
       
   181     def _decompress(self, stream):
       
   182         return util.chunkbuffer(zgenerator(stream))
       
   183 
       
   184 class httpsrepository(httprepository):
       
   185     def __init__(self, ui, path):
       
   186         if not url.has_https:
       
   187             raise util.Abort(_('Python support for SSL and HTTPS '
       
   188                                'is not installed'))
       
   189         httprepository.__init__(self, ui, path)
       
   190 
       
   191 def instance(ui, path, create):
       
   192     if create:
       
   193         raise util.Abort(_('cannot create new http repository'))
       
   194     try:
       
   195         if path.startswith('https:'):
       
   196             inst = httpsrepository(ui, path)
       
   197         else:
       
   198             inst = httprepository(ui, path)
       
   199         inst.between([(nullid, nullid)])
       
   200         return inst
       
   201     except error.RepoError:
       
   202         ui.note('(falling back to static-http)\n')
       
   203         return statichttprepo.instance(ui, "static-" + path, create)