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