~bzr-pqm/bzr/bzr.dev

« back to all changes in this revision

Viewing changes to bzrlib/transport/http.py

  • Committer: Robert Collins
  • Date: 2005-10-06 22:15:52 UTC
  • mfrom: (1185.13.2)
  • mto: This revision was merged to the branch mainline in revision 1420.
  • Revision ID: robertc@robertcollins.net-20051006221552-9b15c96fa504e0ad
mergeĀ fromĀ upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
18
18
 
19
19
from bzrlib.transport import Transport, register_transport
20
20
from bzrlib.errors import (TransportNotPossible, NoSuchFile, 
21
 
                           TransportError, ConnectionError)
 
21
                           NonRelativePath, TransportError)
22
22
import os, errno
23
23
from cStringIO import StringIO
24
 
import urllib, urllib2
 
24
import urllib2
25
25
import urlparse
26
26
 
27
27
from bzrlib.errors import BzrError, BzrCheckError
28
28
from bzrlib.branch import Branch
29
29
from bzrlib.trace import mutter
30
30
 
31
 
 
32
 
def extract_auth(url, password_manager):
33
 
    """
34
 
    Extract auth parameters from am HTTP/HTTPS url and add them to the given
35
 
    password manager.  Return the url, minus those auth parameters (which
36
 
    confuse urllib2).
37
 
    """
38
 
    assert url.startswith('http://') or url.startswith('https://')
39
 
    scheme, host = url.split('//', 1)
40
 
    if '/' in host:
41
 
        host, path = host.split('/', 1)
42
 
        path = '/' + path
43
 
    else:
44
 
        path = ''
45
 
    port = ''
46
 
    if '@' in host:
47
 
        auth, host = host.split('@', 1)
48
 
        if ':' in auth:
49
 
            username, password = auth.split(':', 1)
50
 
        else:
51
 
            username, password = auth, None
52
 
        if ':' in host:
53
 
            host, port = host.split(':', 1)
54
 
            port = ':' + port
55
 
        # FIXME: if password isn't given, should we ask for it?
56
 
        if password is not None:
57
 
            username = urllib.unquote(username)
58
 
            password = urllib.unquote(password)
59
 
            password_manager.add_password(None, host, username, password)
60
 
    url = scheme + '//' + host + port + path
61
 
    return url
62
 
    
 
31
# velocitynet.com.au transparently proxies connections and thereby
 
32
# breaks keep-alive -- sucks!
 
33
 
 
34
 
63
35
def get_url(url):
64
36
    import urllib2
65
37
    mutter("get_url %s" % url)
66
 
    manager = urllib2.HTTPPasswordMgrWithDefaultRealm()
67
 
    url = extract_auth(url, manager)
68
 
    auth_handler = urllib2.HTTPBasicAuthHandler(manager)
69
 
    opener = urllib2.build_opener(auth_handler)
70
 
    url_f = opener.open(url)
 
38
    url_f = urllib2.urlopen(url)
71
39
    return url_f
72
40
 
 
41
class HttpTransportError(TransportError):
 
42
    pass
 
43
 
73
44
class HttpTransport(Transport):
74
45
    """This is the transport agent for http:// access.
75
46
    
106
77
        """Return the full url to the given relative path.
107
78
        This can be supplied with a string or a list
108
79
        """
109
 
        assert isinstance(relpath, basestring)
110
80
        if isinstance(relpath, basestring):
111
 
            relpath_parts = relpath.split('/')
112
 
        else:
113
 
            # TODO: Don't call this with an array - no magic interfaces
114
 
            relpath_parts = relpath[:]
115
 
        if len(relpath_parts) > 1:
116
 
            if relpath_parts[0] == '':
117
 
                raise ValueError("path %r within branch %r seems to be absolute"
118
 
                                 % (relpath, self._path))
119
 
            if relpath_parts[-1] == '':
120
 
                raise ValueError("path %r within branch %r seems to be a directory"
121
 
                                 % (relpath, self._path))
 
81
            relpath = [relpath]
122
82
        basepath = self._path.split('/')
123
83
        if len(basepath) > 0 and basepath[-1] == '':
124
84
            basepath = basepath[:-1]
125
 
        for p in relpath_parts:
 
85
 
 
86
        for p in relpath:
126
87
            if p == '..':
127
 
                if len(basepath) == 0:
 
88
                if len(basepath) < 0:
128
89
                    # In most filesystems, a request for the parent
129
90
                    # of root, just returns root.
130
91
                    continue
131
92
                basepath.pop()
132
 
            elif p == '.' or p == '':
 
93
            elif p == '.':
133
94
                continue # No-op
134
95
            else:
135
96
                basepath.append(p)
 
97
 
136
98
        # Possibly, we could use urlparse.urljoin() here, but
137
99
        # I'm concerned about when it chooses to strip the last
138
100
        # portion of the path, and when it doesn't.
140
102
        return urlparse.urlunparse((self._proto,
141
103
                self._host, path, '', '', ''))
142
104
 
 
105
    def relpath(self, abspath):
 
106
        if not abspath.startswith(self.base):
 
107
            raise NonRelativePath('path %r is not under base URL %r'
 
108
                           % (abspath, self.base))
 
109
        pl = len(self.base)
 
110
        return abspath[pl:].lstrip('/')
 
111
 
143
112
    def has(self, relpath):
144
113
        """Does the target location exist?
145
114
 
151
120
        cleaner if we just do an http HEAD request, and parse
152
121
        the return code.
153
122
        """
154
 
        path = relpath
155
123
        try:
156
 
            path = self.abspath(relpath)
157
 
            f = get_url(path)
 
124
            f = get_url(self.abspath(relpath))
158
125
            # Without the read and then close()
159
126
            # we tend to have busy sockets.
160
127
            f.read()
161
128
            f.close()
162
129
            return True
163
 
        except urllib2.URLError, e:
164
 
            mutter('url error code: %s for has url: %r', e.code, path)
165
 
            if e.code == 404:
166
 
                return False
167
 
            raise
 
130
        except BzrError:
 
131
            return False
 
132
        except urllib2.URLError:
 
133
            return False
168
134
        except IOError, e:
169
 
            mutter('io error: %s %s for has url: %r', 
170
 
                e.errno, errno.errorcode.get(e.errno), path)
171
135
            if e.errno == errno.ENOENT:
172
136
                return False
173
 
            raise TransportError(orig_error=e)
 
137
            raise HttpTransportError(orig_error=e)
174
138
 
175
139
    def get(self, relpath, decode=False):
176
140
        """Get the file at the given relative path.
177
141
 
178
142
        :param relpath: The relative path to the file
179
143
        """
180
 
        path = relpath
181
144
        try:
182
 
            path = self.abspath(relpath)
183
 
            return get_url(path)
184
 
        except urllib2.HTTPError, e:
185
 
            mutter('url error code: %s for has url: %r', e.code, path)
186
 
            if e.code == 404:
187
 
                raise NoSuchFile(path, extra=e)
188
 
            raise
189
 
        except (BzrError, IOError), e:
190
 
            if hasattr(e, 'errno'):
191
 
                mutter('io error: %s %s for has url: %r', 
192
 
                    e.errno, errno.errorcode.get(e.errno), path)
193
 
                if e.errno == errno.ENOENT:
194
 
                    raise NoSuchFile(path, extra=e)
195
 
            raise ConnectionError(msg = "Error retrieving %s: %s" 
196
 
                             % (self.abspath(relpath), str(e)),
197
 
                             orig_error=e)
198
 
 
199
 
    def put(self, relpath, f, mode=None):
 
145
            return get_url(self.abspath(relpath))
 
146
        except (BzrError, urllib2.URLError, IOError), e:
 
147
            raise NoSuchFile(orig_error=e)
 
148
        except Exception,e:
 
149
            raise HttpTransportError(orig_error=e)
 
150
 
 
151
    def get_partial(self, relpath, start, length=None):
 
152
        """Get just part of a file.
 
153
 
 
154
        :param relpath: Path to the file, relative to base
 
155
        :param start: The starting position to read from
 
156
        :param length: The length to read. A length of None indicates
 
157
                       read to the end of the file.
 
158
        :return: A file-like object containing at least the specified bytes.
 
159
                 Some implementations may return objects which can be read
 
160
                 past this length, but this is not guaranteed.
 
161
        """
 
162
        # TODO: You can make specialized http requests for just
 
163
        # a portion of the file. Figure out how to do that.
 
164
        # For now, urllib2 returns files that cannot seek() so
 
165
        # we just read bytes off the beginning, until we
 
166
        # get to the point that we care about.
 
167
        f = self.get(relpath)
 
168
        # TODO: read in smaller chunks, in case things are
 
169
        # buffered internally.
 
170
        f.read(start)
 
171
        return f
 
172
 
 
173
    def put(self, relpath, f):
200
174
        """Copy the file-like or string object into the location.
201
175
 
202
176
        :param relpath: Location to put the contents, relative to base.
204
178
        """
205
179
        raise TransportNotPossible('http PUT not supported')
206
180
 
207
 
    def mkdir(self, relpath, mode=None):
 
181
    def mkdir(self, relpath):
208
182
        """Create a directory at the given path."""
209
183
        raise TransportNotPossible('http does not support mkdir()')
210
184
 
218
192
        """Copy the item at rel_from to the location at rel_to"""
219
193
        raise TransportNotPossible('http does not support copy()')
220
194
 
221
 
    def copy_to(self, relpaths, other, mode=None, pb=None):
 
195
    def copy_to(self, relpaths, other, pb=None):
222
196
        """Copy a set of entries from self into another Transport.
223
197
 
224
198
        :param relpaths: A list/generator of entries to be copied.
232
206
        if isinstance(other, HttpTransport):
233
207
            raise TransportNotPossible('http cannot be the target of copy_to()')
234
208
        else:
235
 
            return super(HttpTransport, self).copy_to(relpaths, other, mode=mode, pb=pb)
 
209
            return super(HttpTransport, self).copy_to(relpaths, other, pb=pb)
236
210
 
237
211
    def move(self, rel_from, rel_to):
238
212
        """Move the item at rel_from to the location at rel_to"""
271
245
        :return: A lock object, which should be passed to Transport.unlock()
272
246
        """
273
247
        raise TransportNotPossible('http does not support lock_write()')
 
248
 
 
249
register_transport('http://', HttpTransport)
 
250
register_transport('https://', HttpTransport)