Rev 50: First implementation for stat. Some test are still failing. in http://bazaar.launchpad.net/%7Ebzr/bzr.webdav/webdav
Vincent Ladeuil
v.ladeuil+lp at free.fr
Sun Jun 8 20:34:38 BST 2008
At http://bazaar.launchpad.net/%7Ebzr/bzr.webdav/webdav
------------------------------------------------------------
revno: 50
revision-id: v.ladeuil+lp at free.fr-20080608193435-02kt8x2camnawwmz
parent: v.ladeuil+lp at free.fr-20080608104358-0n80zg1lz9lbth51
committer: Vincent Ladeuil <v.ladeuil+lp at free.fr>
branch nick: webdav
timestamp: Sun 2008-06-08 21:34:35 +0200
message:
First implementation for stat. Some test are still failing.
* webdav.py:
(DavResponseHandler): Extracted from DavStatHandler and
DavListDirHandler.
(DavListDirHandler): New class. Updated after some modifications
in DavResponseHandler.
(_extract_dir_content): merge DavListDirHandler.get_dir_content.
(DavStatHandler): New class.
(_DAVStat): New class.
(_extract_stat_info): New helper.
(HttpDavTransport.list_dir): D:prop is enough to get file list.
(HttpDavTransport.stat): Implement stat.
* test_webdav.py:
(TestDavSaxParser): Add tests for DavStatHandler.
modified:
TODO todo-20060820113924-ioaocfzvsb4wq9z1-1
test_webdav.py test_webdav.py-20060823130244-qvg4wqdodnmf5nhs-1
webdav.py webdav.py-20060816232542-enpjxth2743ttqpq-3
-------------- next part --------------
=== modified file 'TODO'
--- a/TODO 2007-11-04 17:44:22 +0000
+++ b/TODO 2008-06-08 19:34:35 +0000
@@ -1,4 +1,10 @@
-- add specific tests
-
-- handle the TODOs in webdav.py
+* handle PROPFIND in the test server
+
+** handle depths 0, 1, Infinity
+
+** handle prop and allprop for file and directory as apache2
+
+* handle the TODOs in webdav.py
+
+** move the relevant TODOs in webdav.py and elsewhere here :)
=== modified file 'test_webdav.py'
--- a/test_webdav.py 2008-06-08 10:43:58 +0000
+++ b/test_webdav.py 2008-06-08 19:34:35 +0000
@@ -34,6 +34,7 @@
import socket
import string
import shutil
+import stat
import sys
import time
import urlparse
@@ -422,6 +423,48 @@
class TestDavSaxParser(tests.TestCase):
+ def _extract_dir_content_from_str(self, str):
+ return webdav._extract_dir_content(
+ 'http://localhost/blah', StringIO(str))
+
+ def _extract_stat_from_str(self, str):
+ return webdav._extract_stat_info(
+ 'http://localhost/blah', StringIO(str))
+
+ def test_unkown_format_response(self):
+ # Valid but unrelated xml
+ example = """<document/>"""
+ self.assertRaises(errors.InvalidHttpResponse,
+ self._extract_dir_content_from_str, example)
+
+ def test_list_dir_malformed_response(self):
+ # Invalid xml, neither multistatus nor response are properly closed
+ example = """<?xml version="1.0" encoding="utf-8"?>
+<D:multistatus xmlns:D="DAV:" xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
+<D:response>
+<D:href>http://localhost/</D:href>"""
+ self.assertRaises(errors.InvalidHttpResponse,
+ self._extract_dir_content_from_str, example)
+
+ def test_list_dir_incomplete_format_response(self):
+ # The minimal information is present but doesn't conform to RFC 2518
+ # (well, as I understand it since the reference servers disagree on
+ # more than details).
+
+ # The last href below is not enclosed in a response element and is
+ # therefore ignored.
+ example = """<?xml version="1.0" encoding="utf-8"?>
+<D:multistatus xmlns:D="DAV:" xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
+<D:response>
+<D:href>http://localhost/</D:href>
+</D:response>
+<D:response>
+<D:href>http://localhost/titi</D:href>
+</D:response>
+<D:href>http://localhost/toto</D:href>
+</D:multistatus>"""
+ self.assertEqual(['titi'], self._extract_dir_content_from_str(example))
+
def test_list_dir_apache2_example(self):
example = """<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:ns0="DAV:">
@@ -459,8 +502,7 @@
</D:response>
</D:multistatus>"""
self.assertEqual(['a', 'b', 'c'],
- webdav._extract_dir_content('http://localhost/blah',
- StringIO(example)))
+ self._extract_dir_content_from_str(example))
def test_list_dir_lighttpd_example(self):
example = """<?xml version="1.0" encoding="utf-8"?>
@@ -476,43 +518,224 @@
</D:response>
</D:multistatus>"""
self.assertEqual(['titi', 'toto'],
- webdav._extract_dir_content('http://localhost/blah',
- StringIO(example)))
+ self._extract_dir_content_from_str(example))
- def test_list_dir_malformed_response(self):
+ def test_stat_malformed_response(self):
# Invalid xml, neither multistatus nor response are properly closed
example = """<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
<D:response>
<D:href>http://localhost/</D:href>"""
self.assertRaises(errors.InvalidHttpResponse,
- webdav._extract_dir_content,
- 'http://localhost/blah', StringIO(example))
-
- def test_unkown_format_response(self):
- # Valid but unrelated xml
- example = """<document/>"""
- self.assertRaises(errors.InvalidHttpResponse,
- webdav._extract_dir_content,
- 'http://localhost/blah', StringIO(example))
-
- def test_list_dir_incomplete_format_response(self):
+ self._extract_stat_from_str, example)
+
+ def test_stat_incomplete_format_response(self):
# The minimal information is present but doesn't conform to RFC 2518
# (well, as I understand it since the reference servers disagree on
# more than details).
- # The last href below is not enclosed in a response element and is
+ # The href below is not enclosed in a response element and is
# therefore ignored.
example = """<?xml version="1.0" encoding="utf-8"?>
<D:multistatus xmlns:D="DAV:" xmlns:ns0="urn:uuid:c2f41010-65b3-11d1-a29f-00aa00c14882/">
-<D:response>
-<D:href>http://localhost/</D:href>
-</D:response>
-<D:response>
-<D:href>http://localhost/titi</D:href>
-</D:response>
<D:href>http://localhost/toto</D:href>
</D:multistatus>"""
- self.assertEqual(['titi'],
- webdav._extract_dir_content('http://localhost/blah',
- StringIO(example)))
+ self.assertRaises(errors.InvalidHttpResponse,
+ self._extract_stat_from_str, example)
+
+ def test_stat_apache2_file_example(self):
+ example = """<?xml version="1.0" encoding="utf-8"?>
+<D:multistatus xmlns:D="DAV:" xmlns:ns0="DAV:">
+<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
+<D:href>/executable</D:href>
+<D:propstat>
+<D:prop>
+<lp1:resourcetype/>
+<lp1:creationdate>2008-06-08T09:50:15Z</lp1:creationdate>
+<lp1:getcontentlength>12</lp1:getcontentlength>
+<lp1:getlastmodified>Sun, 08 Jun 2008 09:50:11 GMT</lp1:getlastmodified>
+<lp1:getetag>"da9f81-0-9ef33ac0"</lp1:getetag>
+<lp2:executable>T</lp2:executable>
+<D:supportedlock>
+<D:lockentry>
+<D:lockscope><D:exclusive/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+<D:lockentry>
+<D:lockscope><D:shared/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+</D:supportedlock>
+<D:lockdiscovery/>
+</D:prop>
+<D:status>HTTP/1.1 200 OK</D:status>
+</D:propstat>
+</D:response>
+</D:multistatus>"""
+ st = self._extract_stat_from_str(example)
+ self.assertEquals(12, st.st_size)
+ self.assertFalse(stat.S_ISDIR(st.st_mode))
+ self.assertTrue(stat.S_ISREG(st.st_mode))
+ self.assertTrue(st.st_mode & stat.S_IXUSR)
+
+ def test_stat_apache2_dir_depth_1_example(self):
+ example = """<?xml version="1.0" encoding="utf-8"?>
+<D:multistatus xmlns:D="DAV:" xmlns:ns0="DAV:">
+<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
+<D:href>/</D:href>
+<D:propstat>
+<D:prop>
+<lp1:resourcetype><D:collection/></lp1:resourcetype>
+<lp1:creationdate>2008-06-08T10:50:38Z</lp1:creationdate>
+<lp1:getlastmodified>Sun, 08 Jun 2008 10:50:38 GMT</lp1:getlastmodified>
+<lp1:getetag>"da7f5a-cc-7722db80"</lp1:getetag>
+<D:supportedlock>
+<D:lockentry>
+<D:lockscope><D:exclusive/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+<D:lockentry>
+<D:lockscope><D:shared/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+</D:supportedlock>
+<D:lockdiscovery/>
+</D:prop>
+<D:status>HTTP/1.1 200 OK</D:status>
+</D:propstat>
+</D:response>
+<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
+<D:href>/executable</D:href>
+<D:propstat>
+<D:prop>
+<lp1:resourcetype/>
+<lp1:creationdate>2008-06-08T09:50:15Z</lp1:creationdate>
+<lp1:getcontentlength>0</lp1:getcontentlength>
+<lp1:getlastmodified>Sun, 08 Jun 2008 09:50:11 GMT</lp1:getlastmodified>
+<lp1:getetag>"da9f81-0-9ef33ac0"</lp1:getetag>
+<lp2:executable>T</lp2:executable>
+<D:supportedlock>
+<D:lockentry>
+<D:lockscope><D:exclusive/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+<D:lockentry>
+<D:lockscope><D:shared/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+</D:supportedlock>
+<D:lockdiscovery/>
+</D:prop>
+<D:status>HTTP/1.1 200 OK</D:status>
+</D:propstat>
+</D:response>
+<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
+<D:href>/read-only</D:href>
+<D:propstat>
+<D:prop>
+<lp1:resourcetype/>
+<lp1:creationdate>2008-06-08T09:50:11Z</lp1:creationdate>
+<lp1:getcontentlength>0</lp1:getcontentlength>
+<lp1:getlastmodified>Sun, 08 Jun 2008 09:50:11 GMT</lp1:getlastmodified>
+<lp1:getetag>"da9f80-0-9ef33ac0"</lp1:getetag>
+<lp2:executable>F</lp2:executable>
+<D:supportedlock>
+<D:lockentry>
+<D:lockscope><D:exclusive/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+<D:lockentry>
+<D:lockscope><D:shared/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+</D:supportedlock>
+<D:lockdiscovery/>
+</D:prop>
+<D:status>HTTP/1.1 200 OK</D:status>
+</D:propstat>
+</D:response>
+<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
+<D:href>/titi</D:href>
+<D:propstat>
+<D:prop>
+<lp1:resourcetype/>
+<lp1:creationdate>2008-06-08T09:49:53Z</lp1:creationdate>
+<lp1:getcontentlength>6</lp1:getcontentlength>
+<lp1:getlastmodified>Sun, 08 Jun 2008 09:49:53 GMT</lp1:getlastmodified>
+<lp1:getetag>"da8cbc-6-9de09240"</lp1:getetag>
+<lp2:executable>F</lp2:executable>
+<D:supportedlock>
+<D:lockentry>
+<D:lockscope><D:exclusive/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+<D:lockentry>
+<D:lockscope><D:shared/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+</D:supportedlock>
+<D:lockdiscovery/>
+</D:prop>
+<D:status>HTTP/1.1 200 OK</D:status>
+</D:propstat>
+</D:response>
+<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
+<D:href>/toto/</D:href>
+<D:propstat>
+<D:prop>
+<lp1:resourcetype><D:collection/></lp1:resourcetype>
+<lp1:creationdate>2008-06-06T08:07:07Z</lp1:creationdate>
+<lp1:getlastmodified>Fri, 06 Jun 2008 08:07:07 GMT</lp1:getlastmodified>
+<lp1:getetag>"da8cb9-44-f2ac20c0"</lp1:getetag>
+<D:supportedlock>
+<D:lockentry>
+<D:lockscope><D:exclusive/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+<D:lockentry>
+<D:lockscope><D:shared/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+</D:supportedlock>
+<D:lockdiscovery/>
+</D:prop>
+<D:status>HTTP/1.1 200 OK</D:status>
+</D:propstat>
+</D:response>
+</D:multistatus>
+"""
+ self.assertRaises(errors.InvalidHttpResponse,
+ self._extract_stat_from_str, example)
+
+ def test_stat_apache2_dir_depth_0_example(self):
+ example = """<?xml version="1.0" encoding="utf-8"?>
+<D:multistatus xmlns:D="DAV:" xmlns:ns0="DAV:">
+<D:response xmlns:lp1="DAV:" xmlns:lp2="http://apache.org/dav/props/">
+<D:href>/</D:href>
+<D:propstat>
+<D:prop>
+<lp1:resourcetype><D:collection/></lp1:resourcetype>
+<lp1:creationdate>2008-06-08T10:50:38Z</lp1:creationdate>
+<lp1:getlastmodified>Sun, 08 Jun 2008 10:50:38 GMT</lp1:getlastmodified>
+<lp1:getetag>"da7f5a-cc-7722db80"</lp1:getetag>
+<D:supportedlock>
+<D:lockentry>
+<D:lockscope><D:exclusive/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+<D:lockentry>
+<D:lockscope><D:shared/></D:lockscope>
+<D:locktype><D:write/></D:locktype>
+</D:lockentry>
+</D:supportedlock>
+<D:lockdiscovery/>
+</D:prop>
+<D:status>HTTP/1.1 200 OK</D:status>
+</D:propstat>
+</D:response>
+</D:multistatus>
+"""
+ st = self._extract_stat_from_str(example)
+ self.assertEquals(None, st.st_size)
+ self.assertTrue(stat.S_ISDIR(st.st_mode))
+ self.assertTrue(st.st_mode & stat.S_IXUSR)
=== modified file 'webdav.py'
--- a/webdav.py 2008-06-08 10:43:58 +0000
+++ b/webdav.py 2008-06-08 19:34:35 +0000
@@ -66,21 +66,10 @@
# TODO: Factor out the error handling.
-# TODO: implement list_dir, it's currently used by the pack format
-# The pack format is still experimental but may become the default
-# format in the near future (2007-11-04).
-#
-# list_dir is considered usable for writable transports:
-#
-# <lifeless> there is no technical reason I know of yet to avoid
-# list_dir for writable transports
-# <lifeless> I plan in a future packs format to see if we can remove list_dir
-# <lifeless> but for the current format there is no alternative
-# <lifeless> that isn't worse.
-
from cStringIO import StringIO
import os
import random
+import re
import sys
import time
import urllib2
@@ -102,18 +91,14 @@
class DavResponseHandler(xml.sax.handler.ContentHandler):
- """Handle a mutli-status DAV response.
-
- Currently this class focus on handling a response for a PROPFIND request of
- depth 1 targeted as getting the content of a directory. This may evolve to
- handle more responses.
- """
+ """Handle a multi-status DAV response."""
def __init__(self):
self.url = None
- self.dir_content = None
self.elt_stack = None
self.chars = None
+ self.chars_wanted = False
+ self.expected_content_handled = False
def set_url(self, url):
"""Set the url used for error reporting when handling a response."""
@@ -121,63 +106,79 @@
def startDocument(self):
self.elt_stack = []
- self.dir_content = None
self.chars = None
+ self.expected_content_handled = False
def endDocument(self):
- if self.dir_content is None:
+ self._validate_handling()
+ if not self.expected_content_handled:
raise errors.InvalidHttpResponse(self.url,
msg='Unknown xml response')
def startElement(self, name, attrs):
- self.elt_stack.append(name)
- if name == 'D:href':
- self.chars = []
+ self.elt_stack.append(self._strip_ns(name))
+ # The following is incorrect in the general case where elements are
+ # intermixed with chars in a higher level element. That's not the case
+ # here (otherwise the chars_wanted will have to be stacked too).
+ if self.chars_wanted:
+ self.chars = ''
+ else:
+ self.chars = None
+
+ def endElement(self, name):
+ self.chars = None
+ self.chars_wanted = False
+ self.elt_stack.pop()
+
+ def characters(self, chrs):
+ if self.chars_wanted:
+ self.chars += chrs
+
+ def _current_element(self):
+ return self.elt_stack[-1]
+
+ def _strip_ns(self, name):
+ """Strip the leading namespace from name.
+
+ We don't have namespaces clashes in our context, stripping it makes the
+ code simpler.
+ """
+ where = name.find(':')
+ if where == -1:
+ return name
+ else:
+ return name[where +1:]
+
+
+class DavListDirHandler(DavResponseHandler):
+ """Handle a PROPPFIND depth 1 DAV response for a directory.
+
+ The expected content is a multi-status containing a list of response
+ containing at least a href property.
+ """
+ def __init__(self):
+ DavResponseHandler.__init__(self)
+ self.dir_content = None
+
+ def _validate_handling(self):
+ if self.dir_content is not None:
+ self.expected_content_handled = True
+
+ def startElement(self, name, attrs):
+ self.chars_wanted = (self._strip_ns(name) == 'href')
+ DavResponseHandler.startElement(self, name, attrs)
def endElement(self, name):
stack = self.elt_stack
if (len(stack) == 3
- and stack[0] == 'D:multistatus'
- and stack[1] == 'D:response'
- and name == 'D:href'): # sax guarantees that stack[2] is also D:href
+ and stack[0] == 'multistatus'
+ and stack[1] == 'response'
+ # sax guarantees that name is also href (when ns is stripped)
+ and stack[2] == 'href'):
if self.dir_content is None:
self.dir_content = []
- self.dir_content.append(''.join(self.chars))
- self.chars = None
- stack.pop()
-
- def characters(self, chrs):
- if self._current_element() == 'D:href':
- self.chars.append(chrs)
-
- def _current_element(self):
- return self.elt_stack[-1]
-
- def get_dir_content(self):
- # Surprisingly enough (or not), our two references DAV servers disagree
- # on almost every detail, expect using xml.
- # For the href element:
-
- # - apache2 use the path part of the URL (i.e. http://host/path) and
- # append a '/' to directory names.
-
- # - lighttpd use the full URL (i.e. /path) and doesn't distinguish
- # between files and directories.
-
- # Fortunately they both put the directory requested in front of the
- # list. So we take that directory and strip it from all other
- # elements...
- dir = self.dir_content[0]
- dir_len = len(dir)
- elements = []
- for href in self.dir_content[1:]: # Ignore first element
- if href.startswith(dir):
- name = href[dir_len:]
- if name.endswith('/'):
- # Get rid of final '/'
- name = name[0:-1]
- elements.append(name)
- return elements
+ self.dir_content.append(self.chars)
+ DavResponseHandler.endElement(self, name)
def _extract_dir_content(url, infile):
@@ -188,16 +189,152 @@
"""
parser = xml.sax.make_parser()
- handler = DavResponseHandler()
- parser.setContentHandler(handler)
- try:
- parser.parse(infile)
- except xml.sax.SAXParseException, e:
- raise errors.InvalidHttpResponse(
- url, msg='Malformed xml response: %s' % e)
- # We receive already url-encoded strings so down-casting is safe. And bzr
- # insists on getting strings not unicode strings.
- return map(str, handler.get_dir_content())
+ handler = DavListDirHandler()
+ handler.set_url(url)
+ parser.setContentHandler(handler)
+ try:
+ parser.parse(infile)
+ except xml.sax.SAXParseException, e:
+ raise errors.InvalidHttpResponse(
+ url, msg='Malformed xml response: %s' % e)
+ # Reformat for bzr needs
+ dir_content = handler.dir_content
+ dir = dir_content[0]
+ dir_len = len(dir)
+ elements = []
+ for href in dir_content[1:]: # Ignore first element
+ if href.startswith(dir):
+ name = href[dir_len:]
+ if name.endswith('/'):
+ # Get rid of final '/'
+ name = name[0:-1]
+ # We receive already url-encoded strings so down-casting is
+ # safe. And bzr insists on getting strings not unicode strings.
+ elements.append(str(name))
+ return elements
+
+
+class DavStatHandler(DavResponseHandler):
+ """Handle a PROPPFIND depth 0 DAV response for a file or directory.
+
+ The expected content is:
+ - a multi-status element containing
+ - a single response element containing
+ - a href element
+ - a propstat element containing
+ - a status element (ignored)
+ - a prop element containing at least (other are ignored)
+ - a getcontentlength element (for files only)
+ - an executable element (for files only)
+ """
+
+ def __init__(self):
+ DavResponseHandler.__init__(self)
+ self.href = None
+ self.length = None
+ self.executable = None
+ # Flags defining the context for the actions
+ self._response_seen = False
+
+ def _validate_handling(self):
+ if self.href is not None:
+ self.expected_content_handled = True
+
+ def startElement(self, name, attrs):
+ sname = self._strip_ns(name)
+ self.chars_wanted = sname in ('href', 'getcontentlength', 'executable')
+ DavResponseHandler.startElement(self, name, attrs)
+
+ def endElement(self, name):
+ sname = self._strip_ns(name)
+ if self._response_seen:
+ if sname != 'multistatus':
+ raise errors.InvalidHttpResponse(
+ self.url, msg='Unexpected %s element' % name)
+ else:
+ # We process only the first response (just in case)
+ if self._href_end():
+ self.href = self.chars
+ elif self._getcontentlength_end():
+ self.length = self.chars
+ elif self._executable_end():
+ self.executable = self.chars
+ if sname == 'response':
+ self._response_seen = True
+ DavResponseHandler.endElement(self, name)
+
+ def _href_end(self):
+ stack = self.elt_stack
+ return (len(stack) == 3
+ and stack[0] == 'multistatus'
+ and stack[1] == 'response'
+ and stack[2] == 'href')
+
+ def _getcontentlength_end(self):
+ stack = self.elt_stack
+ return (len(stack) == 5
+ and stack[0] == 'multistatus'
+ and stack[1] == 'response'
+ and stack[2] == 'propstat'
+ and stack[3] == 'prop'
+ and stack[4] == 'getcontentlength')
+
+ def _executable_end(self):
+ stack = self.elt_stack
+ return (len(stack) == 5
+ and stack[0] == 'multistatus'
+ and stack[1] == 'response'
+ and stack[2] == 'propstat'
+ and stack[3] == 'prop'
+ and stack[4] == 'executable')
+
+
+class _DAVStat(object):
+ """The stat info as it can be acquired with DAV."""
+
+ def __init__(self, size, is_dir, is_exec):
+ self.st_size = size
+ # We build a mode considering that:
+
+ # - we have no idea about group or other chmod bits so we use a sane
+ # default (bzr should not care anyway)
+
+ # - we suppose that the user can write
+ if is_dir:
+ self.st_mode = 0040644
+ else:
+ self.st_mode = 0100644
+ if is_exec:
+ self.st_mode = self.st_mode | 0755
+
+
+def _extract_stat_info(url, infile):
+ """Extract the stat-like information from a DAV PROPFIND response.
+
+ :param url: The url used for the PROPFIND request.
+ :param infile: A file-like object pointing at the start of the response.
+ """
+ parser = xml.sax.make_parser()
+
+ handler = DavStatHandler()
+ handler.set_url(url)
+ parser.setContentHandler(handler)
+ try:
+ parser.parse(infile)
+ except xml.sax.SAXParseException, e:
+ raise errors.InvalidHttpResponse(
+ url, msg='Malformed xml response: %s' % e)
+ is_dir = (handler.href is not None
+ and handler.length is None
+ and handler.executable is None)
+ if is_dir:
+ size = None # directory sizes are meaningless for bzr
+ is_exec = True
+ else:
+ size = int(handler.length)
+ is_exec = (handler.executable == 'T')
+ return _DAVStat(size, is_dir, is_exec)
+
class PUTRequest(_urllib2_wrappers.Request):
@@ -601,7 +738,7 @@
abspath = self._remote_path(relpath)
propfind = """<?xml version="1.0" encoding="utf-8" ?>
<D:propfind xmlns:D="DAV:">
- <D:allprop/>
+ <D:prop/>
</D:propfind>
"""
request = _urllib2_wrappers.Request('PROPFIND', abspath, propfind,
@@ -616,11 +753,6 @@
# More precisely some intermediate directories are missing
raise errors.NoSuchFile(abspath)
if code != 207:
- # As we don't want to accept overwriting abs_to, 204
- # (meaning abs_to was existing (but empty, the
- # non-empty case is 412)) will be an error, a server
- # bug even, since we require explicitely to not
- # overwrite.
self._raise_http_error(abspath, response,
'unable to list %r directory' % (abspath))
return _extract_dir_content(abspath, response)
@@ -642,6 +774,33 @@
"""See Transport.rmdir."""
self.delete(relpath) # That was easy thanks DAV
+ def stat(self, relpath):
+ """See Transport.stat.
+
+ We provide a limited implementation for bzr needs.
+ """
+ abspath = self._remote_path(relpath)
+ propfind = """<?xml version="1.0" encoding="utf-8" ?>
+ <D:propfind xmlns:D="DAV:">
+ <D:allprop/>
+ </D:propfind>
+"""
+ request = _urllib2_wrappers.Request('PROPFIND', abspath, propfind,
+ {'Depth': 0},
+ accepted_errors=[207, 404, 409,])
+ response = self._perform(request)
+
+ code = response.code
+ if code == 404:
+ raise errors.NoSuchFile(abspath)
+ if code == 409:
+ # More precisely some intermediate directories are missing
+ raise errors.NoSuchFile(abspath)
+ if code != 207:
+ self._raise_http_error(abspath, response,
+ 'unable to list %r directory' % (abspath))
+ return _extract_stat_info(abspath, response)
+
# TODO: Before
# www.ietf.org/internet-drafts/draft-suma-append-patch-00.txt
# becomes a real RFC and gets implemented, we can try to
More information about the bazaar-commits
mailing list