Commit d414c731 by rfkelly0

adding initial version of DAVFS - no tests yet...

parent da8f721f
0.4:
* New FS implementations (under fs.contrib):
* BigFS: read contents of a BIG file (C&C game file format)
* DAVFS: access a remote files stored on a WebDAV server
0.3: 0.3:
* New FS implementations: * New FS implementations:
......
# Copyright (c) 2009-2010, Cloud Matrix Pty. Ltd.
# All rights reserved; available under the terms of the MIT License.
"""
fs.contrib.davfs.util: utils for FS WebDAV implementation.
"""
import os
import re
import cookielib
def get_fileno(file):
"""Get the os-level fileno of a file-like object.
This function decodes several common file wrapper structures in an attempt
to determine the underlying OS-level fileno for an object.
"""
while not hasattr(file,"fileno"):
if hasattr(file,"file"):
file = file.file
elif hasattr(file,"_file"):
file = file._file
elif hasattr(file,"_fileobj"):
file = file._fileobj
else:
raise AttributeError
return file.fileno()
def file_chunks(f,chunk_size=1024*64):
"""Generator yielding chunks of a file.
This provides a simple way to iterate through binary data chunks from
a file. Recall that using a file directly as an iterator generates the
*lines* from that file, which is useless and very inefficient for binary
data.
"""
chunk = f.read(chunk_size)
while chunk:
yield chunk
chunk = f.read(chunk_size)
def normalize_req_body(body,chunk_size=1024*64):
"""Convert given request body into (size,data_iter) pair.
This function is used to accept a variety of different inputs in HTTP
requests, converting them to a standard format.
"""
if hasattr(body,"read"):
try:
size = int(body.size)
except (AttributeError,TypeError):
try:
size = os.fstat(get_fileno(body)).st_size
except (AttributeError,OSError):
size = None
return (size,file_chunks(body,chunk_size))
else:
body = str(body)
return (len(body),[body])
class FakeReq:
"""Compatability interface to use cookielib with raw httplib objects."""
def __init__(self,connection,scheme,path):
self.connection = connection
self.scheme = scheme
self.path = path
def get_full_url(self):
return self.scheme + "://" + self.connection.host + self.path
def get_type(self):
return self.scheme
def get_host(self):
return self.connection.host
def is_unverifiable(self):
return True
def get_origin_req_host(self):
return self.connection.host
def has_header(self,header):
return False
def add_unredirected_header(self,header,value):
self.connection.putheader(header,value)
class FakeResp:
"""Compatability interface to use cookielib with raw httplib objects."""
def __init__(self,response):
self.response = response
def info(self):
return self
def getheaders(self,header):
header = header.lower()
headers = self.response.getheaders()
return [v for (h,v) in headers if h.lower() == header]
# The standard cooklielib cookie parser doesn't seem to handle multiple
# cookies correctory, so we replace it with a better version. This code
# is a tweaked version of the cookielib function of the same name.
#
_test_cookie = "sessionid=e9c9b002befa93bd865ce155270307ef; Domain=.cloud.me; expires=Wed, 10-Feb-2010 03:27:20 GMT; httponly; Max-Age=1209600; Path=/, sessionid_https=None; Domain=.cloud.me; expires=Wed, 10-Feb-2010 03:27:20 GMT; httponly; Max-Age=1209600; Path=/; secure"
if len(cookielib.parse_ns_headers([_test_cookie])) != 2:
def parse_ns_headers(ns_headers):
"""Improved parser for netscape-style cookies.
This version can handle multiple cookies in a single header.
"""
known_attrs = ("expires", "domain", "path", "secure","port", "max-age")
result = []
for ns_header in ns_headers:
pairs = []
version_set = False
for ii, param in enumerate(re.split(r"(;\s)|(,\s(?=[a-zA-Z0-9_\-]+=))", ns_header)):
if param is None:
continue
param = param.rstrip()
if param == "" or param[0] == ";":
continue
if param[0] == ",":
if pairs:
if not version_set:
pairs.append(("version", "0"))
result.append(pairs)
pairs = []
continue
if "=" not in param:
k, v = param, None
else:
k, v = re.split(r"\s*=\s*", param, 1)
k = k.lstrip()
if ii != 0:
lc = k.lower()
if lc in known_attrs:
k = lc
if k == "version":
# This is an RFC 2109 cookie.
version_set = True
if k == "expires":
# convert expires date to seconds since epoch
if v.startswith('"'): v = v[1:]
if v.endswith('"'): v = v[:-1]
v = cookielib.http2time(v) # None if invalid
pairs.append((k, v))
if pairs:
if not version_set:
pairs.append(("version", "0"))
result.append(pairs)
return result
cookielib.parse_ns_headers = parse_ns_headers
assert len(cookielib.parse_ns_headers([_test_cookie])) == 2
# Copyright (c) 2009-2010, Cloud Matrix Pty. Ltd.
# All rights reserved; available under the terms of the MIT License.
"""
fs.contrib.davfs.xmlobj: dexml model definitions for WebDAV
This module defines the various XML elemen structures for WebDAV as a set
of dexml.Model subclasses.
"""
from urlparse import urlparse, urlunparse
from httplib import responses as STATUS_CODE_TEXT
STATUS_CODE_TEXT[207] = "Multi-Status"
import dexml
from dexml import fields
Error = dexml.Error
class _davbase(dexml.Model):
"""Base class for all davfs XML models."""
class meta:
namespace = "DAV:"
namespace_prefix = "D"
class HrefField(fields.String):
"""Field representing a <href> tag."""
def __init__(self,*args,**kwds):
kwds["tagname"] = "href"
super(HrefField,self).__init__(*args,**kwds)
def parse_value(self,value):
url = urlparse(value.encode("UTF-8"))
return urlunparse((url.scheme,url.netloc,url.path,url.params,url.query,url.fragment))
def render_value(self,value):
url = urlparse(value.encode("UTF-8"))
return urlunparse((url.scheme,url.netloc,url.path,url.params,url.query,url.fragment))
class TimeoutField(fields.Field):
"""Field representing a WebDAV timeout value."""
def __init__(self,*args,**kwds):
if "tagname" not in kwds:
kwds["tagname"] = "timeout"
super(TimeoutField,self).__init__(*args,**kwds)
@classmethod
def parse_value(cls,value):
if value == "Infinite":
return None
if value.startswith("Second-"):
return int(value[len("Second-"):])
raise ValueError("invalid timeout specifier: %s" % (value,))
def render_value(self,value):
if value is None:
return "Infinite"
else:
return "Second-" + str(value)
class StatusField(fields.Value):
"""Field representing a WebDAV status-line value.
The value may be set as either a string or an integer, and is converted
into a StatusString instance.
"""
def __init__(self,*args,**kwds):
kwds["tagname"] = "status"
super(StatusField,self).__init__(*args,**kwds)
def __get__(self,instance,owner):
val = super(StatusField,self).__get__(instance,owner)
if val is not None:
val = StatusString(val,instance,self)
return val
def __set__(self,instance,value):
if isinstance(value,basestring):
# sanity check it
bits = value.split(" ")
if len(bits) < 3 or bits[0] != "HTTP/1.1":
raise ValueError("Not a valid status: '%s'" % (value,))
int(bits[1])
elif isinstance(value,int):
# convert it to a message
value = StatusString._value_for_code(value)
super(StatusField,self).__set__(instance,value)
class StatusString(str):
"""Special string representing a HTTP status line.
It's a string, but it exposes the integer attribute "code" giving just
the actual response code.
"""
def __new__(cls,val,inst,owner):
return str.__new__(cls,val)
def __init__(self,val,inst,owner):
self._owner = owner
self._inst = inst
@staticmethod
def _value_for_code(code):
msg = STATUS_CODE_TEXT.get(code,"UNKNOWN STATUS CODE")
return "HTTP/1.1 %d %s" % (code,msg)
def _get_code(self):
return int(self.split(" ")[1])
def _set_code(self,code):
newval = self._value_for_code(code)
self._owner.__set__(self._inst,newval)
code = property(_get_code,_set_code)
class multistatus(_davbase):
"""XML model for a multi-status response message."""
responses = fields.List("response",minlength=1)
description = fields.String(tagname="responsedescription",required=False)
class response(_davbase):
"""XML model for an individual response in a multi-status message."""
href = HrefField()
# TODO: ensure only one of hrefs/propstats
hrefs = fields.List(HrefField(),minlength=1,required=False)
status = StatusField(required=False)
propstats = fields.List("propstat",minlenth=1,required=False)
description = fields.String(tagname="responsedescription",required=False)
class propstat(_davbase):
"""XML model for a propstat response message."""
props = fields.XmlNode(tagname="prop",encoding="UTF-8")
status = StatusField()
description = fields.String(tagname="responsedescription",required=False)
class propfind(_davbase):
"""XML model for a propfind request message."""
allprop = fields.Boolean(tagname="allprop",required=False)
propname = fields.Boolean(tagname="propname",required=False)
prop = fields.XmlNode(tagname="prop",required=False,encoding="UTF-8")
class propertyupdate(_davbase):
"""XML model for a propertyupdate request message."""
commands = fields.List(fields.Choice("remove","set"))
class remove(_davbase):
"""XML model for a propertyupdate remove command."""
props = fields.XmlNode(tagname="prop",encoding="UTF-8")
class set(_davbase):
"""XML model for a propertyupdate set command."""
props = fields.XmlNode(tagname="prop",encoding="UTF-8")
class lockdiscovery(_davbase):
"""XML model for a lockdiscovery request message."""
locks = fields.List("activelock")
class activelock(_davbase):
"""XML model for an activelock response message."""
lockscope = fields.Model("lockscope")
locktype = fields.Model("locktype")
depth = fields.String(tagname="depth")
owner = fields.XmlNode(tagname="owner",encoding="UTF-8")
timeout = TimeoutField()
locktoken = fields.Model("locktoken")
class lockscope(_davbase):
"""XML model for a lockscope response message."""
shared = fields.Boolean(tagname="shared",empty_only=True)
exclusive = fields.Boolean(tagname="exclusive",empty_only=True)
class locktoken(_davbase):
"""XML model for a locktoken response message."""
tokens = fields.List(HrefField())
class lockentry(_davbase):
"""XML model for a lockentry response message."""
lockscope = fields.Model("lockscope")
locktype = fields.Model("locktype")
class lockinfo(_davbase):
"""XML model for a lockinfo response message."""
lockscope = fields.Model("lockscope")
locktype = fields.Model("locktype")
owner = fields.XmlNode(tagname="owner",encoding="UTF-8")
class locktype(_davbase):
"""XML model for a locktype response message."""
type = fields.XmlNode(encoding="UTF-8")
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment