[Dextrose] [sugar PATCH v4] uy#1769: 1-to-N Feature.

Anish Mangal anish at activitycentral.com
Wed May 2 14:20:23 EDT 2012


-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On Wed 02 May 2012 11:45:27 PM IST, Ajay Garg wrote:
>
>
> On Wed, May 2, 2012 at 11:16 PM, Anish Mangal
> <anish at activitycentral.com <mailto:anish at activitycentral.com>> wrote:
>
>     -----BEGIN PGP SIGNED MESSAGE-----
>     Hash: SHA1
>
>     When both the XO's are connected to the network and see each other,
>     sharing happens fine. I am able to copy journal entries but if the
>     host XO is removed from the network, and I try to access shares folder
>     on the client, sugar freezes on the client.
>
>
> Damn, I think I missed this case ::
>
> Client clicks on "Access Share" -> Server's Share's icon appears ->
> Server gets disconnected -> Client cannot connect to Server's Share by
> clicking on the Server's Share's Icon.
>
>
>
> Anish, please check if the following case works (which was fixed in
> v1->v2)::
>
> Client clicks on "Access Share" -> Server's Share's icon appears ->
> Client clicks on Server's Share's icon -> Server's Share's entries are
> locally mounted -> Server gets disconnected -> Client tries to copy a
> entry -> Error message should appear (both for batch-mode, and
> single-mode).
>
>

yes, the error occurs, and after clicking okay, the busy mode mouse
cursor doesn't turn back to normal

However, if you are in batch mode and after a failure, you reconnect
the server, the next copy operations succeed, and the busy mode cursor
is also gone.

I tested this in your v4 patch, let me know if you want this tested in
v2 patch as well?

>
>
> Regards,
> Ajay
>
>
>     I have attached the shell.log if it helps.
>
>     IIRC, in the earlier (v1/2) patch, there used to be exception in the
>     log, but now sugar UI hangs requiring a restart.
>
>     Also, I should have made it more clear earlier, re: the icons
>
>     In the current patch (with the artwork patch applied), The icon for
>     user's computer's shares folder is nice. We also want the icons of the
>     client XO's to be the same as well (i.e. not the 'XO' icon)
>
>     When this happens, the icons of the host and client XO's will look the
>     same, only differentiated by their color schemes, and accessing their
>     palette.
>
>     I'll add a note to the ticket linking to the rpm containing the
>     earlier version patch.
>
>     On 05/02/2012 11:13 AM, Ajay Garg wrote:
>     > The feature specs, and workflow-screenshots, are listed at ::
>     > http://wiki.sugarlabs.org/go/Features/Transfer_to_many_options
>     >
>     >
>     >
>     > Changes of version-4 over version-3 ::
>     > --------------------------------------
>     >
>     > NOTE ::
>     >
>     > The WebDAV backend-library being used is the one from
>     >
>     https://launchpad.net/python-webdav-lib/trunk/0.3.0/+download/Python_WebDAV_Library-0.3.0.zip
>     >
>     >  The reason for using this, is purely based on not
>     > reinventing-the-wheel. Moreover, it served the following three
>     > major purposes ::
>     >
>     > (i)   Open-Source, in python.
>     >
>     > (ii)  It provided the API for accessing URLs of type ::
>     >
>     > * dav://1.2.3.4/webdav/ <http://1.2.3.4/webdav/> *
>     dav://1.2.3.4/webdav/dir_1 <http://1.2.3.4/webdav/dir_1> *
>     > dav://1.2.3.4/webdav/dir_1/dir_2 <http://1.2.3.4/webdav/dir_1/dir_2>
>     >
>     > (iii) It provided the API for downloading a resource (file) of type
>     > ::
>     >
>     > * dav://1.2.3.4/webdav/1.txt <http://1.2.3.4/webdav/1.txt> *
>     dav://1.2.3.4/webdav/dir_1/2.png <http://1.2.3.4/webdav/dir_1/2.png> *
>     > dav://1.2.3.4/webdav/dir_1/dir_2/1743.avi
>     <http://1.2.3.4/webdav/dir_1/dir_2/1743.avi>
>     >
>     >
>     > Thanks to Sascha, for the motivation of making it absolutely clear
>     > and explicit that the reason for using this 3rd-party library be
>     > mentioned (since I myself had to struggle quite a bit for finding
>     > the suitable one :) )
>     >
>     >
>     >
>     >
>     >
>     ===========================================================================
>     >
>     >
>     >
>     >
>     >
>     > Changes of version-3 over version-2 ::
>     > --------------------------------------
>     >
>     > a) Added new icon for "Shares" folder.
>     >
>     > For this to take effect, patch at
>     > http://patchwork.sugarlabs.org/patch/1419/ must be applied.
>     >
>     > Thanks to Frederick Grose for the image, and Anish for validating
>     > it.
>     >
>     >
>     >
>     >
>     >
>     ===========================================================================
>     >
>     >
>     >
>     >
>     >
>     > Changes of version-2 over version-1 ::
>     > --------------------------------------
>     >
>     > a) Fixed the bug, wherein it was required that activities should
>     > NOT be launched, by clicking on the icons in
>     > "locally-mounted-remote-shares" listview.
>     >
>     > Thanks Anish for providing the solution to this :)
>     >
>     >
>     >
>     > b) Fixed the bug (Thanks Anish for catching this) ::
>     >
>     > * Host adds files to share folder * Client adds the host's share in
>     > journal view, journal entries are visible * Host disconnects from
>     > the network * Client tries to copy/Client tries to access remote
>     > share folder * Exception in logs * UI Freeze.
>     >
>     >
>     > The solution is to "catch" the Connection-Failed exception, and
>     > display the error-message, on similar lines as "Entries without a
>     > file cannot be copied."
>     >
>     >
>     >
>     > c) Did "Code Extraction" from "src/webdav/WebdavClient.py" to
>     > "src/jarabe/journal/webdavmanager.py". Now, all files in
>     > "src/webdav" and "src/webdav/acp" directories contain code present
>     > at
>     >
>     https://launchpad.net/python-webdav-lib/trunk/0.3.0/+download/Python_WebDAV_Library-0.3.0.zip
>     >
>     >  Thanks Anish, for providing the motivation.
>     >
>     >
>     >
>     >
>     > configure.ac <http://configure.ac>                          |
>      4 +- src/Makefile.am
>     > |    4 +- src/jarabe/journal/Makefile.am        |    5 +-
>     > src/jarabe/journal/journalactivity.py |    3 +
>     > src/jarabe/journal/journaltoolbox.py  |    7 +
>     > src/jarabe/journal/listview.py        |    9 +
>     > src/jarabe/journal/model.py           |  205 ++++++++-
>     > src/jarabe/journal/palettes.py        |   78 +++
>     > src/jarabe/journal/volumestoolbar.py  |   94 ++++-
>     > src/jarabe/journal/webdavmanager.py   |  225 +++++++++
>     > src/jarabe/model/buddy.py             |   11 +
>     > src/jarabe/model/neighborhood.py      |    3 +
>     > src/jarabe/view/buddymenu.py          |   11 +
>     > src/jarabe/view/palettes.py           |   33 ++
>     > src/webdav/Condition.py               |  475 +++++++++++++++++++
>     > src/webdav/Connection.py              |  321 +++++++++++++
>     > src/webdav/Constants.py               |  199 ++++++++
>     > src/webdav/Makefile.am                |   20 +
>     > src/webdav/NameCheck.py               |  193 ++++++++
>     > src/webdav/Utils.py                   |  154 ++++++
>     > src/webdav/VersionHandler.py          |  198 ++++++++
>     > src/webdav/WebdavClient.py            |  833
>     > +++++++++++++++++++++++++++++++++ src/webdav/WebdavRequests.py
>     > |  205 ++++++++ src/webdav/WebdavResponse.py          |  525
>     > +++++++++++++++++++++ src/webdav/__init__.py                |   16
>     > + src/webdav/acp/Ace.py                 |  293 ++++++++++++
>     > src/webdav/acp/AceHandler.py          |  182 +++++++
>     > src/webdav/acp/Acl.py                 |  311 ++++++++++++
>     > src/webdav/acp/GrantDeny.py           |  241 ++++++++++
>     > src/webdav/acp/Makefile.am            |   12 +
>     > src/webdav/acp/Principal.py           |  189 ++++++++
>     > src/webdav/acp/Privilege.py           |  125 +++++
>     > src/webdav/acp/__init__.py            |   33 ++
>     > src/webdav/davlib.py                  |  336 +++++++++++++
>     > src/webdav/logger.py                  |   51 ++
>     > src/webdav/qp_xml.py                  |  240 ++++++++++
>     > src/webdav/uuid_.py                   |  476 +++++++++++++++++++ 37
>     > files changed, 6301 insertions(+), 19 deletions(-) create mode
>     > 100644 src/jarabe/journal/webdavmanager.py create mode 100644
>     > src/webdav/Condition.py create mode 100644
>     > src/webdav/Connection.py create mode 100644
>     > src/webdav/Constants.py create mode 100644 src/webdav/Makefile.am
>     > create mode 100644 src/webdav/NameCheck.py create mode 100644
>     > src/webdav/Utils.py create mode 100644
>     > src/webdav/VersionHandler.py create mode 100644
>     > src/webdav/WebdavClient.py create mode 100644
>     > src/webdav/WebdavRequests.py create mode 100644
>     > src/webdav/WebdavResponse.py create mode 100644
>     > src/webdav/__init__.py create mode 100644 src/webdav/acp/Ace.py
>     > create mode 100644 src/webdav/acp/AceHandler.py create mode 100644
>     > src/webdav/acp/Acl.py create mode 100644
>     > src/webdav/acp/GrantDeny.py create mode 100644
>     > src/webdav/acp/Makefile.am create mode 100644
>     > src/webdav/acp/Principal.py create mode 100644
>     > src/webdav/acp/Privilege.py create mode 100644
>     > src/webdav/acp/__init__.py create mode 100644 src/webdav/davlib.py
>     > create mode 100644 src/webdav/logger.py create mode 100644
>     > src/webdav/qp_xml.py create mode 100644 src/webdav/uuid_.py
>     >
>     > diff --git a/configure.ac <http://configure.ac> b/configure.ac
>     <http://configure.ac> index 8e6d871..e04bdec
>     > 100644 --- a/configure.ac <http://configure.ac> +++
>     b/configure.ac <http://configure.ac> @@ -61,8 +61,6 @@
>     > extensions/cpsection/modemconfiguration/config.py
>     > extensions/cpsection/Makefile
>     > extensions/cpsection/network/Makefile
>     > extensions/cpsection/power/Makefile
>     > -extensions/cpsection/updater/backends/Makefile
>     > -extensions/cpsection/updater/Makefile
>     > extensions/deviceicon/Makefile extensions/globalkey/Makefile
>     > extensions/Makefile @@ -79,6 +77,8 @@ src/jarabe/model/Makefile
>     > src/jarabe/util/Makefile src/jarabe/util/telepathy/Makefile
>     > src/jarabe/view/Makefile +src/webdav/acp/Makefile
>     > +src/webdav/Makefile src/Makefile ])
>     >
>     > diff --git a/src/Makefile.am b/src/Makefile.am index
>     > 83571a4..765da8b 100644 --- a/src/Makefile.am +++
>     > b/src/Makefile.am @@ -1 +1,3 @@ -SUBDIRS = jarabe +SUBDIRS =
>     > \ +        jarabe   \ +       webdav diff --git
>     > a/src/jarabe/journal/Makefile.am b/src/jarabe/journal/Makefile.am
>     > index f24dcfe..8efca6d 100644 --- a/src/jarabe/journal/Makefile.am
>     > +++ b/src/jarabe/journal/Makefile.am @@ -15,5 +15,6 @@ sugar_PYTHON
>     > =                             \ model.py                      \
>     objectchooser.py              \ palettes.py                   \ -
>     > volumestoolbar.py                     \ -     processdialog.py +
>          volumestoolbar.py               \ +
>     > processdialog.py                \ +   webdavmanager.py diff --git
>     > a/src/jarabe/journal/journalactivity.py
>     > b/src/jarabe/journal/journalactivity.py index fa308cd..1b841e9
>     > 100644 --- a/src/jarabe/journal/journalactivity.py +++
>     > b/src/jarabe/journal/journalactivity.py @@ -485,6 +485,9 @@ class
>     > JournalActivity(JournalWindow): def is_editing_mode_present(self):
>     > return self._editing_mode
>     >
>     > +    def get_volumes_toolbar(self): +        return
>     > self._volumes_toolbar +
>     >
>     > def get_journal(): global _journal diff --git
>     > a/src/jarabe/journal/journaltoolbox.py
>     > b/src/jarabe/journal/journaltoolbox.py index fd14826..c3614d7
>     > 100644 --- a/src/jarabe/journal/journaltoolbox.py +++
>     > b/src/jarabe/journal/journaltoolbox.py @@ -624,6 +624,13 @@ class
>     > BatchEraseButton(ToolButton, palettes.ActionItem):
>     > show_not_completed_ops_info=True) self.props.tooltip = _('Erase')
>     >
>     > +        # De-sensitize Batch-Erase button, for
>     > locally-mounted-remote-shares. +        from
>     > jarabe.journal.journalactivity import get_mount_point +
>     > current_mount_point = get_mount_point() + +        if
>     >
>     model.is_mount_point_for_locally_mounted_remote_share(current_mount_point):
>     >
>     >
>     +            self.set_sensitive(False)
>     > + def _get_actionable_signal(self): return 'clicked'
>     >
>     > diff --git a/src/jarabe/journal/listview.py
>     > b/src/jarabe/journal/listview.py index 8522dca..10f468f 100644 ---
>     > a/src/jarabe/journal/listview.py +++
>     > b/src/jarabe/journal/listview.py @@ -634,6 +634,15 @@ class
>     > ListView(BaseListView): self.emit('volume-error', message,
>     > severity)
>     >
>     > def __icon_clicked_cb(self, cell, path): +        # For
>     > locally-mounted remote shares, we do not want to launch +        #
>     > by clicking on the icons. +        # So, check if this is a part of
>     > locally-mounted-remote share, +        # and if yes, return,
>     > without doing anything. +        from
>     > jarabe.journal.journalactivity import get_mount_point +
>     > current_mount_point = get_mount_point() +        if
>     >
>     model.is_mount_point_for_locally_mounted_remote_share(current_mount_point):
>     >
>     >
>     +            return
>     > + row = self.tree_view.get_model()[path] metadata =
>     > model.get(row[ListModel.COLUMN_UID]) misc.resume(metadata) diff
>     > --git a/src/jarabe/journal/model.py b/src/jarabe/journal/model.py
>     > index 83e216f..527f78d 100644 --- a/src/jarabe/journal/model.py +++
>     > b/src/jarabe/journal/model.py @@ -20,6 +20,7 @@
>     >
>     > import logging import os +import stat import errno import
>     > subprocess from datetime import datetime @@ -36,11 +37,14 @@ import
>     > gobject import dbus import gio import gconf +import string
>     >
>     > from sugar import dispatch from sugar import mime from sugar import
>     > util
>     >
>     > +from jarabe.journal.webdavmanager import
>     > get_remote_webdav_share_metadata + DS_DBUS_SERVICE =
>     > 'org.laptop.sugar.DataStore' DS_DBUS_INTERFACE =
>     > 'org.laptop.sugar.DataStore' DS_DBUS_PATH =
>     > '/org/laptop/sugar/DataStore' @@ -425,6 +429,123 @@ class
>     > InplaceResultSet(BaseResultSet): return
>     >
>     >
>     > +class RemoteShareResultSet(object): +    def __init__(self,
>     > ip_address, query): +        self._ip_address = ip_address +
>     > self._file_list = [] + +        self.ready = dispatch.Signal() +
>     > self.progress = dispatch.Signal() + +        # First time, query is
>     > none. +        if query is None: +            return + +
>     > query_text = query.get('query', '') +        if
>     > query_text.startswith('"') and query_text.endswith('"'): +
>     > self._regex = re.compile('*%s*' % query_text.strip(['"'])) +
>     > elif query_text: +            expression = '' +            for word
>     > in query_text.split(' '): +                expression +=
>     > '(?=.*%s.*)' % word +            self._regex =
>     > re.compile(expression, re.IGNORECASE) +        else: +
>     > self._regex = None + +        if query.get('timestamp', ''): +
>     > self._date_start = int(query['timestamp']['start']) +
>     > self._date_end = int(query['timestamp']['end']) +        else: +
>     > self._date_start = None +            self._date_end = None + +
>     > self._mime_types = query.get('mime_type', []) + +        self._sort
>     > = query.get('order_by', ['+timestamp'])[0] + +    def setup(self):
>     > +        metadata_list_complete =
>     > get_remote_webdav_share_metadata(self._ip_address) +        for
>     > metadata in metadata_list_complete: + +            add_to_list =
>     > False +            if self._regex is not None: +                for
>     > f in ['fulltext', 'title', +
>     > 'description', 'tags']: +                    if f in metadata and
>     > \ +                            self._regex.match(metadata[f]): +
>     > add_to_list = True +                        break +
>     > else: +                add_to_list = True +            if not
>     > add_to_list: +                continue + +            add_to_list =
>     > False +            if self._date_start is not None: +
>     > if metadata['timestamp'] > self._date_start: +
>     > add_to_list = True +            else: +                add_to_list
>     > = True +            if not add_to_list: +                continue
>     > + +            add_to_list = False +            if self._date_end
>     > is not None: +                if metadata['timestamp'] <
>     > self._date_end: +                    add_to_list = True +
>     > else: +                add_to_list = True +            if not
>     > add_to_list: +                continue + +            add_to_list =
>     > False +            if self._mime_types: +                mime_type
>     > = metadata['mime_type'] +                if mime_type in
>     > self._mime_types: +                    add_to_list = True +
>     > else: +                add_to_list = True +            if not
>     > add_to_list: +                continue + +            # If control
>     > reaches here, the current metadata has passed +            # out
>     > all filter-tests. +            file_info = (metadata['timestamp'],
>     > +                         metadata['creation_time'], +
>     > metadata['filesize'], +                         metadata) +
>     > self._file_list.append(file_info) + +        if self._sort[1:] ==
>     > 'filesize': +            keygetter = itemgetter(2) +        elif
>     > self._sort[1:] == 'creation_time': +            keygetter =
>     > itemgetter(1) +        else: +            # timestamp +
>     > keygetter = itemgetter(0) + +        self._file_list.sort(lambda a,
>     > b: cmp(b, a), +                             key=keygetter, +
>     > reverse=(self._sort[0] == '-')) + +        self.ready.send(self) +
>     > +    def get_length(self): +        return len(self._file_list) + +
>     > length = property(get_length) + +    def seek(self, position): +
>     > self._position = position + +    def read(self): +
>     > modified_timestamp, creation_timestamp, filesize, metadata =
>     > self._file_list[self._position] +        return  metadata + +
>     > def stop(self): +        self._stopped = True + + def
>     > _get_file_metadata(path, stat, fetch_preview=True): """Return the
>     > metadata from the corresponding file.
>     >
>     > @@ -436,10 +557,16 @@ def _get_file_metadata(path, stat,
>     > fetch_preview=True): dir_path = os.path.dirname(path) metadata =
>     > _get_file_metadata_from_json(dir_path, filename, fetch_preview) if
>     > metadata: +        # For Documents/Shares/Mounted-Drives. +
>     > # Special case: for locally-mounted-remote-files, ensure that +
>     > # "metadata['filesize' is already present before-hand. This +
>     > # will have to be done at the time of fetching +        #
>     > webdav-properties per resource. if 'filesize' not in metadata:
>     > metadata['filesize'] = stat.st_size return metadata
>     >
>     > +    # For Journal. return {'uid': path, 'title':
>     > os.path.basename(path), 'timestamp': stat.st_mtime, @@ -529,11
>     > +656,33 @@ def find(query_, page_size): raise ValueError('Exactly
>     > one mount point must be specified')
>     >
>     > if mount_points[0] == '/': +        """ +        For Journal. +
>     > """ return DatastoreResultSet(query, page_size) +    elif
>     > is_mount_point_for_locally_mounted_remote_share(mount_points[0]): +
>     > """ +        For Locally-Mounted-Remote-Shares. +        Regex
>     > Matching is used, to ensure that the mount-point is an +
>     > IP-Address. +        """ +        return
>     > RemoteShareResultSet(mount_points[0], query) else: +        """ +
>     > For Documents/Shares/Mounted-Drives. +        """ return
>     > InplaceResultSet(query, page_size, mount_points[0])
>     >
>     >
>     > +def is_mount_point_for_locally_mounted_remote_share(mount_point):
>     > +    import re + +    pattern =
>     > '[1-9][0-9]{0,2}\.[0-9]{0,3}\.[0-9]{0,3}\.[0-9]{0,3}' +    if
>     > re.match(pattern, mount_point) is None: +        return False +
>     > return True + + def _get_mount_point(path): dir_path =
>     > os.path.dirname(path) while dir_path: @@ -544,14 +693,45 @@ def
>     > _get_mount_point(path): return None
>     >
>     >
>     > +def is_locally_mounted_remote_share(path): +    return
>     > string.find(path, '/tmp/') == 0 + + +def
>     > extract_ip_address_from_locally_mounted_remote_share_path(path): +
>     > """ +    Path is of type :: + +
>      /tmp/127.0.0.1/webdav/a.txt <http://127.0.0.1/webdav/a.txt>
>     > +    """ +    return path.split('/')[2] + + def get(object_id):
>     > """Returns the metadata for an object """ -    if
>     > os.path.exists(object_id): -        stat = os.stat(object_id) +
>     > if (os.path.exists(object_id) or
>     > (is_locally_mounted_remote_share(object_id))): +        """ +
>     > For Documents/Shares/Mounted-Drives/Locally-Mounted-Remote-Shares,
>     > +        where ".Sugar-Metadata" folder exists. + +        The only
>     > thing is that, for locally-mounted-remote-shares, the +
>     > "file" is not physically present. +        """ +        if
>     > os.path.exists(object_id): +            # if the file is physically
>     > present, derive file-metadata +            # by physical
>     > examination of the file. +            stat = os.stat(object_id) +
>     > else: +            # if the file is remote, derive file-metadata by
>     > fetching +            # properties remotely (webdav properties). +
>     > stat = None + metadata = _get_file_metadata(object_id, stat)
>     > metadata['mountpoint'] = _get_mount_point(object_id) else: +
>     > """ +        For journal, where ".Sugar-Metadata" folder does not
>     > exists. +        """ metadata =
>     > _get_datastore().get_properties(object_id, byte_arrays=True)
>     > metadata['mountpoint'] = '/' return metadata @@ -561,9 +741,16 @@
>     > def get_file(object_id): """Returns the file for an object """ if
>     > os.path.exists(object_id): +        """ +        For
>     > Documents/Shares/Mounted-Drives/ +
>     > Locally-Mounted-Remote-Shares-in-case-when-it-is-present-already. +
>     > """ logging.debug('get_file asked for file with path %r',
>     > object_id) return object_id else: +        """ +        For
>     > Journal. +        """ logging.debug('get_file asked for entry with
>     > id %r', object_id) file_path =
>     > _get_datastore().get_filename(object_id) if file_path: @@ -754,6
>     > +941,20 @@ def _write_entry_on_external_device(metadata,
>     > file_path): _rename_entry_on_external_device(file_path,
>     > destination_path, metadata_dir_path)
>     >
>     > +    # For "Shares" folder, we need to set the permissions of the
>     > newly +    # copied file to 0777, else it will not be accessible by
>     > "httpd" +    # service. +    if metadata['mountpoint'] ==
>     > '/var/www/web1/web': +        fd = os.open(destination_path,
>     > os.O_RDONLY) +        os.fchmod(fd, stat.S_IRWXU | stat.S_IRWXG |
>     > stat.S_IRWXO) +        os.close(fd) + +        metadata_file_path =
>     > os.path.join(metadata_dir_path, file_name + '.metadata') +
>     > fd = os.open(metadata_file_path, os.O_RDONLY) +
>     > os.fchmod(fd, stat.S_IRWXU | stat.S_IRWXG | stat.S_IRWXO) +
>     > os.close(fd) + + object_id = destination_path created.send(None,
>     > object_id=object_id)
>     >
>     > diff --git a/src/jarabe/journal/palettes.py
>     > b/src/jarabe/journal/palettes.py index 740b65a..5ff6bd6 100644 ---
>     > a/src/jarabe/journal/palettes.py +++
>     > b/src/jarabe/journal/palettes.py @@ -45,6 +45,10 @@ from
>     > jarabe.model import mimeregistry from jarabe.journal import misc
>     > from jarabe.journal import model
>     >
>     > +from webdav.Connection import WebdavError +from
>     > jarabe.journal.webdavmanager import
>     > get_resource_by_ip_address_and_resource_key + + friends_model =
>     > friends.get_model()
>     >
>     > _copy_menu_helper = None @@ -77,6 +81,9 @@ class
>     > ObjectPalette(Palette): Palette.__init__(self, primary_text=title,
>     > icon=activity_icon)
>     >
>     > +        from jarabe.journal.journalactivity import
>     > get_mount_point +        current_mount_point = get_mount_point() +
>     > if misc.get_activities(metadata) or misc.is_bundle(metadata): if
>     > metadata.get('activity_id', ''): resume_label = _('Resume') @@
>     > -86,10 +93,15 @@ class ObjectPalette(Palette): resume_with_label =
>     > _('Start with') menu_item = MenuItem(resume_label,
>     > 'activity-start') menu_item.connect('activate',
>     > self.__start_activate_cb) +            if
>     >
>     model.is_mount_point_for_locally_mounted_remote_share(current_mount_point):
>     >
>     >
>     +                menu_item.set_sensitive(False)
>     > self.menu.append(menu_item) menu_item.show()
>     >
>     > menu_item = MenuItem(resume_with_label, 'activity-start') +
>     > if
>     >
>     model.is_mount_point_for_locally_mounted_remote_share(current_mount_point):
>     >
>     >
>     +                menu_item.set_sensitive(False)
>     > + self.menu.append(menu_item) menu_item.show() start_with_menu =
>     > StartWithMenu(self._metadata) @@ -101,6 +113,7 @@ class
>     > ObjectPalette(Palette): self.menu.append(menu_item)
>     > menu_item.show()
>     >
>     > + menu_item = MenuItem(_('Copy to')) icon =
>     > Icon(icon_name='edit-copy', xo_color=color,
>     > icon_size=gtk.ICON_SIZE_MENU) @@ -120,16 +133,21 @@ class
>     > ObjectPalette(Palette): copy_menu.connect('volume-error',
>     > self.__volume_error_cb) menu_item.set_submenu(copy_menu)
>     >
>     > + if self._metadata['mountpoint'] == '/': menu_item =
>     > MenuItem(_('Duplicate')) icon = Icon(icon_name='edit-duplicate',
>     > xo_color=color, icon_size=gtk.ICON_SIZE_MENU)
>     > menu_item.set_image(icon) menu_item.connect('activate',
>     > self.__duplicate_activate_cb) +            if
>     >
>     model.is_mount_point_for_locally_mounted_remote_share(current_mount_point):
>     >
>     >
>     +                menu_item.set_sensitive(False)
>     > self.menu.append(menu_item) menu_item.show()
>     >
>     > menu_item = MenuItem(_('Send to'), 'document-send') +        if
>     >
>     model.is_mount_point_for_locally_mounted_remote_share(current_mount_point):
>     >
>     >
>     +            menu_item.set_sensitive(False)
>     > self.menu.append(menu_item) menu_item.show()
>     >
>     > @@ -140,14 +158,19 @@ class ObjectPalette(Palette): if detail ==
>     > True: menu_item = MenuItem(_('View Details'), 'go-right')
>     > menu_item.connect('activate', self.__detail_activate_cb) +
>     > if
>     >
>     model.is_mount_point_for_locally_mounted_remote_share(current_mount_point):
>     >
>     >
>     +                menu_item.set_sensitive(False)
>     > self.menu.append(menu_item) menu_item.show()
>     >
>     > menu_item = MenuItem(_('Erase'), 'list-remove')
>     > menu_item.connect('activate', self.__erase_activate_cb) +        if
>     >
>     model.is_mount_point_for_locally_mounted_remote_share(current_mount_point):
>     >
>     >
>     +            menu_item.set_sensitive(False)
>     > self.menu.append(menu_item) menu_item.show()
>     >
>     > + def __start_activate_cb(self, menu_item):
>     > misc.resume(self._metadata)
>     >
>     > @@ -541,6 +564,31 @@ class ActionItem(gobject.GObject):
>     > self._post_operate_per_metadata_per_action(metadata)
>     >
>     > def _file_path_valid(self, metadata): +        from
>     > jarabe.journal.journalactivity import get_mount_point +
>     > current_mount_point = get_mount_point() + +        # Now, for
>     > locally mounted remote-shares, download the file. +        # Note
>     > that, always download the file, to avoid the problems +        # of
>     > stale-cache. +        if
>     >
>     model.is_mount_point_for_locally_mounted_remote_share(current_mount_point):
>     >
>     >
>     +            file_path = metadata['uid']
>     > +            filename = os.path.basename(file_path) +
>     > ip_address =
>     >
>     model.extract_ip_address_from_locally_mounted_remote_share_path(file_path)
>     >
>     >
>     +            resource =
>     get_resource_by_ip_address_and_resource_key(ip_address, '/webdav/' +
>     filename)
>     > +            download_file_path = '/tmp/' + ip_address + '/' +
>     > filename +            try: +
>     > resource.downloadFile(download_file_path) +                return
>     > True +            except WebdavError, e: +
>     > error_message = e +                logging.warn(error_message) +
>     > if self._batch_mode: +
>     > self._handle_error_alert(error_message, metadata) +
>     > else: +                    self.emit('volume-error',
>     > error_message, +                            _('Error')) +
>     > return False + file_path = model.get_file(metadata['uid']) if not
>     > file_path or not os.path.exists(file_path): logging.warn('Entries
>     > without a file cannot be copied.') @@ -708,6 +756,25 @@ class
>     > DocumentsMenu(BaseCopyMenuItem):
>     > self._post_operate_per_metadata_per_action(metadata)
>     >
>     >
>     > +class SharesMenu(BaseCopyMenuItem): +    def __init__(self,
>     > metadata_list, show_editing_alert, +
>     > show_progress_info_alert, batch_mode): +
>     > BaseCopyMenuItem.__init__(self, metadata_list, _('Shares'), +
>     > show_editing_alert, +
>     > show_progress_info_alert, +
>     > batch_mode) + +    def _operate(self, metadata): +        if not
>     > self._file_path_valid(metadata): +            return False +
>     > if not self._metadata_copy_valid(metadata, +
>     > '/var/www/web1/web'): +            return False + +        # This
>     > is sync-operation. Call the post-operation now. +
>     > self._post_operate_per_metadata_per_action(metadata) + + class
>     > FriendsMenu(gtk.Menu): __gtype_name__ = 'JournalFriendsMenu'
>     >
>     > @@ -835,6 +902,17 @@ class CopyMenuHelper(gtk.Menu):
>     > menu.append(documents_menu) documents_menu.show()
>     >
>     > +        if get_mount_point() != '/var/www/web1/web': +
>     > documents_menu = SharesMenu(metadata_list, +
>     > show_editing_alert, +
>     > show_progress_info_alert, +
>     > batch_mode) +
>     >
>     documents_menu.set_image(Icon(icon_name='emblem-neighborhood-shared',
>     >
>     >
>     +
>      icon_size=gtk.ICON_SIZE_MENU))
>     > +            documents_menu.connect('volume-error',
>     > self.__volume_error_cb) +            menu.append(documents_menu) +
>     > documents_menu.show() + if get_mount_point() != '/': client =
>     > gconf.client_get_default() color =
>     > XoColor(client.get_string('/desktop/sugar/user/color')) diff --git
>     > a/src/jarabe/journal/volumestoolbar.py
>     > b/src/jarabe/journal/volumestoolbar.py index 94914e6..2a2436c
>     > 100644 --- a/src/jarabe/journal/volumestoolbar.py +++
>     > b/src/jarabe/journal/volumestoolbar.py @@ -37,8 +37,10 @@ from
>     > sugar.graphics.palette import Palette from sugar.graphics.xocolor
>     > import XoColor from sugar import env
>     >
>     > +from jarabe.frame.notification import NotificationIcon from
>     > jarabe.journal import model -from jarabe.view.palettes import
>     > JournalVolumePalette, JournalXSPalette +from jarabe.view.palettes
>     > import JournalVolumePalette, JournalXSPalette, RemoteSharePalette
>     > +import jarabe.frame
>     >
>     >
>     > _JOURNAL_0_METADATA_DIR = '.olpc.store' @@ -209,6 +211,7 @@ class
>     > VolumesToolbar(gtk.Toolbar):
>     >
>     > def _set_up_volumes(self): self._set_up_documents_button() +
>     > self._set_up_shares_button()
>     >
>     > volume_monitor = gio.volume_monitor_get() self._mount_added_hid =
>     > volume_monitor.connect('mount-added', @@ -219,12 +222,11 @@ class
>     > VolumesToolbar(gtk.Toolbar): for mount in
>     > volume_monitor.get_mounts(): self._add_button(mount)
>     >
>     > -    def _set_up_documents_button(self): -        documents_path =
>     > model.get_documents_path() -        if documents_path is not None:
>     > -            button = DocumentsButton(documents_path) +    def
>     > _set_up_directory_button(self, dir_path, icon_name, label_text): +
>     > if dir_path is not None: +            button =
>     > DirectoryButton(dir_path, icon_name) button.props.group =
>     > self._volume_buttons[0] -            label =
>     > glib.markup_escape_text(_('Documents')) +            label =
>     > glib.markup_escape_text(label_text)
>     > button.set_palette(Palette(label)) button.connect('toggled',
>     > self._button_toggled_cb) button.show() @@ -234,6 +236,39 @@ class
>     > VolumesToolbar(gtk.Toolbar): self._volume_buttons.append(button)
>     > self.show()
>     >
>     > +    def _set_up_documents_button(self): +        documents_path =
>     > model.get_documents_path() +
>     > self._set_up_directory_button(documents_path, +
>     > 'user-documents', +
>     > _('Documents')) + +    def _set_up_shares_button(self): +
>     > shares_dir_path = '/var/www/web1/web' +
>     > self._set_up_directory_button(shares_dir_path, +
>     > 'emblem-neighborhood-shared', +
>     > _('Shares')) + +    def _add_remote_share_button(self, buddy): +
>     > button = RemoteSharesButton(buddy) +        button.props.group =
>     > self._volume_buttons[0] +        label =
>     > glib.markup_escape_text(_('%s\'s share') % \ +
>     > buddy.props.nick) +
>     > button.set_palette(RemoteSharePalette(buddy, button)) +
>     > button.connect('toggled', self._button_toggled_cb) +
>     > button.show() + +        position =
>     > self.get_item_index(self._volume_buttons[-1]) + 1 +
>     > self.insert(button, position) +
>     > self._volume_buttons.append(button) +        self.show() + +
>     > frame = jarabe.frame.get_view() +        notif_icon =
>     > NotificationIcon() +        notif_icon.props.icon_name =
>     > 'computer-xo' +        notif_icon.props.xo_color =
>     > buddy.props.color +        frame.add_notification(notif_icon, +
>     > gtk.CORNER_BOTTOM_RIGHT) + def __mount_added_cb(self,
>     > volume_monitor, mount): self._add_button(mount)
>     >
>     > @@ -271,8 +306,8 @@ class VolumesToolbar(gtk.Toolbar): def
>     > __volume_error_cb(self, button, strerror, severity):
>     > self.emit('volume-error', strerror, severity)
>     >
>     > -    def _button_toggled_cb(self, button): -        if
>     > button.props.active: +    def _button_toggled_cb(self, button,
>     > force_toggle=False): +        if button.props.active or
>     > force_toggle: self.emit('volume-changed', button.mount_point)
>     >
>     > def _unmount_activated_cb(self, menu_item, mount): @@ -300,6
>     > +335,19 @@ class VolumesToolbar(gtk.Toolbar): if
>     > len(self.get_children()) < 2: self.hide()
>     >
>     > +    def _remove_remote_share_button(self, mount_point): +        #
>     > Here, IP_Address is the mount_point. +        for button in
>     > self.get_children(): +            if type(button) ==
>     > RemoteSharesButton and \ +                    button.mount_point ==
>     > mount_point: +
>     > self._volume_buttons.remove(button) +
>     > self.remove(button) +
>     > self.get_children()[0].props.active = True + +
>     > if len(sel.get_children()) < 2: +
>     > self.hide() +                        break; + def
>     > set_active_volume(self, mount): button =
>     > self._get_button_for_mount(mount) button.props.active = True @@
>     > -313,6 +361,12 @@ class VolumesToolbar(gtk.Toolbar): if
>     > button.mount_point != mount_point: button.set_sensitive(sensitive)
>     >
>     > +    def get_journal_button(self): +        return
>     > self._volume_buttons[0] + +    def get_button_toggled_cb(self): +
>     > return self._button_toggled_cb +
>     >
>     > class BaseButton(RadioToolButton): __gsignals__ = { @@ -427,18
>     > +481,34 @@ class JournalButtonPalette(Palette): {'free_space':
>     > free_space / (1024 * 1024)}
>     >
>     >
>     > -class DocumentsButton(BaseButton): +class
>     > DirectoryButton(BaseButton):
>     >
>     > -    def __init__(self, documents_path): -
>     > BaseButton.__init__(self, mount_point=documents_path) +    def
>     > __init__(self, dir_path, icon_name): +
>     > BaseButton.__init__(self, mount_point=dir_path)
>     >
>     > -        self.props.named_icon = 'user-documents' +
>     > self.props.named_icon = icon_name
>     >
>     > client = gconf.client_get_default() color =
>     > XoColor(client.get_string('/desktop/sugar/user/color'))
>     > self.props.xo_color = color
>     >
>     >
>     > +class RemoteSharesButton(BaseButton): + +    def __init__(self,
>     > buddy): +        BaseButton.__init__(self,
>     > mount_point=buddy.props.ip_address) + +        self._buddy = buddy
>     > +        self.props.named_icon = 'computer-xo' +
>     > self.props.xo_color = buddy.props.color +
>     > self._buddy_ip_address = buddy.props.ip_address + +    def
>     > create_palette(self): +        palette =
>     > RemoteSharePalette(self._buddy) +        return palette + + + class
>     > XSButton(ToolButton): def __init__(self):
>     > ToolButton.__init__(self) diff --git
>     > a/src/jarabe/journal/webdavmanager.py
>     > b/src/jarabe/journal/webdavmanager.py new file mode 100644 index
>     > 0000000..cc2a0ff --- /dev/null +++
>     > b/src/jarabe/journal/webdavmanager.py @@ -0,0 +1,225 @@ +import os
>     > +import sys + +import simplejson +import shutil + +from
>     > webdav.Connection import AuthorizationError +from
>     > webdav.WebdavClient import CollectionStorer + +def
>     > get_key_from_resource(resource): +    return resource.path + +def
>     >
>     ensure_correct_remote_webdav_hierarchy(remote_webdav_share_resources,
>     >
>     >
>     +
>     remote_webdav_share_collections):
>     > +    pass +    #assert len(remote_webdav_share_collections.keys())
>     > == 1 + +class WebDavUrlManager(): +    """ +    This class holds
>     > all data, relevant to a WebDavUrl. + +    One thing must be noted,
>     > that a valid WebDavUrl is the one which +    may contain zero or
>     > more resources (files), or zero or more +    collections
>     > (directories). + +    Thus, following are valid WebDavUrls :: + +
>     > dav://1.2.3.4/webdav <http://1.2.3.4/webdav> +
>      dav://1.2.3.4/webdav/dir_1 <http://1.2.3.4/webdav/dir_1> +
>     > dav://1.2.3.4/webdav/dir_1/dir_2
>     <http://1.2.3.4/webdav/dir_1/dir_2> + +    but following are not :: +
>     > +        dav://1.2.3.4/webdav/a.txt <http://1.2.3.4/webdav/a.txt> +
>     > dav://1.2.3.4/webdav/dir_1/b.jpg
>     <http://1.2.3.4/webdav/dir_1/b.jpg> +
>     > dav://1.2.3.4/webdab/dir_1/dir_2/c.avi
>     <http://1.2.3.4/webdab/dir_1/dir_2/c.avi> +    """ + +    def
>     > __init__(self, WebDavUrl, username, password): +
>     > self._WebDavUrl = WebDavUrl +        self._username = username +
>     > self._password = password + +        # Since we are only
>     > instantiating primitive types in +        # "__init__", we may
>     > safely call the class-function here. +
>     > self._fetch_resources_and_collections() + +    def
>     > _get_key_from_resource(self, resource): +        return
>     > resource.path.encode(sys.getfilesystemencoding()) + +    def
>     > _get_number_of_collections(self): +        return
>     > len(self._remote_webdav_share_collections) + +    def
>     > _get_resources_dict(self): +        return
>     > self._remote_webdav_share_resources + +    def
>     > _get_collections_dict(self): +        return
>     > self._remote_webdav_share_collections + +    def
>     > _get_resource_by_key(self, key): +        return
>     > self._remote_webdav_share_resources[key]['resource'] + +    def
>     > _get_metadata_list(self): +        metadata_list = [] +        for
>     > key in self._remote_webdav_share_resources.keys(): +
>     >
>     metadata_list.append(self._remote_webdav_share_resources[key]['metadata'])
>     >
>     >
>     +        return metadata_list
>     > + +    def _get_live_properties(self, resource_key): +
>     > resource_container =
>     > self._remote_webdav_share_resources[resource_key] +        return
>     > resource_container['webdav-properties'] + +    def
>     > _set_metadata_for_resource(self, key, metadata): +
>     > self._remote_webdav_share_resources[key]['metadata'] = metadata + +
>     > def _fetch_resources_and_collections(self): +
>     > webdavConnection = CollectionStorer(self._WebDavUrl,
>     > validateResourceNames=False) + +        authFailures = 0 +
>     > while authFailures < 2: +            try: +
>     > self._remote_webdav_share_resources = {} +
>     > self._remote_webdav_share_collections = {} + +                for
>     > resource, properties in webdavConnection.getCollectionContents(): +
>     > try: +                        key =
>     > self._get_key_from_resource(resource) +
>     > selected_dict = None + +                        if
>     > properties.getResourceType() == 'resource': +
>     > selected_dict = self._remote_webdav_share_resources +
>     > else: +                            selected_dict =
>     > self._remote_webdav_share_collections + +
>     > selected_dict[key] = {} +
>     > selected_dict[key]['resource'] = resource +
>     > selected_dict[key]['webdav-properties'] = properties +
>     > except UnicodeEncodeError: +                        print("Cannot
>     > encode resource path or properties.") +                break #
>     > break out of the authorization failure counter +            except
>     > AuthorizationError, e: +                if self._username is None
>     > or self._password is None: +                    raise
>     > Exception("WebDav username or password is None. Please specify
>     > appropriate values.") + +                if e.authType == "Basic":
>     > +
>     > webdavConnection.connection.addBasicAuthorization(self._username,
>     > self._password) +                elif e.authType == "Digest": +
>     > info = parseDigestAuthInfo(e.authInfo) +
>     > webdavConnection.connection.addDigestAuthorization(self._username,
>     > self._password, realm=info["realm"], qop=info["qop"],
>     > nonce=info["nonce"]) +                else: +
>     > raise +                authFailures += 1 + +webdav_manager = {} +
>     > + +def get_resource_by_ip_address_and_resource_key(ip_address,
>     > key): +    global webdav_manager + +    if ip_address in
>     > webdav_manager.keys(): +        root_webdav =
>     > webdav_manager[ip_address] +        resources_dict =
>     > root_webdav._get_resources_dict() +        resource_dict =
>     > resources_dict[key] +        resource = resource_dict['resource']
>     > + +        return resource + + +def
>     > get_remote_webdav_share_metadata(ip_address): +    protocol =
>     > 'dav://' + +    root_webdav_url = '/webdav' + +
>     > complete_root_url = protocol + ip_address + root_webdav_url + +
>     > root_webdav = WebDavUrlManager(complete_root_url, 'test', 'olpc')
>     > + +    # Keep reference to the "WebDavUrlManager", keyed by
>     > IP-Address. +    global webdav_manager +
>     > webdav_manager[ip_address] = root_webdav + + +    # Assert that the
>     > number of collections is only one at this url +    # (i.e. only
>     > ".Sugar-Metadata" is present). +    assert
>     > root_webdav._get_number_of_collections() == 1 + +
>     > root_sugar_metadata_url = root_webdav_url + '/.Sugar-Metadata' + +
>     > complete_root_sugar_metadata_url = protocol + ip_address +
>     > root_sugar_metadata_url +    root_webdav_sugar_metadata =
>     > WebDavUrlManager(complete_root_sugar_metadata_url, 'test', 'olpc')
>     > + +    # assert that the number of collections is zero at this
>     > url. +    assert
>     > root_webdav_sugar_metadata._get_number_of_collections() == 0 + +
>     > # Now. associate sugar-metadata with each of the "root-webdav" +
>     > # resource. +    root_webdav_resources =
>     > root_webdav._get_resources_dict() +
>     > root_webdav_sugar_metadata_resources =
>     > root_webdav_sugar_metadata._get_resources_dict() + +    # Prepare
>     > the metadata-download folder. +    downloaded_data_root_dir =
>     > '/tmp/' + ip_address +    downloaded_metadata_file_dir =
>     > downloaded_data_root_dir + '/.Sugar-Metadata' +    if
>     > os.path.isdir(downloaded_data_root_dir): +
>     > shutil.rmtree(downloaded_data_root_dir) +
>     > os.makedirs(downloaded_metadata_file_dir) + +    for
>     > root_webdav_resource_name in root_webdav_resources.keys(): +
>     > """ +        root_webdav_resource_name is of the type :: + +
>     > /webdav/a.txt +        """ +        split_tokens_array =
>     > root_webdav_resource_name.split('/') + +        # This will provide
>     > us with "a.txt" +        basename =
>     > split_tokens_array[len(split_tokens_array) - 1] + +        # This
>     > will provide us with "a.txt.metadata" +
>     > sugar_metadata_basename = basename + '.metadata' + +        # Thus
>     > will provide us with "/webdav/.Sugar-Metadata/a.txt.metadata" +
>     > sugar_metadata_url = root_sugar_metadata_url + '/' +
>     > sugar_metadata_basename + +        # Ensure that
>     > "sugar_metadata_url" is present as one of the +        # keys in
>     > "root_webdav_sugar_metadata_resources" +        assert
>     > sugar_metadata_url in root_webdav_sugar_metadata_resources.keys()
>     > + +        # Now download the metadata file, read its contents, and
>     > store +        # the metadata in memory. +        # It is assumed
>     > that the metadata-file is small enough to be +        # read in one
>     > call to "read". + +        downloaded_metadata_file_path =
>     > downloaded_metadata_file_dir + '/' + sugar_metadata_basename +
>     > metadata_resource =
>     > root_webdav_sugar_metadata._get_resource_by_key(sugar_metadata_url)
>     >
>     >
>     +        metadata_resource.downloadFile(downloaded_metadata_file_path)
>     > + +        file_pointer = open(downloaded_metadata_file_path) +
>     > metadata = eval(file_pointer.read()) +        file_pointer.close()
>     > + +        # Very critical. +        # 1. CRITICAL ONE: +        #
>     > Fill in the uid. +        #    Note that the file is not physically
>     > present. +        metadata['uid'] = downloaded_data_root_dir + '/'
>     > + basename + +        # 2. CRITICAL TWO: +        #    Fill in the
>     > properties, that can only be done by reading +        #    in the
>     > webdav-properties. +        live_properties =
>     > root_webdav._get_live_properties(root_webdav_resource_name) +
>     > metadata['filesize'] = live_properties.getContentLength() +
>     > metadata['timestamp'] = live_properties.getLastModified() +
>     > metadata['creation_time'] = live_properties.getCreationDate() + +
>     > # Now, write this to the metadata-file, so that +        #
>     > webdav-properties get gelled into sugar-metadata. + +
>     > file_pointer = open(downloaded_metadata_file_path, 'w') +
>     > file_pointer.write(simplejson.dumps(metadata)) +
>     > file_pointer.close() + +
>     > root_webdav._set_metadata_for_resource(root_webdav_resource_name, +
>     > metadata) + +    return root_webdav._get_metadata_list() diff --git
>     > a/src/jarabe/model/buddy.py b/src/jarabe/model/buddy.py index
>     > 8f17d7e..c088aa9 100644 --- a/src/jarabe/model/buddy.py +++
>     > b/src/jarabe/model/buddy.py @@ -43,6 +43,7 @@ class
>     > BaseBuddyModel(gobject.GObject): self._color = None self._tags =
>     > None self._current_activity = None +        self._ip_address =
>     > None
>     >
>     > gobject.GObject.__init__(self, **kwargs)
>     >
>     > @@ -87,6 +88,16 @@ class BaseBuddyModel(gobject.GObject):
>     > getter=get_current_activity, setter=set_current_activity)
>     >
>     > +    def get_ip_address(self): +        return self._ip_address + +
>     > def set_ip_address(self, ip_address): +        self._ip_address =
>     > ip_address + +    ip_address = gobject.property(type=object, +
>     > getter=get_ip_address, +
>     > setter=set_ip_address) + def is_owner(self): raise
>     > NotImplementedError
>     >
>     > diff --git a/src/jarabe/model/neighborhood.py
>     > b/src/jarabe/model/neighborhood.py index 4528a15..39f648e 100644
>     > --- a/src/jarabe/model/neighborhood.py +++
>     > b/src/jarabe/model/neighborhood.py @@ -930,6 +930,9 @@ class
>     > Neighborhood(gobject.GObject): if 'key' in properties:
>     > buddy.props.key = properties['key']
>     >
>     > +        if 'ip4-address' in properties: +
>     > buddy.props.ip_address = properties['ip4-address'] + nick_key =
>     > CONNECTION_INTERFACE_ALIASING + '/alias' if nick_key in
>     > properties: buddy.props.nick = properties[nick_key] diff --git
>     > a/src/jarabe/view/buddymenu.py b/src/jarabe/view/buddymenu.py index
>     > de5a772..dfbcfa3 100644 --- a/src/jarabe/view/buddymenu.py +++
>     > b/src/jarabe/view/buddymenu.py @@ -73,6 +73,12 @@ class
>     > BuddyMenu(Palette): self.menu.append(menu_item) menu_item.show()
>     >
>     > +        access_buddy_remote_share_menu_item = MenuItem(_('Access
>     > Share'), 'list-add') +
>     > access_buddy_remote_share_menu_item.connect('activate', +
>     > self._access_share_cb) +
>     > self.menu.append(access_buddy_remote_share_menu_item) +
>     > access_buddy_remote_share_menu_item.show() + self._invite_menu =
>     > MenuItem('') self._invite_menu.connect('activate',
>     > self._invite_friend_cb) self.menu.append(self._invite_menu) @@
>     > -83,6 +89,11 @@ class BuddyMenu(Palette): activity =
>     > home_model.get_active_activity()
>     > self._update_invite_menu(activity)
>     >
>     > +    def _access_share_cb(self, menuitem): +        from
>     > jarabe.journal.journalactivity import get_journal +
>     > volumes_toolbar = get_journal().get_volumes_toolbar() +
>     > volumes_toolbar._add_remote_share_button(self._buddy) + def
>     > _add_my_items(self): item = MenuItem(_('Shutdown'),
>     > 'system-shutdown') item.connect('activate',
>     > self.__shutdown_activate_cb) diff --git
>     > a/src/jarabe/view/palettes.py b/src/jarabe/view/palettes.py index
>     > 7a17f32..3b26faf 100644 --- a/src/jarabe/view/palettes.py +++
>     > b/src/jarabe/view/palettes.py @@ -336,3 +336,36 @@ class
>     > JournalXSPalette(Palette): def __journal_restore_activate_cb(self,
>     > menu_item, xs_hostname): dialog = XSRestoreDialog(xs_hostname)
>     > dialog.show() + + +class RemoteSharePalette(Palette): +    def
>     > __init__(self, buddy, button): +        Palette.__init__(self,
>     > label=('%s\'s share' % buddy.props.nick)) +        self._buddy =
>     > buddy +        self._button = button + +
>     > self.props.secondary_text =
>     > glib.markup_escape_text(buddy.props.ip_address) + +        vbox =
>     > gtk.VBox() +        self.set_content(vbox) +        vbox.show() + +
>     > self.connect('popup', self.__popup_cb) + +        menu_item =
>     > MenuItem(pgettext('Share', 'Unmount')) + +        icon =
>     > Icon(icon_name='media-eject', icon_size=gtk.ICON_SIZE_MENU) +
>     > menu_item.set_image(icon) +        icon.show() + +
>     > menu_item.connect('activate', self.__unmount_activate_cb) +
>     > self.menu.append(menu_item) +        menu_item.show() + +    def
>     > __unmount_activate_cb(self, menu_item): +        from
>     > jarabe.journal.journalactivity import get_journal +
>     > singleton_volumes_toolbar = get_journal().get_volumes_toolbar() +
>     >
>     singleton_volumes_toolbar._remove_remote_share_button(self._buddy.props.ip_address)
>     >
>     >
>     +
>     > +    def __popup_cb(self, palette): +        pass diff --git
>     > a/src/webdav/Condition.py b/src/webdav/Condition.py new file mode
>     > 100644 index 0000000..76acf94 --- /dev/null +++
>     > b/src/webdav/Condition.py @@ -0,0 +1,475 @@ +# pylint:
>     > disable-msg=R0921,W0704,R0901,W0511,R0201 +# Copyright 2008 German
>     > Aerospace Center (DLR) +# +# Licensed under the Apache License,
>     > Version 2.0 (the "License"); +# you may not use this file except in
>     > compliance with the License. +# You may obtain a copy of the
>     > License at +# +#     http://www.apache.org/licenses/LICENSE-2.0 +#
>     > +# Unless required by applicable law or agreed to in writing,
>     > software +# distributed under the License is distributed on an "AS
>     > IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
>     > express or implied. +# See the License for the specific language
>     > governing permissions and +# limitations under the License. + +
>     > +""" +This module contains classes for creating a search condition
>     > according to the DASL draft. +The classes will output the WHERE
>     > part of a search request to a WebDAV server. + +Instances of the
>     > classes defined in this module form a tree data structure which
>     > represents +a search condition. This tree is made up of AND-nodes,
>     > OR-nodes, Operator- and comparison- +nodes and from property (i.e.
>     > variable) and constant leaf nodes. +""" + + +import types +from
>     > time import strftime +from calendar import timegm +from rfc822
>     > import formatdate + +from webdav.Constants import NS_DAV,
>     > PROP_LAST_MODIFIED, DATE_FORMAT_ISO8601 + + +__version__ =
>     > "$Revision$"[11:-2] + + +class ConditionTerm(object): +    """ +
>     > This is the abstact base class for all condition terms. +    """ +
>     > def __init__(self): +        pass + +    def toXML(self): +
>     > """ +        Abstact method which return a XML string which can be
>     > passed to a WebDAV server +        for a search condition. +
>     > """ +        raise NotImplementedError + +    # start Tamino
>     > workaround for missing like-op: +    def postFilter(self,
>     > resultSet): +        """ +        Abstact method for temporary
>     > workaround for Tamino's absense of the like-operator. +        This
>     > method shall filter the given result set for those resources which
>     > match +        all Contains-trems. +        """ +        return
>     > resultSet +    # end of workaround + + +class
>     > IsCollectionTerm(ConditionTerm): +    """ Leaf condition. Checks if
>     > the matching resources are collections. """ + +    def
>     > __init__(self): +        """ Constructor. """ + +
>     > ConditionTerm.__init__(self) + +    def toXML(self): +        """ +
>     > Returns XML encoding. +        """ + +        return
>     > "<D:is-collection/>" + + +class Literal(ConditionTerm): +    """ +
>     > A leaf class for condition expressions representing a constant
>     > value. +    """ +    def __init__(self, literal): +
>     > ConditionTerm.__init__(self) +        self.literal = literal +
>     >  +    def toXML(self): +        ''' +        Returns XML encoding.
>     > +        ''' +        return "<D:literal>" + self.literal +
>     > "</D:literal>" + + +class UnaryTerm(ConditionTerm): +    """ +
>     > Base class of all nodes with a single child node. +    """ +    def
>     > __init__(self, child): +        ConditionTerm.__init__(self) +
>     > self.child = child + +    def toXML(self): +        ''' +
>     > Returns XML encoding. +        ''' +        return
>     > self.child.toXML() + + +class BinaryTerm(ConditionTerm): +    """ +
>     > Base class of all nodes with two child nodes +    """ +    def
>     > __init__(self, left, right): +        ConditionTerm.__init__(self)
>     > +        self.left = left +        self.right = right + +    def
>     > toXML(self): +        ''' +        Returns XML encoding. +
>     > ''' +        return self.left.toXML() + self.right.toXML() + +class
>     > TupleTerm(ConditionTerm): +    """ +    Base class of all nodes
>     > with multiple single child nodes. +    """ +    def __init__(self,
>     > terms): +        ConditionTerm.__init__(self) +        self.terms =
>     > terms + +    def addTerm(self, term): +        ''' +        Removes
>     > a term. + +        @param term: term to add +        ''' +
>     > self.terms.append(term) + +    def removeTerm(self, term): +
>     > ''' +        Adds a term. + +        @param term: term to remove +
>     > ''' +        try: +            self.terms.remove(term) +
>     > except ValueError: +            pass + +    def toXML(self): +
>     > ''' +        Returns XML encoding. +        ''' +        result =
>     > "" +        for term in self.terms: +            result +=
>     > term.toXML() +        return result + + +class AndTerm(TupleTerm):
>     > +    """ +    This class represents and logical AND-condition with
>     > multiple sub terms. +    """ +    def toXML(self): +        ''' +
>     > Returns XML encoding. +        ''' +        return "<D:and>" +
>     > TupleTerm.toXML(self) + "</D:and>" + +    # start Tamino workaround
>     > for missing like-op: +    def postFilter(self, resultSet): +
>     > ''' +        Filters the given result set. This is a TAMINO WebDav
>     > server workaround +        for the missing 'like' tag. + +
>     > @param resultSet: the result set that needs to be filtered. +
>     > ''' +        for term in self.terms: +            filtered =
>     > term.postFilter(resultSet) +            resultSet = filtered +
>     > return resultSet +    # end of workaround + +class
>     > OrTerm(TupleTerm): +    """ +    This class represents and logical
>     > OR-condition with multiple sub terms. +    """ +    def
>     > toXML(self): +        ''' +        Returns XML encoding. +
>     > ''' +        return "<D:or>" + TupleTerm.toXML(self) + "</D:or>" +
>     >  +    # start Tamino workaround for missing like-op: +    def
>     > postFilter(self, resultSet): +        ''' +        Filters the
>     > given result set. This is a TAMINO WebDav server workaround +
>     > for the missing 'like' tag. + +        @param resultSet: the result
>     > set that needs to be filtered. +        ''' +        raise
>     > NotImplementedError + + +class NotTerm(UnaryTerm): +    """ +
>     > This class represents a negation term for the contained sub term. +
>     > """ +    def toXML(self): +        ''' +        Returns XML
>     > encoding. +        ''' +        # start Tamino workaround for
>     > missing like-op: +        if  isinstance(self.child,
>     > ContainsTerm): +            return "" +        # end of workaround
>     >  +        return "<D:not>" + UnaryTerm.toXML(self) + "</D:not>" + +
>     > # start Tamino workaround for missing like-op: +    def
>     > postFilter(self, resultSet): +        ''' +        Filters the
>     > given result set. This is a TAMINO WebDav server workaround +
>     > for the missing 'like' tag. + +        @param resultSet: the result
>     > set that needs to be filtered. +        ''' +        if
>     > isinstance(self.child, ContainsTerm): +
>     > self.child.negate = 1 +            # TODO: pass on filter +
>     > return self.child.postFilter(resultSet) + + +class
>     > ExistsTerm(UnaryTerm): +    """ +    Nodes of this class must have
>     > a single child with tuple type (of len 2) representing a +
>     > WebDAV property. +    This leaf term evaluates to true if the
>     > (child) property exists. +    """ +    def toXML(self): +
>     > ''' +        Returns XML encoding. +        ''' +        return
>     > '<D:is-defined><D:prop xmlns="%s"><%s' % self.child + '
>     > /></D:prop></D:is-defined>' + +class
>     > ContentContainsTerm(UnaryTerm): +    """ +    This class can be
>     > used to search for a given phrase in resources' contents. +    """
>     > +    def toXML(self): +        ''' +        Returns XML encoding. +
>     > ''' +        return "<D:contains>" + self.child + "</D:contains>"
>     > + + + +class BinaryRelationTerm(BinaryTerm): +    """ +    This is
>     > the abstact base class for the following relation operands. +
>     > """ +    def __init__(self, left, right): +
>     > BinaryTerm.__init__(self, left, right) +        if
>     > isinstance(self.left, types.StringType):     # Must be namespace +
>     > name pair +            self.left = ('DAV:', self.left) +        if
>     > not isinstance(self.right, Literal): +            self.right =
>     > Literal(self.right)             # Must be Literal instance + +
>     > def toXML(self): +        ''' +        Returns XML encoding. +
>     > ''' +        ## TODO: extract name space and create shortcut for
>     > left element +        return '<D:prop xmlns="%s"><%s /></D:prop>' %
>     > self.left + self.right.toXML() + + +class
>     > StringRelationTerm(BinaryRelationTerm): +    """ +    This is the
>     > abstact base class for the following string relation classes. +
>     > """ +    def __init__(self, left, right, caseless=None): +
>     > """ +        @param left: webdav property (namespace, name) +
>     > @param right: string/unicode literal +        qparam caseless: 1
>     > for case sensitive comparison +        """ +
>     > BinaryRelationTerm.__init__(self, left, Literal(right)) +
>     > self.caseless = caseless +        if self.caseless: +
>     > self.attrCaseless = "yes" +        else: +
>     > self.attrCaseless = "no" + +class
>     > NumberRelationTerm(BinaryRelationTerm): +    """ +    This is the
>     > abstact base class for the following number comparison classes. +
>     > """ +    def __init__(self, left, right): +        """ +
>     > @param left: webdav property (namespace, name) +        @param
>     > right: constant number +        """ +        ## TODO: implemet
>     > typed literal +        BinaryRelationTerm.__init__(self, left,
>     > Literal(str(right))) + +class
>     > DateRelationTerm(BinaryRelationTerm): +    """ +    This is the
>     > abstact base class for the following date comparison classes. +
>     > """ +    def __init__(self, left, right): +        """ +
>     > @param left: webdav property (namespace, name) +        @param
>     > right: string literal containing a date in ISO8601 format +
>     > """ +        ## TODO: implemet typed literal +        assert
>     > len(right) == 9, "No time is specified for literal: " + str(right)
>     > +        BinaryRelationTerm.__init__(self, left, right) +        if
>     > self.left == (NS_DAV, PROP_LAST_MODIFIED): +            rfc822Time
>     > = formatdate(timegm(right))      # must not use locale setting
>     >  +            self.right = Literal(rfc822Time) +        else:
>     >  +            self.right = Literal(strftime(DATE_FORMAT_ISO8601,
>     > right)) + + +class MatchesTerm(StringRelationTerm): +    """ +
>     > Nodes of this class evaluate to true if the (child) property's
>     > value matches the (child) string. +    """ +    def toXML(self): +
>     > ''' +        Returns XML encoding. +        ''' +        return
>     > '<D:eq caseless="%s">' % self.attrCaseless +
>     > StringRelationTerm.toXML(self) + "</D:eq>" + +class
>     > ContainsTerm(StringRelationTerm): +    """ +    Nodes of this class
>     > evaluate to true if the (left child) property's value contains the
>     > +    (right child) string. +    """ +    def __init__(self, left,
>     > right, isTaminoWorkaround=False): +        right = unicode(right) +
>     > StringRelationTerm.__init__(self, left, "%" + right + "%") +
>     > # Tamino workaround: operator like is not yet implemented: +
>     > self.negate = 0 +        self.isTaminoWorkaround =
>     > isTaminoWorkaround + +    def toXML(self): +        ''' +
>     > Returns XML encoding. +        ''' +        # Tamino workaround:
>     > operator like is not yet implemented: +        # Produce a
>     > is-defined-condition instead +        if self.isTaminoWorkaround: +
>     > return "<D:isdefined><D:prop xmlns='%s'><%s" % self.left + "
>     > /></D:prop></D:isdefined>" +        else: +            return
>     > '<D:like caseless="%s">' % self.attrCaseless +
>     > StringRelationTerm.toXML(self) + "</D:like>" + +    # start Tamino
>     > workaround for missing like-op: +    def postFilter(self,
>     > resultSet): +        ''' +        Filters the given result set.
>     > This is a TAMINO WebDav server workaround +        for the missing
>     > 'like' tag. + +        @param resultSet: the result set that needs
>     > to be filtered. +        ''' +        newResult = {} +        word
>     > = self.right.literal[1:-1]       # remove leading and trailing '%'
>     > characters (see __init__()) +        for url, properties in
>     > resultSet.items(): +            value = properties.get(self.left) +
>     > if self.negate: +                if not value or
>     > value.textof().find(word) < 0: +                    newResult[url]
>     > = properties +            else: +                if value and
>     > value.textof().find(word) >= 0: +                    newResult[url]
>     > = properties +        return newResult +    # end of workaround +
>     > +class IsEqualTerm(NumberRelationTerm): +    """ +    Nodes of this
>     > class evaluate to true if the (left child) numerical property's
>     > value is equal +    to the (right child) number. +    """ +    def
>     > toXML(self): +        ''' +        Returns XML encoding. +
>     > ''' +        return "<D:eq>" + NumberRelationTerm.toXML(self) +
>     > "</D:eq>" + +class IsGreaterTerm(NumberRelationTerm): +    """ +
>     > Nodes of this class evaluate to true if the (left child) numerical
>     > property's value is greater +    than the (right child) number. +
>     > """ +    def toXML(self): +        ''' +        Returns XML
>     > encoding. +        ''' +        return "<D:gt>" +
>     > NumberRelationTerm.toXML(self) + "</D:gt>" + +class
>     > IsGreaterOrEqualTerm(NumberRelationTerm): +    """ +    Nodes of
>     > this class evaluate to true if the (left child) numerical
>     > property's value is greater +    than or equal to the (right child)
>     > number. +    """ +    def toXML(self): +        ''' +
>     > Returns XML encoding. +        ''' +        return "<D:gte>" +
>     > NumberRelationTerm.toXML(self) + "</D:gte>" + +class
>     > IsSmallerTerm(NumberRelationTerm): +    """ +    Nodes of this
>     > class evaluate to true if the (left child) numerical property's
>     > value is less +    than the (right child) number. +    """ +    def
>     > toXML(self): +        ''' +        Returns XML encoding. +
>     > ''' +        return "<D:lt>" + NumberRelationTerm.toXML(self) +
>     > "</D:lt>" + +class IsSmallerOrEqualTerm(NumberRelationTerm): +
>     > """ +    Nodes of this class evaluate to true if the (left child)
>     > numerical property's value is less +    than or equal to the (right
>     > child) number. +    """ +    def toXML(self): +        ''' +
>     > Returns XML encoding. +        ''' +        return "<D:lte>" +
>     > NumberRelationTerm.toXML(self) + "</D:lte>" + + +class
>     > OnTerm(DateRelationTerm): +    """ +    Nodes of this class
>     > evaluate to true if the (left child) property's value is a date +
>     > equal to the (right child) date. +    """ +    def toXML(self): +
>     > ''' +        Returns XML encoding. +        ''' +        return
>     > "<D:eq>" + DateRelationTerm.toXML(self) + "</D:eq>" + +class
>     > AfterTerm(DateRelationTerm): +    """ +    Nodes of this class
>     > evaluate to true if the (left child) property's value is a date +
>     > succeeding the (right child) date. +    """ +    def toXML(self): +
>     > ''' +        Returns XML encoding. +        ''' +        return
>     > "<D:gt>" + DateRelationTerm.toXML(self) + "</D:gt>" + +class
>     > BeforeTerm(DateRelationTerm): +    """ +    Nodes of this class
>     > evaluate to true if the (left child) property's value is a date +
>     > preceeding the (right child) date. +    """ +    def toXML(self): +
>     > ''' +        Returns XML encoding. +        ''' +        return
>     > "<D:lt>" + DateRelationTerm.toXML(self) + "</D:lt>" + + + +# Simple
>     > module test +if  __name__ == '__main__': +    # use the example
>     > from the webdav specification +    condition = AndTerm(
>     > (MatchesTerm('getcontenttype', 'image/gif'), \ +
>     > IsGreaterTerm('getcontentlength', 4096)) ) +    print "Where: " +
>     > condition.toXML() diff --git a/src/webdav/Connection.py
>     > b/src/webdav/Connection.py new file mode 100644 index
>     > 0000000..66f7833 --- /dev/null +++ b/src/webdav/Connection.py @@
>     > -0,0 +1,321 @@ +# pylint:
>     > disable-msg=W0142,W0102,R0901,R0904,E0203,E1101,C0103 +# +#
>     > Copyright 2008 German Aerospace Center (DLR) +# +# Licensed under
>     > the Apache License, Version 2.0 (the "License"); +# you may not use
>     > this file except in compliance with the License. +# You may obtain
>     > a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +The contained class
>     > extends the HTTPConnection class for WebDAV support. +""" + + +from
>     > httplib import HTTPConnection, CannotSendRequest, BadStatusLine,
>     > ResponseNotReady +from copy import copy +import base64   # for
>     > basic authentication +try: +    import hashlib +except ImportError:
>     > # for Python 2.4 compatibility +    import md5 +    hashlib = md5
>     > +import mimetypes +import os       # file handling +import urllib
>     > +import types +import socket   # to "catch" socket.error +from
>     > threading import RLock +try: +    from uuid import uuid4 +except
>     > ImportError: # for Python 2.4 compatibility +    from uuid_ import
>     > uuid4 +from xml.parsers.expat import ExpatError + +from davlib
>     > import DAV +from qp_xml import Parser + +from webdav.WebdavResponse
>     > import MultiStatusResponse, ResponseFormatError +from webdav import
>     > Constants +from webdav.logger import getDefaultLogger + +
>     > +__version__ = "$LastChangedRevision$" + + +class Connection(DAV):
>     > +    """ +    This class handles a connection to a WebDAV server. +
>     > This class is used internally. Client code should prefer classes +
>     > L{WebdavClient.ResourceStorer} and
>     > L{WebdavClient.CollectionStorer}. + +    @author: Roland Betz +
>     > """ + +    # Constants +    #  The following switch activates a
>     > workaround for the Tamino webdav server: +    #  Tamino expects
>     > URLs which are passed in a HTTP header to be Latin-1 encoded +    #
>     > instead of Utf-8 encoded. +    #  Set this switch to zero in order
>     > to communicate with conformant servers. +    blockSize = 30000 +
>     > MaxRetries = 10 + +    def __init__(self, *args, **kwArgs): +
>     > DAV.__init__(self, *args, **kwArgs) +
>     > self.__authorizationInfo = None +        self.logger =
>     > getDefaultLogger() +        self.isConnectedToCatacomb = True +
>     > self.serverTypeChecked = False +        self._lock = RLock() +
>     >  +    def _request(self, method, url, body=None, extra_hdrs={}): +
>     >  +        self._lock.acquire() +        try: +            # add the
>     > authorization header +            extraHeaders = copy(extra_hdrs) +
>     > if self.__authorizationInfo: + +                # update (digest)
>     > authorization data +                if
>     > hasattr(self.__authorizationInfo, "update"): +
>     > self.__authorizationInfo.update(method=method, uri=url) +
>     >  +                extraHeaders["AUTHORIZATION"] =
>     > self.__authorizationInfo.authorization + +            # encode
>     > message parts +            body = _toUtf8(body) +            url =
>     > _urlEncode(url) +            for key, value in
>     > extraHeaders.items(): +                extraHeaders[key] =
>     > _toUtf8(value) +                if key == "Destination": #
>     > copy/move header +                    if
>     > self.isConnectedToCatacomb: +
>     > extraHeaders[key] = _toUtf8(value.replace(Constants.SHARP,
>     > Constants.QUOTED_SHARP)) + +                    else: # in case of
>     > TAMINO 4.4 +                        extraHeaders[key] =
>     > _urlEncode(value) +            # pass message to httplib class +
>     > for retry in range(0, Connection.MaxRetries):    # retry loop +
>     > try: +                    self.logger.debug("REQUEST Send %s for
>     > %s" % (method, url)) +
>     > self.logger.debug("REQUEST Body: " + repr(body)) +
>     > for hdr in extraHeaders.items(): +
>     > self.logger.debug("REQUEST Header: " + repr(hdr)) +
>     > self.request(method, url, body, extraHeaders) +
>     > response = self.getresponse() +                    break  # no
>     > retry needed +                except (CannotSendRequest,
>     > socket.error, BadStatusLine, ResponseNotReady), exc: +
>     > # Workaround, start: reconnect and retry... +
>     > self.logger.debug("Exception: " + str(exc) + " Retry ... ") +
>     > self.close() +                    try: +
>     > self.connect() +                    except (CannotSendRequest,
>     > socket.error, BadStatusLine, ResponseNotReady), exc: +
>     > raise WebdavError("Cannot perform request. Connection failed.") +
>     > if retry == Connection.MaxRetries - 1: +
>     > raise WebdavError("Cannot perform request.") +            return
>     > self.__evaluateResponse(method, response) +        finally: +
>     > self._lock.release() + +    def __evaluateResponse(self, method,
>     > response): +        """ Evaluates the response of the WebDAV
>     > server. """ + +        status, reason = response.status,
>     > response.reason +        self.logger.debug("Method: " + method + "
>     > Status %d: " % status + reason) + +        if status >=
>     > Constants.CODE_LOWEST_ERROR:     # error has occured ? +
>     > self.logger.debug("ERROR Response: " + response.read()) +
>     >  +            # identify authentication CODE_UNAUTHORIZED, throw
>     > appropriate exception +            if status ==
>     > Constants.CODE_UNAUTHORIZED: +                raise
>     > AuthorizationError(reason, status,
>     > response.msg["www-authenticate"]) + +            response.close() +
>     > raise WebdavError(reason, status) + +        if status ==
>     > Constants.CODE_MULTISTATUS: +            content = response.read()
>     > +            ## check for UTF-8 encoding +            try: +
>     > response.root = Parser().parse(content) +            except
>     > ExpatError, error: +                errorMessage = "Invalid XML
>     > document has been returned.\nReason: '%s'" % str(error.args) +
>     > raise WebdavError(errorMessage) +            try: +
>     > response.msr = MultiStatusResponse(response.root) +
>     > except ResponseFormatError: +                raise
>     > WebdavError("Invalid WebDAV response.") +
>     > response.close() +            self.logger.debug("RESPONSE
>     > (Multi-Status): " + unicode(response.msr)) +        elif method ==
>     > 'LOCK' and status == Constants.CODE_SUCCEEDED: +
>     > response.parse_lock_response() +            response.close() +
>     > elif method != 'GET' and method != 'PUT': +
>     > self.logger.debug("RESPONSE Body: " + response.read()) +
>     > response.close() +        return response + +    def
>     > addBasicAuthorization(self, user, password, realm=None): +
>     > if user and len(user) > 0: +            self.__authorizationInfo =
>     > _BasicAuthenticationInfo(realm=realm, user=user,
>     > password=password) + +    def addDigestAuthorization(self, user,
>     > password, realm, qop, nonce, uri = None, method = None): +
>     > if user and len(user) > 0: +            # username, realm,
>     > password, uri, method, qop are required +
>     > self.__authorizationInfo = _DigestAuthenticationInfo(realm=realm,
>     > user=user, password=password, uri=uri, method=method, qop=qop,
>     > nonce=nonce) + +    def putFile(self, path, srcfile, header={}): +
>     > self._lock.acquire() +        try: +            # Assemble header +
>     > try: +                size = os.path.getsize(srcfile.name
>     <http://srcfile.name>) +
>     > except os.error, error: +                raise WebdavError("Cannot
>     > determine file size.\nReason: ''" % str(error.args)) +
>     > header["Content-length"] = str(size) + +            contentType,
>     > contentEnc = mimetypes.guess_type(path) +            if
>     > contentType: +                header['Content-Type'] = contentType
>     > +            if contentEnc: +
>     > header['Content-Encoding'] = contentEnc +            if
>     > self.__authorizationInfo: +                # update (digest)
>     > authorization data +                if
>     > hasattr(self.__authorizationInfo, "update"): +
>     > self.__authorizationInfo.update(method="PUT", uri=path) +
>     > header["AUTHORIZATION"] = self.__authorizationInfo.authorization +
>     >  +            # send first request +            path =
>     > _urlEncode(path) +            try: +
>     > HTTPConnection.request(self, 'PUT', path, "", header) +
>     > self._blockCopySocket(srcfile, self, Connection.blockSize) +
>     > srcfile.close() +                response = self.getresponse() +
>     > except (CannotSendRequest, socket.error, BadStatusLine,
>     > ResponseNotReady), exc: +
>     > self.logger.debug("Exception: " + str(exc) + " Retry ... ") +
>     > raise WebdavError("Cannot perform request.") +            status,
>     > reason = (response.status, response.reason) +
>     > self.logger.debug("Status %d: %s" % (status, reason)) +
>     > try: +                if status >= Constants.CODE_LOWEST_ERROR:
>     > # error has occured ? +                    raise
>     > WebdavError(reason, status) +            finally: +
>     > self.logger.debug("RESPONSE Body: " + response.read()) +
>     > response.close() +            return response +        finally: +
>     > self._lock.release() + +    def _blockCopySocket(self, source,
>     > toSocket, blockSize): +        transferedBytes = 0 +        block =
>     > source.read(blockSize) +        #while source.readinto(block,
>     > blockSize): +        while len(block): +
>     > toSocket.send(block) +            self.logger.debug("Wrote %d
>     > bytes." % len(block)) +            transferedBytes += len(block) +
>     > block = source.read(blockSize) +
>     > self.logger.info <http://self.logger.info>("Transfered %d
>     bytes." % transferedBytes) + +
>     > def __str__(self): +        return self.protocol + "://" +
>     > self.host + ':' + str(self.port) + + +class
>     > _BasicAuthenticationInfo(object): +    def __init__(self,
>     > **kwArgs): +        self.__dict__.update(kwArgs) +
>     > self.cookie = base64.encodestring("%s:%s" % (self.user,
>     > self.password) ).strip() +        self.authorization = "Basic " +
>     > self.cookie +        self.password = None     # protect password
>     > security + +class _DigestAuthenticationInfo(object): + +    __nc =
>     > "0000000" # in hexadecimal without leading 0x + +    def
>     > __init__(self, **kwArgs): + +        self.__dict__.update(kwArgs) +
>     >  +        if self.qop is None: +            raise
>     > WebdavError("Digest without qop is not implemented.") +        if
>     > self.qop == "auth-int": +            raise WebdavError("Digest with
>     > qop-int is not implemented.") + +    def update(self, **kwArgs): +
>     > """ Update input data between requests""" + +
>     > self.__dict__.update(kwArgs) + +    def _makeDigest(self): +
>     > """ Creates the digest information. """ + +        # increment
>     > nonce count +        self._incrementNc() + +        # username,
>     > realm, password, uri, method, qop are required + +        a1 =
>     > "%s:%s:%s" % (self.user, self.realm, self.password) +        ha1 =
>     > hashlib.md5(a1).hexdigest() + +        #qop == auth +        a2 =
>     > "%s:%s" % (self.method, self.uri) +        ha2 =
>     > hashlib.md5(a2).hexdigest() + +        cnonce = str(uuid4()) +
>     >  +        responseData = "%s:%s:%s:%s:%s:%s" % (ha1, self.nonce,
>     > _DigestAuthenticationInfo.__nc, cnonce, self.qop, ha2) +
>     > digestResponse = hashlib.md5(responseData).hexdigest() + +
>     > authorization = "Digest username=\"%s\", realm=\"%s\",
>     > nonce=\"%s\", uri=\"%s\", algorithm=MD5, response=\"%s\", qop=auth,
>     > nc=%s, cnonce=\"%s\"" \ +                        % (self.user,
>     > self.realm, self.nonce, self.uri, digestResponse,
>     > _DigestAuthenticationInfo.__nc, cnonce) +        return
>     > authorization + +    authorization = property(_makeDigest) + +
>     > def _incrementNc(self): +        _DigestAuthenticationInfo.__nc =
>     > self._dec2nc(self._nc2dec() + 1) + +    def _nc2dec(self): +
>     > return int(_DigestAuthenticationInfo.__nc, 16) + +    def
>     > _dec2nc(self, decimal): +        return hex(decimal)[2:].zfill(8) +
>     >  + +class WebdavError(IOError): +    def __init__(self, reason,
>     > code=0): +        IOError.__init__(self, code) +        self.code =
>     > code +        self.reason = reason +    def __str__(self): +
>     > return self.reason + + +class AuthorizationError(WebdavError): +
>     > def __init__(self, reason, code, authHeader): +
>     > WebdavError.__init__(self, reason, code) + +        self.authType =
>     > authHeader.split(" ")[0] +        self.authInfo = authHeader + +
>     > +def _toUtf8(body): +    if not body is None: +        if
>     > type(body) == types.UnicodeType: +            body =
>     > body.encode('utf-8') +    return body + + +def _urlEncode(url): +
>     > if type(url) == types.UnicodeType: +        url =
>     > url.encode('utf-8') +    return urllib.quote(url) diff --git
>     > a/src/webdav/Constants.py b/src/webdav/Constants.py new file mode
>     > 100644 index 0000000..56dfd77 --- /dev/null +++
>     > b/src/webdav/Constants.py @@ -0,0 +1,199 @@ +# pylint:
>     > disable-msg=C0103 +# +# Copyright 2008 German Aerospace Center
>     > (DLR) +# +# Licensed under the Apache License, Version 2.0 (the
>     > "License"); +# you may not use this file except in compliance with
>     > the License. +# You may obtain a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +Contains XML tag
>     > names for the WebDAV protocol (RFC 2815) +and further WebDAV
>     > related constants. +""" + + +__version__ = "$Revision$"[11:-2] + +
>     > +QUOTED_SHARP = "%23" +SHARP = "#" + +# Date formats
>     > +DATE_FORMAT_ISO8601 = r"%Y-%m-%dT%H:%M:%SZ" +DATE_FORMAT_HTTP =
>     > r"%a, %d %b %Y %H:%M:%S GMT"  # not used, substituted by rfc822
>     > function + +NS_DAV = 'DAV:' +NS_TAMINO =
>     > 'http://namespaces.softwareag.com/tamino/response2' +
>     > +TAG_PROPERTY_FIND   = 'propfind' +TAG_PROPERTY_NAME   =
>     > 'propname' +TAG_PROPERTY_UPDATE = 'propertyupdate'
>     > +TAG_PROPERTY_SET    = 'set' +TAG_PROPERTY_REMOVE = 'remove'
>     > +TAG_ALL_PROPERTY    = 'allprop' +TAG_PROP            = 'prop' +
>     > +TAG_MULTISTATUS         = 'multistatus' +TAG_RESPONSE            =
>     > 'response' +TAG_HREF                = 'href' +TAG_PROPERTY_STATUS
>     > = 'propstat' +TAG_STATUS              = 'status'
>     > +TAG_RESPONSEDESCRIPTION = 'responsdescription' +
>     > +PROP_CREATION_DATE    = 'creationdate' +PROP_DISPLAY_NAME     =
>     > 'displayname' +PROP_CONTENT_LANGUAGE = 'getcontentlanguage'
>     > +PROP_CONTENT_LENGTH   = 'getcontentlength' +PROP_CONTENT_TYPE
>     > = 'getcontenttype' +PROP_ETAG             = 'getetag'
>     > +PROP_MODIFICATION_DATE = 'modificationdate' # this property is
>     > supported by +# Tamino 4.4 but not by Catacomb; the date format is
>     > ISO8601 +PROP_LAST_MODIFIED    = 'getlastmodified'
>     > +PROP_LOCK_DISCOVERY   = 'lockdiscovery' +PROP_RESOURCE_TYPE    =
>     > 'resourcetype' +PROP_SOURCE           = 'source'
>     > +PROP_SUPPORTED_LOCK   = 'supportedlock' +PROP_OWNER            =
>     > 'owner' + +PROP_RESOURCE_TYPE_RESOURCE    = 'resource'
>     > +PROP_RESOURCE_TYPE_COLLECTION  = 'collection' + +TAG_LINK
>     > = 'link' +TAG_LINK_SOURCE      = 'src' +TAG_LINK_DESTINATION =
>     > 'dst' + +TAG_LOCK_ENTRY     = 'lockentry' +TAG_LOCK_SCOPE     =
>     > 'lockscope' +TAG_LOCK_TYPE      = 'locktype' +TAG_LOCK_INFO      =
>     > 'lockinfo' +TAG_ACTIVE_LOCK    = 'activelock' +TAG_LOCK_DEPTH     =
>     > 'depth' +TAG_LOCK_TOKEN     = 'locktoken' +TAG_LOCK_TIMEOUT   =
>     > 'timeout' +TAG_LOCK_EXCLUSIVE = 'exclusive' +TAG_LOCK_SHARED    =
>     > 'shared' +TAG_LOCK_OWNER     = 'owner' + +# HTTP error code
>     > constants +CODE_MULTISTATUS = 207 +CODE_SUCCEEDED = 200
>     > +CODE_CREATED = 201 +CODE_NOCONTENT = 204 + +CODE_LOWEST_ERROR =
>     > 300 + +CODE_UNAUTHORIZED = 401 +CODE_FORBIDDEN = 403
>     > +CODE_NOT_FOUND = 404 +CODE_CONFLICT = 409
>     > +CODE_PRECONDITION_FAILED = 412 +CODE_LOCKED = 423   # no
>     > permission +CODE_FAILED_DEPENDENCY = 424 + +CODE_OUTOFMEM = 507 +
>     > +# ? +CONFIG_UNICODE_URL = 1 + +# constants for WebDAV DASL
>     > according to draft + +TAG_SEARCH_REQUEST = 'searchrequest'
>     > +TAG_SEARCH_BASIC   = 'basicsearch' +TAG_SEARCH_SELECT  = 'select'
>     > +TAG_SEARCH_FROM    = 'from' +TAG_SEARCH_SCOPE   = 'scope'
>     > +TAG_SEARCH_WHERE   = 'where' + +# constants for WebDAV ACP
>     > (according to draft-ietf-webdav-acl-09) below ... + +TAG_ACL
>     > = 'acl' +TAG_ACE                 = 'ace' +TAG_GRANT               =
>     > 'grant' +TAG_DENY                = 'deny' +TAG_PRIVILEGE
>     > = 'privilege' +TAG_PRINCIPAL           = 'principal' +TAG_ALL
>     > = 'all' +TAG_AUTHENTICATED       = 'authenticated'
>     > +TAG_UNAUTHENTICATED     = 'unauthenticated' +TAG_OWNER
>     > = 'owner' +TAG_PROPERTY            = 'property' +TAG_SELF
>     > = 'self' +TAG_INHERITED           = 'inherited' +TAG_PROTECTED
>     > = 'protected' +TAG_SUPPORTED_PRIVILEGE = 'supported-privilege'
>     > +TAG_DESCRIPTION         = 'description' + +# privileges for WebDAV
>     > ACP: +TAG_READ = 'read' +TAG_WRITE = 'write' +TAG_WRITE_PROPERTIES
>     > = 'write-properties' +TAG_WRITE_CONTENT = 'write-content'
>     > +TAG_UNLOCK = 'unlock' +TAG_READ_ACL = 'read-acl'
>     > +TAG_READ_CURRENT_USER_PRIVILEGE_SET =
>     > 'read-current-user-privilege-set' +TAG_WRITE_ACL = 'write-acl'
>     > +TAG_ALL = 'all' +TAG_BIND = 'bind' +TAG_UNBIND = 'unbind' +#
>     > Tamino-specific privileges +TAG_TAMINO_SECURITY = 'security' +#
>     > Limestone-specific privileges +TAG_BIND_COLLECTION =
>     > 'bind-collection' +TAG_UNBIND_COLLECTION = 'unbind-collection'
>     > +TAG_READ_PRIVATE_PROPERTIES = 'read-private-properties'
>     > +TAG_WRITE_PRIVATE_PROPERTIES = 'write-private-properties' + +#
>     > properties for WebDAV ACP: +PROP_CURRENT_USER_PRIVILEGE_SET =
>     > 'current-user-privilege-set' +PROP_SUPPORTED_PRIVILEGE_SET    =
>     > 'supported-privilege-set' +PROP_PRINCIPAL_COLLECTION_SET =
>     > 'principal-collection-set' + +# reports for WebDAV ACP
>     > +REPORT_ACL_PRINCIPAL_PROP_SET   = 'acl-principal-prop-set' + + +
>     > +# constants for WebDAV Delta-V + +#   WebDAV Delta-V method names
>     > +METHOD_REPORT = 'REPORT' +METHOD_VERSION_CONTROL =
>     > 'VERSION-CONTROL' +METHOD_UNCHECKOUT = 'UNCHECKOUT'
>     > +METHOD_CHECKOUT = 'CHECKOUT' +METHOD_CHECKIN = 'CHECKIN'
>     > +METHOD_UPDATE = 'UPDATE' + +#   Special properties
>     > +PROP_SUCCESSOR_SET = (NS_DAV, 'successor-set')
>     > +PROP_PREDECESSOR_SET = (NS_DAV, 'predecessor-set')
>     > +PROP_VERSION_HISTORY = (NS_DAV, 'version-history') +PROP_CREATOR =
>     > (NS_DAV, 'creator-displayname') +PROP_VERSION_NAME = (NS_DAV,
>     > 'version-name') +PROP_CHECKEDIN = (NS_DAV, 'checked-in')
>     > +PROP_CHECKEDOUT = (NS_DAV, 'checked-out') +PROP_COMMENT = (NS_DAV,
>     > 'comment') + +#   XML tags for request body +TAG_VERSION_TREE =
>     > 'version-tree' +TAG_LOCATE_BY_HISTORY = 'locate-by-history'
>     > +TAG_UPDATE = 'update' +TAG_VERSION = 'version' + +# HTTP header
>     > constants +HTTP_HEADER_DEPTH_INFINITY  = 'infinity' +HTTP_HEADER_IF
>     > = 'if' +HTTP_HEADER_DAV = 'dav' +HTTP_HEADER_DASL = 'dasl'
>     > +HTTP_HEADER_OPTION_ACL = 'access-control'
>     > +HTTP_HEADER_OPTION_DAV_BASIC_SEARCH = 'DAV:basicsearch'
>     > +HTTP_HEADER_SERVER = 'server' +HTTP_HEADER_SERVER_TAMINO =
>     > 'Apache/2.0.54 (Win32)' diff --git a/src/webdav/Makefile.am
>     > b/src/webdav/Makefile.am new file mode 100644 index
>     > 0000000..3356daf --- /dev/null +++ b/src/webdav/Makefile.am @@ -0,0
>     > +1,20 @@ +SUBDIRS = acp + +sugardir = $(pythondir)/webdav
>     > +sugar_PYTHON =                  \ +             Connection.py
>     > \ +             davlib.py          \ +             logger.py
>     > \ +             NameCheck.py       \ +             Utils.py
>     > \ +             VersionHandler.py  \ +
>     > WebdavRequests.py  \ +             Condition.py       \ +
>     > Constants.py       \ +             __init__.py        \ +
>     > qp_xml.py          \ +             uuid_.py           \ +
>     > WebdavClient.py    \ +             WebdavResponse.py + + diff --git
>     > a/src/webdav/NameCheck.py b/src/webdav/NameCheck.py new file mode
>     > 100644 index 0000000..7976973 --- /dev/null +++
>     > b/src/webdav/NameCheck.py @@ -0,0 +1,193 @@ +# pylint:
>     > disable-msg=R0904,W0142,W0511,W0104,C0321,E1103,W0212 +# +#
>     > Copyright 2008 German Aerospace Center (DLR) +# +# Licensed under
>     > the Apache License, Version 2.0 (the "License"); +# you may not use
>     > this file except in compliance with the License. +# You may obtain
>     > a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +Check name of new
>     > collections/resources for "illegal" characters. +""" + + +import
>     > re +import unicodedata + + +__version__ = "$LastChangedRevision$"
>     > + + +_unicodeUmlaut = [unicodedata.lookup("LATIN CAPITAL LETTER A
>     > WITH DIAERESIS"), +                  unicodedata.lookup("LATIN
>     > SMALL LETTER A WITH DIAERESIS"), +
>     > unicodedata.lookup("LATIN CAPITAL LETTER O WITH DIAERESIS"), +
>     > unicodedata.lookup("LATIN SMALL LETTER O WITH DIAERESIS"), +
>     > unicodedata.lookup("LATIN CAPITAL LETTER U WITH DIAERESIS"), +
>     > unicodedata.lookup("LATIN SMALL LETTER U WITH DIAERESIS"), +
>     > unicodedata.lookup("LATIN SMALL LETTER SHARP S")] + +# Define
>     > characters and character base sets +_german  =
>     > u"".join(_unicodeUmlaut) +_alpha = "A-Za-z" +_num = "0-9"
>     > +_alphaNum = _alpha + _num +_space = " " +_under = "_" +_dash =
>     > "\-" +_dot = "\." +_exclam = "\!" +_tilde   = "\~" +_dollar  =
>     > "\$" +_plus = "+" +_equal = "=" +_sharp = "#" + +# Define character
>     > groups +_letterNum = _alphaNum + _german +_letter = _alpha +
>     > _german + +# Define character sets for names +firstPropertyChar =
>     > _letter + _under +propertyChar = firstPropertyChar + _num + _dash +
>     > _dot +firstResourceChar = firstPropertyChar + _num + _tilde +
>     > _exclam + _dollar + \ +                     _dot + _dash + _plus +
>     > _equal + _sharp +resourceChar = firstResourceChar + _space + +#
>     > Define regular expressions for name validation +_propertyFirstRe =
>     > re.compile(u"^["+ firstPropertyChar +"]") + +_propertyRe =
>     > re.compile(u"[^"+ propertyChar +"]") +_resourceFirstRe =
>     > re.compile(u"^["+ firstResourceChar +"]") +_resourceRe =
>     > re.compile(u"[^"+ resourceChar +"]") + + +def
>     > isValidPropertyName(name): +    """ +    Check if the given
>     > property name is valid. + +    @param name: Property name. +
>     > @type name: C{unicode} + +    @return: Boolean indicating whether
>     > the given property name is valid or not. +    @rtype: C{bool} +
>     > """ + +    illegalChar = _propertyRe.search(name) +    return
>     > illegalChar == None  and  _propertyFirstRe.match(name) != None +
>     >  + +def isValidResourceName(name): +    """ +    Check if the given
>     > resource name is valid. + +    @param name: Resource name. +
>     > @type name: C{unicode} + +    @return: Boolean indicating whether
>     > the given resource name is valid or not. +    @rtype: C{bool} +
>     > """ + +    illegalChar = _resourceRe.search(name) +    return
>     > illegalChar == None  and  _resourceFirstRe.match(name) != None + +
>     > +def validatePropertyName(name): +    """ +    Check if the given
>     > property name is valid. + +    @param name: Property name. +
>     > @type name: C{unicode} +    @raise WrongNameError: if validation
>     > fails (see L{datafinder.common.NameCheck.WrongNameError}) +    """
>     > + +    illegalChar = _propertyRe.search(name) +    if illegalChar:
>     > +        raise WrongNameError(illegalChar.start(),
>     > name[illegalChar.start()]) +    if not
>     > _propertyFirstRe.match(name): +        if len(name) > 0: +
>     > raise WrongNameError(0, name[0]) +        else: +            raise
>     > WrongNameError(0, 0) + + +def validateResourceName(name): +    """
>     > +    Check if the given resource name is valid. + +    @param name:
>     > name of resource/collection +    @type name: C{unicode} +    @raise
>     > WrongNameError: if validation fails (@see
>     > L{datafinder.common.NameCheck.WrongNameError}) +    """ + +
>     > illegalChar = _resourceRe.search(name) +    if illegalChar: +
>     > raise WrongNameError(illegalChar.start(),
>     > name[illegalChar.start()]) +    if not
>     > _resourceFirstRe.match(name): +        if len(name) > 0: +
>     > raise WrongNameError(0, name[0]) +        else: +            raise
>     > WrongNameError(0, 0) + + +def getResourceNameErrorPosition(name): +
>     > """ +    Get position of illegal character (and the
>     > error-message). +    This method can be used to get this
>     > information if L{isValidPropertyName} +    or
>     > L{isValidResourceName} failed. + +    @param name: Resource name. +
>     > @type name: C{unicode} + +    @return: Tuple of error position and
>     > message. +    @rtype: C{tuple} of C{int} and C{unicode} +    """ +
>     > +    result = (-1, None) +    illegalChar =
>     > _resourceRe.search(name) +    if illegalChar: +        result =
>     > (illegalChar.start(), \ +                  u"Illegal character '%s'
>     > at index %d." % \ +
>     > (name[illegalChar.start()], illegalChar.start())) +    elif not
>     > _resourceFirstRe.match(name): +        result = (0, u"Illegal
>     > character '%s' at index %d." % (name[0], 0)) +    return result + +
>     >  +class WrongNameError(ValueError): +    """ +    Exception raised
>     > if an "illegal" character was found. + +    @ivar character:
>     > character that caused the exception +    @type character:
>     > C{unicode} +    @ivar position: position of C{character} +    @type
>     > position: C{int} +    """ + +    def __init__(self, position,
>     > character): +        """ +        Constructor. + +        @param
>     > character: Character that caused the exception. +        @type
>     > character: C{unicode} +        @param position: Position of
>     > C{character} +        @type position: C{int} +        """ +
>     >  +        ValueError.__init__(self) +        self.character =
>     > character +        self.position = position + +    def
>     > __str__(self): +        """ Returns string representation. """ +
>     >  +        return ValueError.__str__(self) + \ +
>     > "Character '%s' at index %d." % (self.character, self.position)
>     > diff --git a/src/webdav/Utils.py b/src/webdav/Utils.py new file
>     > mode 100644 index 0000000..ec05755 --- /dev/null +++
>     > b/src/webdav/Utils.py @@ -0,0 +1,154 @@ +# pylint:
>     > disable-msg=W0141,R0912 +# +# Copyright 2008 German Aerospace
>     > Center (DLR) +# +# Licensed under the Apache License, Version 2.0
>     > (the "License"); +# you may not use this file except in compliance
>     > with the License. +# You may obtain a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +The module contains
>     > functions to support use of the WebDav functionalities. +""" + +
>     > +import os +import sys + +from webdav.WebdavClient import
>     > CollectionStorer, ResourceStorer +from webdav.Constants import
>     > NS_DAV, PROP_RESOURCE_TYPE, CODE_NOT_FOUND,
>     > PROP_RESOURCE_TYPE_RESOURCE +from webdav.Connection import
>     > WebdavError + + +__version__ = "$Revision$"[11:-2] + + +def
>     > resourceExists(node, name = None, resourceType =
>     > PROP_RESOURCE_TYPE_RESOURCE): +    """ +    Check if resource
>     > exists. + +    Usage: +      -
>     > resourceExists(ResourceStorer-object): +        check if resource
>     > exists +      - resourceExists(CollectionStorer-object, name): +
>     > check if resource name exists in collection + +    @param node:
>     > node that has to be checked or node of collection +    @type node:
>     > L{ResourceStorer<webdav.WebdavClient.ResourceStorer>} +    @param
>     > name: name of resource (in collection node) that has to be checked
>     > +    @type name: string + +    @return: boolean + +    @raise
>     > WebdavError: all WebDAV errors except WebDAV error 404 (not found)
>     > +    """ + +    exists = False +    if not node: +        return
>     > exists +    try: +        myResourceType = "" +        if name: +
>     > # make sure it's unicode: +            if not isinstance(name,
>     > unicode): +                name =
>     > name.decode(sys.getfilesystemencoding()) +            url =
>     > node.url +            if url.endswith("/"): +                url =
>     > url  + name +            else: +                url = url + "/" +
>     > name +            newNode = ResourceStorer(url, node.connection) +
>     > element = newNode.readProperty(NS_DAV, PROP_RESOURCE_TYPE) +
>     > else: # name is "None": +            element =
>     > node.readProperty(NS_DAV, PROP_RESOURCE_TYPE) + +        if
>     > len(element.children) > 0: +            myResourceType =
>     > element.children[0].name +        if resourceType == myResourceType
>     > or resourceType == PROP_RESOURCE_TYPE_RESOURCE: +            exists
>     > = True +        else: +            exists = False +    except
>     > WebdavError, wderr: +        if wderr.code == CODE_NOT_FOUND: +
>     > # node doesn't exist -> exists = False: +            exists =
>     > False +        else: +            # another exception occured ->
>     > "re-raise" it: +            raise +    return exists + + +def
>     > downloadCollectionContent(destinationPath, collectionToDownload): +
>     > """ +    Downloads the resources contained to the given directory.
>     > + +    @param destinationPath: Path to download the files to, will
>     > be created if it not exists. +    @type destinationPath: C{String}
>     > +    @param collectionToDownload: Collection to download the
>     > content from. +    @type collectionToDownload: instance of
>     > L{CollectionStorer<webdav.WebdavClient.CollectionStorer>} + +
>     > @raise  WebdavError: If something goes wrong. +    """ + +    from
>     > time import mktime, gmtime + +    downloadCount = 0 + +
>     > listOfItems = collectionToDownload.getCollectionContents() + +
>     > if not os.path.exists(destinationPath): +        try: +
>     > os.makedirs(destinationPath) +        except OSError: +
>     > errorMessage = "Cannot create download destination directory '%s'."
>     > % destinationPath +            raise WebdavError(errorMessage) +
>     >  +    try: +        itemsInPath = os.listdir(destinationPath) +
>     > except OSError: +        errorMessage = "Cannot read the content of
>     > download destination directory '%s'." % destinationPath +
>     > raise WebdavError(errorMessage) + +    for item in listOfItems: +
>     > # skip collections +        if not isinstance(item[0],
>     > CollectionStorer): +            itemSavePath =
>     > os.path.join(destinationPath, item[0].name) +
>     > existsItemSavePath = os.path.exists(itemSavePath) + +            #
>     > update? +            if existsItemSavePath: +                try: +
>     > isUpdateNecessary = mktime(item[1].getLastModified()) >
>     > mktime(gmtime(os.path.getmtime(itemSavePath))) +
>     > except (ValueError, OverflowError): +
>     > isUpdateNecessary = True +                # windows is not case
>     > sensitive +                for realItem in itemsInPath: +
>     > if realItem.lower() == item[0].name.lower(): +
>     > itemsInPath.remove(realItem) +            else: +
>     > isUpdateNecessary = True + +            # download +            if
>     > not existsItemSavePath or (existsItemSavePath and
>     > isUpdateNecessary): +
>     > item[0].downloadFile(itemSavePath) +                downloadCount =
>     > downloadCount + 1 + +    # delete old items +    try: +        for
>     > item in itemsInPath: +
>     > os.remove(os.path.join(destinationPath, item)) +    except OSError,
>     > e: +        if e.errno == 13:    # permission error +
>     > sys.stderr.write("permission problem on '%s' in %s\n" %
>     > (e.filename, e.strerror)) +        else: +            raise +
>     >  +    return downloadCount diff --git
>     > a/src/webdav/VersionHandler.py b/src/webdav/VersionHandler.py new
>     > file mode 100644 index 0000000..a1962c6 --- /dev/null +++
>     > b/src/webdav/VersionHandler.py @@ -0,0 +1,198 @@ +# pylint:
>     > disable-msg=W0612,W0142 +# +# Copyright 2008 German Aerospace
>     > Center (DLR) +# +# Licensed under the Apache License, Version 2.0
>     > (the "License"); +# you may not use this file except in compliance
>     > with the License. +# You may obtain a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +The WebDAV client
>     > module forwards Delta-V related method invocations to +the
>     > following VersionHandler class. +""" + +__version__ =
>     > '$Revision$'[11:-2] + + +import types + +from webdav import
>     > Constants +from davlib import XML_CONTENT_TYPE, XML_DOC_HEADER + +
>     > +class VersionHandler(object): +    """ +    Implements a client
>     > interface for WebDAV Delta-V methods +    For the Delta-V see RFC
>     > 3253 at http://www.ietf.org/rfc/rfc3253.txt +    """ + +    #
>     > restrict instance variables +    __slots__ = ('path',
>     > 'connection') + + +    def __init__(self, connection, path): +
>     > """ +        Construct a VersionHandler with a URL path and a
>     > WebDAV connection. +        This constructor must not be called
>     > outside class ResourceStorer. + +        @param connection:
>     > L{webdav.Connection} instance +        @param path: resource's path
>     > part of URL +        """ +        #assert isinstance(connection,
>     > Connection), \ +        #    "Class of connection is %s." %
>     > connection.__class__.__name__ +        self.connection =
>     > connection +        self.path = path + + +    def
>     > activateVersionControl(self): +        """ +        Turns version
>     > control on for this resource. +        The resource becomes a
>     > version controlled resource (VCR) +        """ +        response =
>     > self.connection._request(Constants.METHOD_VERSION_CONTROL,
>     > self.path, None, {}) +        # set auto-versioning to
>     > DAV:locked-checkout +        ## parse response body in case of an
>     > error + +    def uncheckout(self, lockToken=None): +        """ +
>     > Undos a previous check-out operation on this VCR. +        The VCR
>     > is reverted to the state before the checkout/lock operation. +
>     > Beware: Property or content changes will be lost ! +        A
>     > (optional) lock has to be removed seperatedly. + +        @param
>     > lockToken: returned by a preceeding lock() method invocation or
>     > None +        """ +        headers = {} +        if lockToken: +
>     > headers = lockToken.toHeader() +        response =
>     > self.connection._request(Constants.METHOD_UNCHECKOUT, self.path,
>     > None, headers) +        ## parse response body in case of an error
>     > + +    def listAllVersions(self): +        """ +        List
>     > version history. + +        @return: List of versions for this VCR.
>     > Each version entry is a tuple adhering +            to the format
>     > (URL-path, name, creator, tuple of successor URL-paths). +
>     > If there are no branches then there is at most one successor within
>     > the tuple. +        """ +        # implementation is similar to the
>     > propfind method +        headers                 = {} +
>     > headers['Content-Type'] = XML_CONTENT_TYPE +        body =
>     > _createReportVersionTreeBody() +        response =
>     > self.connection._request(Constants.METHOD_REPORT, self.path, body,
>     > headers) +        # response is multi-status +        result = [] +
>     > for path, properties in response.msr.items(): +            # parse
>     > the successor-set value from XML into alist +
>     > result.append( (path, str(properties[Constants.PROP_VERSION_NAME]),
>     > \ +
>     > str(properties[Constants.PROP_CREATOR]), \ +
>     > _extractSuccessorList(properties[Constants.PROP_SUCCESSOR_SET])) )
>     > +        ## TODO: sort for path and produce list +
>     > result.sort() +        return result + +    # warning: not tested
>     > yet +    def readVersionProperties(self): +        """ +
>     > Provide version related information on this VCR. +        This
>     > include a reference to the latest version resource, +
>     > check-out state information and a comment. + +        @return: map
>     > of version properties with values. +        """ +
>     > versionProperties = (Constants.PROP_CHECKEDIN,
>     > Constants.PROP_CHECKEDOUT, Constants.PROP_COMMENT) +        return
>     > self.connection.readProperties(*versionProperties) + + +    def
>     > revertToVersion(self, oldVersion): +        """ +        Revert
>     > this VCR to the given version. +        Beware: All versions
>     > succeeding the given version are made unavailable. + +
>     > @param oldVersion: URL-path of a previous version of this VCR. +
>     > """ +        ## send an update request +        assert
>     > isinstance(oldVersion, types.StringType) or isinstance(oldVersion,
>     > types.UnicodeType) +        response =
>     > self.connection._request(Constants.METHOD_UPDATE, self.path, +
>     > _createUpdateBody(oldVersion), {}) +        return response + + +
>     > # the following is not needed when using auto-versioning + +    #
>     > warning: not tested yet +    def checkout(self): +        """ +
>     > Marks resource as checked-out +        This is usually followed by
>     > a GET (download) operation. +        """ +        response =
>     > self.connection._request(Constants.METHOD_CHECKOUT, self.path,
>     > None, {}) +        ## parse response body in case of an error + +
>     > # warning: not tested yet +    def checkin(self): +        """ +
>     > Creates a new version from the VCR's content. +        This
>     > opeartion is usually preceeded by one or more write operations. +
>     > """ +        response =
>     > self.connection._request(Constants.METHOD_CHECKIN, self.path, None,
>     > {}) +        versionUrl = response.getheader('Location') +
>     > return versionUrl +        ## parse response body in case of an
>     > error + + + + +# Helper functions +def
>     > _createReportVersionTreeBody(): +    """ +    TBD + +    @return:
>     > ... +    @rtype: string +    """ +    versions = 'D:' +
>     > Constants.TAG_VERSION_TREE +    prop = 'D:' + Constants.TAG_PROP +
>     > nameList = [Constants.PROP_SUCCESSOR_SET,
>     > Constants.PROP_VERSION_NAME, Constants.PROP_CREATOR] +    return
>     > XML_DOC_HEADER + \ +        '<%s xmlns:D="DAV:"><%s>' % (versions,
>     > prop) + \ +        reduce(lambda xml, name: xml + "<D:%s/>" %
>     > name[1], [''] + nameList) + \ +        '</%s></%s>' % (prop,
>     > versions) + +def _createUpdateBody(path): +    """ +    TBD + +
>     > @return: ... +    @rtype: string +    """ +    update = 'D:' +
>     > Constants.TAG_UPDATE +    version = 'D:' + Constants.TAG_VERSION +
>     > href = 'D:' + Constants.TAG_HREF +    #PROP = 'D:' + TAG_PROP +
>     > return XML_DOC_HEADER + \ +        '<%s xmlns:D="DAV:"><%s><%s>' %
>     > (update, version, href) + \ +        path + \ +
>     > '</%s></%s></%s>' % (href, version, update) + +def
>     > _extractSuccessorList(element): +    """ +    TBD + +    @return:
>     > ... +    @rtype: tuple of strings +    """ +    result = [] +
>     > for href in element.children: +
>     > result.append(href.textof()) +    return tuple(result) diff --git
>     > a/src/webdav/WebdavClient.py b/src/webdav/WebdavClient.py new file
>     > mode 100644 index 0000000..4cf8c90 --- /dev/null +++
>     > b/src/webdav/WebdavClient.py @@ -0,0 +1,833 @@ +# pylint:
>     > disable-msg=R0904,W0142,W0511,W0104,C0321,E1103,W0212 +# +#
>     > Copyright 2008 German Aerospace Center (DLR) +# +# Licensed under
>     > the Apache License, Version 2.0 (the "License"); +# you may not use
>     > this file except in compliance with the License. +# You may obtain
>     > a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +This module
>     > contains the classes ResourceStorer and CollectionStorer for
>     > accessing WebDAV resources. +""" + + +from davlib import
>     > XML_CONTENT_TYPE + +from urlparse import urlsplit +import re
>     > +import types +import sys +import os +import shutil + +from webdav
>     > import Constants +from webdav.WebdavResponse import LiveProperties
>     > +from webdav.WebdavRequests import createFindBody,
>     > createUpdateBody, createDeleteBody, createSearchBody +from
>     > webdav.Condition import ConditionTerm +from webdav.Connection
>     > import Connection, WebdavError, AuthorizationError +from
>     > webdav.VersionHandler import VersionHandler + +from
>     > webdav.acp.Privilege import Privilege +from webdav.acp.Acl import
>     > ACL +from webdav.NameCheck import validateResourceName,
>     > WrongNameError + + +__version__ = '$Revision$'[11:-2] + + +def
>     > switchUnicodeUrlOn(switch): +    """ +    Configure whether to use
>     > unicode (UTF-8) encoded URLs (default) or +    Latin-1 encoded
>     > URLs. + +    @param switch: 1 if unicode URLs shall be used +
>     > """ + +    assert switch == 0 or switch == 1, "Pass boolean
>     > argument, please." +    Constants.CONFIG_UNICODE_URL = switch + +
>     > +def parseDigestAuthInfo(authInfo): +    """ +    Parses the
>     > authentication information returned from a server and returns +
>     > a dictionary containing realm, qop, and nonce. + +    @see:
>     > L{AuthorizationError<webdav.Connection.AuthorizationError>} +    or
>     > the main function of this module. +    """ + +    info = dict() +
>     > info["realm"] = re.search('realm="([^"]+)"', authInfo).group(1) +
>     > info["qop"] = re.search('qop="([^"]+)"', authInfo).group(1) +
>     > info["nonce"] = re.search('nonce="([^"]+)"', authInfo).group(1) +
>     > return info + + +class ResourceStorer(object): +    """ +    This
>     > class provides client access to a WebDAV resource +    identified
>     > by an URI. It provides all WebDAV class 2 features which include +
>     > uploading data, getting and setting properties qualified by a XML
>     > name space, +    locking and unlocking the resource. +    This
>     > class does not cache resource data. This has to be performed by its
>     > clients. + +    @author: Roland Betz +    """ + +    # Instance
>     > properties +    url = property(lambda self: str(self.connection) +
>     > self.path, None, None, "Resource's URL") + +    def __init__(self,
>     > url, connection=None, validateResourceNames=True): +        """ +
>     > Creates an instance for the given URL +        User must invoke
>     > validate() after construction to check the resource on the server.
>     > + +        @param url: Unique resource location for this storer. +
>     > @type  url: C{string} +        @param connection: this optional
>     > parameter contains a Connection object +            for the host
>     > part of the given URL. Passing a connection saves +
>     > memory by sharing this connection. (defaults to None) +
>     > @type  connection: L{webdav.Connection} +        @raise
>     > WebdavError: If validation of resource name path parts fails. +
>     > """ + +        assert connection == None or isinstance(connection,
>     > Connection) +        parts = urlsplit(url, allow_fragments=False) +
>     > self.path = parts[2] +        self.validateResourceNames =
>     > validateResourceNames + +        # validate URL path +        for
>     > part in self.path.split('/'): +            if part != '' and not
>     > "ino:" in part: # explicitly allowing this character sequence as a
>     > part of a path (Tamino 4.4) +                if
>     > self.validateResourceNames: +                    try: +
>     > validateResourceName(part) +                    except
>     > WrongNameError: +                        raise WebdavError("Found
>     > invalid resource name part.") +                self.name
>     <http://self.name> = part +
>     > # was: filter(lambda part: part and validateResourceName(part),
>     > self.path.split('/')) +        # but filter is deprecated +
>     >  +        self.defaultNamespace = None     # default XML name space
>     > of properties +        if connection: +            self.connection
>     > = connection +        else: +            conn =
>     > parts[1].split(":") +            if len(conn) == 1: +
>     > self.connection = Connection(conn[0], protocol = parts[0])  # host
>     > and protocol +            else: +                self.connection =
>     > Connection(conn[0], int(conn[1]), protocol = parts[0])  # host and
>     > port and protocol +        self.versionHandler =
>     > VersionHandler(self.connection, self.path) + + +    def
>     > validate(self): +        """ +        Check whether URL contains a
>     > WebDAV resource +        Uses the WebDAV OPTIONS method. + +
>     > @raise WebdavError: L{WebdavError} if URL does not contain a WebDAV
>     > resource +        """ +        #davHeader =
>     > response.getheader(HTTP_HEADER_DAV) +        davHeader =
>     > self.getSpecificOption(Constants.HTTP_HEADER_DAV) +
>     > self.connection.logger.debug("HEADER DAV: %s" % davHeader) +
>     > if not(davHeader) or davHeader.find("2") < 0:   # DAV class 2
>     > supported ? +            raise WebdavError("URL does not support
>     > WebDAV", 0) + +    def options(self): +        """ +        Send an
>     > OPTIONS request to server and return all HTTP headers. + +
>     > @return: map of all HTTP headers returned by the OPTIONS method. +
>     > """ +        response = self.connection.options(self.path) +
>     > result = {} +        result.update(response.msg) +
>     > self.connection.logger.debug("OPTION returns: " +
>     > str(result.keys())) +        return result + +    def
>     > _getAclSupportAvailable(self): +        """ +        Returns True
>     > if the current connection has got ACL support. + +        @return:
>     > ACL support (True / False) +        @rtype: C{bool} +        """ +
>     > options = self.getSpecificOption(Constants.HTTP_HEADER_DAV) +
>     > if options.find(Constants.HTTP_HEADER_OPTION_ACL) >= 0: +
>     > return True +        else: +            return False + +
>     > aclSupportAvailable = property(_getAclSupportAvailable) + +    def
>     > _getDaslBasicsearchSupportAvailable(self): +        """ +
>     > Returns True if the current connection supports DASL basic search.
>     > + +        @return: DASL basic search support (True / False) +
>     > @rtype: C{bool} +        """ +        options =
>     > self.getSpecificOption(Constants.HTTP_HEADER_DASL) +        if not
>     > options or \ +           not
>     > options.find(Constants.HTTP_HEADER_OPTION_DAV_BASIC_SEARCH) >= 0: +
>     > return False +        else: +            return True + +
>     > daslBasicsearchSupportAvailable =
>     > property(_getDaslBasicsearchSupportAvailable) + +    def
>     > isConnectedToCatacombServer(self): +        """ +        Returns
>     > True if connected to a Catacomb WebDav server. + +        @return:
>     > if connected to Catacomb Webdav server (True / False) +
>     > @rtype: C{bool} +        """ +        if not
>     > self.connection.serverTypeChecked: +            options =
>     > self.getSpecificOption(Constants.HTTP_HEADER_SERVER) +
>     > if options.find(Constants.HTTP_HEADER_SERVER_TAMINO) >= 0: +
>     > self.connection.isConnectedToCatacomb = False +            else: +
>     > self.connection.isConnectedToCatacomb = True +
>     > self.connection.serverTypeChecked = True +        return
>     > self.connection.isConnectedToCatacomb + +    def
>     > getSpecificOption(self, option): +        """ +        Returns
>     > specified WebDav options. +        @param option: name of the
>     > option + +        @return: String containing the value of the
>     > option. +        @rtype: C{string} +        """ +        options =
>     > '' +        try: +            options = self.options().get(option)
>     > +        except KeyError: +            return options +
>     > return options + +    ### delegate some method invocations +    def
>     > __getattr__(self, name): +        """ +        Build-in method: +
>     > Forwards unknow lookups (methods) to delegate object
>     > 'versionHandler'. + +        @param name: name of unknown
>     > attribute +        """ +        # delegate Delta-V methods +
>     > return getattr(self.versionHandler, name) + +    def copy(self,
>     > toUrl, infinity=True): +        """ +        Copies this resource.
>     > + +        @param toUrl: target URI path +        @param infinity:
>     > Flag that indicates that the complete content of collection is
>     > copied. (default) +        @type depth: C{boolean} +        """ +
>     > self.connection.logger.debug("Copy to " + repr(toUrl)); +
>     > _checkUrl(toUrl) +        if infinity: +            response =
>     > self.connection.copy(self.path, toUrl) +        else: +
>     > response = self.connection.copy(self.path, toUrl, 0) +        if
>     > response.status == Constants.CODE_MULTISTATUS and
>     > response.msr.errorCount > 0: +            raise
>     > WebdavError("Request failed: " + response.msr.reason,
>     > response.msr.code) + +    def delete(self, lockToken=None): +
>     > """ +        Deletes this resource. + +        @param lockToken:
>     > String returned by last lock operation or null. +        @type
>     > lockToken: L{LockToken} +        """ +        assert lockToken ==
>     > None or isinstance(lockToken, LockToken), \ +
>     > "Invalid lockToken argument %s" % type(lockToken) +        header =
>     > {} +        if lockToken: +            header =
>     > lockToken.toHeader() +        response =
>     > self.connection.delete(self.path, header) +        if
>     > response.status == Constants.CODE_MULTISTATUS and
>     > response.msr.errorCount > 0: +            raise
>     > WebdavError("Request failed: " + response.msr.reason,
>     > response.msr.code) + +    def move(self, toUrl): +        """ +
>     > Moves this resource to the given path or renames it. + +
>     > @param toUrl: new (URI) path +        """ +
>     > self.connection.logger.debug("Move to " + repr(toUrl)); +
>     > _checkUrl(toUrl) +        response =
>     > self.connection.move(self.path, toUrl) +        if  response.status
>     > == Constants.CODE_MULTISTATUS and response.msr.errorCount > 0: +
>     > raise WebdavError("Request failed: " + response.msr.reason,
>     > response.msr.code) + + +    def lock(self, owner): +        """ +
>     > Locks this resource for exclusive write access. This means that for
>     > succeeding +        write operations the returned lock token has to
>     > be passed. +        If the methode does not throw an exception the
>     > lock has been granted. + +        @param owner: describes the lock
>     > holder +        @return: lock token string (automatically
>     > generated) +        @rtype: L{LockToken} +        """ +
>     > response = self.connection.lock(self.path, owner) +        if
>     > response.status == Constants.CODE_MULTISTATUS and
>     > response.msr.errorCount > 0: +            raise
>     > WebdavError("Request failed: " + response.msr.reason,
>     > response.msr.code) +        return LockToken(self.url,
>     > response.locktoken) + +    def unlock(self, lockToken): +
>     > """ +        Removes the lock from this resource. + +        @param
>     > lockToken: which has been return by the lock() methode +
>     > @type  lockToken: L{LockToken} +        """ +
>     > self.connection.unlock(self.path, lockToken.token) + + +    def
>     > deleteContent(self, lockToken=None): +        """ +        Delete
>     > binary data at permanent storage. + +        @param lockToken: None
>     > or lock token from last lock request +        @type  lockToken:
>     > L{LockToken} +        """ +        assert lockToken == None or
>     > isinstance(lockToken, LockToken), \ +                "Invalid
>     > lockToken argument %s" % type(lockToken) +        header = {} +
>     > if lockToken: +            header = lockToken.toHeader() +
>     > self.connection.put(self.path, "", extra_hdrs=header) + +    def
>     > uploadContent(self, content, lockToken=None): +        """ +
>     > Write binary data to permanent storage. + +        @param content:
>     > containing binary data +        @param lockToken: None or lock
>     > token from last lock request +        @type  lockToken:
>     > L{LockToken} +        """ +        assert not content or
>     > isinstance(content, types.UnicodeType) or\ +
>     > isinstance(content, types.StringType), "Content is not a string: "
>     > + content.__class__.__name__ +        assert lockToken == None or
>     > isinstance(lockToken, LockToken), \ +                "Invalid
>     > lockToken argument %s" % type(lockToken) +        header = {} +
>     > if lockToken: +            header = lockToken.toHeader() +
>     > response = None +        if not content is None: +
>     > header["Content-length"] = len(content) +        else: +
>     > header["Content-length"] = 0 + +        try: +            response
>     > = self.connection.put(self.path, content, extra_hdrs=header) +
>     > finally: +            if response: +
>     > self.connection.logger.debug(response.read()) +
>     > response.close() + +    def uploadFile(self, newFile,
>     > lockToken=None): +        """ +        Write binary data to
>     > permanent storage. + +        @param newFile: File containing
>     > binary data. +        @param lockToken: None or lock token from
>     > last lock request +        @type  lockToken: L{LockToken} +
>     > """ +        assert isinstance(newFile, types.FileType), "Argument
>     > is no file: " + file.__class__.__name__ +        assert lockToken
>     > == None or isinstance(lockToken, LockToken), \ +
>     > "Invalid lockToken argument %s" % type(lockToken) +        header =
>     > {} +        if lockToken: +            header =
>     > lockToken.toHeader() +        self.connection.putFile(self.path,
>     > newFile, header=header) + +    def downloadContent(self): +
>     > """ +        Read binary data from permanent storage. +        """
>     > +        response = self.connection.get(self.path) +        # TODO:
>     > Other interface ? return self.connection.getfile() +        return
>     > response + +    def downloadFile(self, localFileName): +
>     > """ +        Copy binary data from permanent storage to a local
>     > file. + +        @param localFileName: file to write binary data
>     > to +        """ +        localFile = open(localFileName, 'wb') +
>     > remoteFile = self.downloadContent() +
>     > _blockCopyFile(remoteFile, localFile, Connection.blockSize) +
>     > remoteFile.close() +        localFile.close() + +    def
>     > readProperties(self, *names): +        """ +        Reads the given
>     > properties. + +        @param names: a list of property names. +
>     > A property name is a (XmlNameSpace, propertyName) tuple. +
>     > @return: a map from property names to DOM Element or String
>     > values. +        """ +        assert names, "Property names are
>     > missing." +        body = createFindBody(names,
>     > self.defaultNamespace) +        response =
>     > self.connection.propfind(self.path, body, depth=0) +
>     > properties = response.msr.values()[0] +        if
>     > properties.errorCount > 0: +            raise WebdavError("Property
>     > is missing on '%s': %s" % (self.path, properties.reason),
>     > properties.code) +        return properties + +    def
>     > readProperty(self, nameSpace, name): +        """ +        Reads
>     > the given property. + +        @param nameSpace: XML-namespace +
>     > @type nameSpace: string +        @param name: A property name. +
>     > @type name: string + +        @return: a map from property names to
>     > DOM Element or String values. +        """ +        results =
>     > self.readProperties((nameSpace, name)) +        if  len(results) ==
>     > 0: +            raise WebdavError("Property is missing: " +
>     > results.reason) +        return results.values()[0] + +    def
>     > readAllProperties(self): +        """ +        Reads all properties
>     > of this resource. + +        @return: a map from property names to
>     > DOM Element or String values. +        """ +        response =
>     > self.connection.allprops(self.path, depth=0) +        return
>     > response.msr.values()[0] + +    def readAllPropertyNames(self): +
>     > """ +        Returns the names of all properties attached to this
>     > resource. + +        @return: List of property names +        """ +
>     > response = self.connection.propnames(self.path, depth=0) +
>     > return response.msr.values()[0] + +    def
>     > readStandardProperties(self): +        """ +        Read all WebDAV
>     > live properties. + +        @return: A L{LiveProperties} instance
>     > which contains a getter method for each live property. +
>     > """ +        body = createFindBody(LiveProperties.NAMES,
>     > Constants.NS_DAV) +        response =
>     > self.connection.propfind(self.path, body, depth=0) +
>     > properties = response.msr.values()[0] +        return
>     > LiveProperties(properties) + +    def writeProperties(self,
>     > properties, lockToken=None): +        """ +        Sets or updates
>     > the given properties. + +        @param lockToken: if the resource
>     > has been locked this is the lock token. +        @type  lockToken:
>     > L{LockToken} +        @param properties: a map from property names
>     > to a String or +                           DOM element value for
>     > each property to add or update. +        """ +        assert
>     > isinstance(properties, types.DictType) +        assert lockToken ==
>     > None or isinstance(lockToken, LockToken), \ +
>     > "Invalid lockToken argument %s" % type(lockToken) +        header =
>     > {} +        if lockToken: +            header =
>     > lockToken.toHeader() +        body = createUpdateBody(properties,
>     > self.defaultNamespace) +        response =
>     > self.connection.proppatch(self.path, body, header) +        if
>     > response.msr.errorCount > 0: +            raise
>     > WebdavError("Request failed: " + response.msr.reason,
>     > response.msr.code) + +    def deleteProperties(self,
>     > lockToken=None, *names): +        """ +        Removes the given
>     > properties from this resource. + +        @param lockToken: if the
>     > resource has been locked this is the lock token. +        @type
>     > lockToken: L{LockToken} +        @param names: a collection of
>     > property names. +               A property name is a (XmlNameSpace,
>     > propertyName) tuple. +        """ +        assert lockToken == None
>     > or isinstance(lockToken, LockToken), \ +                "Invalid
>     > lockToken argument %s" % type(lockToken) +        header = {} +
>     > if lockToken: +            header = lockToken.toHeader() +
>     > body = createDeleteBody(names, self.defaultNamespace) +
>     > response = self.connection.proppatch(self.path, body, header) +
>     > if  response.msr.errorCount > 0: +            raise
>     > WebdavError("Request failed: " + response.msr.reason,
>     > response.msr.code) + +    # ACP extension +    def setAcl(self,
>     > acl, lockToken=None): +        """ +        Sets ACEs in the
>     > non-inherited and non-protected ACL or the resource. +        This
>     > is the implementation of the ACL method of the WebDAV ACP. +
>     >  +        @param acl: ACL to be set on resource as ACL object. +
>     > @param lockToken: If the resource has been locked this is the lock
>     > token (defaults to None). +        @type  lockToken: L{LockToken} +
>     > """ +        assert lockToken == None or isinstance(lockToken,
>     > LockToken), \ +                "Invalid lockToken argument %s" %
>     > type(lockToken) +        headers = {} +        if lockToken: +
>     > headers = lockToken.toHeader() +        headers['Content-Type'] =
>     > XML_CONTENT_TYPE +        body                    = acl.toXML() +
>     > response = self.connection._request('ACL', self.path, body,
>     > headers) +        return response +        ## TODO: parse DAV:error
>     > response + +    def getAcl(self): +        """ +        Returns
>     > this resource's ACL in an ACL instance. + +        @return: Access
>     > Control List. +        @rtype: L{ACL<webdav.acp.Acl.ACL>} +
>     > """ +        xmlAcl = self.readProperty(Constants.NS_DAV,
>     > Constants.TAG_ACL) +        return ACL(xmlAcl) + +    def
>     > getCurrentUserPrivileges(self): +        """ +        Returns a
>     > tuple of the current user privileges. + +        @return: list of
>     > Privilege instances +        @rtype: list of
>     > L{Privilege<webdav.acp.Privilege.Privilege>} +        """ +
>     > privileges = self.readProperty(Constants.NS_DAV,
>     > Constants.PROP_CURRENT_USER_PRIVILEGE_SET) +        result = [] +
>     > for child in privileges.children: +
>     > result.append(Privilege(domroot=child)) +        return result +
>     >  +    def getPrincipalCollections(self): +        """ +
>     > Returns a list principal collection URLs. + +        @return: list
>     > of principal collection URLs +        @rtype: C{list} of C{unicode}
>     > elements +        """ +        webdavQueryResult =
>     > self.readProperty(Constants.NS_DAV,
>     > Constants.PROP_PRINCIPAL_COLLECTION_SET) +
>     > principalCollectionList = [] +        for child in
>     > webdavQueryResult.children: +
>     > principalCollectionList.append(child.first_cdata) +        return
>     > principalCollectionList + +    def getOwnerUrl(self): +        """
>     > Explicitly retireve the Url of the owner. """ + +        result =
>     > self.readProperty(Constants.NS_DAV, Constants.PROP_OWNER) +
>     > if result and len(result.children): +            return
>     > result.children[0].textof() +        return None + +class
>     > CollectionStorer(ResourceStorer): +    """ +    This class provides
>     > client access to a WebDAV collection resource identified by an
>     > URI. +    This class does not cache resource data. This has to be
>     > performed by its clients. + +    @author: Roland Betz +    """ +
>     >  +    def __init__(self, url, connection=None,
>     > validateResourceNames=True): +        """ +        Creates a
>     > CollectionStorer instance for a URL and an optional Connection
>     > object. +        User must invoke validate() after constuction to
>     > check the resource on the server. + +        @see:
>     > L{webdav.WebdavClient.ResourceStorer.__init__} +        @param url:
>     > unique resource location for this storer +        @param
>     > connection: this optional parameter contains a Connection object
>     > for the host part +            of the given URL. Passing a
>     > connection saves memory by sharing this connection. +        """ +
>     > if  url[-1] != '/':     # Collection URL must end with slash +
>     > url += '/' +        ResourceStorer.__init__(self, url, connection,
>     > validateResourceNames) + +    def getResourceStorer(self, name): +
>     > """ +        Return a ResourceStorer instance for a child resource
>     > (member) of this Collection. + +        @param name: leaf name of
>     > child resource +        @return: L{ResourceStorer} instance +
>     > """ +        assert isinstance(name, types.StringType) or
>     > isinstance(name, types.UnicodeType) +        return
>     > ResourceStorer(self.url + name, self.connection,
>     > self.validateResourceNames) + +    def validate(self): +
>     > """ +        Check whether this URL contains a WebDAV collection. +
>     > Uses the WebDAV OPTION method. + +        @raise WebdavError:
>     > L{WebdavError} if URL does not contain a WebDAV collection
>     > resource. +        """ +        super(CollectionStorer,
>     > self).validate() +        isCollection =
>     > self.readProperty(Constants.NS_DAV, Constants.PROP_RESOURCE_TYPE) +
>     > if not (isCollection and isCollection.children): +            raise
>     > WebdavError("Not a collection URL.", 0) + +    def
>     > addCollection(self, name, lockToken=None): +        """ +
>     > Make a new WebDAV collection resource within this collection. +
>     >  +        @param name: of the new collection +        @param
>     > lockToken: None or token returned by last lock operation +
>     > @type  lockToken: L{LockToken} +        """ +        assert
>     > isinstance(name, types.StringType) or isinstance(name,
>     > types.UnicodeType) +        assert lockToken == None or
>     > isinstance(lockToken, LockToken), \ +                "Invalid
>     > lockToken argument %s" % type(lockToken) +        header = {} +
>     > if lockToken: +            header = lockToken.toHeader() +
>     > if self.validateResourceNames: +
>     > validateResourceName(name) +        if  name[-1] != '/':     #
>     > Collection URL must end with slash +            name += '/' +
>     > self.connection.mkcol(self.path + name, header) +        return
>     > CollectionStorer(self.url + name, self.connection,
>     > self.validateResourceNames) + +    def addResource(self, name,
>     > content=None, properties=None, lockToken=None): +        """ +
>     > Create a new empty WebDAV resource contained in this collection
>     > with the given +        properties. + +        @param name: leaf
>     > name of the new resource +        @param content: None or initial
>     > binary content of resource +        @param properties:
>     > name/value-map containing properties +        @param lockToken:
>     > None or token returned by last lock operation +        @type
>     > lockToken: L{LockToken} +        """ +        assert
>     > isinstance(name, types.StringType) or isinstance(name,
>     > types.UnicodeType) +        assert lockToken == None or
>     > isinstance(lockToken, LockToken), \ +                "Invalid
>     > lockToken argument %s" % type(lockToken) +        if
>     > self.validateResourceNames: +            validateResourceName(name)
>     > # check for invalid characters +        resource_ =
>     > ResourceStorer(self.url + name, self.connection,
>     > self.validateResourceNames) +
>     > resource_.uploadContent(content, lockToken) +        if
>     > properties: +            resource_.writeProperties(properties,
>     > lockToken) +        return resource_ + +    def
>     > deleteResource(self, name, lockToken=None): +        """ +
>     > Delete a collection which is contained within this collection +
>     >  +        @param name: leaf name of a contained collection
>     > resource +        @param lockToken: None or token returned by last
>     > lock operation +        @type  lockToken: L{LockToken} +
>     > """ +        assert isinstance(name, types.StringType) or
>     > isinstance(name, types.UnicodeType) +        assert lockToken ==
>     > None or isinstance(lockToken, LockToken), \ +
>     > "Invalid lockToken argument %s" % type(lockToken) +        header =
>     > {} +        if lockToken: +            header =
>     > lockToken.toHeader() +        if self.validateResourceNames: +
>     > validateResourceName(name) +        response =
>     > self.connection.delete(self.path + name, header) +        if
>     > response.status == Constants.CODE_MULTISTATUS and
>     > response.msr.errorCount > 0: +            raise
>     > WebdavError("Request failed: %s" % response.msr.reason,
>     > response.msr.code) + +    def lockAll(self, owner): +        """ +
>     > Locks this collection resource for exclusive write access. This
>     > means that for +        succeeding write operations the returned
>     > lock token has to be passed. +        The operation is applied
>     > recursively to all contained resources. +        If the methode
>     > does not throw an exception then the lock has been granted. +
>     >  +        @param owner: describes the lock holder +        @return:
>     > Lock token string (automatically generated). +        @rtype:
>     > L{LockToken} +        """ +        assert isinstance(owner,
>     > types.StringType) or isinstance(owner, types.UnicodeType) +
>     > response = self.connection.lock(self.path, owner,
>     > depth=Constants.HTTP_HEADER_DEPTH_INFINITY) +        return
>     > LockToken(self.url, response.locktoken) + +    def
>     > listResources(self): +        """ +        Describe all members
>     > within this collection. + +        @return: map from URI to a
>     > L{LiveProperties} instance containing the WebDAV +
>     > live attributes of the contained resource +        """ +        #
>     > *LiveProperties.NAMES denotes the list of all live properties as
>     > an +        # argument to the method call. +        response =
>     > self.connection.getprops(self.path, +
>     > depth=1, +
>     > ns=Constants.NS_DAV, +
>     > *LiveProperties.NAMES) +        result = {} +        for path,
>     > properties in response.msr.items(): +            if path ==
>     > self.path:      # omit this collection resource +
>     > continue +            ## some servers do not append a trailing
>     > slash to collection paths +            if self.path.endswith('/')
>     > and self.path[0:-1] == path: +                continue +
>     > result[path] = LiveProperties(properties=properties) +
>     > return result + +    def getCollectionContents(self): +        """
>     > +        Return a list of the tuple (resources or collection) /
>     > properties) + +        @return: a list of the tuple (resources or
>     > collection) / properties) +        @rtype: C{list} +        """ +
>     > self.validate() +        collectionContents = [] +        result =
>     > self.listResources() +        for url, properties_ in
>     > result.items(): +            if not self.path == url: +
>     > if properties_.getResourceType() == 'resource': +
>     > myWebDavStorer = ResourceStorer(url, self.connection,
>     > self.validateResourceNames) +                else: +
>     > myWebDavStorer = CollectionStorer(url, self.connection,
>     > self.validateResourceNames) +
>     > collectionContents.append((myWebDavStorer, properties_)) +
>     > return collectionContents + +    def findProperties(self, *names):
>     > +        """ +        Retrieve given properties for this collection
>     > and all directly contained resources. + +        @param names: a
>     > list of property names +        @return: a map from resource URI to
>     > a map from property name to value. +        """ +        assert
>     > isinstance(names, types.ListType) or isinstance(names,
>     > types.TupleType), \ +                "Argument name has type %s" %
>     > str(type(names)) +        body = createFindBody(names,
>     > self.defaultNamespace) +        response =
>     > self.connection.propfind(self.path, body, depth=1) +        return
>     > response.msr + +    def deepFindProperties(self, *names): +
>     > """ +        Retrieve given properties for this collection and all
>     > contained (nested) resources. + +        Note: +        ===== +
>     > This operation can take a long time if used with recursive=true and
>     > is therefore +          disabled on some WebDAV servers. + +
>     > @param names: a list of property names +        @return: a map from
>     > resource URI to a map from property name to value. +        """ +
>     > assert isinstance(names, types.ListType.__class__) or
>     > isinstance(names, types.TupleType), \ +                "Argument
>     > name has type %s" % str(type(names)) +        body =
>     > createFindBody(names, self.defaultNamespace) +        response =
>     > self.connection.propfind(self.path, body,
>     > depth=Constants.HTTP_HEADER_DEPTH_INFINITY) +        return
>     > response.msr + +    def findAllProperties(self): +        """ +
>     > Retrieve all properties for this collection and all directly
>     > contained resources. + +        @return: a map from resource URI to
>     > a map from property name to value. +        """ +        response =
>     > self.connection.allprops(self.path, depth=1) +        return
>     > response.msr + + +    # DASL extension +    def search(self,
>     > conditions, selects): +        """ +        Search for contained
>     > resources which match the given search condition. + +        @param
>     > conditions: tree of ConditionTerm instances representing a logical
>     > search term +        @param selects: list of property names to
>     > retrieve for the found resources +        """ +        assert
>     > isinstance(conditions, ConditionTerm) +        headers = {
>     > 'Content-Type' : XML_CONTENT_TYPE, "depth":
>     > Constants.HTTP_HEADER_DEPTH_INFINITY} +        body =
>     > createSearchBody(selects, self.path, conditions) +        response
>     > = self.connection._request('SEARCH', self.path, body, headers) +
>     > return response.msr + + +class LockToken(object): +    """ +
>     > This class provides help on handling WebDAV lock tokens. + +
>     > @author: Roland Betz +    """ +    # restrict instance variables +
>     > __slots__ = ('url', 'token') + +    def __init__(self, url,
>     > token): +        assert isinstance(url, types.StringType) or
>     > isinstance(url, types.UnicodeType), \ +            "Invalid url
>     > argument %s" % type(url) +        assert isinstance(token,
>     > types.StringType) or isinstance(token, types.UnicodeType), \ +
>     > "Invalid lockToken argument %s" % type(token) +        self.url =
>     > url +        self.token = token + +    def value(self): +
>     > """ +        Descriptive string containing the lock token's URL and
>     > the token itself. + +        @return: Descriptive lock token with
>     > URL. +        @rtype: C{string} +        """ +        return "<" +
>     > self.url + "> (<" + self.token + ">)" + +    def toHeader(self): +
>     > """ +        Header fragment for WebDAV request. + +
>     > @return: Dictionary containing an entry for the lock token query. +
>     > @rtype: C{dictionary} +        """ +        return
>     > {Constants.HTTP_HEADER_IF: self.value()} + +    def __str__(self):
>     > +        return self.value() + + +def _blockCopyFile(source, dest,
>     > blockSize): +    """ +    Copies a file in chunks of C{blockSize}.
>     > + +    @param source: Source file. +    @type  source: FileIO
>     > buffer. +    @param dest: Destination file. +    @type  dest:
>     > FileIO buffer. +    @param blockSize: Size of block in bytes. +
>     > @type  blockSize: C{int} +    """ +    transferedBytes = 0 +
>     > block = source.read(blockSize) +    while len(block): +
>     > dest.write(block) +        transferedBytes += len(block); +
>     > block = source.read(blockSize) + +def _checkUrl(url): +    """ +
>     > Checks the given URL for validity. + +    @param url: URL to
>     > check. +    @type  url: C{string} + +    @raise ValueError: If the
>     > URL does not contain valid/usable content. +    """ + +    parts =
>     > urlsplit(url, allow_fragments=False) +    if len(parts[0]) == 0 or
>     > len(parts[1]) == 0 or len(parts[2]) == 0: +        raise
>     > ValueError("Invalid URL: " + repr(url)) diff --git
>     > a/src/webdav/WebdavRequests.py b/src/webdav/WebdavRequests.py new
>     > file mode 100644 index 0000000..79e586a --- /dev/null +++
>     > b/src/webdav/WebdavRequests.py @@ -0,0 +1,205 @@ +# pylint:
>     > disable-msg=W0511,W0212,E1111 +# +# Copyright 2008 German Aerospace
>     > Center (DLR) +# +# Licensed under the Apache License, Version 2.0
>     > (the "License"); +# you may not use this file except in compliance
>     > with the License. +# You may obtain a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +This module handles
>     > WebDav server requests. +""" + + +import types +from webdav import
>     > Constants +import qp_xml +from tempfile import TemporaryFile +
>     > +from davlib import XML_DOC_HEADER + +from webdav.NameCheck import
>     > validatePropertyName + + +__version__ = "$LastChangedRevision$" +
>     > + +## TODO: create a property list class + +class
>     > XmlNameSpaceMangler(object): +    ''' +    Handles WebDav
>     > requests. +    ''' + +    # restrict instance variables +
>     > __slots__ = ('shortcuts', 'defaultNameSpace') + +    def
>     > __init__(self, nameList, defaultNameSpace = None): +        ''' +
>     >  +        @param nameList: +        @param defaultNameSpace: +
>     > ''' + +        assert isinstance(nameList, types.ListType) or
>     > isinstance(nameList, types.TupleType), \ +            "1. argument
>     > has wrong type %s" % type(nameList) +        self.shortcuts = {} +
>     > self.defaultNameSpace = defaultNameSpace +        for name in
>     > nameList: +            if  not isinstance(name, types.TupleType): +
>     > name = (defaultNameSpace, name) +            assert
>     > isinstance(name, types.TupleType) and len(name) == 2, \ +
>     > "Name is not a namespace, name tuple: %s" % type(name) +
>     > validatePropertyName(name[1]) +            if  name[0] and not
>     > self.shortcuts.has_key(name[0]): +
>     > self.shortcuts[name[0]] = 'ns%d' % len(self.shortcuts) + +    def
>     > getNameSpaces(self): +        ''' +        Returns the namespace. +
>     > ''' + +        result = "" +        for namespace, short in
>     > self.shortcuts.items(): +            result += ' xmlns:%s="%s"' %
>     > (short, namespace) +        return result + +    def
>     > getUpdateElements(self, valueMap): +        ''' + +        @param
>     > valueMap: +        ''' + +        elements = "" +        for name
>     > in valueMap.keys(): +            fullname = name +            if
>     > isinstance(name, types.StringType): +                fullname =
>     > (self.defaultNameSpace, name) +            if  not fullname[0]: +
>     > tag = fullname[1] +            else: +                tag =
>     > self.shortcuts[fullname[0]] + ':' + fullname[1] +            value
>     > = valueMap[name] +            if value: +                if
>     > isinstance(value, qp_xml._element): +                    tmpFile =
>     > TemporaryFile('w+') +                    value =
>     > qp_xml.dump(tmpFile, value) +                    tmpFile.flush() +
>     > tmpFile.seek(0) +                    tmpFile.readline() +
>     > value = tmpFile.read() +                else: +
>     > value = "<![CDATA[%s]]>" % value +            else: +
>     > value = "" +            elements += "<%s>%s</%s>" % (tag, value,
>     > tag) +        return elements + +    def getNameElements(self,
>     > nameList): +        ''' + +        @param nameList: +        ''' +
>     >  +        elements = "" +        for name in nameList: +
>     > if  isinstance(name, types.StringType): +                name =
>     > (self.defaultNameSpace, name) +            if  not name[0]: +
>     > tag = name[1] +            else: +                tag =
>     > self.shortcuts[name[0]] + ':' + name[1] +            elements +=
>     > "<%s />" % tag +        return elements + + + +def
>     > createUpdateBody(propertyDict, defaultNameSpace = None): +    ''' +
>     >  +    @param propertyDict: +    @param defaultNameSpace: +    ''' +
>     >  +    updateTag = 'D:' + Constants.TAG_PROPERTY_UPDATE +    setTag
>     > = 'D:' + Constants.TAG_PROPERTY_SET +    propTag = 'D:' +
>     > Constants.TAG_PROP +    mangler =
>     > XmlNameSpaceMangler(propertyDict.keys(), defaultNameSpace) +
>     > return XML_DOC_HEADER + \ +        '<%s xmlns:D="DAV:"><%s><%s %s>'
>     > % (updateTag, setTag, propTag, mangler.getNameSpaces()) + \ +
>     > mangler.getUpdateElements(propertyDict) + \ +
>     > '</%s></%s></%s>' % (propTag, setTag, updateTag) + + +def
>     > createDeleteBody(nameList, defaultNameSpace = None): +    ''' +
>     >  +    @param nameList: +    @param defaultNameSpace: +    ''' +
>     >  +    updateTag = 'D:' + Constants.TAG_PROPERTY_UPDATE +
>     > removeTag = 'D:' + Constants.TAG_PROPERTY_REMOVE +    propTag =
>     > 'D:' + Constants.TAG_PROP +    mangler =
>     > XmlNameSpaceMangler(nameList, defaultNameSpace) +    return
>     > XML_DOC_HEADER + \ +        '<%s xmlns:D="DAV:"><%s><%s %s>' %
>     > (updateTag, removeTag, propTag, mangler.getNameSpaces()) + \ +
>     > mangler.getNameElements(nameList) + \ +        '</%s></%s></%s>' %
>     > (propTag, removeTag, updateTag) + + +def createFindBody(nameList,
>     > defaultNameSpace = None): +    ''' + +    @param nameList: +
>     > @param defaultNameSpace: +    ''' + +    findTag = 'D:' +
>     > Constants.TAG_PROPERTY_FIND +    propTag = 'D:' +
>     > Constants.TAG_PROP +    mangler = XmlNameSpaceMangler(nameList,
>     > defaultNameSpace) +    return XML_DOC_HEADER + \ +        '<%s
>     > xmlns:D="DAV:"><%s %s>' % (findTag, propTag,
>     > mangler.getNameSpaces()) + \ +
>     > mangler.getNameElements(nameList) + \ +        '</%s></%s>' %
>     > (propTag, findTag) + + +def createSearchBody(selects, path,
>     > conditions, defaultNameSpace = None): +    ''' +    Creates DASL
>     > XML body. + +    @param selects: list of property names to retrieve
>     > for the found resources +    @param path: list of conditions +
>     > @param conditions: tree of ConditionTerm instances representing a
>     > logical search term +    @param defaultNameSpace: default
>     > namespace +    ''' + +    searchTag = 'D:' +
>     > Constants.TAG_SEARCH_REQUEST +    basicTag = 'D:' +
>     > Constants.TAG_SEARCH_BASIC +    selectTag = 'D:' +
>     > Constants.TAG_SEARCH_SELECT +    fromTag = 'D:' +
>     > Constants.TAG_SEARCH_FROM +    scopeTag = 'D:' +
>     > Constants.TAG_SEARCH_SCOPE +    whereTag = 'D:' +
>     > Constants.TAG_SEARCH_WHERE +    propTag = 'D:' +
>     > Constants.TAG_PROP +    hrefTag = 'D:' + Constants.TAG_HREF +
>     > depthTag = 'D:' + Constants.TAG_LOCK_DEPTH +    depthValue =
>     > Constants.HTTP_HEADER_DEPTH_INFINITY +    mangler =
>     > XmlNameSpaceMangler(selects, defaultNameSpace) +    return
>     > XML_DOC_HEADER + \ +        '<%s xmlns:D="DAV:"><%s>' % (searchTag,
>     > basicTag) + \ +        '<%s><%s %s>%s</%s></%s>' % (selectTag,
>     > propTag, mangler.getNameSpaces(), +
>     > mangler.getNameElements(selects), propTag, selectTag) + \ +
>     > '<%s><%s><%s>%s</%s><%s>%s</%s></%s></%s>' % (fromTag, scopeTag,
>     > hrefTag, path, hrefTag, +
>     > depthTag, depthValue, depthTag, scopeTag, fromTag) + \ +
>     > '<%s>%s</%s>' % (whereTag, conditions.toXML(),whereTag) + \ +
>     > '</%s></%s>' % (basicTag, searchTag) + \ No newline at end of file
>     > diff --git a/src/webdav/WebdavResponse.py
>     > b/src/webdav/WebdavResponse.py new file mode 100644 index
>     > 0000000..c84943d --- /dev/null +++ b/src/webdav/WebdavResponse.py
>     > @@ -0,0 +1,525 @@ +# pylint:
>     > disable-msg=R0903,W0142,W0221,W0212,W0104,W0511,C0103,R0901 +# +#
>     > Copyright 2008 German Aerospace Center (DLR) +# +# Licensed under
>     > the Apache License, Version 2.0 (the "License"); +# you may not use
>     > this file except in compliance with the License. +# You may obtain
>     > a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +Handles WebDAV
>     > responses. +""" + + +from davlib import _parse_status +import
>     > qp_xml +from webdav import Constants +import time +import rfc822
>     > +import urllib +# Handling Jython 2.5 bug concerning the date
>     > pattern +# conversion in time.strptime +try: +    from java.lang
>     > import IllegalArgumentException +except ImportError: +    class
>     > IllegalArgumentException(object): +        pass + + +__version__ =
>     > "$LastChangedRevision$" + + +class HttpStatus(object): +    """ +
>     > TBD + +    @ivar code: +    @type code: +    @ivar reason: +
>     > @type reason: +    @ivar errorCount: +    @type errorCount: int +
>     > """ + +    def __init__(self, elem): +        """ +        TBD +
>     >  +        @param elem: ... +        @type elem: instance of
>     > L{Element} +        """ +        self.code, self.reason =
>     > _parse_status(elem) +        self.errorCount = (self.code >=
>     > Constants.CODE_LOWEST_ERROR) +    def __str__(self): +
>     > return "HTTP status %d: %s" % (self.code, self.reason) + + +class
>     > MultiStatusResponse(dict): +    """ +    TBD + +    @ivar status: +
>     > @type status: +    @ivar reason: +    @type reason: +    @ivar
>     > errorCount: +    @type errorCount: +    """ + +    # restrict
>     > instance variables +    __slots__ = ('errorCount', 'reason',
>     > 'status') + +    def __init__(self, domroot): +
>     > dict.__init__(self) +        self.errorCount = 0 +
>     > self.reason = None +        self.status =
>     > Constants.CODE_MULTISTATUS +        if (domroot.ns !=
>     > Constants.NS_DAV) or (domroot.name <http://domroot.name> !=
>     Constants.TAG_MULTISTATUS): +
>     > raise ResponseFormatError(domroot, 'Invalid response:
>     > <DAV:multistatus> expected.') +        self._scan(domroot) + +
>     > def getCode(self): +        if self.errorCount == 0: +
>     > return Constants.CODE_SUCCEEDED +        if  len(self) >
>     > self.errorCount: +            return Constants.CODE_MULTISTATUS +
>     > return self.values()[0].code + +    def getReason(self): +
>     > result = "" +        for response in self.values(): +            if
>     > response.code > Constants.CODE_LOWEST_ERROR: +
>     > result += response.reason +        return result
>     >  + +    def __str__(self): +        result = "" +        for key,
>     > value in self.items(): +            if  isinstance(value,
>     > PropertyResponse): +                result += "Resource at %s has
>     > %d properties and %d errors.\n" % (key, len(value),
>     > value.errorCount) +            else: +                result +=
>     > "Resource at %s returned " % key + str(value) +        return
>     > result + +    def _scan(self, root): +        for child in
>     > root.children: +            if child.ns != Constants.NS_DAV: +
>     > continue +            if child.name <http://child.name> ==
>     > Constants.TAG_RESPONSEDESCRIPTION: +                self.reason =
>     > child.textof() +            elif child.name <http://child.name> ==
>     > Constants.TAG_RESPONSE: +                self._scanResponse(child)
>     > +            ### unknown child element + +    def
>     > _scanResponse(self, elem): +        hrefs = [] +        response =
>     > None +        for child in elem.children: +            if child.ns
>     > != Constants.NS_DAV: +                continue +            if
>     > child.name <http://child.name> == Constants.TAG_HREF: +
>            try: +
>     > href = _unquoteHref(child.textof()) +                except
>     > UnicodeDecodeError: +                    raise
>     > ResponseFormatError(child, "Invalid 'href' data encoding.") +
>     > hrefs.append(href) +            elif child.name
>     <http://child.name> ==
>     > Constants.TAG_STATUS: +                self._scanStatus(child,
>     > *hrefs) +            elif child.name <http://child.name> ==
>     > Constants.TAG_PROPERTY_STATUS: +                if not response: +
>     > if len(hrefs) != 1: +                        raise
>     > ResponseFormatError(child, 'Invalid response: One <DAV:href>
>     > expected.') +                    response = PropertyResponse()
>     >  +                    self[hrefs[0]] = response
>     >  +                response._scan(child) +            elif
>     > child.name <http://child.name> ==
>     Constants.TAG_RESPONSEDESCRIPTION: +
>     > for href in hrefs: +
>     > self[href].reasons.append(child.textOf()) +            ### unknown
>     > child element +        if response and response.errorCount > 0: +
>     > self.errorCount += 1 + +    def _scanStatus(self, elem, *hrefs): +
>     > if  len(hrefs) == 0: +            raise ResponseFormatError(elem,
>     > 'Invalid response: <DAV:href> expected.') +        status =
>     > HttpStatus(elem) +        for href in hrefs: +
>     > self[href] = status +            if  status.errorCount: +
>     > self.errorCount += 1 + +    # Instance properties +    code =
>     > property(getCode, None, None, "HTTP response code") + + + +class
>     > PropertyResponse(dict): +    """ +    TBD + +    @ivar errors: +
>     > @type errors: list of ... +    @ivar reasons: +    @type reasons:
>     > list of ... +    @ivar failedProperties: +    @type
>     > failedProperties: dict of ... +    """ + +    # restrict instance
>     > variables +    __slots__ = ('errors', 'reasons',
>     > 'failedProperties') + +    def __init__(self): +
>     > dict.__init__(self) +        self.errors = [] +        self.reasons
>     > = [] +        self.failedProperties = {} + +    def __str__(self):
>     > +        result = "" +        for value in self.values(): +
>     > result += value.name <http://value.name> + '= ' + value.textof()
>     + '\n' +        result
>     > += self.getReason() +        return result + +    def
>     > getCode(self): +        if  len(self.errors) == 0: +
>     > return Constants.CODE_SUCCEEDED +        if  len(self) > 0: +
>     > return Constants.CODE_MULTISTATUS +        return
>     > self.errors[-1].code + +    def getReason(self): +        result =
>     > "" +        if len(self.errors) > 0: +            result = "Failed
>     > for: "   + repr(self.failedProperties.keys()) + "\n" +        for
>     > error in self.errors: +            result += "%s (%d).  " %
>     > (error.reason, error.code) +        for reason in self.reasons: +
>     > result += "%s.  " % reason +        return result + +    def
>     > _scan(self, element): +        status = None +        statusElement
>     > = element.find(Constants.TAG_STATUS, Constants.NS_DAV) +        if
>     > statusElement: +            status = HttpStatus(statusElement) +
>     > if status.errorCount: +                self.errors.append(status) +
>     >  +        propElement = element.find(Constants.TAG_PROP,
>     > Constants.NS_DAV) +        if propElement: +            for prop in
>     > propElement.children: +                if status.errorCount: +
>     > self.failedProperties[(prop.ns, prop.name <http://prop.name>)]=
>     status +
>     > else: +                    prop.__class__ = Element     # bad, bad
>     > trick +                    self[prop.fullname] = prop +
>     > reasonElement = element.find(Constants.TAG_RESPONSEDESCRIPTION,
>     > Constants.NS_DAV) +        if reasonElement: +
>     > self.reasons.append(reasonElement.textOf()) + +    # Instance
>     > properties +    code = property(getCode, None, None, "HTTP response
>     > code") +    errorCount = property(lambda self: len(self.errors),
>     > None, None, "HTTP response code") +    reason = property(getReason,
>     > None, None, "HTTP response code") + + + + +class
>     > LiveProperties(object): +    """ +    This class provides
>     > convenient access to the WebDAV 'live' properties of a resource. +
>     > WebDav 'live' properties are defined in RFC 2518, Section 13. +
>     > Each property is converted from string to its natural data type. +
>     >  +    @version: $Revision$ +    @author: Roland Betz +    """ +
>     >  +    # restrict instance variables +    __slots__ =
>     > ('properties') + +    NAMES = (Constants.PROP_CREATION_DATE,
>     > Constants.PROP_DISPLAY_NAME, +
>     > Constants.PROP_CONTENT_LENGTH, Constants.PROP_CONTENT_TYPE,
>     > Constants.PROP_ETAG, +             Constants.PROP_LAST_MODIFIED,
>     > Constants.PROP_OWNER, +             Constants.PROP_LOCK_DISCOVERY,
>     > Constants.PROP_RESOURCE_TYPE, Constants.PROP_SUPPORTED_LOCK ) +
>     >  +    def __init__(self, properties=None, propElement=None): +
>     > """ +        Construct <code>StandardProperties</code> from a map
>     > of properties containing +        live properties or from a XML
>     > 'prop' element containing live properties + +        @param
>     > properties: map as implemented by class L{PropertyResponse} +
>     > @param propElement: an C{Element} value +        """ +
>     > assert isinstance(properties, PropertyResponse) or \ +
>     > isinstance(propElement, qp_xml._element), \ +
>     > "Argument properties has type %s" % str(type(properties)) +
>     > self.properties = {} +        for name, value in
>     > properties.items(): +            if  name[0] == Constants.NS_DAV
>     > and  name[1] in self.NAMES: +
>     > self.properties[name[1]] = value + +    def
>     > getContentLanguage(self): +        """ +        Return the language
>     > of a resource's textual content or null + +        @return: string
>     > +        """ + +        result = "" +        if not
>     > self.properties.get(Constants.PROP_CONTENT_LANGUAGE, None) is
>     > None: +            result =
>     > self.properties.get(Constants.PROP_CONTENT_LANGUAGE).textof() +
>     > return result + +    def getContentLength(self): +        """ +
>     > Returns the length of the resource's content in bytes. + +
>     > @return: number of bytes +        """ + +        result = 0 +
>     > if not self.properties.get(Constants.PROP_CONTENT_LENGTH, None) is
>     > None: +            result =
>     > int(self.properties.get(Constants.PROP_CONTENT_LENGTH).textof()) +
>     > return result + +    def getContentType(self): +        """ +
>     > Return the resource's content MIME type. + +        @return: MIME
>     > type string +        """ + +        result = "" +        if not
>     > self.properties.get(Constants.PROP_CONTENT_TYPE, None) is None: +
>     > result = self.properties.get(Constants.PROP_CONTENT_TYPE).textof()
>     > +        return result + +    def getCreationDate(self): +
>     > """ +        Return date of creation as time tuple. +
>     >  +        @return: time tuple +        @rtype: C{time.struct_time}
>     > + +        @raise ValueError: If string is not in the expected
>     > format (ISO 8601). +        """ + +        datetimeString = "" +
>     > if not self.properties.get(Constants.PROP_CREATION_DATE, None) is
>     > None: +            datetimeString =
>     > self.properties.get(Constants.PROP_CREATION_DATE).textof() + +
>     > result = rfc822.parsedate(datetimeString) +        if result is
>     > None: +            result = _parseIso8601String(datetimeString) + +
>     > return time.mktime(result) + +    def getEntityTag(self): +
>     > """ +        Return a entity tag which is unique for a particular
>     > version of a resource. +        Different resources or one resource
>     > before and after modification have different etags. + +
>     > @return: entity tag string +        """ + +        result = "" +
>     > if not self.properties.get(Constants.PROP_ETAG, None) is None: +
>     > result = self.properties.get(Constants.PROP_ETAG).textof() +
>     > return result + +    def getDisplayName(self): +        """ +
>     > Returns a resource's display name. + +        @return: string +
>     > """ + +        result = "" +        if not
>     > self.properties.get(Constants.PROP_DISPLAY_NAME, None) is None: +
>     > result = self.properties.get(Constants.PROP_DISPLAY_NAME).textof()
>     > +        return result + +    def getLastModified(self): +
>     > """ +        Return last modification of resource as time tuple. +
>     >  +        @return: Modification date time. +        @rtype:
>     > C{time.struct_time} + +        @raise ValueError: If the date time
>     > string is not in the expected format (RFC 822 / ISO 8601). +
>     > """ + +        datetimeString = None +        if not
>     > self.properties.get(Constants.PROP_LAST_MODIFIED, None) is None: +
>     > datetimeString =
>     > self.properties.get(Constants.PROP_LAST_MODIFIED).textof() +
>     > result = rfc822.parsedate(datetimeString) +        if result is
>     > None: +            result = _parseIso8601String(datetimeString) +
>     > return time.mktime(result) + +    def getLockDiscovery(self): +
>     > """ +        Return all current lock's applied to a resource or
>     > null if it is not locked. + +        @return: a lockdiscovery DOM
>     > element according to RFC 2815 +        """ + +        xml =
>     > self.properties.get(Constants.PROP_LOCK_DISCOVERY) +        return
>     > _scanLockDiscovery(xml) + +    def getResourceType(self): +
>     > """ +        Return a resource's WebDAV type. + +        @return:
>     > 'collection' or 'resource' +        """ + +        xml =
>     > self.properties.get(Constants.PROP_RESOURCE_TYPE) +        if xml
>     > and xml.children: +            return xml.children[0].name +
>     > return "resource" + +    def getSupportedLock(self): +        """ +
>     > Return a DOM element describing all supported lock options for a
>     > resource. +        Usually this is shared and exclusive write
>     > lock. + +        @return: supportedlock DOM element according to
>     > RFC 2815 +        """ + +        xml =
>     > self.properties.get(Constants.PROP_SUPPORTED_LOCK) +        return
>     > xml + +    def getOwnerAsUrl(self): +        """ +        Return a
>     > resource's owner in form of a URL. + +        @return: string +
>     > """ + +        xml = self.properties.get(Constants.PROP_OWNER) +
>     > if xml and len(xml.children): +            return
>     > xml.children[0].textof() +        return None + +    def
>     > __str__(self): +        result = "" +        result += " Name=" +
>     > self.getDisplayName() +        result += "\n Type=" +
>     > self.getResourceType() +        result += "\n Length=" +
>     > str(self.getContentLength()) +        result += "\n Content Type="+
>     > self.getContentType() +        result += "\n ETag=" +
>     > self.getEntityTag() +        result += "\n Created=" +
>     > time.strftime("%c GMT", self.getCreationDate()) +        result +=
>     > "\n Modified=" + time.strftime("%c GMT", self.getLastModified()) +
>     > return result + + +def _parseIso8601String(date): +    """ +
>     > Parses the given ISO 8601 string and returns a time tuple. +    The
>     > strings should be formatted according to RFC 3339 (see section
>     > 5.6). +    But currently there are two exceptions: +        1. Time
>     > offset is limited to "Z". +        2. Fragments of seconds are
>     > ignored. +    """ + +    if "." in date and "Z" in date: # Contains
>     > fragments of second? +        secondFragmentPos = date.rfind(".") +
>     > timeOffsetPos = date.rfind("Z") +        date =
>     > date[:secondFragmentPos] + date[timeOffsetPos:] +    try: +
>     > timeTuple = time.strptime(date, Constants.DATE_FORMAT_ISO8601) +
>     > except IllegalArgumentException: # Handling Jython 2.5 bug
>     > concerning the date pattern accordingly +        import _strptime #
>     > Using the Jython fall back solution directly +        timeTuple =
>     > _strptime.strptime(date, Constants.DATE_FORMAT_ISO8601) +    return
>     > timeTuple + + +class ResponseFormatError(IOError): +    """ +    An
>     > instance of this class is raised when the web server returned a
>     > webdav +    reply which does not adhere to the standard and cannot
>     > be recognized. +    """ +    def __init__(self, element, message=
>     > None): +        IOError.__init__(self, "ResponseFormatError at
>     > element %s: %s"  % (element.name <http://element.name>,
>     message)) +        self.element =
>     > element +        self.message = message + + +class
>     > Element(qp_xml._element): +    """ +    This class improves the DOM
>     > interface (i.e. element interface) provided by the qp_xml module +
>     > TODO: substitute qp_xml by 'real' implementation. e.g. domlette +
>     > """ +    def __init__(self, namespace, name, cdata=''): +
>     > qp_xml._element.__init__(self, ns=namespace, name=name, lang=None,
>     > parent=None, +                    children=[], ns_scope={},
>     > attrs={}, +                    first_cdata=cdata,
>     > following_cdata='') + +    def __str__(self): +        return
>     > self.textof() + +    def __getattr__(self, name): +        if
>     > (name == 'fullname'): +            return (self.__dict__['ns'],
>     > self.__dict__['name']) +        raise AttributeError, name + +
>     > def add(self, child): +        self.children.append(child) +
>     > return child + +def _scanLockDiscovery(root): +    assert
>     root.name <http://root.name>
>     > == Constants.PROP_LOCK_DISCOVERY, "Invalid lock discovery XML
>     > element" +    active = root.find(Constants.TAG_ACTIVE_LOCK,
>     > Constants.NS_DAV) +    if active: +        return
>     > _scanActivelock(active) +    return None + +def
>     > _scanActivelock(root): +    assert root.name <http://root.name> ==
>     > Constants.TAG_ACTIVE_LOCK, "Invalid active lock XML element" +
>     > token = _scanOrError(root, Constants.TAG_LOCK_TOKEN) +    value =
>     > _scanOrError(token, Constants.TAG_HREF) +    owner =
>     > _scanOwner(root) +    depth = _scanOrError(root,
>     > Constants.TAG_LOCK_DEPTH) +    return (value.textof(), owner,
>     > depth.textof()) + +def _scanOwner(root): +    owner =
>     > root.find(Constants.TAG_LOCK_OWNER, Constants.NS_DAV) +    if
>     > owner: +        href = owner.find(Constants.TAG_HREF,
>     > Constants.NS_DAV) +        if href: +            return
>     > href.textof() +        return owner.textof() +    return None +
>     >  +def _scanOrError(elem, childName): +    child =
>     > elem.find(childName, Constants.NS_DAV) +    if not child: +
>     > raise ResponseFormatError(elem, "Invalid response: <"+childName+">
>     > expected") +    return child + + +def _unquoteHref(href): +
>     > #print "*** Response HREF=", repr(href) +    if type(href) ==
>     > type(u""): +        try: +            href = href.encode('ascii') +
>     > except UnicodeError:    # URL contains unescaped non-ascii
>     > character +            # handle bug in Tamino webdav server +
>     > return urllib.unquote(href) +    href = urllib.unquote(href) +
>     > if Constants.CONFIG_UNICODE_URL: +        return unicode(href,
>     > 'utf-8') +    else: +        return unicode(href, 'latin-1') diff
>     > --git a/src/webdav/__init__.py b/src/webdav/__init__.py new file
>     > mode 100644 index 0000000..3e46609 --- /dev/null +++
>     > b/src/webdav/__init__.py @@ -0,0 +1,16 @@ +# Copyright 2008 German
>     > Aerospace Center (DLR) +# +# Licensed under the Apache License,
>     > Version 2.0 (the "License"); +# you may not use this file except in
>     > compliance with the License. +# You may obtain a copy of the
>     > License at +# +#     http://www.apache.org/licenses/LICENSE-2.0 +#
>     > +# Unless required by applicable law or agreed to in writing,
>     > software +# distributed under the License is distributed on an "AS
>     > IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
>     > express or implied. +# See the License for the specific language
>     > governing permissions and +# limitations under the License. + +
>     > +__version__ = "$LastChangedRevision$" diff --git
>     > a/src/webdav/acp/Ace.py b/src/webdav/acp/Ace.py new file mode
>     > 100644 index 0000000..8321d41 --- /dev/null +++
>     > b/src/webdav/acp/Ace.py @@ -0,0 +1,293 @@ +# Copyright 2008 German
>     > Aerospace Center (DLR) +# +# Licensed under the Apache License,
>     > Version 2.0 (the "License"); +# you may not use this file except in
>     > compliance with the License. +# You may obtain a copy of the
>     > License at +# +#     http://www.apache.org/licenses/LICENSE-2.0 +#
>     > +# Unless required by applicable law or agreed to in writing,
>     > software +# distributed under the License is distributed on an "AS
>     > IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
>     > express or implied. +# See the License for the specific language
>     > governing permissions and +# limitations under the License. + +
>     > +""" +ACE object handling according to WebDAV ACP specification.
>     > +""" + + +from webdav.acp.Principal import Principal +from
>     > webdav.acp.GrantDeny import GrantDeny +from webdav import
>     > Constants +from webdav.Connection import WebdavError + +
>     > +__version__ = "$LastChangedRevision$" + + +class ACE(object): +
>     > """ +    This class provides functionality for handling ACEs + +
>     > @ivar principal:   A principal (user or group) +    @type
>     > principal:   L{Principal} object +    @ivar invert:      Flag
>     > indicating whether ACE should invert the principal. +    @type
>     > invert:      C{bool} +    @ivar grantDenies: Grant or deny clauses
>     > for privileges +    @type grantDenies: C{list} of L{GrantDeny}
>     > objects +    @ivar protected:   Flag indicating whether ACE is
>     > protected. +    @type protected:   C{bool} +    @ivar inherited:
>     > URL indicating the source from where the ACE is inherited. +
>     > @type inherited:   C{string} +    """ + +    # restrict instance
>     > variables +    __slots__ = ('principal', 'invert', 'grantDenies',
>     > 'protected', 'inherited') + +    def __init__(self, domroot=None,
>     > principal=None, grantDenies=None): +        """ +
>     > Constructor should be called with either no parameters (create
>     > blank ACE), +        one parameter (a DOM tree or principal), or
>     > two parameters (principal and +        sequence of GrantDenies). +
>     >  +        @param domroot:     A DOM tree (default: None). +
>     > @type  domroot:     L{webdav.WebdavResponse.Element} object +
>     > @param principal:   A principal (user or group), (default: None). +
>     > @type  principal:   L{Principal} object +        @param
>     > grantDenies: Grant and deny clauses for privileges (default:
>     > None). +        @type  grantDenies: sequence of L{GrantDeny}
>     > objects + +        @raise WebdavError: When non-valid parameters
>     > are passed a L{WebdavError} is raised. +        """ +
>     > self.principal   = Principal() +        self.protected   = None +
>     > self.inherited   = None +        self.invert      = None +
>     > self.grantDenies = [] + +        if domroot: +
>     > self.principal   =
>     > Principal(domroot=domroot.find(Constants.TAG_PRINCIPAL,
>     > Constants.NS_DAV)) +            self.inherited   =
>     > domroot.find(Constants.TAG_INHERITED, Constants.NS_DAV) +
>     > if self.inherited: +                self.inherited =
>     > self.inherited.children[0].textof() +            if
>     > domroot.find(Constants.TAG_PROTECTED, Constants.NS_DAV): +
>     > self.protected = 1 +            for child in domroot.children: +
>     > if child.ns == Constants.NS_DAV \ +                        and
>     > (child.name <http://child.name> == Constants.TAG_GRANT or
>     child.name <http://child.name> ==
>     > Constants.TAG_DENY): +
>     > self.grantDenies.append(GrantDeny(domroot=child)) +        elif
>     > isinstance(principal, Principal): +            newPrincipal =
>     > Principal() +            newPrincipal.copy(principal) +
>     > self.principal = newPrincipal +            if
>     > (isinstance(grantDenies, list) or isinstance(grantDenies, tuple)):
>     > +                self.addGrantDenies(grantDenies) +        elif
>     > domroot == None and grantDenies == None: +            # no param
>     > ==> blank ACE +            pass +        else: +            # This
>     > shouldn't happen, someone screwed up with the params ... +
>     > raise WebdavError('non-valid parameters handed to ACE
>     > constructor') + +    def __cmp__(self, other): +        if not
>     > isinstance(other, ACE): +            return 1 +        if
>     > self.principal == other.principal \ +                and
>     > self.invert == other.invert \ +                and self.protected
>     > == other.protected \ +                and self.inherited ==
>     > other.inherited: +            equal = 1 +            for grantDeny
>     > in self.grantDenies: +                inList = 0 +
>     > for otherGrantDeny in other.grantDenies: +                    if
>     > grantDeny == otherGrantDeny: +                        inList = 1 +
>     > if inList == 0: +                    equal = 0 +            return
>     > not equal +        else: +            return 1 + +    def
>     > __repr__(self): +        repr = '<class ACE: ' +        if
>     > self.invert: +            repr += 'inverted principal, ' %
>     > (self.invert) +        if self.principal: +            repr +=
>     > 'principal: %s, ' % (self.principal) +        if self.protected: +
>     > repr += 'protected, ' +        if self.inherited: +            repr
>     > += 'inherited href: %s, ' % (self.inherited) +        first = 1 +
>     > repr += 'grantDenies: [' +        for grantDeny in
>     > self.grantDenies: +            if first: +                repr +=
>     > '%s' % grantDeny +                first = 0 +            else: +
>     > repr += ', %s' % grantDeny +        return '%s]>' % (repr) + +
>     > def copy(self, other): +        '''Copy an ACE object. + +
>     > @param other: Another ACE to copy. +        @type  other: L{ACE}
>     > object + +        @raise WebdavError: When an object that is not an
>     > L{ACE} is passed +            a L{WebdavError} is raised. +
>     > ''' +        if not isinstance(other, ACE): +            raise
>     > WebdavError('Non-ACE object passed to copy method: %s.' %
>     > other.__class__) +        self.invert    = other.invert +
>     > self.protected = other.protected +        self.inherited =
>     > other.inherited +        self.principal = Principal() +        if
>     > other.principal: +            self.principal.copy(other.principal)
>     > +        if other.grantDenies: +
>     > self.addGrantDenies(other.grantDenies) + +    def isValid(self): +
>     > """ +        Returns true/false (1/0) whether necessarry props +
>     > principal and grantDenies are set and whether the ACE contains one
>     >  +        grant or deny clauses. + +        @return: Validity of
>     > ACE. +        @rtype:  C{bool} +        """ +        return
>     > self.principal and len(self.grantDenies) == 1 + +    def
>     > isGrant(self): +        ''' +        Returns true/false (1/0) if
>     > ACE contains only grant clauses. + +        @return: Value whether
>     > the ACE is of grant type. +        @rtype:  C{bool} +        ''' +
>     > if self.isMixed() or len(self.grantDenies) < 1: +            return
>     > 0 +        else: +            return self.grantDenies[0].isGrant()
>     > + +    def isDeny(self): +        ''' +        Returns true/false
>     > (1/0) if ACE contains only deny clauses. + +        @return: Value
>     > whether the ACE is of deny type. +        @rtype:  C{bool} +
>     > ''' +        if self.isMixed() or len(self.grantDenies) < 1: +
>     > return 0 +        else: +            return
>     > self.grantDenies[0].isDeny() + +    def isMixed(self): +
>     > ''' +        Returns true/false (1/0) if ACE contains both types
>     > (grant and deny) of clauses. + +        @return: Value whether the
>     > ACE is of mixed (grant and deny) type. +        @rtype:  C{bool} +
>     > ''' +        mixed = 0 +        if len(self.grantDenies): +
>     > first = self.grantDenies[0].grantDeny +            for grantDeny in
>     > self.grantDenies: +                if grantDeny.grantDeny !=
>     > first: +                    mixed = 1 +        return mixed + +
>     > def toXML(self, defaultNameSpace=None): +        """ +
>     > Returns ACE content as a string of valid XML as described in WebDAV
>     > ACP. + +        @param defaultNameSpace: Name space (default:
>     > None). +        @type  defaultNameSpace: C(string) +        """ +
>     > assert self.isValid(), "ACE is not initialized or does not contain
>     > valid content!" + +        ACE = 'D:' + Constants.TAG_ACE +
>     > res = self.principal.toXML(self.invert) +        for grantDeny in
>     > self.grantDenies: +            res += grantDeny.toXML() +        if
>     > self.protected: +            res += '<D:protected/>' +        if
>     > self.inherited: +            res +=
>     > '<D:inherited><D:href>%s</D:href></D:inherited>' %
>     > (self.inherited) +        return '<%s>%s</%s>' % (ACE, res, ACE) +
>     > +    def setPrincipal(self, principal): +        ''' +        Sets
>     > the passed principal on the ACE. + +        @param principal: A
>     > principal. +        @type  principal: L{Principal} object +
>     > ''' +        self.principal = Principal() +
>     > self.principal.copy(principal) + +    def setInherited(self,
>     > href): +        ''' +        Sets the passed URL on the ACE to
>     > denote from where it is inherited. + +        @param href: A URL. +
>     > @type  href: C{string} +        ''' +        self.inherited = href
>     > + +    def addGrantDeny(self, grantDeny): +        ''' +
>     > Adds the passed GrantDeny object to list if it's not in it, yet. +
>     >  +        @param grantDeny: A grant or deny clause. +        @type
>     > grantDeny: L{GrantDeny} object +        ''' +        # only add it
>     > if it's not in the list, yet ... +        inList = 0 +        for
>     > element in self.grantDenies: +            if element == grantDeny:
>     > +                inList = 1 +        if not inList: +
>     > newGrantDeny = GrantDeny() +
>     > newGrantDeny.copy(grantDeny) +
>     > self.grantDenies.append(newGrantDeny) + +    def
>     > addGrantDenies(self, grantDenies): +        '''Adds the list of
>     > passed grant/deny objects to list. + +        @param grantDenies:
>     > Grant or deny clauses. +        @type  grantDenies: sequence of
>     > L{GrantDeny} objects +        ''' +        map(lambda grantDeny:
>     > self.addGrantDeny(grantDeny), grantDenies) + +    def
>     > delGrantDeny(self, grantDeny): +        '''Deletes the passed
>     > GrantDeny object from list. + +        @param grantDeny: A grant or
>     > deny clause. +        @type  grantDeny: L{GrantDeny} object +
>     >  +        @raise WebdavError: A L{WebdavError} is raised if the
>     > clause to be +            deleted is not present. +        ''' +
>     > # only add it if it's not in the list, yet ... +        count = 0 +
>     > index = 0 +        for element in self.grantDenies: +
>     > count += 1 +            if element == grantDeny: +
>     > index = count +        if index: +
>     > self.grantDenies.pop(index - 1) +        else: +            raise
>     > WebdavError('GrantDeny to be deleted not in list: %s.' %
>     > grantDeny) + +    def delGrantDenies(self, grantDenies): +
>     > '''Deletes the list of passed grant/deny objects from list. +
>     >  +        @param grantDenies: Grant or deny clauses. +        @type
>     > grantDenies: sequence of L{GrantDeny} objects +        ''' +
>     > map(lambda grantDeny: self.delGrantDeny(grantDeny), grantDenies)
>     > diff --git a/src/webdav/acp/AceHandler.py
>     > b/src/webdav/acp/AceHandler.py new file mode 100644 index
>     > 0000000..e07b74d --- /dev/null +++ b/src/webdav/acp/AceHandler.py
>     > @@ -0,0 +1,182 @@ +# Copyright 2008 German Aerospace Center (DLR)
>     > +# +# Licensed under the Apache License, Version 2.0 (the
>     > "License"); +# you may not use this file except in compliance with
>     > the License. +# You may obtain a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +Handling of WebDAV
>     > Access Protocol Extensions and ACL preparation for UI. +""" + +
>     > +from webdav import Constants +from webdav.WebdavClient import
>     > ResourceStorer +from webdav.Connection import WebdavError + +
>     > +__version__ = "$LastChangedRevision$" + + +def
>     > extractSupportedPrivilegeSet(userPrivileges): +    """ +    Returns
>     > a dictionary of supported privileges. + +    @param userPrivileges:
>     > A DOM tree. +    @type  userPrivileges:
>     > L{webdav.WebdavResponse.Element} object + +    @raise WebdavError:
>     > When unknown elements appear in the +
>     > C{DAV:supported-privilege} appear a L{WebdavError} is raised. +
>     >  +    @return: A dictionary with privilege names as keys and
>     > privilege descriptions as values. +    @rtype:  C{dictionary} +
>     > """ +    result = {} +    for element in userPrivileges.children: +
>     > if element.name <http://element.name> ==
>     Constants.TAG_SUPPORTED_PRIVILEGE: +
>     > privName        = '' +            privDescription = '' +
>     > for privilege in element.children: +                if
>     > privilege.name <http://privilege.name> == Constants.TAG_PRIVILEGE: +
>     > privName = privilege.children[0].name +                elif
>     > privilege.name <http://privilege.name> ==
>     Constants.TAG_DESCRIPTION: +
>     > privDescription = privilege.textof() +                else: +
>     > raise WebdavError('Unknown element in DAV:supported-privilege: ' +
>     > privilege.name <http://privilege.name>) + +                if
>     privName and
>     > privDescription: +                    result[privName] =
>     > privDescription +                    privName        = '' +
>     > privDescription = '' +        else: +            raise
>     > WebdavError('Invalid element tag in DAV:supported-privilege-set: '
>     > + element.name <http://element.name>) +    return result + + +def
>     > _insertAclDisplaynames(acl): +    """ +    Modifies the ACL by
>     > adding the human readable names +    (DAV:displayname property) of
>     > each principal found in an ACL. + +    This should be done with the
>     > REPORT method, but it is not supported by +    Jacarta Slide, yet.
>     > (As of Aug. 1, 2003 in CVS repository) + +    So we are going to do
>     > it differently by foot the harder way ... + +    @param acl: An ACL
>     > object for which the displaynames should be retrieved. +    @type
>     > acl: L{ACL} object +    """ +    ## This is redundant code to be
>     > still kept for the REPORT method way of doing it ... +    ##
>     > property = '''<D:prop><D:displayname/></D:prop>''' +    ## return
>     > self.getReport(REPORT_ACL_PRINCIPAL_PROP_SET, property) +    for
>     > ace in acl.aces: +        if not ace.principal.property: +
>     > principalConnection = \ +
>     > ResourceStorer(ace.principal.principalURL) +
>     > ace.principal.displayname = \ +
>     > principalConnection.readProperty(Constants.NS_DAV,
>     > Constants.PROP_DISPLAY_NAME) + + +def prepareAcls(acls): +    """ +
>     > Returns all ACLs describing the behaviour of the resource. The
>     > information +    in the ACL is modified to contain all information
>     > needed to display in the UI. + +    @param acls: ACL objects. +
>     > @type  acls: C{list} of L{ACL} objects + +    @return: (non-valid)
>     > ACLs that contain both grant and deny clauses in an ACE. +
>     > Displaynames are added to the Principals where needed. +    @rtype:
>     > C{list} of L{ACL} objects +    """ +    for acl in acls.keys(): +
>     > acls[acl] = acls[acl].joinGrantDeny() +
>     > _insertAclDisplaynames(acls[acl]) +    return acls + + +def
>     > prepareAcl(acl): +    """ +    Returns an ACL describing the
>     > behaviour of the resource. The information +    in the ACL is
>     > modified to contain all information needed to display in the UI. +
>     >  +    @param acl: An ACL object. +    @type  acl: L{ACL} object +
>     >  +    @return: A (non-valid) ACL that contains both grant and deny
>     > clauses in an ACE. +        Displaynames are added to the
>     > Principals where needed. +    @rtype:  L{ACL} object +    """ +
>     > acl = acl.joinGrantDeny() +    _insertAclDisplaynames(acl) +
>     > return acl + + +def refineAclForSet(acl): +    """ +    Sets the
>     > ACL composed from the UI on the WebDAV server. For that purpose the
>     >  +    ACL object gets refined first to form a well accepted ACL to
>     > be set by the +    ACL WebDAV method. + +    @param acl: An ACL
>     > object to be refined. +    @type  acl: L{ACL} object + +
>     > @return: A valid ACL that contains only grant or deny clauses in an
>     > ACE. +        Inherited and protected ACEs are stripped out. +
>     > @rtype:  L{ACL} object +    """ +    acl = acl.splitGrantDeny() +
>     > acl = acl.stripAces() +    return acl + + +##~ unsupported or
>     > unfinished methods: +##~ +##~ def report(self, report,
>     > request=None, lockToken=None): +##~     """ +##~     This method
>     > implements the WebDAV ACP method: REPORT for given report +##~
>     > types. +##~ +##~     Parameters: +##~ +##~       'report' -- Report
>     > type as a string. +##~ +##~       'request' -- XML content of the
>     > request for the report (defaults to None). +##~ +##~
>     > 'lockToken' -- Lock token to be set (defaults to None). +##~
>     > """ +##~     raise WebdavError('Reports are not supported by our
>     > Jacarta Slide, yet (as of Aug. 1, 2003 in CVS).') +##~ +##~
>     > headers                 = createCondition(lockToken) +##~
>     > headers['Content-Type'] = XML_CONTENT_TYPE +##~     body
>     > = '<D:%s xmlns:D="DAV:">%s</D:%s>' % (report, request, report) +##~
>     > #print "Body: ", body +##~     response =
>     > self.connection._request('REPORT', self.path, body, headers) +##~
>     > return response +##~     ## TODO: parse DAV:error response +##~
>     > +##~ +##~ def getAllAcls(self): +##~    """ +##~     Returns a
>     > dictionary of ACL resources with respective ACL objects +##~
>     > that apply to the given resource. +##~ +##~     ### This method
>     > needs to be extended for inherited ACLs when Tamino +##~
>     > support tells me (Guy) how to get to them. +##~     """ +##~
>     > acls = {self.path: self.getAcl()} +##~     for ace in
>     > acls[self.path].aces: +##~         if ace.inherited: +##~
>     > if not ace.inherited in acls: +##~
>     > acls[ace.inherited] = self.getAcl() +##~ +##~     # append some
>     > more stuff here to acls for possible inherited ACLs +##~     return
>     > acls diff --git a/src/webdav/acp/Acl.py b/src/webdav/acp/Acl.py new
>     > file mode 100644 index 0000000..8f2b36f --- /dev/null +++
>     > b/src/webdav/acp/Acl.py @@ -0,0 +1,311 @@ +# pylint:
>     > disable-msg=W0622 +# +# Copyright 2008 German Aerospace Center
>     > (DLR) +# +# Licensed under the Apache License, Version 2.0 (the
>     > "License"); +# you may not use this file except in compliance with
>     > the License. +# You may obtain a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + + +""" +ACL object
>     > handling according to WebDAV ACP specification. +""" + + +from
>     > webdav.acp.Ace import ACE +from webdav import Constants +from
>     > webdav.Connection import WebdavError +from webdav.davlib import
>     > XML_DOC_HEADER + + +__version__ = "$LastChangedRevision$" + +
>     > +class ACL(object): +    """ +    This class provides access to
>     > Access Control List funcionality +    as specified in the WebDAV
>     > ACP. + +    @ivar aces:          ACEs in ACL +    @type aces:
>     > C{list} of L{ACE} objects +    @ivar withInherited: Flag indicating
>     > whether ACL contains inherited ACEs. +    @type withInherited:
>     > C{bool} +    """ + +    # restrict instance variables +
>     > __slots__ = ('aces', 'withInherited') + +    def __init__(self,
>     > domroot=None, aces=None): +        """ +        Constructor should
>     > be called with either no parameters (create blank ACE), +        or
>     > one parameter (a DOM tree or ACE list). + +        @param domroot:
>     > A DOM tree (default: None). +        @type  domroot:
>     > L{webdav.WebdavResponse.Element} object +        @param aces:
>     > ACE objects (default: None) +        @type  aces:    C{list} of
>     > L{ACE} objects + +        @raise WebdavError: When non-valid
>     > parameters are passed a L{WebdavError} is raised. +        """ +
>     > self.withInherited = None +        self.aces          = [] +
>     >  +        if domroot: +            for child in domroot.children: +
>     > if child.name <http://child.name> == Constants.TAG_ACE and
>     child.ns ==
>     > Constants.NS_DAV: +                    self.addAce(ACE(child)) +
>     > else: +                    # This shouldn't happen, someone screwed
>     > up with the params ... +                    raise
>     > WebdavError('Non-ACE tag handed to ACL constructor: ' + child.ns +
>     > child.name <http://child.name>) +        elif isinstance(aces,
>     list) or
>     > isinstance(aces, tuple): +            self.addAces(aces) +
>     > elif domroot == None and aces == None: +            # no param ==>
>     > blank object +            pass +        else: +            # This
>     > shouldn't happen, someone screwed up with the params ... +
>     > raise WebdavError('non-valid parameters handed to ACL
>     > constructor') + +    def __cmp__(self, other): +        if not
>     > isinstance(other, ACL): +            return 1 +        if
>     > self.withInherited == other.withInherited: +            equal = 1 +
>     > for ace in self.aces: +                inList = 0 +
>     > for otherAce in other.aces: +                    if ace ==
>     > otherAce: +                        inList = 1 +                if
>     > inList == 0: +                    equal = 0 +            return not
>     > equal +        else: +            return 1 + +    def
>     > __repr__(self): +        repr = '<class ACL: ' +        if
>     > self.withInherited: +            repr += 'with inherited, ' +
>     > first = 1 +        repr += 'aces: [' +        for ace in
>     > self.aces: +            if first: +                repr += '%s' %
>     > ace +                first = 0 +            else: +
>     > repr += ', %s' % ace +        return '%s]>' % (repr) + +    def
>     > copy(self, other): +        '''Copy an ACL object. + +
>     > @param other: Another ACL to copy. +        @type  other: L{ACL}
>     > object + +        @raise WebdavError: When an object that is not an
>     > L{ACL} is passed +            a L{WebdavError} is raised. +
>     > ''' +        if not isinstance(other, ACL): +            raise
>     > WebdavError('Non-ACL object passed to copy method: %s' %
>     > other.__class__) +        self.withInherited = other.withInherited
>     > +        if other.aces: +            self.addAces(other.aces) + +
>     > def toXML(self): +        """ +        Returns ACL content as a
>     > string of valid XML as described in WebDAV ACP. +        """ +
>     > aclTag = 'D:' + Constants.TAG_ACL +        return XML_DOC_HEADER
>     > +\ +            '<' + aclTag + ' xmlns:D="DAV:">' + reduce(lambda
>     > xml, ace: xml + ace.toXML() + '\n', [''] + self.aces) +\ +
>     > '</' + aclTag + '>' + +    def addAce(self, ace): +        ''' +
>     > Adds the passed ACE object to list if it's not in it, yet. +
>     >  +        @param ace: An ACE. +        @type  ace: L{ACE} object +
>     > ''' +        newAce = ACE() +        newAce.copy(ace) +        #
>     > only add it if it's not in the list, yet ... +        inList = 0 +
>     > for element in self.aces: +            if element == ace: +
>     > inList = 1 +        if not inList: +
>     > self.aces.append(newAce) + +    def addAces(self, aces): +
>     > '''Adds the list of passed ACE objects to list. + +        @param
>     > aces: ACEs +        @type  aces: sequence of L{ACE} objects +
>     > ''' +        for ace in aces: +            self.addAce(ace) + +
>     > def delAce(self, ace): +        '''Deletes the passed ACE object
>     > from list. + +        @param ace: An ACE. +        @type  ace:
>     > L{ACE} object + +        @raise WebdavError: When the ACE to be
>     > deleted is not within the ACL +            a L{WebdavError} is
>     > raised. +        ''' +        # find where it is and delete it ...
>     > +        count = 0 +        index = 0 +        for element in
>     > self.aces: +            count += 1 +            if element == ace:
>     > +                index = count +        if index: +
>     > self.aces.pop(index - 1) +        else: +            raise
>     > WebdavError('ACE to be deleted not in list: %s.' % ace) + +    def
>     > delAces(self, aces): +        '''Deletes the list of passed ACE
>     > objects from list. + +        @param aces: ACEs +        @type
>     > aces: sequence of L{ACE} objects +        ''' +        for ace in
>     > aces: +            self.delAce(ace) + +    def
>     > delPrincipalsAces(self, principal): +        """ +        Deletes
>     > all ACEs in ACL by given principal. + +        @param principal: A
>     > principal. +        @type  principal: L{Principal} object +
>     > """ +        # find where it is and delete it ... +        index =
>     > 0 +        while index < len(self.aces): +            if
>     > self.aces[index].principal.principalURL == principal.principalURL:
>     > +                self.aces.pop(index) +            else: +
>     > index += 1 + +    def joinGrantDeny(self): +        """ +
>     > Returns a "refined" ACL of the ACL for ease of use in the UI. +
>     > The purpose is to post the user an ACE that can contain both,
>     > granted +        and denied, privileges. So possible pairs of grant
>     > and deny ACEs are joined +        to return them in one ACE. This
>     > resulting ACE then of course IS NOT valid +        for setting ACLs
>     > anymore. They will have to be reconverted to yield valid +
>     > ACLs for the ACL method. + +        @return: A (non-valid) ACL that
>     > contains both grant and deny clauses in an ACE. +        @rtype:
>     > L{ACL} object +        """ +        joinedAces = {} +        for
>     > ace in self.aces: +            if not ace.principal.principalURL is
>     > None: +                principalKey = ace.principal.principalURL +
>     > elif not ace.principal.property is None: +
>     > principalKey = ace.principal.property +            else: +
>     > principalKey = None +            if ace.inherited: +
>     > principalKey = ace.inherited + ":" + principalKey +            if
>     > principalKey in joinedAces: +
>     > joinedAces[principalKey].addGrantDenies(ace.grantDenies) +
>     > else: +                joinedAces[principalKey] = ACE() +
>     > joinedAces[principalKey].copy(ace) +        newAcl = ACL() +
>     > newAcl.addAces(joinedAces.values()) +        return newAcl + +
>     > def splitGrantDeny(self): +        """ +        Returns a "refined"
>     > ACL of the ACL for ease of use in the UI. +        The purpose is
>     > to post the user an ACE that can contain both, granted +        and
>     > denied, privileges. So possible joined grant and deny clauses in
>     > ACEs +        splitted to return them in separate ACEs. This
>     > resulting ACE then is valid +        for setting ACLs again. This
>     > method is to be seen in conjunction with the +        method
>     > joinGrantDeny as it reverts its effect. + +        @return: A valid
>     > ACL that contains only ACEs with either grant or deny clauses. +
>     > @rtype:  L{ACL} object +        """ +        acesGrant = {} +
>     > acesDeny  = {} +        for ace in self.aces: +            for
>     > grantDeny in ace.grantDenies: +                if
>     > grantDeny.isGrant(): +                    if
>     > ace.principal.principalURL in acesGrant: +
>     > ace.addGrantDeny(grantDeny) +                    else: +
>     > acesGrant[ace.principal.principalURL] = ACE() +
>     > acesGrant[ace.principal.principalURL].copy(ace) +
>     > acesGrant[ace.principal.principalURL].grantDenies = [] +
>     > acesGrant[ace.principal.principalURL].addGrantDeny(grantDeny) +
>     > else: +                    if ace.principal.principalURL in
>     > acesDeny: +                        ace.addGrantDeny(grantDeny) +
>     > else: +                        acesDeny[ace.principal.principalURL]
>     > = ACE() +
>     > acesDeny[ace.principal.principalURL].copy(ace) +
>     > acesDeny[ace.principal.principalURL].grantDenies = [] +
>     > acesDeny[ace.principal.principalURL].addGrantDeny(grantDeny) +
>     > newAcl = ACL() +        newAcl.addAces(acesGrant.values()) +
>     > newAcl.addAces(acesDeny.values()) +        return newAcl + +    def
>     > isValid(self): +        """ +        Returns true (1) if all
>     > contained ACE objects are valid, +        otherwise false (0) is
>     > returned. + +        @return: Validity of ACL. +        @rtype:
>     > C{bool} +        """ +        valid = 1 +        if
>     > len(self.aces): +            for ace in self.aces: +
>     > if not ace.isValid(): +                    valid = 0 +
>     > return valid + +    def stripAces(self, inherited=True,
>     > protected=True): +        """ +        Returns an ACL object with
>     > all ACEs stripped that are inherited +        and/or protected. +
>     >  +        @param inherited: Flag to indicate whether inherited ACEs
>     > should +            be stripped (default: True). +        @type
>     > inherited: C{bool} +        @param protected: Flag to indicate
>     > whether protected ACEs should +            be stripped (default:
>     > True). +        @type  protected: C{bool} + +        @return: An
>     > ACL without the stripped ACEs. +        @rtype:  L{ACL} object +
>     > """ +        newAcl = ACL() +        if len(self.aces): +
>     > for ace in self.aces: +                keep = 1 +                if
>     > inherited and ace.inherited: +                    keep = 0 +
>     > elif protected and ace.protected: +                    keep = 0 +
>     > if keep: +                    newAcl.addAce(ace) +        return
>     > newAcl diff --git a/src/webdav/acp/GrantDeny.py
>     > b/src/webdav/acp/GrantDeny.py new file mode 100644 index
>     > 0000000..52c9b93 --- /dev/null +++ b/src/webdav/acp/GrantDeny.py @@
>     > -0,0 +1,241 @@ +# Copyright 2008 German Aerospace Center (DLR) +#
>     > +# Licensed under the Apache License, Version 2.0 (the "License");
>     > +# you may not use this file except in compliance with the
>     > License. +# You may obtain a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +Handling of grant
>     > and deny clauses in ACEs according to WebDAV ACP specification.
>     > +""" + + +from webdav.acp.Privilege import Privilege +from webdav
>     > import Constants +from webdav.Connection import WebdavError + +
>     > +__version__ = "$LastChangedRevision$" + + +class
>     > GrantDeny(object): +    """ +    This class provides functionality
>     > for handling +    grant and deny clauses in ACEs. + +    @ivar
>     > grantDeny:  Flag indicating whether clause grants or denies. +
>     > @type grantDeny:  C{bool} +    @ivar privileges: Privileges to be
>     > granted or denied. +    @type privileges: C{list} of L{Privilege}
>     > objects +    """ + +    def __init__(self, domroot=None): +
>     > """ +        Constructor should be called with either no parameters
>     >  +        (create blank GrantDeny), or one parameter (a DOM tree).
>     > + +        @param domroot:     A DOM tree (default: None). +
>     > @type  domroot:     L{webdav.WebdavResponse.Element} object +
>     >  +        @raise WebdavError: When non-valid parameters are passed
>     > a L{WebdavError} is raised. +        """ +        self.grantDeny  =
>     > 0   # 0: deny, 1: grant +        self.privileges = [] + +        if
>     > domroot: +            self.grantDeny = (domroot.name
>     <http://domroot.name> ==
>     > Constants.TAG_GRANT) +            for child in domroot.children: +
>     > if child.name <http://child.name> == Constants.TAG_PRIVILEGE and
>     child.ns ==
>     > Constants.NS_DAV: +
>     > self.privileges.append(Privilege(domroot=child)) +
>     > else: +                    # This shouldn't happen, someone screwed
>     > up with the params ... +                    raise
>     > WebdavError('Non-privilege tag handed to GrantDeny constructor: %s'
>     > \ +                        % child.name <http://child.name>) +
>          elif domroot ==
>     > None: +            # no param ==> blank object +            pass +
>     > else: +            # This shouldn't happen, someone screwed up with
>     > the params ... +            raise WebdavError('Non-valid parameters
>     > handed to GrantDeny constructor.') + +    def __cmp__(self,
>     > other): +        """ Compares two GrantDeny instances. """ +
>     > if not isinstance(other, GrantDeny): +            return 1 +
>     > if self.grantDeny == other.grantDeny: +            equal = 1 +
>     > for priv in self.privileges: +                inList = 0 +
>     > for otherPriv in other.privileges: +                    if priv ==
>     > otherPriv: +                        inList = 1 +                if
>     > inList == 0: +                    equal = 0 +            return not
>     > equal +        else: +            return 1 + +    def
>     > __repr__(self): +        """ Returns the representation of an
>     > instance. """ +        representation = '<class GrantDeny: ' +
>     > if self.grantDeny: +            representation += 'grant
>     > privileges: [' +        else: +            representation += 'deny
>     > privileges: [' +        first = 1 +        for priv in
>     > self.privileges: +            if first: +
>     > representation += '%s' % priv +                first = 0 +
>     > else: +                representation += ', %s' % priv +
>     > return '%s]>' % (representation) + +    def copy(self, other): +
>     > """ +        Copy a GrantDeny object. + +        @param other:
>     > Another grant or deny clause to copy. +        @type  other:
>     > L{GrantDeny} object + +        @raise WebdavError: When an object
>     > that is not an L{GrantDeny} is passed +            a L{WebdavError}
>     > is raised. +        """ +        if not isinstance(other,
>     > GrantDeny): +            raise WebdavError('Non-GrantDeny object
>     > passed to copy method: %s' \ +                % other) +
>     > self.grantDeny = other.grantDeny +        if other.privileges: +
>     > self.addPrivileges(other.privileges) + +    def isGrant(self): +
>     > """ +        Returns whether the set of privileges is of type
>     > "grant" +        indicating true or false. + +        @return:
>     > Value whether the clause is of grant type. +        @rtype:
>     > C{bool} +        """ +        return self.grantDeny + +    def
>     > isDeny(self): +        """ +        Returns whether the set of
>     > privileges is of type "deny" +        indicating true or false. +
>     >  +        @return: Value whether the clause is of deny type. +
>     > @rtype:  C{bool} +        """ +        return not self.grantDeny +
>     > +    def setGrantDeny(self, grantDeny): +        """ +        Sets
>     > the set of privileges to given value for grantDeny. + +
>     > @param grantDeny: Grant/deny value for clause (grant: True/1, deny:
>     > False/0). +        @type  grantDeny: C{bool} +        """ +
>     > if grantDeny == 0 or grantDeny == 1: +            self.grantDeny =
>     > grantDeny + +    def setGrant(self): +        """ Sets the set of
>     > privileges to type "grant". """ +        self.grantDeny = 1 + +
>     > def setDeny(self): +        """ Sets the set of privileges to type
>     > "deny". """ +        self.grantDeny = 0 + +    def isAll(self): +
>     > """ +        Checks whether the privileges contained are equal +
>     > to aggregate DAV:all privilege. + +        @return: Value whether
>     > all un-aggregated privileges are present. +        @rtype:
>     > C{bool} +        """ + +        if len(self.privileges) == 1 and
>     > self.privileges[0].name == Constants.TAG_ALL: +            return
>     > 1 +        return 0 + +    def addPrivilege(self, privilege): +
>     > """ +        Adds the passed privilege to list if it's not in it,
>     > yet. + +        @param privilege: A privilege. +        @type
>     > privilege: L{Privilege} object +        """ +        inList =
>     > False +        for priv in self.privileges: +            if priv ==
>     > privilege: +                inList = True +        if not inList: +
>     > newPrivilege = Privilege() +
>     > newPrivilege.copy(privilege) +
>     > self.privileges.append(newPrivilege) + +    def addPrivileges(self,
>     > privileges): +        """ +        Adds the list of passed
>     > privileges to list. + +        @param privileges: Several
>     > privileges. +        @type  privileges: sequence of L{Privilege}
>     > objects +        """ +        for priv in privileges: +
>     > self.addPrivilege(priv) + +    def delPrivilege(self, privilege): +
>     > """ +        Deletes the passed privilege from list if it's in it.
>     > + +        @param privilege: A privilege. +        @type
>     > privilege: L{Privilege} object + +        @raise WebdavError: A
>     > L{WebdavError} is raised if the privilege to be +
>     > deleted is not present. +        """ +        count = 0 +
>     > index = 0 +        for priv in self.privileges: +            count
>     > += 1 +            if priv == privilege: +                index =
>     > count +        if index: +            self.privileges.pop(index -
>     > 1) +        else: +            raise WebdavError('Privilege to be
>     > deleted not in list: %s' % privilege) + +    def
>     > delPrivileges(self, privileges): +        """ +        Deletes the
>     > list of passed privileges from list. + +        @param privileges:
>     > Several privileges. +        @type  privileges: sequence of
>     > L{Privilege} objects +        """ +        for priv in privileges:
>     > +            self.delPrivilege(priv) + +    def toXML(self): +
>     > """ +        Returns string of GrantDeny content to valid XML as
>     > described in WebDAV ACP. +        """ +        assert
>     > self.privileges, "GrantDeny object is not initialized or does not
>     > contain content!" + +        if self.isGrant(): +            tag =
>     > 'D:' + Constants.TAG_GRANT +        else: +            tag = 'D:' +
>     > Constants.TAG_DENY + +        res = '' +        for privilege in
>     > self.privileges: +            res += privilege.toXML() +
>     > return '<%s>%s</%s>' % (tag, res, tag) diff --git
>     > a/src/webdav/acp/Makefile.am b/src/webdav/acp/Makefile.am new file
>     > mode 100644 index 0000000..506eb92 --- /dev/null +++
>     > b/src/webdav/acp/Makefile.am @@ -0,0 +1,12 @@ +sugardir =
>     > $(pythondir)/webdav/acp +sugar_PYTHON =                  \ +
>     > AceHandler.py      \ +             Ace.py             \ +
>     > Acl.py             \ +             GrantDeny.py       \ +
>     > __init__.py        \ +             Principal.py       \ +
>     > Privilege.py + + + diff --git a/src/webdav/acp/Principal.py
>     > b/src/webdav/acp/Principal.py new file mode 100644 index
>     > 0000000..a0d5ec9 --- /dev/null +++ b/src/webdav/acp/Principal.py @@
>     > -0,0 +1,189 @@ +# Copyright 2008 German Aerospace Center (DLR) +#
>     > +# Licensed under the Apache License, Version 2.0 (the "License");
>     > +# you may not use this file except in compliance with the
>     > License. +# You may obtain a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +Handling of
>     > principals for ACEs according to WebDAV ACP specification. +""" +
>     > + +from webdav import Constants +from webdav.Connection import
>     > WebdavError + + +__version__ = "$LastChangedRevision$" + + +class
>     > Principal(object): +    """ +    This class provides functionality
>     > for handling +    principals according to the WebDAV ACP. + +
>     > @ivar displayname:  Name of the principal for output +    @type
>     > displayname:  C{string} +    @ivar principalURL: URL under which
>     > the principal can be referenced on the server. +    @type
>     > principalURL: C{string} +    @ivar property:     Information on
>     > type of a pseudo/jproperty principal, e. g. +        DAV:owner,
>     > DAV:authenticated, etc. +    @type property:     C{string} + +
>     > @cvar _TAG_LIST_PRINCIPALS: List of allowed XML tags within a
>     > principal declaration. +    @type _TAG_LIST_PRINCIPALS: C{tuple} of
>     > C{string}s +    @cvar _TAG_LIST_STATUS:     List of XML tags for
>     > the status of a pseudo principal. +    @type _TAG_LIST_STATUS:
>     > C{tuple} of C{string}s +    """ + +    # some local constants for
>     > this class to make things easier/more readable: +
>     > _TAG_LIST_PRINCIPALS = (Constants.TAG_HREF,     # directly by URL +
>     > Constants.TAG_ALL, Constants.TAG_AUTHENTICATED,
>     > Constants.TAG_UNAUTHENTICATED, +
>     > # by log-in status +
>     > Constants.TAG_PROPERTY, # for property info, e. g. 'owner' +
>     > Constants.TAG_SELF,     # only if the resource is the principal
>     > itself +                            Constants.TAG_PROP)     #
>     > contains property info like 'displayname' +    _TAG_LIST_STATUS
>     > = (Constants.TAG_ALL, Constants.TAG_AUTHENTICATED,
>     > Constants.TAG_UNAUTHENTICATED) + +    # restrict instance
>     > variables +    __slots__ = ('displayname', 'principalURL',
>     > 'property') + +    def __init__(self, domroot=None,
>     > displayname=None, principalURL=None): +        """ +
>     > Constructor should be called with either no parameters (create
>     > blank Principal), +        one parameter (a DOM tree), or two
>     > parameters (displayname and URL or property tag). + +        @param
>     > domroot:      A DOM tree (default: None). +        @type  domroot:
>     > L{webdav.WebdavResponse.Element} object +        @param
>     > displayname:  The display name of a principal (default: None). +
>     > @type  displayname:  C{string} +        @param principalURL: The
>     > URL representing a principal (default: None). +        @type
>     > principalURL: C{string} + +        @raise WebdavError: When
>     > non-valid parameters or sets of parameters are +            passed
>     > a L{WebdavError} is raised. +        """ +        self.displayname
>     > = None +        self.principalURL = None +        self.property
>     > = None + +        if domroot: +            for child in
>     > domroot.children: +                if child.ns == Constants.NS_DAV
>     > and (child.name <http://child.name> in self._TAG_LIST_PRINCIPALS): +
>     > if child.name <http://child.name> == Constants.TAG_PROP: +
>     > self.displayname = \ +
>     > child.find(Constants.PROP_DISPLAY_NAME, Constants.NS_DAV) +
>     > elif child.name <http://child.name> == Constants.TAG_HREF: +
>     > self.principalURL = child.textof() +                        if
>     > self.principalURL and self.property in self._TAG_LIST_STATUS: +
>     > raise WebdavError('Principal cannot contain a URL and "%s"' %
>     > (self.property)) +                    elif child.name
>     <http://child.name> ==
>     > Constants.TAG_PROPERTY: +                        if child.count()
>     > == 1: +                            if self.property: +
>     > raise WebdavError('Property for principal has already been set: old
>     > "%s", new "%s"' \ +                                    %
>     > (self.property, child.pop().name)) +
>     > elif self.principalURL: +                                raise
>     > WebdavError('Principal cannot contain a URL and "%s"' %
>     > (self.property)) +                            else: +
>     > self.property = child.pop().name +                        else: +
>     > raise WebdavError("There should be only one value in the property
>     > for a principal, we have: %s" \ +                                %
>     > child.name <http://child.name>) +                    else: +
>                        if
>     > self.property: +                            raise
>     > WebdavError('Property for principal has already been set: old "%s",
>     > new "%s"' \ +                                % (self.property,
>     > child.name <http://child.name>)) +                        else: +
>     > self.property = child.name <http://child.name> +
>            if
>     > self.principalURL and self.property in self._TAG_LIST_STATUS: +
>     > raise WebdavError('Principal cannot contain a URL and "%s"' %
>     > (self.property)) +                else: # This shouldn't happen,
>     > something's wrong with the DOM tree +                    raise
>     > WebdavError('Non-valid tag in principal DOM tree for constructor:
>     > %s' % child.name <http://child.name>) +        elif displayname
>     == None or principalURL
>     > == None: +            if displayname: +
>     > self.displayname  = displayname +            if principalURL: +
>     > self.principalURL = principalURL +        else: +            # This
>     > shouldn't happen, someone screwed up with the params ... +
>     > raise WebdavError('Non-valid parameters handed to Principal
>     > constructor.') + +    def __cmp__(self, other): +        if not
>     > isinstance(other, Principal): +            return 1 +        if
>     > self.displayname == other.displayname \ +                and
>     > self.principalURL == other.principalURL \ +                and
>     > self.property == other.property: +            return 0 +
>     > else: +            return 1 + +    def __repr__(self): +
>     > return '<class Principal: displayname: "%s", principalURL: "%s",
>     > property: "%s">' \ +            % (self.displayname,
>     > self.principalURL, self.property) + +    def copy(self, other): +
>     > """Copy Principal object. + +        @param other: Another
>     > principal to copy. +        @type  other: L{Principal} object +
>     >  +        @raise WebdavError: When an object that is not a
>     > L{Principal} is passed +            a L{WebdavError} is raised. +
>     > """ +        if not isinstance(other, Principal): +
>     > raise WebdavError('Non-Principal object passed to copy method: ' %
>     > other.__class__) +        self.displayname  = other.displayname +
>     > self.principalURL = other.principalURL +        self.property     =
>     > other.property + +    def isValid(self): +        """ +
>     > Checks whether necessarry props for principal are set. + +
>     > @return: Validity of principal. +        @rtype:  C{bool} +
>     > """ +        return (self.displayname and +
>     > (self.principalURL or self.property) and +                not
>     > (self.principalURL and self.property)) + +    def toXML(self,
>     > invert=False, displayname=False, defaultNameSpace=None): +
>     > """Returns string of Principal content in valid XML as described in
>     > WebDAV ACP. + +        @param defaultNameSpace: Name space
>     > (default: None). +        @type  defaultNameSpace: C(string) +
>     > @param invert:           True if principal should be inverted
>     > (default: False). +        @type  invert:           C{bool} +
>     > @param displayname:      True if displayname should be in output
>     > (default: False). +        @type  displayname:      C{bool} +
>     > """ +        # this check is needed for setting principals only: +
>     > # assert self.isValid(), "principal is not initialized or does not
>     > contain valid content!" + +        PRINCIPAL = 'D:' +
>     > Constants.TAG_PRINCIPAL +        res = '' +        if
>     > self.principalURL: +            res += '<D:%s>%s</D:%s>' %
>     > (Constants.TAG_HREF, self.principalURL, Constants.TAG_HREF) +
>     > elif self.property in self._TAG_LIST_STATUS \ +                or
>     > self.property == Constants.TAG_SELF: +            res += '<D:%s/>'
>     > % (self.property) +        elif self.property: +            res +=
>     > '<D:%s><D:%s/></D:%s>' \ +                %
>     > (Constants.TAG_PROPERTY, self.property, Constants.TAG_PROPERTY) +
>     > if self.displayname and displayname: +            res +=
>     > '<D:%s><D:%s>%s</D:%s></D:%s>' \ +                %
>     > (Constants.TAG_PROP, Constants.PROP_DISPLAY_NAME, +
>     > self.displayname, +                    Constants.PROP_DISPLAY_NAME,
>     > Constants.TAG_PROP) +        if invert: +            res =
>     > '<D:invert>%s</D:invert>' % (res) +        return '<%s>%s</%s>' %
>     > (PRINCIPAL, res, PRINCIPAL) diff --git
>     > a/src/webdav/acp/Privilege.py b/src/webdav/acp/Privilege.py new
>     > file mode 100644 index 0000000..abfdcf9 --- /dev/null +++
>     > b/src/webdav/acp/Privilege.py @@ -0,0 +1,125 @@ +# Copyright 2008
>     > German Aerospace Center (DLR) +# +# Licensed under the Apache
>     > License, Version 2.0 (the "License"); +# you may not use this file
>     > except in compliance with the License. +# You may obtain a copy of
>     > the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +""" +Handling for
>     > privileges for grant and deny clauses in ACEs +according to WebDAV
>     > ACP specification. +""" + + +from webdav import Constants +from
>     > webdav.Connection import WebdavError + + +__version__ =
>     > "$LastChangedRevision$" + + +class Privilege(object): +    """This
>     > class provides functionality for handling privileges for ACEs. +
>     >  +    @ivar name: Name of the privilege. +    @type name:
>     > C{string} + +    @cvar __privileges: List of allowed XML tags for
>     > privileges. +    @type __privileges: C{tuple} of C{string}s +
>     > """ + + +    __privileges = list() + + +    def __init__(self,
>     > privilege=None, domroot=None): +        """ +        Constructor
>     > should be called with either no parameters (create blank
>     > Privilege), +        one parameter (a DOM tree or privilege name to
>     > initialize it directly). + +        @param domroot: A DOM tree
>     > (default: None). +        @type  domroot:
>     > L{webdav.WebdavResponse.Element} object +        @param privilege:
>     > The valid name of a privilege (default: None). +        @type
>     > privilege: C{string} + +        @raise WebdavError: When non-valid
>     > parameters or sets of parameters are +
>     > passed a L{WebdavError} is raised. +        """ + +
>     > self.name <http://self.name> = None + +        if domroot: +
>            if
>     > len(domroot.children) != 1: +                raise
>     > WebdavError('Wrong number of elements for Privilege constructor, we
>     > have: %i' \ +                    % (len(domroot.children))) +
>     > else: +                child = domroot.children[0] +
>     > if child.ns == Constants.NS_DAV and child.name
>     <http://child.name> in
>     > self.__privileges: +                    self.name
>     <http://self.name> = child.name <http://child.name> +
>     > else: +                    raise WebdavError('Not a valid privilege
>     > tag, we have: %s%s' \ +                        % (child.ns,
>     > child.name <http://child.name>)) +        elif privilege: +
>            if privilege in
>     > self.__privileges: +                self.name <http://self.name>
>     = privilege +
>     > else: +                raise WebdavError('Not a valid privilege
>     > tag, we have: %s.' % str(privilege)) + +    @classmethod +    def
>     > registerPrivileges(cls, privileges): +        """ +
>     > Registers supported privilege tags. + +        @param privileges:
>     > List of privilege tags. +        @type privileges: C{list} of
>     > C{unicode} +        """ + +        for privilege in privileges: +
>     > cls.__privileges.append(privilege) + +    def __cmp__(self,
>     > other): +        """ Compares two Privilege instances. """ +
>     > if not isinstance(other, Privilege): +            return 1 +
>     > if self.name <http://self.name> != other.name
>     <http://other.name>: +            return 1 +        else: +
>     > return 0 + +    def __repr__(self): +        """ Returns the string
>     > representation of an instance. """ +        return '<class
>     > Privilege: name: "%s">' % (self.name <http://self.name>) + +
>      def copy(self,
>     > other): +        """ +        Copy Privilege object. + +
>     > @param other: Another privilege to copy. +        @type  other:
>     > L{Privilege} object + +        @raise WebdavError: When an object
>     > that is not a L{Privilege} is passed +            a L{WebdavError}
>     > is raised. +        """ +        if not isinstance(other,
>     > Privilege): +            raise WebdavError('Non-Privilege object
>     > passed to copy method: %s' % other.__class__) +        self.name
>     <http://self.name> =
>     > other.name <http://other.name> + +    def toXML(self): +
>      """ +        Returns
>     > privilege content as string in valid XML as described in WebDAV
>     > ACP. + +        @param defaultNameSpace: Name space (default:
>     > None). +        @type  defaultNameSpace: C(string) +        """ +
>     > assert self.name <http://self.name> != None, "privilege is not
>     initialized or does not
>     > contain valid content!" + +        privilege = 'D:' +
>     > Constants.TAG_PRIVILEGE +        return '<%s><D:%s/></%s>' %
>     > (privilege, self.name <http://self.name>, privilege) diff --git
>     > a/src/webdav/acp/__init__.py b/src/webdav/acp/__init__.py new file
>     > mode 100644 index 0000000..b5af299 --- /dev/null +++
>     > b/src/webdav/acp/__init__.py @@ -0,0 +1,33 @@ +# Copyright 2008
>     > German Aerospace Center (DLR) +# +# Licensed under the Apache
>     > License, Version 2.0 (the "License"); +# you may not use this file
>     > except in compliance with the License. +# You may obtain a copy of
>     > the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +from webdav import
>     > Constants +from webdav.acp.Acl import ACL +from webdav.acp.Ace
>     > import ACE +from webdav.acp.GrantDeny import GrantDeny +from
>     > webdav.acp.Privilege import Privilege +from webdav.acp.Principal
>     > import Principal + + +__version__ = "$LastChangedRevision$" + +
>     > +privileges = [Constants.TAG_READ, Constants.TAG_WRITE,
>     > Constants.TAG_WRITE_PROPERTIES, +
>     > Constants.TAG_WRITE_CONTENT, Constants.TAG_UNLOCK,
>     > Constants.TAG_READ_ACL, +
>     > Constants.TAG_READ_CURRENT_USER_PRIVILEGE_SET,
>     > Constants.TAG_WRITE_ACL, Constants.TAG_ALL, +
>     > Constants.TAG_BIND, Constants.TAG_UNBIND,
>     > Constants.TAG_TAMINO_SECURITY, +
>     > Constants.TAG_BIND_COLLECTION, Constants.TAG_UNBIND_COLLECTION,
>     > Constants.TAG_READ_PRIVATE_PROPERTIES, +
>     > Constants.TAG_WRITE_PRIVATE_PROPERTIES]
>     > +Privilege.registerPrivileges(privileges) diff --git
>     > a/src/webdav/davlib.py b/src/webdav/davlib.py new file mode 100644
>     > index 0000000..f4dac91 --- /dev/null +++ b/src/webdav/davlib.py @@
>     > -0,0 +1,336 @@ +# pylint:
>     >
>     disable-msg=W0402,W0231,W0141,R0903,C0321,W0701,R0904,C0103,W0201,W0102,R0913,W0622,E1101,C0111,C0121,R0901
>     >
>     >
>     +# DAV client library
>     > +# +# Copyright (C) 1998-2000 Guido van Rossum. All Rights
>     > Reserved. +# Written by Greg Stein. Given to Guido. Licensed using
>     > the Python license. +# +# This module is maintained by Greg and is
>     > available at: +#    http://www.lyra.org/greg/python/davlib.py +# +#
>     > Since this isn't in the Python distribution yet, we'll use the CVS
>     > ID +# for tracking: +#   $Id: davlib.py 3182 2008-02-22 15:57:55
>     > +0000 (Fr, 22 Feb 2008) schlauch $ +# + +import httplib +import
>     > urllib +import string +import types +import mimetypes +import
>     > qp_xml + + +INFINITY = 'infinity' +XML_DOC_HEADER = '<?xml
>     > version="1.0" encoding="utf-8"?>' +XML_CONTENT_TYPE = 'text/xml;
>     > charset="utf-8"' + +# block size for copying files up to the
>     > server +BLOCKSIZE = 16384 + + +class
>     > HTTPProtocolChooser(httplib.HTTPSConnection): +    def
>     > __init__(self, *args, **kw): +        self.protocol =
>     > kw.pop('protocol') +        if self.protocol == "https": +
>     > self.default_port = 443 +        else: +
>     > self.default_port = 80 + +
>     > apply(httplib.HTTPSConnection.__init__, (self,) + args, kw) + +
>     > def connect(self): +        if self.protocol == "https": +
>     > httplib.HTTPSConnection.connect(self) +        else: +
>     > httplib.HTTPConnection.connect(self) + + +class
>     > HTTPConnectionAuth(HTTPProtocolChooser): +    def __init__(self,
>     > *args, **kw): +        apply(HTTPProtocolChooser.__init__, (self,)
>     > + args, kw) + +        self.__username = None +
>     > self.__password = None +        self.__nonce = None +
>     > self.__opaque = None + +    def setauth(self, username, password):
>     > +        self.__username = username +        self.__password =
>     > password + + +def _parse_status(elem): +    text = elem.textof() +
>     > idx1 = string.find(text, ' ') +    idx2 = string.find(text, ' ',
>     > idx1+1) +    return int(text[idx1:idx2]), text[idx2+1:] + +class
>     > _blank: +    def __init__(self, **kw): +
>     > self.__dict__.update(kw) +class _propstat(_blank): pass +class
>     > _response(_blank): pass +class _multistatus(_blank): pass + +def
>     > _extract_propstat(elem): +    ps = _propstat(prop={}, status=None,
>     > responsedescription=None) +    for child in elem.children: +
>     > if child.ns != 'DAV:': +            continue +        if
>     child.name <http://child.name>
>     > == 'prop': +            for prop in child.children: +
>     > ps.prop[(prop.ns, prop.name <http://prop.name>)] = prop +
>      elif child.name <http://child.name> ==
>     > 'status': +            ps.status = _parse_status(child) +
>     > elif child.name <http://child.name> == 'responsedescription': +
>     > ps.responsedescription = child.textof() +        ### unknown
>     > element name + +    return ps + +def _extract_response(elem): +
>     > resp = _response(href=[], status=None, responsedescription=None,
>     > propstat=[]) +    for child in elem.children: +        if child.ns
>     > != 'DAV:': +            continue +        if child.name
>     <http://child.name> == 'href':
>     > +            resp.href.append(child.textof()) +        elif
>     > child.name <http://child.name> == 'status': +
>      resp.status =
>     > _parse_status(child) +        elif child.name <http://child.name> ==
>     > 'responsedescription': +            resp.responsedescription =
>     > child.textof() +        elif child.name <http://child.name> ==
>     'propstat': +
>     > resp.propstat.append(_extract_propstat(child)) +        ### unknown
>     > child element + +    return resp + +def _extract_msr(root): +    if
>     > root.ns != 'DAV:' or root.name <http://root.name> !=
>     'multistatus': +        raise
>     > 'invalid response: <DAV:multistatus> expected' + +    msr =
>     > _multistatus(responses=[ ], responsedescription=None) + +    for
>     > child in root.children: +        if child.ns != 'DAV:': +
>     > continue +        if child.name <http://child.name> ==
>     'responsedescription': +
>     > msr.responsedescription = child.textof() +        elif
>     child.name <http://child.name>
>     > == 'response': +
>     > msr.responses.append(_extract_response(child)) +        ### unknown
>     > child element + +    return msr + +def _extract_locktoken(root): +
>     > if root.ns != 'DAV:' or root.name <http://root.name> != 'prop':
>     +        raise
>     > 'invalid response: <DAV:prop> expected' +    elem =
>     > root.find('lockdiscovery', 'DAV:') +    if not elem: +        raise
>     > 'invalid response: <DAV:lockdiscovery> expected' +    elem =
>     > elem.find('activelock', 'DAV:') +    if not elem: +        raise
>     > 'invalid response: <DAV:activelock> expected' +    elem =
>     > elem.find('locktoken', 'DAV:') +    if not elem: +        raise
>     > 'invalid response: <DAV:locktoken> expected' +    elem =
>     > elem.find('href', 'DAV:') +    if not elem: +        raise 'invalid
>     > response: <DAV:href> expected' +    return elem.textof() + + +class
>     > DAVResponse(httplib.HTTPResponse): +    def
>     > parse_multistatus(self): +        self.root =
>     > qp_xml.Parser().parse(self) +        self.msr =
>     > _extract_msr(self.root) + +    def parse_lock_response(self): +
>     > self.root = qp_xml.Parser().parse(self) +        self.locktoken =
>     > _extract_locktoken(self.root) + + +class DAV(HTTPConnectionAuth):
>     > + +    response_class = DAVResponse + +    def get(self, url,
>     > extra_hdrs={ }): +        return self._request('GET', url,
>     > extra_hdrs=extra_hdrs) + +    def head(self, url, extra_hdrs={ }):
>     > +        return self._request('HEAD', url, extra_hdrs=extra_hdrs)
>     > + +    def post(self, url, data={ }, body=None, extra_hdrs={ }): +
>     > headers = extra_hdrs.copy() + +        assert body or data, "body
>     > or data must be supplied" +        assert not (body and data),
>     > "cannot supply both body and data" +        if data: +
>     > body = '' +            for key, value in data.items(): +
>     > if isinstance(value, types.ListType): +                    for item
>     > in value: +                        body = body + '&' + key + '=' +
>     > urllib.quote(str(item)) +                else: +
>     > body = body + '&' + key + '=' + urllib.quote(str(value)) +
>     > body = body[1:] +            headers['Content-Type'] =
>     > 'application/x-www-form-urlencoded' + +        return
>     > self._request('POST', url, body, headers) + +    def options(self,
>     > url='*', extra_hdrs={ }): +        return self._request('OPTIONS',
>     > url, extra_hdrs=extra_hdrs) + +    def trace(self, url,
>     > extra_hdrs={ }): +        return self._request('TRACE', url,
>     > extra_hdrs=extra_hdrs) + +    def put(self, url, contents, +
>     > content_type=None, content_enc=None, extra_hdrs={ }): + +        if
>     > not content_type: +            content_type, content_enc =
>     > mimetypes.guess_type(url) + +        headers = extra_hdrs.copy() +
>     > if content_type: +            headers['Content-Type'] =
>     > content_type +        if content_enc: +
>     > headers['Content-Encoding'] = content_enc +        return
>     > self._request('PUT', url, contents, headers) + +    def
>     > delete(self, url, extra_hdrs={ }): +        return
>     > self._request('DELETE', url, extra_hdrs=extra_hdrs) + +    def
>     > propfind(self, url, body=None, depth=None, extra_hdrs={ }): +
>     > headers = extra_hdrs.copy() +        headers['Content-Type'] =
>     > XML_CONTENT_TYPE +        if depth is not None: +
>     > headers['Depth'] = str(depth) +        return
>     > self._request('PROPFIND', url, body, headers) + +    def
>     > proppatch(self, url, body, extra_hdrs={ }): +        headers =
>     > extra_hdrs.copy() +        headers['Content-Type'] =
>     > XML_CONTENT_TYPE +        return self._request('PROPPATCH', url,
>     > body, headers) + +    def mkcol(self, url, extra_hdrs={ }): +
>     > return self._request('MKCOL', url, extra_hdrs=extra_hdrs) + +
>     > def move(self, src, dst, extra_hdrs={ }): +        headers =
>     > extra_hdrs.copy() +        headers['Destination'] = dst +
>     > return self._request('MOVE', src, extra_hdrs=headers) + +    def
>     > copy(self, src, dst, depth=None, extra_hdrs={ }): +        headers
>     > = extra_hdrs.copy() +        headers['Destination'] = dst +
>     > if depth is not None: +            headers['Depth'] = str(depth) +
>     > return self._request('COPY', src, extra_hdrs=headers) + +    def
>     > lock(self, url, owner='', timeout=None, depth=None, +
>     > scope='exclusive', type='write', extra_hdrs={ }): +        headers
>     > = extra_hdrs.copy() +        headers['Content-Type'] =
>     > XML_CONTENT_TYPE +        if depth is not None: +
>     > headers['Depth'] = str(depth) +        if timeout is not None: +
>     > headers['Timeout'] = timeout +        body = XML_DOC_HEADER + \ +
>     > '<DAV:lockinfo xmlns:DAV="DAV:">' + \ +
>     > '<DAV:lockscope><DAV:%s/></DAV:lockscope>' % scope + \ +
>     > '<DAV:locktype><DAV:%s/></DAV:locktype>' % type + \ +
>     > '<DAV:owner>' + owner + '</DAV:owner>' + \ +
>     > '</DAV:lockinfo>' +        return self._request('LOCK', url, body,
>     > extra_hdrs=headers) + +    def unlock(self, url, locktoken,
>     > extra_hdrs={ }): +        headers = extra_hdrs.copy() +        if
>     > locktoken[0] != '<': +            locktoken = '<' + locktoken +
>     > '>' +        headers['Lock-Token'] = locktoken +        return
>     > self._request('UNLOCK', url, extra_hdrs=headers) + +    def
>     > _request(self, method, url, body=None, extra_hdrs={}): +
>     > "Internal method for sending a request." + +
>     > self.request(method, url, body, extra_hdrs) +        return
>     > self.getresponse() + + +    # +    # Higher-level methods for
>     > typical client use +    # + +    def allprops(self, url,
>     > depth=None): +        body = XML_DOC_HEADER + \ +
>     > '<DAV:propfind xmlns:DAV="DAV:"><DAV:allprop/></DAV:propfind>' +
>     > return self.propfind(url, body, depth=depth) + +    def
>     > propnames(self, url, depth=None): +        body = XML_DOC_HEADER +
>     > \ +               '<DAV:propfind
>     > xmlns:DAV="DAV:"><DAV:propname/></DAV:propfind>' +        return
>     > self.propfind(url, body, depth) + +    def getprops(self, url,
>     > *names, **kw): +        assert names, 'at least one property name
>     > must be provided' +        if kw.has_key('ns'): +            xmlns
>     > = ' xmlns:NS="' + kw['ns'] + '"' +            ns = 'NS:' +
>     > del kw['ns'] +        else: +            xmlns = ns = '' +
>     > if kw.has_key('depth'): +            depth = kw['depth'] +
>     > del kw['depth'] +        else: +            depth = 0 +
>     > assert not kw, 'unknown arguments' +        body = XML_DOC_HEADER +
>     > \ +               '<DAV:propfind xmlns:DAV="DAV:"' + xmlns +
>     > '><DAV:prop><' + ns + \ +               string.joinfields(names,
>     > '/><' + ns) + \ +               '/></DAV:prop></DAV:propfind>' +
>     > return self.propfind(url, body, depth) + +    def delprops(self,
>     > url, *names, **kw): +        assert names, 'at least one property
>     > name must be provided' +        if kw.has_key('ns'): +
>     > xmlns = ' xmlns:NS="' + kw['ns'] + '"' +            ns = 'NS:' +
>     > del kw['ns'] +        else: +            xmlns = ns = '' +
>     > assert not kw, 'unknown arguments' +        body = XML_DOC_HEADER +
>     > \ +               '<DAV:propertyupdate xmlns:DAV="DAV:"' + xmlns +
>     > \ +               '><DAV:remove><DAV:prop><' + ns + \ +
>     > string.joinfields(names, '/><' + ns) + \ +
>     > '/></DAV:prop></DAV:remove></DAV:propertyupdate>' +        return
>     > self.proppatch(url, body) + +    def setprops(self, url, *xmlprops,
>     > **props): +        assert xmlprops or props, 'at least one property
>     > must be provided' +        xmlprops = list(xmlprops) +        if
>     > props.has_key('ns'): +            xmlns = ' xmlns:NS="' +
>     > props['ns'] + '"' +            ns = 'NS:' +            del
>     > props['ns'] +        else: +            xmlns = ns = '' +
>     > for key, value in props.items(): +            if value: +
>     > xmlprops.append('<%s%s>%s</%s%s>' % (ns, key, value, ns, key)) +
>     > else: +                xmlprops.append('<%s%s/>' % (ns, key)) +
>     > elems = string.joinfields(xmlprops, '') +        body =
>     > XML_DOC_HEADER + \ +               '<DAV:propertyupdate
>     > xmlns:DAV="DAV:"' + xmlns + \ +
>     > '><DAV:set><DAV:prop>' + \ +               elems + \ +
>     > '</DAV:prop></DAV:set></DAV:propertyupdate>' +        return
>     > self.proppatch(url, body) + +    def get_lock(self, url, owner='',
>     > timeout=None, depth=None): +        response = self.lock(url,
>     > owner, timeout, depth) +        response.parse_lock_response() +
>     > return response.locktoken + \ No newline at end of file diff --git
>     > a/src/webdav/logger.py b/src/webdav/logger.py new file mode 100644
>     > index 0000000..d2538ef --- /dev/null +++ b/src/webdav/logger.py @@
>     > -0,0 +1,51 @@ +# Copyright 2008 German Aerospace Center (DLR) +# +#
>     > Licensed under the Apache License, Version 2.0 (the "License"); +#
>     > you may not use this file except in compliance with the License. +#
>     > You may obtain a copy of the License at +# +#
>     > http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by
>     > applicable law or agreed to in writing, software +# distributed
>     > under the License is distributed on an "AS IS" BASIS, +# WITHOUT
>     > WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +#
>     > See the License for the specific language governing permissions
>     > and +# limitations under the License. + + +"""" +Module provides
>     > access to a configured logger instance. +The logger writes
>     > C{sys.stdout}. +""" + + +import logging +import sys + +
>     > +__version__ = "$LastChangedRevision$"[11:-2] + +
>     > +_defaultLoggerName = "webdavLogger" +_fileLogFormat =
>     > "%(asctime)s: %(levelname)s: %(message)s" + + +def
>     > getDefaultLogger(handler=None): +    """ +    Returns a configured
>     > logger object. + +    @return: Logger instance. +    @rtype:
>     > C{logging.Logger} +    """ + +    myLogger =
>     > logging.getLogger(_defaultLoggerName) +    if
>     > len(myLogger.handlers) == 0: +        myLogger.level =
>     > logging.DEBUG +        formatter =
>     > logging.Formatter(_fileLogFormat) +        if handler is None: +
>     > stdoutHandler = logging.StreamHandler(sys.stdout) +
>     > stdoutHandler.setFormatter(formatter) +
>     > myLogger.addHandler(stdoutHandler) +        else: +
>     > myLogger.addHandler(handler) +    return myLogger diff --git
>     > a/src/webdav/qp_xml.py b/src/webdav/qp_xml.py new file mode 100644
>     > index 0000000..f167e1b --- /dev/null +++ b/src/webdav/qp_xml.py @@
>     > -0,0 +1,240 @@ +# pylint:
>     >
>     disable-msg=W0311,E1101,E1103,W0201,C0103,W0622,W0402,W0706,R0911,W0613,W0612,R0912,W0141,C0111,C0121
>     >
>     >
>     +
>     > +# qp_xml: Quick Parsing for XML +# +# Written by Greg Stein.
>     > Public Domain. +# No Copyright, no Rights Reserved, and no
>     > Warranties. +# +# This module is maintained by Greg and is
>     > available as part of the XML-SIG +# distribution. This module and
>     > its changelog can be fetched at: +#
>     > http://www.lyra.org/cgi-bin/viewcvs.cgi/xml/xml/utils/qp_xml.py +#
>     > +# Additional information can be found on Greg's Python page at: +#
>     > http://www.lyra.org/greg/python/ +# +# This module was added to the
>     > XML-SIG distribution on February 14, 2000. +# As part of that
>     > distribution, it falls under the XML distribution license. +# +
>     > +import string +from xml.parsers import expat + + +error = __name__
>     > + '.error' + + +# +# The parsing class. Instantiate and pass a
>     > string/file to .parse() +# +class Parser: +  def __init__(self): +
>     > self.reset() + +  def reset(self): +    self.root = None +
>     > self.cur_elem = None + +  def find_prefix(self, prefix): +    elem
>     > = self.cur_elem +    while elem: +      if
>     > elem.ns_scope.has_key(prefix): +        return
>     > elem.ns_scope[prefix] +      elem = elem.parent + +    if prefix ==
>     > '': +      return ''        # empty URL for "no namespace" + +
>     > return None + +  def process_prefix(self, name, use_default): +
>     > idx = string.find(name, ':') +    if idx == -1: +      if
>     > use_default: +        return self.find_prefix(''), name +
>     > return '', name    # no namespace + +    if string.lower(name[:3])
>     > == 'xml': +      return '', name    # name is reserved by XML.
>     > don't break out a NS. + +    ns = self.find_prefix(name[:idx]) +
>     > if ns is None: +      raise error, 'namespace prefix ("%s") not
>     > found' % name[:idx] + +    return ns, name[idx+1:] + +  def
>     > start(self, name, attrs): +    elem = _element(name=name,
>     > lang=None, parent=None, +                    children=[],
>     > ns_scope={}, attrs={}, +                    first_cdata='',
>     > following_cdata='') + +    if self.cur_elem: +      elem.parent =
>     > self.cur_elem +      elem.parent.children.append(elem) +
>     > self.cur_elem = elem +    else: +      self.cur_elem = self.root =
>     > elem + +    work_attrs = [ ] + +    # scan for namespace
>     > declarations (and xml:lang while we're at it) +    for name, value
>     > in attrs.items(): +      if name == 'xmlns': +
>     > elem.ns_scope[''] = value +      elif name[:6] == 'xmlns:': +
>     > elem.ns_scope[name[6:]] = value +      elif name == 'xml:lang': +
>     > elem.lang = value +      else: +        work_attrs.append((name,
>     > value)) + +    # inherit xml:lang from parent +    if elem.lang is
>     > None and elem.parent: +      elem.lang = elem.parent.lang + +    #
>     > process prefix of the element name +    elem.ns, elem.name
>     <http://elem.name> =
>     > self.process_prefix(elem.name <http://elem.name>, 1) + +    #
>     process attributes'
>     > namespace prefixes +    for name, value in work_attrs: +
>     > elem.attrs[self.process_prefix(name, 0)] = value + +  def end(self,
>     > name): +    parent = self.cur_elem.parent + +    del
>     > self.cur_elem.ns_scope +    del self.cur_elem.parent + +
>     > self.cur_elem = parent + +  def cdata(self, data): +    elem =
>     > self.cur_elem +    if elem.children: +      last =
>     > elem.children[-1] +      last.following_cdata =
>     > last.following_cdata + data +    else: +      elem.first_cdata =
>     > elem.first_cdata + data + +  def parse(self, input): +
>     > self.reset() + +    p = expat.ParserCreate() +
>     > p.StartElementHandler = self.start +    p.EndElementHandler =
>     > self.end +    p.CharacterDataHandler = self.cdata + +    try: +
>     > if type(input) == type(''): +        p.Parse(input, 1) +
>     > else: +        while 1: +          s = input.read(_BLOCKSIZE) +
>     > if not s: +            p.Parse('', 1) +            break + +
>     > p.Parse(s, 0) + +    finally: +      if self.root: +
>     > _clean_tree(self.root) + +    return self.root + + +# +# handy
>     > function for dumping a tree that is returned by Parser +# +def
>     > dump(f, root): +  f.write('<?xml version="1.0"?>\n') +  namespaces
>     > = _collect_ns(root) +  _dump_recurse(f, root, namespaces,
>     > dump_ns=1) +  f.write('\n') + + +# +# This function returns the
>     > element's CDATA. Note: this is not recursive -- +# it only returns
>     > the CDATA immediately within the element, excluding the +# CDATA in
>     > child elements. +# +def textof(elem): +  return elem.textof() + +
>     >
>     +#########################################################################
>     >
>     >
>     +#
>     > +# private stuff for qp_xml +# + +_BLOCKSIZE = 16384    # chunk
>     > size for parsing input + +class _element: +  def __init__(self,
>     > **kw): +    self.__dict__.update(kw) + +  def textof(self): +
>     > '''Return the CDATA of this element. + +    Note: this is not
>     > recursive -- it only returns the CDATA immediately +    within the
>     > element, excluding the CDATA in child elements. +    ''' +    s =
>     > self.first_cdata +    for child in self.children: +      s = s +
>     > child.following_cdata +    return s + +  def find(self, name,
>     > ns=''): +    for elem in self.children: +      if elem.name
>     <http://elem.name> == name
>     > and elem.ns == ns: +        return elem +    return None + + +def
>     > _clean_tree(elem): +  elem.parent = None +  del elem.parent +
>     > map(_clean_tree, elem.children) + + +def _collect_recurse(elem,
>     > dict): +  dict[elem.ns] = None +  for ns, name in
>     > elem.attrs.keys(): +    dict[ns] = None +  for child in
>     > elem.children: +    _collect_recurse(child, dict) + +def
>     > _collect_ns(elem): +  "Collect all namespaces into a NAMESPACE ->
>     > PREFIX mapping." +  d = { '' : None } +  _collect_recurse(elem, d)
>     > +  del d['']    # make sure we don't pick up no-namespace entries +
>     > keys = d.keys() +  for i in range(len(keys)): +    d[keys[i]] = i +
>     > return d + +def _dump_recurse(f, elem, namespaces, lang=None,
>     > dump_ns=0): +  if elem.ns: +    f.write('<ns%d:%s' %
>     > (namespaces[elem.ns], elem.name <http://elem.name>)) +  else: +
>        f.write('<' +
>     > elem.name <http://elem.name>) +  for (ns, name), value in
>     elem.attrs.items(): +    if
>     > ns: +      f.write(' ns%d:%s="%s"' % (namespaces[ns], name,
>     > value)) +    else: +      f.write(' %s="%s"' % (name, value)) +  if
>     > dump_ns: +    for ns, id in namespaces.items(): +      f.write('
>     > xmlns:ns%d="%s"' % (id, ns)) +  if elem.lang != lang: +
>     > f.write(' xml:lang="%s"' % elem.lang) +  if elem.children or
>     > elem.first_cdata: +    f.write('>' + elem.first_cdata) +    for
>     > child in elem.children: +      _dump_recurse(f, child, namespaces,
>     > elem.lang) +      f.write(child.following_cdata) +    if elem.ns: +
>     > f.write('</ns%d:%s>' % (namespaces[elem.ns], elem.name
>     <http://elem.name>)) +
>     > else: +      f.write('</%s>' % elem.name <http://elem.name>) +
>      else: +
>     > f.write('/>') diff --git a/src/webdav/uuid_.py
>     > b/src/webdav/uuid_.py new file mode 100644 index 0000000..3b590e8
>     > --- /dev/null +++ b/src/webdav/uuid_.py @@ -0,0 +1,476 @@ +r"""UUID
>     > objects (universally unique identifiers) according to RFC 4122. +
>     > +This module provides immutable UUID objects (class UUID) and the
>     > functions +uuid1(), uuid3(), uuid4(), uuid5() for generating
>     > version 1, 3, 4, and 5 +UUIDs as specified in RFC 4122. + +If all
>     > you want is a unique ID, you should probably call uuid1() or
>     > uuid4(). +Note that uuid1() may compromise privacy since it creates
>     > a UUID containing +the computer's network address.  uuid4() creates
>     > a random UUID. + +Typical usage: + +    >>> import uuid + +    #
>     > make a UUID based on the host ID and current time +    >>>
>     > uuid.uuid1() +    UUID('a8098c1a-f86e-11da-bd1a-00112444be1e') + +
>     > # make a UUID using an MD5 hash of a namespace UUID and a name +
>     > >>> uuid.uuid3(uuid.NAMESPACE_DNS, 'python.org
>     <http://python.org>') +
>     > UUID('6fa459ea-ee8a-3ca4-894e-db77e160355e') + +    # make a random
>     > UUID +    >>> uuid.uuid4() +
>     > UUID('16fd2706-8baf-433b-82eb-8c7fada847da') + +    # make a UUID
>     > using a SHA-1 hash of a namespace UUID and a name +    >>>
>     > uuid.uuid5(uuid.NAMESPACE_DNS, 'python.org <http://python.org>') +
>     > UUID('886313e1-3b8a-5372-9b90-0c9aee199e5d') + +    # make a UUID
>     > from a string of hex digits (braces and hyphens ignored) +    >>> x
>     > = uuid.UUID('{00010203-0405-0607-0809-0a0b0c0d0e0f}') + +    #
>     > convert a UUID to a string of hex digits in standard form +    >>>
>     > str(x) +    '00010203-0405-0607-0809-0a0b0c0d0e0f' + +    # get the
>     > raw 16 bytes of the UUID +    >>> x.bytes +
>     > '\x00\x01\x02\x03\x04\x05\x06\x07\x08\t\n\x0b\x0c\r\x0e\x0f' + +
>     > # make a UUID from a 16-byte string +    >>>
>     > uuid.UUID(bytes=x.bytes) +
>     > UUID('00010203-0405-0607-0809-0a0b0c0d0e0f') +""" + +__author__ =
>     > 'Ka-Ping Yee <ping at zesty.ca <mailto:ping at zesty.ca>>' +__date__ =
>     '$Date: 2006/06/12
>     > 23:15:40 $'.split()[1].replace('/', '-') +__version__ = '$Revision:
>     > 1.30 $'.split()[1] + +RESERVED_NCS, RFC_4122, RESERVED_MICROSOFT,
>     > RESERVED_FUTURE = [ +    'reserved for NCS compatibility',
>     > 'specified in RFC 4122', +    'reserved for Microsoft
>     > compatibility', 'reserved for future definition'] + +class
>     > UUID(object): +    """Instances of the UUID class represent UUIDs
>     > as specified in RFC 4122. +    UUID objects are immutable,
>     > hashable, and usable as dictionary keys. +    Converting a UUID to
>     > a string with str() yields something in the form +
>     > '12345678-1234-1234-1234-123456789abc'.  The UUID constructor
>     > accepts +    four possible forms: a similar string of hexadecimal
>     > digits, or a +    string of 16 raw bytes as an argument named
>     > 'bytes', or a tuple of +    six integer fields (with 32-bit,
>     > 16-bit, 16-bit, 8-bit, 8-bit, and +    48-bit values respectively)
>     > as an argument named 'fields', or a single +    128-bit integer as
>     > an argument named 'int'. + +    UUIDs have these read-only
>     > attributes: + +        bytes       the UUID as a 16-byte string + +
>     > fields      a tuple of the six integer fields of the UUID, +
>     > which are also available as six individual attributes +
>     > and two derived attributes: + +            time_low
>     > the first 32 bits of the UUID +            time_mid
>     > the next 16 bits of the UUID +            time_hi_version
>     > the next 16 bits of the UUID +            clock_seq_hi_variant
>     > the next 8 bits of the UUID +            clock_seq_low
>     > the next 8 bits of the UUID +            node
>     > the last 48 bits of the UUID + +            time
>     > the 60-bit timestamp +            clock_seq               the
>     > 14-bit sequence number + +        hex         the UUID as a
>     > 32-character hexadecimal string + +        int         the UUID as
>     > a 128-bit integer + +        urn         the UUID as a URN as
>     > specified in RFC 4122 + +        variant     the UUID variant (one
>     > of the constants RESERVED_NCS, +                    RFC_4122,
>     > RESERVED_MICROSOFT, or RESERVED_FUTURE) + +        version     the
>     > UUID version number (1 through 5, meaningful only +
>     > when the variant is RFC_4122) +    """ + +    def __init__(self,
>     > hex=None, bytes=None, fields=None, int=None, +
>     > version=None): +        r"""Create a UUID from either a string of
>     > 32 hexadecimal digits, +        a string of 16 bytes as the 'bytes'
>     > argument, a tuple of six +        integers (32-bit time_low, 16-bit
>     > time_mid, 16-bit time_hi_version, +        8-bit
>     > clock_seq_hi_variant, 8-bit clock_seq_low, 48-bit node) as +
>     > the 'fields' argument, or a single 128-bit integer as the 'int' +
>     > argument.  When a string of hex digits is given, curly braces, +
>     > hyphens, and a URN prefix are all optional.  For example, these +
>     > expressions all yield the same UUID: + +
>     > UUID('{12345678-1234-5678-1234-567812345678}') +
>     > UUID('12345678123456781234567812345678') +
>     > UUID('urn:uuid:12345678-1234-5678-1234-567812345678') +
>     > UUID(bytes='\x12\x34\x56\x78'*4) +        UUID(fields=(0x12345678,
>     > 0x1234, 0x5678, 0x12, 0x34, 0x567812345678)) +
>     > UUID(int=0x12345678123456781234567812345678) + +        Exactly one
>     > of 'hex', 'bytes', 'fields', or 'int' must be given. +        The
>     > 'version' argument is optional; if given, the resulting UUID +
>     > will have its variant and version number set according to RFC
>     > 4122, +        overriding bits in the given 'hex', 'bytes',
>     > 'fields', or 'int'. +        """ + +        if [hex, bytes, fields,
>     > int].count(None) != 3: +            raise TypeError('need just one
>     > of hex, bytes, fields, or int') +        if hex is not None: +
>     > hex = hex.replace('urn:', '').replace('uuid:', '') +            hex
>     > = hex.strip('{}').replace('-', '') +            if len(hex) != 32:
>     > +                raise ValueError('badly formed hexadecimal UUID
>     > string') +            int = long(hex, 16) +        if bytes is not
>     > None: +            if len(bytes) != 16: +                raise
>     > ValueError('bytes is not a 16-char string') +            int =
>     > long(('%02x'*16) % tuple(map(ord, bytes)), 16) +        if fields
>     > is not None: +            if len(fields) != 6: +
>     > raise ValueError('fields is not a 6-tuple') +            (time_low,
>     > time_mid, time_hi_version, +             clock_seq_hi_variant,
>     > clock_seq_low, node) = fields +            if not 0 <= time_low <
>     > 1<<32L: +                raise ValueError('field 1 out of range
>     > (need a 32-bit value)') +            if not 0 <= time_mid <
>     > 1<<16L: +                raise ValueError('field 2 out of range
>     > (need a 16-bit value)') +            if not 0 <= time_hi_version <
>     > 1<<16L: +                raise ValueError('field 3 out of range
>     > (need a 16-bit value)') +            if not 0 <=
>     > clock_seq_hi_variant < 1<<8L: +                raise
>     > ValueError('field 4 out of range (need an 8-bit value)') +
>     > if not 0 <= clock_seq_low < 1<<8L: +                raise
>     > ValueError('field 5 out of range (need an 8-bit value)') +
>     > if not 0 <= node < 1<<48L: +                raise ValueError('field
>     > 6 out of range (need a 48-bit value)') +            clock_seq =
>     > (clock_seq_hi_variant << 8L) | clock_seq_low +            int =
>     > ((time_low << 96L) | (time_mid << 80L) | +
>     > (time_hi_version << 64L) | (clock_seq << 48L) | node) +        if
>     > int is not None: +            if not 0 <= int < 1<<128L: +
>     > raise ValueError('int is out of range (need a 128-bit value)') +
>     > if version is not None: +            if not 1 <= version <= 5: +
>     > raise ValueError('illegal version number') +            # Set the
>     > variant to RFC 4122. +            int &= ~(0xc000 << 48L) +
>     > int |= 0x8000 << 48L +            # Set the version number. +
>     > int &= ~(0xf000 << 64L) +            int |= version << 76L +
>     > self.__dict__['int'] = int + +    def __cmp__(self, other): +
>     > if isinstance(other, UUID): +            return cmp(self.int
>     <http://self.int>,
>     > other.int <http://other.int>) +        return NotImplemented + +
>        def
>     > __hash__(self): +        return hash(self.int <http://self.int>)
>     + +    def
>     > __int__(self): +        return self.int <http://self.int> + +
>      def __repr__(self):
>     > +        return 'UUID(%r)' % str(self) + +    def __setattr__(self,
>     > name, value): +        raise TypeError('UUID objects are
>     > immutable') + +    def __str__(self): +        hex = '%032x' %
>     > self.int <http://self.int> +        return '%s-%s-%s-%s-%s' % (
>     +            hex[:8],
>     > hex[8:12], hex[12:16], hex[16:20], hex[20:]) + +    def
>     > get_bytes(self): +        bytes = '' +        for shift in range(0,
>     > 128, 8): +            bytes = chr((self.int <http://self.int> >>
>     shift) & 0xff) +
>     > bytes +        return bytes + +    bytes = property(get_bytes) + +
>     > def get_fields(self): +        return (self.time_low,
>     > self.time_mid, self.time_hi_version, +
>     > self.clock_seq_hi_variant, self.clock_seq_low, self.node) + +
>     > fields = property(get_fields) + +    def get_time_low(self): +
>     > return self.int <http://self.int> >> 96L + +    time_low =
>     property(get_time_low) + +
>     > def get_time_mid(self): +        return (self.int
>     <http://self.int> >> 80L) & 0xffff
>     > + +    time_mid = property(get_time_mid) + +    def
>     > get_time_hi_version(self): +        return (self.int
>     <http://self.int> >> 64L) &
>     > 0xffff + +    time_hi_version = property(get_time_hi_version) + +
>     > def get_clock_seq_hi_variant(self): +        return (self.int
>     <http://self.int> >>
>     > 56L) & 0xff + +    clock_seq_hi_variant =
>     > property(get_clock_seq_hi_variant) + +    def
>     > get_clock_seq_low(self): +        return (self.int
>     <http://self.int> >> 48L) & 0xff
>     > + +    clock_seq_low = property(get_clock_seq_low) + +    def
>     > get_time(self): +        return (((self.time_hi_version & 0x0fffL)
>     > << 48L) | +                (self.time_mid << 32L) | self.time_low)
>     > + +    time = property(get_time) + +    def get_clock_seq(self): +
>     > return (((self.clock_seq_hi_variant & 0x3fL) << 8L) | +
>     > self.clock_seq_low) + +    clock_seq = property(get_clock_seq) + +
>     > def get_node(self): +        return self.int <http://self.int> &
>     0xffffffffffff + +
>     > node = property(get_node) + +    def get_hex(self): +        return
>     > '%032x' % self.int <http://self.int> + +    hex =
>     property(get_hex) + +    def
>     > get_urn(self): +        return 'urn:uuid:' + str(self) + +    urn =
>     > property(get_urn) + +    def get_variant(self): +        if not
>     > self.int <http://self.int> & (0x8000 << 48L): +
>      return RESERVED_NCS +
>     > elif not self.int <http://self.int> & (0x4000 << 48L): +
>        return RFC_4122 +
>     > elif not self.int <http://self.int> & (0x2000 << 48L): +
>        return
>     > RESERVED_MICROSOFT +        else: +            return
>     > RESERVED_FUTURE + +    variant = property(get_variant) + +    def
>     > get_version(self): +        # The version bits are only meaningful
>     > for RFC 4122 UUIDs. +        if self.variant == RFC_4122: +
>     > return int((self.int <http://self.int> >> 76L) & 0xf) + +
>      version =
>     > property(get_version) + +def _ifconfig_getnode(): +    """Get the
>     > hardware address on Unix by running ifconfig.""" +    import os +
>     > for dir in ['', '/sbin/', '/usr/sbin']: +        try: +
>     > pipe = os.popen(os.path.join(dir, 'ifconfig')) +        except
>     > IOError: +            continue +        for line in pipe: +
>     > words = line.lower().split() +            for i in
>     > range(len(words)): +                if words[i] in ['hwaddr',
>     > 'ether']: +                    return int(words[i + 1].replace(':',
>     > ''), 16) + +def _ipconfig_getnode(): +    """Get the hardware
>     > address on Windows by running ipconfig.exe.""" +    import os, re +
>     > dirs = ['', r'c:\windows\system32', r'c:\winnt\system32'] +
>     > try: +        import ctypes +        buffer =
>     > ctypes.create_string_buffer(300) +
>     > ctypes.windll.kernel32.GetSystemDirectoryA(buffer, 300) +
>     > dirs.insert(0, buffer.value.decode('mbcs')) +    except: +
>     > pass +    for dir in dirs: +        try: +            pipe =
>     > os.popen(os.path.join(dir, 'ipconfig') + ' /all') +        except
>     > IOError: +            continue +        for line in pipe: +
>     > value = line.split(':')[-1].strip().lower() +            if
>     > re.match('([0-9a-f][0-9a-f]-){5}[0-9a-f][0-9a-f]', value): +
>     > return int(value.replace('-', ''), 16) + +def _netbios_getnode(): +
>     > """Get the hardware address on Windows using NetBIOS calls. +
>     > See http://support.microsoft.com/kb/118623 for details.""" +
>     > import win32wnet, netbios +    ncb = netbios.NCB() +    ncb.Command
>     > = netbios.NCBENUM +    ncb.Buffer = adapters = netbios.LANA_ENUM()
>     > +    adapters._pack() +    if win32wnet.Netbios(ncb) != 0: +
>     > return +    adapters._unpack() +    for i in
>     > range(adapters.length): +        ncb.Reset() +        ncb.Command =
>     > netbios.NCBRESET +        ncb.Lana_num = ord(adapters.lana[i]) +
>     > if win32wnet.Netbios(ncb) != 0: +            continue +
>     > ncb.Reset() +        ncb.Command = netbios.NCBASTAT +
>     > ncb.Lana_num = ord(adapters.lana[i]) +        ncb.Callname =
>     > '*'.ljust(16) +        ncb.Buffer = status =
>     > netbios.ADAPTER_STATUS() +        if win32wnet.Netbios(ncb) != 0: +
>     > continue +        status._unpack() +        bytes = map(ord,
>     > status.adapter_address) +        return ((bytes[0]<<40L) +
>     > (bytes[1]<<32L) + (bytes[2]<<24L) + +
>     > (bytes[3]<<16L) + (bytes[4]<<8L) + bytes[5]) + +# Thanks to Thomas
>     > Heller for ctypes and for his help with its use here. + +# If
>     > ctypes is available, use it to find system routines for UUID
>     > generation. +_uuid_generate_random = _uuid_generate_time =
>     > _UuidCreate = None +try: +    import ctypes, ctypes.util +
>     > _buffer = ctypes.create_string_buffer(16) + +    # The
>     > uuid_generate_* routines are provided by libuuid on at least +    #
>     > Linux and FreeBSD, and provided by libc on Mac OS X. +    for
>     > libname in ['uuid', 'c']: +        try: +            lib =
>     > ctypes.CDLL(ctypes.util.find_library(libname)) +        except: +
>     > continue +        if hasattr(lib, 'uuid_generate_random'): +
>     > _uuid_generate_random = lib.uuid_generate_random +        if
>     > hasattr(lib, 'uuid_generate_time'): +
>     > _uuid_generate_time = lib.uuid_generate_time + +    # On Windows
>     > prior to 2000, UuidCreate gives a UUID containing the +    #
>     > hardware address.  On Windows 2000 and later, UuidCreate makes a +
>     > # random UUID and UuidCreateSequential gives a UUID containing the
>     > +    # hardware address.  These routines are provided by the RPC
>     > runtime. +    try: +        lib = ctypes.windll.rpcrt4 +
>     > except: +        lib = None +    _UuidCreate = getattr(lib,
>     > 'UuidCreateSequential', +                          getattr(lib,
>     > 'UuidCreate', None)) +except: +    pass + +def _unixdll_getnode():
>     > +    """Get the hardware address on Unix using ctypes.""" +
>     > _uuid_generate_time(_buffer) +    return
>     > UUID(bytes=_buffer.raw).node + +def _windll_getnode(): +    """Get
>     > the hardware address on Windows using ctypes.""" +    if
>     > _UuidCreate(_buffer) == 0: +        return
>     > UUID(bytes=_buffer.raw).node + +def _random_getnode(): +    """Get
>     > a random node ID, with eighth bit set as suggested by RFC 4122."""
>     > +    import random +    return random.randrange(0, 1<<48L) |
>     > 0x010000000000L + +_node = None + +def getnode(): +    """Get the
>     > hardware address as a 48-bit integer.  The first time this +
>     > runs, it may launch a separate program, which could be quite slow.
>     > If +    all attempts to obtain the hardware address fail, we choose
>     > a random +    48-bit number with its eighth bit set to 1 as
>     > recommended in RFC 4122.""" + +    global _node +    if _node is
>     > not None: +        return _node + +    import sys +    if
>     > sys.platform == 'win32': +        getters = [_windll_getnode,
>     > _netbios_getnode, _ipconfig_getnode] +    else: +        getters =
>     > [_unixdll_getnode, _ifconfig_getnode] + +    for getter in getters
>     > + [_random_getnode]: +        try: +            _node = getter() +
>     > except: +            continue +        if _node is not None: +
>     > return _node + +def uuid1(node=None, clock_seq=None): +
>     > """Generate a UUID from a host ID, sequence number, and the current
>     > time. +    If 'node' is not given, getnode() is used to obtain the
>     > hardware +    address.  If 'clock_seq' is given, it is used as the
>     > sequence number; +    otherwise a random 14-bit sequence number is
>     > chosen.""" + +    # When the system provides a version-1 UUID
>     > generator, use it (but don't +    # use UuidCreate here because its
>     > UUIDs don't conform to RFC 4122). +    if _uuid_generate_time and
>     > node is clock_seq is None: +        _uuid_generate_time(_buffer) +
>     > return UUID(bytes=_buffer.raw) + +    import time +    nanoseconds
>     > = int(time.time() * 1e9) +    # 0x01b21dd213814000 is the number of
>     > 100-ns intervals between the +    # UUID epoch 1582-10-15 00:00:00
>     > and the Unix epoch 1970-01-01 00:00:00. +    timestamp =
>     > int(nanoseconds/100) + 0x01b21dd213814000L +    if clock_seq is
>     > None: +        import random +        clock_seq =
>     > random.randrange(1<<14L) # instead of stable storage +    time_low
>     > = timestamp & 0xffffffffL +    time_mid = (timestamp >> 32L) &
>     > 0xffffL +    time_hi_version = (timestamp >> 48L) & 0x0fffL +
>     > clock_seq_low = clock_seq & 0xffL +    clock_seq_hi_variant =
>     > (clock_seq >> 8L) & 0x3fL +    if node is None: +        node =
>     > getnode() +    return UUID(fields=(time_low, time_mid,
>     > time_hi_version, +                        clock_seq_hi_variant,
>     > clock_seq_low, node), version=1) + +def uuid3(namespace, name): +
>     > """Generate a UUID from the MD5 hash of a namespace UUID and a
>     > name.""" +    import md5 +    hash = md5.md5(namespace.bytes +
>     > name).digest() +    return UUID(bytes=hash[:16], version=3) + +def
>     > uuid4(): +    """Generate a random UUID.""" + +    # When the
>     > system provides a version-4 UUID generator, use it. +    if
>     > _uuid_generate_random: +        _uuid_generate_random(_buffer) +
>     > return UUID(bytes=_buffer.raw) + +    # Otherwise, get randomness
>     > from urandom or the 'random' module. +    try: +        import os +
>     > return UUID(bytes=os.urandom(16), version=4) +    except: +
>     > import random +        bytes = [chr(random.randrange(256)) for i in
>     > range(16)] +        return UUID(bytes=bytes, version=4) + +def
>     > uuid5(namespace, name): +    """Generate a UUID from the SHA-1 hash
>     > of a namespace UUID and a name.""" +    import sha +    hash =
>     > sha.sha(namespace.bytes + name).digest() +    return
>     > UUID(bytes=hash[:16], version=5) + +# The following standard UUIDs
>     > are for use with uuid3() or uuid5(). + +NAMESPACE_DNS =
>     > UUID('6ba7b810-9dad-11d1-80b4-00c04fd430c8') +NAMESPACE_URL =
>     > UUID('6ba7b811-9dad-11d1-80b4-00c04fd430c8') +NAMESPACE_OID =
>     > UUID('6ba7b812-9dad-11d1-80b4-00c04fd430c8') +NAMESPACE_X500 =
>     > UUID('6ba7b814-9dad-11d1-80b4-00c04fd430c8')
>
>
>     - --
>     Anish Mangal
>     Dextrose Project Manager
>     Activity Central
>     -----BEGIN PGP SIGNATURE-----
>     Version: GnuPG v1.4.12 (GNU/Linux)
>     Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
>
>     iQEcBAEBAgAGBQJPoXL+AAoJEBoxUdDHDZVpEIIIAIpb3QZlLpNpQU/+zC3aUbgf
>     aEKpRhUHCEZ6Yl0pMhLIT7jo++cYtHkAjElPhDEaffrkFURDKZMcbNGKSS2p8EVL
>     Xf7ax9Erg1ruVTz90xQ1gADld2WANU6rFea8+6KxfSe/JQkVgs2QNy2IVwkgq9KS
>     iFnk4o8aPjVz1H0EriEBTc5t7tIkpFOrKqIMCjHsCELZZ7ItpMeFG7vL66m35zC2
>     PeRzn3NcA6fktuihYyl/dGqAwUgogXBSY1AzxHKB6lc79OJaQS8bQOhIpSldycV6
>     0oo5yv8+0P53+Ops6jhQIaekFO2lUxWklhoWTEvlFP6vCf7sBUJ+dA8qjyFPx+o=
>     =nY7E
>     -----END PGP SIGNATURE-----
>
>



- --
Anish Mangal
Dextrose Project Manager
Activity Central
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.12 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iQEcBAEBAgAGBQJPoXrnAAoJEBoxUdDHDZVphpoH/RfGDj7mu4AbxhJfQv2GgguZ
AOu3RApzUL+hUXIwGCyncJXkDLgQMHwJEJWFzTwiwlaOza6xQMGPFXLkknzAOG+i
lcgFqYMUybdx8vEABnBzTQxfIe0UAVGiUpr5yswAvc0OuiUgYM29MZ1L1jBk7bGR
udTgplEspepxnOepixGtd1rnb+aHpTx5KksqKGczhhdT4dPjQYJl1CDYK2QB7gLb
gMG2CrAaFuAnnt5I+gyrMD/n7Y7fuoSFFSyBI1fsaXI0llbX6uTGtiefG+hOHnFo
uNDRLvRKrJEC7EvSmr+cRZ2e/NFRXvnj9qlIZiCWQRm4sE3k+3jc2g39Oco7D4g=
=/tXB
-----END PGP SIGNATURE-----



More information about the Dextrose mailing list