# This is a shell archive. Save it in a file, remove anything before # this line, and then unpack it by entering "sh file". Note, it may # create directories; files and directories will be owned by you and # have default permissions. # # This archive contains: # # py-seafdav # py-seafdav/Makefile # py-seafdav/distinfo # py-seafdav/pkg-descr # py-seafdav/pkg-plist # py-seafdav/work # py-seafdav/work/.stage_done.seafdav._usr_local # py-seafdav/work/.license_done.seafdav._usr_local # py-seafdav/work/.PLIST.mktmp # py-seafdav/work/haiwen-seafdav-42dfd99 # py-seafdav/work/haiwen-seafdav-42dfd99/.project # py-seafdav/work/haiwen-seafdav-42dfd99/.gitignore # py-seafdav/work/haiwen-seafdav-42dfd99/run.sh.template # py-seafdav/work/haiwen-seafdav-42dfd99/.pydevproject # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/dir_browser.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/xml_tools.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/version.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/property_manager.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/util.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/rw_lock.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/error_printer.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/request_resolver.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/http_authenticator.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/dav_provider_tools.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/__init__.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/mongo_dav_provider.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/virtual_dav_provider.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/dav_error.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/fs_dav_provider.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/debug_filter.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/__init__.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/wsgidav_app.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/domain_controller.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/dav_provider.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/dav_provider_interface.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/propertymanagerinterface.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/domaincontrollerinterface.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/lockmanagerinterface.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/lock_storage.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/request_server.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/lock_manager.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/__init__.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/mongo_property_manager.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/hg_dav_provider.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/nt_domain_controller.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/couch_property_manager.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/seafile_dav_provider.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/domain_controller.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/__init__.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/seaf_utils.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/mysql_dav_provider.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/__init__.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/cherrypy_wsgiserver # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/cherrypy_wsgiserver/__init__.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/cherrypy_wsgiserver/LICENSE.txt # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/settings.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/sample_wsgidav.conf # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/server_sample.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/ext_wsgiutils_server.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/run_reloading_server.py # py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/run_server.py # py-seafdav/work/haiwen-seafdav-42dfd99/Makefile # py-seafdav/work/haiwen-seafdav-42dfd99/README.md # py-seafdav/work/.license-catalog.mk # py-seafdav/work/.patch_done.seafdav._usr_local # py-seafdav/work/.configure_done.seafdav._usr_local # py-seafdav/work/.extract_done.seafdav._usr_local # py-seafdav/work/stage # py-seafdav/work/stage/usr # py-seafdav/work/stage/usr/local # py-seafdav/work/stage/usr/local/lib # py-seafdav/work/stage/usr/local/lib/python2.7 # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.py # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyo # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.pyo # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.py # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyo # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyo # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyc # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.py # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.pyc # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyc # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.py # py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyc # py-seafdav/work/stage/usr/local/lib/X11 # py-seafdav/work/stage/usr/local/lib/X11/fonts # py-seafdav/work/stage/usr/local/lib/X11/fonts/local # py-seafdav/work/stage/usr/local/lib/X11/app-defaults # py-seafdav/work/stage/usr/local/info # py-seafdav/work/stage/usr/local/www # py-seafdav/work/stage/usr/local/etc # py-seafdav/work/stage/usr/local/etc/rc.d # py-seafdav/work/stage/usr/local/etc/devd # py-seafdav/work/stage/usr/local/etc/newsyslog.conf.d # py-seafdav/work/stage/usr/local/etc/pam.d # py-seafdav/work/stage/usr/local/etc/man.d # py-seafdav/work/stage/usr/local/libexec # py-seafdav/work/stage/usr/local/bin # py-seafdav/work/stage/usr/local/libdata # py-seafdav/work/stage/usr/local/libdata/pkgconfig # py-seafdav/work/stage/usr/local/libdata/ldconfig # py-seafdav/work/stage/usr/local/libdata/ldconfig32 # py-seafdav/work/stage/usr/local/tests # py-seafdav/work/stage/usr/local/sbin # py-seafdav/work/stage/usr/local/include # py-seafdav/work/stage/usr/local/include/X11 # py-seafdav/work/stage/usr/local/man # py-seafdav/work/stage/usr/local/man/man3 # py-seafdav/work/stage/usr/local/man/man9 # py-seafdav/work/stage/usr/local/man/cat4 # py-seafdav/work/stage/usr/local/man/catn # py-seafdav/work/stage/usr/local/man/mann # py-seafdav/work/stage/usr/local/man/cat9 # py-seafdav/work/stage/usr/local/man/cat3 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat1aout # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat2 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat8 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat8/i386 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat5 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat1 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat6 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/catn # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat4 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat4/i386 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat9 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat9/i386 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat3 # py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat7 # py-seafdav/work/stage/usr/local/man/man4 # py-seafdav/work/stage/usr/local/man/cat7 # py-seafdav/work/stage/usr/local/man/ja # py-seafdav/work/stage/usr/local/man/ja/cat6 # py-seafdav/work/stage/usr/local/man/ja/man1 # py-seafdav/work/stage/usr/local/man/ja/catl # py-seafdav/work/stage/usr/local/man/ja/manl # py-seafdav/work/stage/usr/local/man/ja/man6 # py-seafdav/work/stage/usr/local/man/ja/cat1 # py-seafdav/work/stage/usr/local/man/ja/man8 # py-seafdav/work/stage/usr/local/man/ja/man2 # py-seafdav/work/stage/usr/local/man/ja/cat5 # py-seafdav/work/stage/usr/local/man/ja/cat2 # py-seafdav/work/stage/usr/local/man/ja/cat8 # py-seafdav/work/stage/usr/local/man/ja/man5 # py-seafdav/work/stage/usr/local/man/ja/man7 # py-seafdav/work/stage/usr/local/man/ja/cat7 # py-seafdav/work/stage/usr/local/man/ja/cat9 # py-seafdav/work/stage/usr/local/man/ja/cat3 # py-seafdav/work/stage/usr/local/man/ja/man4 # py-seafdav/work/stage/usr/local/man/ja/mann # py-seafdav/work/stage/usr/local/man/ja/catn # py-seafdav/work/stage/usr/local/man/ja/man3 # py-seafdav/work/stage/usr/local/man/ja/man9 # py-seafdav/work/stage/usr/local/man/ja/cat4 # py-seafdav/work/stage/usr/local/man/man7 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man5 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat8 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat2 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat5 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man2 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man8 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat1 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man6 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/manl # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/catl # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man1 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat6 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat4 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man9 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man3 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/catn # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/mann # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man4 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat3 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat9 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat7 # py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man7 # py-seafdav/work/stage/usr/local/man/cat2 # py-seafdav/work/stage/usr/local/man/cat8 # py-seafdav/work/stage/usr/local/man/man5 # py-seafdav/work/stage/usr/local/man/man8 # py-seafdav/work/stage/usr/local/man/man2 # py-seafdav/work/stage/usr/local/man/cat5 # py-seafdav/work/stage/usr/local/man/man6 # py-seafdav/work/stage/usr/local/man/cat1 # py-seafdav/work/stage/usr/local/man/manl # py-seafdav/work/stage/usr/local/man/catl # py-seafdav/work/stage/usr/local/man/ru.KOI8-R # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat6 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man1 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/catl # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/manl # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man6 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat1 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man8 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man2 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat5 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat2 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat8 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man5 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man7 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat7 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat9 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat3 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man4 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/mann # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/catn # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man3 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man9 # py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat4 # py-seafdav/work/stage/usr/local/man/cat6 # py-seafdav/work/stage/usr/local/man/man1 # py-seafdav/work/stage/usr/local/share # py-seafdav/work/stage/usr/local/share/emacs # py-seafdav/work/stage/usr/local/share/emacs/site-lisp # py-seafdav/work/stage/usr/local/share/locale # py-seafdav/work/stage/usr/local/share/locale/cs # py-seafdav/work/stage/usr/local/share/locale/cs/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/cy # py-seafdav/work/stage/usr/local/share/locale/cy/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/en_CA # py-seafdav/work/stage/usr/local/share/locale/en_CA/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/uz # py-seafdav/work/stage/usr/local/share/locale/uz/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/lt # py-seafdav/work/stage/usr/local/share/locale/lt/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/bn # py-seafdav/work/stage/usr/local/share/locale/bn/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/tg # py-seafdav/work/stage/usr/local/share/locale/tg/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ar # py-seafdav/work/stage/usr/local/share/locale/ar/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/sr@Latn # py-seafdav/work/stage/usr/local/share/locale/sr@Latn/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/fa # py-seafdav/work/stage/usr/local/share/locale/fa/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/af # py-seafdav/work/stage/usr/local/share/locale/af/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/he # py-seafdav/work/stage/usr/local/share/locale/he/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/es_ES # py-seafdav/work/stage/usr/local/share/locale/es_ES/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/fr # py-seafdav/work/stage/usr/local/share/locale/fr/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/en # py-seafdav/work/stage/usr/local/share/locale/en/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/nl # py-seafdav/work/stage/usr/local/share/locale/nl/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/mn # py-seafdav/work/stage/usr/local/share/locale/mn/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/mt # py-seafdav/work/stage/usr/local/share/locale/mt/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ga # py-seafdav/work/stage/usr/local/share/locale/ga/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/nb # py-seafdav/work/stage/usr/local/share/locale/nb/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/gu # py-seafdav/work/stage/usr/local/share/locale/gu/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/zh_TW.Big5 # py-seafdav/work/stage/usr/local/share/locale/zh_TW.Big5/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/bg # py-seafdav/work/stage/usr/local/share/locale/bg/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/li # py-seafdav/work/stage/usr/local/share/locale/li/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/kn # py-seafdav/work/stage/usr/local/share/locale/kn/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/zh_TW # py-seafdav/work/stage/usr/local/share/locale/zh_TW/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/pa # py-seafdav/work/stage/usr/local/share/locale/pa/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/et # py-seafdav/work/stage/usr/local/share/locale/et/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/bs # py-seafdav/work/stage/usr/local/share/locale/bs/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/hr # py-seafdav/work/stage/usr/local/share/locale/hr/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/hu # py-seafdav/work/stage/usr/local/share/locale/hu/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/or # py-seafdav/work/stage/usr/local/share/locale/or/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/wa # py-seafdav/work/stage/usr/local/share/locale/wa/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/es # py-seafdav/work/stage/usr/local/share/locale/es/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/pl # py-seafdav/work/stage/usr/local/share/locale/pl/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/zh_CN # py-seafdav/work/stage/usr/local/share/locale/zh_CN/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/no # py-seafdav/work/stage/usr/local/share/locale/no/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/gl # py-seafdav/work/stage/usr/local/share/locale/gl/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ne # py-seafdav/work/stage/usr/local/share/locale/ne/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ms # py-seafdav/work/stage/usr/local/share/locale/ms/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/es_MX # py-seafdav/work/stage/usr/local/share/locale/es_MX/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/de_AT # py-seafdav/work/stage/usr/local/share/locale/de_AT/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ro # py-seafdav/work/stage/usr/local/share/locale/ro/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/it # py-seafdav/work/stage/usr/local/share/locale/it/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ca # py-seafdav/work/stage/usr/local/share/locale/ca/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/en_GB # py-seafdav/work/stage/usr/local/share/locale/en_GB/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/sr # py-seafdav/work/stage/usr/local/share/locale/sr/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/hi # py-seafdav/work/stage/usr/local/share/locale/hi/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ta # py-seafdav/work/stage/usr/local/share/locale/ta/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/fr_FR # py-seafdav/work/stage/usr/local/share/locale/fr_FR/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/sl # py-seafdav/work/stage/usr/local/share/locale/sl/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/tk # py-seafdav/work/stage/usr/local/share/locale/tk/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ka # py-seafdav/work/stage/usr/local/share/locale/ka/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/eo # py-seafdav/work/stage/usr/local/share/locale/eo/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ee # py-seafdav/work/stage/usr/local/share/locale/ee/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/en_AU # py-seafdav/work/stage/usr/local/share/locale/en_AU/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/be # py-seafdav/work/stage/usr/local/share/locale/be/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/zh # py-seafdav/work/stage/usr/local/share/locale/zh/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/sk # py-seafdav/work/stage/usr/local/share/locale/sk/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/am # py-seafdav/work/stage/usr/local/share/locale/am/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/tr # py-seafdav/work/stage/usr/local/share/locale/tr/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/da # py-seafdav/work/stage/usr/local/share/locale/da/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/dk # py-seafdav/work/stage/usr/local/share/locale/dk/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/is # py-seafdav/work/stage/usr/local/share/locale/is/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/uk # py-seafdav/work/stage/usr/local/share/locale/uk/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/nn # py-seafdav/work/stage/usr/local/share/locale/nn/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/vi # py-seafdav/work/stage/usr/local/share/locale/vi/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ja # py-seafdav/work/stage/usr/local/share/locale/ja/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/de # py-seafdav/work/stage/usr/local/share/locale/de/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ml # py-seafdav/work/stage/usr/local/share/locale/ml/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/th # py-seafdav/work/stage/usr/local/share/locale/th/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/fa_IR # py-seafdav/work/stage/usr/local/share/locale/fa_IR/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/sq # py-seafdav/work/stage/usr/local/share/locale/sq/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/el # py-seafdav/work/stage/usr/local/share/locale/el/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/pt # py-seafdav/work/stage/usr/local/share/locale/pt/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ko # py-seafdav/work/stage/usr/local/share/locale/ko/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/lv # py-seafdav/work/stage/usr/local/share/locale/lv/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/br # py-seafdav/work/stage/usr/local/share/locale/br/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/eu # py-seafdav/work/stage/usr/local/share/locale/eu/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/pt_PT # py-seafdav/work/stage/usr/local/share/locale/pt_PT/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/sv # py-seafdav/work/stage/usr/local/share/locale/sv/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/fi # py-seafdav/work/stage/usr/local/share/locale/fi/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/az # py-seafdav/work/stage/usr/local/share/locale/az/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/pt_BR # py-seafdav/work/stage/usr/local/share/locale/pt_BR/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/mk # py-seafdav/work/stage/usr/local/share/locale/mk/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/id # py-seafdav/work/stage/usr/local/share/locale/id/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/zh_CN.GB2312 # py-seafdav/work/stage/usr/local/share/locale/zh_CN.GB2312/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/locale/ru # py-seafdav/work/stage/usr/local/share/locale/ru/LC_MESSAGES # py-seafdav/work/stage/usr/local/share/java # py-seafdav/work/stage/usr/local/share/java/classes # py-seafdav/work/stage/usr/local/share/licenses # py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4 # py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4/LICENSE # py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4/catalog.mk # py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4/APACHE20 # py-seafdav/work/stage/usr/local/share/xml # py-seafdav/work/stage/usr/local/share/aclocal # py-seafdav/work/stage/usr/local/share/applications # py-seafdav/work/stage/usr/local/share/doc # py-seafdav/work/stage/usr/local/share/doc/ja # py-seafdav/work/stage/usr/local/share/misc # py-seafdav/work/stage/usr/local/share/skel # py-seafdav/work/stage/usr/local/share/examples # py-seafdav/work/stage/usr/local/share/pixmaps # py-seafdav/work/stage/usr/local/share/nls # py-seafdav/work/stage/usr/local/share/nls/ru_RU.ISO8859-5 # py-seafdav/work/stage/usr/local/share/nls/he_IL.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/en_GB.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/en_US.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/be_BY.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/en_NZ.US-ASCII # py-seafdav/work/stage/usr/local/share/nls/fi_FI.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/sl_SI.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/es_ES.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/it_IT.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/et_EE.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/en_AU.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/zh_CN.eucCN # py-seafdav/work/stage/usr/local/share/nls/en_IE.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/zh_HK.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/hy_AM.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/kk_KZ.PT154 # py-seafdav/work/stage/usr/local/share/nls/ca_ES.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/de_DE.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/sr_YU.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/ru_RU.CP866 # py-seafdav/work/stage/usr/local/share/nls/pt_PT.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/cs_CZ.ISO8859-2 # py-seafdav/work/stage/usr/local/share/nls/fi_FI.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/zh_CN.GB2312 # py-seafdav/work/stage/usr/local/share/nls/ja_JP.eucJP # py-seafdav/work/stage/usr/local/share/nls/en_CA.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/ca_ES.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/en_GB.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/de_DE.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/en_US.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/en_US.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/en_GB.US-ASCII # py-seafdav/work/stage/usr/local/share/nls/fr_CH.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/en_CA.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/el_GR.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/kk_KZ.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/it_CH.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/el_GR.ISO8859-7 # py-seafdav/work/stage/usr/local/share/nls/fr_FR.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/pl_PL.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/uk_UA.KOI8-U # py-seafdav/work/stage/usr/local/share/nls/pt_PT.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/lt_LT.ISO8859-4 # py-seafdav/work/stage/usr/local/share/nls/ko_KR.eucKR # py-seafdav/work/stage/usr/local/share/nls/fr_FR.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/en_AU.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/et_EE.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/sv_SE.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/ro_RO.ISO8859-2 # py-seafdav/work/stage/usr/local/share/nls/de_CH.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/da_DK.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/ru_RU.CP1251 # py-seafdav/work/stage/usr/local/share/nls/de_CH.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/sk_SK.ISO8859-2 # py-seafdav/work/stage/usr/local/share/nls/en_NZ.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/pt_BR.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/sk_SK.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/es_ES.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/ru_RU.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/af_ZA.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/ko_KR.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/en_AU.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/pl_PL.ISO8859-2 # py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-4 # py-seafdav/work/stage/usr/local/share/nls/no_NO.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/nl_BE.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/zh_HK.Big5HKSCS # py-seafdav/work/stage/usr/local/share/nls/sl_SI.ISO8859-2 # py-seafdav/work/stage/usr/local/share/nls/es_ES.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/da_DK.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/fr_BE.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/de_CH.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/hr_HR.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/uk_UA.ISO8859-5 # py-seafdav/work/stage/usr/local/share/nls/pt_PT.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/ko_KR.CP949 # py-seafdav/work/stage/usr/local/share/nls/en_AU.US-ASCII # py-seafdav/work/stage/usr/local/share/nls/bg_BG.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/fr_CA.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/af_ZA.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/lt_LT.ISO8859-13 # py-seafdav/work/stage/usr/local/share/nls/nl_BE.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/it_CH.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/fr_CA.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/cs_CZ.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/is_IS.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/ja_JP.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/hy_AM.ARMSCII-8 # py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/am_ET.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/lt_LT.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/hr_HR.ISO8859-2 # py-seafdav/work/stage/usr/local/share/nls/ca_ES.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/hu_HU.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/en_NZ.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/da_DK.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/de_AT.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/hu_HU.ISO8859-2 # py-seafdav/work/stage/usr/local/share/nls/nl_BE.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/zh_CN.GB18030 # py-seafdav/work/stage/usr/local/share/nls/bg_BG.CP1251 # py-seafdav/work/stage/usr/local/share/nls/fr_CA.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/en_CA.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/zh_TW.Big5 # py-seafdav/work/stage/usr/local/share/nls/fr_BE.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/de_DE.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/fr_BE.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/de_AT.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/nl_NL.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/af_ZA.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/sr_YU.ISO8859-5 # py-seafdav/work/stage/usr/local/share/nls/hi_IN.ISCII-DEV # py-seafdav/work/stage/usr/local/share/nls/en_GB.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/it_IT.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/no_NO.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/fr_CH.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/is_IS.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/zh_CN.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/pt_BR.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/fi_FI.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/tr_TR.ISO8859-9 # py-seafdav/work/stage/usr/local/share/nls/be_BY.CP1131 # py-seafdav/work/stage/usr/local/share/nls/sv_SE.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/nl_NL.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/C # py-seafdav/work/stage/usr/local/share/nls/be_BY.ISO8859-5 # py-seafdav/work/stage/usr/local/share/nls/ro_RO.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-2 # py-seafdav/work/stage/usr/local/share/nls/en_CA.US-ASCII # py-seafdav/work/stage/usr/local/share/nls/ru_RU.KOI8-R # py-seafdav/work/stage/usr/local/share/nls/zh_CN.GBK # py-seafdav/work/stage/usr/local/share/nls/sv_SE.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/ja_JP.SJIS # py-seafdav/work/stage/usr/local/share/nls/it_IT.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/en_NZ.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/de_AT.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/no_NO.ISO8859-15 # py-seafdav/work/stage/usr/local/share/nls/it_CH.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/sr_YU.ISO8859-2 # py-seafdav/work/stage/usr/local/share/nls/is_IS.ISO8859-1 # py-seafdav/work/stage/usr/local/share/nls/be_BY.CP1251 # py-seafdav/work/stage/usr/local/share/nls/zh_TW.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/la_LN.US-ASCII # py-seafdav/work/stage/usr/local/share/nls/nl_NL.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/tr_TR.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/fr_FR.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/uk_UA.UTF-8 # py-seafdav/work/stage/usr/local/share/nls/fr_CH.ISO8859-1 # py-seafdav/work/stage/usr/local/share/dict # py-seafdav/work/stage/usr/local/share/sgml # py-seafdav/work/.license-report # py-seafdav/work/.build_done.seafdav._usr_local # echo c - py-seafdav mkdir -p py-seafdav > /dev/null 2>&1 echo x - py-seafdav/Makefile sed 's/^X//' >py-seafdav/Makefile << 'd14583c93f4bd1203b5ffe5b1b245d3d' X# $FreeBSD$ X XPORTNAME= seafdav XPORTVERSION= 3.0.4 XCATEGORIES= www python XPKGNAMEPREFIX= ${PYTHON_PKGNAMEPREFIX} X XMAINTAINER= yan_jingfeng@yahoo.com XCOMMENT= Python library for accessing seafile data model X XLICENSE= APACHE20 X XRUN_DEPENDS= ${PYTHON_PKGNAMEPREFIX}WsgiDAV>=0.5:${PORTSDIR}/www/py-wsgidav X XUSE_GITHUB= yes XGH_ACCOUNT= haiwen XGH_TAGNAME= v${PORTNAME}-server-testing XGH_COMMIT= 42dfd99 X XNO_BUILD= yes X XUSES= python:2 X Xdo-install: X @${ECHO_MSG} "install script ... " X @${MKDIR} ${STAGEDIR}${PYTHON_SITELIBDIR}/wsgidav/addons/seafile X @cd ${WRKSRC}/wsgidav/addons/seafile && \ X ${COPYTREE_SHARE} . ${STAGEDIR}${PYTHON_SITELIBDIR}/wsgidav/addons/seafile X Xpost-install: X (cd ${STAGEDIR}${PREFIX} && \ X ${PYTHON_CMD} ${PYTHON_LIBDIR}/compileall.py \ X -d ${PYTHONPREFIX_SITELIBDIR} -f ${PYTHONPREFIX_SITELIBDIR:S;${PREFIX}/;;} && \ X ${PYTHON_CMD} -O ${PYTHON_LIBDIR}/compileall.py \ X -d ${PYTHONPREFIX_SITELIBDIR} -f ${PYTHONPREFIX_SITELIBDIR:S;${PREFIX}/;;}) X X.include d14583c93f4bd1203b5ffe5b1b245d3d echo x - py-seafdav/distinfo sed 's/^X//' >py-seafdav/distinfo << 'bcdd1d2931f5c44f5bdba75f42e1cdf8' XSHA256 (seafdav-3.0.4.tar.gz) = d938f3324223f09c7466b8028505e0aeb4d5e8b63caa96c0811e775f8416e55b XSIZE (seafdav-3.0.4.tar.gz) = 134684 bcdd1d2931f5c44f5bdba75f42e1cdf8 echo x - py-seafdav/pkg-descr sed 's/^X//' >py-seafdav/pkg-descr << '70cb34a02591e000405a4fdae47872d0' XThis addon provide seafile implementation for Web DAV server by ultilizaing XWsgiDAV interface. X XWWW: https://github.com/haiwen/seafdav 70cb34a02591e000405a4fdae47872d0 echo x - py-seafdav/pkg-plist sed 's/^X//' >py-seafdav/pkg-plist << 'a6f24ecc8941957825d75d6f99cd64d1' X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/__init__.py X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/__init__.pyc X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/__init__.pyo X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/domain_controller.py X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/domain_controller.pyc X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/domain_controller.pyo X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/seaf_utils.py X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/seaf_utils.pyc X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/seaf_utils.pyo X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/seafile_dav_provider.py X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/seafile_dav_provider.pyc X%%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile/seafile_dav_provider.pyo X@dirrmtry %%PYTHON_SITELIBDIR%%/wsgidav/addons/seafile a6f24ecc8941957825d75d6f99cd64d1 echo c - py-seafdav/work mkdir -p py-seafdav/work > /dev/null 2>&1 echo x - py-seafdav/work/.stage_done.seafdav._usr_local sed 's/^X//' >py-seafdav/work/.stage_done.seafdav._usr_local << '1beee489eec96e810f156b0825023ab5' 1beee489eec96e810f156b0825023ab5 echo x - py-seafdav/work/.license_done.seafdav._usr_local sed 's/^X//' >py-seafdav/work/.license_done.seafdav._usr_local << '1598a1ea8249a703129228d05501fa0d' 1598a1ea8249a703129228d05501fa0d echo x - py-seafdav/work/.PLIST.mktmp sed 's/^X//' >py-seafdav/work/.PLIST.mktmp << '14abc6e63cd813b415bfe43ee8940aa0' X@owner root X@group wheel Xshare/licenses/py27-seafdav-3.0.4/catalog.mk Xshare/licenses/py27-seafdav-3.0.4/LICENSE Xshare/licenses/py27-seafdav-3.0.4/APACHE20 Xlib/python2.7/site-packages/wsgidav/addons/seafile/__init__.py Xlib/python2.7/site-packages/wsgidav/addons/seafile/__init__.pyc Xlib/python2.7/site-packages/wsgidav/addons/seafile/__init__.pyo Xlib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.py Xlib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyc Xlib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyo Xlib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.py Xlib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyc Xlib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyo Xlib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.py Xlib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyc Xlib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyo X@dirrmtry lib/python2.7/site-packages/wsgidav/addons/seafile X@owner root X@group wheel X@cwd /usr/local X@dirrm share/licenses/py27-seafdav-3.0.4 X@unexec rmdir %D/share/licenses 2>/dev/null || true 14abc6e63cd813b415bfe43ee8940aa0 echo c - py-seafdav/work/haiwen-seafdav-42dfd99 mkdir -p py-seafdav/work/haiwen-seafdav-42dfd99 > /dev/null 2>&1 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/.project sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/.project << '7078c687fe0918d69ca5cdf96e1d2355' X X X wsgidav X X X X X X org.python.pydev.PyDevBuilder X X X X X X org.python.pydev.pythonNature X X X X todo_wsgidav.txt X 1 X DROPBOX/todo_wsgidav.txt X X X 7078c687fe0918d69ca5cdf96e1d2355 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/.gitignore sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/.gitignore << 'bdfefcc065dd6c5ef85626555e634846' X*.pyc X*.pydevproject X*~ X*# X*.log X*.gz Xbuild/ Xdist/ Xrun.sh bdfefcc065dd6c5ef85626555e634846 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/run.sh.template sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/run.sh.template << '9ad12651ee663eea01882fbbf1eede9f' X#!/bin/bash X Xexport CCNET_CONF_DIR=/data/data/ccnet Xexport SEAFILE_CONF_DIR=/data/data/seafile-data X XTOP_DIR=$(python -c "import os; print os.path.dirname(os.path.realpath('$0'))") X Xcd "$TOP_DIR" X Xpython -m wsgidav.server.run_server 9ad12651ee663eea01882fbbf1eede9f echo x - py-seafdav/work/haiwen-seafdav-42dfd99/.pydevproject sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/.pydevproject << 'b4687abb3767303efc67e7ef8442cc8f' X X X X Xpython 2.4 X X/wsgidav X XDefault X XC:\apps\Python26\Lib\site-packages\CouchDB-0.8-py2.6.egg XC:\apps\Python26\Lib\site-packages\pymongo-1.9-py2.6-win32.egg X X b4687abb3767303efc67e7ef8442cc8f echo c - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav mkdir -p py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav > /dev/null 2>&1 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/dir_browser.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/dir_browser.py << 'e51d911c36dbc72b39d9dc8d3990b019' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XWSGI middleware that handles GET requests on collections to display directories. X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom wsgidav.dav_error import DAVError, HTTP_OK, HTTP_MEDIATYPE_NOT_SUPPORTED Xfrom wsgidav.version import __version__ Ximport sys Ximport urllib Ximport util X X__docformat__ = "reStructuredText" X X Xclass WsgiDavDirBrowser(object): X """WSGI middleware that handles GET requests on collections to display directories.""" X X def __init__(self, application): X self._application = application X self._verbose = 2 X X def __call__(self, environ, start_response): X path = environ["PATH_INFO"] X X davres = None X if environ["wsgidav.provider"]: X davres = environ["wsgidav.provider"].getResourceInst(path, environ) X X if environ["REQUEST_METHOD"] in ("GET", "HEAD") and davres and davres.isCollection: X X# if "mozilla" not in environ.get("HTTP_USER_AGENT").lower(): X# # issue 14: Nautilus sends GET on collections X# # http://code.google.com/p/wsgidav/issues/detail?id=14 X# util.status("Directory browsing disabled for agent '%s'" % environ.get("HTTP_USER_AGENT")) X# self._fail(HTTP_NOT_IMPLEMENTED) X# return self._application(environ, start_response) X X if util.getContentLength(environ) != 0: X self._fail(HTTP_MEDIATYPE_NOT_SUPPORTED, X "The server does not handle any body content.") X X if environ["REQUEST_METHOD"] == "HEAD": X return util.sendStatusResponse(environ, start_response, HTTP_OK) X X # Support DAV mount (http://www.ietf.org/rfc/rfc4709.txt) X dirConfig = environ["wsgidav.config"].get("dir_browser", {}) X if dirConfig.get("davmount") and "davmount" in environ.get("QUERY_STRING"): X# collectionUrl = davres.getHref() X collectionUrl = util.makeCompleteUrl(environ) X collectionUrl = collectionUrl.split("?")[0] X res = """ X X %s X """ % (collectionUrl) X # TODO: support %s X X start_response("200 OK", [("Content-Type", "application/davmount+xml"), X ("Content-Length", str(len(res))), X ("Cache-Control", "private"), X ("Date", util.getRfc1123Time()), X ]) X return [ res ] X X # Profile calls X# if True: X# from cProfile import Profile X# profile = Profile() X# profile.runcall(self._listDirectory, environ, start_response) X# # sort: 0:"calls",1:"time", 2: "cumulative" X# profile.print_stats(sort=2) X return self._listDirectory(davres, environ, start_response) X X return self._application(environ, start_response) X X X def _fail(self, value, contextinfo=None, srcexception=None, errcondition=None): X """Wrapper to raise (and log) DAVError.""" X e = DAVError(value, contextinfo, srcexception, errcondition) X if self._verbose >= 2: X print >>sys.stdout, "Raising DAVError %s" % e.getUserInfo() X raise e X X X def _listDirectory(self, davres, environ, start_response): X """ X @see: http://www.webdav.org/specs/rfc4918.html#rfc.section.9.4 X """ X assert davres.isCollection X X dirConfig = environ["wsgidav.config"].get("dir_browser", {}) X displaypath = urllib.unquote(davres.getHref()) X X trailer = dirConfig.get("response_trailer") X if trailer: X trailer = trailer.replace("${version}", X "WsgiDAV/%s" % __version__) X trailer = trailer.replace("${time}", util.getRfc1123Time()) X else: X trailer = ("WsgiDAV/%s - %s" X % (__version__, util.getRfc1123Time())) X X X html = [] X html.append(""); X html.append("") X html.append("") X html.append("") X html.append("" % __version__) X html.append("WsgiDAV - Index of %s " % displaypath) X X html.append("""\ X""") X # Special CSS to enable MS Internet Explorer behaviour X if dirConfig.get("msmount"): X html.append("""\ X""") X X html.append("") X X # Title X html.append("

Index of %s

" % displaypath) X # Add DAV-Mount link and Web-Folder link X links = [] X if dirConfig.get("davmount"): X links.append("Mount" % util.makeCompleteUrl(environ)) X if dirConfig.get("msmount"): X links.append("Open as Web Folder" % util.makeCompleteUrl(environ)) X# html.append("Open setup.py as WebDAV" % util.makeCompleteUrl(environ)) X if links: X html.append("

%s

" % " – ".join(links)) X X html.append("
") X # Listing X html.append("") X X html.append("") X html.append("") X html.append("") X X html.append("") X if davres.path in ("", "/"): X html.append("") X else: X parentUrl = util.getUriParent(davres.getHref()) X html.append("") X X # Ask collection for member info list X dirInfoList = davres.getDirectoryInfo() X X if dirInfoList is None: X # No pre-build info: traverse members X dirInfoList = [] X childList = davres.getDescendants(depth="1", addSelf=False) X for res in childList: X di = res.getDisplayInfo() X infoDict = {"href": res.getHref(), X "displayName": res.getDisplayName(), X "lastModified": res.getLastModified(), X "isCollection": res.isCollection, X "contentLength": res.getContentLength(), X "displayType": di.get("type"), X "displayTypeComment": di.get("typeComment"), X } X dirInfoList.append(infoDict) X # X for infoDict in dirInfoList: X lastModified = infoDict.get("lastModified") X if lastModified is None: X infoDict["strModified"] = "" X else: X infoDict["strModified"] = util.getRfc1123Time(lastModified) X X infoDict["strSize"] = "-" X if not infoDict.get("isCollection"): X contentLength = infoDict.get("contentLength") X if contentLength is not None: X infoDict["strSize"] = util.byteNumberString(contentLength) X X html.append("""\ X X X X """ % infoDict) X X html.append("") X html.append("
Name Type Size Last modified
Top level share
Parent Directory
%(displayName)s%(displayType)s%(strSize)s%(strModified)s
") X X html.append("
") X X if "http_authenticator.username" in environ: X if environ.get("http_authenticator.username"): X html.append("

Authenticated user: '%s', realm: '%s'.

" X % (environ.get("http_authenticator.username"), X environ.get("http_authenticator.realm"))) X# else: X# html.append("

Anonymous

") X X if trailer: X html.append("

%s

" % trailer) X# html.append("

WsgiDAV/%s - %s

" X# % (__version__, util.getRfc1123Time())) X X html.append("") X X body = "\n".join(html) X X start_response("200 OK", [("Content-Type", "text/html"), X ("Content-Length", str(len(body))), X ("Date", util.getRfc1123Time()), X ]) X return [ body ] e51d911c36dbc72b39d9dc8d3990b019 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/xml_tools.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/xml_tools.py << '5e4b981a89a64b7b4ef5845f95f13041' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XSmall wrapper for different etree packages. X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Ximport sys X X__docformat__ = "reStructuredText" X X Xtry: X from cStringIO import StringIO Xexcept ImportError: X from StringIO import StringIO X X# Import XML support XuseLxml = False Xtry: X from lxml import etree X useLxml = True Xexcept ImportError: X try: X # Try xml module (Python 2.5 or later) X from xml.etree import ElementTree as etree X print "WARNING: Could not import lxml: using xml instead (slower). Consider installing lxml from http://codespeak.net/lxml/." X except ImportError: X try: X # Try elementtree (http://effbot.org/zone/element-index.htm) X from elementtree import ElementTree as etree X except ImportError: X print "ERROR: Could not import lxml, xml, nor elementtree. Consider installing lxml from http://codespeak.net/lxml/ or update to Python 2.5 or later." X raise X X X#=============================================================================== X# XML X#=============================================================================== X Xdef stringToXML(text): X """Convert XML string into etree.Element.""" X try: X return etree.XML(text) X except Exception, e: X # TODO: X # ExpatError: reference to invalid character number: line 1, column 62 X # litmus fails, when xml is used instead of lxml X # 18. propget............... FAIL (PROPFIND on `/temp/litmus/prop2': Could not read status line: connection was closed by server) X # text = �� X# t2 = text.encode("utf8") X# return etree.XML(t2) X print >>sys.stderr, "Error parsing XML string. If lxml is not available, and unicode is involved, then installing it _may_ solve this issue." X raise X X Xdef xmlToString(element, pretty_print=False): X """Wrapper for etree.tostring, that takes care of unsupported pretty_print X option and prepends an encoding header.""" X if useLxml: X xml = etree.tostring(element, X encoding="UTF-8", X xml_declaration=True, X pretty_print=pretty_print) X else: X xml = etree.tostring(element, "UTF-8") X assert xml.startswith(">stream, xmlToString(childnode, pretty_print=False) X s = stream.getvalue() X stream.close() X return s X X X#=============================================================================== X# TEST X#=============================================================================== X Xif __name__ == "__main__": X pass 5e4b981a89a64b7b4ef5845f95f13041 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/version.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/version.py << '1738f5717192871a86e7ef92e9db92ca' X""" XCurrent WsgiDAV version number. X Xhttp://peak.telecommunity.com/DevCenter/setuptools#specifying-your-project-s-version Xhttp://peak.telecommunity.com/DevCenter/setuptools#tagging-and-daily-build-or-snapshot-releases X""" X__version__ = "0.5.1" 1738f5717192871a86e7ef92e9db92ca echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/property_manager.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/property_manager.py << '931f31399fb06c3d3eae6961a495c0ae' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplements two property managers: one in-memory (dict-based), and one Xpersistent low performance variant using shelve. X XThe properties dictionaray is built like:: X X { ref-url1: {propname1: value1, X propname2: value2, X }, X ref-url2: {propname1: value1, X propname2: value2, X }, X } X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom wsgidav import util Ximport os Ximport sys Ximport shelve Xfrom rw_lock import ReadWriteLock X X# TODO: comment's from Ian Bicking (2005) X#@@: Use of shelve means this is only really useful in a threaded environment. X# And if you have just a single-process threaded environment, you could get X# nearly the same effect with a dictionary of threading.Lock() objects. Of course, X# it would be better to move off shelve anyway, probably to a system with X# a directory of per-file locks, using the file locking primitives (which, X# sadly, are not quite portable). X# @@: It would probably be easy to store the properties as pickle objects X# in a parallel directory structure to the files you are describing. X# Pickle is expedient, but later you could use something more readable X# (pickles aren't particularly readable) X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X X X X#=============================================================================== X# PropertyManager X#=============================================================================== Xclass PropertyManager(object): X """ X An in-memory property manager implementation using a dictionary. X X This is obviously not persistent, but should be enough in some cases. X For a persistent implementation, see property_manager.ShelvePropertyManager(). X """ X def __init__(self): X self._dict = None X self._loaded = False X self._lock = ReadWriteLock() X self._verbose = 2 X X X def __repr__(self): X return "PropertyManager" X X X def __del__(self): X if __debug__ and self._verbose >= 2: X self._check() X self._close() X X X def _lazyOpen(self): X _logger.debug("_lazyOpen()") X self._lock.acquireWrite() X try: X self._dict = {} X self._loaded = True X finally: X self._lock.release() X X X def _sync(self): X pass X X X def _close(self): X _logger.debug("_close()") X self._lock.acquireWrite() X try: X self._dict = None X self._loaded = False X finally: X self._lock.release() X X X def _check(self, msg=""): X try: X if not self._loaded: X return True X# for k in self._dict.keys(): X# print "%s" % k X# print " -> %s" % self._dict[k] X# self._dump() X for k, v in self._dict.items(): X _ = "%s, %s" % (k, v) X# _logger.debug("%s checks ok %s" % (self.__class__.__name__, msg)) X return True X except Exception: X _logger.exception("%s _check: ERROR %s" % (self.__class__.__name__, msg)) X# traceback.print_exc() X# raise X# sys.exit(-1) X return False X X X def _dump(self, msg="", out=None): X if out is None: X out = sys.stdout X print >>out, "%s(%s): %s" % (self.__class__.__name__, self.__repr__(), msg) X if not self._loaded: X self._lazyOpen() X if self._verbose >= 2: X return # Already dumped in _lazyOpen X try: X for k, v in self._dict.items(): X print >>out, " ", k X for k2, v2 in v.items(): X try: X print >>out, " %s: '%s'" % (k2, v2) X except Exception, e: X print >>out, " %s: ERROR %s" % (k2, e) X out.flush() X except Exception, e: X util.warn("PropertyManager._dump() ERROR: %s" % e) X X X def getProperties(self, normurl): X _logger.debug("getProperties(%s)" % normurl) X self._lock.acquireRead() X try: X if not self._loaded: X self._lazyOpen() X returnlist = [] X if normurl in self._dict: X for propdata in self._dict[normurl].keys(): X returnlist.append(propdata) X return returnlist X finally: X self._lock.release() X X X def getProperty(self, normurl, propname): X _logger.debug("getProperty(%s, %s)" % (normurl, propname)) X self._lock.acquireRead() X try: X if not self._loaded: X self._lazyOpen() X if normurl not in self._dict: X return None X # TODO: sometimes we get exceptions here: (catch or otherwise make more robust?) X try: X resourceprops = self._dict[normurl] X except Exception, e: X _logger.exception("getProperty(%s, %s) failed : %s" % (normurl, propname, e)) X raise X return resourceprops.get(propname) X finally: X self._lock.release() X X X def writeProperty(self, normurl, propname, propertyvalue, dryRun=False): X# self._log("writeProperty(%s, %s, dryRun=%s):\n\t%s" % (normurl, propname, dryRun, propertyvalue)) X assert normurl and normurl.startswith("/") X assert propname #and propname.startswith("{") X assert propertyvalue is not None X X _logger.debug("writeProperty(%s, %s, dryRun=%s):\n\t%s" % (normurl, propname, dryRun, propertyvalue)) X if dryRun: X return # TODO: can we check anything here? X X self._lock.acquireWrite() X try: X if not self._loaded: X self._lazyOpen() X if normurl in self._dict: X locatordict = self._dict[normurl] X else: X locatordict = {} #dict([]) X locatordict[propname] = propertyvalue X # This re-assignment is important, so Shelve realizes the change: X self._dict[normurl] = locatordict X self._sync() X if __debug__ and self._verbose >= 2: X self._check() X finally: X self._lock.release() X X X def removeProperty(self, normurl, propname, dryRun=False): X """ X Specifying the removal of a property that does not exist is NOT an error. X """ X _logger.debug("removeProperty(%s, %s, dryRun=%s)" % (normurl, propname, dryRun)) X if dryRun: X # TODO: can we check anything here? X return X self._lock.acquireWrite() X try: X if not self._loaded: X self._lazyOpen() X if normurl in self._dict: X locatordict = self._dict[normurl] X if propname in locatordict: X del locatordict[propname] X # This re-assignment is important, so Shelve realizes the change: X self._dict[normurl] = locatordict X self._sync() X if __debug__ and self._verbose >= 2: X self._check() X finally: X self._lock.release() X X X def removeProperties(self, normurl): X _logger.debug("removeProperties(%s)" % normurl) X self._lock.acquireWrite() X try: X if not self._loaded: X self._lazyOpen() X if normurl in self._dict: X del self._dict[normurl] X self._sync() X finally: X self._lock.release() X X X def copyProperties(self, srcurl, desturl): X _logger.debug("copyProperties(%s, %s)" % (srcurl, desturl)) X self._lock.acquireWrite() X try: X if __debug__ and self._verbose >= 2: X self._check() X if not self._loaded: X self._lazyOpen() X if srcurl in self._dict: X self._dict[desturl] = self._dict[srcurl].copy() X self._sync() X if __debug__ and self._verbose >= 2: X self._check("after copy") X finally: X self._lock.release() X X X def moveProperties(self, srcurl, desturl, withChildren): X _logger.debug("moveProperties(%s, %s, %s)" % (srcurl, desturl, withChildren)) X self._lock.acquireWrite() X try: X if __debug__ and self._verbose >= 2: X self._check() X if not self._loaded: X self._lazyOpen() X if withChildren: X # Move srcurl\* X for url in self._dict.keys(): X if util.isEqualOrChildUri(srcurl, url): X d = url.replace(srcurl, desturl) X self._dict[d] = self._dict[url] X del self._dict[url] X# print "moveProperties:", url, d X elif srcurl in self._dict: X # Move srcurl only X self._dict[desturl] = self._dict[srcurl] X del self._dict[srcurl] X self._sync() X if __debug__ and self._verbose >= 2: X self._check("after move") X finally: X self._lock.release() X X X#=============================================================================== X# ShelvePropertyManager X#=============================================================================== X Xclass ShelvePropertyManager(PropertyManager): X """ X A low performance property manager implementation using shelve X """ X def __init__(self, storagePath): X self._storagePath = os.path.abspath(storagePath) X super(ShelvePropertyManager, self).__init__() X X X def __repr__(self): X return "ShelvePropertyManager(%s)" % self._storagePath X X X def _lazyOpen(self): X _logger.debug("_lazyOpen(%s)" % self._storagePath) X self._lock.acquireWrite() X try: X # Test again within the critical section X if self._loaded: X return True X # Open with writeback=False, which is faster, but we have to be X # careful to re-assign values to _dict after modifying them X self._dict = shelve.open(self._storagePath, X writeback=False) X self._loaded = True X if __debug__ and self._verbose >= 2: X self._check("After shelve.open()") X self._dump("After shelve.open()") X finally: X self._lock.release() X X X def _sync(self): X """Write persistent dictionary to disc.""" X _logger.debug("_sync()") X self._lock.acquireWrite() # TODO: read access is enough? X try: X if self._loaded: X self._dict.sync() X finally: X self._lock.release() X X X def _close(self): X _logger.debug("_close()") X self._lock.acquireWrite() X try: X if self._loaded: X self._dict.close() X self._dict = None X self._loaded = False X finally: X self._lock.release() X 931f31399fb06c3d3eae6961a495c0ae echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/util.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/util.py << 'e97e72c8664fd756bc95e42b1d47bdbb' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XMiscellaneous support functions for WsgiDAV. X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom pprint import pformat Xfrom wsgidav.dav_error import DAVError, HTTP_PRECONDITION_FAILED, HTTP_NOT_MODIFIED,\ X HTTP_NO_CONTENT, HTTP_CREATED, getHttpStatusString, HTTP_BAD_REQUEST,\ X HTTP_OK Xfrom wsgidav.xml_tools import xmlToString, makeSubElement Ximport urllib Ximport socket X X Ximport locale Ximport logging Ximport re Ximport mimetypes Xtry: X from hashlib import md5 Xexcept ImportError: X from md5 import md5 Ximport os Ximport calendar Ximport sys Ximport time Ximport stat X Xtry: X from email.utils import formatdate, parsedate Xexcept ImportError, e: X # Python < 2.5 X from email.Utils import formatdate, parsedate X Xtry: X from cStringIO import StringIO Xexcept ImportError: X from StringIO import StringIO #@UnusedImport X X#import xml_tools X## Trick PyDev to do intellisense and don't produce warnings: Xfrom xml_tools import etree #@UnusedImport Xif False: from xml.etree import ElementTree as etree #@Reimport @UnresolvedImport X X__docformat__ = "reStructuredText" X XBASE_LOGGER_NAME = "wsgidav" X_logger = logging.getLogger(BASE_LOGGER_NAME) X# Pre-initialize, so we get some output before initLogging() was called X# (for example during parsing of wsgidav.conf) Xlogging.basicConfig(level=logging.INFO) X X X#=============================================================================== X# String handling X#=============================================================================== X Xdef getRfc1123Time(secs=None): X """Return in rfc 1123 date/time format (pass secs=None for current date).""" X # issue #20: time string must be locale independent X# return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime(secs)) X return formatdate(timeval=secs, localtime=False, usegmt=True) X X Xdef getRfc3339Time(secs=None): X """Return in RFC 3339 date/time format (pass secs=None for current date). X X RFC 3339 is a subset of ISO 8601, used for '{DAV:}creationdate'. X See http://tools.ietf.org/html/rfc3339 X """ X return time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(secs)) X X Xdef getLogTime(secs=None): X """Return in log time format (pass secs=None for current date).""" X return time.strftime("%Y-%m-%d %H:%M:%S", time.gmtime(secs)) X X Xdef parseTimeString(timestring): X """Return the number of seconds since the epoch, for a date/time string. X X Returns None for invalid input X X The following time type strings are supported: X X Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 X Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 X Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format X """ X result = _parsegmtime(timestring) X if result: X return calendar.timegm(result) X return None X X Xdef _parsegmtime(timestring): X """Return a standard time tuple (see time and calendar), for a date/time string.""" X # Sun, 06 Nov 1994 08:49:37 GMT ; RFC 822, updated by RFC 1123 X try: X return time.strptime(timestring, "%a, %d %b %Y %H:%M:%S GMT") X except: X pass X X # Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036 X try: X return time.strptime(timestring, "%A %d-%b-%y %H:%M:%S GMT") X except: X pass X X # Sun Nov 6 08:49:37 1994 ; ANSI C's asctime() format X try: X return time.strptime(timestring, "%a %b %d %H:%M:%S %Y") X except: X pass X X # Sun Nov 6 08:49:37 1994 +0100 ; ANSI C's asctime() format with timezon X try: X return parsedate(timestring) X except: X pass X X return None X X X#=============================================================================== X# Logging X#=============================================================================== X Xdef initLogging(verbose=2, log_file="", enable_loggers=[]): X """Initialize base logger named 'wsgidav'. X X The base logger is filtered by the `verbose` configuration option. X Log entries will have a time stamp and thread id. X X :Parameters: X verbose : int X Verbosity configuration (0..3) X enable_loggers : string list X List of module logger names, that will be switched to DEBUG level. X X Module loggers X ~~~~~~~~~~~~~~ X Module loggers (e.g 'wsgidav.lock_manager') are named loggers, that can be X independently switched to DEBUG mode. X X Except for verbosity, they will inherit settings from the base logger. X X They will suppress DEBUG level messages, unless they are enabled by passing X their name to util.initLogging(). X X If enabled, module loggers will print DEBUG messages, even if verbose == 2. X X Example initialize and use a module logger, that will generate output, X if enabled (and verbose >= 2): X X .. python:: X _logger = util.getModuleLogger(__name__) X [..] X _logger.debug("foo: '%s'" % s) X X This logger would be enabled by passing its name to initLoggiong(): X X .. python:: X enable_loggers = ["lock_manager", X "property_manager", X ] X util.initLogging(2, enable_loggers) X X X Log level matrix X ~~~~~~~~~~~~~~~~ X ======= ====== =========== ====================== ======================= X verbose util Log level X ------- ------ ------------------------------------------------------------ X n () base logger module logger(enabled) module logger(disabled) X ======= ====== =========== ====================== ======================= X 0 write ERROR ERROR ERROR X 1 status WARN WARN WARN X 2 note INFO DEBUG INFO X 3 debug DEBUG DEBUG INFO X ======= ====== =========== ====================== ======================= X """ X X formatter = logging.Formatter("[%(asctime)s]: %(message)s") X X # Define handlers X if not log_file: X myHandler = logging.StreamHandler(sys.stdout) X else: X myHandler = logging.FileHandler(log_file) X# consoleHandler = logging.StreamHandler(sys.stderr) X myHandler.setFormatter(formatter) X myHandler.setLevel(logging.DEBUG) X X # Add the handlers to the base logger X logger = logging.getLogger(BASE_LOGGER_NAME) X X if verbose >= 3: # --debug X logger.setLevel(logging.DEBUG) X elif verbose >= 2: # --verbose X logger.setLevel(logging.INFO) X elif verbose >= 1: # standard X logger.setLevel(logging.WARN) X consoleHandler.setLevel(logging.WARN) X else: # --quiet X logger.setLevel(logging.ERROR) X consoleHandler.setLevel(logging.ERROR) X X # Don't call the root's handlers after our custom handlers X logger.propagate = False X X # Remove previous handlers X for hdlr in logger.handlers[:]: # Must iterate an array copy X try: X hdlr.flush() X hdlr.close() X except: X pass X logger.removeHandler(hdlr) X X logger.addHandler(myHandler) X X if verbose >= 2: X for e in enable_loggers: X if not e.startswith(BASE_LOGGER_NAME + "."): X e = BASE_LOGGER_NAME + "." + e X l = logging.getLogger(e.strip()) X# if verbose >= 2: X# log("Logger(%s).setLevel(DEBUG)" % e.strip()) X l.setLevel(logging.DEBUG) X X X Xdef getModuleLogger(moduleName, defaultToVerbose=False): X """Create a module logger, that can be en/disabled by configuration. X X @see: unit.initLogging X """ X# _logger.debug("getModuleLogger(%s)" % moduleName) X if not moduleName.startswith(BASE_LOGGER_NAME + "."): X moduleName = BASE_LOGGER_NAME + "." + moduleName X# assert not "." in moduleName, "Only pass the module name, without leading '%s.'." % BASE_LOGGER_NAME X# logger = logging.getLogger("%s.%s" % (BASE_LOGGER_NAME, moduleName)) X logger = logging.getLogger(moduleName) X if logger.level == logging.NOTSET and not defaultToVerbose: X logger.setLevel(logging.INFO) # Disable debug messages by default X return logger X X Xdef log(msg, var=None): X """Shortcut for logging.getLogger('wsgidav').info(msg) X X This message will only display, if verbose >= 2. X """ X# _logger.info(msg) X# if var and logging.INFO >= _logger.getEffectiveLevel(): X# pprint(var, sys.stderr, indent=4) X note(msg, var=var) X X Xdef _write(msg, var, module, level, flush): X if module: X logger = logging.getLogger(BASE_LOGGER_NAME+"."+module) X # Disable debug messages for module loggers by default X if logger.level == logging.NOTSET: X logger.setLevel(logging.INFO) X else: X logger = _logger X logger.log(level, msg) X# if level >= logger.getEffectiveLevel(): X if var is not None and level >= logger.getEffectiveLevel(): X logger.log(level, pformat(var, indent=4)) X if flush: X for hdlr in logger.handlers: X hdlr.flush() X Xdef write(msg, var=None, module=None, flush=True): X """Log always.""" X _write(msg, var, module, logging.CRITICAL, flush) Xdef warn(msg, var=None, module=None, flush=True): X """Log to stderr.""" X _write(msg, var, module, logging.ERROR, flush) Xdef status(msg, var=None, module=None, flush=True): X """Log if not --quiet.""" X _write(msg, var, module, logging.WARNING, flush) Xdef note(msg, var=None, module=None, flush=True): X """Log if --verbose.""" X _write(msg, var, module, logging.INFO, flush) Xdef debug(msg, var=None, module=None, flush=True): X """Log if --debug.""" X _write(msg, var, module, logging.DEBUG, flush) X X Xdef traceCall(msg=None): X """Return name of calling function.""" X if __debug__: X f_code = sys._getframe(2).f_code X if msg is None: X msg = ": %s" X else: msg = "" X print "%s.%s #%s%s" % (f_code.co_filename, f_code.co_name, f_code.co_lineno, msg) X X X#=============================================================================== X# Strings X#=============================================================================== X Xdef lstripstr(s, prefix, ignoreCase=False): X if ignoreCase: X if not s.lower().startswith(prefix.lower()): X return s X else: X if not s.startswith(prefix): X return s X return s[len(prefix):] X X Xdef saveSplit(s, sep, maxsplit): X """Split string, always returning n-tuple (filled with None if necessary).""" X tok = s.split(sep, maxsplit) X while len(tok) <= maxsplit: X tok.append(None) X return tok X X Xdef popPath(path): X """Return '/a/b/c' -> ('a', '/b/c').""" X if path in ("", "/"): X return ("", "") X assert path.startswith("/") X first, _sep, rest = path.lstrip("/").partition("/") X return (first, "/"+rest) X X Xdef popPath2(path): X """Return '/a/b/c' -> ('a', 'b', '/c').""" X if path in ("", "/"): X return ("", "", "") X first, rest = popPath(path) X second, rest = popPath(rest) X return (first, second, "/"+rest) X X Xdef shiftPath(scriptName, pathInfo): X """Return ('/a', '/b/c') -> ('b', '/a/b', 'c').""" X segment, rest = popPath(pathInfo) X return (segment, joinUri(scriptName.rstrip("/"), segment), rest.rstrip("/")) X X Xdef splitNamespace(clarkName): X """Return (namespace, localname) tuple for a property name in Clark Notation. X X Namespace defaults to ''. X Example: X '{DAV:}foo' -> ('DAV:', 'foo') X 'bar' -> ('', 'bar') X """ X if clarkName.startswith("{") and "}" in clarkName: X ns, localname = clarkName.split("}", 1) X return (ns[1:], localname) X return ("", clarkName) X X Xdef toUnicode(s): X """Convert a binary string to Unicode using UTF-8 (fallback to latin-1).""" X if not isinstance(s, str): X return s X try: X u = s.decode("utf8") X# log("toUnicode(%r) = '%r'" % (s, u)) X except: X log("toUnicode(%r) *** UTF-8 failed. Trying latin-1 " % s) X u = s.decode("latin-1") X return u X X Xdef stringRepr(s): X """Return a string as hex dump.""" X if isinstance(s, str): X res = "'%s': " % s X for b in s: X res += "%02x " % ord(b) X return res X return "%s" % s X X X Xdef byteNumberString(number, thousandsSep=True, partition=False, base1024=True, appendBytes=True): X """Convert bytes into human-readable representation.""" X magsuffix = "" X bytesuffix = "" X X if partition: X magnitude = 0 X if base1024: X while number >= 1024: X magnitude += 1 X number = number >> 10 X else: X while number >= 1000: X magnitude += 1 X number /= 1000.0 X # TODO: use "9 KB" instead of "9K Bytes"? X # TODO use 'kibi' for base 1024? X # http://en.wikipedia.org/wiki/Kibi-#IEC_standard_prefixes X magsuffix = ["", "K", "M", "G", "T", "P"][magnitude] X X if appendBytes: X if number == 1: X bytesuffix = " Byte" X else: X bytesuffix = " Bytes" X X if thousandsSep and (number >= 1000 or magsuffix): X locale.setlocale(locale.LC_ALL, "") X # TODO: make precision configurable X snum = locale.format("%d", number, thousandsSep) X else: X snum = str(number) X X return "%s%s%s" % (snum, magsuffix, bytesuffix) X X X X#=============================================================================== X# WSGI X#=============================================================================== Xdef getContentLength(environ): X """Return a positive CONTENT_LENGTH in a safe way (return 0 otherwise).""" X # TODO: http://www.wsgi.org/wsgi/WSGI_2.0 X try: X return max(0, long(environ.get("CONTENT_LENGTH", 0))) X except ValueError: X return 0 X X X#def readAllInput(environ): X# """Read and discard all from from wsgi.input, if this has not been done yet.""" X# cl = getContentLength(environ) X# if environ.get("wsgidav.all_input_read") or cl == 0: X# return X# assert not environ.get("wsgidav.some_input_read") X# write("Reading and discarding %s bytes input." % cl) X# environ["wsgi.input"].read(cl) X# environ["wsgidav.all_input_read"] = 1 X X Xdef readAndDiscardInput(environ): X """Read 1 byte from wsgi.input, if this has not been done yet. X X Returning a response without reading from a request body might confuse the X WebDAV client. X This may happen, if an exception like '401 Not authorized', or X '500 Internal error' was raised BEFORE anything was read from the request X stream. X X See issue 13, issue 23 X See http://groups.google.com/group/paste-users/browse_frm/thread/fc0c9476047e9a47?hl=en X X Note that with persistent sessions (HTTP/1.1) we must make sure, that the X 'Connection: closed' header is set with the response, to prevent reusing X the current stream. X """ X if environ.get("wsgidav.some_input_read") or environ.get("wsgidav.all_input_read"): X return X cl = getContentLength(environ) X assert cl >= 0 X if cl == 0: X return X X READ_ALL = True X X environ["wsgidav.some_input_read"] = 1 X if READ_ALL: X environ["wsgidav.all_input_read"] = 1 X X X wsgi_input = environ["wsgi.input"] X X # TODO: check if still required after issue 24 is fixed X if hasattr(wsgi_input, "_consumed") and hasattr(wsgi_input, "length"): X # Seems to be Paste's httpserver.LimitedLengthFile X # see http://groups.google.com/group/paste-users/browse_thread/thread/fc0c9476047e9a47/aa4a3aa416016729?hl=en&lnk=gst&q=.input#aa4a3aa416016729 X # Consume something if nothing was consumed *and* work X # around a bug where paste.httpserver allows negative lengths X if wsgi_input._consumed == 0 and wsgi_input.length > 0: X # This seems to work even if there's 10K of input. X if READ_ALL: X n = wsgi_input.length X else: X n = 1 X body = wsgi_input.read(n) X debug("Reading %s bytes from potentially unread httpserver.LimitedLengthFile: '%s'..." % (n, body[:50])) X X elif hasattr(wsgi_input, "_sock") and hasattr(wsgi_input._sock, "settimeout"): X # Seems to be a socket X try: X # Set socket to non-blocking X sock = wsgi_input._sock X timeout = sock.gettimeout() X sock.settimeout(0) X # Read one byte X try: X if READ_ALL: X n = cl X else: X n = 1 X body = wsgi_input.read(n) X debug("Reading %s bytes from potentially unread POST body: '%s'..." % (n, body[:50])) X except socket.error, se: X # se(10035, 'The socket operation could not complete without blocking') X warn("-> read %s bytes failed: %s" % (n, se)) X # Restore socket settings X sock.settimeout(timeout) X except: X warn("--> wsgi_input.read(): %s" % sys.exc_info()) X X X X#=============================================================================== X# URLs X#=============================================================================== X Xdef joinUri(uri, *segments): X """Append segments to URI. X X Example: joinUri("/a/b", "c", "d") X """ X sub = "/".join(segments) X if not sub: X return uri X return uri.rstrip("/") + "/" + sub X X Xdef getUriName(uri): X """Return local name, i.e. last segment of URI.""" X return uri.strip("/").split("/")[-1] X X Xdef getUriParent(uri): X """Return URI of parent collection with trailing '/', or None, if URI is top-level. X X This function simply strips the last segment. It does not test, if the X target is a 'collection', or even exists. X """ X if not uri or uri.strip() == "/": X return None X return uri.rstrip("/").rsplit("/", 1)[0] + "/" X X Xdef isChildUri(parentUri, childUri): X """Return True, if childUri is a child of parentUri. X X This function accounts for the fact that '/a/b/c' and 'a/b/c/' are X children of '/a/b' (and also of '/a/b/'). X Note that '/a/b/cd' is NOT a child of 'a/b/c'. X """ X return parentUri and childUri and childUri.rstrip("/").startswith(parentUri.rstrip("/")+"/") X X Xdef isEqualOrChildUri(parentUri, childUri): X """Return True, if childUri is a child of parentUri or maps to the same resource. X X Similar to _ , but this method also returns True, if parent X equals child. ('/a/b' is considered identical with '/a/b/'). X """ X return parentUri and childUri and (childUri.rstrip("/")+"/").startswith(parentUri.rstrip("/")+"/") X X Xdef makeCompleteUrl(environ, localUri=None): X """URL reconstruction according to PEP 333. X @see http://www.python.org/dev/peps/pep-0333/#id33 X """ X url = environ["wsgi.url_scheme"]+"://" X X if environ.get("HTTP_HOST"): X url += environ["HTTP_HOST"] X else: X url += environ["SERVER_NAME"] X X if environ["wsgi.url_scheme"] == "https": X if environ["SERVER_PORT"] != "443": X url += ":" + environ["SERVER_PORT"] X else: X if environ["SERVER_PORT"] != "80": X url += ":" + environ["SERVER_PORT"] X X url += urllib.quote(environ.get("SCRIPT_NAME","")) X X if localUri is None: X url += urllib.quote(environ.get("PATH_INFO","")) X if environ.get("QUERY_STRING"): X url += "?" + environ["QUERY_STRING"] X else: X url += localUri # TODO: quote? X return url X X X#=============================================================================== X# XML X#=============================================================================== X Xdef parseXmlBody(environ, allowEmpty=False): X """Read request body XML into an etree.Element. X X Return None, if no request body was sent. X Raise HTTP_BAD_REQUEST, if something else went wrong. X X TODO: this is a very relaxed interpretation: should we raise HTTP_BAD_REQUEST X instead, if CONTENT_LENGTH is missing, invalid, or 0? X X RFC: For compatibility with HTTP/1.0 applications, HTTP/1.1 requests containing X a message-body MUST include a valid Content-Length header field unless the X server is known to be HTTP/1.1 compliant. X If a request contains a message-body and a Content-Length is not given, the X server SHOULD respond with 400 (bad request) if it cannot determine the X length of the message, or with 411 (length required) if it wishes to insist X on receiving a valid Content-Length." X X So I'd say, we should accept a missing CONTENT_LENGTH, and try to read the X content anyway. X But WSGI doesn't guarantee to support input.read() without length(?). X At least it locked, when I tried it with a request that had a missing X content-type and no body. X X Current approach: if CONTENT_LENGTH is X X - valid and >0: X read body (exactly bytes) and parse the result. X - 0: X Assume empty body and return None or raise exception. X - invalid (negative or not a number: X raise HTTP_BAD_REQUEST X - missing: X NOT: Try to read body until end and parse the result. X BUT: assume '0' X - empty string: X WSGI allows it to be empty or absent: treated like 'missing'. X """ X # X clHeader = environ.get("CONTENT_LENGTH", "").strip() X# contentLength = -1 # read all of stream X if clHeader == "": X # No Content-Length given: read to end of stream X # TODO: etree.parse() locks, if input is invalid? X# pfroot = etree.parse(environ["wsgi.input"]).getroot() X# requestbody = environ["wsgi.input"].read() # TODO: read() should be called in a loop? X requestbody = "" X else: X try: X contentLength = long(clHeader) X if contentLength < 0: X raise DAVError(HTTP_BAD_REQUEST, "Negative content-length.") X except ValueError: X raise DAVError(HTTP_BAD_REQUEST, "content-length is not numeric.") X X if contentLength == 0: X requestbody = "" X else: X requestbody = environ["wsgi.input"].read(contentLength) X environ["wsgidav.all_input_read"] = 1 X X if requestbody == "": X if allowEmpty: X return None X else: X raise DAVError(HTTP_BAD_REQUEST, "Body must not be empty.") X X try: X rootEL = etree.fromstring(requestbody) X except Exception, e: X raise DAVError(HTTP_BAD_REQUEST, "Invalid XML format.", srcexception=e) X X # If dumps of the body are desired, then this is the place to do it pretty: X if environ.get("wsgidav.dump_request_body"): X write("%s XML request body:\n%s" % (environ["REQUEST_METHOD"], X xmlToString(rootEL, pretty_print=True))) X environ["wsgidav.dump_request_body"] = False X X return rootEL X X X#def sendResponse(environ, start_response, body, content_type): X# """Send a WSGI response for a HTML or XML string.""" X# assert content_type in ("application/xml", "text/html") X# X# start_response(status, [("Content-Type", content_type), X# ("Date", getRfc1123Time()), X# ("Content-Length", str(len(body))), X# ]) X# return [ body ] X X Xdef sendStatusResponse(environ, start_response, e): X """Start a WSGI response for a DAVError or status code.""" X status = getHttpStatusString(e) X headers = [] X# if 'keep-alive' in environ.get('HTTP_CONNECTION', '').lower(): X# headers += [ X# ('Connection', 'keep-alive'), X# ] X X if e in (HTTP_NOT_MODIFIED, HTTP_NO_CONTENT): X # See paste.lint: these code don't have content X start_response(status, [("Content-Length", "0"), X ("Date", getRfc1123Time()), X ] + headers) X return [ "" ] X X if e in (HTTP_OK, HTTP_CREATED): X e = DAVError(e) X assert isinstance(e, DAVError) X X content_type, body = e.getResponsePage() X X start_response(status, [("Content-Type", content_type), X ("Date", getRfc1123Time()), X ("Content-Length", str(len(body))), X ] + headers) X assert type(body) is str # If not, Content-Length is wrong! X return [ body ] X X Xdef sendMultiStatusResponse(environ, start_response, multistatusEL): X # If logging of the body is desired, then this is the place to do it pretty: X if environ.get("wsgidav.dump_response_body"): X xml = "%s XML response body:\n%s" % (environ["REQUEST_METHOD"], X xmlToString(multistatusEL, pretty_print=True)) X environ["wsgidav.dump_response_body"] = xml X X # Hotfix for Windows XP X # PROPFIND XML response is not recognized, when pretty_print = True! X # (Vista and others would accept this). X xml_data = xmlToString(multistatusEL, pretty_print=False) X X headers = [ X ("Content-Type", "application/xml"), X ("Date", getRfc1123Time()), X ('Content-Length', str(len(xml_data))), X ] X X# if 'keep-alive' in environ.get('HTTP_CONNECTION', '').lower(): X# headers += [ X# ('Connection', 'keep-alive'), X# ] X X start_response("207 Multistatus", headers) X assert type(xml_data) is str # If not, Content-Length is wrong! X return [ xml_data ] X X Xdef addPropertyResponse(multistatusEL, href, propList): X """Append element to element. X X node depends on the value type: X - str or unicode: add element with this content X - None: add an empty element X - etree.Element: add XML element as child X - DAVError: add an empty element to an own for this status code X X @param multistatusEL: etree.Element X @param href: global URL of the resource, e.g. 'http://server:port/path'. X @param propList: list of 2-tuples (name, value) X """ X # Split propList by status code and build a unique list of namespaces X nsCount = 1 X nsDict = {} X nsMap = {} X propDict = {} X X for name, value in propList: X status = "200 OK" X if isinstance(value, DAVError): X status = getHttpStatusString(value) X # Always generate *empty* elements for props with error status X value = None X X # Collect namespaces, so we can declare them in the for X # compacter output X ns, _ = splitNamespace(name) X if ns!="DAV:" and not ns in nsDict and ns != "": X nsDict[ns] = True X nsMap["NS%s" % nsCount] = ns X nsCount += 1 X X propDict.setdefault(status, []).append( (name, value) ) X X # X responseEL = makeSubElement(multistatusEL, "{DAV:}response", nsmap=nsMap) X X# log("href value:%s" % (stringRepr(href))) X# etree.SubElement(responseEL, "{DAV:}href").text = toUnicode(href) X etree.SubElement(responseEL, "{DAV:}href").text = href X# etree.SubElement(responseEL, "{DAV:}href").text = urllib.quote(href, safe="/" + "!*'()," + "$-_|.") X X X # One per status code X for status in propDict: X propstatEL = etree.SubElement(responseEL, "{DAV:}propstat") X # List of X propEL = etree.SubElement(propstatEL, "{DAV:}prop") X for name, value in propDict[status]: X if value is None: X etree.SubElement(propEL, name) X elif isinstance(value, etree._Element): X propEL.append(value) X else: X # value must be string or unicode X# log("%s value:%s" % (name, stringRepr(value))) X# etree.SubElement(propEL, name).text = value X etree.SubElement(propEL, name).text = toUnicode(value) X # X etree.SubElement(propstatEL, "{DAV:}status").text = "HTTP/1.1 %s" % status X X X#=============================================================================== X# ETags X#=============================================================================== Xdef getETag(filePath): X """Return a strong Entity Tag for a (file)path. X X http://www.webdav.org/specs/rfc4918.html#etag X X Returns the following as entity tags:: X X Non-file - md5(pathname) X Win32 - md5(pathname)-lastmodifiedtime-filesize X Others - inode-lastmodifiedtime-filesize X """ X # (At least on Vista) os.path.exists returns False, if a file name contains X # special characters, even if it is correctly UTF-8 encoded. X # So we convert to unicode. On the other hand, md5() needs a byte string. X if isinstance(filePath, unicode): X unicodeFilePath = filePath X filePath = filePath.encode("utf8") X else: X unicodeFilePath = toUnicode(filePath) X X if not os.path.isfile(unicodeFilePath): X return md5(filePath).hexdigest() X if sys.platform == "win32": X statresults = os.stat(unicodeFilePath) X return md5(filePath).hexdigest() + "-" + str(statresults[stat.ST_MTIME]) + "-" + str(statresults[stat.ST_SIZE]) X else: X statresults = os.stat(unicodeFilePath) X return str(statresults[stat.ST_INO]) + "-" + str(statresults[stat.ST_MTIME]) + "-" + str(statresults[stat.ST_SIZE]) X X X#=============================================================================== X# Ranges X#=============================================================================== X X# Range Specifiers XreByteRangeSpecifier = re.compile("(([0-9]+)\-([0-9]*))") XreSuffixByteRangeSpecifier = re.compile("(\-([0-9]+))") X Xdef obtainContentRanges(rangetext, filesize): X """ X returns tuple (list, value) X X list X content ranges as values to their parsed components in the tuple X (seek_position/abs position of first byte, abs position of last byte, num_of_bytes_to_read) X value X total length for Content-Length X """ X listReturn = [] X seqRanges = rangetext.split(",") X for subrange in seqRanges: X matched = False X if not matched: X mObj = reByteRangeSpecifier.search(subrange) X if mObj: X# print mObj.group(0), mObj.group(1), mObj.group(2), mObj.group(3) X firstpos = long(mObj.group(2)) X if mObj.group(3) == "": X lastpos = filesize - 1 X else: X lastpos = long(mObj.group(3)) X if firstpos <= lastpos and firstpos < filesize: X if lastpos >= filesize: X lastpos = filesize - 1 X listReturn.append( (firstpos , lastpos) ) X matched = True X if not matched: X mObj = reSuffixByteRangeSpecifier.search(subrange) X if mObj: X firstpos = filesize - long(mObj.group(2)) X if firstpos < 0: X firstpos = 0 X lastpos = filesize - 1 X listReturn.append( (firstpos , lastpos) ) X X matched = True X X # consolidate ranges X listReturn.sort() X listReturn2 = [] X totallength = 0 X while(len(listReturn) > 0): X (rfirstpos, rlastpos) = listReturn.pop() X counter = len(listReturn) X while counter > 0: X (nfirstpos, nlastpos) = listReturn[counter-1] X if nlastpos < rfirstpos - 1 or nfirstpos > nlastpos + 1: X pass X else: X rfirstpos = min(rfirstpos, nfirstpos) X rlastpos = max(rlastpos, nlastpos) X del listReturn[counter-1] X counter = counter - 1 X listReturn2.append((rfirstpos,rlastpos,rlastpos - rfirstpos + 1 )) X totallength = totallength + rlastpos - rfirstpos + 1 X X return (listReturn2, totallength) X X#=============================================================================== X# X#=============================================================================== X# any numofsecs above the following limit is regarded as infinite XMAX_FINITE_TIMEOUT_LIMIT = 10*365*24*60*60 #approx 10 years XreSecondsReader = re.compile(r'second\-([0-9]+)', re.I) X Xdef readTimeoutValueHeader(timeoutvalue): X """Return -1 if infinite, else return numofsecs.""" X timeoutsecs = 0 X timeoutvaluelist = timeoutvalue.split(",") X for timeoutspec in timeoutvaluelist: X timeoutspec = timeoutspec.strip() X if timeoutspec.lower() == "infinite": X return -1 X else: X listSR = reSecondsReader.findall(timeoutspec) X for secs in listSR: X timeoutsecs = long(secs) X if timeoutsecs > MAX_FINITE_TIMEOUT_LIMIT: X return -1 X if timeoutsecs != 0: X return timeoutsecs X return None X X X#=============================================================================== X# If Headers X#=============================================================================== X Xdef evaluateHTTPConditionals(davres, lastmodified, entitytag, environ): X """Handle 'If-...:' headers (but not 'If:' header). X X If-Match X @see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.24 X Only perform the action if the client supplied entity matches the X same entity on the server. This is mainly for methods like X PUT to only update a resource if it has not been modified since the X user last updated it. X If-Match: "737060cd8c284d8af7ad3082f209582d" X If-Modified-Since X @see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.25 X Allows a 304 Not Modified to be returned if content is unchanged X If-Modified-Since: Sat, 29 Oct 1994 19:43:31 GMT X If-None-Match X @see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.26 X Allows a 304 Not Modified to be returned if content is unchanged, X see HTTP ETag X If-None-Match: "737060cd8c284d8af7ad3082f209582d" X If-Unmodified-Since X @see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.28 X Only send the response if the entity has not been modified since a X specific time. X """ X if not davres: X return X ## Conditions X X # An HTTP/1.1 origin server, upon receiving a conditional request that includes both a Last-Modified date X # (e.g., in an If-Modified-Since or If-Unmodified-Since header field) and one or more entity tags (e.g., X # in an If-Match, If-None-Match, or If-Range header field) as cache validators, MUST NOT return a response X # status of 304 (Not Modified) unless doing so is consistent with all of the conditional header fields in X # the request. X X if "HTTP_IF_MATCH" in environ and davres.supportEtag(): X ifmatchlist = environ["HTTP_IF_MATCH"].split(",") X for ifmatchtag in ifmatchlist: X ifmatchtag = ifmatchtag.strip(" \"\t") X if ifmatchtag == entitytag or ifmatchtag == "*": X break X raise DAVError(HTTP_PRECONDITION_FAILED, X "If-Match header condition failed") X X # TODO: after the refactoring X ifModifiedSinceFailed = False X if "HTTP_IF_MODIFIED_SINCE" in environ and davres.supportModified(): X ifmodtime = parseTimeString(environ["HTTP_IF_MODIFIED_SINCE"]) X if ifmodtime and ifmodtime > lastmodified: X ifModifiedSinceFailed = True X X # If-None-Match X # If none of the entity tags match, then the server MAY perform the requested method as if the X # If-None-Match header field did not exist, but MUST also ignore any If-Modified-Since header field X # (s) in the request. That is, if no entity tags match, then the server MUST NOT return a 304 (Not Modified) X # response. X ignoreIfModifiedSince = False X if "HTTP_IF_NONE_MATCH" in environ and davres.supportEtag(): X ifmatchlist = environ["HTTP_IF_NONE_MATCH"].split(",") X for ifmatchtag in ifmatchlist: X ifmatchtag = ifmatchtag.strip(" \"\t") X if ifmatchtag == entitytag or ifmatchtag == "*": X # ETag matched. If it's a GET request and we don't have an X # conflicting If-Modified header, we return NOT_MODIFIED X if environ["REQUEST_METHOD"] in ("GET", "HEAD") and not ifModifiedSinceFailed: X raise DAVError(HTTP_NOT_MODIFIED, X "If-None-Match header failed") X raise DAVError(HTTP_PRECONDITION_FAILED, X "If-None-Match header condition failed") X ignoreIfModifiedSince = True X X if "HTTP_IF_UNMODIFIED_SINCE" in environ and davres.supportModified(): X ifunmodtime = parseTimeString(environ["HTTP_IF_UNMODIFIED_SINCE"]) X if ifunmodtime and ifunmodtime <= lastmodified: X raise DAVError(HTTP_PRECONDITION_FAILED, X "If-Unmodified-Since header condition failed") X X if ifModifiedSinceFailed and not ignoreIfModifiedSince: X raise DAVError(HTTP_NOT_MODIFIED, X "If-Modified-Since header condition failed") X X return X X X X XreIfSeparator = re.compile(r'(\<([^>]+)\>)|(\(([^\)]+)\))') XreIfHeader = re.compile(r'\<([^>]+)\>([^<]+)') XreIfTagList = re.compile(r'\(([^)]+)\)') XreIfTagListContents = re.compile(r'(\S+)') X X Xdef parseIfHeaderDict(environ): X """Parse HTTP_IF header into a dictionary and lists, and cache the result. X X @see http://www.webdav.org/specs/rfc4918.html#HEADER_If X """ X if "wsgidav.conditions.if" in environ: X return X X if not "HTTP_IF" in environ: X environ["wsgidav.conditions.if"] = None X environ["wsgidav.ifLockTokenList"] = [] X return X X iftext = environ["HTTP_IF"].strip() X if not iftext.startswith("<"): X iftext = "<*>" + iftext X X ifDict = dict([]) X ifLockList = [] X X resource1 = "*" X for (tmpURLVar, URLVar, _tmpContentVar, contentVar) in reIfSeparator.findall(iftext): X if tmpURLVar != "": X resource1 = URLVar X else: X listTagContents = [] X testflag = True X for listitem in reIfTagListContents.findall(contentVar): X if listitem.upper() != "NOT": X if listitem.startswith("["): X listTagContents.append((testflag,"entity",listitem.strip('\"[]'))) X else: X listTagContents.append((testflag,"locktoken",listitem.strip("<>"))) X ifLockList.append(listitem.strip("<>")) X testflag = listitem.upper() != "NOT" X X if resource1 in ifDict: X listTag = ifDict[resource1] X else: X listTag = [] X ifDict[resource1] = listTag X listTag.append(listTagContents) X X environ["wsgidav.conditions.if"] = ifDict X environ["wsgidav.ifLockTokenList"] = ifLockList X debug("parseIfHeaderDict", var=ifDict, module="if") X return X X Xdef testIfHeaderDict(davres, dictIf, fullurl, locktokenlist, entitytag): X debug("testIfHeaderDict(%s, %s, %s)" % (fullurl, locktokenlist, entitytag), X var=dictIf, module="if") X X if fullurl in dictIf: X listTest = dictIf[fullurl] X elif "*" in dictIf: X listTest = dictIf["*"] X else: X return True X X# supportEntityTag = dav.isInfoTypeSupported(path, "etag") X supportEntityTag = davres.supportEtag() X for listTestConds in listTest: X matchfailed = False X X for (testflag, checkstyle, checkvalue) in listTestConds: X if checkstyle == "entity" and supportEntityTag: X testresult = entitytag == checkvalue X elif checkstyle == "entity": X testresult = testflag X elif checkstyle == "locktoken": X testresult = checkvalue in locktokenlist X else: # unknown X testresult = True X checkresult = testresult == testflag X if not checkresult: X matchfailed = True X break X if not matchfailed: X return True X debug(" -> FAILED", module="if") X return False X XtestIfHeaderDict.__test__ = False # Tell nose to ignore this function X X#=============================================================================== X# guessMimeType X#=============================================================================== X_MIME_TYPES ={".oga": "audio/ogg", X ".ogg": "audio/ogg", X ".ogv": "video/ogg", X ".webm": "video/webm", X } Xdef guessMimeType(url): X """Use the mimetypes module to lookup the type for an extension. X X This function also adds some extensions required for HTML5 X """ X (mimetype, _mimeencoding) = mimetypes.guess_type(url) X# print "mimetype(%s): %r, %r" % (url, mimetype, _mimeencoding) X if not mimetype: X ext = os.path.splitext(url)[1] X# mimetype = _MIME_TYPES[ext] X mimetype = _MIME_TYPES.get(ext) X debug("mimetype(%s): %r" % (url, mimetype)) X if not mimetype: X mimetype = "application/octet-stream" X# print "mimetype(%s): return %r" % (url, mimetype) X return mimetype X X#=============================================================================== X# TEST X#=============================================================================== Xdef testLogging(): X enable_loggers = ["test", X ] X initLogging(3, enable_loggers) X X _baseLogger = logging.getLogger(BASE_LOGGER_NAME) X _enabledLogger = getModuleLogger("test") X _disabledLogger = getModuleLogger("test2") X X _baseLogger.debug("_baseLogger.debug") X _baseLogger.info("_baseLogger.info") X _baseLogger.warning("_baseLogger.warning") X _baseLogger.error("_baseLogger.error") X print X X _enabledLogger.debug("_enabledLogger.debug") X _enabledLogger.info("_enabledLogger.info") X _enabledLogger.warning("_enabledLogger.warning") X _enabledLogger.error("_enabledLogger.error") X print X X _disabledLogger.debug("_disabledLogger.debug") X _disabledLogger.info("_disabledLogger.info") X _disabledLogger.warning("_disabledLogger.warning") X _disabledLogger.error("_disabledLogger.error") X print X X write("util.write()") X warn("util.warn()") X status("util.status()") X note("util.note()") X debug("util.debug()") X X Xif __name__ == "__main__": X testLogging() X pass e97e72c8664fd756bc95e42b1d47bdbb echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/rw_lock.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/rw_lock.py << '5d581f7ba61210e7e3f11be9194eea2b' X# -*- coding: iso-8859-15 -*- X""" XReadWriteLock X XTaken from http://code.activestate.com/recipes/502283/ X Xlocks.py - Read-Write lock thread lock implementation X XSee the class documentation for more info. X XCopyright (C) 2007, Heiko Wundram. XReleased under the BSD-license. X""" X X# Imports X# ------- X Xfrom threading import Condition, Lock, currentThread Xfrom time import time X X X# Read write lock X# --------------- X Xclass ReadWriteLock(object): X """Read-Write lock class. A read-write lock differs from a standard X threading.RLock() by allowing multiple threads to simultaneously hold a X read lock, while allowing only a single thread to hold a write lock at the X same point of time. X X When a read lock is requested while a write lock is held, the reader X is blocked; when a write lock is requested while another write lock is X held or there are read locks, the writer is blocked. X X Writers are always preferred by this implementation: if there are blocked X threads waiting for a write lock, current readers may request more read X locks (which they eventually should free, as they starve the waiting X writers otherwise), but a new thread requesting a read lock will not X be granted one, and block. This might mean starvation for readers if X two writer threads interweave their calls to acquireWrite() without X leaving a window only for readers. X X In case a current reader requests a write lock, this can and will be X satisfied without giving up the read locks first, but, only one thread X may perform this kind of lock upgrade, as a deadlock would otherwise X occur. After the write lock has been granted, the thread will hold a X full write lock, and not be downgraded after the upgrading call to X acquireWrite() has been match by a corresponding release(). X """ X X def __init__(self): X """Initialize this read-write lock.""" X X # Condition variable, used to signal waiters of a change in object X # state. X self.__condition = Condition(Lock()) X X # Initialize with no writers. X self.__writer = None X self.__upgradewritercount = 0 X self.__pendingwriters = [] X X # Initialize with no readers. X self.__readers = {} X X def acquireRead(self,timeout=None): X """Acquire a read lock for the current thread, waiting at most X timeout seconds or doing a non-blocking check in case timeout is <= 0. X X In case timeout is None, the call to acquireRead blocks until the X lock request can be serviced. X X In case the timeout expires before the lock could be serviced, a X RuntimeError is thrown.""" X X if timeout is not None: X endtime = time() + timeout X me = currentThread() X self.__condition.acquire() X try: X if self.__writer is me: X # If we are the writer, grant a new read lock, always. X self.__writercount += 1 X return X while True: X if self.__writer is None: X # Only test anything if there is no current writer. X if self.__upgradewritercount or self.__pendingwriters: X if me in self.__readers: X # Only grant a read lock if we already have one X # in case writers are waiting for their turn. X # This means that writers can't easily get starved X # (but see below, readers can). X self.__readers[me] += 1 X return X # No, we aren't a reader (yet), wait for our turn. X else: X # Grant a new read lock, always, in case there are X # no pending writers (and no writer). X self.__readers[me] = self.__readers.get(me,0) + 1 X return X if timeout is not None: X remaining = endtime - time() X if remaining <= 0: X # Timeout has expired, signal caller of this. X raise RuntimeError("Acquiring read lock timed out") X self.__condition.wait(remaining) X else: X self.__condition.wait() X finally: X self.__condition.release() X X def acquireWrite(self,timeout=None): X """Acquire a write lock for the current thread, waiting at most X timeout seconds or doing a non-blocking check in case timeout is <= 0. X X In case the write lock cannot be serviced due to the deadlock X condition mentioned above, a ValueError is raised. X X In case timeout is None, the call to acquireWrite blocks until the X lock request can be serviced. X X In case the timeout expires before the lock could be serviced, a X RuntimeError is thrown.""" X X if timeout is not None: X endtime = time() + timeout X me, upgradewriter = currentThread(), False X self.__condition.acquire() X try: X if self.__writer is me: X # If we are the writer, grant a new write lock, always. X self.__writercount += 1 X return X elif me in self.__readers: X # If we are a reader, no need to add us to pendingwriters, X # we get the upgradewriter slot. X if self.__upgradewritercount: X # If we are a reader and want to upgrade, and someone X # else also wants to upgrade, there is no way we can do X # this except if one of us releases all his read locks. X # Signal this to user. X raise ValueError( X "Inevitable dead lock, denying write lock" X ) X upgradewriter = True X self.__upgradewritercount = self.__readers.pop(me) X else: X # We aren't a reader, so add us to the pending writers queue X # for synchronization with the readers. X self.__pendingwriters.append(me) X while True: X if not self.__readers and self.__writer is None: X # Only test anything if there are no readers and writers. X if self.__upgradewritercount: X if upgradewriter: X # There is a writer to upgrade, and it's us. Take X # the write lock. X self.__writer = me X self.__writercount = self.__upgradewritercount + 1 X self.__upgradewritercount = 0 X return X # There is a writer to upgrade, but it's not us. X # Always leave the upgrade writer the advance slot, X # because he presumes he'll get a write lock directly X # from a previously held read lock. X elif self.__pendingwriters[0] is me: X # If there are no readers and writers, it's always X # fine for us to take the writer slot, removing us X # from the pending writers queue. X # This might mean starvation for readers, though. X self.__writer = me X self.__writercount = 1 X self.__pendingwriters = self.__pendingwriters[1:] X return X if timeout is not None: X remaining = endtime - time() X if remaining <= 0: X # Timeout has expired, signal caller of this. X if upgradewriter: X # Put us back on the reader queue. No need to X # signal anyone of this change, because no other X # writer could've taken our spot before we got X # here (because of remaining readers), as the test X # for proper conditions is at the start of the X # loop, not at the end. X self.__readers[me] = self.__upgradewritercount X self.__upgradewritercount = 0 X else: X # We were a simple pending writer, just remove us X # from the FIFO list. X self.__pendingwriters.remove(me) X raise RuntimeError("Acquiring write lock timed out") X self.__condition.wait(remaining) X else: X self.__condition.wait() X finally: X self.__condition.release() X X def release(self): X """Release the currently held lock. X X In case the current thread holds no lock, a ValueError is thrown.""" X X me = currentThread() X self.__condition.acquire() X try: X if self.__writer is me: X # We are the writer, take one nesting depth away. X self.__writercount -= 1 X if not self.__writercount: X # No more write locks; take our writer position away and X # notify waiters of the new circumstances. X self.__writer = None X self.__condition.notifyAll() X elif me in self.__readers: X # We are a reader currently, take one nesting depth away. X self.__readers[me] -= 1 X if not self.__readers[me]: X # No more read locks, take our reader position away. X del self.__readers[me] X if not self.__readers: X # No more readers, notify waiters of the new X # circumstances. X self.__condition.notifyAll() X else: X raise ValueError("Trying to release unheld lock") X finally: X self.__condition.release() 5d581f7ba61210e7e3f11be9194eea2b echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/error_printer.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/error_printer.py << '1f83a9ce6d6401f6b4b0c3cf9a43955f' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XWSGI middleware to catch application thrown DAVErrors and return proper Xresponses. X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" X__docformat__ = "reStructuredText" X Ximport util Xfrom dav_error import DAVError, getHttpStatusString, asDAVError,\ X HTTP_INTERNAL_ERROR, HTTP_NOT_MODIFIED, HTTP_NO_CONTENT Ximport traceback Ximport sys X X_logger = util.getModuleLogger(__name__) X X#=============================================================================== X# ErrorPrinter X#=============================================================================== Xclass ErrorPrinter(object): X# def __init__(self, application, server_descriptor=None, catchall=False): X def __init__(self, application, catchall=False): X self._application = application X# self._server_descriptor = server_descriptor X self._catch_all_exceptions = catchall X X def __call__(self, environ, start_response): X try: X try: X # request_server app may be a generator (for example the GET handler) X # So we must iterate - not return self._application(..)! X # Otherwise the we could not catch exceptions here. X for v in self._application(environ, start_response): X yield v X except DAVError, e: X _logger.debug("re-raising %s" % e) X raise X except Exception, e: X # Caught a non-DAVError X if self._catch_all_exceptions: X # Catch all exceptions to return as 500 Internal Error X# traceback.print_exc() X util.warn(traceback.format_exc()) X raise asDAVError(e) X else: X util.warn("ErrorPrinter: caught Exception") X util.warn(traceback.format_exc()) X raise X except DAVError, e: X _logger.debug("caught %s" % e) X X status = getHttpStatusString(e) X # Dump internal errors to console X if e.value == HTTP_INTERNAL_ERROR: X util.warn(traceback.format_exc()) X util.warn("e.srcexception:\n%s" % e.srcexception) X elif e.value in (HTTP_NOT_MODIFIED, HTTP_NO_CONTENT): X# util.log("ErrorPrinter: forcing empty error response for %s" % e.value) X # See paste.lint: these code don't have content X start_response(status, [("Content-Length", "0"), X ("Date", util.getRfc1123Time()), X ]) X yield "" X return X X # If exception has pre-/post-condition: return as XML response, X # else return as HTML X content_type, body = e.getResponsePage() X X # TODO: provide exc_info=sys.exc_info()? X start_response(status, [("Content-Type", content_type), X ("Content-Length", str(len(body))), X ("Date", util.getRfc1123Time()), X ]) X yield body X return 1f83a9ce6d6401f6b4b0c3cf9a43955f echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/request_resolver.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/request_resolver.py << '3fbdb2fdcb897d15b79e6f33b3c2422b' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XWSGI middleware that finds the registered mapped DAV-Provider, creates a new XRequestServer instance, and dispatches the request. X X X+-------------------------------------------------------------------------------+ X| The following documentation was taken over from PyFileServer and is outdated! | X+-------------------------------------------------------------------------------+ X X XWsgiDAV file sharing X-------------------- X XWsgiDAV allows the user to specify in wsgidav.conf a number of Xrealms, and a number of users for each realm. X XRealms X Each realm corresponds to a filestructure on disk to be stored, X for example:: X X addShare('pubshare','/home/public/share') X X would allow the users to access using WebDAV the directory/file X structure at /home/public/share from the url X http:////pubshare X X The realm name is set as '/pubshare' X X e.g. /home/public/share/WsgiDAV/LICENSE becomes accessible as X http:////pubshare/WsgiDAV/LICENSE X XUsers X A number of username/password pairs can be set for each realm:: X X adduser('pubshare', 'username', 'password', 'description/unused') X X would add a username/password pair to realm /pubshare. X XNote: if developers wish to maintain a separate users database, you can Xwrite your own domain controller for the HTTPAuthenticator. See Xhttp_authenticator.py and domain_controller.py for more details. X X XRequest Resolver X---------------- X XWSGI middleware for resolving Realm and Paths for the WsgiDAV Xapplication. X XUsage:: X X from wsgidav.request_resolver import RequestResolver X WSGIApp = RequestResolver(InternalWSGIApp) X XThe RequestResolver resolves the requested URL to the following values Xplaced in the environ dictionary. First it resolves the corresponding Xrealm:: X X url: http:////pubshare/WsgiDAV/LICENSE X environ['wsgidav.mappedrealm'] = /pubshare X XBased on the configuration given, the resource abstraction layer for the Xrealm is determined. if no configured abstraction layer is found, the Xdefault abstraction layer fileabstractionlayer.FilesystemAbstractionLayer() Xis used:: X X environ['wsgidav.resourceAL'] = fileabstractionlayer.MyOwnFilesystemAbstractionLayer() X XThe path identifiers for the requested url are then resolved using the Xresource abstraction layer:: X X environ['wsgidav.mappedpath'] = /home/public/share/WsgiDAV/LICENSE X environ['wsgidav.mappedURI'] = /pubshare/WsgiDAV/LICENSE X Xin this case, FilesystemAbstractionLayer resolves any relative paths Xto its canonical absolute path X XThe RequestResolver also resolves any value in the Destination request Xheader, if present, to:: X X Destination: http:////pubshare/WsgiDAV/LICENSE-dest X environ['wsgidav.destrealm'] = /pubshare X environ['wsgidav.destpath'] = /home/public/share/WsgiDAV/LICENSE-dest X environ['wsgidav.destURI'] = /pubshare/WsgiDAV/LICENSE X environ['wsgidav.destresourceAL'] = fileabstractionlayer.MyOwnFilesystemAbstractionLayer() X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Ximport util Xfrom dav_error import DAVError, HTTP_NOT_FOUND Xfrom request_server import RequestServer X X__docformat__ = "reStructuredText" X X# NOTE (Martin Wendt, 2009-05): X# The following remarks were made by Ian Bicking when reviewing PyFileServer in 2005. X# I leave them here after my refactoring for reference. X# X#Remarks: X#@@: If this were just generalized URL mapping, you'd map it like: X# Incoming: X# SCRIPT_NAME=; PATH_INFO=/pubshare/PyFileServer/LICENSE X# After transforamtion: X# SCRIPT_NAME=/pubshare; PATH_INFO=/PyFileServer/LICENSE X# Then you dispatch to the application that serves '/home/public/share/' X# X# This uses SCRIPT_NAME and PATH_INFO exactly how they are intended to be X# used -- they give context about where you are (SCRIPT_NAME) and what you X# still have to handle (PATH_INFO) X# X# An example of an dispatcher that does this is paste.urlmap, and you use it X# like: X# X# urlmap = paste.urlmap.URLMap() X# # urlmap is a WSGI application X# urlmap['/pubshare'] = PyFileServerForPath('/home/public/share') X# X# Now, that requires that you have a server that is easily X# instantiated, but that's kind of a separate concern -- what you X# really want is to do more general configuration at another level. E.g., X# you might have:: X# X# app = config(urlmap, config_file) X# X# Which adds the configuration from that file to the request, and X# PyFileServerForPath then fetches that configuration. paste.deploy X# has another way of doing that at instantiation-time; either way X# though you want to inherit configuration you can still use more general X# dispatching. X# X# Incidentally some WebDAV servers do redirection based on the user X# agent (Zope most notably). This is because of how WebDAV reuses X# GET in an obnxious way, so that if you want to use WebDAV on pages X# that also include dynamic content you have to mount the whole X# thing at another point in the URL space, so you can GET the X# content without rendering the dynamic parts. I don't actually X# like using user agents -- I'd rather mount the same resources at X# two different URLs -- but it's just an example of another kind of X# dispatching that can be done at a higher level. X# X X#=============================================================================== X# RequestResolver X#=============================================================================== Xclass RequestResolver(object): X X def __init__(self): X pass X X X def __call__(self, environ, start_response): X path = environ["PATH_INFO"] X X # We want to answer OPTIONS(*), even if no handler was registered for X # the top-level realm (e.g. required to map drive letters). X X # Hotfix for WinXP / Vista: accept '/' for a '*' X if environ["REQUEST_METHOD"] == "OPTIONS" and path in ("/", "*"): X # Answer HTTP 'OPTIONS' method on server-level. X # From RFC 2616: X # If the Request-URI is an asterisk ("*"), the OPTIONS request is X # intended to apply to the server in general rather than to a specific X # resource. Since a server's communication options typically depend on X # the resource, the "*" request is only useful as a "ping" or "no-op" X # type of method; it does nothing beyond allowing the client to test the X # capabilities of the server. For example, this can be used to test a X # proxy for HTTP/1.1 compliance (or lack thereof). X start_response("200 OK", [("Content-Type", "text/html"), X ("Content-Length", "0"), X ("DAV", "1,2"), X ("Server", "DAV/2"), X ("Date", util.getRfc1123Time()), X ]) X yield "" X return X X provider = environ["wsgidav.provider"] X if provider is None: X raise DAVError(HTTP_NOT_FOUND, X "Could not find resource provider for '%s'" % path) X X # Let the appropriate resource provider for the realm handle the request X app = RequestServer(provider) X for v in app(environ, start_response): X yield v X return 3fbdb2fdcb897d15b79e6f33b3c2422b echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/http_authenticator.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/http_authenticator.py << '4e0b8debb48861c7fc7bcb18244eea36' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XWSGI middleware for HTTP basic and digest authentication. X XUsage:: X X from http_authenticator import HTTPAuthenticator X X WSGIApp = HTTPAuthenticator(ProtectedWSGIApp, domain_controller, acceptbasic, X acceptdigest, defaultdigest) X X where: X ProtectedWSGIApp is the application requiring authenticated access X X domain_controller is a domain controller object meeting specific X requirements (below) X X acceptbasic is a boolean indicating whether to accept requests using X the basic authentication scheme (default = True) X X acceptdigest is a boolean indicating whether to accept requests using X the digest authentication scheme (default = True) X X defaultdigest is a boolean. if True, an unauthenticated request will X be sent a digest authentication required response, else the unauthenticated X request will be sent a basic authentication required response X (default = True) X XThe HTTPAuthenticator will put the following authenticated information in the Xenviron dictionary:: X X environ["http_authenticator.realm"] = realm name X environ["http_authenticator.username"] = username X X X**Domain Controllers** X XThe HTTP basic and digest authentication schemes are based on the following Xconcept: X XEach requested relative URI can be resolved to a realm for authentication, Xfor example: X/fac_eng/courses/ee5903/timetable.pdf -> might resolve to realm 'Engineering General' X/fac_eng/examsolns/ee5903/thisyearssolns.pdf -> might resolve to realm 'Engineering Lecturers' X/med_sci/courses/m500/surgery.htm -> might resolve to realm 'Medical Sciences General' Xand each realm would have a set of username and password pairs that would Xallow access to the resource. X XA domain controller provides this information to the HTTPAuthenticator. XThis allows developers to write their own domain controllers, that might, Xfor example, interface with their own user database. X Xfor simple applications, a SimpleDomainController is provided that will take Xin a single realm name (for display) and a single dictionary of username (key) Xand password (value) string pairs X XUsage:: X X from http_authenticator import SimpleDomainController X users = dict(({'John Smith': 'YouNeverGuessMe', 'Dan Brown': 'DontGuessMeEither'}) X realm = 'Sample Realm' X domain_controller = SimpleDomainController(users, realm) X X XDomain Controllers must provide the methods as described in X``wsgidav.interfaces.domaincontrollerinterface`` (interface_) X X.. _interface : interfaces/domaincontrollerinterface.py X XThe environ variable here is the WSGI 'environ' dictionary. It is passed to Xall methods of the domain controller as a means for developers to pass information Xfrom previous middleware or server config (if required). X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" X__docformat__ = "reStructuredText" X Ximport random Ximport base64 Xtry: X from hashlib import md5 Xexcept ImportError: X from md5 import md5 Ximport time Ximport re Ximport util X X_logger = util.getModuleLogger(__name__, True) X X# HOTFIX for Windows XP (Microsoft-WebDAV-MiniRedir/5.1.2600): X# When accessing a share '/dav/', XP sometimes sends digests for '/'. X# With this fix turned on, we allow '/' digests, when a matching '/dav' account X# is present. XHOTFIX_WINXP_AcceptRootShareLogin = True X X Xclass SimpleDomainController(object): X """SimpleDomainController : Simple domain controller for HTTPAuthenticator.""" X def __init__(self, dictusers = None, realmname = "SimpleDomain"): X if dictusers is None: X self._users = dict({"John Smith": "YouNeverGuessMe"}) X else: X self._users = dictusers X self._realmname = realmname X X def getDomainRealm(self, inputRelativeURL, environ): X return self._realmname X X def requireAuthentication(self, realmname, environ): X return True X X def isRealmUser(self, realmname, username, environ): X return username in self._users X X def getRealmUserPassword(self, realmname, username, environ): X if username in self._users: X return self._users[username] X return None X X def authDomainUser(self, realmname, username, password, environ): X if username in self._users: X return self._users[username] == password X return False X X X#=============================================================================== X# HTTPAuthenticator X#=============================================================================== Xclass HTTPAuthenticator(object): X """WSGI Middleware for basic and digest authenticator.""" X def __init__(self, application, domaincontroller, acceptbasic=True, acceptdigest=True, defaultdigest=True): X self._domaincontroller = domaincontroller X self._application = application X self._noncedict = dict([]) X X self._headerparser = re.compile(r"([\w]+)=([^,]*),") X self._headermethod = re.compile(r"^([\w]+)") X X self._acceptbasic = acceptbasic X self._acceptdigest = acceptdigest X self._defaultdigest = defaultdigest X X X def __call__(self, environ, start_response): X realmname = self._domaincontroller.getDomainRealm(environ["PATH_INFO"], environ) X X if not self._domaincontroller.requireAuthentication(realmname, environ): X # no authentication needed X _logger.debug("No authorization required for realm '%s'" % realmname) X environ["http_authenticator.realm"] = realmname X environ["http_authenticator.username"] = "" X return self._application(environ, start_response) X X if "HTTP_AUTHORIZATION" in environ: X authheader = environ["HTTP_AUTHORIZATION"] X authmatch = self._headermethod.search(authheader) X authmethod = "None" X if authmatch: X authmethod = authmatch.group(1).lower() X X if authmethod == "digest" and self._acceptdigest: X return self.authDigestAuthRequest(environ, start_response) X elif authmethod == "digest" and self._acceptbasic: X return self.sendBasicAuthResponse(environ, start_response) X elif authmethod == "basic" and self._acceptbasic: X return self.authBasicAuthRequest(environ, start_response) X X util.log("HTTPAuthenticator: respond with 400 Bad request; Auth-Method: %s" % authmethod) X X start_response("400 Bad Request", [("Content-Length", "0"), X ("Date", util.getRfc1123Time()), X ]) X return [""] X X X if self._defaultdigest: X return self.sendDigestAuthResponse(environ, start_response) X return self.sendBasicAuthResponse(environ, start_response) X X X def sendBasicAuthResponse(self, environ, start_response): X realmname = self._domaincontroller.getDomainRealm(environ["PATH_INFO"] , environ) X _logger.debug("401 Not Authorized for realm '%s' (basic)" % realmname) X wwwauthheaders = "Basic realm=\"" + realmname + "\"" X X body = self.getErrorMessage() X start_response("401 Not Authorized", [("WWW-Authenticate", wwwauthheaders), X ("Content-Type", "text/html"), X ("Content-Length", str(len(body))), X ("Date", util.getRfc1123Time()), X ]) X return [ body ] X X X def authBasicAuthRequest(self, environ, start_response): X realmname = self._domaincontroller.getDomainRealm(environ["PATH_INFO"] , environ) X authheader = environ["HTTP_AUTHORIZATION"] X authvalue = "" X try: X authvalue = authheader[len("Basic "):] X except: X authvalue = "" X authvalue = authvalue.strip().decode("base64") X username, password = authvalue.split(":",1) X X if self._domaincontroller.authDomainUser(realmname, username, password, environ): X environ["http_authenticator.realm"] = realmname X environ["http_authenticator.username"] = username X return self._application(environ, start_response) X return self.sendBasicAuthResponse(environ, start_response) X X X def sendDigestAuthResponse(self, environ, start_response): X realmname = self._domaincontroller.getDomainRealm(environ["PATH_INFO"] , environ) X random.seed() X serverkey = hex(random.getrandbits(32))[2:] X etagkey = md5(environ["PATH_INFO"]).hexdigest() X timekey = str(time.time()) X nonce = base64.b64encode(timekey + md5(timekey + ":" + etagkey + ":" + serverkey).hexdigest()) X wwwauthheaders = "Digest realm=\"" + realmname + "\", nonce=\"" + nonce + \ X "\", algorithm=\"MD5\", qop=\"auth\"" X _logger.debug("401 Not Authorized for realm '%s' (digest): %s" % (realmname, wwwauthheaders)) X X body = self.getErrorMessage() X# start_response("403 Forbidden", [("WWW-Authenticate", wwwauthheaders), X start_response("401 Not Authorized", [("WWW-Authenticate", wwwauthheaders), X ("Content-Type", "text/html"), X ("Content-Length", str(len(body))), X ("Date", util.getRfc1123Time()), X ]) X return [ body ] X X X def authDigestAuthRequest(self, environ, start_response): X X realmname = self._domaincontroller.getDomainRealm(environ["PATH_INFO"] , environ) X X isinvalidreq = False X X authheaderdict = dict([]) X authheaders = environ["HTTP_AUTHORIZATION"] + "," X if not authheaders.lower().strip().startswith("digest"): X isinvalidreq = True X authheaderlist = self._headerparser.findall(authheaders) X for authheader in authheaderlist: X authheaderkey = authheader[0] X authheadervalue = authheader[1].strip().strip("\"") X authheaderdict[authheaderkey] = authheadervalue X X _logger.debug("authDigestAuthRequest: %s" % environ["HTTP_AUTHORIZATION"]) X _logger.debug(" -> %s" % authheaderdict) X X if "username" in authheaderdict: X req_username = authheaderdict["username"] X req_username_org = req_username X # Hotfix for Windows XP: X # net use W: http://127.0.0.1/dav /USER:DOMAIN\tester tester X # will send the name with double backslashes ('DOMAIN\\tester') X # but send the digest for the simple name ('DOMAIN\tester'). X if r"\\" in req_username: X req_username = req_username.replace("\\\\", "\\") X _logger.info("Fixing Windows name with double backslash: '%s' --> '%s'" % (req_username_org, req_username)) X X if not self._domaincontroller.isRealmUser(realmname, req_username, environ): X isinvalidreq = True X else: X isinvalidreq = True X X # TODO: Chun added this comments, but code was commented out X # Do not do realm checking - a hotfix for WinXP using some other realm's X # auth details for this realm - if user/password match X# print authheaderdict.get("realm"), realmname X if 'realm' in authheaderdict: X if authheaderdict["realm"].upper() != realmname.upper(): X if HOTFIX_WINXP_AcceptRootShareLogin: X # Hotfix: also accept '/' X if authheaderdict["realm"].upper() != "/": X isinvalidreq = True X else: X isinvalidreq = True X X if "algorithm" in authheaderdict: X if authheaderdict["algorithm"].upper() != "MD5": X isinvalidreq = True # only MD5 supported X X if "uri" in authheaderdict: X req_uri = authheaderdict["uri"] X X if "nonce" in authheaderdict: X req_nonce = authheaderdict["nonce"] X else: X isinvalidreq = True X X req_hasqop = False X if "qop" in authheaderdict: X req_hasqop = True X req_qop = authheaderdict["qop"] X if req_qop.lower() != "auth": X isinvalidreq = True # only auth supported, auth-int not supported X else: X req_qop = None X X if "cnonce" in authheaderdict: X req_cnonce = authheaderdict["cnonce"] X else: X req_cnonce = None X if req_hasqop: X isinvalidreq = True X X if "nc" in authheaderdict: # is read but nonce-count checking not implemented X req_nc = authheaderdict["nc"] X else: X req_nc = None X if req_hasqop: X isinvalidreq = True X X if "response" in authheaderdict: X req_response = authheaderdict["response"] X else: X isinvalidreq = True X X if not isinvalidreq: X req_password = self._domaincontroller.getRealmUserPassword(realmname, req_username, environ) X X req_method = environ["REQUEST_METHOD"] X X required_digest = self.computeDigestResponse(req_username, realmname, req_password, req_method, req_uri, req_nonce, req_cnonce, req_qop, req_nc) X X if required_digest != req_response: X _logger.warning("computeDigestResponse('%s', '%s', ...): %s != %s" % (realmname, req_username, required_digest, req_response)) X if HOTFIX_WINXP_AcceptRootShareLogin: X # Hotfix: also accept '/' digest X root_digest = self.computeDigestResponse(req_username, "/", req_password, req_method, req_uri, req_nonce, req_cnonce, req_qop, req_nc) X if root_digest == req_response: X _logger.warning("authDigestAuthRequest: HOTFIX: accepting '/' login for '%s'." % realmname) X else: X isinvalidreq = True X else: X isinvalidreq = True X else: X# _logger.debug("digest succeeded for realm '%s', user '%s'" % (realmname, req_username)) X pass X X if isinvalidreq: X _logger.warning("Authentication failed for user '%s', realm '%s'" % (req_username, realmname)) X return self.sendDigestAuthResponse(environ, start_response) X X environ["http_authenticator.realm"] = realmname X environ["http_authenticator.username"] = req_username X return self._application(environ, start_response) X X X def computeDigestResponse(self, username, realm, password, method, uri, nonce, cnonce, qop, nc): X A1 = username + ":" + realm + ":" + password X A2 = method + ":" + uri X if qop: X digestresp = self.md5kd( self.md5h(A1), nonce + ":" + nc + ":" + cnonce + ":" + qop + ":" + self.md5h(A2)) X else: X digestresp = self.md5kd( self.md5h(A1), nonce + ":" + self.md5h(A2)) X return digestresp X X X def md5h(self, data): X return md5(data).hexdigest() X X X def md5kd(self, secret, data): X return self.md5h(secret + ":" + data) X X X def getErrorMessage(self): X message = """\ X401 Access not authorized X X

401 Access not authorized

X X X """ X return message 4e0b8debb48861c7fc7bcb18244eea36 echo c - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples mkdir -p py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples > /dev/null 2>&1 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/dav_provider_tools.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/dav_provider_tools.py << '5bbecf74265cb6ec252ffd6cf9d4c673' X# -*- coding: iso-8859-1 -*- X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" X X""" Ximport stat Ximport os X#import mimetypes Xtry: X from cStringIO import StringIO Xexcept ImportError: X from StringIO import StringIO #@UnusedImport Xfrom wsgidav.dav_provider import DAVCollection, DAVNonCollection Xfrom wsgidav import util X X__docformat__ = "reStructuredText en" X X_logger = util.getModuleLogger(__name__) X X#=============================================================================== X# VirtualCollection X#=============================================================================== X Xclass VirtualCollection(DAVCollection): X """Abstract base class for collections that contain a list of static members. X X Member names are passed to the constructor. X getMember() is implemented by calling self.provider.getResourceInst() X """ X def __init__(self, path, environ, displayInfo, memberNameList): X DAVCollection.__init__(self, path, environ) X if isinstance(displayInfo, basestring): X displayInfo = { "type": displayInfo } X assert type(displayInfo) is dict X assert type(memberNameList) is list X self.displayInfo = displayInfo X self.memberNameList = memberNameList X X def getDisplayInfo(self): X return self.displayInfo X X def getMemberNames(self): X return self.memberNameList X X def preventLocking(self): X """Return True, since we don't want to lock virtual collections.""" X return True X X def getMember(self, name): X# raise NotImplementedError() X return self.provider.getResourceInst(util.joinUri(self.path, name), X self.environ) X X X X#=============================================================================== X# _VirtualNonCollection classes X#=============================================================================== Xclass _VirtualNonCollection(DAVNonCollection): X """Abstract base class for all non-collection resources.""" X def __init__(self, path, environ): X DAVNonCollection.__init__(self, path, False, environ) X def getContentLength(self): X return None X def getContentType(self): X return None X def getCreationDate(self): X return None X def getDisplayName(self): X return self.name X def getDisplayInfo(self): X raise NotImplementedError() X def getEtag(self): X return None X def getLastModified(self): X return None X def supportRanges(self): X return False X# def handleDelete(self): X# raise DAVError(HTTP_FORBIDDEN) X# def handleMove(self, destPath): X# raise DAVError(HTTP_FORBIDDEN) X# def handleCopy(self, destPath, depthInfinity): X# raise DAVError(HTTP_FORBIDDEN) X X X#=============================================================================== X# VirtualTextResource X#=============================================================================== Xclass VirtualTextResource(_VirtualNonCollection): X """A virtual file, containing a string.""" X def __init__(self, path, environ, content, X displayName=None, displayType=None): X _VirtualNonCollection.__init__(self, path, environ) X self.content = content X self.displayName = displayName X self.displayType = displayType X def getContentLength(self): X return len(self.getContent().read()) X def getContentType(self): X if self.name.endswith(".txt"): X return "text/plain" X return "text/html" X def getDisplayName(self): X return self.displayName or self.name X def getDisplayInfo(self): X return {"type": "Virtual info file"} X def preventLocking(self): X return True X# def getRefUrl(self): X# refPath = "/by_key/%s/%s" % (self._data["key"], self.name) X# return urllib.quote(self.provider.sharePath + refPath) X def getContent(self): X return StringIO(self.content) X X X#=============================================================================== X# FileResource X#=============================================================================== Xclass FileResource(_VirtualNonCollection): X """Represents an existing file.""" X BUFFER_SIZE = 8192 X def __init__(self, path, environ, filePath): X if not os.path.exists(filePath): X util.warn("FileResource(%r) does not exist." % filePath) X _VirtualNonCollection.__init__(self, path, environ) X self.filePath = filePath X def getContentLength(self): X statresults = os.stat(self.filePath) X return statresults[stat.ST_SIZE] X def getContentType(self): X if not os.path.isfile(self.filePath): X return "text/html" X# (mimetype, _mimeencoding) = mimetypes.guess_type(self.filePath) X# if not mimetype: X# mimetype = "application/octet-stream" X# return mimetype X return util.guessMimeType(self.filePath) X def getCreationDate(self): X statresults = os.stat(self.filePath) X return statresults[stat.ST_CTIME] X def getDisplayInfo(self): X return {"type": "File"} X def getLastModified(self): X statresults = os.stat(self.filePath) X return statresults[stat.ST_MTIME] X# def getRefUrl(self): X# refPath = "/by_key/%s/%s" % (self._data["key"], os.path.basename(self.filePath)) X# return urllib.quote(self.provider.sharePath + refPath) X def getContent(self): X mime = self.getContentType() X if mime.startswith("text"): X return file(self.filePath, "r", FileResource.BUFFER_SIZE) X return file(self.filePath, "rb", FileResource.BUFFER_SIZE) X X X#=============================================================================== X# Resolvers X#=============================================================================== Xclass DAVResolver(object): X """Return a DAVResource object for a path (None, if not found).""" X def __init__(self, parentResolver, name): X self.parentResolver = parentResolver X self.name = name X def resolve(self, scriptName, pathInfo, environ): X raise NotImplementedError 5bbecf74265cb6ec252ffd6cf9d4c673 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/__init__.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/__init__.py << 'edd091e52206b39d99f3318118add82e' edd091e52206b39d99f3318118add82e echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/mongo_dav_provider.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/mongo_dav_provider.py << '17710ca38713a46d9dda54c1efe784f0' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplementation of a WebDAV provider that provides a very basic, read-only Xresource layer emulation of a MongoDB database. X XUsage: add the following entries to wsgidav.conf:: X X from wsgidav.samples.mongo_dav_provider import MongoResourceProvider X mongo_dav_opts = {} X addShare("mongo", MongoResourceProvider(mongo_dav_opts)) X XValid options are (sample shows defaults):: X X opts = {"host": "localhost", # MongoDB server X "port": 27017, # MongoDB port X # This options are used with `mongod --auth` X # The user must be created in the admin db with X # > use admin X # > db.addUser(username, password) X "user": None, # Authenticate with this user X "pwd": None, # ... and password X } X X""" Xfrom wsgidav.dav_provider import DAVProvider, DAVCollection, DAVNonCollection Xfrom wsgidav import util Ximport pymongo Xfrom wsgidav.util import joinUri Xfrom pprint import pformat Xfrom bson.objectid import ObjectId Xtry: X from cStringIO import StringIO Xexcept ImportError: X from StringIO import StringIO #@UnusedImport X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X X X#=============================================================================== X# X#=============================================================================== Xclass ConnectionCollection(DAVCollection): X """Root collection, lists all mongo databases.""" X def __init__(self, path, environ): X DAVCollection.__init__(self, path, environ) X self.conn = self.provider.conn X X def getMemberNames(self): X return [ name.encode("utf8") for name in self.conn.database_names() ] X X def getMember(self, name): X return DbCollection(joinUri(self.path, name), self.environ) X X Xclass DbCollection(DAVCollection): X """Mongo database, contains mongo collections.""" X def __init__(self, path, environ): X DAVCollection.__init__(self, path, environ) X self.conn = self.provider.conn X self.db = self.conn[self.name] X X def getDisplayInfo(self): X return {"type": "Mongo database"} X X def getMemberNames(self): X return [ name.encode("utf8") for name in self.db.collection_names() ] X X def getMember(self, name): X coll = self.db[name] X return CollCollection(joinUri(self.path, name), self.environ, coll) X X Xclass CollCollection(DAVCollection): X """Mongo collections, contains mongo documents.""" X def __init__(self, path, environ, coll): X DAVCollection.__init__(self, path, environ) X self.conn = self.provider.conn X self.coll = coll X X def getDisplayInfo(self): X return {"type": "Mongo collection"} X X def getMemberNames(self): X res = [] X for doc in self.coll.find(): X res.append(str(doc["_id"])) X return res X X def getMember(self, name): X doc = self.coll.find_one(ObjectId(name)) X return DocResource(joinUri(self.path, name), self.environ, doc) X X Xclass DocResource(DAVNonCollection): X """Mongo document, returned as virtual text resource.""" X def __init__(self, path, environ, doc): X DAVNonCollection.__init__(self, path, environ) X self.doc = doc X def getContent(self): X html = "
" + pformat(self.doc) + "
" X return StringIO(html.encode("utf8")) X def getContentLength(self): X return len(self.getContent().read()) X def getContentType(self): X return "text/html" X def getDisplayName(self): X doc = self.doc X if doc.get("_title"): X return doc["_title"].encode("utf8") X elif doc.get("title"): X return doc["title"].encode("utf8") X elif doc.get("_id"): X return str(doc["_id"]) X return str(doc["key"]) X def getDisplayInfo(self): X return {"type": "Mongo document"} X X X#=============================================================================== X# MongoResourceProvider X#=============================================================================== Xclass MongoResourceProvider(DAVProvider): X """DAV provider that serves a MongoDB structure.""" X def __init__(self, options): X super(MongoResourceProvider, self).__init__() X self.options = options X self.conn = pymongo.Connection(options.get("host"), options.get("port")) X if options.get("user"): X # If credentials are passed, acquire root access X db = self.conn["admin"] X res = db.authenticate(options.get("user"), options.get("pwd")) X if not res: X raise RuntimeError("Failed to logon to db %s as user %s" % X (db.name, options.get("user"))) X util.log("Logged on to mongo db '%s' as user '%s'" % (db.name, options.get("user"))) X util.log("MongoResourceProvider connected to %s" % self.conn) X X def getResourceInst(self, path, environ): X """Return DAVResource object for path. X X See DAVProvider.getResourceInst() X """ X _logger.info("getResourceInst('%s')" % path) X self._count_getResourceInst += 1 X root = ConnectionCollection("/", environ) X return root.resolve("/", path) X X X#=============================================================================== X# Main X#=============================================================================== Xdef test(): X pass X Xif __name__ == "__main__": X test() 17710ca38713a46d9dda54c1efe784f0 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/virtual_dav_provider.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/samples/virtual_dav_provider.py << 'e4ae967170dfb1ad6f630c9e70aec015' X# -*- coding: iso-8859-1 -*- X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XSample implementation of a DAV provider that provides a browsable, Xmulti-categorized resource tree. X XNote that this is simply an example with no concrete real world benefit. XBut it demonstrates some techniques to customize WsgiDAV. X XCompared to a published file system, we have these main differences: X X#. A resource like ``My doc 1`` has several attributes like ``key``, X ``orga``, ``tags``, ``status``, ``description``. X Also there may be a list of attached files. X#. These attributes are used to dynamically create a virtual hierarchy. X For example, if ``status`` is ``draft``, a collection X ``/by_status/draft/`` is created and the resource is mapped to X ``/by_status/draft/My doc 1``. X#. The resource ``My doc 1`` is rendered as a collection, that contains X some virtual descriptive files and the attached files. X#. The same resource may be referenced using different paths X For example ``/by_tag/cool/My doc 1``, X ``/by_tag/hot/My doc 1``, and ``/by_key/1`` map to the same X resource. X Only the latter is considered the *real-path*, all others are X *virtual-paths*. X#. The attributes are exposed as live properties, like "{virtres:}key", X "{virtres:}tags", and "{virtres:}description". X Some of them are even writable. Note that modifying an attribute may also X change the dynamically created tree structure. X For example changing "{virtres:}status" from 'draft' to 'published' will X make the resource appear as ``/by_status/published/My doc 1``. X#. This provider implements native delete/move/copy methods, to change the X semantics of these operations for the virtual '/by_tag/' collection. X For example issuing a DELETE on ``/by_tag/cool/My doc 1`` will X simply remove the 'cool' tag from that resource. X#. Virtual collections and artifacts cannot be locked. X However a resource can be locked. X For example locking ``/by_tag/cool/My doc 1`` will also lock X ``/by_key/1``. X#. Some paths may be hidden, i.e. by_key is not browsable (but can be referenced) X TODO: is this WebDAV compliant? X XThe *database* is a simple hard coded variable ``_resourceData``, that contains Xa list of resource description dictionaries. X XA resource is served as an collection, which is generated on-the-fly and Xcontains some virtual files (*artifacts*). X XIn general, a URL is interpreted like this:: X X //// X X XAn example layout:: X X / X by_tag/ X cool/ X My doc 1/ X .Info.html X .Info.txt X .Description.txt X MySpec.pdf X MySpec.doc X My doc 2/ X hot/ X My doc 1/ X My doc 2/ X nice/ X My doc 2/ X My doc 3 X by_orga/ X development/ X My doc 3/ X marketing/ X My doc 1/ X My doc 2/ X by_status/ X draft/ X My doc 2 X published/ X My doc 1 X My doc 3 X by_key/ X 1/ X 2/ X 3/ X XWhen accessed using WebDAV, the following URLs both return the same resource X'My doc 1':: X X /by_tag/cool/My doc 1 X /by_tag/hot/My doc 1 X /by_key/1 X""" Ximport urllib Ximport stat Ximport os X#import mimetypes Xfrom wsgidav.util import joinUri Xtry: X from cStringIO import StringIO Xexcept ImportError: X from StringIO import StringIO #@UnusedImport Xfrom wsgidav.dav_provider import DAVProvider, DAVNonCollection, DAVCollection Xfrom wsgidav.dav_error import DAVError, HTTP_FORBIDDEN, HTTP_INTERNAL_ERROR,\ X PRECONDITION_CODE_ProtectedProperty Xfrom wsgidav import util X X__docformat__ = "reStructuredText en" X X_logger = util.getModuleLogger(__name__) X XBUFFER_SIZE = 8192 X X#=============================================================================== X# Fake hierarchical repository X#=============================================================================== X""" XThis is a dummy 'database', that serves as an example source for the XVirtualResourceProvider. X XAll files listed in resPathList are expected to exist in FILE_FOLDER. X""" XFILE_FOLDER = r"c:\temp\virtfiles" X X_resourceData = [ X {"key": "1", X "title": "My doc 1", X "orga": "development", X "tags": ["cool", "hot"], X "status": "draft", X "description": "This resource contains two specification files.", X "resPathList": [os.path.join(FILE_FOLDER, "MySpec.doc"), X os.path.join(FILE_FOLDER, "MySpec.pdf"), X ], X }, X {"key": "2", X "title": "My doc 2", X "orga": "development", X "tags": ["cool", "nice"], X "status": "published", X "description": "This resource contains one file.", X "resPathList": [os.path.join(FILE_FOLDER, "My URS.doc"), X ], X }, X {"key": "3", X "title": u"My doc (euro:\u20AC, uuml:ü€)".encode("utf8"), X "orga": "marketing", X "tags": ["nice"], X "status": "published", X "description": "Long text describing it", X "resPathList": [os.path.join(FILE_FOLDER, "My URS.doc"), X ], X }, X ] X X Xdef _getResListByAttr(attrName, attrVal): X """""" X assert attrName in RootCollection._visibleMemberNames X if attrName == "by_status": X return [ data for data in _resourceData if data.get("status") == attrVal ] X elif attrName == "by_orga": X resList =[ data for data in _resourceData if data.get("orga") == attrVal ] X elif attrName == "by_tag": X resList =[ data for data in _resourceData if attrVal in data.get("tags") ] X return resList X X Xdef _getResByKey(key): X """""" X for data in _resourceData: X if data["key"] == key: X return data X return None X X X#=============================================================================== X# X#=============================================================================== Xclass RootCollection(DAVCollection): X """Resolve top-level requests '/'.""" X _visibleMemberNames = ("by_orga", "by_tag", "by_status") X _validMemberNames = _visibleMemberNames + ("by_key", ) X X def __init__(self, environ): X DAVCollection.__init__(self, "/", environ) X X def getMemberNames(self): X return self._visibleMemberNames X X def getMember(self, name): X # Handle visible categories and also /by_key/... X if name in self._validMemberNames: X return CategoryTypeCollection(joinUri(self.path, name), self.environ) X return None X X Xclass CategoryTypeCollection(DAVCollection): X """Resolve '/catType' URLs, for example '/by_tag'.""" X def __init__(self, path, environ): X DAVCollection.__init__(self, path, environ) X X def getDisplayInfo(self): X return {"type": "Category type"} X X def getMemberNames(self): X names = [] X for data in _resourceData: X if self.name == "by_status": X if not data["status"] in names: X names.append(data["status"]) X elif self.name == "by_orga": X if not data["orga"] in names: X names.append(data["orga"]) X elif self.name == "by_tag": X for tag in data["tags"]: X if not tag in names: X names.append(tag) X X names.sort() X return names X X def getMember(self, name): X if self.name == "by_key": X data = _getResByKey(name) X if data: X return VirtualResource(joinUri(self.path, name), self.environ, data) X else: X return None X return CategoryCollection(joinUri(self.path, name), self.environ, self.name) X X Xclass CategoryCollection(DAVCollection): X """Resolve '/catType/cat' URLs, for example '/by_tag/cool'.""" X def __init__(self, path, environ, catType): X DAVCollection.__init__(self, path, environ) X self.catType = catType X X def getDisplayInfo(self): X return {"type": "Category"} X X def getMemberNames(self): X names = [ data["title"] for data in _getResListByAttr(self.catType, self.name) ] X names.sort() X return names X X def getMember(self, name): X for data in _getResListByAttr(self.catType, self.name): X if data["title"] == name: X return VirtualResource(joinUri(self.path, name), self.environ, data) X return None X X X#=============================================================================== X# VirtualResource X#=============================================================================== Xclass VirtualResource(DAVCollection): X """A virtual 'resource', displayed as a collection of artifacts and files.""" X _artifactNames = (".Info.txt", X ".Info.html", X ".Description.txt", X# ".Admin.html", X ) X _supportedProps = ["{virtres:}key", X "{virtres:}title", X "{virtres:}status", X "{virtres:}orga", X "{virtres:}tags", X "{virtres:}description", X ] X X def __init__(self, path, environ, data): X DAVCollection.__init__(self, path, environ) X self.data = data X X def getDisplayInfo(self): X return {"type": "Virtual Resource"} X X def getMemberNames(self): X names = list(self._artifactNames) X for f in self.data["resPathList"]: X name = os.path.basename(f) X names.append(name) X return names X X def getMember(self, name): X if name in self._artifactNames: X return VirtualArtifact(joinUri(self.path, name), self.environ, self.data) X for filePath in self.data["resPathList"]: X fname = os.path.basename(filePath) X if fname == name: X return VirtualResFile(joinUri(self.path, name), self.environ, self.data, filePath) X return None X X def handleDelete(self): X """Change semantic of DELETE to remove resource tags.""" X # DELETE is only supported for the '/by_tag/' collection X if not "/by_tag/" in self.path: X raise DAVError(HTTP_FORBIDDEN) X # path must be '/by_tag//' X catType, tag, _rest = util.saveSplit(self.path.strip("/"), "/", 2) X assert catType == "by_tag" X assert tag in self.data["tags"] X self.data["tags"].remove(tag) X return True # OK X X def handleCopy(self, destPath, depthInfinity): X """Change semantic of COPY to add resource tags.""" X # destPath must be '/by_tag//' X if not "/by_tag/" in destPath: X raise DAVError(HTTP_FORBIDDEN) X catType, tag, _rest = util.saveSplit(destPath.strip("/"), "/", 2) X assert catType == "by_tag" X if not tag in self.data["tags"]: X self.data["tags"].append(tag) X return True # OK X X def handleMove(self, destPath): X """Change semantic of MOVE to change resource tags.""" X # path and destPath must be '/by_tag//' X if not "/by_tag/" in self.path: X raise DAVError(HTTP_FORBIDDEN) X if not "/by_tag/" in destPath: X raise DAVError(HTTP_FORBIDDEN) X catType, tag, _rest = util.saveSplit(self.path.strip("/"), "/", 2) X assert catType == "by_tag" X assert tag in self.data["tags"] X self.data["tags"].remove(tag) X catType, tag, _rest = util.saveSplit(destPath.strip("/"), "/", 2) X assert catType == "by_tag" X if not tag in self.data["tags"]: X self.data["tags"].append(tag) X return True # OK X X def getRefUrl(self): X refPath = "/by_key/%s" % self.data["key"] X return urllib.quote(self.provider.sharePath + refPath) X X def getPropertyNames(self, isAllProp): X """Return list of supported property names in Clark Notation. X X See DAVResource.getPropertyNames() X """ X # Let base class implementation add supported live and dead properties X propNameList = super(VirtualResource, self).getPropertyNames(isAllProp) X # Add custom live properties (report on 'allprop' and 'propnames') X propNameList.extend(VirtualResource._supportedProps) X return propNameList X X def getPropertyValue(self, propname): X """Return the value of a property. X X See getPropertyValue() X """ X # Supported custom live properties X if propname == "{virtres:}key": X return self.data["key"] X elif propname == "{virtres:}title": X return self.data["title"] X elif propname == "{virtres:}status": X return self.data["status"] X elif propname == "{virtres:}orga": X return self.data["orga"] X elif propname == "{virtres:}tags": X # 'tags' is a string list X return ",".join(self.data["tags"]) X elif propname == "{virtres:}description": X return self.data["description"] X # Let base class implementation report live and dead properties X return super(VirtualResource, self).getPropertyValue(propname) X X def setPropertyValue(self, propname, value, dryRun=False): X """Set or remove property value. X X See DAVResource.setPropertyValue() X """ X if value is None: X # We can never remove properties X raise DAVError(HTTP_FORBIDDEN) X if propname == "{virtres:}tags": X # value is of type etree.Element X self.data["tags"] = value.text.split(",") X elif propname == "{virtres:}description": X # value is of type etree.Element X self.data["description"] = value.text X elif propname in VirtualResource._supportedProps: X # Supported property, but read-only X raise DAVError(HTTP_FORBIDDEN, X errcondition=PRECONDITION_CODE_ProtectedProperty) X else: X # Unsupported property X raise DAVError(HTTP_FORBIDDEN) X # Write OK X return X X X X#=============================================================================== X# _VirtualNonCollection classes X#=============================================================================== Xclass _VirtualNonCollection(DAVNonCollection): X """Abstract base class for all non-collection resources.""" X def __init__(self, path, environ): X DAVNonCollection.__init__(self, path, environ) X def getContentLength(self): X return None X def getContentType(self): X return None X def getCreationDate(self): X return None X def getDisplayName(self): X return self.name X def getDisplayInfo(self): X raise NotImplementedError() X def getEtag(self): X return None X def getLastModified(self): X return None X def supportRanges(self): X return False X# def handleDelete(self): X# raise DAVError(HTTP_FORBIDDEN) X# def handleMove(self, destPath): X# raise DAVError(HTTP_FORBIDDEN) X# def handleCopy(self, destPath, depthInfinity): X# raise DAVError(HTTP_FORBIDDEN) X X X#=============================================================================== X# VirtualArtifact X#=============================================================================== Xclass VirtualArtifact(_VirtualNonCollection): X """A virtual file, containing resource descriptions.""" X def __init__(self, path, environ, data): X# assert name in _artifactNames X _VirtualNonCollection.__init__(self, path, environ) X self.data = data X X def getContentLength(self): X return len(self.getContent().read()) X def getContentType(self): X if self.name.endswith(".txt"): X return "text/plain" X return "text/html" X def getDisplayInfo(self): X return {"type": "Virtual info file"} X def preventLocking(self): X return True X X def getRefUrl(self): X refPath = "/by_key/%s/%s" % (self.data["key"], self.name) X return urllib.quote(self.provider.sharePath + refPath) X X def getContent(self): X fileLinks = [ "%s\n" % (os.path.basename(f), f) for f in self.data["resPathList"] ] X dict = self.data.copy() X dict["fileLinks"] = ", ".join(fileLinks) X if self.name == ".Info.html": X html = """\ X X %(title)s X X

%(title)s

X X X X X X X X X X X X X X X X X X X X X
Description%(description)s
Status%(status)s
Tags%(tags)s
Orga unit%(orga)s
Files%(fileLinks)s
Key%(key)s
X

This is a virtual WsgiDAV resource called '%(title)s'.

X """ % dict X elif self.name == ".Info.txt": X lines = [self.data["title"], X "=" * len(self.data["title"]), X self.data["description"], X "", X "Status: %s" % self.data["status"], X "Orga: %8s" % self.data["orga"], X "Tags: '%s'" % "', '".join(self.data["tags"]), X "Key: %s" % self.data["key"], X ] X html = "\n".join(lines) X elif self.name == ".Description.txt": X html = self.data["description"] X else: X raise DAVError(HTTP_INTERNAL_ERROR, "Invalid artifact '%s'" % self.name) X return StringIO(html) X X X#=============================================================================== X# VirtualResFile X#=============================================================================== Xclass VirtualResFile(_VirtualNonCollection): X """Represents an existing file, that is a member of a VirtualResource.""" X def __init__(self, path, environ, data, filePath): X if not os.path.exists(filePath): X util.warn("VirtualResFile(%r) does not exist." % filePath) X _VirtualNonCollection.__init__(self, path, environ) X self.data = data X self.filePath = filePath X X def getContentLength(self): X statresults = os.stat(self.filePath) X return statresults[stat.ST_SIZE] X def getContentType(self): X if not os.path.isfile(self.filePath): X return "text/html" X# (mimetype, _mimeencoding) = mimetypes.guess_type(self.filePath) X# if not mimetype: X# mimetype = "application/octet-stream" X# return mimetype X return util.guessMimeType(self.filePath) X def getCreationDate(self): X statresults = os.stat(self.filePath) X return statresults[stat.ST_CTIME] X def getDisplayInfo(self): X return {"type": "Content file"} X def getLastModified(self): X statresults = os.stat(self.filePath) X return statresults[stat.ST_MTIME] X X def getRefUrl(self): X refPath = "/by_key/%s/%s" % (self.data["key"], os.path.basename(self.filePath)) X return urllib.quote(self.provider.sharePath + refPath) X X def getContent(self): X mime = self.getContentType() X if mime.startswith("text"): X return file(self.filePath, "r", BUFFER_SIZE) X return file(self.filePath, "rb", BUFFER_SIZE) X X X#=============================================================================== X# VirtualResourceProvider X#=============================================================================== Xclass VirtualResourceProvider(DAVProvider): X """ X DAV provider that serves a VirtualResource derived structure. X """ X def __init__(self): X super(VirtualResourceProvider, self).__init__() X self.resourceData = _resourceData X X def getResourceInst(self, path, environ): X """Return _VirtualResource object for path. X X path is expected to be X categoryType/category/name/artifact X for example: X 'by_tag/cool/My doc 2/info.html' X X See DAVProvider.getResourceInst() X """ X _logger.info("getResourceInst('%s')" % path) X self._count_getResourceInst += 1 X root = RootCollection(environ) X return root.resolve("", path) X X#class VirtualResourceProvider(DAVProvider): X# """ X# DAV provider that serves a VirtualResource derived structure. X# """ X# def __init__(self): X# super(VirtualResourceProvider, self).__init__() X# self.resourceData = _resourceData X## self.rootCollection = VirtualCollection(self, "/", environ) X# X# X# def getResourceInst(self, path, environ): X# """Return _VirtualResource object for path. X# X# path is expected to be X# categoryType/category/name/artifact X# for example: X# 'by_tag/cool/My doc 2/info.html' X# X# See DAVProvider.getResourceInst() X# """ X# self._count_getResourceInst += 1 X# X# catType, cat, resName, artifactName = util.saveSplit(path.strip("/"), "/", 3) X# X# _logger.info("getResourceInst('%s'): catType=%s, cat=%s, resName=%s" % (path, catType, cat, resName)) X# X# if catType and catType not in _alllowedCategories: X# return None X# X# if catType == _realCategory: X# # Accessing /by_key/ X# data = _getResByKey(cat) X# if data: X# return VirtualResource(self, path, environ, data) X# return None X# X# elif resName: X# # Accessing /// or //// X# res = None X# for data in _getResListByAttr(catType, cat): X# if data["title"] == resName: X# res = data X# break X# if not res: X# return None X# # Accessing /// X# if artifactName in _artifactNames: X# # Accessing ////.info.html, or similar X# return VirtualArtifact(self, util.joinUri(path, artifactName), environ, X# res, artifactName) X# elif artifactName: X# # Accessing //// X# for f in res["resPathList"]: X# if artifactName == os.path.basename(f): X# return VirtualResFile(self, path, environ, res, f) X# return None X# # Accessing /// X# return VirtualResource(self, path, environ, data) X# X# elif cat: X# # Accessing /catType/cat: return list of matching names X# resList = _getResListByAttr(catType, cat) X# nameList = [ data["title"] for data in resList ] X# return VirtualCollection(self, path, environ, nameList) X# X# elif catType: X# # Accessing /catType/: return all possible values for this catType X# if catType in _browsableCategories: X# resList = [] X# for data in _resourceData: X# if catType == "by_status": X# if not data["status"] in resList: X# resList.append(data["status"]) X# elif catType == "by_orga": X# if not data["orga"] in resList: X# resList.append(data["orga"]) X# elif catType == "by_tag": X# for tag in data["tags"]: X# if not tag in resList: X# resList.append(tag) X# X# return VirtualCollection(self, path, environ, resList) X# # Known category type, but not browsable (e.g. 'by_key') X# raise DAVError(HTTP_FORBIDDEN) X# X# # Accessing /: return list of categories X# return VirtualCollection(self, path, environ, _browsableCategories) e4ae967170dfb1ad6f630c9e70aec015 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/dav_error.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/dav_error.py << 'd15ed780e1b42021d66ba9d55dbc1103' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplements a DAVError class that is used to signal WebDAV and HTTP errors. X""" Ximport traceback Ximport datetime Ximport cgi Ximport sys X Ximport xml_tools X## Trick PyDev to do intellisense and don't produce warnings: Xfrom xml_tools import etree #@UnusedImport Xfrom wsgidav.version import __version__ Xif False: from xml.etree import ElementTree as etree #@Reimport @UnresolvedImport X X__docformat__ = "reStructuredText" X X#=============================================================================== X# List of HTTP Response Codes. X#=============================================================================== XHTTP_CONTINUE = 100 XHTTP_SWITCHING_PROTOCOLS = 101 XHTTP_PROCESSING = 102 X XHTTP_OK = 200 XHTTP_CREATED = 201 XHTTP_ACCEPTED = 202 XHTTP_NON_AUTHORITATIVE_INFO = 203 XHTTP_NO_CONTENT = 204 XHTTP_RESET_CONTENT = 205 XHTTP_PARTIAL_CONTENT = 206 XHTTP_MULTI_STATUS = 207 XHTTP_IM_USED = 226 X XHTTP_MULTIPLE_CHOICES = 300 XHTTP_MOVED = 301 XHTTP_FOUND = 302 XHTTP_SEE_OTHER = 303 XHTTP_NOT_MODIFIED = 304 XHTTP_USE_PROXY = 305 XHTTP_TEMP_REDIRECT = 307 XHTTP_BAD_REQUEST = 400 XHTTP_PAYMENT_REQUIRED = 402 XHTTP_FORBIDDEN = 403 XHTTP_NOT_FOUND = 404 XHTTP_METHOD_NOT_ALLOWED = 405 XHTTP_NOT_ACCEPTABLE = 406 XHTTP_PROXY_AUTH_REQUIRED = 407 XHTTP_REQUEST_TIMEOUT = 408 XHTTP_CONFLICT = 409 XHTTP_GONE = 410 XHTTP_LENGTH_REQUIRED = 411 XHTTP_PRECONDITION_FAILED = 412 XHTTP_REQUEST_ENTITY_TOO_LARGE = 413 XHTTP_REQUEST_URI_TOO_LONG = 414 XHTTP_MEDIATYPE_NOT_SUPPORTED = 415 XHTTP_RANGE_NOT_SATISFIABLE = 416 XHTTP_EXPECTATION_FAILED = 417 XHTTP_UNPROCESSABLE_ENTITY = 422 XHTTP_LOCKED = 423 XHTTP_FAILED_DEPENDENCY = 424 XHTTP_UPGRADE_REQUIRED = 426 X XHTTP_INTERNAL_ERROR = 500 XHTTP_NOT_IMPLEMENTED = 501 XHTTP_BAD_GATEWAY = 502 XHTTP_SERVICE_UNAVAILABLE = 503 XHTTP_GATEWAY_TIMEOUT = 504 XHTTP_VERSION_NOT_SUPPORTED = 505 XHTTP_INSUFFICIENT_STORAGE = 507 XHTTP_NOT_EXTENDED = 510 X X X#=============================================================================== X# if ERROR_DESCRIPTIONS exists for an error code, the error description will be X# sent as the error response code. X# Otherwise only the numeric code itself is sent. X#=============================================================================== X# TODO: paste.httpserver may raise exceptions, if a status code is not followed by a description, so should define all of them. XERROR_DESCRIPTIONS = { X HTTP_OK: "200 OK", X HTTP_CREATED: "201 Created", X HTTP_NO_CONTENT: "204 No Content", X HTTP_NOT_MODIFIED: "304 Not Modified", X HTTP_BAD_REQUEST: "400 Bad Request", X HTTP_FORBIDDEN: "403 Forbidden", X HTTP_METHOD_NOT_ALLOWED: "405 Method Not Allowed", X HTTP_NOT_FOUND: "404 Not Found", X HTTP_CONFLICT: "409 Conflict", X HTTP_PRECONDITION_FAILED: "412 Precondition Failed", X HTTP_RANGE_NOT_SATISFIABLE: "416 Range Not Satisfiable", X HTTP_MEDIATYPE_NOT_SUPPORTED: "415 Media Type Not Supported", X HTTP_LOCKED: "423 Locked", X HTTP_FAILED_DEPENDENCY: "424 Failed Dependency", X HTTP_INTERNAL_ERROR: "500 Internal Server Error", X HTTP_NOT_IMPLEMENTED: "501 Not Implemented", X HTTP_BAD_GATEWAY: "502 Bad Gateway", X } X X#=============================================================================== X# if ERROR_RESPONSES exists for an error code, a html output will be sent as response X# body including the ERROR_RESPONSES value. Otherwise a null response body is sent. X# Mostly for browser viewing X#=============================================================================== X XERROR_RESPONSES = { X HTTP_BAD_REQUEST: "An invalid request was specified", X HTTP_NOT_FOUND: "The specified resource was not found", X HTTP_FORBIDDEN: "Access denied to the specified resource", X HTTP_INTERNAL_ERROR: "An internal server error occurred", X HTTP_NOT_IMPLEMENTED: "Not Implemented", X } X X X#=============================================================================== X# Condition codes X# http://www.webdav.org/specs/rfc4918.html#precondition.postcondition.xml.elements X#=============================================================================== X XPRECONDITION_CODE_ProtectedProperty = "{DAV:}cannot-modify-protected-property" XPRECONDITION_CODE_MissingLockToken = "{DAV:}lock-token-submitted" XPRECONDITION_CODE_LockTokenMismatch = "{DAV:}lock-token-matches-request-uri" XPRECONDITION_CODE_LockConflict = "{DAV:}no-conflicting-lock" XPRECONDITION_CODE_PropfindFiniteDepth = "{DAV:}propfind-finite-depth" X X Xclass DAVErrorCondition(object): X def __init__(self, conditionCode): X self.conditionCode = conditionCode X self.hrefs = [] X X def __str__(self): X return "%s(%s)" % (self.conditionCode, self.hrefs) X X def add_href(self, href): X assert href.startswith("/") X assert self.conditionCode in (PRECONDITION_CODE_LockConflict, X PRECONDITION_CODE_MissingLockToken) X if not href in self.hrefs: X self.hrefs.append(href) X X def as_xml(self): X if self.conditionCode == PRECONDITION_CODE_MissingLockToken: X assert len(self.hrefs) > 0, "lock-token-submitted requires at least one href" X errorEL = etree.Element("{DAV:}error") X condEL = etree.SubElement(errorEL, self.conditionCode) X for href in self.hrefs: X etree.SubElement(condEL, "{DAV:}href").text = href X return errorEL X X def as_string(self): X return xml_tools.xmlToString(self.as_xml(), True) X X X X#=============================================================================== X# DAVError X#=============================================================================== X# @@: I prefer having a separate exception type for each response, X# as in paste.httpexceptions. This way you can catch just the exceptions X# you want (or you can catch an abstract superclass to get any of them) X Xclass DAVError(Exception): X # TODO: Ian Bicking proposed to add an additional 'comment' arg, but X # couldn't we use the existing 'contextinfo'? X # @@: This should also take some message value, for a detailed error message. X # This would be helpful for debugging. X def __init__(self, X statusCode, X contextinfo=None, X srcexception=None, X errcondition=None): # allow passing of Pre- and Postconditions, see http://www.webdav.org/specs/rfc4918.html#precondition.postcondition.xml.elements X self.value = int(statusCode) X self.contextinfo = contextinfo X self.srcexception = srcexception X self.errcondition = errcondition X if type(errcondition) is str: X self.errcondition = DAVErrorCondition(errcondition) X assert self.errcondition is None or type(self.errcondition) is DAVErrorCondition X X def __repr__(self): X return "DAVError(%s)" % self.getUserInfo() X X def __str__(self): # Required for 2.4 X return self.__repr__() X X def getUserInfo(self): X """Return readable string.""" X if self.value in ERROR_DESCRIPTIONS: X s = "%s" % ERROR_DESCRIPTIONS[self.value] X else: X s = "%s" % self.value X X if self.contextinfo: X s+= ": %s" % self.contextinfo X elif self.value in ERROR_RESPONSES: X s += ": %s" % ERROR_RESPONSES[self.value] X X if self.srcexception: X s += "\n Source exception: '%s'" % self.srcexception X X if self.errcondition: X s += "\n Error condition: '%s'" % self.errcondition X return s X X def getResponsePage(self): X """Return an tuple (content-type, response page).""" X # If it has pre- or post-condition: return as XML response X if self.errcondition: X return ("application/xml", self.errcondition.as_string()) X X # Else return as HTML X status = getHttpStatusString(self) X html = [] X html.append(""); X html.append("") X html.append(" ") X html.append(" %s" % status) X html.append("") X html.append("

%s

" % status) X html.append("

%s

" % cgi.escape(self.getUserInfo())) X# html.append("
") X# html.append("

%s

" % cgi.escape(str(datetime.datetime.now()))) X# if self._server_descriptor: X# respbody.append(self._server_descriptor + "
") X html.append("
") X html.append("WsgiDAV/%s - %s" X % (__version__, cgi.escape(str(datetime.datetime.now())))) X html.append("") X html = "\n".join(html) X return ("text/html", html) X X Xdef getHttpStatusCode(v): X """Return HTTP response code as integer, e.g. 204.""" X if hasattr(v, "value"): X return int(v.value) # v is a DAVError X else: X return int(v) X X Xdef getHttpStatusString(v): X """Return HTTP response string, e.g. 204 -> ('204 No Content'). X X `v`: status code or DAVError X """ X code = getHttpStatusCode(v) X try: X return ERROR_DESCRIPTIONS[code] X except: X return str(code) X X Xdef getResponsePage(v): X v = asDAVError(v) X return v.getResponsePage() X X Xdef asDAVError(e): X """Convert any non-DAVError exception to HTTP_INTERNAL_ERROR.""" X if isinstance(e, DAVError): X return e X elif isinstance(e, Exception): X return DAVError(HTTP_INTERNAL_ERROR, srcexception=e) X else: X return DAVError(HTTP_INTERNAL_ERROR, "%s" % e) X X X Xif __name__ == "__main__": X dec = DAVErrorCondition(PRECONDITION_CODE_LockConflict) X print dec.as_string() X dec.add_href("/dav/a") X print dec.as_string() d15ed780e1b42021d66ba9d55dbc1103 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/fs_dav_provider.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/fs_dav_provider.py << '83900b2d6d3cc66df7b3a2f7956cf08d' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplementation of a DAV provider that serves resource from a file system. X XReadOnlyFilesystemProvider implements a DAV resource provider that publishes Xa file system for read-only access. XWrite attempts will raise HTTP_FORBIDDEN. X XFilesystemProvider inherits from ReadOnlyFilesystemProvider and implements the Xmissing write access functionality. X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom wsgidav.dav_error import DAVError, HTTP_FORBIDDEN Xfrom wsgidav.dav_provider import DAVProvider, DAVCollection, DAVNonCollection X Ximport util Ximport os X#import mimetypes Ximport shutil Ximport stat Ximport sys X X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X XBUFFER_SIZE = 8192 X X X#=============================================================================== X# FileResource X#=============================================================================== Xclass FileResource(DAVNonCollection): X """Represents a single existing DAV resource instance. X X See also _DAVResource, DAVNonCollection, and FilesystemProvider. X """ X def __init__(self, path, environ, filePath): X super(FileResource, self).__init__(path, environ) X self._filePath = filePath X self.filestat = os.stat(self._filePath) X # Setting the name from the file path should fix the case on Windows X self.name = os.path.basename(self._filePath) X self.name = self.name.encode("utf8") X X # Getter methods for standard live properties X def getContentLength(self): X return self.filestat[stat.ST_SIZE] X def getContentType(self): X# (mimetype, _mimeencoding) = mimetypes.guess_type(self.path) X# print "mimetype(%s): %r, %r" % (self.path, mimetype, _mimeencoding) X# if not mimetype: X# mimetype = "application/octet-stream" X# print "mimetype(%s): return %r" % (self.path, mimetype) X# return mimetype X return util.guessMimeType(self.path) X def getCreationDate(self): X return self.filestat[stat.ST_CTIME] X def getDisplayName(self): X return self.name X def getEtag(self): X return util.getETag(self._filePath) X def getLastModified(self): X return self.filestat[stat.ST_MTIME] X def supportEtag(self): X return True X def supportRanges(self): X return True X X def getContent(self): X """Open content as a stream for reading. X X See DAVResource.getContent() X """ X assert not self.isCollection X # issue 28: if we open in text mode, \r\n is converted to one byte. X # So the file size reported by Windows differs from len(..), thus X # content-length will be wrong. X# mime = self.getContentType() X# if mime.startswith("text"): X# return file(self._filePath, "r", BUFFER_SIZE) X return file(self._filePath, "rb", BUFFER_SIZE) X X X def beginWrite(self, contentType=None): X """Open content as a stream for writing. X X See DAVResource.beginWrite() X """ X assert not self.isCollection X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X mode = "wb" X if contentType and contentType.startswith("text"): X mode = "w" X _logger.debug("beginWrite: %s, %s" % (self._filePath, mode)) X return file(self._filePath, mode, BUFFER_SIZE) X X X def delete(self): X """Remove this resource or collection (recursive). X X See DAVResource.delete() X """ X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X os.unlink(self._filePath) X self.removeAllProperties(True) X self.removeAllLocks(True) X X X def copyMoveSingle(self, destPath, isMove): X """See DAVResource.copyMoveSingle() """ X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X fpDest = self.provider._locToFilePath(destPath) X assert not util.isEqualOrChildUri(self.path, destPath) X # Copy file (overwrite, if exists) X shutil.copy2(self._filePath, fpDest) X # (Live properties are copied by copy2 or copystat) X # Copy dead properties X propMan = self.provider.propManager X if propMan: X destRes = self.provider.getResourceInst(destPath, self.environ) X if isMove: X propMan.moveProperties(self.getRefUrl(), destRes.getRefUrl(), X withChildren=False) X else: X propMan.copyProperties(self.getRefUrl(), destRes.getRefUrl()) X X X def supportRecursiveMove(self, destPath): X """Return True, if moveRecursive() is available (see comments there).""" X return True X X X def moveRecursive(self, destPath): X """See DAVResource.moveRecursive() """ X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X fpDest = self.provider._locToFilePath(destPath) X assert not util.isEqualOrChildUri(self.path, destPath) X assert not os.path.exists(fpDest) X _logger.debug("moveRecursive(%s, %s)" % (self._filePath, fpDest)) X shutil.move(self._filePath, fpDest) X # (Live properties are copied by copy2 or copystat) X # Move dead properties X if self.provider.propManager: X destRes = self.provider.getResourceInst(destPath, self.environ) X self.provider.propManager.moveProperties(self.getRefUrl(), destRes.getRefUrl(), X withChildren=True) X X X X X#=============================================================================== X# FolderResource X#=============================================================================== Xclass FolderResource(DAVCollection): X """Represents a single existing file system folder DAV resource. X X See also _DAVResource, DAVCollection, and FilesystemProvider. X """ X def __init__(self, path, environ, filePath): X super(FolderResource, self).__init__(path, environ) X self._filePath = filePath X# self._dict = None X self.filestat = os.stat(self._filePath) X # Setting the name from the file path should fix the case on Windows X self.name = os.path.basename(self._filePath) X self.name = self.name.encode("utf8") X X X # Getter methods for standard live properties X def getCreationDate(self): X return self.filestat[stat.ST_CTIME] X def getDisplayName(self): X return self.name + "-1" X def getDirectoryInfo(self): X return None X def getEtag(self): X return None X def getLastModified(self): X return self.filestat[stat.ST_MTIME] X X def getMemberNames(self): X """Return list of direct collection member names (utf-8 encoded). X X See DAVCollection.getMemberNames() X """ X # On Windows NT/2k/XP and Unix, if path is a Unicode object, the result X # will be a list of Unicode objects. X # Undecodable filenames will still be returned as string objects X # If we don't request unicode, for example Vista may return a '?' X # instead of a special character. The name would then be unusable to X # build a distinct URL that references this resource. X X nameList = [] X # self._filePath is unicode, so os.listdir returns unicode as well X assert isinstance(self._filePath, unicode) X for name in os.listdir(self._filePath): X if not isinstance(name, unicode): X name = name.decode(sys.getfilesystemencoding()) X assert isinstance(name, unicode) X # Skip non files (links and mount points) X fp = os.path.join(self._filePath, name) X if not os.path.isdir(fp) and not os.path.isfile(fp): X _logger.debug("Skipping non-file %s" % fp) X continue X name = name.encode("utf8") X nameList.append(name) X return nameList X X def getMember(self, name): X """Return direct collection member (DAVResource or derived). X X See DAVCollection.getMember() X """ X fp = os.path.join(self._filePath, name.decode("utf8")) X# name = name.encode("utf8") X path = util.joinUri(self.path, name) X if os.path.isdir(fp): X res = FolderResource(path, self.environ, fp) X elif os.path.isfile(fp): X res = FileResource(path, self.environ, fp) X else: X _logger.debug("Skipping non-file %s" % fp) X res = None X return res X X X X # --- Read / write --------------------------------------------------------- X X def createEmptyResource(self, name): X """Create an empty (length-0) resource. X X See DAVResource.createEmptyResource() X """ X assert not "/" in name X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X path = util.joinUri(self.path, name) X fp = self.provider._locToFilePath(path) X f = open(fp, "w") X f.close() X return self.provider.getResourceInst(path, self.environ) X X X def createCollection(self, name): X """Create a new collection as member of self. X X See DAVResource.createCollection() X """ X assert not "/" in name X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X path = util.joinUri(self.path, name) X fp = self.provider._locToFilePath(path) X os.mkdir(fp) X X X def delete(self): X """Remove this resource or collection (recursive). X X See DAVResource.delete() X """ X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X shutil.rmtree(self._filePath, ignore_errors=False) X self.removeAllProperties(True) X self.removeAllLocks(True) X X X def copyMoveSingle(self, destPath, isMove): X """See DAVResource.copyMoveSingle() """ X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X fpDest = self.provider._locToFilePath(destPath) X assert not util.isEqualOrChildUri(self.path, destPath) X # Create destination collection, if not exists X if not os.path.exists(fpDest): X os.mkdir(fpDest) X try: X # may raise: [Error 5] Permission denied: u'C:\\temp\\litmus\\ccdest' X shutil.copystat(self._filePath, fpDest) X except Exception, e: X _logger.debug("Could not copy folder stats: %s" % e) X # (Live properties are copied by copy2 or copystat) X # Copy dead properties X propMan = self.provider.propManager X if propMan: X destRes = self.provider.getResourceInst(destPath, self.environ) X if isMove: X propMan.moveProperties(self.getRefUrl(), destRes.getRefUrl(), X withChildren=False) X else: X propMan.copyProperties(self.getRefUrl(), destRes.getRefUrl()) X X X def supportRecursiveMove(self, destPath): X """Return True, if moveRecursive() is available (see comments there).""" X return True X X X def moveRecursive(self, destPath): X """See DAVResource.moveRecursive() """ X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X fpDest = self.provider._locToFilePath(destPath) X assert not util.isEqualOrChildUri(self.path, destPath) X assert not os.path.exists(fpDest) X _logger.debug("moveRecursive(%s, %s)" % (self._filePath, fpDest)) X shutil.move(self._filePath, fpDest) X # (Live properties are copied by copy2 or copystat) X # Move dead properties X if self.provider.propManager: X destRes = self.provider.getResourceInst(destPath, self.environ) X self.provider.propManager.moveProperties(self.getRefUrl(), destRes.getRefUrl(), X withChildren=True) X X X X X#=============================================================================== X# FilesystemProvider X#=============================================================================== Xclass FilesystemProvider(DAVProvider): X X def __init__(self, rootFolderPath, readonly=False): X if not rootFolderPath or not os.path.exists(rootFolderPath): X raise ValueError("Invalid root path: %s" % rootFolderPath) X super(FilesystemProvider, self).__init__() X self.rootFolderPath = os.path.abspath(rootFolderPath) X self.readonly = readonly X X X def __repr__(self): X rw = "Read-Write" X if self.readonly: X rw = "Read-Only" X return "%s for path '%s' (%s)" % (self.__class__.__name__, X self.rootFolderPath, rw) X X X def _locToFilePath(self, path): X """Convert resource path to a unicode absolute file path.""" X assert self.rootFolderPath is not None X pathInfoParts = path.strip("/").split("/") X X r = os.path.abspath(os.path.join(self.rootFolderPath, *pathInfoParts)) X if not r.startswith(self.rootFolderPath): X raise RuntimeError("Security exception: tried to access file outside root.") X r = util.toUnicode(r) X# print "_locToFilePath(%s): %s" % (path, r) X return r X X X def getResourceInst(self, path, environ): X """Return info dictionary for path. X X See DAVProvider.getResourceInst() X """ X self._count_getResourceInst += 1 X fp = self._locToFilePath(path) X if not os.path.exists(fp): X return None X X if os.path.isdir(fp): X return FolderResource(path, environ, fp) X return FileResource(path, environ, fp) 83900b2d6d3cc66df7b3a2f7956cf08d echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/debug_filter.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/debug_filter.py << 'e6b3b731738bb1e0a6dcf1e8569f0b6e' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XWSGI middleware used for debugging (optional). X XThis module dumps request and response information to the console, depending Xon current debug configuration. X XOn init: X Define HTTP methods and litmus tests, that should turn on the verbose mode X (currently hard coded). XFor every request: X Increase value of ``environ['verbose']``, if the request should be debugged. X Also dump request and response headers and body. X X Then pass the request to the next middleware. X XThese configuration settings are evaluated: X X*verbose* X This is also used by other modules. This filter adds additional information X depending on the value. X X ======= =================================================================== X verbose Effect X ======= =================================================================== X 0 No additional output. X 1 No additional output (only standard request logging). X 2 Dump headers of all requests and responses. X 3 Dump headers and bodies of all requests and responses. X ======= =================================================================== X X*debug_methods* X Boost verbosity to 3 while processing certain request methods. This option X is ignored, when ``verbose < 2``. X X Configured like:: X X debug_methods = ["PROPPATCH", "PROPFIND", "GET", "HEAD","DELETE", X "PUT", "COPY", "MOVE", "LOCK", "UNLOCK", X ] X X*debug_litmus* X Boost verbosity to 3 while processing litmus tests that contain certain X substrings. This option is ignored, when ``verbose < 2``. X X Configured like:: X X debug_litmus = ["notowner_modify", "props: 16", ] X X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom wsgidav import util Ximport sys Ximport threading X X__docformat__ = "reStructuredText" X X X Xclass WsgiDavDebugFilter(object): X X def __init__(self, application, config): X self._application = application X self._config = config X# self.out = sys.stderr X self.out = sys.stdout X self.passedLitmus = {} X # These methods boost verbose=2 to verbose=3 X self.debug_methods = config.get("debug_methods", []) X # Litmus tests containing these string boost verbose=2 to verbose=3 X self.debug_litmus = config.get("debug_litmus", []) X # Exit server, as soon as this litmus test has finished X self.break_after_litmus = [ X# "locks: 15", X ] X X X def __call__(self, environ, start_response): X """""" X# srvcfg = environ["wsgidav.config"] X verbose = self._config.get("verbose", 2) X X method = environ["REQUEST_METHOD"] X X debugBreak = False X dumpRequest = False X dumpResponse = False X X if verbose >= 3: X dumpRequest = dumpResponse = True X X # Process URL commands X if "dump_storage" in environ.get("QUERY_STRING"): X dav = environ.get("wsgidav.provider") X if dav.lockManager: X dav.lockManager._dump() X if dav.propManager: X dav.propManager._dump() X X # Turn on max. debugging for selected litmus tests X litmusTag = environ.get("HTTP_X_LITMUS", environ.get("HTTP_X_LITMUS_SECOND")) X if litmusTag and verbose >= 2: X print >> self.out, "----\nRunning litmus test '%s'..." % litmusTag X for litmusSubstring in self.debug_litmus: X if litmusSubstring in litmusTag: X verbose = 3 X debugBreak = True X dumpRequest = True X dumpResponse = True X break X for litmusSubstring in self.break_after_litmus: X if litmusSubstring in self.passedLitmus and litmusSubstring not in litmusTag: X print >> self.out, " *** break after litmus %s" % litmusTag X sys.exit(-1) X if litmusSubstring in litmusTag: X self.passedLitmus[litmusSubstring] = True X X # Turn on max. debugging for selected request methods X if verbose >= 2 and method in self.debug_methods: X verbose = 3 X debugBreak = True X dumpRequest = True X dumpResponse = True X X # Set debug options to environment X environ["wsgidav.verbose"] = verbose X# environ["wsgidav.debug_methods"] = self.debug_methods X environ["wsgidav.debug_break"] = debugBreak X environ["wsgidav.dump_request_body"] = dumpRequest X environ["wsgidav.dump_response_body"] = dumpResponse X X # Dump request headers X if dumpRequest: X print >> self.out, "<%s> --- %s Request ---" % (threading._get_ident(), method) X for k, v in environ.items(): X if k == k.upper(): X print >> self.out, "%20s: '%s'" % (k, v) X print >> self.out, "\n" X X # Call parent application and return it's response X def start_response_wrapper(status, response_headers, exc_info=None): X # TODO: not fully understood: X if exc_info is not None: X util.log("DebugFilter got exc_info", exc_info) X X# # Dump response headers X# if dumpResponse: X# print >> self.out, "<%s> --- %s Response(%s): ---" % (threading._get_ident(), method, status) X# headersdict = dict(response_headers) X# for envitem in headersdict.keys(): X# print >> self.out, "\t%s:\t'%s'" % (envitem, repr(headersdict[envitem])) X# print >> self.out, "\n" X # Store response headers X environ["wsgidav.response_status"] = status X environ["wsgidav.response_headers"] = response_headers X return start_response(status, response_headers, exc_info) X X nbytes = 0 X firstyield = True X for v in iter(self._application(environ, start_response_wrapper)): X # Dump response headers X if firstyield and dumpResponse: X print >> self.out, "<%s> --- %s Response(%s): ---" % (threading._get_ident(), X method, X environ.get("wsgidav.response_status")) X headersdict = dict(environ.get("wsgidav.response_headers")) X for envitem in headersdict.keys(): X print >> self.out, "%s: %s" % (envitem, repr(headersdict[envitem])) X print >> self.out, "" X X # Check, if response is a binary string, otherwise we probably have X # calculated a wrong content-length X assert type(v) is str X X # Dump response body X drb = environ.get("wsgidav.dump_response_body") X if type(drb) is str: X # Middleware provided a formatted body representation X print >> self.out, drb X drb = environ["wsgidav.dump_response_body"] = None X elif drb is True: X # Else dump what we get, (except for long GET responses) X if method == "GET": X if firstyield: X print >> self.out, v[:50], "..." X elif len(v) > 0: X print >> self.out, v X X nbytes += len(v) X firstyield = False X yield v X X if dumpResponse: X print >> self.out, "\n<%s> --- End of %s Response (%i bytes) ---" % (threading._get_ident(), method, nbytes) X return e6b3b731738bb1e0a6dcf1e8569f0b6e echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/__init__.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/__init__.py << 'c706b23cbd912e5a3b0d31899033c72c' c706b23cbd912e5a3b0d31899033c72c echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/wsgidav_app.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/wsgidav_app.py << '71e7930a5b0ce78b64861fa928ba0d98' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XWSGI container, that handles the HTTP requests. This object is passed to the XWSGI server and represents our WsgiDAV application to the outside. X XOn init: X X Use the configuration dictionary to initialize lock manager, property manager, X domain controller. X X Create a dictionary of share-to-provider mappings. X X Initialize middleware objects and RequestResolver and setup the WSGI X application stack. X XFor every request: X X Find the registered DAV provider for the current request. X X Add or modify info in the WSGI ``environ``: X X environ["SCRIPT_NAME"] X Mount-point of the current share. X environ["PATH_INFO"] X Resource path, relative to the mount path. X environ["wsgidav.provider"] X DAVProvider object that is registered for handling the current X request. X environ["wsgidav.config"] X Configuration dictionary. X environ["wsgidav.verbose"] X Debug level [0-3]. X X Log the HTTP request, then pass the request to the first middleware. X X Note: The OPTIONS method for the '*' path is handled directly. X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom fs_dav_provider import FilesystemProvider Xfrom wsgidav.dir_browser import WsgiDavDirBrowser Xfrom wsgidav.dav_provider import DAVProvider Xfrom wsgidav.lock_storage import LockStorageDict Ximport time Ximport sys Ximport threading Ximport urllib Ximport util Xfrom error_printer import ErrorPrinter Xfrom debug_filter import WsgiDavDebugFilter Xfrom http_authenticator import HTTPAuthenticator Xfrom request_resolver import RequestResolver Xfrom domain_controller import WsgiDAVDomainController Xfrom property_manager import PropertyManager Xfrom lock_manager import LockManager X#from wsgidav.version import __version__ X X__docformat__ = "reStructuredText" X X X# Use these settings, if config file does not define them (or is totally missing) XDEFAULT_CONFIG = { X "mount_path": None, # Application root, e.g. // X "provider_mapping": {}, X "host": "localhost", X "port": 8080, X "ext_servers": [ X# "paste", X# "cherrypy", X# "wsgiref", X "cherrypy-bundled", X "wsgidav", X ], X X "enable_loggers": [ X ], X X "propsmanager": None, # True: use property_manager.PropertyManager X "locksmanager": True, # True: use lock_manager.LockManager X X # HTTP Authentication Options X "user_mapping": {}, # dictionary of dictionaries X "domaincontroller": None, # None: domain_controller.WsgiDAVDomainController(user_mapping) X "acceptbasic": True, # Allow basic authentication, True or False X "acceptdigest": True, # Allow digest authentication, True or False X "defaultdigest": True, # True (default digest) or False (default basic) X X # Verbose Output X "verbose": 2, # 0 - no output (excepting application exceptions) X # 1 - show single line request summaries (for HTTP logging) X # 2 - show additional events X # 3 - show full request/response header info (HTTP Logging) X # request body and GET response bodies not shown X X "dir_browser": { X "enable": True, # Render HTML listing for GET requests on collections X "response_trailer": "", # Raw HTML code, appended as footer X "davmount": False, # Send response if request URL contains '?davmount' X "msmount": False, # Add an 'open as webfolder' link (requires Windows) X } X} X X X X Xdef _checkConfig(config): X mandatoryFields = ["provider_mapping", X ] X for field in mandatoryFields: X if not field in config: X raise ValueError("Invalid configuration: missing required field '%s'" % field) X X X X X#=============================================================================== X# WsgiDAVApp X#=============================================================================== Xclass WsgiDAVApp(object): X X def __init__(self, config): X self.config = config X X util.initLogging(config["verbose"], X config.get("log_path", ""), X config.get("enable_loggers", [])) X X util.log("Default encoding: %s (file system: %s)" % (sys.getdefaultencoding(), sys.getfilesystemencoding())) X X # Evaluate configuration and set defaults X _checkConfig(config) X provider_mapping = self.config["provider_mapping"] X# response_trailer = config.get("response_trailer", "") X self._verbose = config.get("verbose", 2) X X lockStorage = config.get("locksmanager") X if lockStorage is True: X lockStorage = LockStorageDict() X X if not lockStorage: X locksManager = None X else: X locksManager = LockManager(lockStorage) X X propsManager = config.get("propsmanager") X if not propsManager: X # Normalize False, 0 to None X propsManager = None X elif propsManager is True: X propsManager = PropertyManager() X X mount_path = config.get("mount_path") X X user_mapping = self.config.get("user_mapping", {}) X domainController = config.get("domaincontroller") or WsgiDAVDomainController(user_mapping) X isDefaultDC = isinstance(domainController, WsgiDAVDomainController) X X # authentication fields X authacceptbasic = config.get("acceptbasic", True) X authacceptdigest = config.get("acceptdigest", True) X authdefaultdigest = config.get("defaultdigest", True) X X # Check configuration for NTDomainController X # We don't use 'isinstance', because include would fail on non-windows boxes. X wdcName = "NTDomainController" X if domainController.__class__.__name__ == wdcName: X if authacceptdigest or authdefaultdigest or not authacceptbasic: X util.warn("WARNING: %s requires basic authentication.\n\tSet acceptbasic=True, acceptdigest=False, defaultdigest=False" % wdcName) X X # Instantiate DAV resource provider objects for every share X self.providerMap = {} X for (share, provider) in provider_mapping.items(): X # Make sure share starts with, or is, '/' X share = "/" + share.strip("/") X X # We allow a simple string as 'provider'. In this case we interpret X # it as a file system root folder that is published. X if isinstance(provider, basestring): X provider = FilesystemProvider(provider) X X assert isinstance(provider, DAVProvider) X X provider.setSharePath(share) X if mount_path: X provider.setMountPath(mount_path) X X # TODO: someday we may want to configure different lock/prop managers per provider X provider.setLockManager(locksManager) X provider.setPropManager(propsManager) X X self.providerMap[share] = provider X X X if self._verbose >= 2: X print "Using lock manager: %r" % locksManager X print "Using property manager: %r" % propsManager X print "Using domain controller: %s" % domainController X print "Registered DAV providers:" X for share, provider in self.providerMap.items(): X hint = "" X if isDefaultDC and not user_mapping.get(share): X hint = " (anonymous)" X print " Share '%s': %s%s" % (share, provider, hint) X X # If the default DC is used, emit a warning for anonymous realms X if isDefaultDC and self._verbose >= 1: X for share in self.providerMap: X if not user_mapping.get(share): X # TODO: we should only warn here, if --no-auth is not given X print "WARNING: share '%s' will allow anonymous access." % share X X # Define WSGI application stack X application = RequestResolver() X X if config.get("dir_browser") and config["dir_browser"].get("enable", True): X application = config["dir_browser"].get("app_class", WsgiDavDirBrowser)(application) X X application = HTTPAuthenticator(application, X domainController, X authacceptbasic, X authacceptdigest, X authdefaultdigest) X application = ErrorPrinter(application, catchall=True) X X application = WsgiDavDebugFilter(application, config) X X self._application = application X X X def __call__(self, environ, start_response): X X# util.log("SCRIPT_NAME='%s', PATH_INFO='%s'" % (environ.get("SCRIPT_NAME"), environ.get("PATH_INFO"))) X X # We unquote PATH_INFO here, although this should already be done by X # the server. X path = urllib.unquote(environ["PATH_INFO"]) X # issue 22: Pylons sends root as u'/' X if isinstance(path, unicode): X util.log("Got unicode PATH_INFO: %r" % path) X path = path.encode("utf8") X X # Always adding these values to environ: X environ["wsgidav.config"] = self.config X environ["wsgidav.provider"] = None X environ["wsgidav.verbose"] = self._verbose X X ## Find DAV provider that matches the share X X # sorting share list by reverse length X shareList = self.providerMap.keys() X shareList.sort(key=len, reverse=True) X X share = None X for r in shareList: X # @@: Case sensitivity should be an option of some sort here; X # os.path.normpath might give the preferred case for a filename. X if r == "/": X share = r X break X elif path.upper() == r.upper() or path.upper().startswith(r.upper()+"/"): X share = r X break X X provider = self.providerMap.get(share) X X # Note: we call the next app, even if provider is None, because OPTIONS X # must still be handled. X # All other requests will result in '404 Not Found' X environ["wsgidav.provider"] = provider X X # TODO: test with multi-level realms: 'aa/bb' X # TODO: test security: url contains '..' X X # Transform SCRIPT_NAME and PATH_INFO X # (Since path and share are unquoted, this also fixes quoted values.) X if share == "/" or not share: X environ["PATH_INFO"] = path X else: X environ["SCRIPT_NAME"] += share X environ["PATH_INFO"] = path[len(share):] X# util.log("--> SCRIPT_NAME='%s', PATH_INFO='%s'" % (environ.get("SCRIPT_NAME"), environ.get("PATH_INFO"))) X X assert isinstance(path, str) X # See http://mail.python.org/pipermail/web-sig/2007-January/002475.html X # for some clarification about SCRIPT_NAME/PATH_INFO format X # SCRIPT_NAME starts with '/' or is empty X assert environ["SCRIPT_NAME"] == "" or environ["SCRIPT_NAME"].startswith("/") X # SCRIPT_NAME must not have a trailing '/' X assert environ["SCRIPT_NAME"] in ("", "/") or not environ["SCRIPT_NAME"].endswith("/") X # PATH_INFO starts with '/' X assert environ["PATH_INFO"] == "" or environ["PATH_INFO"].startswith("/") X X start_time = time.time() X def _start_response_wrapper(status, response_headers, exc_info=None): X # Postprocess response headers X headerDict = {} X for header, value in response_headers: X if header.lower() in headerDict: X util.warn("Duplicate header in response: %s" % header) X headerDict[header.lower()] = value X X # Check if we should close the connection after this request. X # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.4 X forceCloseConnection = False X currentContentLength = headerDict.get("content-length") X statusCode = int(status.split(" ", 1)[0]) X contentLengthRequired = (environ["REQUEST_METHOD"] != "HEAD" X and statusCode >= 200 X and not statusCode in (204, 304)) X# print environ["REQUEST_METHOD"], statusCode, contentLengthRequired X if contentLengthRequired and currentContentLength in (None, ""): X # A typical case: a GET request on a virtual resource, for which X # the provider doesn't know the length X util.warn("Missing required Content-Length header in %s-response: closing connection" % statusCode) X forceCloseConnection = True X elif not type(currentContentLength) is str: X util.warn("Invalid Content-Length header in response (%r): closing connection" % headerDict.get("content-length")) X forceCloseConnection = True X X # HOTFIX for Vista and Windows 7 (issue 13, issue 23) X # It seems that we must read *all* of the request body, otherwise X # clients may miss the response. X # For example Vista MiniRedir didn't understand a 401 response, X # when trying an anonymous PUT of big files. As a consequence, it X # doesn't retry with credentials and the file copy fails. X # (XP is fine however). X util.readAndDiscardInput(environ) X X # Make sure the socket is not reused, unless we are 100% sure all X # current input was consumed X if(util.getContentLength(environ) != 0 X and not environ.get("wsgidav.all_input_read")): X util.warn("Input stream not completely consumed: closing connection") X forceCloseConnection = True X X if forceCloseConnection and headerDict.get("connection") != "close": X util.warn("Adding 'Connection: close' header") X response_headers.append(("Connection", "close")) X X # Log request X if self._verbose >= 1: X userInfo = environ.get("http_authenticator.username") X if not userInfo: X userInfo = "(anonymous)" X threadInfo = "" X if self._verbose >= 1: X threadInfo = "<%s> " % threading._get_ident() X extra = [] X if "HTTP_DESTINATION" in environ: X extra.append('dest="%s"' % environ.get("HTTP_DESTINATION")) X if environ.get("CONTENT_LENGTH", "") != "": X extra.append("length=%s" % environ.get("CONTENT_LENGTH")) X if "HTTP_DEPTH" in environ: X extra.append("depth=%s" % environ.get("HTTP_DEPTH")) X if "HTTP_RANGE" in environ: X extra.append("range=%s" % environ.get("HTTP_RANGE")) X if "HTTP_OVERWRITE" in environ: X extra.append("overwrite=%s" % environ.get("HTTP_OVERWRITE")) X if self._verbose >= 1 and "HTTP_EXPECT" in environ: X extra.append('expect="%s"' % environ.get("HTTP_EXPECT")) X if self._verbose >= 2 and "HTTP_CONNECTION" in environ: X extra.append('connection="%s"' % environ.get("HTTP_CONNECTION")) X if self._verbose >= 2 and "HTTP_USER_AGENT" in environ: X extra.append('agent="%s"' % environ.get("HTTP_USER_AGENT")) X if self._verbose >= 2 and "HTTP_TRANSFER_ENCODING" in environ: X extra.append('transfer-enc=%s' % environ.get("HTTP_TRANSFER_ENCODING")) X if self._verbose >= 1: X extra.append('elap=%.3fsec' % (time.time() - start_time)) X extra = ", ".join(extra) X X util.log('%s - %s - "%s" %s -> %s' % ( X environ.get("REMOTE_ADDR",""), X userInfo, X environ.get("REQUEST_METHOD") + " " + environ.get("PATH_INFO", ""), X extra, X status X )) X X return start_response(status, response_headers, exc_info) X X # Call next middleware X for v in self._application(environ, _start_response_wrapper): X yield v X return 71e7930a5b0ce78b64861fa928ba0d98 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/domain_controller.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/domain_controller.py << '48d4caafb7fed1d3a54f8f106dc46497' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplementation of a domain controller that uses realm/username/password mappings Xfrom the configuration file and uses the share path as realm name. X XuserMap is defined a follows:: X X userMap = {'realm1': { X 'John Smith': {'description': '', X 'password': 'YouNeverGuessMe', X }, X 'Dan Brown': {'description': '', X 'password': 'DontGuessMeEither', X }, X } X 'realm2': { X ... X } X } X XThe WsgiDAVDomainController fulfills the requirements of a DomainController as Xused for authentication with http_authenticator.HTTPAuthenticator for the XWsgiDAV application. X XDomain Controllers must provide the methods as described in Xdomaincontrollerinterface_ X X.. _domaincontrollerinterface : interfaces/domaincontrollerinterface.py X X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Ximport sys X__docformat__ = "reStructuredText" X Xclass WsgiDAVDomainController(object): X X def __init__(self, userMap): X self.userMap = userMap X# self.allowAnonymous = allowAnonymous X X X def __repr__(self): X return self.__class__.__name__ X X X def getDomainRealm(self, inputURL, environ): X """Resolve a relative url to the appropriate realm name.""" X # we don't get the realm here, its already been resolved in request_resolver X davProvider = environ["wsgidav.provider"] X if not davProvider: X if environ["wsgidav.verbose"] >= 2: X print >>sys.stdout, "getDomainRealm(%s): '%s'" %(inputURL, None) X return None X realm = davProvider.sharePath X if realm == "": X realm = "/" X# if environ["wsgidav.verbose"] >= 2: X# print >>sys.stdout, "getDomainRealm(%s): '%s'" %(inputURL, realm) X return realm X X X def requireAuthentication(self, realmname, environ): X """Return True if this realm requires authentication or False if it is X available for general access.""" X # TODO: Should check for --allow_anonymous? X# assert realmname in environ["wsgidav.config"]["user_mapping"], "Currently there must be at least on user mapping for this realm" X return realmname in self.userMap X X X def isRealmUser(self, realmname, username, environ): X """Returns True if this username is valid for the realm, False otherwise.""" X# if environ["wsgidav.verbose"] >= 2: X# print >>sys.stdout, "isRealmUser('%s', '%s'): %s" %(realmname, username, realmname in self.userMap and username in self.userMap[realmname]) X return realmname in self.userMap and username in self.userMap[realmname] X X X def getRealmUserPassword(self, realmname, username, environ): X """Return the password for the given username for the realm. X X Used for digest authentication. X """ X return self.userMap.get(realmname, {}).get(username, {}).get("password") X X X def authDomainUser(self, realmname, username, password, environ): X """Returns True if this username/password pair is valid for the realm, X False otherwise. Used for basic authentication.""" X user = self.userMap.get(realmname, {}).get(username) X return user is not None and password == user.get("password") 48d4caafb7fed1d3a54f8f106dc46497 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/dav_provider.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/dav_provider.py << '5777038edcdd85757e2ee4285c40a913' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XAbstract base class for DAV resource providers. X XThis module serves these purposes: X X 1. Documentation of the DAVProvider interface X 2. Common base class for all DAV providers X 3. Default implementation for most functionality that a resource provider must X deliver. X XIf no default implementation can be provided, then all write actions generate XFORBIDDEN errors. Read requests generate NOT_IMPLEMENTED errors. X X X**_DAVResource, DAVCollection, DAVNonCollection** X XRepresents an existing (i.e. mapped) WebDAV resource or collection. X XA _DAVResource object is created by a call to the DAVProvider. X XThe resource may then be used to query different attributes like ``res.name``, X``res.isCollection``, ``res.getContentLength()``, and ``res.supportEtag()``. X XIt also implements operations, that require an *existing* resource, like: X``getPreferredPath()``, ``createCollection()``, or ``getPropertyValue()``. X XUsage:: X X res = provider.getResourceInst(path, environ) X if res is not None: X print res.getName() X X X X**DAVProvider** X XA DAV provider represents a shared WebDAV system. X XThere is only one provider instance per share, which is created during Xserver start-up. After that, the dispatcher (``request_resolver.RequestResolver``) Xparses the request URL and adds it to the WSGI environment, so it Xcan be accessed like this:: X X provider = environ["wsgidav.provider"] X XThe main purpose of the provider is to create _DAVResource objects for URLs:: X X res = provider.getResourceInst(path, environ) X X X**Supporting Objects** XThe DAVProvider takes two supporting objects: X XpropertyManager X An object that provides storage for dead properties assigned for webDAV resources. X X PropertyManagers must provide the methods as described in X ``wsgidav.interfaces.propertymanagerinterface`` X X See property_manager.PropertyManager for a sample implementation X using shelve. X XlockmMnager X An object that provides storage for locks made on webDAV resources. X X LockManagers must provide the methods as described in X ``wsgidav.interfaces.lockmanagerinterface`` X X See lock_manager.LockManager for a sample implementation X using shelve. X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Ximport sys Ximport time Ximport traceback Ximport urllib Xfrom datetime import datetime Xfrom wsgidav import util, xml_tools X# Trick PyDev to do intellisense and don't produce warnings: Xfrom util import etree #@UnusedImport Ximport os Xif False: from xml.etree import ElementTree as etree #@Reimport @UnresolvedImport X Xfrom dav_error import DAVError, \ X HTTP_NOT_FOUND, HTTP_FORBIDDEN,\ X PRECONDITION_CODE_ProtectedProperty, asDAVError X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X X_standardLivePropNames = ["{DAV:}creationdate", X "{DAV:}displayname", X "{DAV:}getcontenttype", X "{DAV:}resourcetype", X "{DAV:}getlastmodified", X "{DAV:}getcontentlength", X "{DAV:}getetag", X "{DAV:}getcontentlanguage", X# "{DAV:}source", # removed in rfc4918 X ] X_lockPropertyNames = ["{DAV:}lockdiscovery", X "{DAV:}supportedlock"] X X#DAVHRES_Continue = "continue" X#DAVHRES_Done = "done" X X#=============================================================================== X# _DAVResource X#=============================================================================== Xclass _DAVResource(object): X """Represents a single existing DAV resource instance. X X A resource may be a collection (aka 'folder') or a non-collection (aka X 'file'). X _DAVResource is the common base class for the specialized classes:: X X _DAVResource X +- DAVCollection X \- DAVNonCollection X X Instances of this class are created through the DAVProvider:: X X res = provider.getResourceInst(path, environ) X if res and res.isCollection: X print res.getDisplayName() X X In the example above, res will be ``None``, if the path cannot be mapped to X an existing resource. X The following attributes and methods are considered 'cheap':: X X res.path X res.provider X res.name X res.isCollection X res.environ X X Querying other attributes is considered 'expensive' and may be delayed until X the first access. X X getContentLength() X getContentType() X getCreationDate() X getDisplayName() X getEtag() X getLastModified() X supportRanges() X X supportEtag() X supportModified() X supportContentLength() X X These functions return ``None``, if the property is not available, or X not supported. X X See also DAVProvider.getResourceInst(). X """ X X def __init__(self, path, isCollection, environ): X assert path=="" or path.startswith("/") X self.provider = environ["wsgidav.provider"] X self.path = path X self.isCollection = isCollection X self.environ = environ X self.name = util.getUriName(self.path) X X def __repr__(self): X return "%s(%r)" % (self.__class__.__name__, self.path) X X# def getContentLanguage(self): X# """Contains the Content-Language header returned by a GET without accept X# headers. X# X# The getcontentlanguage property MUST be defined on any DAV compliant X# resource that returns the Content-Language header on a GET. X# """ X# raise NotImplementedError() X X def getContentLength(self): X """Contains the Content-Length header returned by a GET without accept X headers. X X The getcontentlength property MUST be defined on any DAV compliant X resource that returns the Content-Length header in response to a GET. X X This method MUST be implemented by non-collections only. X """ X if self.isCollection: X return None X raise NotImplementedError() X X def getContentType(self): X """Contains the Content-Type header returned by a GET without accept X headers. X X This getcontenttype property MUST be defined on any DAV compliant X resource that returns the Content-Type header in response to a GET. X See http://www.webdav.org/specs/rfc4918.html#PROPERTY_getcontenttype X X This method MUST be implemented by non-collections only. X """ X if self.isCollection: X return None X raise NotImplementedError() X X def getCreationDate(self): X """Records the time and date the resource was created. X X The creationdate property should be defined on all DAV compliant X resources. If present, it contains a timestamp of the moment when the X resource was created (i.e., the moment it had non-null state). X X This method SHOULD be implemented, especially by non-collections. X """ X return None X X def getDirectoryInfo(self): X """Return a list of dictionaries with information for directory X rendering. X X This default implementation return None, so the dir browser will X traverse all members. X X This method COULD be implemented for collection resources. X """ X assert self.isCollection X return None X X def getDisplayName(self): X """Provides a name for the resource that is suitable for presentation to X a user. X X The displayname property should be defined on all DAV compliant X resources. If present, the property contains a description of the X resource that is suitable for presentation to a user. X X This default implementation returns `name`, which is the last path X segment. X """ X return self.name X X def getDisplayInfo(self): X """Return additional info dictionary for displaying (optional). X X This information is not part of the DAV specification, but meant for use X by the dir browser middleware. X X This default implementation returns ``{'type': '...'}`` X """ X if self.isCollection: X return { "type": "Directory" } X elif os.extsep in self.name: X ext = self.name.split(os.extsep)[-1].upper() X if len(ext) < 5: X return { "type": "%s-File" % ext } X return { "type": "File" } X X def getEtag(self): X """ X See http://www.webdav.org/specs/rfc4918.html#PROPERTY_getetag X X This method SHOULD be implemented, especially by non-collections. X """ X return None X X def getLastModified(self): X """Contains the Last-Modified header returned by a GET method without X accept headers. X X Return None, if this live property is not supported. X X Note that the last-modified date on a resource may reflect changes in X any part of the state of the resource, not necessarily just a change to X the response to the GET method. For example, a change in a property may X cause the last-modified date to change. The getlastmodified property X MUST be defined on any DAV compliant resource that returns the X Last-Modified header in response to a GET. X X This method SHOULD be implemented, especially by non-collections. X """ X return None X X def supportRanges(self): X """Return True, if this non-resource supports Range on GET requests. X X This method MUST be implemented by non-collections only. X """ X raise NotImplementedError() X X def supportContentLength(self): X """Return True, if this resource supports Content-Length. X X This default implementation checks `self.getContentLength() is None`. X """ X return self.getContentLength() is not None X X def supportEtag(self): X """Return True, if this resource supports ETags. X X This default implementation checks `self.getEtag() is None`. X """ X return self.getEtag() is not None X X def supportModified(self): X """Return True, if this resource supports last modified dates. X X This default implementation checks `self.getLastModified() is None`. X """ X return self.getLastModified() is not None X X def getPreferredPath(self): X """Return preferred mapping for a resource mapping. X X Different URLs may map to the same resource, e.g.: X '/a/b' == '/A/b' == '/a/b/' X getPreferredPath() returns the same value for all these variants, e.g.: X '/a/b/' (assuming resource names considered case insensitive) X X @param path: a UTF-8 encoded, unquoted byte string. X @return: a UTF-8 encoded, unquoted byte string. X """ X if self.path in ("", "/"): X return "/" X # Append '/' for collections X if self.isCollection and not self.path.endswith("/"): X return self.path + "/" X # TODO: handle case-sensitivity, depending on OS X # (FileSystemProvider could do this with os.path: X # (?) on unix we can assume that the path already matches exactly the case of filepath X # on windows we could use path.lower() or get the real case from the file system X return self.path X X def getRefUrl(self): X """Return the quoted, absolute, unique URL of a resource, relative to appRoot. X X Byte string, UTF-8 encoded, quoted. X Starts with a '/'. Collections also have a trailing '/'. X X This is basically the same as getPreferredPath, but deals with X 'virtual locations' as well. X X e.g. '/a/b' == '/A/b' == '/bykey/123' == '/byguid/abc532' X X getRefUrl() returns the same value for all these URLs, so it can be X used as a key for locking and persistence storage. X X DAV providers that allow virtual-mappings must override this method. X X See also comments in DEVELOPERS.txt glossary. X """ X return urllib.quote(self.provider.sharePath + self.getPreferredPath()) X X# def getRefKey(self): X# """Return an unambigous identifier string for a resource. X# X# Since it is always unique for one resource, is used as key for X# the lock- and property storage dictionaries. X# X# This default implementation calls getRefUrl(), and strips a possible X# trailing '/'. X# """ X# refKey = self.getRefUrl(path) X# if refKey == "/": X# return refKey X# return refKey.rstrip("/") X X def getHref(self): X """Convert path to a URL that can be passed to XML responses. X X Byte string, UTF-8 encoded, quoted. X X See http://www.webdav.org/specs/rfc4918.html#rfc.section.8.3 X We are using the path-absolute option. i.e. starting with '/'. X URI ; See section 3.2.1 of [RFC2068] X """ X # Nautilus chokes, if href encodes '(' as '%28' X # So we don't encode 'extra' and 'safe' characters (see rfc2068 3.2.1) X safe = "/" + "!*'()," + "$-_|." X return urllib.quote(self.provider.mountPath + self.provider.sharePath X + self.getPreferredPath(), safe=safe) X X X# def getParent(self): X# """Return parent _DAVResource or None. X# X# There is NO checking, if the parent is really a mapped collection. X# """ X# parentpath = util.getUriParent(self.path) X# if not parentpath: X# return None X# return self.provider.getResourceInst(parentpath) X X X def getMemberList(self): X """Return a list of direct members (_DAVResource or derived objects). X X This default implementation calls self.getMemberNames() and X self.getMember() for each of them. X A provider COULD overwrite this for performance reasons. X """ X if not self.isCollection: X raise NotImplementedError() X memberList = [] X for name in self.getMemberNames(): X member = self.getMember(name) X assert member is not None X memberList.append(member) X return memberList X X X def getMemberNames(self): X """Return list of (direct) collection member names (UTF-8 byte strings). X X Every provider MUST provide this method for collection resources. X """ X raise NotImplementedError() X X X def getDescendants(self, collections=True, resources=True, X depthFirst=False, depth="infinity", addSelf=False): X """Return a list _DAVResource objects of a collection (children, X grand-children, ...). X X This default implementation calls self.getMemberList() recursively. X X This function may also be called for non-collections (with addSelf=True). X X :Parameters: X depthFirst : bool X use , to list containers before content. X (e.g. when moving / copying branches.) X Use , to list content before containers. X (e.g. when deleting branches.) X depth : string X '0' | '1' | 'infinity' X """ X assert depth in ("0", "1", "infinity") X res = [] X if addSelf and not depthFirst: X res.append(self) X if depth != "0" and self.isCollection: X for child in self.getMemberList(): X if not child: X _ = self.getMemberList() X want = (collections and child.isCollection) or (resources and not child.isCollection) X if want and not depthFirst: X res.append(child) X if child.isCollection and depth == "infinity": X res.extend(child.getDescendants(collections, resources, depthFirst, depth, addSelf=False)) X if want and depthFirst: X res.append(child) X if addSelf and depthFirst: X res.append(self) X return res X X X # --- Properties ----------------------------------------------------------- X X def getPropertyNames(self, isAllProp): X """Return list of supported property names in Clark Notation. X X Note that 'allprop', despite its name, which remains for X backward-compatibility, does not return every property, but only dead X properties and the live properties defined in RFC4918. X X This default implementation returns a combination of: X X - Supported standard live properties in the {DAV:} namespace, if the X related getter method returns not None. X - {DAV:}lockdiscovery and {DAV:}supportedlock, if a lock manager is X present X - If a property manager is present, then a list of dead properties is X appended X X A resource provider may override this method, to add a list of X supported custom live property names. X """ X ## Live properties X propNameList = [] X X propNameList.append("{DAV:}resourcetype") X X if self.getCreationDate() is not None: X propNameList.append("{DAV:}creationdate") X if self.getContentLength() is not None: X assert not self.isCollection X propNameList.append("{DAV:}getcontentlength") X if self.getContentType() is not None: X propNameList.append("{DAV:}getcontenttype") X if self.getLastModified() is not None: X propNameList.append("{DAV:}getlastmodified") X if self.getDisplayName() is not None: X propNameList.append("{DAV:}displayname") X if self.getEtag() is not None: X propNameList.append("{DAV:}getetag") X X ## Locking properties X if self.provider.lockManager and not self.preventLocking(): X propNameList.extend(_lockPropertyNames) X X ## Dead properties X if self.provider.propManager: X refUrl = self.getRefUrl() X propNameList.extend(self.provider.propManager.getProperties(refUrl)) X X return propNameList X X X def getProperties(self, mode, nameList=None): X """Return properties as list of 2-tuples (name, value). X X If mode is 'propname', then None is returned for the value. X X name X the property name in Clark notation. X value X may have different types, depending on the status: X - string or unicode: for standard property values. X - etree.Element: for complex values. X - DAVError in case of errors. X - None: if mode == 'propname'. X X @param mode: "allprop", "propname", or "named" X @param nameList: list of property names in Clark Notation (required for mode 'named') X X This default implementation basically calls self.getPropertyNames() to X get the list of names, then call self.getPropertyValue on each of them. X """ X assert mode in ("allprop", "propname", "named") X X if mode in ("allprop", "propname"): X # TODO: 'allprop' could have nameList, when option is X # implemented X assert nameList is None X nameList = self.getPropertyNames(mode == "allprop") X else: X assert nameList is not None X X propList = [] X namesOnly = (mode == "propname") X for name in nameList: X try: X if namesOnly: X propList.append( (name, None) ) X else: X value = self.getPropertyValue(name) X propList.append( (name, value) ) X except DAVError, e: X propList.append( (name, e) ) X except Exception, e: X propList.append( (name, asDAVError(e)) ) X if self.provider.verbose >= 2: X traceback.print_exc(10, sys.stdout) X X return propList X X X def getPropertyValue(self, propname): X """Return the value of a property. X X propname: X the property name in Clark notation. X return value: X may have different types, depending on the status: X X - string or unicode: for standard property values. X - lxml.etree.Element: for complex values. X X If the property is not available, a DAVError is raised. X X This default implementation handles ``{DAV:}lockdiscovery`` and X ``{DAV:}supportedlock`` using the associated lock manager. X X All other *live* properties (i.e. propname starts with ``{DAV:}``) are X delegated to the self.xxx() getters. X X Finally, other properties are considered *dead*, and are handled by X the associated property manager. X """ X refUrl = self.getRefUrl() X X # lock properties X lm = self.provider.lockManager X if lm and propname == "{DAV:}lockdiscovery": X # TODO: we return HTTP_NOT_FOUND if no lockmanager is present. Correct? X activelocklist = lm.getUrlLockList(refUrl) X lockdiscoveryEL = etree.Element(propname) X for lock in activelocklist: X activelockEL = etree.SubElement(lockdiscoveryEL, "{DAV:}activelock") X X locktypeEL = etree.SubElement(activelockEL, "{DAV:}locktype") X etree.SubElement(locktypeEL, "{DAV:}%s" % lock["type"]) X X lockscopeEL = etree.SubElement(activelockEL, "{DAV:}lockscope") X etree.SubElement(lockscopeEL, "{DAV:}%s" % lock["scope"]) X X etree.SubElement(activelockEL, "{DAV:}depth").text = lock["depth"] X # lock["owner"] is an XML string X ownerEL = xml_tools.stringToXML(lock["owner"]) X X activelockEL.append(ownerEL) X X timeout = lock["timeout"] X if timeout < 0: X timeout = "Infinite" X else: X timeout = "Second-" + str(long(timeout - time.time())) X etree.SubElement(activelockEL, "{DAV:}timeout").text = timeout X X locktokenEL = etree.SubElement(activelockEL, "{DAV:}locktoken") X etree.SubElement(locktokenEL, "{DAV:}href").text = lock["token"] X X # TODO: this is ugly: X # res.getPropertyValue("{DAV:}lockdiscovery") X # X# lockRoot = self.getHref(self.provider.refUrlToPath(lock["root"])) X lockPath = self.provider.refUrlToPath(lock["root"]) X lockRes = self.provider.getResourceInst(lockPath, self.environ) X # FIXME: test for None X lockHref = lockRes.getHref() X# print "lockedRoot: %s -> href=%s" % (lockPath, lockHref) X X lockrootEL = etree.SubElement(activelockEL, "{DAV:}lockroot") X etree.SubElement(lockrootEL, "{DAV:}href").text = lockHref X X return lockdiscoveryEL X X elif lm and propname == "{DAV:}supportedlock": X # TODO: we return HTTP_NOT_FOUND if no lockmanager is present. Correct? X # TODO: the lockmanager should decide about it's features X supportedlockEL = etree.Element(propname) X X lockentryEL = etree.SubElement(supportedlockEL, "{DAV:}lockentry") X lockscopeEL = etree.SubElement(lockentryEL, "{DAV:}lockscope") X etree.SubElement(lockscopeEL, "{DAV:}exclusive") X locktypeEL = etree.SubElement(lockentryEL, "{DAV:}locktype") X etree.SubElement(locktypeEL, "{DAV:}write") X X lockentryEL = etree.SubElement(supportedlockEL, "{DAV:}lockentry") X lockscopeEL = etree.SubElement(lockentryEL, "{DAV:}lockscope") X etree.SubElement(lockscopeEL, "{DAV:}shared") X locktypeEL = etree.SubElement(lockentryEL, "{DAV:}locktype") X etree.SubElement(locktypeEL, "{DAV:}write") X X return supportedlockEL X X elif propname.startswith("{DAV:}"): X # Standard live property (raises HTTP_NOT_FOUND if not supported) X if propname == "{DAV:}creationdate" and self.getCreationDate() is not None: X # Note: uses RFC3339 format (ISO 8601) X return util.getRfc3339Time(self.getCreationDate()) X elif propname == "{DAV:}getcontenttype" and self.getContentType() is not None: X return self.getContentType() X elif propname == "{DAV:}resourcetype": X if self.isCollection: X resourcetypeEL = etree.Element(propname) X etree.SubElement(resourcetypeEL, "{DAV:}collection") X return resourcetypeEL X return "" X elif propname == "{DAV:}getlastmodified" and self.getLastModified() is not None: X # Note: uses RFC1123 format X return util.getRfc1123Time(self.getLastModified()) X elif propname == "{DAV:}getcontentlength" and self.getContentLength() is not None: X # Note: must be a numeric string X return str(self.getContentLength()) X elif propname == "{DAV:}getetag" and self.getEtag() is not None: X return self.getEtag() X elif propname == "{DAV:}displayname" and self.getDisplayName() is not None: X return self.getDisplayName() X X # Unsupported, no persistence available, or property not found X raise DAVError(HTTP_NOT_FOUND) X X # Dead property X pm = self.provider.propManager X if pm: X value = pm.getProperty(refUrl, propname) X if value is not None: X return xml_tools.stringToXML(value) X X # No persistence available, or property not found X raise DAVError(HTTP_NOT_FOUND) X X X def setPropertyValue(self, propname, value, dryRun=False): X """Set a property value or remove a property. X X value == None means 'remove property'. X Raise HTTP_FORBIDDEN if property is read-only, or not supported. X X When dryRun is True, this function should raise errors, as in a real X run, but MUST NOT change any data. X X This default implementation X X - raises HTTP_FORBIDDEN, if trying to modify a locking property X - raises HTTP_FORBIDDEN, if trying to modify a {DAV:} property X - stores everything else as dead property, if a property manager is X present. X - raises HTTP_FORBIDDEN, else X X Removing a non-existing prop is NOT an error. X X Note: RFC 4918 states that {DAV:}displayname 'SHOULD NOT be protected' X X A resource provider may override this method, to update supported custom X live properties. X """ X assert value is None or isinstance(value, (etree._Element)) X X if propname in _lockPropertyNames: X # Locking properties are always read-only X raise DAVError(HTTP_FORBIDDEN, X errcondition=PRECONDITION_CODE_ProtectedProperty) X X # Dead property X pm = self.provider.propManager X if pm and not propname.startswith("{DAV:}"): X refUrl = self.getRefUrl() X if value is None: X return pm.removeProperty(refUrl, propname) X else: X value = etree.tostring(value) X return pm.writeProperty(refUrl, propname, value, dryRun) X X raise DAVError(HTTP_FORBIDDEN) X X X X X def removeAllProperties(self, recursive): X """Remove all associated dead properties.""" X if self.provider.propManager: X self.provider.propManager.removeProperties(self.getRefUrl()) X X X X X # --- Locking -------------------------------------------------------------- X X def preventLocking(self): X """Return True, to prevent locking. X X This default implementation returns ``False``, so standard processing X takes place: locking (and refreshing of locks) is implemented using X the lock manager, if one is configured. X """ X return False X X def isLocked(self): X """Return True, if URI is locked.""" X if self.provider.lockManager is None: X return False X return self.provider.lockManager.isUrlLocked(self.getRefUrl()) X X def removeAllLocks(self, recursive): X if self.provider.lockManager: X self.provider.lockManager.removeAllLocksFromUrl(self.getRefUrl()) X X X # --- Read / write --------------------------------------------------------- X X def createEmptyResource(self, name): X """Create and return an empty (length-0) resource as member of self. X X Called for LOCK requests on unmapped URLs. X X Preconditions (to be ensured by caller): X X - this must be a collection X - must not exist X - there must be no conflicting locks X X Returns a DAVResuource. X X This method MUST be implemented by all providers that support write X access. X This default implementation simply raises HTTP_FORBIDDEN. X """ X assert self.isCollection X raise DAVError(HTTP_FORBIDDEN) X X X def createCollection(self, name): X """Create a new collection as member of self. X X Preconditions (to be ensured by caller): X X - this must be a collection X - must not exist X - there must be no conflicting locks X X This method MUST be implemented by all providers that support write X access. X This default implementation raises HTTP_FORBIDDEN. X """ X assert self.isCollection X raise DAVError(HTTP_FORBIDDEN) X X X def getContent(self): X """Open content as a stream for reading. X X Returns a file-like object / stream containing the contents of the X resource specified. X The calling application will close() the stream. X X This method MUST be implemented by all providers. X """ X assert not self.isCollection X raise NotImplementedError() X X def beginWrite(self, contentType=None): X """Open content as a stream for writing. X X This method MUST be implemented by all providers that support write X access. X """ X assert not self.isCollection X raise DAVError(HTTP_FORBIDDEN) X X def endWrite(self, withErrors): X """Called when PUT has finished writing. X X This is only a notification. that MAY be handled. X """ X pass X X def handleDelete(self): X """Handle a DELETE request natively. X X This method is called by the DELETE handler after checking for valid X request syntax and making sure that there are no conflicting locks and X If-headers. X Depending on the return value, this provider can control further X processing: X X False: X handleDelete() did not do anything. WsgiDAV will process the request X by calling delete() for every resource, bottom-up. X True: X handleDelete() has successfully performed the DELETE request. X HTTP_NO_CONTENT will be reported to the DAV client. X List of errors: X handleDelete() tried to perform the delete request, but failed X completely or partially. A list of errors is returned like X ``[ (, ), ... ]`` X These errors will be reported to the client. X DAVError raised: X handleDelete() refuses to perform the delete request. The DAVError X will be reported to the client. X X An implementation may choose to apply other semantics and return True. X For example deleting '/by_tag/cool/myres' may simply remove the 'cool' X tag from 'my_res'. X In this case, the resource might still be available by other URLs, so X locks and properties are not removed. X X This default implementation returns ``False``, so standard processing X takes place. X X Implementation of this method is OPTIONAL. X """ X return False X X def supportRecursiveDelete(self): X """Return True, if delete() may be called on non-empty collections X (see comments there). X X This method MUST be implemented for collections (not called on X non-collections). X """ X assert self.isCollection X raise NotImplementedError() X X def delete(self): X """Remove this resource (recursive). X X Preconditions (ensured by caller): X X - there are no conflicting locks or If-headers X - if supportRecursiveDelete() is False, and this is a collection, X all members have already been deleted. X X When supportRecursiveDelete is True, this method must be prepared to X handle recursive deletes. This implies that child errors must be X reported as tuple list [ (, ), ... ]. X See http://www.webdav.org/specs/rfc4918.html#delete-collections X X This function X X - removes this resource X - if this is a non-empty collection, also removes all members. X Note that this may only occur, if supportRecursiveDelete is True. X - For recursive deletes, return a list of error tuples for all failed X resource paths. X - removes associated direct locks X - removes associated dead properties X - raises HTTP_FORBIDDEN for read-only resources X - raises HTTP_INTERNAL_ERROR on error X X This method MUST be implemented by all providers that support write X access. X """ X raise NotImplementedError() X X def handleCopy(self, destPath, depthInfinity): X """Handle a COPY request natively. X X This method is called by the COPY handler after checking for valid X request syntax and making sure that there are no conflicting locks and X If-headers. X Depending on the return value, this provider can control further X processing: X X False: X handleCopy() did not do anything. WsgiDAV will process the request X by calling copyMoveSingle() for every resource, bottom-up. X True: X handleCopy() has successfully performed the COPY request. X HTTP_NO_CONTENT/HTTP_CREATED will be reported to the DAV client. X List of errors: X handleCopy() tried to perform the copy request, but failed X completely or partially. A list of errors is returned like X ``[ (, ), ... ]`` X These errors will be reported to the client. X DAVError raised: X handleCopy() refuses to perform the copy request. The DAVError X will be reported to the client. X X An implementation may choose to apply other semantics and return True. X For example copying '/by_tag/cool/myres' to '/by_tag/hot/myres' may X simply add a 'hot' tag. X In this case, the resource might still be available by other URLs, so X locks and properties are not removed. X X This default implementation returns ``False``, so standard processing X takes place. X X Implementation of this method is OPTIONAL. X """ X return False X X def copyMoveSingle(self, destPath, isMove): X """Copy or move this resource to destPath (non-recursive). X X Preconditions (ensured by caller): X X - there must not be any conflicting locks on destination X - overwriting is only allowed (i.e. destPath exists), when source and X dest are of the same type ((non-)collections) and a Overwrite='T' X was passed X - destPath must not be a child path of this resource X X This function X X - Overwrites non-collections content, if destination exists. X - MUST NOT copy collection members. X - MUST NOT copy locks. X - SHOULD copy live properties, when appropriate. X E.g. displayname should be copied, but creationdate should be X reset if the target did not exist before. X See http://www.webdav.org/specs/rfc4918.html#dav.properties X - SHOULD copy dead properties. X - raises HTTP_FORBIDDEN for read-only providers X - raises HTTP_INTERNAL_ERROR on error X X When isMove is True, X X - Live properties should be moved too (e.g. creationdate) X - Non-collections must be moved, not copied X - For collections, this function behaves like in copy-mode: X detination collection must be created and properties are copied. X Members are NOT created. X The source collection MUST NOT be removed. X X This method MUST be implemented by all providers that support write X access. X """ X raise NotImplementedError() X X def handleMove(self, destPath): X """Handle a MOVE request natively. X X This method is called by the MOVE handler after checking for valid X request syntax and making sure that there are no conflicting locks and X If-headers. X Depending on the return value, this provider can control further X processing: X X False: X handleMove() did not do anything. WsgiDAV will process the request X by calling delete() and copyMoveSingle() for every resource, X bottom-up. X True: X handleMove() has successfully performed the MOVE request. X HTTP_NO_CONTENT/HTTP_CREATED will be reported to the DAV client. X List of errors: X handleMove() tried to perform the move request, but failed X completely or partially. A list of errors is returned like X ``[ (, ), ... ]`` X These errors will be reported to the client. X DAVError raised: X handleMove() refuses to perform the move request. The DAVError X will be reported to the client. X X An implementation may choose to apply other semantics and return True. X For example moving '/by_tag/cool/myres' to '/by_tag/hot/myres' may X simply remove the 'cool' tag from 'my_res' and add a 'hot' tag instead. X In this case, the resource might still be available by other URLs, so X locks and properties are not removed. X X This default implementation returns ``False``, so standard processing X takes place. X X Implementation of this method is OPTIONAL. X """ X return False X X def supportRecursiveMove(self, destPath): X """Return True, if moveRecursive() is available (see comments there).""" X assert self.isCollection X raise NotImplementedError() X X def moveRecursive(self, destPath): X """Move this resource and members to destPath. X X This method is only called, when supportRecursiveMove() returns True. X X MOVE is frequently used by clients to rename a file without changing its X parent collection, so it's not appropriate to reset all live properties X that are set at resource creation. For example, the DAV:creationdate X property value SHOULD remain the same after a MOVE. X X Preconditions (ensured by caller): X X - there must not be any conflicting locks or If-header on source X - there must not be any conflicting locks or If-header on destination X - destPath must not exist X - destPath must not be a member of this resource X X This method must be prepared to handle recursive moves. This implies X that child errors must be reported as tuple list X [ (, ), ... ]. X See http://www.webdav.org/specs/rfc4918.html#move-collections X X This function X X - moves this resource and all members to destPath. X - MUST NOT move associated locks. X Instead, if the source (or children thereof) have locks, then X these locks should be removed. X - SHOULD maintain associated live properties, when applicable X See http://www.webdav.org/specs/rfc4918.html#dav.properties X - MUST maintain associated dead properties X - raises HTTP_FORBIDDEN for read-only resources X - raises HTTP_INTERNAL_ERROR on error X X An implementation may choose to apply other semantics. X For example copying '/by_tag/cool/myres' to '/by_tag/new/myres' may X simply add a 'new' tag to 'my_res'. X X This method is only called, when self.supportRecursiveMove() returns X True. Otherwise, the request server implements MOVE using delete/copy. X X This method MAY be implemented in order to improve performance. X """ X raise DAVError(HTTP_FORBIDDEN) X X def resolve(self, scriptName, pathInfo): X """Return a _DAVResource object for the path (None, if not found). X X `pathInfo`: is a URL relative to this object. X X DAVCollection.resolve() provides an implementation. X """ X raise NotImplementedError() X X X#=============================================================================== X# DAVCollection X#=============================================================================== Xclass DAVNonCollection(_DAVResource): X """ X A DAVNonCollection is a _DAVResource, that has content (like a 'file' on X a filesystem). X X A DAVNonCollecion is able to read and write file content. X X See also _DAVResource X """ X def __init__(self, path, environ): X _DAVResource.__init__(self, path, False, environ) X X def getContentLength(self): X """Returns the byte length of the content. X X MUST be implemented. X X See also _DAVResource.getContentLength() X """ X raise NotImplementedError() X X def getContentType(self): X """Contains the Content-Type header returned by a GET without accept X headers. X X This getcontenttype property MUST be defined on any DAV compliant X resource that returns the Content-Type header in response to a GET. X See http://www.webdav.org/specs/rfc4918.html#PROPERTY_getcontenttype X """ X raise NotImplementedError() X X def getContent(self): X """Open content as a stream for reading. X X Returns a file-like object / stream containing the contents of the X resource specified. X The application will close() the stream. X X This method MUST be implemented by all providers. X """ X raise NotImplementedError() X X def supportRanges(self): X """Return True, if this non-resource supports Range on GET requests. X X This default implementation returns False. X """ X return False X X def beginWrite(self, contentType=None): X """Open content as a stream for writing. X X This method MUST be implemented by all providers that support write X access. X """ X raise DAVError(HTTP_FORBIDDEN) X X def endWrite(self, withErrors): X """Called when PUT has finished writing. X X This is only a notification that MAY be handled. X """ X pass X X def resolve(self, scriptName, pathInfo): X """Return a _DAVResource object for the path (None, if not found). X X Since non-collection don't have members, we return None if path is not X empty. X """ X if pathInfo in ("", "/"): X return self X return None X X X#=============================================================================== X# DAVCollection X#=============================================================================== Xclass DAVCollection(_DAVResource): X """ X A DAVCollection is a _DAVResource, that has members (like a 'folder' on X a filesystem). X X A DAVCollecion 'knows' its members, and how to obtain them from the backend X storage. X There is also optional built-in support for member caching. X X See also _DAVResource X """ X def __init__(self, path, environ): X _DAVResource.__init__(self, path, True, environ) X X # Allow caching of members X# self.memberCache = {"enabled": False, X# "expire": 10, # Purge, if not used for n seconds X# "maxAge": 60, # Force purge, if older than n seconds X# "created": None, X# "lastUsed": None, X# "members": None, X# } X X# def _cacheSet(self, members): X# if self.memberCache["enabled"]: X# if not members: X# # We cannot cache None, because _cacheGet() == None means 'not in cache' X# members = [] X# self.memberCache["created"] = self.memberCache["lastUsed"] = datetime.now() X# self.memberCache["members"] = members X# X# def _cacheGet(self): X# if not self.memberCache["enabled"]: X# return None X# now = datetime.now() X# if (now - self.memberCache["lastUsed"]) > self.memberCache["expire"]: X# return None X# elif (now - self.memberCache["created"]) > self.memberCache["maxAge"]: X# return None X# self.memberCache["lastUsed"] = datetime.now() X# return self.memberCache["members"] X# X# def _cachePurge(self): X# self.memberCache["created"] = self.memberCache["lastUsed"] = self.memberCache["members"] = None X X# def getContentLanguage(self): X# return None X X def getContentLength(self): X return None X X def getContentType(self): X return None X X def createEmptyResource(self, name): X """Create and return an empty (length-0) resource as member of self. X X Called for LOCK requests on unmapped URLs. X X Preconditions (to be ensured by caller): X X - this must be a collection X - must not exist X - there must be no conflicting locks X X Returns a DAVResuource. X X This method MUST be implemented by all providers that support write X access. X This default implementation simply raises HTTP_FORBIDDEN. X """ X raise DAVError(HTTP_FORBIDDEN) X X def createCollection(self, name): X """Create a new collection as member of self. X X Preconditions (to be ensured by caller): X X - this must be a collection X - must not exist X - there must be no conflicting locks X X This method MUST be implemented by all providers that support write X access. X This default implementation raises HTTP_FORBIDDEN. X """ X assert self.isCollection X raise DAVError(HTTP_FORBIDDEN) X X def getMember(self, name): X """Return child resource with a given name (None, if not found). X X This method COULD be overridden by a derived class, for performance X reasons. X This default implementation calls self.provider.getResourceInst(). X """ X assert self.isCollection X return self.provider.getResourceInst(util.joinUri(self.path, name), X self.environ) X X def getMemberNames(self): X """Return list of (direct) collection member names (UTF-8 byte strings). X X This method MUST be implemented. X """ X assert self.isCollection X raise NotImplementedError() X X def supportRecursiveDelete(self): X """Return True, if delete() may be called on non-empty collections X (see comments there). X X This default implementation returns False. X """ X return False X X def delete(self): X """Remove this resource (possibly recursive). X X This method MUST be implemented if resource allows write access. X X See _DAVResource.delete() X """ X raise DAVError(HTTP_FORBIDDEN) X X def copyMoveSingle(self, destPath, isMove): X """Copy or move this resource to destPath (non-recursive). X X This method MUST be implemented if resource allows write access. X X See _DAVResource.copyMoveSingle() X """ X raise DAVError(HTTP_FORBIDDEN) X X def supportRecursiveMove(self, destPath): X """Return True, if moveRecursive() is available (see comments there).""" X return False X X def moveRecursive(self, destPath): X """Move this resource and members to destPath. X X This method MAY be implemented in order to improve performance. X """ X raise DAVError(HTTP_FORBIDDEN) X X def resolve(self, scriptName, pathInfo): X """Return a _DAVResource object for the path (None, if not found). X X `pathInfo`: is a URL relative to this object. X """ X if pathInfo in ("", "/"): X return self X assert pathInfo.startswith("/") X name, rest = util.popPath(pathInfo) X res = self.getMember(name) X if res is None or rest in ("", "/"): X return res X return res.resolve(util.joinUri(scriptName, name), rest) X X X#=============================================================================== X# DAVProvider X#=============================================================================== X Xclass DAVProvider(object): X """Abstract base class for DAV resource providers. X X There will be only one DAVProvider instance per share (not per request). X """ X def __init__(self): X self.mountPath = "" X self.sharePath = None X self.lockManager = None X self.propManager = None X self.verbose = 2 X X self._count_getResourceInst = 0 X self._count_getResourceInstInit = 0 X# self.caseSensitiveUrls = True X X def __repr__(self): X return self.__class__.__name__ X X def setMountPath(self, mountPath): X """Set application root for this resource provider. X X This is the value of SCRIPT_NAME, when WsgiDAVApp is called. X """ X assert mountPath in ("", "/") or not mountPath.endswith("/") X self.mountPath = mountPath X X def setSharePath(self, sharePath): X """Set application location for this resource provider. X X @param sharePath: a UTF-8 encoded, unquoted byte string. X """ X if isinstance(sharePath, unicode): X sharePath = sharePath.encode("utf8") X assert sharePath=="" or sharePath.startswith("/") X if sharePath == "/": X sharePath = "" # This allows to code 'absPath = sharePath + path' X assert sharePath in ("", "/") or not sharePath.endswith("/") X self.sharePath = sharePath X X def setLockManager(self, lockManager): X assert not lockManager or hasattr(lockManager, "checkWritePermission"), "Must be compatible with wsgidav.lock_manager.LockManager" X self.lockManager = lockManager X X def setPropManager(self, propManager): X assert not propManager or hasattr(propManager, "copyProperties"), "Must be compatible with wsgidav.property_manager.PropertyManager" X self.propManager = propManager X X def refUrlToPath(self, refUrl): X """Convert a refUrl to a path, by stripping the share prefix. X X Used to calculate the from a storage key by inverting getRefUrl(). X """ X return "/" + urllib.unquote(util.lstripstr(refUrl, self.sharePath)).lstrip("/") X X def getResourceInst(self, path, environ): X """Return a _DAVResource object for path. X X Should be called only once per request and resource:: X X res = provider.getResourceInst(path, environ) X if res and not res.isCollection: X print res.getContentType() X X If does not exist, None is returned. X may be used by the provider to implement per-request caching. X X See _DAVResource for details. X X This method MUST be implemented. X """ X raise NotImplementedError() X X def exists(self, path, environ): X """Return True, if path maps to an existing resource. X X This method should only be used, if no other information is queried X for . Otherwise a _DAVResource should be created first. X X This method SHOULD be overridden by a more efficient implementation. X """ X return self.getResourceInst(path, environ) is not None X X def isCollection(self, path, environ): X """Return True, if path maps to an existing collection resource. X X This method should only be used, if no other information is queried X for . Otherwise a _DAVResource should be created first. X """ X res = self.getResourceInst(path, environ) X return res and res.isCollection 5777038edcdd85757e2ee4285c40a913 echo c - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces mkdir -p py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces > /dev/null 2>&1 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/dav_provider_interface.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/dav_provider_interface.py << '2770cb8d297fb9fc9f15fc38807c3750' Xclass IDAVProvider(object): X """ X +----------------------------------------------------------------------+ X | TODO: document this interface | X | For now, see wsgidav.DAVProvider instead. | X +----------------------------------------------------------------------+ X X This class is an interface for a WebDAV provider. X Implementations in WsgiDAV include:: X X wsgidav.DAVProvider (abstract base class) X wsgidav.fs_dav_provider.ReadOnlyFilesystemProvider X wsgidav.fs_dav_provider.FilesystemProvider X wsgidav.addons.mysql_dav_provider.MySQLBrowserProvider X wsgidav.addons.VirtualResourceProvider X X All methods must be implemented. X X """ 2770cb8d297fb9fc9f15fc38807c3750 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/propertymanagerinterface.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/propertymanagerinterface.py << '948dd012a19bc49a7c5c475f0e9dbf0a' X Xclass PropertyManagerInterface(object): X """ X +----------------------------------------------------------------------+ X | TODO: document this interface | X | For now, see wsgidav.lock_manager instead | X +----------------------------------------------------------------------+ X X This class is an interface for a PropertyManager. X Implementations of a property manager in WsgiDAV include:: X X _ X wsgidav.property_manager.ShelvePropertyManager X X All methods must be implemented. X X The url variable in methods refers to the relative URL of a resource. e.g. the X resource http://server/share1/dir1/dir2/file3.txt would have a url of X '/share1/dir1/dir2/file3.txt' X X X All methods must be implemented. X X The url variables in methods refers to the relative URL of a resource. e.g. the X resource http://server/share1/dir1/dir2/file3.txt would have a url of X '/share1/dir1/dir2/file3.txt' X X Properties and WsgiDAV X ---------------------- X Properties of a resource refers to the attributes of the resource. A property X is referenced by the property name and the property namespace. We usually X refer to the property as ``{property namespace}property name`` X X Properties of resources as defined in webdav falls under three categories: X X Live properties X These properties are attributes actively maintained by the server, such as X file size, or read permissions. if you are sharing a database record as a X resource, for example, the attributes of the record could become the live X properties of the resource. X X The webdav specification defines the following properties that could be X live properties (refer to webdav specification for details): X {DAV:}creationdate X {DAV:}displayname X {DAV:}getcontentlanguage X {DAV:}getcontentlength X {DAV:}getcontenttype X {DAV:}getetag X {DAV:}getlastmodified X {DAV:}resourcetype X {DAV:}source X X These properties are implemented by the abstraction layer. X X Locking properties X They refer to the two webdav-defined properties X {DAV:}supportedlock and {DAV:}lockdiscovery X X These properties are implemented by the locking library in X ``wsgidav.lock_manager`` and dead properties library in X ``wsgidav.property_manager`` X X Dead properties X They refer to arbitrarily assigned properties not actively maintained. X X These properties are implemented by the dead properties library in X ``wsgidav.property_manager`` X X """ 948dd012a19bc49a7c5c475f0e9dbf0a echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/domaincontrollerinterface.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/domaincontrollerinterface.py << 'fdf5943a9e8e3eb8298ce5597bde3a0f' Xclass IDomainController(object): X """ X +----------------------------------------------------------------------+ X | TODO: document this interface | X | For now, see wsgidav.domain_controller instead | X +----------------------------------------------------------------------+ X X This class is an interface for a domain controller. X Implementations in WsgiDAV include:: X X wsgidav.domain_controller.WsgiDAVDomainController X wsgidav.addons.nt_domain_controller.NTDomainController X X All methods must be implemented. X X The environ variable here is the WSGI 'environ' dictionary. It is passed to X all methods of the domain controller as a means for developers to pass information X from previous middleware or server config (if required). X X X Domain Controllers X ------------------ X X The HTTP basic and digest authentication schemes are based on the following X concept: X X Each requested relative URI can be resolved to a realm for authentication, X for example: X /fac_eng/courses/ee5903/timetable.pdf -> might resolve to realm 'Engineering General' X /fac_eng/examsolns/ee5903/thisyearssolns.pdf -> might resolve to realm 'Engineering Lecturers' X /med_sci/courses/m500/surgery.htm -> might resolve to realm 'Medical Sciences General' X and each realm would have a set of username and password pairs that would X allow access to the resource. X X A domain controller provides this information to the HTTPAuthenticator. X """ fdf5943a9e8e3eb8298ce5597bde3a0f echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/lockmanagerinterface.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/interfaces/lockmanagerinterface.py << 'd53a13e59d81e569e14ce5a1ba016252' Xclass LockManagerInterface(object): X """ X +----------------------------------------------------------------------+ X | TODO: document this interface | X | For now, see wsgidav.lock_manager instead | X +----------------------------------------------------------------------+ X X This class is an interface for a LockManager. X Implementations for the lock manager in WsgiDAV include:: X X wsgidav.lock_manager.LockManager X wsgidav.lock_manager.ShelveLockManager X X All methods must be implemented. X X The url variable in methods refers to the relative URL of a resource. e.g. the X resource http://server/share1/dir1/dir2/file3.txt would have a url of X '/share1/dir1/dir2/file3.txt' X """ d53a13e59d81e569e14ce5a1ba016252 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/lock_storage.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/lock_storage.py << '5320485269d6034ead40a38628805a04' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplements two storage providers for `LockManager`. X XTwo alternative lock storage classes are defined here: one in-memory X(dict-based), and one persistent low performance variant using shelve. X XSee wsgidav.lock_manager.LockManager X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom wsgidav.lock_manager import normalizeLockRoot, lockString,\ X generateLockToken, validateLock Ximport os Ximport util Ximport shelve Ximport time Xfrom rw_lock import ReadWriteLock X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X X# TODO: comment's from Ian Bicking (2005) X#@@: Use of shelve means this is only really useful in a threaded environment. X# And if you have just a single-process threaded environment, you could get X# nearly the same effect with a dictionary of threading.Lock() objects. Of course, X# it would be better to move off shelve anyway, probably to a system with X# a directory of per-file locks, using the file locking primitives (which, X# sadly, are not quite portable). X# @@: It would probably be easy to store the properties as pickle objects X# in a parallel directory structure to the files you are describing. X# Pickle is expedient, but later you could use something more readable X# (pickles aren't particularly readable) X X X#=============================================================================== X# LockStorageDict X#=============================================================================== Xclass LockStorageDict(object): X """ X An in-memory lock manager storage implementation using a dictionary. X X R/W access is guarded by a thread.lock object. X X Also, to make it work with a Shelve dictionary, modifying dictionary X members is done by re-assignment and we call a _flush() method. X X This is obviously not persistent, but should be enough in some cases. X For a persistent implementation, see lock_manager.LockStorageShelve(). X X Notes: X expire is stored as expiration date in seconds since epoch (not in X seconds until expiration). X X The dictionary is built like:: X X { 'URL2TOKEN:/temp/litmus/lockme': ['opaquelocktoken:0x1d7b86...', X 'opaquelocktoken:0xd7d4c0...'], X 'opaquelocktoken:0x1d7b86...': { 'depth': '0', X 'owner': "\\nlitmus test suite\\n", X 'principal': 'tester', X 'root': '/temp/litmus/lockme', X 'scope': 'shared', X 'expire': 1261328382.4530001, X 'token': 'opaquelocktoken:0x1d7b86...', X 'type': 'write', X }, X 'opaquelocktoken:0xd7d4c0...': { 'depth': '0', X 'owner': '\\nlitmus: notowner_sharedlock\\n', X 'principal': 'tester', X 'root': '/temp/litmus/lockme', X 'scope': 'shared', X 'expire': 1261328381.6040001, X 'token': 'opaquelocktoken:0xd7d4c0...', X 'type': 'write' X }, X } X """ X LOCK_TIME_OUT_DEFAULT = 604800 # 1 week, in seconds X LOCK_TIME_OUT_MAX = 4 * 604800 # 1 month, in seconds X X def __init__(self): X self._dict = None X self._lock = ReadWriteLock() X X X def __repr__(self): X return self.__class__.__name__ X X X def __del__(self): X pass X X X def _flush(self): X """Overloaded by Shelve implementation.""" X pass X X X def open(self): X """Called before first use. X X May be implemented to initialize a storage. X """ X assert self._dict is None X self._dict = {} X X X def close(self): X """Called on shutdown.""" X self._dict = None X X X def cleanup(self): X """Purge expired locks (optional).""" X pass X X X def get(self, token): X """Return a lock dictionary for a token. X X If the lock does not exist or is expired, None is returned. X X token: X lock token X Returns: X Lock dictionary or X X Side effect: if lock is expired, it will be purged and None is returned. X """ X self._lock.acquireRead() X try: X lock = self._dict.get(token) X if lock is None: X # Lock not found: purge dangling URL2TOKEN entries X _logger.debug("Lock purged dangling: %s" % token) X self.delete(token) X return None X expire = float(lock["expire"]) X if expire >= 0 and expire < time.time(): X _logger.debug("Lock timed-out(%s): %s" % (expire, lockString(lock))) X self.delete(token) X return None X return lock X finally: X self._lock.release() X X X def create(self, path, lock): X """Create a direct lock for a resource path. X X path: X Normalized path (utf8 encoded string, no trailing '/') X lock: X lock dictionary, without a token entry X Returns: X New unique lock token.: X - lock['timeout'] may be normalized and shorter than requested X - lock['token'] is added X """ X self._lock.acquireWrite() X try: X # We expect only a lock definition, not an existing lock X assert lock.get("token") is None X assert lock.get("expire") is None, "Use timeout instead of expire" X assert path and "/" in path X X # Normalize root: /foo/bar X org_path = path X path = normalizeLockRoot(path) X lock["root"] = path X X # Normalize timeout from ttl to expire-date X timeout = float(lock.get("timeout")) X if timeout is None: X timeout = LockStorageDict.LOCK_TIME_OUT_DEFAULT X elif timeout < 0 or timeout > LockStorageDict.LOCK_TIME_OUT_MAX: X timeout = LockStorageDict.LOCK_TIME_OUT_MAX X X lock["timeout"] = timeout X lock["expire"] = time.time() + timeout X X validateLock(lock) X X token = generateLockToken() X lock["token"] = token X X # Store lock X self._dict[token] = lock X X # Store locked path reference X key = "URL2TOKEN:%s" % path X if not key in self._dict: X self._dict[key] = [ token ] X else: X # Note: Shelve dictionary returns copies, so we must reassign values: X tokList = self._dict[key] X tokList.append(token) X self._dict[key] = tokList X self._flush() X _logger.debug("LockStorageDict.set(%r): %s" % (org_path, lockString(lock))) X# print("LockStorageDict.set(%r): %s" % (org_path, lockString(lock))) X return lock X finally: X self._lock.release() X X X def refresh(self, token, timeout): X """Modify an existing lock's timeout. X X token: X Valid lock token. X timeout: X Suggested lifetime in seconds (-1 for infinite). X The real expiration time may be shorter than requested! X Returns: X Lock dictionary. X Raises ValueError, if token is invalid. X """ X assert token in self._dict, "Lock must exist" X assert timeout == -1 or timeout > 0 X if timeout < 0 or timeout > LockStorageDict.LOCK_TIME_OUT_MAX: X timeout = LockStorageDict.LOCK_TIME_OUT_MAX X X self._lock.acquireWrite() X try: X # Note: shelve dictionary returns copies, so we must reassign values: X lock = self._dict[token] X lock["timeout"] = timeout X lock["expire"] = time.time() + timeout X self._dict[token] = lock X self._flush() X finally: X self._lock.release() X return lock X X X def delete(self, token): X """Delete lock. X X Returns True on success. False, if token does not exist, or is expired. X """ X self._lock.acquireWrite() X try: X lock = self._dict.get(token) X _logger.debug("delete %s" % lockString(lock)) X if lock is None: X return False X # Remove url to lock mapping X key = "URL2TOKEN:%s" % lock.get("root") X if key in self._dict: X# _logger.debug(" delete token %s from url %s" % (token, lock.get("root"))) X tokList = self._dict[key] X if len(tokList) > 1: X # Note: shelve dictionary returns copies, so we must reassign values: X tokList.remove(token) X self._dict[key] = tokList X else: X del self._dict[key] X # Remove the lock X del self._dict[token] X X self._flush() X finally: X self._lock.release() X return True X X X def getLockList(self, path, includeRoot, includeChildren, tokenOnly): X """Return a list of direct locks for . X X Expired locks are *not* returned (but may be purged). X X path: X Normalized path (utf8 encoded string, no trailing '/') X includeRoot: X False: don't add lock (only makes sense, when includeChildren X is True). X includeChildren: X True: Also check all sub-paths for existing locks. X tokenOnly: X True: only a list of token is returned. This may be implemented X more efficiently by some providers. X Returns: X List of valid lock dictionaries (may be empty). X """ X assert path and path.startswith("/") X assert includeRoot or includeChildren X def __appendLocks(toklist): X # Since we can do this quickly, we use self.get() even if X # tokenOnly is set, so expired locks are purged. X for token in toklist: X lock = self.get(token) X if lock: X if tokenOnly: X lockList.append(lock["token"]) X else: X lockList.append(lock) X X path = normalizeLockRoot(path) X self._lock.acquireRead() X try: X key = "URL2TOKEN:%s" % path X tokList = self._dict.get(key, []) X lockList = [] X if includeRoot: X __appendLocks(tokList) X X if includeChildren: X for u, ltoks in self._dict.items(): X if util.isChildUri(key, u): X __appendLocks(ltoks) X X return lockList X finally: X self._lock.release() X X X#=============================================================================== X# LockStorageShelve X#=============================================================================== X Xclass LockStorageShelve(LockStorageDict): X """ X A low performance lock manager implementation using shelve. X """ X def __init__(self, storagePath): X super(LockStorageShelve, self).__init__() X self._storagePath = os.path.abspath(storagePath) X X X def __repr__(self): X return "LockStorageShelve(%r)" % self._storagePath X X X def _flush(self): X """Write persistent dictionary to disc.""" X _logger.debug("_flush()") X self._lock.acquireWrite() # TODO: read access is enough? X try: X self._dict.sync() X finally: X self._lock.release() X X X def open(self): X _logger.debug("open(%r)" % self._storagePath) X # Open with writeback=False, which is faster, but we have to be X # careful to re-assign values to _dict after modifying them X self._dict = shelve.open(self._storagePath, writeback=False) X# if __debug__ and self._verbose >= 2: X## self._check("After shelve.open()") X# self._dump("After shelve.open()") X X X def close(self): X _logger.debug("close()") X self._lock.acquireWrite() X try: X if self._dict is not None: X self._dict.close() X self._dict = None X finally: X self._lock.release() X X X#=============================================================================== X# test X#=============================================================================== Xdef test(): X# l = ShelveLockManager("wsgidav-locks.shelve") X# l._lazyOpen() X# l._dump() X# l.generateLock("martin", "", lockscope, lockdepth, lockowner, lockroot, timeout) X pass X Xif __name__ == "__main__": X test() 5320485269d6034ead40a38628805a04 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/request_server.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/request_server.py << 'a4a6956fc9fbafbe294f0a2256c81ce1' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XWSGI application that handles one single WebDAV request. X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom urlparse import urlparse Xfrom wsgidav.dav_error import HTTP_OK, HTTP_LENGTH_REQUIRED Xfrom wsgidav import xml_tools Ximport util Ximport urllib Xtry: X from cStringIO import StringIO Xexcept ImportError: X from StringIO import StringIO #@UnusedImport X Xfrom dav_error import DAVError, asDAVError, \ X HTTP_BAD_REQUEST,\ X HTTP_NOT_IMPLEMENTED, HTTP_NOT_FOUND, HTTP_FORBIDDEN, HTTP_INTERNAL_ERROR,\ X HTTP_FAILED_DEPENDENCY, HTTP_METHOD_NOT_ALLOWED,\ X PRECONDITION_CODE_PropfindFiniteDepth, HTTP_MEDIATYPE_NOT_SUPPORTED,\ X HTTP_CONFLICT, \ X PRECONDITION_CODE_LockTokenMismatch, getHttpStatusString,\ X HTTP_PRECONDITION_FAILED, HTTP_BAD_GATEWAY, HTTP_NO_CONTENT, HTTP_CREATED,\ X HTTP_RANGE_NOT_SATISFIABLE X X#import lock_manager X X# Trick PyDev to do intellisense and don't produce warnings: Xfrom util import etree #@UnusedImport Xif False: from xml.etree import ElementTree as etree #@Reimport X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X XBLOCK_SIZE = 8192 X X X X#=============================================================================== X# RequestServer X#=============================================================================== Xclass RequestServer(object): X X def __init__(self, davProvider): X self._davProvider = davProvider X self.allowPropfindInfinite = True X self._verbose = 2 X util.debug("RequestServer: __init__", module="sc") X X def __del__(self): X util.debug("RequestServer: __del__", module="sc") X X def __call__(self, environ, start_response): X assert "wsgidav.verbose" in environ X # TODO: allow anonymous somehow: this should run, even if http_authenticator middleware is not installed X# assert "http_authenticator.username" in environ X if not "http_authenticator.username" in environ: X _logger.info("*** missing 'http_authenticator.username' in environ") X X environ["wsgidav.username"] = environ.get("http_authenticator.username", "anonymous") X requestmethod = environ["REQUEST_METHOD"] X X # Convert 'infinity' and 'T'/'F' to a common case X if environ.get("HTTP_DEPTH") is not None: X environ["HTTP_DEPTH"] = environ["HTTP_DEPTH"].lower() X if environ.get("HTTP_OVERWRITE") is not None: X environ["HTTP_OVERWRITE"] = environ["HTTP_OVERWRITE"].upper() X X if "HTTP_EXPECT" in environ: X pass X X # Dispatch HTTP request methods to 'doMETHOD()' handlers X method = getattr(self, "do%s" % requestmethod, None) X if not method: X self._fail(HTTP_METHOD_NOT_ALLOWED) X X if environ.get("wsgidav.debug_break"): X pass # Set a break point here X X if environ.get("wsgidav.debug_profile"): X from cProfile import Profile X profile = Profile() X res = profile.runcall(method, environ, start_response) X # sort: 0:"calls",1:"time", 2: "cumulative" X profile.print_stats(sort=2) X for v in res: X yield v X return X X for v in method(environ, start_response): X yield v X return X# return method(environ, start_response) X X X def _fail(self, value, contextinfo=None, srcexception=None, errcondition=None): X """Wrapper to raise (and log) DAVError.""" X if isinstance(value, Exception): X e = asDAVError(value) X else: X e = DAVError(value, contextinfo, srcexception, errcondition) X util.log("Raising DAVError %s" % e.getUserInfo()) X raise e X X X def _sendResponse(self, environ, start_response, rootRes, successCode, errorList): X """Send WSGI response (single or multistatus). X X - If errorList is None or [], then is send as response. X - If errorList contains a single error with a URL that matches rootRes, X then this error is returned. X - If errorList contains more than one error, then '207 Multistatus' is X returned. X """ X assert successCode in (HTTP_CREATED, HTTP_NO_CONTENT, HTTP_OK) X if not errorList: X # Status OK X return util.sendStatusResponse(environ, start_response, successCode) X if len(errorList) == 1 and errorList[0][0] == rootRes.getHref(): X # Only one error that occurred on the root resource X return util.sendStatusResponse(environ, start_response, errorList[0][1]) X X # Multiple errors, or error on one single child X multistatusEL = xml_tools.makeMultistatusEL() X X for refurl, e in errorList: X# assert refurl.startswith("http:") X assert refurl.startswith("/") X assert isinstance(e, DAVError) X responseEL = etree.SubElement(multistatusEL, "{DAV:}response") X etree.SubElement(responseEL, "{DAV:}href").text = refurl X etree.SubElement(responseEL, "{DAV:}status").text = "HTTP/1.1 %s" % getHttpStatusString(e) X X return util.sendMultiStatusResponse(environ, start_response, multistatusEL) X X X def _checkWritePermission(self, res, depth, environ): X """Raise DAVError(HTTP_LOCKED), if res is locked. X X If depth=='infinity', we also raise when child resources are locked. X """ X lockMan = self._davProvider.lockManager X if lockMan is None or res is None: X return True X X refUrl = res.getRefUrl() X X if "wsgidav.conditions.if" not in environ: X util.parseIfHeaderDict(environ) X X # raise HTTP_LOCKED if conflict exists X lockMan.checkWritePermission(refUrl, depth, X environ["wsgidav.ifLockTokenList"], X environ["wsgidav.username"]) X X X def _evaluateIfHeaders(self, res, environ): X """Apply HTTP headers on , raising DAVError if conditions fail. X X Add environ['wsgidav.conditions.if'] and environ['wsgidav.ifLockTokenList']. X Handle these headers: X X - If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since: X Raising HTTP_PRECONDITION_FAILED or HTTP_NOT_MODIFIED X - If: X Raising HTTP_PRECONDITION_FAILED X X @see http://www.webdav.org/specs/rfc4918.html#HEADER_If X @see util.evaluateHTTPConditionals X """ X # Add parsed If header to environ X if "wsgidav.conditions.if" not in environ: X util.parseIfHeaderDict(environ) X X # Bail out, if res does not exist X if res is None: X return X X ifDict = environ["wsgidav.conditions.if"] X X # Raise HTTP_PRECONDITION_FAILED or HTTP_NOT_MODIFIED, if standard X # HTTP condition fails X lastmodified = -1 # nonvalid modified time X entitytag = "[]" # Non-valid entity tag X if res.getLastModified() is not None: X lastmodified = res.getLastModified() X if res.getEtag() is not None: X entitytag = res.getEtag() X X if ("HTTP_IF_MODIFIED_SINCE" in environ X or "HTTP_IF_UNMODIFIED_SINCE" in environ X or "HTTP_IF_MATCH" in environ X or "HTTP_IF_NONE_MATCH" in environ): X util.evaluateHTTPConditionals(res, lastmodified, entitytag, environ) X X if not "HTTP_IF" in environ: X return X X # Raise HTTP_PRECONDITION_FAILED, if DAV 'If' condition fails X # TODO: handle empty locked resources X # TODO: handle unmapped locked resources X# isnewfile = not provider.exists(mappedpath) X X refUrl = res.getRefUrl() X lockMan = self._davProvider.lockManager X locktokenlist = [] X if lockMan: X lockList = lockMan.getIndirectUrlLockList(refUrl, environ["wsgidav.username"]) X for lock in lockList: X locktokenlist.append(lock["token"]) X X if not util.testIfHeaderDict(res, ifDict, refUrl, locktokenlist, entitytag): X self._fail(HTTP_PRECONDITION_FAILED, "'If' header condition failed.") X X return X X X X X def doPROPFIND(self, environ, start_response): X """ X TODO: does not yet support If and If HTTP Conditions X @see http://www.webdav.org/specs/rfc4918.html#METHOD_PROPFIND X """ X path = environ["PATH_INFO"] X res = self._davProvider.getResourceInst(path, environ) X X # RFC: By default, the PROPFIND method without a Depth header MUST act X # as if a "Depth: infinity" header was included. X environ.setdefault("HTTP_DEPTH", "infinity") X if not environ["HTTP_DEPTH"] in ("0", "1", "infinity"): X self._fail(HTTP_BAD_REQUEST, X "Invalid Depth header: '%s'." % environ["HTTP_DEPTH"]) X X if environ["HTTP_DEPTH"] == "infinity" and not self.allowPropfindInfinite: X self._fail(HTTP_FORBIDDEN, X "PROPFIND 'infinite' was disabled for security reasons.", X errcondition=PRECONDITION_CODE_PropfindFiniteDepth) X X if res is None: X self._fail(HTTP_NOT_FOUND) X X if environ.get("wsgidav.debug_break"): X pass # break point X X self._evaluateIfHeaders(res, environ) X X # Parse PROPFIND request X requestEL = util.parseXmlBody(environ, allowEmpty=True) X if requestEL is None: X # An empty PROPFIND request body MUST be treated as a request for X # the names and values of all properties. X requestEL = etree.XML("") X X if requestEL.tag != "{DAV:}propfind": X self._fail(HTTP_BAD_REQUEST) X X propNameList = [] X propFindMode = None X for pfnode in requestEL: X if pfnode.tag == "{DAV:}allprop": X if propFindMode: X # RFC: allprop and propname are mutually exclusive X self._fail(HTTP_BAD_REQUEST) X propFindMode = "allprop" X # TODO: implement option X# elif pfnode.tag == "{DAV:}include": X# if not propFindMode in (None, "allprop"): X# self._fail(HTTP_BAD_REQUEST, " element is only valid with 'allprop'.") X# for pfpnode in pfnode: X# propNameList.append(pfpnode.tag) X elif pfnode.tag == "{DAV:}propname": X if propFindMode: # RFC: allprop and propname are mutually exclusive X self._fail(HTTP_BAD_REQUEST) X propFindMode = "propname" X elif pfnode.tag == "{DAV:}prop": X if propFindMode not in (None, "named"): # RFC: allprop and propname are mutually exclusive X self._fail(HTTP_BAD_REQUEST) X propFindMode = "named" X for pfpnode in pfnode: X propNameList.append(pfpnode.tag) X X # --- Build list of resource URIs X X reslist = res.getDescendants(depth=environ["HTTP_DEPTH"], addSelf=True) X# if environ["wsgidav.verbose"] >= 3: X# pprint(reslist, indent=4) X X multistatusEL = xml_tools.makeMultistatusEL() X responsedescription = [] X X for child in reslist: X X if propFindMode == "allprop": X propList = child.getProperties("allprop") X elif propFindMode == "propname": X propList = child.getProperties("propname") X else: X propList = child.getProperties("named", nameList=propNameList) X X href = child.getHref() X util.addPropertyResponse(multistatusEL, href, propList) X X if responsedescription: X etree.SubElement(multistatusEL, "{DAV:}responsedescription").text = "\n".join(responsedescription) X X return util.sendMultiStatusResponse(environ, start_response, multistatusEL) X X X X X def doPROPPATCH(self, environ, start_response): X """Handle PROPPATCH request to set or remove a property. X X @see http://www.webdav.org/specs/rfc4918.html#METHOD_PROPPATCH X """ X path = environ["PATH_INFO"] X res = self._davProvider.getResourceInst(path, environ) X X # Only accept Depth: 0 (but assume this, if omitted) X environ.setdefault("HTTP_DEPTH", "0") X if environ["HTTP_DEPTH"] != "0": X self._fail(HTTP_BAD_REQUEST, "Depth must be '0'.") X X if res is None: X self._fail(HTTP_NOT_FOUND) X X self._evaluateIfHeaders(res, environ) X self._checkWritePermission(res, "0", environ) X X # Parse request X requestEL = util.parseXmlBody(environ) X X if requestEL.tag != "{DAV:}propertyupdate": X self._fail(HTTP_BAD_REQUEST) X X # Create a list of update request tuples: (propname, value) X propupdatelist = [] X X for ppnode in requestEL: X propupdatemethod = None X if ppnode.tag == "{DAV:}remove": X propupdatemethod = "remove" X elif ppnode.tag == "{DAV:}set": X propupdatemethod = "set" X else: X self._fail(HTTP_BAD_REQUEST, "Unknown tag (expected 'set' or 'remove').") X X for propnode in ppnode: X if propnode.tag != "{DAV:}prop": X self._fail(HTTP_BAD_REQUEST, "Unknown tag (expected 'prop').") X X for propertynode in propnode: X propvalue = None X if propupdatemethod == "remove": X propvalue = None # Mark as 'remove' X if len(propertynode) > 0: X # 14.23: All the XML elements in a 'prop' XML element inside of a 'remove' XML element MUST be empty X self._fail(HTTP_BAD_REQUEST, "prop element must be empty for 'remove'.") X else: X propvalue = propertynode X X propupdatelist.append( (propertynode.tag, propvalue) ) X X # Apply updates in SIMULATION MODE and create a result list (propname, result) X successflag = True X writeresultlist = [] X X for (propname, propvalue) in propupdatelist: X try: X res.setPropertyValue(propname, propvalue, dryRun=True) X except Exception, e: X writeresult = asDAVError(e) X else: X writeresult = "200 OK" X writeresultlist.append( (propname, writeresult) ) X successflag = successflag and writeresult == "200 OK" X X # Generate response list of 2-tuples (name, value) X # is None on success, or an instance of DAVError X propResponseList = [] X responsedescription = [] X X if not successflag: X # If dry run failed: convert all OK to FAILED_DEPENDENCY. X for (propname, result) in writeresultlist: X if result == "200 OK": X result = DAVError(HTTP_FAILED_DEPENDENCY) X elif isinstance(result, DAVError): X responsedescription.append(result.getUserInfo()) X propResponseList.append( (propname, result) ) X X else: X # Dry-run succeeded: set properties again, this time in 'real' mode X # In theory, there should be no exceptions thrown here, but this is real live... X for (propname, propvalue) in propupdatelist: X try: X res.setPropertyValue(propname, propvalue, dryRun=False) X # Set value to None, so the response xml contains empty tags X propResponseList.append( (propname, None) ) X except Exception, e: X e = asDAVError(e) X propResponseList.append( (propname, e) ) X responsedescription.append(e.getUserInfo()) X X # Generate response XML X multistatusEL = xml_tools.makeMultistatusEL() X# href = util.makeCompleteUrl(environ, path) X href = res.getHref() X util.addPropertyResponse(multistatusEL, href, propResponseList) X if responsedescription: X etree.SubElement(multistatusEL, "{DAV:}responsedescription").text = "\n".join(responsedescription) X X # Send response X return util.sendMultiStatusResponse(environ, start_response, multistatusEL) X X X X X def doMKCOL(self, environ, start_response): X """Handle MKCOL request to create a new collection. X X @see http://www.webdav.org/specs/rfc4918.html#METHOD_MKCOL X """ X path = environ["PATH_INFO"] X provider = self._davProvider X# res = provider.getResourceInst(path, environ) X X # Do not understand ANY request body entities X if util.getContentLength(environ) != 0: X self._fail(HTTP_MEDIATYPE_NOT_SUPPORTED, X "The server does not handle any body content.") X X # Only accept Depth: 0 (but assume this, if omitted) X if environ.setdefault("HTTP_DEPTH", "0") != "0": X self._fail(HTTP_BAD_REQUEST, "Depth must be '0'.") X X if provider.exists(path, environ): X self._fail(HTTP_METHOD_NOT_ALLOWED, X "MKCOL can only be executed on an unmapped URL.") X X parentRes = provider.getResourceInst(util.getUriParent(path), environ) X if not parentRes or not parentRes.isCollection: X self._fail(HTTP_CONFLICT, "Parent must be an existing collection.") X X # TODO: should we check If headers here? X# self._evaluateIfHeaders(res, environ) X # Check for write permissions on the PARENT X self._checkWritePermission(parentRes, "0", environ) X X parentRes.createCollection(util.getUriName(path)) X X return util.sendStatusResponse(environ, start_response, HTTP_CREATED) X X X X X def doPOST(self, environ, start_response): X """ X @see http://www.webdav.org/specs/rfc4918.html#METHOD_POST X """ X self._fail(HTTP_METHOD_NOT_ALLOWED) X X X X X def doDELETE(self, environ, start_response): X """ X @see: http://www.webdav.org/specs/rfc4918.html#METHOD_DELETE X """ X path = environ["PATH_INFO"] X provider = self._davProvider X res = provider.getResourceInst(path, environ) X X # --- Check request preconditions -------------------------------------- X X if util.getContentLength(environ) != 0: X self._fail(HTTP_MEDIATYPE_NOT_SUPPORTED, X "The server does not handle any body content.") X if res is None: X self._fail(HTTP_NOT_FOUND) X X if res.isCollection: X # Delete over collection X # "The DELETE method on a collection MUST act as if a X # 'Depth: infinity' header was used on it. A client MUST NOT submit X # a Depth header with a DELETE on a collection with any value but X # infinity." X if environ.setdefault("HTTP_DEPTH", "infinity") != "infinity": X self._fail(HTTP_BAD_REQUEST, X "Only Depth: infinity is supported for collections.") X else: X if not environ.setdefault("HTTP_DEPTH", "0") in ("0", "infinity"): X self._fail(HTTP_BAD_REQUEST, X "Only Depth: 0 or infinity are supported for non-collections.") X X self._evaluateIfHeaders(res, environ) X # We need write access on the parent collection. Also we check for X # locked children X parentRes = provider.getResourceInst(util.getUriParent(path), environ) X if parentRes: X# self._checkWritePermission(parentRes, environ["HTTP_DEPTH"], environ) X self._checkWritePermission(parentRes, "0", environ) X else: X# self._checkWritePermission(res, environ["HTTP_DEPTH"], environ) X self._checkWritePermission(res, "0", environ) X X # --- Let provider handle the request natively ------------------------- X X # Errors in deletion; [ (, ), ... ] X errorList = [] X X try: X handled = res.handleDelete() X assert handled in (True, False) or type(handled) is list X if type(handled) is list: X errorList = handled X handled = True X except Exception, e: X errorList = [ (res.getHref(), asDAVError(e)) ] X handled = True X if handled: X return self._sendResponse(environ, start_response, X res, HTTP_NO_CONTENT, errorList) X X X # --- Let provider implement own recursion ----------------------------- X X # Get a list of all resources (parents after children, so we can remove X # them in that order) X reverseChildList = res.getDescendants(depthFirst=True, X depth=environ["HTTP_DEPTH"], X addSelf=True) X X if res.isCollection and res.supportRecursiveDelete(): X hasConflicts = False X for childRes in reverseChildList: X try: X self._evaluateIfHeaders(childRes, environ) X self._checkWritePermission(childRes, "0", environ) X except: X hasConflicts = True X break X X if not hasConflicts: X try: X errorList = res.delete() X except Exception, e: X errorList = [ (res.getHref(), asDAVError(e)) ] X return self._sendResponse(environ, start_response, X res, HTTP_NO_CONTENT, errorList) X X # --- Implement file-by-file processing -------------------------------- X X # Hidden paths (ancestors of failed deletes) {: True, ...} X ignoreDict = {} X for childRes in reverseChildList: X if childRes.path in ignoreDict: X _logger.debug("Skipping %s (contains error child)" % childRes.path) X ignoreDict[util.getUriParent(childRes.path)] = "" X continue X X try: X # 9.6.1.: Any headers included with delete must be applied in X # processing every resource to be deleted X self._evaluateIfHeaders(childRes, environ) X self._checkWritePermission(childRes, "0", environ) X childRes.delete() X # Double-check, if deletion succeeded X if provider.exists(childRes.path, environ): X raise DAVError(HTTP_INTERNAL_ERROR, X "Resource could not be deleted.") X except Exception, e: X errorList.append( (childRes.getHref(), asDAVError(e)) ) X ignoreDict[util.getUriParent(childRes.path)] = True X X # --- Send response ---------------------------------------------------- X X return self._sendResponse(environ, start_response, X res, HTTP_NO_CONTENT, errorList) X X X X X def doPUT(self, environ, start_response): X """ X @see: http://www.webdav.org/specs/rfc4918.html#METHOD_PUT X """ X path = environ["PATH_INFO"] X provider = self._davProvider X res = provider.getResourceInst(path, environ) X parentRes = provider.getResourceInst(util.getUriParent(path), environ) X X isnewfile = res is None X X ## Test for unsupported stuff X if "HTTP_CONTENT_ENCODING" in environ: X self._fail(HTTP_NOT_IMPLEMENTED, X "Content-encoding header is not supported.") X if "HTTP_CONTENT_RANGE" in environ: X self._fail(HTTP_NOT_IMPLEMENTED, X "Content-range header is not supported.") X X if res and res.isCollection: X self._fail(HTTP_METHOD_NOT_ALLOWED, "Cannot PUT to a collection") X elif not parentRes.isCollection: # TODO: allow parentRes==None? X self._fail(HTTP_CONFLICT, "PUT parent must be a collection") X X self._evaluateIfHeaders(res, environ) X X if isnewfile: X self._checkWritePermission(parentRes, "0", environ) X res = parentRes.createEmptyResource(util.getUriName(path)) X else: X self._checkWritePermission(res, "0", environ) X X ## Start Content Processing X # Content-Length may be 0 or greater. (Set to -1 if missing or invalid.) X# WORKAROUND_BAD_LENGTH = True X try: X contentlength = max(-1, long(environ.get("CONTENT_LENGTH", -1))) X except ValueError: X contentlength = -1 X X# if contentlength < 0 and not WORKAROUND_BAD_LENGTH: X if ( X (contentlength < 0) and X (environ.get("HTTP_TRANSFER_ENCODING", "").lower() != "chunked") X ): X # HOTFIX: not fully understood, but MS sends PUT without content-length, X # when creating new files X # if "Microsoft-WebDAV-MiniRedir" in environ.get("HTTP_USER_AGENT", ""): X # _logger.warning("Setting misssing Content-Length to 0 for MS client") X # contentlength = 0 X # else: X # self._fail(HTTP_LENGTH_REQUIRED, X # "PUT request with invalid Content-Length: (%s)" % environ.get("CONTENT_LENGTH")) X contentlength = 0 X X hasErrors = False X try: X fileobj = res.beginWrite(contentType=environ.get("CONTENT_TYPE")) X X if environ.get("HTTP_TRANSFER_ENCODING", "").lower() == "chunked": X buf = environ["wsgi.input"].readline() X if buf == '': X l = 0 X else: X l = int(buf, 16) X X environ["wsgidav.some_input_read"] = 1 X while l > 0: X buf = environ["wsgi.input"].read(l) X fileobj.write(buf) X environ["wsgi.input"].readline() X buf = environ["wsgi.input"].readline() X if buf == '': X l = 0 X else: X l = int(buf, 16) X environ["wsgidav.all_input_read"] = 1 X X elif contentlength == 0: X # TODO: review this X # XP and Vista MiniRedir submit PUT with Content-Length 0, X # before LOCK and the real PUT. So we have to accept this. X _logger.info("PUT: Content-Length == 0. Creating empty file...") X X# elif contentlength < 0: X# # TODO: review this X# # If CONTENT_LENGTH is invalid, we may try to workaround this X# # by reading until the end of the stream. This may block however! X# # The iterator produced small chunks of varying size, but not X# # sure, if we always get everything before it times out. X# _logger.warning("PUT with invalid Content-Length (%s). Trying to read all (this may timeout)..." % environ.get("CONTENT_LENGTH")) X# nb = 0 X# try: X# for s in environ["wsgi.input"]: X# environ["wsgidav.some_input_read"] = 1 X# _logger.debug("PUT: read from wsgi.input.__iter__, len=%s" % len(s)) X# fileobj.write(s) X# nb += len (s) X# except socket.timeout: X# _logger.warning("PUT: input timed out after writing %s bytes" % nb) X# hasErrors = True X else: X assert contentlength > 0 X contentremain = contentlength X while contentremain > 0: X n = min(contentremain, BLOCK_SIZE) X readbuffer = environ["wsgi.input"].read(n) X # This happens with litmus expect-100 test: X# assert len(readbuffer) > 0, "input.read(%s) returned %s bytes" % (n, len(readbuffer)) X if not len(readbuffer) > 0: X util.warn("input.read(%s) returned 0 bytes" % n) X break X environ["wsgidav.some_input_read"] = 1 X fileobj.write(readbuffer) X contentremain -= len(readbuffer) X X if contentremain == 0: X environ["wsgidav.all_input_read"] = 1 X X fileobj.close() X X except Exception, e: X res.endWrite(withErrors=True) X _logger.exception("PUT: byte copy failed") X self._fail(e) X X res.endWrite(hasErrors) X X if isnewfile: X return util.sendStatusResponse(environ, start_response, HTTP_CREATED) X return util.sendStatusResponse(environ, start_response, HTTP_NO_CONTENT) X X X X X def doCOPY(self, environ, start_response): X return self._copyOrMove(environ, start_response, False) X X X X X def doMOVE(self, environ, start_response): X return self._copyOrMove(environ, start_response, True) X X X X X def _copyOrMove(self, environ, start_response, isMove): X """ X @see: http://www.webdav.org/specs/rfc4918.html#METHOD_COPY X @see: http://www.webdav.org/specs/rfc4918.html#METHOD_MOVE X """ X srcPath = environ["PATH_INFO"] X provider = self._davProvider X srcRes = provider.getResourceInst(srcPath, environ) X srcParentRes = provider.getResourceInst(util.getUriParent(srcPath), environ) X X # --- Check source ----------------------------------------------------- X X if srcRes is None: X self._fail(HTTP_NOT_FOUND) X if "HTTP_DESTINATION" not in environ: X self._fail(HTTP_BAD_REQUEST, "Missing required Destination header.") X if not environ.setdefault("HTTP_OVERWRITE", "T") in ("T", "F"): X # Overwrite defaults to 'T' X self._fail(HTTP_BAD_REQUEST, "Invalid Overwrite header.") X if util.getContentLength(environ) != 0: X # RFC 2518 defined support for . X # This was dropped with RFC 4918. X # Still clients may send it (e.g. DAVExplorer 0.9.1 File-Copy) sends X # * X body = environ["wsgi.input"].read(util.getContentLength(environ)) X environ["wsgidav.all_input_read"] = 1 X _logger.info("Ignored copy/move body: '%s'..." % body[:50]) X X if srcRes.isCollection: X # The COPY method on a collection without a Depth header MUST act as X # if a Depth header with value "infinity" was included. X # A client may submit a Depth header on a COPY on a collection with X # a value of "0" or "infinity". X environ.setdefault("HTTP_DEPTH", "infinity") X if not environ["HTTP_DEPTH"] in ("0", "infinity"): X self._fail(HTTP_BAD_REQUEST, "Invalid Depth header.") X if isMove and environ["HTTP_DEPTH"] != "infinity": X self._fail(HTTP_BAD_REQUEST, "Depth header for MOVE collection must be 'infinity'.") X else: X # It's an existing non-collection: assume Depth 0 X # Note: litmus 'copymove: 3 (copy_simple)' sends 'infinity' for a X # non-collection resource, so we accept that too X environ.setdefault("HTTP_DEPTH", "0") X if not environ["HTTP_DEPTH"] in ("0", "infinity"): X self._fail(HTTP_BAD_REQUEST, "Invalid Depth header.") X environ["HTTP_DEPTH"] = "0" X X # --- Get destination path and check for cross-realm access ------------ X X # Destination header may be quoted (e.g. DAV Explorer sends unquoted, X # Windows quoted) X destinationHeader = urllib.unquote(environ["HTTP_DESTINATION"]) X X # Return fragments as part of X # Fixes litmus -> running `basic': 9. delete_fragment....... WARNING: DELETE removed collection resource withRequest-URI including fragment; unsafe X destScheme, destNetloc, destPath, \ X _destParams, _destQuery, _destFrag = urlparse(destinationHeader, X allow_fragments=False) X X if srcRes.isCollection: X destPath = destPath.rstrip("/") + "/" X X # Remove username X atLoc = destNetloc.rfind("@") X if atLoc != -1: X destNetloc = destNetloc[atLoc+1:] X X # if destScheme and destScheme.lower() != environ["wsgi.url_scheme"].lower(): X # self._fail(HTTP_BAD_GATEWAY, X # "Source and destination must have the same scheme.") X # elif destNetloc and destNetloc.lower() != environ["HTTP_HOST"].lower(): X # # TODO: this should consider environ["SERVER_PORT"] also X # self._fail(HTTP_BAD_GATEWAY, X # "Source and destination must have the same host name.") X # elif not destPath.startswith(provider.mountPath + provider.sharePath): X # # Inter-realm copying not supported, since its not possible to X # # authentication-wise X # self._fail(HTTP_BAD_GATEWAY, X # "Inter-realm copy/move is not supported.") X X destPath = destPath[len(provider.mountPath + provider.sharePath):] X assert destPath.startswith("/") X X # destPath is now relative to current mount/share starting with '/' X X destRes = provider.getResourceInst(destPath, environ) X destExists = destRes is not None X X destParentRes = provider.getResourceInst(util.getUriParent(destPath), environ) X X if not destParentRes or not destParentRes.isCollection: X self._fail(HTTP_CONFLICT, "Destination parent must be a collection.") X X self._evaluateIfHeaders(srcRes, environ) X self._evaluateIfHeaders(destRes, environ) X # Check permissions X # http://www.webdav.org/specs/rfc4918.html#rfc.section.7.4 X if isMove: X self._checkWritePermission(srcRes, "infinity", environ) X # Cannot remove members from locked-0 collections X if srcParentRes: X self._checkWritePermission(srcParentRes, "0", environ) X X # Cannot create or new members in locked-0 collections X if not destExists: X self._checkWritePermission(destParentRes, "0", environ) X # If target exists, it must not be locked X self._checkWritePermission(destRes, "infinity", environ) X X if srcPath == destPath: X self._fail(HTTP_FORBIDDEN, "Cannot copy/move source onto itself") X elif util.isEqualOrChildUri(srcPath, destPath): X self._fail(HTTP_FORBIDDEN, "Cannot copy/move source below itself") X X if destExists and environ["HTTP_OVERWRITE"] != "T": X self._fail(HTTP_PRECONDITION_FAILED, X "Destination already exists and Overwrite is set to false") X X # --- Let provider handle the request natively ------------------------- X X # Errors in copy/move; [ (, ), ... ] X errorList = [] X successCode = HTTP_CREATED X if destExists: X successCode = HTTP_NO_CONTENT X X try: X if isMove: X handled = srcRes.handleMove(destPath) X else: X isInfinity = environ["HTTP_DEPTH"] == "infinity" X handled = srcRes.handleCopy(destPath, isInfinity) X assert handled in (True, False) or type(handled) is list X if type(handled) is list: X errorList = handled X handled = True X except Exception, e: X errorList = [ (srcRes.getHref(), asDAVError(e)) ] X handled = True X if handled: X return self._sendResponse(environ, start_response, X srcRes, HTTP_NO_CONTENT, errorList) X X # --- Cleanup destination before copy/move ----------------------------- X X srcList = srcRes.getDescendants(addSelf=True) X X srcRootLen = len(srcPath) X destRootLen = len(destPath) X X if destExists: X if (isMove X or not destRes.isCollection X or not srcRes.isCollection ): X # MOVE: X # If a resource exists at the destination and the Overwrite X # header is "T", then prior to performing the move, the server X # MUST perform a DELETE with "Depth: infinity" on the X # destination resource. X _logger.debug("Remove dest before move: '%s'" % destRes) X destRes.delete() X destRes = None X else: X # COPY collection over collection: X # Remove destination files, that are not part of source, because X # source and dest collections must not be merged (9.8.4). X # This is not the same as deleting the complete dest collection X # before copying, because that would also discard the history of X # existing resources. X reverseDestList = destRes.getDescendants(depthFirst=True, addSelf=False) X srcPathList = [ s.path for s in srcList ] X _logger.debug("check srcPathList: %s" % srcPathList) X for dRes in reverseDestList: X _logger.debug("check unmatched dest before copy: %s" % dRes) X relUrl = dRes.path[destRootLen:] X sp = srcPath + relUrl X if not sp in srcPathList: X _logger.debug("Remove unmatched dest before copy: %s" % dRes) X dRes.delete() X X # --- Let provider implement recursive move ---------------------------- X # We do this only, if the provider supports it, and no conflicts exist. X # A provider can implement this very efficiently, without allocating X # double memory as a copy/delete approach would. X X if isMove and srcRes.supportRecursiveMove(destPath): X hasConflicts = False X for s in srcList: X try: X self._evaluateIfHeaders(s, environ) X except: X hasConflicts = True X break X X if not hasConflicts: X try: X _logger.debug("Recursive move: %s -> '%s'" % (srcRes, destPath)) X errorList = srcRes.moveRecursive(destPath) X except Exception, e: X errorList = [ (srcRes.getHref(), asDAVError(e)) ] X return self._sendResponse(environ, start_response, X srcRes, successCode, errorList) X X # --- Copy/move file-by-file using copy/delete ------------------------- X X # We get here, if X # - the provider does not support recursive moves X # - this is a copy request X # In this case we would probably not win too much by a native provider X # implementation, since we had to handle single child errors anyway. X # - the source tree is partially locked X # We would have to pass this information to the native provider. X X # Hidden paths (paths of failed copy/moves) {: True, ...} X ignoreDict = {} X X for sRes in srcList: X # Skip this resource, if there was a failure copying a parent X parentError = False X for ignorePath in ignoreDict.keys(): X if util.isEqualOrChildUri(ignorePath, sRes.path): X parentError = True X break X if parentError: X _logger.debug("Copy: skipping '%s', because of parent error" % sRes.path) X continue X X try: X relUrl = sRes.path[srcRootLen:] X dPath = destPath + relUrl X X self._evaluateIfHeaders(sRes, environ) X X # We copy resources and their properties top-down. X # Collections are simply created (without members), for X # non-collections bytes are copied (overwriting target) X sRes.copyMoveSingle(dPath, isMove) X X # If copy succeeded, and it was a non-collection delete it now. X # So the source tree shrinks while the destination grows and we X # don't have to allocate the memory twice. X # We cannot remove collections here, because we have not yet X # copied all children. X if isMove and not sRes.isCollection: X sRes.delete() X X except Exception, e: X ignoreDict[sRes.path] = True X # TODO: the error-href should be 'most appropriate of the source X # and destination URLs'. So maybe this should be the destination X # href sometimes. X # http://www.webdav.org/specs/rfc4918.html#rfc.section.9.8.5 X errorList.append( (sRes.getHref(), asDAVError(e)) ) X X # MOVE: Remove source tree (bottom-up) X if isMove: X reverseSrcList = srcList[:] X reverseSrcList.reverse() X util.status("Delete after move, ignore=", var=ignoreDict) X for sRes in reverseSrcList: X # Non-collections have already been removed in the copy loop. X if not sRes.isCollection: X continue X # Skip collections that contain errors (unmoved resources) X childError = False X for ignorePath in ignoreDict.keys(): X if util.isEqualOrChildUri(sRes.path, ignorePath): X childError = True X break X if childError: X util.status("Delete after move: skipping '%s', because of child error" % sRes.path) X continue X X try: X# _logger.debug("Remove source after move: %s" % sRes) X util.status("Remove collection after move: %s" % sRes) X sRes.delete() X except Exception, e: X errorList.append( (srcRes.getHref(), asDAVError(e)) ) X util.status("ErrorList", var=errorList) X X # --- Return response -------------------------------------------------- X X return self._sendResponse(environ, start_response, X srcRes, successCode, errorList) X X X X X def doLOCK(self, environ, start_response): X """ X @see: http://www.webdav.org/specs/rfc4918.html#METHOD_LOCK X """ X path = environ["PATH_INFO"] X provider = self._davProvider X res = provider.getResourceInst(path, environ) X lockMan = provider.lockManager X X if lockMan is None: X # http://www.webdav.org/specs/rfc4918.html#rfc.section.6.3 X self._fail(HTTP_NOT_IMPLEMENTED, X "This realm does not support locking.") X if res and res.preventLocking(): X self._fail(HTTP_FORBIDDEN, X "This resource does not support locking.") X X if environ.setdefault("HTTP_DEPTH", "infinity") not in ("0", "infinity"): X self._fail(HTTP_BAD_REQUEST, "Expected Depth: 'infinity' or '0'.") X X self._evaluateIfHeaders(res, environ) X X timeoutsecs = util.readTimeoutValueHeader(environ.get("HTTP_TIMEOUT", "")) X submittedTokenList = environ["wsgidav.ifLockTokenList"] X X lockinfoEL = util.parseXmlBody(environ, allowEmpty=True) X X # --- Special case: empty request body --------------------------------- X X if lockinfoEL is None: X # TODO: @see 9.10.2 X # TODO: 'URL of a resource within the scope of the lock' X # Other (shared) locks are unaffected and don't prevent refreshing X # TODO: check for valid user X # TODO: check for If with single lock token X environ["HTTP_DEPTH"] = "0" # MUST ignore depth header on refresh X X if res is None: X self._fail(HTTP_BAD_REQUEST, X "LOCK refresh must specify an existing resource.") X if len(submittedTokenList) != 1: X self._fail(HTTP_BAD_REQUEST, X "Expected a lock token (only one lock may be refreshed at a time).") X elif not lockMan.isUrlLockedByToken(res.getRefUrl(), submittedTokenList[0]): X self._fail(HTTP_PRECONDITION_FAILED, X "Lock token does not match URL.", X errcondition=PRECONDITION_CODE_LockTokenMismatch) X # TODO: test, if token is owned by user X X lock = lockMan.refresh(submittedTokenList[0], timeoutsecs) X X # The lock root may be , or a parent of . X lockPath = provider.refUrlToPath(lock["root"]) X lockRes = provider.getResourceInst(lockPath, environ) X X propEL = xml_tools.makePropEL() X # TODO: handle exceptions in getPropertyValue X lockdiscoveryEL = lockRes.getPropertyValue("{DAV:}lockdiscovery") X propEL.append(lockdiscoveryEL) X X # Lock-Token header is not returned X xml = xml_tools.xmlToString(propEL) X start_response("200 OK", [("Content-Type", "application/xml"), X ("Content-Length", str(len(xml))), X ("Date", util.getRfc1123Time()), X ]) X return [ xml ] X X # --- Standard case: parse xml body ------------------------------------ X X if lockinfoEL.tag != "{DAV:}lockinfo": X self._fail(HTTP_BAD_REQUEST) X X locktype = None X lockscope = None X lockowner = "" X lockdepth = environ.setdefault("HTTP_DEPTH", "infinity") X X for linode in lockinfoEL: X if linode.tag == "{DAV:}lockscope": X for lsnode in linode: X if lsnode.tag == "{DAV:}exclusive": X lockscope = "exclusive" X elif lsnode.tag == "{DAV:}shared": X lockscope = "shared" X break X elif linode.tag == "{DAV:}locktype": X for ltnode in linode: X if ltnode.tag == "{DAV:}write": X locktype = "write" # only type accepted X break X X elif linode.tag == "{DAV:}owner": X # Store whole tag, so we can use etree.XML() later X lockowner = xml_tools.xmlToString(linode, pretty_print=False) X X else: X self._fail(HTTP_BAD_REQUEST, "Invalid node '%s'." % linode.tag) X X if not lockscope: X self._fail(HTTP_BAD_REQUEST, "Missing or invalid lockscope.") X if not locktype: X self._fail(HTTP_BAD_REQUEST, "Missing or invalid locktype.") X X if environ.get("wsgidav.debug_break"): X pass # break point X X # TODO: check for locked parents BEFORE creating an empty child X X # http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.4 X # Locking unmapped URLs: must create an empty resource X createdNewResource = False X if res is None: X parentRes = provider.getResourceInst(util.getUriParent(path), environ) X if not parentRes or not parentRes.isCollection: X self._fail(HTTP_CONFLICT, "LOCK-0 parent must be a collection") X res = parentRes.createEmptyResource(util.getUriName(path)) X createdNewResource = True X X # --- Check, if path is already locked --------------------------------- X X # May raise DAVError(HTTP_LOCKED): X lock = lockMan.acquire(res.getRefUrl(), X locktype, lockscope, lockdepth, X lockowner, timeoutsecs, X environ["wsgidav.username"], X submittedTokenList) X X # Lock succeeded X propEL = xml_tools.makePropEL() X # TODO: handle exceptions in getPropertyValue X lockdiscoveryEL = res.getPropertyValue("{DAV:}lockdiscovery") X propEL.append(lockdiscoveryEL) X X respcode = "200 OK" X if createdNewResource: X respcode = "201 Created" X X xml = xml_tools.xmlToString(propEL) X start_response(respcode, [("Content-Type", "application/xml"), X ("Content-Length", str(len(xml))), X ("Lock-Token", lock["token"]), X ("Date", util.getRfc1123Time()), X ]) X return [ xml ] X X # TODO: LOCK may also fail with HTTP_FORBIDDEN. X # In this case we should return 207 multi-status. X # http://www.webdav.org/specs/rfc4918.html#rfc.section.9.10.9 X # Checking this would require to call res.preventLocking() X # recursively. X X# # --- Locking FAILED: return fault response X# if len(conflictList) == 1 and conflictList[0][0]["root"] == res.getRefUrl(): X# # If there is only one error for the root URL, send as simple error response X# return util.sendStatusResponse(environ, start_response, conflictList[0][1]) X# X# dictStatus = {} X# X# for lockDict, e in conflictList: X# dictStatus[lockDict["root"]] = e X# X# if not res.getRefUrl() in dictStatus: X# dictStatus[res.getRefUrl()] = DAVError(HTTP_FAILED_DEPENDENCY) X# X# # Return multi-status fault response X# multistatusEL = xml_tools.makeMultistatusEL() X# for nu, e in dictStatus.items(): X# responseEL = etree.SubElement(multistatusEL, "{DAV:}response") X# etree.SubElement(responseEL, "{DAV:}href").text = nu X# etree.SubElement(responseEL, "{DAV:}status").text = "HTTP/1.1 %s" % getHttpStatusString(e) X# # TODO: all responses should have this(?): X# if e.contextinfo: X# etree.SubElement(multistatusEL, "{DAV:}responsedescription").text = e.contextinfo X# X## if responsedescription: X## etree.SubElement(multistatusEL, "{DAV:}responsedescription").text = "\n".join(responsedescription) X# X# return util.sendMultiStatusResponse(environ, start_response, multistatusEL) X X X X X def doUNLOCK(self, environ, start_response): X """ X @see: http://www.webdav.org/specs/rfc4918.html#METHOD_UNLOCK X """ X path = environ["PATH_INFO"] X provider = self._davProvider X res = self._davProvider.getResourceInst(path, environ) X X lockMan = provider.lockManager X if lockMan is None: X self._fail(HTTP_NOT_IMPLEMENTED, "This share does not support locking.") X elif util.getContentLength(environ) != 0: X self._fail(HTTP_MEDIATYPE_NOT_SUPPORTED, X "The server does not handle any body content.") X elif not "HTTP_LOCK_TOKEN" in environ: X self._fail(HTTP_BAD_REQUEST, "Missing lock token.") X X self._evaluateIfHeaders(res, environ) X X lockToken = environ["HTTP_LOCK_TOKEN"].strip("<>") X refUrl = res.getRefUrl() X X if not lockMan.isUrlLockedByToken(refUrl, lockToken): X self._fail(HTTP_CONFLICT, X "Resource is not locked by token.", X errcondition=PRECONDITION_CODE_LockTokenMismatch) X X if not lockMan.isTokenLockedByUser(lockToken, environ["wsgidav.username"]): X # TODO: there must be a way to allow this for admins. X # Maybe test for "remove_locks" in environ["wsgidav.roles"] X self._fail(HTTP_FORBIDDEN, "Token was created by another user.") X X # TODO: Is this correct?: unlock(a/b/c) will remove Lock for 'a/b' X lockMan.release(lockToken) X X return util.sendStatusResponse(environ, start_response, HTTP_NO_CONTENT) X X X X X def doOPTIONS(self, environ, start_response): X """ X @see http://www.webdav.org/specs/rfc4918.html#HEADER_DAV X """ X path = environ["PATH_INFO"] X provider = self._davProvider X res = provider.getResourceInst(path, environ) X X headers = [("Content-Type", "text/html"), X ("Content-Length", "0"), X ("DAV", "1,2"), # TODO: 10.1: 'OPTIONS MUST return DAV header with compliance class "1"' X # TODO: 10.1: In cases where WebDAV is only supported in part of the server namespace, an OPTIONS request to non-WebDAV resources (including "/") SHOULD NOT advertise WebDAV support X ("Server", "DAV/2"), X ("Date", util.getRfc1123Time()), X ] X X if path == "/": X path = "*" # Hotfix for WinXP X X if path == "*": X # Answer HTTP 'OPTIONS' method on server-level. X # From RFC 2616 X # If the Request-URI is an asterisk ("*"), the OPTIONS request is X # intended to apply to the server in general rather than to a specific X # resource. Since a server's communication options typically depend on X # the resource, the "*" request is only useful as a "ping" or "no-op" X # type of method; it does nothing beyond allowing the client to test the X # capabilities of the server. For example, this can be used to test a X # proxy for HTTP/1.1 compliance (or lack thereof). X start_response("200 OK", headers) X return [""] X X # TODO: should we have something like provider.isReadOnly() and then omit MKCOL PUT DELETE PROPPATCH COPY MOVE? X # TODO: LOCK UNLOCK is only available, if lockmanager not None X if res and res.isCollection: X # Existing collection X headers.append( ("Allow", "OPTIONS HEAD GET DELETE PROPFIND PROPPATCH COPY MOVE LOCK UNLOCK") ) X elif res: X # Existing resource X headers.append( ("Allow", "OPTIONS HEAD GET PUT DELETE PROPFIND PROPPATCH COPY MOVE LOCK UNLOCK") ) X if res.supportRanges(): X headers.append( ("Allow-Ranges", "bytes") ) X elif provider.isCollection(util.getUriParent(path), environ): X # A new resource below an existing collection X # TODO: should we allow LOCK here? I think it is allowed to lock an non-existing resource X headers.append( ("Allow", "OPTIONS PUT MKCOL") ) X else: X self._fail(HTTP_NOT_FOUND) X X start_response("200 OK", headers) X return [""] X X X X X def doGET(self, environ, start_response): X return self._sendResource(environ, start_response, isHeadMethod=False) X X X def doHEAD(self, environ, start_response): X return self._sendResource(environ, start_response, isHeadMethod=True) X X X def _sendResource(self, environ, start_response, isHeadMethod): X """ X If-Range X If the entity is unchanged, send me the part(s) that I am missing; X otherwise, send me the entire new entity X If-Range: "737060cd8c284d8af7ad3082f209582d" X X @see: http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.27 X """ X path = environ["PATH_INFO"] X res = self._davProvider.getResourceInst(path, environ) X X if util.getContentLength(environ) != 0: X self._fail(HTTP_MEDIATYPE_NOT_SUPPORTED, X "The server does not handle any body content.") X elif environ.setdefault("HTTP_DEPTH", "0") != "0": X self._fail(HTTP_BAD_REQUEST, "Only Depth: 0 supported.") X elif res is None: X self._fail(HTTP_NOT_FOUND) X elif res.isCollection: X self._fail(HTTP_FORBIDDEN, X "Directory browsing not enabled (WsgiDavDirBrowser middleware may be enabled using the dir_browser option).") X X self._evaluateIfHeaders(res, environ) X X filesize = res.getContentLength() X if filesize is None: X filesize = -1 # flag logic to read until EOF X X lastmodified = res.getLastModified() X if lastmodified is None: X lastmodified = -1 X X entitytag = res.getEtag() X if entitytag is None: X entitytag = "[]" X X ## Ranges X doignoreranges = (not res.supportContentLength() X or not res.supportRanges() X or filesize == 0) X if "HTTP_RANGE" in environ and "HTTP_IF_RANGE" in environ and not doignoreranges: X ifrange = environ["HTTP_IF_RANGE"] X # Try as http-date first (Return None, if invalid date string) X secstime = util.parseTimeString(ifrange) X if secstime: X if lastmodified != secstime: X doignoreranges = True X else: X # Use as entity tag X ifrange = ifrange.strip("\" ") X if entitytag is None or ifrange != entitytag: X doignoreranges = True X X ispartialranges = False X if "HTTP_RANGE" in environ and not doignoreranges: X ispartialranges = True X listRanges, _totallength = util.obtainContentRanges(environ["HTTP_RANGE"], filesize) X if len(listRanges) == 0: X #No valid ranges present X self._fail(HTTP_RANGE_NOT_SATISFIABLE) X X # More than one range present -> take only the first range, since X # multiple range returns require multipart, which is not supported X # obtainContentRanges supports more than one range in case the above X # behaviour changes in future X (rangestart, rangeend, rangelength) = listRanges[0] X else: X (rangestart, rangeend, rangelength) = (0L, filesize - 1, filesize) X X ## Content Processing X mimetype = res.getContentType() #provider.getContentType(path) X X responseHeaders = [] X if res.supportContentLength(): X # Content-length must be of type string (otherwise CherryPy server chokes) X responseHeaders.append(("Content-Length", str(rangelength))) X if res.supportModified(): X responseHeaders.append(("Last-Modified", util.getRfc1123Time(lastmodified))) X responseHeaders.append(("Content-Type", mimetype)) X responseHeaders.append(("Date", util.getRfc1123Time())) X if res.supportEtag(): X responseHeaders.append(("ETag", '"%s"' % entitytag)) X X if ispartialranges: X# responseHeaders.append(("Content-Ranges", "bytes " + str(rangestart) + "-" + str(rangeend) + "/" + str(rangelength))) X responseHeaders.append(("Content-Range", "bytes %s-%s/%s" % (rangestart, rangeend, filesize))) X start_response("206 Partial Content", responseHeaders) X else: X start_response("200 OK", responseHeaders) X X # Return empty body for HEAD requests X if isHeadMethod: X yield "" X return X X fileobj = res.getContent() X X if not doignoreranges: X fileobj.seek(rangestart) X X contentlengthremaining = rangelength X while 1: X if contentlengthremaining < 0 or contentlengthremaining > BLOCK_SIZE: X readbuffer = fileobj.read(BLOCK_SIZE) X else: X readbuffer = fileobj.read(contentlengthremaining) X yield readbuffer X contentlengthremaining -= len(readbuffer) X if len(readbuffer) == 0 or contentlengthremaining == 0: X break X fileobj.close() X return X X X X X# def doTRACE(self, environ, start_response): X# """ TODO: TRACE pending, but not essential.""" X# self._fail(HTTP_NOT_IMPLEMENTED) a4a6956fc9fbafbe294f0a2256c81ce1 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/lock_manager.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/lock_manager.py << '9a04e603ab1d53c40cd318360f06985f' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplements the `LockManager` object that provides the locking functionality. X XThe LockManager requires a LockStorage object to implement persistence. XTwo alternative lock storage classes are defined in the lock_storage module: X X- wsgidav.lock_storage.LockStorageDict X- wsgidav.lock_storage.LockStorageShelve X X XThe lock data model is a dictionary with these fields: X X root: X Resource URL. X principal: X Name of the authenticated user that created the lock. X type: X Must be 'write'. X scope: X Must be 'shared' or 'exclusive'. X depth: X Must be '0' or 'infinity'. X owner: X String identifying the owner. X timeout: X Seconds remaining until lock expiration. X This value is passed to create() and refresh() X expire: X Converted timeout for persistence: expire = time() + timeout. X token: X Automatically generated unique token. X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom pprint import pprint Xfrom dav_error import DAVError, HTTP_LOCKED, PRECONDITION_CODE_LockConflict Xfrom wsgidav.dav_error import DAVErrorCondition Ximport sys Ximport util Ximport random Ximport time Xfrom rw_lock import ReadWriteLock X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X X X#=============================================================================== X# Tool functions X#=============================================================================== X Xdef generateLockToken(): X return "opaquelocktoken:" + str(hex(random.getrandbits(256))) X X Xdef normalizeLockRoot(path): X # Normalize root: /foo/bar X assert path X if type(path) is unicode: X path = path.encode("utf-8") X path = "/" + path.strip("/") X return path X X Xdef isLockExpired(lock): X expire = float(lock["expire"]) X return expire >= 0 and expire < time.time() X X Xdef lockString(lockDict): X """Return readable rep.""" X if not lockDict: X return "Lock: None" X X if lockDict["expire"] < 0: X expire = "Infinite (%s)" % (lockDict["expire"]) X else: X expire = "%s (in %s seconds)" % (util.getLogTime(lockDict["expire"]), X lockDict["expire"] - time.time()) X X return "Lock(<%s..>, '%s', %s, %s, depth-%s, until %s" % ( X lockDict.get("token","?"*30)[18:22], # first 4 significant token characters X lockDict.get("root"), X lockDict.get("principal"), X lockDict.get("scope"), X lockDict.get("depth"), X expire, X ) X X Xdef validateLock(lock): X assert type(lock["root"]) is str X assert lock["root"].startswith("/") X assert lock["type"] == "write" X assert lock["scope"] in ("shared", "exclusive") X assert lock["depth"] in ("0", "infinity") X assert type(lock["owner"]) is str X # raises TypeError: X timeout = float(lock["timeout"]) X assert timeout > 0 or timeout == -1, "timeout must be positive or -1" X assert type(lock["principal"]) is str X if "token" in lock: X assert type(lock["token"]) is str X X X#=============================================================================== X# LockManager X#=============================================================================== Xclass LockManager(object): X """ X Implements locking functionality using a custom storage layer. X X """ X LOCK_TIME_OUT_DEFAULT = 604800 # 1 week, in seconds X X def __init__(self, storage): X """ X storage: X LockManagerStorage object X """ X assert hasattr(storage, "getLockList") X self._lock = ReadWriteLock() X self.storage = storage X self.storage.open() X X X def __del__(self): X self.storage.close() X X X def __repr__(self): X return "%s(%r)" % (self.__class__.__name__, self.storage) X X X def _dump(self, msg="", out=None): X if out is None: X out = sys.stdout X X urlDict = {} # { : [] } X ownerDict = {} # { : [] } X userDict = {} # { : [] } X tokenDict = {} # { : } X X print >>out, "%s: %s" % (self, msg) X X for lock in self.storage.getLockList("/", includeRoot=True, X includeChildren=True, X tokenOnly=False): X tok = lock["token"] X tokenDict[tok] = lockString(lock) X userDict.setdefault(lock["principal"], []).append(tok) X ownerDict.setdefault(lock["owner"], []).append(tok) X urlDict.setdefault(lock["root"], []).append(tok) X X# assert ("URL2TOKEN:" + v["root"]) in self._dict, "Inconsistency: missing URL2TOKEN:%s" % v["root"] X# assert v["token"] in self._dict["URL2TOKEN:" + v["root"]], "Inconsistency: missing token %s in URL2TOKEN:%s" % (v["token"], v["root"]) X X print >>out, "Locks:" X pprint(tokenDict, indent=0, width=255) X if tokenDict: X print >>out, "Locks by URL:" X pprint(urlDict, indent=4, width=255, stream=out) X print >>out, "Locks by principal:" X pprint(userDict, indent=4, width=255, stream=out) X print >>out, "Locks by owner:" X pprint(ownerDict, indent=4, width=255, stream=out) X X X def _generateLock(self, principal, X locktype, lockscope, lockdepth, lockowner, path, timeout): X """Acquire lock and return lockDict. X X principal X Name of the principal. X locktype X Must be 'write'. X lockscope X Must be 'shared' or 'exclusive'. X lockdepth X Must be '0' or 'infinity'. X lockowner X String identifying the owner. X path X Resource URL. X timeout X Seconds to live X X This function does NOT check, if the new lock creates a conflict! X """ X if timeout is None: X timeout = LockManager.LOCK_TIME_OUT_DEFAULT X elif timeout < 0: X timeout = -1 X X lockDict = {"root": path, X "type": locktype, X "scope": lockscope, X "depth": lockdepth, X "owner": lockowner, X "timeout": timeout, X "principal": principal, X } X # X self.storage.create(path, lockDict) X return lockDict X X X def acquire(self, url, locktype, lockscope, lockdepth, lockowner, timeout, X principal, tokenList): X """Check for permissions and acquire a lock. X X On success return new lock dictionary. X On error raise a DAVError with an embedded DAVErrorCondition. X """ X url = normalizeLockRoot(url) X self._lock.acquireWrite() X try: X # Raises DAVError on conflict: X self._checkLockPermission(url, locktype, lockscope, lockdepth, tokenList, principal) X return self._generateLock(principal, locktype, lockscope, lockdepth, lockowner, url, timeout) X finally: X self._lock.release() X X X def refresh(self, token, timeout=None): X """Set new timeout for lock, if existing and valid.""" X if timeout is None: X timeout = LockManager.LOCK_TIME_OUT_DEFAULT X return self.storage.refresh(token, timeout) X X X def getLock(self, token, key=None): X """Return lockDict, or None, if not found or invalid. X X Side effect: if lock is expired, it will be purged and None is returned. X X key: X name of lock attribute that will be returned instead of a dictionary. X """ X assert key in (None, "type", "scope", "depth", "owner", "root", X "timeout", "principal", "token") X lock = self.storage.get(token) X if key is None or lock is None: X return lock X return lock[key] X X X def release(self, token): X """Delete lock.""" X self.storage.delete(token) X X X def isTokenLockedByUser(self, token, principal): X """Return True, if exists, is valid, and bound to .""" X return self.getLock(token, "principal") == principal X X X# def getUrlLockList(self, url, principal=None): X def getUrlLockList(self, url): X """Return list of lockDict, if is protected by at least one direct, valid lock. X X Side effect: expired locks for this url are purged. X """ X url = normalizeLockRoot(url) X lockList = self.storage.getLockList(url, includeRoot=True, X includeChildren=False, X tokenOnly=False) X return lockList X X X def getIndirectUrlLockList(self, url, principal=None): X """Return a list of valid lockDicts, that protect directly or indirectly. X X If a principal is given, only locks owned by this principal are returned. X Side effect: expired locks for this path and all parents are purged. X """ X url = normalizeLockRoot(url) X lockList = [] X u = url X while u: X ll = self.storage.getLockList(u, includeRoot=True, X includeChildren=False, X tokenOnly=False) X for l in ll: X if u != url and l["depth"] != "infinity": X continue # We only consider parents with Depth: infinity X # TODO: handle shared locks in some way? X# if l["scope"] == "shared" and lockscope == "shared" and principal != l["principal"]: X# continue # Only compatible with shared locks by other users X if principal is None or principal == l["principal"]: X lockList.append(l) X u = util.getUriParent(u) X return lockList X X X def isUrlLocked(self, url): X """Return True, if url is directly locked.""" X lockList = self.getUrlLockList(url) X return len(lockList) > 0 X X X def isUrlLockedByToken(self, url, locktoken): X """Check, if url (or any of it's parents) is locked by locktoken.""" X lockUrl = self.getLock(locktoken, "root") X return lockUrl and util.isEqualOrChildUri(lockUrl, url) X X X def removeAllLocksFromUrl(self, url): X self._lock.acquireWrite() X try: X lockList = self.getUrlLockList(url) X for lock in lockList: X self.release(lock["token"]) X finally: X self._lock.release() X X X def _checkLockPermission(self, url, locktype, lockscope, lockdepth, X tokenList, principal): X """Check, if can lock , otherwise raise an error. X X If locking would create a conflict, DAVError(HTTP_LOCKED) is X raised. An embedded DAVErrorCondition contains the conflicting resource. X X @see http://www.webdav.org/specs/rfc4918.html#lock-model X X - Parent locks WILL NOT be conflicting, if they are depth-0. X - Exclusive depth-infinity parent locks WILL be conflicting, even if X they are owned by . X - Child locks WILL NOT be conflicting, if we request a depth-0 lock. X - Exclusive child locks WILL be conflicting, even if they are owned by X . (7.7) X - It is not enough to check whether a lock is owned by , but X also the token must be passed with the request. (Because X may run two different applications on his client.) X - cannot lock-exclusive, if he holds a parent shared-lock. X (This would only make sense, if he was the only shared-lock holder.) X - TODO: litmus tries to acquire a shared lock on one resource twice X (locks: 27 'double_sharedlock') and fails, when we return HTTP_LOCKED. X So we allow multi shared locks on a resource even for the same X principal. X X @param url: URL that shall be locked X @param locktype: "write" X @param lockscope: "shared"|"exclusive" X @param lockdepth: "0"|"infinity" X @param tokenList: list of lock tokens, that the user submitted in If: header X @param principal: name of the principal requesting a lock X X @return: None (or raise) X """ X assert locktype == "write" X assert lockscope in ("shared", "exclusive") X assert lockdepth in ("0", "infinity") X X _logger.debug("checkLockPermission(%s, %s, %s, %s)" % (url, lockscope, lockdepth, principal)) X X # Error precondition to collect conflicting URLs X errcond = DAVErrorCondition(PRECONDITION_CODE_LockConflict) X X self._lock.acquireRead() X try: X # Check url and all parents for conflicting locks X u = url X while u: X ll = self.getUrlLockList(u) X for l in ll: X _logger.debug(" check parent %s, %s" % (u, lockString(l))) X if u != url and l["depth"] != "infinity": X # We only consider parents with Depth: infinity X continue X elif l["scope"] == "shared" and lockscope == "shared": X # Only compatible with shared locks (even by same principal) X continue X # Lock conflict X _logger.debug(" -> DENIED due to locked parent %s" % lockString(l)) X errcond.add_href(l["root"]) X u = util.getUriParent(u) X X if lockdepth == "infinity": X # Check child URLs for conflicting locks X childLocks = self.storage.getLockList(url, X includeRoot=False, X includeChildren=True, X tokenOnly=False) X X for l in childLocks: X assert util.isChildUri(url, l["root"]) X# if util.isChildUri(url, l["root"]): X _logger.debug(" -> DENIED due to locked child %s" % lockString(l)) X errcond.add_href(l["root"]) X finally: X self._lock.release() X X # If there were conflicts, raise HTTP_LOCKED for , and pass X # conflicting resource with 'no-conflicting-lock' precondition X if len(errcond.hrefs) > 0: X raise DAVError(HTTP_LOCKED, errcondition=errcond) X return X X X def checkWritePermission(self, url, depth, tokenList, principal): X """Check, if can modify , otherwise raise HTTP_LOCKED. X X If modifying is prevented by a lock, DAVError(HTTP_LOCKED) is X raised. An embedded DAVErrorCondition contains the conflicting locks. X X may be modified by , if it is not currently locked X directly or indirectly (i.e. by a locked parent). X For depth-infinity operations, also must not have locked children. X X It is not enough to check whether a lock is owned by , but X also the token must be passed with the request. Because may X run two different applications. X X See http://www.webdav.org/specs/rfc4918.html#lock-model X http://www.webdav.org/specs/rfc4918.html#rfc.section.7.4 X X TODO: verify assumptions: X - Parent locks WILL NOT be conflicting, if they are depth-0. X - Exclusive child locks WILL be conflicting, even if they are owned by . X X @param url: URL that shall be modified, created, moved, or deleted X @param depth: "0"|"infinity" X @param tokenList: list of lock tokens, that the principal submitted in If: header X @param principal: name of the principal requesting a lock X X @return: None or raise error X """ X assert depth in ("0", "infinity") X _logger.debug("checkWritePermission(%s, %s, %s, %s)" % (url, depth, tokenList, principal)) X X # Error precondition to collect conflicting URLs X errcond = DAVErrorCondition(PRECONDITION_CODE_LockConflict) X X self._lock.acquireRead() X try: X # Check url and all parents for conflicting locks X u = url X while u: X ll = self.getUrlLockList(u) X _logger.debug(" checking %s" % u) X for l in ll: X _logger.debug(" l=%s" % lockString(l)) X if u != url and l["depth"] != "infinity": X # We only consider parents with Depth: inifinity X continue X elif principal == l["principal"] and l["token"] in tokenList: X # User owns this lock X continue X else: X # Token is owned by principal, but not passed with lock list X _logger.debug(" -> DENIED due to locked parent %s" % lockString(l)) X errcond.add_href(l["root"]) X u = util.getUriParent(u) X X if depth == "infinity": X # Check child URLs for conflicting locks X childLocks = self.storage.getLockList(url, X includeRoot=False, X includeChildren=True, X tokenOnly=False) X X for l in childLocks: X assert util.isChildUri(url, l["root"]) X# if util.isChildUri(url, l["root"]): X _logger.debug(" -> DENIED due to locked child %s" % lockString(l)) X errcond.add_href(l["root"]) X finally: X self._lock.release() X X # If there were conflicts, raise HTTP_LOCKED for , and pass X # conflicting resource with 'no-conflicting-lock' precondition X if len(errcond.hrefs) > 0: X raise DAVError(HTTP_LOCKED, errcondition=errcond) X return X X X#=============================================================================== X# test X#=============================================================================== Xdef test(): X# l = ShelveLockManager("wsgidav-locks.shelve") X# l._lazyOpen() X# l._dump() X# l.generateLock("martin", "", lockscope, lockdepth, lockowner, lockroot, timeout) X pass X Xif __name__ == "__main__": X test() 9a04e603ab1d53c40cd318360f06985f echo c - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons mkdir -p py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons > /dev/null 2>&1 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/__init__.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/__init__.py << '9e290e1207bc9ed8a584b8643a86d2ca' X#__all__ = ['nt_domain_controller', X# 'simplemysqlabstractionlayer', X# ] 9e290e1207bc9ed8a584b8643a86d2ca echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/mongo_property_manager.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/mongo_property_manager.py << '11b57e9e48816e2a8537de3a3f770ce9' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplements a property manager based on MongoDB. X XUsage: add this lines to wsgidav.conf:: X X from wsgidav.addons.mongo_property_manager import MongoPropertyManager X prop_man_opts = {} X propsmanager = MongoPropertyManager(prop_man_opts) X XValid options are (sample shows defaults):: X X opts = {"host": "localhost", # MongoDB server X "port": 27017, # MongoDB port X "dbName": "wsgidav-props", # Name of DB to store the properties X # This options are used with `mongod --auth` X # The user must be created with db.addUser() X "user": None, # Authenticate with this user X "pwd": None, # ... and password X } X X""" Xfrom wsgidav import util Ximport pymongo Xfrom urllib import quote X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X X# We use these keys internally, so they must be protected XHIDDEN_KEYS = ("_id", "_url", "_title") X X### MongiDB doesn't accept '.' in key names, so we have to escape it. X# Use a key that is unlikely to occur in proprty names XDOT_ESCAPE = "^" X Xdef encodeMongoKey(s): X """Return an encoded version of `s` that may be used as MongoDB key.""" X assert not DOT_ESCAPE in s X return s.replace(".", DOT_ESCAPE) X Xdef decodeMongoKey(key): X """Decode a string that was encoded by encodeMongoKey().""" X return key.replace(DOT_ESCAPE, ".") X X X#=============================================================================== X# MongoPropertyManager X#=============================================================================== Xclass MongoPropertyManager(object): X """Implements a property manager based on MongoDB.""" X def __init__(self, options): X self.options = options X self._connect() X X def __del__(self): X self._disconnect() X X def _connect(self): X opts = self.options X self.conn = pymongo.Connection(opts.get("host"), opts.get("port")) X _logger.debug(self.conn.server_info()) X self.db = self.conn[opts.get("dbName", "wsgidav-props")] X # If credentials are passed, logon to the property storage db X if opts.get("user"): X if not self.db.authenticate(opts.get("user"), opts.get("pwd")): X raise RuntimeError("Failed to logon to db %s as user %s" % X (self.db.name, opts.get("user"))) X util.log("Logged on to mongo db '%s' as user '%s'" % (self.db.name, opts.get("user"))) X X self.collection = self.db["properties"] X util.log("MongoPropertyManager connected %r" % self.collection) X _res = self.collection.ensure_index("_url") X X def _disconnect(self): X if self.conn: X self.conn.disconnect() X self.conn = None X X def __repr__(self): X return "MongoPropertyManager(%s)" % self.db X X def _sync(self): X pass X X def _check(self, msg=""): X pass X X def _dump(self, msg="", out=None): X pass X X def getProperties(self, normurl): X _logger.debug("getProperties(%s)" % normurl) X doc = self.collection.find_one({"_url": normurl}) X propNames = [] X if doc: X for name in doc.keys(): X if not name in HIDDEN_KEYS: X propNames.append(decodeMongoKey(name)) X return propNames X X def getProperty(self, normurl, propname): X _logger.debug("getProperty(%s, %s)" % (normurl, propname)) X doc = self.collection.find_one({"_url": normurl}) X if not doc: X return None X prop = doc.get(encodeMongoKey(propname)) X return prop X X def writeProperty(self, normurl, propname, propertyvalue, dryRun=False): X assert normurl and normurl.startswith("/") X assert propname X assert propertyvalue is not None X assert propname not in HIDDEN_KEYS, "MongoDB key is protected: '%s'" % propname X X _logger.debug("writeProperty(%s, %s, dryRun=%s):\n\t%s" % (normurl, propname, dryRun, propertyvalue)) X if dryRun: X return # TODO: can we check anything here? X X doc = self.collection.find_one({"_url": normurl}) X if not doc: X doc = {"_url": normurl, X "_title": quote(normurl), X } X doc[encodeMongoKey(propname)] = propertyvalue X self.collection.save(doc) X X def removeProperty(self, normurl, propname, dryRun=False): X """ X """ X _logger.debug("removeProperty(%s, %s, dryRun=%s)" % (normurl, propname, dryRun)) X if dryRun: X # TODO: can we check anything here? X return X doc = self.collection.find_one({"_url": normurl}) X # Specifying the removal of a property that does not exist is NOT an error. X if not doc or doc.get(encodeMongoKey(propname)) is None: X return X del doc[encodeMongoKey(propname)] X self.collection.save(doc) X X def removeProperties(self, normurl): X _logger.debug("removeProperties(%s)" % normurl) X doc = self.collection.find_one({"_url": normurl}) X if doc: X self.collection.remove(doc) X return X X def copyProperties(self, srcUrl, destUrl): X doc = self.collection.find_one({"_url": srcUrl}) X if not doc: X _logger.debug("copyProperties(%s, %s): src has no properties" % (srcUrl, destUrl)) X return X _logger.debug("copyProperties(%s, %s)" % (srcUrl, destUrl)) X doc2 = doc.copy() X self.collection.insert(doc2) X X def moveProperties(self, srcUrl, destUrl, withChildren): X _logger.debug("moveProperties(%s, %s, %s)" % (srcUrl, destUrl, withChildren)) X if withChildren: X # Match URLs that are equal to or begin with '/' X matchBegin = "^" + srcUrl.rstrip("/") + "/" X query = {"$or": [{"_url": srcUrl}, X {"_url": {"$regex": matchBegin}}, X ]} X docList = self.collection.find(query) X for doc in docList: X newDest = doc["_url"].replace(srcUrl, destUrl) X _logger.debug("move property %s -> %s" % (doc["_url"], newDest)) X doc["_url"] = newDest X self.collection.save(doc) X else: X # Move srcUrl only X # TODO: use findAndModify()? X doc = self.collection.find_one({"_url": srcUrl}) X if doc: X _logger.debug("move property %s -> %s" % (doc["_url"], destUrl)) X doc["_url"] = destUrl X self.collection.save(doc) X return 11b57e9e48816e2a8537de3a3f770ce9 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/hg_dav_provider.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/hg_dav_provider.py << 'c34264c5858dc99e65e066a1db0dd834' X# -*- coding: iso-8859-1 -*- X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XDAV provider that publishes a Mercurial repository. X XNote: This is **not** production code! X XThe repository is rendered as three top level collections. X Xedit: X Contains the working directory, i.e. all files. This includes uncommitted X changes and untracked new files. X This folder is writable. Xreleased: X Contains the latest committed files, also known as 'tip'. X This folder is read-only. Xarchive: X Contains the last 10 revisions as sub-folders. X This folder is read-only. X XSample layout:: X X // X edit/ X server/ X ext_server.py X README.txt X released/ X archive/ X 19/ X 18/ X ... X XSupported features: X X#. Copying or moving files from ``/edit/..`` to the ``/edit/..`` folder will X result in a ``hg copy`` or ``hg rename``. X#. Deleting resources from ``/edit/..`` will result in a ``hg remove``. X#. Copying or moving files from ``/edit/..`` to the ``/released`` folder will X result in a ``hg commit``. X Note that the destination path is ignored, instead the source path is used. X So a user can drag a file or folder from somewhere under the ``edit/..`` X directory and drop it directly on the ``released`` directory to commit X changes. X#. To commit all changes, simply drag'n'drop the ``/edit`` folder on the X ``/released`` folder. X#. Creating new collections results in creation of a file called ``.directory``, X which is then ``hg add`` ed since Mercurial doesn't track directories. X#. Some attributes are published as live properties, such as ``{hg:}date``. X X XKnown limitations: X X#. This 'commit by drag-and-drop' only works, if the WebDAV clients produces X MOVE or COPY requests. Alas, some clients will send PUT, MKCOL, ... sequences X instead. X#. Adding and then removing a file without committing after the 'add' will X leave this file on disk (untracked) X This happens for example whit lock files that Open Office Write and other X applications will create. X#. Dragging the 'edit' folder onto 'released' with Windows File Explorer will X remove the folder in the explorer view, although WsgiDAV did not delete it. X This seems to be done by the client. X X XSee: X http://mercurial.selenic.com/wiki/MercurialApi XRequirements: X ``easy_install mercurial`` or install the API as non-standalone version X from here: http://mercurial.berkwood.com/ X http://mercurial.berkwood.com/binaries/mercurial-1.4.win32-py2.6.exe X""" Xfrom pprint import pprint Xfrom wsgidav.dav_error import DAVError, HTTP_FORBIDDEN Ximport os Xfrom wsgidav.samples.dav_provider_tools import VirtualCollection Xtry: X from hashlib import md5 Xexcept ImportError: X from md5 import md5 Ximport time Ximport sys X#import mimetypes X Xtry: X from cStringIO import StringIO Xexcept ImportError: X from StringIO import StringIO #@UnusedImport Xfrom wsgidav.dav_provider import DAVProvider, _DAVResource Xfrom wsgidav import util X Xtry: X import mercurial.ui X from mercurial.__version__ import version as hgversion X from mercurial import commands, hg X #from mercurial import util as hgutil Xexcept ImportError: X print >>sys.stderr, "Could not import Mercurial API. Try 'easy_install -U mercurial'." X raise X X__docformat__ = "reStructuredText en" X X_logger = util.getModuleLogger(__name__) X XBUFFER_SIZE = 8192 X X X#=============================================================================== X# HgResource X#=============================================================================== Xclass HgResource(_DAVResource): X """Abstract base class for all resources.""" X def __init__(self, path, isCollection, environ, rev, localHgPath): X super(HgResource, self).__init__(path, isCollection, environ) X self.rev = rev X self.localHgPath = localHgPath X self.absFilePath = self._getFilePath() X assert not "\\" in self.localHgPath X assert not "/" in self.absFilePath X X if isCollection: X self.fctx = None X else: X # Change Context for the requested revision: X # rev=None: current working dir X # rev="tip": TIP X # rev=: Revision ID X wdctx = self.provider.repo[self.rev] X self.fctx = wdctx[self.localHgPath] X# util.status("HgResource: path=%s, rev=%s, localHgPath=%s, fctx=%s" % (self.path, self.rev, self.localHgPath, self.fctx)) X# util.status("HgResource: name=%s, dn=%s, abspath=%s" % (self.name, self.getDisplayName(), self.absFilePath)) X X def _getFilePath(self, *addParts): X parts = self.localHgPath.split("/") X if addParts: X parts.extend(addParts) X return os.path.join(self.provider.repo.root, *parts) X X def _commit(self, message): X user = self.environ.get("http_authenticator.username") or "Anonymous" X commands.commit(self.provider.ui, self.provider.repo, X self.localHgPath, X addremove=True, X user=user, X message=message) X X def _checkWriteAccess(self): X """Raise HTTP_FORBIDDEN, if resource is unwritable.""" X if self.rev is not None: X # Only working directory may be edited X raise DAVError(HTTP_FORBIDDEN) X X def getContentLength(self): X if self.isCollection: X return None X return self.fctx.size() X X def getContentType(self): X if self.isCollection: X return None X# (mimetype, _mimeencoding) = mimetypes.guess_type(self.path) X# if not mimetype: X# return "application/octet-stream" X# return mimetype X return util.guessMimeType(self.path) X X def getCreationDate(self): X# statresults = os.stat(self._filePath) X# return statresults[stat.ST_CTIME] X return None # TODO X X def getDisplayName(self): X if self.isCollection or self.fctx.filerev() is None: X return self.name X return "%s@%s" % (self.name, self.fctx.filerev()) X X def getEtag(self): X return md5(self.path).hexdigest() + "-" + str(self.getLastModified()) + "-" + str(self.getContentLength()) X X def getLastModified(self): X if self.isCollection: X return None X # (secs, tz-ofs) X return self.fctx.date()[0] X X def supportRanges(self): X return False X X def getMemberNames(self): X assert self.isCollection X cache = self.environ["wsgidav.hg.cache"][str(self.rev)] X dirinfos = cache["dirinfos"] X if not dirinfos.has_key(self.localHgPath): X return [] X return dirinfos[self.localHgPath][0] + dirinfos[self.localHgPath][1] X# return self.provider._listMembers(self.path) X X def getMember(self, name): X # Rely on provider to get member oinstances X assert self.isCollection X return self.provider.getResourceInst(util.joinUri(self.path, name), X self.environ) X def getDisplayInfo(self): X if self.isCollection: X return {"type": "Directory"} X return {"type": "File"} X X def getPropertyNames(self, isAllProp): X """Return list of supported property names in Clark Notation. X X See DAVResource.getPropertyNames() X """ X # Let base class implementation add supported live and dead properties X propNameList = super(HgResource, self).getPropertyNames(isAllProp) X # Add custom live properties (report on 'allprop' and 'propnames') X if self.fctx: X propNameList.extend(["{hg:}branch", X "{hg:}date", X "{hg:}description", X "{hg:}filerev", X "{hg:}rev", X "{hg:}user", X ]) X return propNameList X X def getPropertyValue(self, propname): X """Return the value of a property. X X See getPropertyValue() X """ X # Supported custom live properties X if propname == "{hg:}branch": X return self.fctx.branch() X elif propname == "{hg:}date": X # (secs, tz-ofs) X return str(self.fctx.date()[0]) X elif propname == "{hg:}description": X return self.fctx.description() X elif propname == "{hg:}filerev": X return str(self.fctx.filerev()) X elif propname == "{hg:}rev": X return str(self.fctx.rev()) X elif propname == "{hg:}user": X return str(self.fctx.user()) X X # Let base class implementation report live and dead properties X return super(HgResource, self).getPropertyValue(propname) X X def setPropertyValue(self, propname, value, dryRun=False): X """Set or remove property value. X X See DAVResource.setPropertyValue() X """ X raise DAVError(HTTP_FORBIDDEN) X X def preventLocking(self): X """Return True, to prevent locking. X X See preventLocking() X """ X if self.rev is not None: X # Only working directory may be locked X return True X return False X X def createEmptyResource(self, name): X """Create and return an empty (length-0) resource as member of self. X X See DAVResource.createEmptyResource() X """ X assert self.isCollection X self._checkWriteAccess() X filepath = self._getFilePath(name) X f = open(filepath, "w") X f.close() X commands.add(self.provider.ui, self.provider.repo, filepath) X # getResourceInst() won't work, because the cached manifest is outdated X# return self.provider.getResourceInst(self.path.rstrip("/")+"/"+name, self.environ) X return HgResource(self.path.rstrip("/")+"/"+name, False, X self.environ, self.rev, self.localHgPath+"/"+name) X X def createCollection(self, name): X """Create a new collection as member of self. X X A dummy member is created, because Mercurial doesn't handle folders. X """ X assert self.isCollection X self._checkWriteAccess() X collpath = self._getFilePath(name) X os.mkdir(collpath) X filepath = self._getFilePath(name, ".directory") X f = open(filepath, "w") X f.write("Created by WsgiDAV.") X f.close() X commands.add(self.provider.ui, self.provider.repo, filepath) X X def getContent(self): X """Open content as a stream for reading. X X See DAVResource.getContent() X """ X assert not self.isCollection X d = self.fctx.data() X return StringIO(d) X X def beginWrite(self, contentType=None): X """Open content as a stream for writing. X X See DAVResource.beginWrite() X """ X assert not self.isCollection X self._checkWriteAccess() X mode = "wb" X if contentType and contentType.startswith("text"): X mode = "w" X return file(self.absFilePath, mode, BUFFER_SIZE) X X def endWrite(self, withErrors): X """Called when PUT has finished writing. X X See DAVResource.endWrite() X """ X if not withErrors: X commands.add(self.provider.ui, self.provider.repo, self.localHgPath) X X# def handleDelete(self): X# """Handle a DELETE request natively. X# X# """ X# self._checkWriteAccess() X# return False X X X def supportRecursiveDelete(self): X """Return True, if delete() may be called on non-empty collections X (see comments there).""" X return True X X X def delete(self): X """Remove this resource (recursive).""" X self._checkWriteAccess() X filepath = self._getFilePath() X commands.remove(self.provider.ui, self.provider.repo, X filepath, X force=True) X X X def handleCopy(self, destPath, depthInfinity): X """Handle a COPY request natively. X X """ X destType, destHgPath = util.popPath(destPath) X destHgPath = destHgPath.strip("/") X ui = self.provider.ui X repo = self.provider.repo X util.write("handleCopy %s -> %s" % (self.localHgPath, destHgPath)) X if self.rev is None and destType == "edit": X # COPY /edit/a/b to /edit/c/d: turn into 'hg copy -f a/b c/d' X commands.copy(ui, repo, X self.localHgPath, X destHgPath, X force=True) X elif self.rev is None and destType == "released": X # COPY /edit/a/b to /released/c/d X # This is interpreted as 'hg commit a/b' (ignoring the dest. path) X self._commit("WsgiDAV commit (COPY %s -> %s)" % (self.path, destPath)) X else: X raise DAVError(HTTP_FORBIDDEN) X # Return True: request was handled X return True X X X def handleMove(self, destPath): X """Handle a MOVE request natively. X X """ X destType, destHgPath = util.popPath(destPath) X destHgPath = destHgPath.strip("/") X ui = self.provider.ui X repo = self.provider.repo X util.write("handleCopy %s -> %s" % (self.localHgPath, destHgPath)) X if self.rev is None and destType == "edit": X # MOVE /edit/a/b to /edit/c/d: turn into 'hg rename -f a/b c/d' X commands.rename(ui, repo, self.localHgPath, destHgPath, X force=True) X elif self.rev is None and destType == "released": X # MOVE /edit/a/b to /released/c/d X # This is interpreted as 'hg commit a/b' (ignoring the dest. path) X self._commit("WsgiDAV commit (MOVE %s -> %s)" % (self.path, destPath)) X else: X raise DAVError(HTTP_FORBIDDEN) X # Return True: request was handled X return True X X X#=============================================================================== X# HgResourceProvider X#=============================================================================== X Xclass HgResourceProvider(DAVProvider): X """ X DAV provider that serves a VirtualResource derived structure. X """ X def __init__(self, repoRoot): X super(HgResourceProvider, self).__init__() X self.repoRoot = repoRoot X print "Mercurial version %s" % hgversion X self.ui = mercurial.ui.ui() X self.repo = hg.repository(self.ui, repoRoot) X self.ui.status("Connected to repository %s\n" % self.repo.root) X self.repoRoot = self.repo.root X X # Some commands (remove) seem to expect cwd set to the repo X # TODO: try to go along without this, because it prevents serving X # multiple repos. Instead pass absolute paths to the commands. X# print os.getcwd() X os.chdir(self.repo.root) X X # Verify integrity of the repository X util.status("Verify repository '%s' tree..." % self.repo.root) X commands.verify(self.ui, self.repo) X X# self.ui.status("Changelog: %s\n" % self.repo.changelog) X print "Status:" X pprint(self.repo.status()) X self.repo.ui.status("the default username to be used in commits: %s\n" % self.repo.ui.username()) X# self.repo.ui.status("a short form of user name USER %s\n" % self.repo.ui.shortuser(user)) X self.ui.status("Expandpath: %s\n" % self.repo.ui.expandpath(repoRoot)) X X print "Working directory state summary:" X self.ui.pushbuffer() X commands.summary(self.ui, self.repo, remote=False) X res = self.ui.popbuffer().strip() X reslines = [ tuple(line.split(":", 1)) for line in res.split("\n")] X pprint(reslines) X X print "Repository state summary:" X self.ui.pushbuffer() X commands.identify(self.ui, self.repo, X num=True, id=True, branch=True, tags=True) X res = self.ui.popbuffer().strip() X reslines = [ tuple(line.split(":", 1)) for line in res.split("\n")] X pprint(reslines) X X self._getLog() X X X def _getLog(self, limit=None): X """Read log entries into a list of dictionaries.""" X self.ui.pushbuffer() X commands.log(self.ui, self.repo, limit=limit, X date=None, rev=None, user=None) X res = self.ui.popbuffer().strip() X X logList = [] X for logentry in res.split("\n\n"): X log = {} X logList.append(log) X for line in logentry.split("\n"): X k, v = line.split(":", 1) X assert k in ("changeset", "tag", "user", "date", "summary") X log[k.strip()] = v.strip() X log["parsed_date"] = util.parseTimeString(log["date"]) X local_id, unid = log["changeset"].split(":") X log["local_id"] = int(local_id) X log["unid"] = unid X# pprint(logList) X return logList X X X def _getRepoInfo(self, environ, rev, reload=False): X """Return a dictionary containing all files under source control. X X dirinfos: X Dictionary containing direct members for every collection. X {folderpath: (collectionlist, filelist), ...} X files: X Sorted list of all file paths in the manifest. X filedict: X Dictionary containing all files under source control. X X :: X X {'dirinfos': {'': (['wsgidav', X 'tools', X 'WsgiDAV.egg-info', X 'tests'], X ['index.rst', X 'wsgidav MAKE_DAILY_BUILD.launch', X 'wsgidav run_server.py DEBUG.launch', X 'wsgidav-paste.conf', X ... X 'setup.py']), X 'wsgidav': (['addons', 'samples', 'server', 'interfaces'], X ['__init__.pyc', X 'dav_error.pyc', X 'dav_provider.pyc', X ... X 'wsgidav_app.py']), X }, X 'files': ['.hgignore', X 'ADDONS.txt', X 'wsgidav/addons/mysql_dav_provider.py', X ... X ], X 'filedict': {'.hgignore': True, X 'README.txt': True, X 'WsgiDAV.egg-info/PKG-INFO': True, X } X } X """ X caches = environ.setdefault("wsgidav.hg.cache", {}) X if caches.get(str(rev)) is not None: X util.debug("_getRepoInfo(%s): cache hit." % rev) X return caches[str(rev)] X X start_time = time.time() X self.ui.pushbuffer() X commands.manifest(self.ui, self.repo, rev) X res = self.ui.popbuffer() X files = [] X dirinfos = {} X filedict = {} X for file in res.split("\n"): X if file.strip() == "": X continue X file = file.replace("\\", "/") X # add all parent directories to 'dirinfos' X parents = file.split("/") X if len(parents) >= 1: X p1 = "" X for i in range(0, len(parents)-1): X p2 = parents[i] X dir = dirinfos.setdefault(p1, ([], [])) X if not p2 in dir[0]: X dir[0].append(p2) X if p1 == "": X p1 = p2 X else: X p1 = "%s/%s" % (p1, p2) X dirinfos.setdefault(p1, ([], []))[1].append(parents[-1]) X filedict[file] = True X files.sort() X X cache = {"files": files, X "dirinfos": dirinfos, X "filedict": filedict, X } X caches[str(rev)] = cache X util.note("_getRepoInfo(%s) took %.3f" % (rev, time.time() - start_time) X# , var=cache X ) X return cache X X X# def _listMembers(self, path, rev=None): X# """Return a list of all non-collection members""" X# # Pattern for direct members: X# glob = "glob:" + os.path.join(path, "*").lstrip("/") X## print glob X# self.ui.pushbuffer() X# commands.status(self.ui, self.repo, X# glob, X# all=True) X# lines = self.ui.popbuffer().strip().split("\n") X# pprint(lines) X# return dict X X X def getResourceInst(self, path, environ): X """Return HgResource object for path. X X See DAVProvider.getResourceInst() X """ X self._count_getResourceInst += 1 X X # HG expects the resource paths without leading '/' X localHgPath = path.strip("/") X rev = None X cmd, rest = util.popPath(path) X X if cmd == "": X return VirtualCollection(path, environ, X "root", X ["edit", "released", "archive"]) X elif cmd == "edit": X localHgPath = rest.strip("/") X rev = None X elif cmd == "released": X localHgPath = rest.strip("/") X rev = "tip" X elif cmd == "archive": X if rest == "/": X # Browse /archive: return a list of revision folders: X loglist = self._getLog(limit=10) X members = [ str(l["local_id"]) for l in loglist ] X return VirtualCollection(path, environ, "Revisions", members) X revid, rest = util.popPath(rest) X try: X int(revid) X except: X # Tried to access /archive/anyname X return None X # Access /archive/19 X rev = revid X localHgPath = rest.strip("/") X else: X return None X X # read mercurial repo into request cache X cache = self._getRepoInfo(environ, rev) X X if localHgPath in cache["filedict"]: X # It is a version controlled file X return HgResource(path, False, environ, rev, localHgPath) X X if localHgPath in cache["dirinfos"] or localHgPath == "": X # It is an existing folder X return HgResource(path, True, environ, rev, localHgPath) X return None c34264c5858dc99e65e066a1db0dd834 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/nt_domain_controller.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/nt_domain_controller.py << '28f5f8935d43d847706b268aa4899066' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplementation of a domain controller that allows users to authenticate against Xa Windows NT domain or a local computer (used by HTTPAuthenticator). X XPurpose X------- X XUsage:: X X from wsgidav.addons.nt_domain_controller import NTDomainController X domaincontroller = NTDomainController(presetdomain=None, presetserver=None) X Xwhere: X X+ domaincontroller object corresponds to that in ``wsgidav.conf`` or X as input into ``wsgidav.http_authenticator.HTTPAuthenticator``. X X+ presetdomain allows the admin to specify a domain to be used (instead of any domain that X may come as part of the username in domain\\user). This is useful only if there X is one domain to be authenticated against and you want to spare users from typing the X domain name X X+ presetserver allows the admin to specify the NETBIOS name of the domain controller to X be used (complete with the preceding \\\\). if absent, it will look for trusted X domain controllers on the localhost. X XThis class allows the user to authenticate against a Windows NT domain or a local computer, Xrequires NT or beyond (2000, XP, 2003, etc). X XThis class requires Mark Hammond's Win32 extensions for Python at here_ or sourceforge_ X X.. _here : http://starship.python.net/crew/mhammond/win32/Downloads.html X.. _sourceforge : http://sourceforge.net/projects/pywin32/ X XInformation on Win32 network authentication was from the following resources: X X+ http://ejabberd.jabber.ru/node/55 X X+ http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/81402 X X XTestability and caveats X----------------------- X X**Digest Authentication** X Digest authentication requires the password to be retrieve from the system to compute X the correct digest for comparison. This is so far impossible (and indeed would be a X big security loophole if it was allowed), so digest authentication WILL not work X with this class. X X Highly recommend basic authentication over SSL support. X X**User Login** X Authentication will count as a user login attempt, so any security in place for X invalid password attempts may be triggered. X X Also note that, even though the user is logged in, the application does not impersonate X the user - the application will continue to run under the account and permissions it X started with. The user has the read/write permissions to the share of the running account X and not his own account. X X**Using on a local computer** X This class has been tested on a local computer (Windows XP). Leave domain as None and X do not specify domain when entering username in this case. X X**Using for a network domain** X This class is being tested for a network domain (I'm setting one up to test). X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom wsgidav import util X Ximport win32net #@UnresolvedImport Ximport win32security #@UnresolvedImport Ximport win32netcon #@UnresolvedImport X X__docformat__ = "reStructuredText" X_logger = util.getModuleLogger(__name__) X X Xclass NTDomainController(object): X X def __init__(self, presetdomain = None, presetserver = None): X self._presetdomain = presetdomain X self._presetserver = presetserver X X X def __repr__(self): X return self.__class__.__name__ X X X def getDomainRealm(self, inputURL, environ): X return "Windows Domain Authentication" X X X def requireAuthentication(self, realmname, environ): X return True X X X def isRealmUser(self, realmname, username, environ): X (domain, usern) = self._getDomainUsername(username) X dcname = self._getDomainControllerName(domain) X return self._isUser(usern, domain, dcname) X X X def getRealmUserPassword(self, realmname, username, environ): X (domain, user) = self._getDomainUsername(username) X dcname = self._getDomainControllerName(domain) X X try: X userdata = win32net.NetUserGetInfo(dcname, user, 1) X except: X _logger.exception("NetUserGetInfo") X userdata = {} X# if "password" in userdata: X# if userdata["password"] != None: X# return userdata["password"] X# return None X return userdata.get("password") X X X def authDomainUser(self, realmname, username, password, environ): X (domain, usern) = self._getDomainUsername(username) X dcname = self._getDomainControllerName(domain) X return self._authUser(usern, password, domain, dcname) X X X def _getDomainUsername(self, inusername): X userdata = inusername.split("\\", 1) X if len(userdata) == 1: X domain = None X username = userdata[0] X else: X domain = userdata[0] X username = userdata[1] X X if self._presetdomain != None: X domain = self._presetdomain X X return (domain, username) X X X def _getDomainControllerName(self, domain): X if self._presetserver != None: X return self._presetserver X X try: X # execute this on the localhost X pdc = win32net.NetGetAnyDCName(None, domain) X except: X pdc = None X X return pdc X X X def _isUser(self, username, domain, server): X resume = "init" X while resume: X if resume == "init": X resume = 0 X try: X users, _total, resume = win32net.NetUserEnum(server, 0, win32netcon.FILTER_NORMAL_ACCOUNT, 0) X # Make sure, we compare unicode X un = username.decode("utf8").lower() X for userinfo in users: X uiname = userinfo.get("name") X assert uiname X assert isinstance(uiname, unicode) X if un == userinfo["name"].lower(): X return True X except win32net.error, e: X _logger.exception("NetUserEnum: %s" % e) X return False X _logger.info("User '%s' not found on server '%s'" % (username, server)) X return False X X X def _authUser(self, username, password, domain, server): X if not self._isUser(username, domain, server): X return False X X try: X htoken = win32security.LogonUser(username, domain, password, win32security.LOGON32_LOGON_NETWORK, win32security.LOGON32_PROVIDER_DEFAULT) X except win32security.error, err: X _logger.warning("LogonUser failed for user '%s': %s" % (username, err)) X return False X else: X if htoken: X htoken.Close() #guarantee's cleanup X _logger.debug("User '%s' logged on." % username) X return True X _logger.warning("Logon failed for user '%s'." % username) X return False 28f5f8935d43d847706b268aa4899066 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/couch_property_manager.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/couch_property_manager.py << 'f90ce8b5194c19db1a77a136b0073406' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplements a property manager based on CouchDB. X X Xhttp://wiki.apache.org/couchdb/Reference Xhttp://packages.python.org/CouchDB/views.html X X XUsage: add this lines to wsgidav.conf:: X X from wsgidav.addons.couch_property_manager import CouchPropertyManager X prop_man_opts = {} X propsmanager = CouchPropertyManager(prop_man_opts) X XValid options are (sample shows defaults):: X X opts = {"url": "http://localhost:5984/", # CouchDB server X "dbName": "wsgidav-props", # Name of DB to store the properties X } X X""" Xfrom wsgidav import util Ximport couchdb Xfrom urllib import quote Xfrom uuid import uuid4 X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X X#=============================================================================== X# CouchPropertyManager X#=============================================================================== Xclass CouchPropertyManager(object): X """Implements a property manager based on CouchDB.""" X def __init__(self, options): X self.options = options X self._connect() X X def __del__(self): X self._disconnect() X X def _connect(self): X opts = self.options X if opts.get("url"): X self.couch = couchdb.Server(opts.get("url")) X else: X self.couch = couchdb.Server() X X dbName = opts.get("dbName", "wsgidav_props") X if dbName in self.couch: X self.db = self.couch[dbName] X util.log("CouchPropertyManager connected to %s v%s" % (self.db, self.couch.version())) X else: X self.db = self.couch.create(dbName) X util.log("CouchPropertyManager created new db %s v%s" % (self.db, self.couch.version())) X X # Ensure that we have a permanent view X if not "_design/properties" in self.db: X map = """ X function(doc) { X if(doc.type == 'properties') { X emit(doc.url, { 'id': doc._id, 'url': doc.url }); X } X } X """ X designDoc = { X "_id": "_design/properties", X# "_rev": "42351258", X "language": "javascript", X "views": { X "titles": { X "map": "function(doc) { emit(null, { 'id': doc._id, 'title': doc.title }); }" X }, X # http://127.0.0.1:5984/wsgidav_props/_design/properties/_view/by_url X "by_url": { X "map": map X } X } X } X self.db.save(designDoc) X X# pprint(self.couch.stats()) X X def _disconnect(self): X pass X X def __repr__(self): X return "CouchPropertyManager(%s)" % self.db X X def _sync(self): X pass X X def _check(self, msg=""): X pass X X def _dump(self, msg="", out=None): X pass X X def _find(self, url): X """Return properties document for path.""" X # Query the permanent view to find a url X vr = self.db.view("properties/by_url", key=url, include_docs=True) X _logger.debug("find(%r) returned %s" % (url, len(vr))) X assert len(vr) <= 1, "Found multiple matches for %r" % url X for row in vr: X assert row.doc X return row.doc X return None X X def _findDescendents(self, url): X """Return properties document for url and all children.""" X # Ad-hoc query for URL starting with a prefix X map_fun = """function(doc) { X var url = doc.url + "/"; X if(doc.type === 'properties' && url.indexOf('%s') === 0) { X emit(doc.url, { 'id': doc._id, 'url': doc.url }); X } X }""" % (url + "/") X vr = self.db.query(map_fun, include_docs=True) X for row in vr: X yield row.doc X return X X def getProperties(self, normurl): X _logger.debug("getProperties(%s)" % normurl) X doc = self._find(normurl) X propNames = [] X if doc: X for name in doc["properties"].keys(): X propNames.append(name) X return propNames X X def getProperty(self, normurl, propname): X _logger.debug("getProperty(%s, %s)" % (normurl, propname)) X doc = self._find(normurl) X if not doc: X return None X prop = doc["properties"].get(propname) X return prop X X def writeProperty(self, normurl, propname, propertyvalue, dryRun=False): X assert normurl and normurl.startswith("/") X assert propname X assert propertyvalue is not None X X _logger.debug("writeProperty(%s, %s, dryRun=%s):\n\t%s" % (normurl, propname, dryRun, propertyvalue)) X if dryRun: X return # TODO: can we check anything here? X X doc = self._find(normurl) X if doc: X doc["properties"][propname] = propertyvalue X else: X doc = {"_id": uuid4().hex, # Documentation suggests to set the id X "url": normurl, X "title": quote(normurl), X "type": "properties", X "properties": {propname: propertyvalue} X } X self.db.save(doc) X X def removeProperty(self, normurl, propname, dryRun=False): X _logger.debug("removeProperty(%s, %s, dryRun=%s)" % (normurl, propname, dryRun)) X if dryRun: X # TODO: can we check anything here? X return X doc = self._find(normurl) X # Specifying the removal of a property that does not exist is NOT an error. X if not doc or doc["properties"].get(propname) is None: X return X del doc["properties"][propname] X self.db.save(doc) X X def removeProperties(self, normurl): X _logger.debug("removeProperties(%s)" % normurl) X doc = self._find(normurl) X if doc: X self.db.delete(doc) X return X X def copyProperties(self, srcUrl, destUrl): X doc = self._find(srcUrl) X if not doc: X _logger.debug("copyProperties(%s, %s): src has no properties" % (srcUrl, destUrl)) X return X _logger.debug("copyProperties(%s, %s)" % (srcUrl, destUrl)) X assert not self._find(destUrl) X doc2 = {"_id": uuid4().hex, X "url": destUrl, X "title": quote(destUrl), X "type": "properties", X "properties": doc["properties"], X } X self.db.save(doc2) X X def moveProperties(self, srcUrl, destUrl, withChildren): X _logger.debug("moveProperties(%s, %s, %s)" % (srcUrl, destUrl, withChildren)) X if withChildren: X # Match URLs that are equal to or begin with '/' X docList = self._findDescendents(srcUrl) X for doc in docList: X newDest = doc["url"].replace(srcUrl, destUrl) X _logger.debug("move property %s -> %s" % (doc["url"], newDest)) X doc["url"] = newDest X self.db.save(doc) X else: X # Move srcUrl only X # TODO: use findAndModify()? X doc = self._find(srcUrl) X if doc: X _logger.debug("move property %s -> %s" % (doc["url"], destUrl)) X doc["url"] = destUrl X self.db.save(doc) X return X X#=============================================================================== X# X#=============================================================================== Xdef test(): X pass X X Xif __name__ == "__main__": X test() f90ce8b5194c19db1a77a136b0073406 echo c - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile mkdir -p py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile > /dev/null 2>&1 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/seafile_dav_provider.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/seafile_dav_provider.py << '6e07fa407bffc0928bbfa6921cf69212' X# Copyright 2013 Seafile, Inc X# Licensed under the terms of seafile-pro-license.txt. X# You are not allowed to modify or redistribute this file. X# X Xfrom wsgidav.dav_error import DAVError, HTTP_BAD_REQUEST, HTTP_FORBIDDEN, \ X HTTP_NOT_FOUND, HTTP_INTERNAL_ERROR Xfrom wsgidav.dav_provider import DAVProvider, DAVCollection, DAVNonCollection X Ximport wsgidav.util as util Ximport os X#import mimetypes Ximport tempfile X Ximport seaserv Xfrom seaserv import seafile_api Xfrom pysearpc import SearpcError Xfrom seafobj import commit_mgr, fs_mgr Xfrom seafobj.fs import SeafFile, SeafDir Xfrom seaf_utils import SEAFILE_CONF_DIR, UTF8Dict, utf8_path_join, utf8_wrap X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X XNEED_PROGRESS = 0 XSYNCHRONOUS = 1 X X#=============================================================================== X# SeafileResource X#=============================================================================== Xclass SeafileResource(DAVNonCollection): X def __init__(self, path, repo, rel_path, obj, environ): X super(SeafileResource, self).__init__(path, environ) X self.repo = repo X self.rel_path = rel_path X self.obj = obj X self.username = environ.get("http_authenticator.username", "") X X # Getter methods for standard live properties X def getContentLength(self): X return self.obj.size X def getContentType(self): X# (mimetype, _mimeencoding) = mimetypes.guess_type(self.path) X# print "mimetype(%s): %r, %r" % (self.path, mimetype, _mimeencoding) X# if not mimetype: X# mimetype = "application/octet-stream" X# print "mimetype(%s): return %r" % (self.path, mimetype) X# return mimetype X return util.guessMimeType(self.path) X def getCreationDate(self): X# return int(time.time()) X return None X def getDisplayName(self): X return self.name X def getEtag(self): X return utf8_wrap(self.obj.obj_id) X X def getLastModified(self): X cached_mtime = getattr(self.obj, 'last_modified', None) X if cached_mtime: X return cached_mtime X X parent, filename = os.path.split(self.rel_path) X mtimes = seafile_api.get_files_last_modified(self.repo.id, parent, -1) X for mtime in mtimes: X if (mtime.file_name.encode('utf-8') == filename): X return mtime.last_modified X X return None X X def supportEtag(self): X return True X def supportRanges(self): X return False X X def getContent(self): X """Open content as a stream for reading. X X See DAVResource.getContent() X """ X assert not self.isCollection X return self.obj.get_stream() X X X def beginWrite(self, contentType=None): X """Open content as a stream for writing. X X See DAVResource.beginWrite() X """ X assert not self.isCollection X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X if seafile_api.check_permission(self.repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X fd, path = tempfile.mkstemp(dir=self.provider.tmpdir) X self.tmpfile_path = path X return os.fdopen(fd, "wb") X X def endWrite(self, withErrors): X if not withErrors: X parent, filename = os.path.split(self.rel_path) X seafile_api.put_file(self.repo.id, self.tmpfile_path, parent, filename, X self.username, None) X os.unlink(self.tmpfile_path) X X def handleDelete(self): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X if seafile_api.check_permission(self.repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X parent, filename = os.path.split(self.rel_path) X seafile_api.del_file(self.repo.id, parent, filename, self.username) X X return True X X def handleMove(self, destPath): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X parts = destPath.strip("/").split("/", 1) X if len(parts) <= 1: X raise DAVError(HTTP_BAD_REQUEST) X repo_name = parts[0] X rel_path = parts[1] X X dest_dir, dest_file = os.path.split(rel_path) X dest_repo = getRepoByName(repo_name, self.username) X X if seafile_api.check_permission(dest_repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X src_dir, src_file = os.path.split(self.rel_path) X if not src_file: X raise DAVError(HTTP_BAD_REQUEST) X X if not seafile_api.is_valid_filename(dest_repo.id, dest_file): X raise DAVError(HTTP_BAD_REQUEST) X X # some clients such as GoodReader requires "overwrite" semantics X file_id_dest = seafile_api.get_file_id_by_path(dest_repo.id, rel_path) X if file_id_dest != None: X seafile_api.del_file(dest_repo.id, dest_dir, dest_file, self.username) X X seafile_api.move_file(self.repo.id, src_dir, src_file, X dest_repo.id, dest_dir, dest_file, self.username, NEED_PROGRESS, SYNCHRONOUS) X X return True X X def handleCopy(self, destPath, depthInfinity): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X parts = destPath.strip("/").split("/", 1) X if len(parts) <= 1: X raise DAVError(HTTP_BAD_REQUEST) X repo_name = parts[0] X rel_path = parts[1] X X dest_dir, dest_file = os.path.split(rel_path) X dest_repo = getRepoByName(repo_name, self.username) X X if seafile_api.check_permission(dest_repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X src_dir, src_file = os.path.split(self.rel_path) X if not src_file: X raise DAVError(HTTP_BAD_REQUEST) X X if not seafile_api.is_valid_filename(dest_repo.id, dest_file): X raise DAVError(HTTP_BAD_REQUEST) X X seafile_api.copy_file(self.repo.id, src_dir, src_file, X dest_repo.id, dest_dir, dest_file, self.username, NEED_PROGRESS, SYNCHRONOUS) X X return True X X#=============================================================================== X# SeafDirResource X#=============================================================================== Xclass SeafDirResource(DAVCollection): X def __init__(self, path, repo, rel_path, obj, environ): X super(SeafDirResource, self).__init__(path, environ) X self.repo = repo X self.rel_path = rel_path X self.obj = obj X self.username = environ.get("http_authenticator.username", "") X X # Getter methods for standard live properties X def getCreationDate(self): X# return int(time.time()) X return None X def getDisplayName(self): X return self.name X def getDirectoryInfo(self): X return None X def getEtag(self): X return utf8_wrap(self.obj.obj_id) X def getLastModified(self): X# return int(time.time()) X return None X X def getMemberNames(self): X namelist = [] X for e in self.obj.dirs: X namelist.append(e[0]) X for e in self.obj.files: X namelist.append(e[0]) X return namelist X X def getMember(self, name): X member_rel_path = "/".join([self.rel_path, name]) X member_path = "/".join([self.path, name]) X member = self.obj.lookup(name) X X if not member: X raise DAVError(HTTP_NOT_FOUND) X X if isinstance(member, SeafFile): X return SeafileResource(member_path, self.repo, member_rel_path, member, self.environ) X else: X return SeafDirResource(member_path, self.repo, member_rel_path, member, self.environ) X X def getMemberList(self): X member_list = [] X d = self.obj X X if d.version == 0: X file_mtimes = [] X try: X file_mtimes = seafile_api.get_files_last_modified(self.repo.id, self.rel_path, -1) X except: X raise DAVError(HTTP_INTERNAL_ERROR) X X mtimes = UTF8Dict() X for entry in file_mtimes: X mtimes[entry.file_name] = entry.last_modified X for name, dent in d.dirents.iteritems(): X member_path = utf8_path_join(self.path, name) X member_rel_path = utf8_path_join(self.rel_path, name) X X if dent.is_dir(): X obj = fs_mgr.load_seafdir(d.store_id, d.version, dent.id) X res = SeafDirResource(member_path, self.repo, member_rel_path, obj, self.environ) X elif dent.is_file(): X obj = fs_mgr.load_seafile(d.store_id, d.version, dent.id) X res = SeafileResource(member_path, self.repo, member_rel_path, obj, self.environ) X else: X continue X X if d.version == 1: X obj.last_modified = dent.mtime X else: X obj.last_modified = mtimes[name] X X member_list.append(res) X X return member_list X X # --- Read / write --------------------------------------------------------- X X def createEmptyResource(self, name): X """Create an empty (length-0) resource. X X See DAVResource.createEmptyResource() X """ X assert not "/" in name X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X if seafile_api.check_permission(self.repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X try: X seafile_api.post_empty_file(self.repo.id, self.rel_path, name, self.username) X except SearpcError, e: X if e.msg == 'Invalid file name': X raise DAVError(HTTP_BAD_REQUEST) X raise X X # Repo was updated, can't use self.repo X repo = seafile_api.get_repo(self.repo.id) X if not repo: X raise DAVError(HTTP_INTERNAL_ERROR) X X member_rel_path = "/".join([self.rel_path, name]) X member_path = "/".join([self.path, name]) X obj = resolveRepoPath(repo, member_rel_path) X if not obj or not isinstance(obj, SeafFile): X raise DAVError(HTTP_INTERNAL_ERROR) X X return SeafileResource(member_path, repo, member_rel_path, obj, self.environ) X X def createCollection(self, name): X """Create a new collection as member of self. X X See DAVResource.createCollection() X """ X assert not "/" in name X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X if seafile_api.check_permission(self.repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X if not seafile_api.is_valid_filename(self.repo.id, name): X raise DAVError(HTTP_BAD_REQUEST) X X seafile_api.post_dir(self.repo.id, self.rel_path, name, self.username) X X def handleDelete(self): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X if seafile_api.check_permission(self.repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X parent, filename = os.path.split(self.rel_path) X # Can't delete repo root X if not filename: X raise DAVError(HTTP_BAD_REQUEST) X X seafile_api.del_file(self.repo.id, parent, filename, self.username) X X return True X X def handleMove(self, destPath): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X parts = destPath.strip("/").split("/", 1) X if len(parts) <= 1: X raise DAVError(HTTP_BAD_REQUEST) X repo_name = parts[0] X rel_path = parts[1] X X dest_dir, dest_file = os.path.split(rel_path) X dest_repo = getRepoByName(repo_name, self.username) X X if seafile_api.check_permission(dest_repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X src_dir, src_file = os.path.split(self.rel_path) X if not src_file: X raise DAVError(HTTP_BAD_REQUEST) X X if not seafile_api.is_valid_filename(dest_repo.id, dest_file): X raise DAVError(HTTP_BAD_REQUEST) X X seafile_api.move_file(self.repo.id, src_dir, src_file, X dest_repo.id, dest_dir, dest_file, self.username, NEED_PROGRESS, SYNCHRONOUS) X X return True X X def handleCopy(self, destPath, depthInfinity): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X parts = destPath.strip("/").split("/", 1) X if len(parts) <= 1: X raise DAVError(HTTP_BAD_REQUEST) X repo_name = parts[0] X rel_path = parts[1] X X dest_dir, dest_file = os.path.split(rel_path) X dest_repo = getRepoByName(repo_name, self.username) X X if seafile_api.check_permission(dest_repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X src_dir, src_file = os.path.split(self.rel_path) X if not src_file: X raise DAVError(HTTP_BAD_REQUEST) X X if not seafile_api.is_valid_filename(dest_repo.id, dest_file): X raise DAVError(HTTP_BAD_REQUEST) X X seafile_api.copy_file(self.repo.id, src_dir, src_file, X dest_repo.id, dest_dir, dest_file, self.username, NEED_PROGRESS, SYNCHRONOUS) X X return True X Xclass RootResource(DAVCollection): X def __init__(self, username, environ): X super(RootResource, self).__init__("/", environ) X self.username = username X X # Getter methods for standard live properties X def getCreationDate(self): X# return int(time.time()) X return None X def getDisplayName(self): X return "" X def getDirectoryInfo(self): X return None X def getEtag(self): X return None X def getLastModified(self): X# return int(time.time()) X return None X X def getMemberNames(self): X all_repos = getAccessibleRepos(self.username) X X name_hash = {} X for r in all_repos: X r_list = name_hash[r.name] X if not r_list: X name_hash[r.name] = [r] X else: X r_list.append(r) X X namelist = [] X for r_list in name_hash.values(): X if len(r_list) == 1: X repo = r_list[0] X namelist.append(repo.name) X else: X for repo in r_list: X unique_name = repo.name + "-" + repo.id X namelist.append(unique_name) X X return namelist X X def getMember(self, name): X repo = getRepoByName(name, self.username) X return self._createRootRes(repo, name) X X def getMemberList(self): X """ X Overwrite this method for better performance. X The default implementation call getMemberNames() then call getMember() X for each name. This calls getAccessibleRepos() for too many times. X """ X all_repos = getAccessibleRepos(self.username) X X name_hash = {} X for r in all_repos: X r_list = name_hash.get(r.name, []) X if not r_list: X name_hash[r.name] = [r] X else: X r_list.append(r) X X member_list = [] X for r_list in name_hash.values(): X if len(r_list) == 1: X repo = r_list[0] X res = self._createRootRes(repo, repo.name) X member_list.append(res) X else: X for repo in r_list: X unique_name = repo.name + "-" + repo.id X res = self._createRootRes(repo, unique_name) X member_list.append(res) X X return member_list X X def _createRootRes(self, repo, name): X obj = get_repo_root_seafdir(repo) X return SeafDirResource("/"+name, repo, "", obj, self.environ) X X # --- Read / write --------------------------------------------------------- X X def createEmptyResource(self, name): X raise DAVError(HTTP_FORBIDDEN) X X def createCollection(self, name): X raise DAVError(HTTP_FORBIDDEN) X X def handleDelete(self): X raise DAVError(HTTP_FORBIDDEN) X X def handleMove(self, destPath): X raise DAVError(HTTP_FORBIDDEN) X X def handleCopy(self, destPath, depthInfinity): X raise DAVError(HTTP_FORBIDDEN) X X X#=============================================================================== X# SeafileProvider X#=============================================================================== Xclass SeafileProvider(DAVProvider): X X def __init__(self, readonly=False): X super(SeafileProvider, self).__init__() X self.readonly = readonly X self.tmpdir = os.path.join(SEAFILE_CONF_DIR, "webdavtmp") X if not os.access(self.tmpdir, os.F_OK): X os.mkdir(self.tmpdir) X X def __repr__(self): X rw = "Read-Write" X if self.readonly: X rw = "Read-Only" X return "%s for Seafile (%s)" % (self.__class__.__name__, rw) X X X def getResourceInst(self, path, environ): X """Return info dictionary for path. X X See DAVProvider.getResourceInst() X """ X self._count_getResourceInst += 1 X X username = environ.get("http_authenticator.username", "") X X if path == "/" or path == "": X return RootResource(username, environ) X X path = path.rstrip("/") X try: X repo, rel_path, obj = resolvePath(path, username) X except DAVError, e: X if e.value == HTTP_NOT_FOUND: X return None X raise X X if isinstance(obj, SeafDir): X return SeafDirResource(path, repo, rel_path, obj, environ) X return SeafileResource(path, repo, rel_path, obj, environ) X Xdef resolvePath(path, username): X segments = path.strip("/").split("/") X if len(segments) == 0: X raise DAVError(HTTP_BAD_REQUEST) X repo_name = segments.pop(0) X X repo = getRepoByName(repo_name, username) X X rel_path = "" X obj = get_repo_root_seafdir(repo) X X n_segs = len(segments) X i = 0 X for segment in segments: X obj = obj.lookup(segment) X X if not obj or (isinstance(obj, SeafFile) and i != n_segs-1): X raise DAVError(HTTP_NOT_FOUND) X X rel_path += "/" + segment X i += 1 X X return (repo, rel_path, obj) X Xdef resolveRepoPath(repo, path): X segments = path.strip("/").split("/") X X obj = get_repo_root_seafdir(repo) X X n_segs = len(segments) X i = 0 X for segment in segments: X obj = obj.lookup(segment) X X if not obj or (isinstance(obj, SeafFile) and i != n_segs-1): X return None X X i += 1 X X return obj X Xdef get_repo_root_seafdir(repo): X root_id = commit_mgr.get_commit_root_id(repo.id, repo.version, repo.head_cmmt_id) X return fs_mgr.load_seafdir(repo.store_id, repo.version, root_id) X Xdef getRepoByName(repo_name, username): X repos = getAccessibleRepos(username) X X ret_repo = None X for repo in repos: X if repo.name == repo_name: X ret_repo = repo X break X X if not ret_repo: X for repo in repos: X if repo.name + "-" + repo.id == repo_name: X ret_repo = repo X break X if not ret_repo: X raise DAVError(HTTP_NOT_FOUND) X X return ret_repo X Xdef getAccessibleRepos(username): X all_repos = {} X X def addRepo(repo_id): X try: X if all_repos.has_key(repo_id): X return X repo = seafile_api.get_repo(repo_id) X if repo: X all_repos[repo_id] = repo X except SearpcError, e: X util.warn("Failed to get repo %.8s: %s" % (repo_id, e.msg)) X X try: X owned_repos = seafile_api.get_owned_repo_list(username) X except SearpcError, e: X util.warn("Failed to list owned repos: %s" % e.msg) X X for orepo in owned_repos: X addRepo(orepo.id) X X try: X shared_repos = seafile_api.get_share_in_repo_list(username, -1, -1) X except SearpcError, e: X util.warn("Failed to list shared repos: %s" % e.msg) X X for srepo in shared_repos: X addRepo(srepo.repo_id) X X try: X joined_groups = seaserv.get_personal_groups_by_user(username) X except SearpcError, e: X util.warn("Failed to get groups for %s" % username) X for g in joined_groups: X try: X group_repos = seafile_api.get_group_repo_list(g.id) X for repo in group_repos: X if all_repos.has_key(repo.id): X continue X all_repos[repo.id] = repo X except SearpcError, e: X util.warn("Failed to list repos in group %d" % g.id) X X # Don't include encrypted repos X ret = [] X for repo in all_repos.values(): X if not repo.encrypted: X repo.name = repo.name.encode('utf-8') X ret.append(repo) X X return ret 6e07fa407bffc0928bbfa6921cf69212 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/domain_controller.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/domain_controller.py << '7b80cd64af700e7f43b9ecef0a23f054' X# Copyright 2013 Seafile, Inc X# Licensed under the terms of seafile-pro-license.txt. X# You are not allowed to modify or redistribute this file. X# X Ximport os Ximport ccnet Xfrom pysearpc import SearpcError Xfrom seaf_utils import CCNET_CONF_DIR X Xclass SeafileDomainController(object): X X def __init__(self): X ccnet_conf_dir = os.path.normpath(os.path.expanduser(CCNET_CONF_DIR)) X X pool = ccnet.ClientPool(ccnet_conf_dir) X self.ccnet_threaded_rpc = ccnet.CcnetThreadedRpcClient(pool, req_pool=True) X X def __repr__(self): X return self.__class__.__name__ X X def getDomainRealm(self, inputURL, environ): X return "Seafile Authentication" X X def requireAuthentication(self, realmname, envrion): X return True X X def isRealmUser(self, realmname, username, environ): X return True X X def getRealmUserPassword(self, realmname, username, environ): X """ X Not applicable to seafile. X """ X return "" X X def authDomainUser(self, realmname, username, password, environ): X if "'" in username: X return False X X try: X ret = self.ccnet_threaded_rpc.validate_emailuser(username, password) X except: X return False X X if ret == 0: X return True X else: X return False 7b80cd64af700e7f43b9ecef0a23f054 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/__init__.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/__init__.py << 'd9e575392cc1a92f1be46311dc1c43a6' d9e575392cc1a92f1be46311dc1c43a6 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/seaf_utils.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/seafile/seaf_utils.py << '2776f5cefafa5061d6b2e2e752f18134' X#!/usr/bin/env python X# -*- coding: utf-8 -*- X X# Copyright 2013 Seafile, Inc X# Licensed under the terms of seafile-pro-license.txt. X# You are not allowed to modify or redistribute this file. X# X Ximport os X Xdef get_seafile_conf_dir(): X try: X SEAFILE_CONF_DIR = os.environ['SEAFILE_CONF_DIR'] X except KeyError: X raise RuntimeError('SEAFILE_CONF_DIR is not set') X X return SEAFILE_CONF_DIR X XSEAFILE_CONF_DIR = get_seafile_conf_dir() X Xdef get_ccnet_conf_dir(): X try: X CCNET_CONF_DIR = os.environ['CCNET_CONF_DIR'] X except KeyError: X raise RuntimeError('CCNET_CONF_DIR is not set') X X return CCNET_CONF_DIR X XCCNET_CONF_DIR = get_ccnet_conf_dir() X Xdef utf8_wrap(s): X if isinstance(s, unicode): X s = s.encode('utf-8') X X return s X Xclass UTF8Dict(dict): X '''A dict whose keys are always converted to utf8, so we don't need to X care whether the param for the key is in utf-8 or unicode when set/get X X ''' X def __init__(self): X dict.__init__(self) X X def __setitem__(self, k, v): X dict.__setitem__(self, utf8_wrap(k), v) X X def __getitem__(self, k): X return dict.__getitem__(self, utf8_wrap(k)) X X Xdef utf8_path_join(*args): X args = [ utf8_wrap(arg) for arg in args ] X return os.path.join(*args) X 2776f5cefafa5061d6b2e2e752f18134 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/mysql_dav_provider.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/addons/mysql_dav_provider.py << 'c19686f02fed00371c35f3f18dabdc46' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XImplementation of a WebDAV provider that provides a very basic, read-only Xresource layer emulation of a MySQL database. X XThis module is specific to the WsgiDAV application. It provides a Xclasses ``MySQLBrowserProvider``. X XUsage:: X X (see sample_wsgidav.conf) X MySQLBrowserProvider(host, user, passwd, db) X X host - host of database server X user - username to access database X passwd - passwd to access database X db - name of database on database server X XThe ``MySQLBrowserProvider`` provides a very basic, read-only Xresource layer emulation of a MySQL database. XIt provides the following interface: X X - the root collection shared consists of collections that correspond to X table names X X - in each table collection, there is a resource called "_ENTIRE_CONTENTS". X This is a non-collection resource that returns a csv representation of the X entire table X X - if the table has a single primary key, each table record will also appear X as a non-collection resource in the table collection using the primary key X value as its name. This resource returns a csv representation of the record X and will also include the record attributes as live properties with X attribute name as property name and table name suffixed with colon as the X property namespace X X XThis is a very basic interface and below is a by no means thorough summary of Xits limitations: X X - Really only supports having numbers or strings as primary keys. The code uses X a numeric or string comparison that may not hold up if the primary key is X a date or some other datatype. X X - There is no handling for cases like BLOBs as primary keys or such. Well, there is X no handling for BLOBs in general. X X - When returning contents, it buffers the entire contents! A bad way to return X large tables. Ideally you would have a FileMixin that reads the database even X as the application reads the file object.... X X - It takes too many database queries to return information. X Ideally there should be some sort of caching for metadata at least, to avoid X unnecessary queries to the database. X XSee `Developers info`_ for more information about the WsgiDAV architecture. X X.. _`Developers info`: http://docs.wsgidav.googlecode.com/hg/html/develop.html X""" Xfrom wsgidav.dav_provider import DAVProvider, _DAVResource Xfrom wsgidav import util Xfrom wsgidav.dav_error import DAVError, HTTP_FORBIDDEN,\ X PRECONDITION_CODE_ProtectedProperty Ximport MySQLdb #@UnresolvedImport Ximport md5 Ximport time Ximport csv Xtry: X from cStringIO import StringIO Xexcept ImportError: X from StringIO import StringIO X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X X Xclass MySQLBrowserResource(_DAVResource): X """Represents a single existing DAV resource instance. X X See also DAVResource and MySQLBrowserProvider. X """ X def __init__(self, provider, path, isCollection, environ): X super(MySQLBrowserResource, self).__init__(path, isCollection, environ) X self._cache = None X X X def _init(self): X """Read resource information into self._cache, for cached access. X X See DAVResource._init() X """ X # TODO: recalc self.path from , to fix correct file system case X # On windows this would lead to correct URLs X self.provider._count_getResourceInstInit += 1 X tableName, primKey = self.provider._splitPath(self.path) X X displayType = "Unknown" X displayTypeComment = "" X contentType = "text/html" X X# _logger.debug("getInfoDict(%s), nc=%s" % (path, self.connectCount)) X if tableName is None: X displayType = "Database" X elif primKey is None: # "database" and table name X displayType = "Database Table" X else: X contentType = "text/csv" X if primKey == "_ENTIRE_CONTENTS": X displayType = "Database Table Contents" X displayTypeComment = "CSV Representation of Table Contents" X else: X displayType = "Database Record" X displayTypeComment = "Attributes available as properties" X X # Avoid calling isCollection, since it would call isExisting -> _initConnection X isCollection = primKey is None X X self._cache = {"contentLength": None, X "contentType": contentType, X "created": time.time(), X "displayName": self.name, X "etag": md5.new(self.path).hexdigest(), X "modified": None, X "supportRanges": False, X "displayInfo": {"type": displayType, X "typeComment": displayTypeComment, X }, X } X X # Some resource-only infos: X if not isCollection: X self._cache["modified"] = time.time() X _logger.debug("---> _init, nc=%s" % self.provider._count_initConnection) X X X def _getInfo(self, info): X if self._cache is None: X self._init() X return self._cache.get(info) X X # Getter methods for standard live properties X def getContentLength(self): X return self._getInfo("contentLength") X def getContentType(self): X return self._getInfo("contentType") X def getCreationDate(self): X return self._getInfo("created") X def getDisplayName(self): X return self.name X def getDisplayInfo(self): X return self._getInfo("displayInfo") X def getEtag(self): X return self._getInfo("etag") X def getLastModified(self): X return self._getInfo("modified") X X def getMemberList(self): X """Return list of (direct) collection member names (UTF-8 byte strings). X X See DAVResource.getMemberList() X """ X members = [] X conn = self.provider._initConnection() X try: X tableName, primKey = self.provider._splitPath(self.path) X if tableName is None: X retlist = self.provider._listTables(conn) X for name in retlist: X members.append(MySQLBrowserResource(self.provider, X util.joinUri(self.path, name), X True, X self.environ)) X elif primKey is None: X pri_key = self.provider._findPrimaryKey(conn, tableName) X if pri_key is not None: X retlist = self.provider._listFields(conn, tableName, pri_key) X for name in retlist: X members.append(MySQLBrowserResource(self.provider, X util.joinUri(self.path, name), X False, X self.environ)) X members.insert(0, MySQLBrowserResource(self.provider, X util.joinUri(self.path, "_ENTIRE_CONTENTS"), X False, X self.environ)) X finally: X conn.close() X return members X X X def getContent(self): X """Open content as a stream for reading. X X See DAVResource.getContent() X """ X filestream = StringIO() X X tableName, primKey = self.provider._splitPath(self.path) X if primKey is not None: X conn = self.provider._initConnection() X listFields = self.provider._getFieldList(conn, tableName) X csvwriter = csv.DictWriter(filestream, listFields, extrasaction="ignore") X dictFields = {} X for field_name in listFields: X dictFields[field_name] = field_name X csvwriter.writerow(dictFields) X X if primKey == "_ENTIRE_CONTENTS": X cursor = conn.cursor (MySQLdb.cursors.DictCursor) X cursor.execute ("SELECT * from " + self.provider._db + "." + tableName) X result_set = cursor.fetchall () X for row in result_set: X csvwriter.writerow(row) X cursor.close () X else: X row = self.provider._getRecordByPrimaryKey(conn, tableName, primKey) X if row is not None: X csvwriter.writerow(row) X conn.close() X X #this suffices for small dbs, but X #for a production big database, I imagine you would have a FileMixin that X #does the retrieving and population even as the file object is being read X filestream.seek(0) X return filestream X X X def getPropertyNames(self, isAllProp): X """Return list of supported property names in Clark Notation. X X Return supported live and dead properties. (See also DAVProvider.getPropertyNames().) X X In addition, all table field names are returned as properties. X """ X # Let default implementation return supported live and dead properties X propNames = super(MySQLBrowserResource, self).getPropertyNames(isAllProp) X # Add fieldnames as properties X tableName, primKey = self.provider._splitPath(self.path) X if primKey is not None: X conn = self.provider._initConnection() X fieldlist = self.provider._getFieldList(conn, tableName) X for fieldname in fieldlist: X propNames.append("{%s:}%s" % (tableName, fieldname)) X conn.close() X return propNames X X X def getPropertyValue(self, propname): X """Return the value of a property. X X The base implementation handles: X X - ``{DAV:}lockdiscovery`` and ``{DAV:}supportedlock`` using the X associated lock manager. X - All other *live* properties (i.e. name starts with ``{DAV:}``) are X delegated to self.getLivePropertyValue() X - Finally, other properties are considered *dead*, and are handled using X the associated property manager, if one is present. X """ X # Return table field as property X tableName, primKey = self.provider._splitPath(self.path) X if primKey is not None: X ns, localName = util.splitNamespace(propname) X if ns == (tableName + ":"): X conn = self.provider._initConnection() X fieldlist = self.provider._getFieldList(conn, tableName) X if localName in fieldlist: X val = self.provider._getFieldByPrimaryKey(conn, tableName, primKey, localName) X conn.close() X return val X conn.close() X # else, let default implementation return supported live and dead properties X return super(MySQLBrowserResource, self).getPropertyValue(propname) X X X def setPropertyValue(self, propname, value, dryRun=False): X """Set or remove property value. X X See DAVResource.setPropertyValue() X """ X raise DAVError(HTTP_FORBIDDEN, X errcondition=PRECONDITION_CODE_ProtectedProperty) X X#=============================================================================== X# MySQLBrowserProvider X#=============================================================================== Xclass MySQLBrowserProvider(DAVProvider): X X def __init__(self, host, user, passwd, db): X super(MySQLBrowserProvider, self).__init__() X self._host = host X self._user = user X self._passwd = passwd X self._db = db X self._count_initConnection = 0 X X X def __repr__(self): X return "%s for db '%s' on '%s' (user: '%s')'" % (self.__class__.__name__, self._db, self._host, self._user) X X X def _splitPath(self, path): X """Return (tableName, primaryKey) tuple for a request path.""" X if path.strip() in (None, "", "/"): X return (None, None) X tableName, primKey = util.saveSplit(path.strip("/"), "/", 1) X# _logger.debug("'%s' -> ('%s', '%s')" % (path, tableName, primKey)) X return (tableName, primKey) X X X def _initConnection(self): X self._count_initConnection += 1 X return MySQLdb.connect(host=self._host, X user=self._user, X passwd=self._passwd, X db=self._db) X X X def _getFieldList(self, conn, table_name): X retlist = [] X cursor = conn.cursor (MySQLdb.cursors.DictCursor) X cursor.execute ("DESCRIBE " + table_name) X result_set = cursor.fetchall () X for row in result_set: X retlist.append(row["Field"]) X cursor.close () X return retlist X X X def _isDataTypeNumeric(self, datatype): X if datatype is None: X return False X #how many MySQL datatypes does it take to change a lig... I mean, store numbers X numerictypes = ["BIGINT", X "INTT", X "MEDIUMINT", X "SMALLINT", X "TINYINT", X "BIT", X "DEC", X "DECIMAL", X "DOUBLE", X "FLOAT", X "REAL", X "DOUBLE PRECISION", X "INTEGER", X "NUMERIC"] X datatype = datatype.upper() X for numtype in numerictypes: X if datatype.startswith(numtype): X return True X return False X X X def _existsRecordByPrimaryKey(self, conn, table_name, pri_key_value): X pri_key = None X pri_field_type = None X cursor = conn.cursor (MySQLdb.cursors.DictCursor) X cursor.execute ("DESCRIBE " + table_name) X result_set = cursor.fetchall () X for row in result_set: X if row["Key"] == "PRI": X if pri_key is None: X pri_key = row["Field"] X pri_field_type = row["Type"] X else: X return False #more than one primary key - multipart key? X cursor.close () X X isNumType = self._isDataTypeNumeric(pri_field_type) X X cursor = conn.cursor (MySQLdb.cursors.DictCursor) X if isNumType: X cursor.execute("SELECT " + pri_key + " FROM " + self._db + "." + table_name + " WHERE " + pri_key + " = " + pri_key_value) X else: X cursor.execute("SELECT " + pri_key + " FROM " + self._db + "." + table_name + " WHERE " + pri_key + " = '" + pri_key_value + "'") X row = cursor.fetchone () X if row is None: X cursor.close() X return False X cursor.close() X return True X X X def _getFieldByPrimaryKey(self, conn, table_name, pri_key_value, field_name): X pri_key = None X pri_field_type = None X cursor = conn.cursor (MySQLdb.cursors.DictCursor) X cursor.execute ("DESCRIBE " + table_name) X result_set = cursor.fetchall () X for row in result_set: X if row["Key"] == "PRI": X if pri_key is None: X pri_key = row["Field"] X pri_field_type = row["Type"] X else: X return None #more than one primary key - multipart key? X cursor.close () X X isNumType = self._isDataTypeNumeric(pri_field_type) X X cursor = conn.cursor (MySQLdb.cursors.DictCursor) X if isNumType: X cursor.execute("SELECT " + field_name + " FROM " + self._db + "." + table_name + " WHERE " + pri_key + " = " + pri_key_value) X else: X cursor.execute("SELECT " + field_name + " FROM " + self._db + "." + table_name + " WHERE " + pri_key + " = '" + pri_key_value + "'") X row = cursor.fetchone () X if row is None: X cursor.close() X return None X val = str(row[field_name]) X cursor.close() X return val X X X def _getRecordByPrimaryKey(self, conn, table_name, pri_key_value): X dictRet = {} X pri_key = None X pri_field_type = None X cursor = conn.cursor (MySQLdb.cursors.DictCursor) X cursor.execute ("DESCRIBE " + table_name) X result_set = cursor.fetchall () X for row in result_set: X if row["Key"] == "PRI": X if pri_key is None: X pri_key = row["Field"] X pri_field_type = row["Type"] X else: X return None #more than one primary key - multipart key? X cursor.close () X X isNumType = self._isDataTypeNumeric(pri_field_type) X X cursor = conn.cursor (MySQLdb.cursors.DictCursor) X if isNumType: X cursor.execute("SELECT * FROM " + self._db + "." + table_name + " WHERE " + pri_key + " = " + pri_key_value) X else: X cursor.execute("SELECT * FROM " + self._db + "." + table_name + " WHERE " + pri_key + " = '" + pri_key_value + "'") X row = cursor.fetchone () X if row is None: X cursor.close() X return None X for fname in row.keys(): X dictRet[fname] = str(row[fname]) X cursor.close() X return dictRet X X X def _findPrimaryKey(self, conn, table_name): X pri_key = None X cursor = conn.cursor (MySQLdb.cursors.DictCursor) X cursor.execute ("DESCRIBE " + table_name) X result_set = cursor.fetchall () X for row in result_set: X fieldname = row["Field"] X keyvalue = row["Key"] X if keyvalue == "PRI": X if pri_key is None: X pri_key = fieldname X else: X return None #more than one primary key - multipart key? X cursor.close () X return pri_key X X X def _listFields(self, conn, table_name, field_name): X retlist = [] X cursor = conn.cursor (MySQLdb.cursors.DictCursor) X cursor.execute("SELECT " + field_name + " FROM " + self._db + "." + table_name) X result_set = cursor.fetchall () X for row in result_set: X retlist.append(str(row[field_name])) X cursor.close() X return retlist X X X def _listTables(self, conn): X retlist = [] X cursor = conn.cursor () X cursor.execute ("SHOW TABLES") X result_set = cursor.fetchall () X for row in result_set: X retlist.append("%s" % (row[0])) X cursor.close () X return retlist X X X def getResourceInst(self, path, environ): X """Return info dictionary for path. X X See getResourceInst() X """ X # TODO: calling exists() makes directory browsing VERY slow. X # At least compared to PyFileServer, which simply used string X # functions to get displayType and displayTypeComment X self._count_getResourceInst += 1 X if not self.exists(path, environ): X return None X _tableName, primKey = self._splitPath(path) X isCollection = primKey is None X return MySQLBrowserResource(self, path, isCollection, environ) X X X def exists(self, path, environ): X tableName, primKey = self._splitPath(path) X if tableName is None: X return True X X try: X conn = None X conn = self._initConnection() X # Check table existence: X tbllist = self._listTables(conn) X if tableName not in tbllist: X return False X # Check table key existence: X if primKey and primKey != "_ENTIRE_CONTENTS": X return self._existsRecordByPrimaryKey(conn, tableName, primKey) X return True X finally: X if conn: X conn.close() X X X def isCollection(self, path, environ): X _tableName, primKey = self._splitPath(path) X return self.exists(path, environ) and primKey is None c19686f02fed00371c35f3f18dabdc46 echo c - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server mkdir -p py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server > /dev/null 2>&1 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/__init__.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/__init__.py << 'a89fed01928b319f1776f92d50c72425' X#__all__ = [] a89fed01928b319f1776f92d50c72425 echo c - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/cherrypy_wsgiserver mkdir -p py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/cherrypy_wsgiserver > /dev/null 2>&1 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/cherrypy_wsgiserver/__init__.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/cherrypy_wsgiserver/__init__.py << '6e9c2e3249a8dbcca82cb68cb765118f' X"""A high-speed, production ready, thread pooled, generic WSGI server. X XSimplest example on how to use this module directly X(without using CherryPy's application machinery): X X from cherrypy import wsgiserver X X def my_crazy_app(environ, start_response): X status = '200 OK' X response_headers = [('Content-type','text/plain')] X start_response(status, response_headers) X return ['Hello world!\n'] X X server = wsgiserver.CherryPyWSGIServer( X ('0.0.0.0', 8070), my_crazy_app, X server_name='www.cherrypy.example') X XThe CherryPy WSGI server can serve as many WSGI applications Xas you want in one instance by using a WSGIPathInfoDispatcher: X X d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app}) X server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d) X XWant SSL support? Just set these attributes: X X server.ssl_certificate = X server.ssl_private_key = X X if __name__ == '__main__': X try: X server.start() X except KeyboardInterrupt: X server.stop() X XThis won't call the CherryPy engine (application side) at all, only the XWSGI server, which is independant from the rest of CherryPy. Don't Xlet the name "CherryPyWSGIServer" throw you; the name merely reflects Xits origin, not its coupling. X XFor those of you wanting to understand internals of this module, here's the Xbasic call flow. The server's listening thread runs a very tight loop, Xsticking incoming connections onto a Queue: X X server = CherryPyWSGIServer(...) X server.start() X while True: X tick() X # This blocks until a request comes in: X child = socket.accept() X conn = HTTPConnection(child, ...) X server.requests.put(conn) X XWorker threads are kept in a pool and poll the Queue, popping off and then Xhandling each connection in turn. Each connection can consist of an arbitrary Xnumber of requests and their responses, so we run a nested loop: X X while True: X conn = server.requests.get() X conn.communicate() X -> while True: X req = HTTPRequest(...) X req.parse_request() X -> # Read the Request-Line, e.g. "GET /page HTTP/1.1" X req.rfile.readline() X req.read_headers() X req.respond() X -> response = wsgi_app(...) X try: X for chunk in response: X if chunk: X req.write(chunk) X finally: X if hasattr(response, "close"): X response.close() X if req.close_connection: X return X""" X X Ximport base64 Ximport os Ximport Queue Ximport re Xquoted_slash = re.compile("(?i)%2F") Ximport rfc822 Ximport socket Xtry: X import cStringIO as StringIO Xexcept ImportError: X import StringIO X X_fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring) X Ximport sys Ximport threading Ximport time Ximport traceback Xfrom urllib import unquote Xfrom urlparse import urlparse Ximport warnings X Xtry: X from OpenSSL import SSL X from OpenSSL import crypto Xexcept ImportError: X SSL = None X Ximport errno X Xdef plat_specific_errors(*errnames): X """Return error numbers for all errors in errnames on this platform. X X The 'errno' module contains different global constants depending on X the specific platform (OS). This function will return the list of X numeric values for a given list of potential names. X """ X errno_names = dir(errno) X nums = [getattr(errno, k) for k in errnames if k in errno_names] X # de-dupe the list X return dict.fromkeys(nums).keys() X Xsocket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR") X Xsocket_errors_to_ignore = plat_specific_errors( X "EPIPE", X "EBADF", "WSAEBADF", X "ENOTSOCK", "WSAENOTSOCK", X "ETIMEDOUT", "WSAETIMEDOUT", X "ECONNREFUSED", "WSAECONNREFUSED", X "ECONNRESET", "WSAECONNRESET", X "ECONNABORTED", "WSAECONNABORTED", X "ENETRESET", "WSAENETRESET", X "EHOSTDOWN", "EHOSTUNREACH", X ) Xsocket_errors_to_ignore.append("timed out") X Xsocket_errors_nonblocking = plat_specific_errors( X 'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK') X Xcomma_separated_headers = ['ACCEPT', 'ACCEPT-CHARSET', 'ACCEPT-ENCODING', X 'ACCEPT-LANGUAGE', 'ACCEPT-RANGES', 'ALLOW', 'CACHE-CONTROL', X 'CONNECTION', 'CONTENT-ENCODING', 'CONTENT-LANGUAGE', 'EXPECT', X 'IF-MATCH', 'IF-NONE-MATCH', 'PRAGMA', 'PROXY-AUTHENTICATE', 'TE', X 'TRAILER', 'TRANSFER-ENCODING', 'UPGRADE', 'VARY', 'VIA', 'WARNING', X 'WWW-AUTHENTICATE'] X X Xclass WSGIPathInfoDispatcher(object): X """A WSGI dispatcher for dispatch based on the PATH_INFO. X X apps: a dict or list of (path_prefix, app) pairs. X """ X X def __init__(self, apps): X try: X apps = apps.items() X except AttributeError: X pass X X # Sort the apps by len(path), descending X apps.sort() X apps.reverse() X X # The path_prefix strings must start, but not end, with a slash. X # Use "" instead of "/". X self.apps = [(p.rstrip("/"), a) for p, a in apps] X X def __call__(self, environ, start_response): X path = environ["PATH_INFO"] or "/" X for p, app in self.apps: X # The apps list should be sorted by length, descending. X if path.startswith(p + "/") or path == p: X environ = environ.copy() X environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p X environ["PATH_INFO"] = path[len(p):] X return app(environ, start_response) X X start_response('404 Not Found', [('Content-Type', 'text/plain'), X ('Content-Length', '0')]) X return [''] X X Xclass MaxSizeExceeded(Exception): X pass X Xclass SizeCheckWrapper(object): X """Wraps a file-like object, raising MaxSizeExceeded if too large.""" X X def __init__(self, rfile, maxlen): X self.rfile = rfile X self.maxlen = maxlen X self.bytes_read = 0 X X def _check_length(self): X if self.maxlen and self.bytes_read > self.maxlen: X raise MaxSizeExceeded() X X def read(self, size=None): X data = self.rfile.read(size) X self.bytes_read += len(data) X self._check_length() X return data X X def readline(self, size=None): X if size is not None: X data = self.rfile.readline(size) X self.bytes_read += len(data) X self._check_length() X return data X X # User didn't specify a size ... X # We read the line in chunks to make sure it's not a 100MB line ! X res = [] X while True: X data = self.rfile.readline(256) X self.bytes_read += len(data) X self._check_length() X res.append(data) X # See http://www.cherrypy.org/ticket/421 X if len(data) < 256 or data[-1:] == "\n": X return ''.join(res) X X def readlines(self, sizehint=0): X # Shamelessly stolen from StringIO X total = 0 X lines = [] X line = self.readline() X while line: X lines.append(line) X total += len(line) X if 0 < sizehint <= total: X break X line = self.readline() X return lines X X def close(self): X self.rfile.close() X X def __iter__(self): X return self X X def next(self): X data = self.rfile.next() X self.bytes_read += len(data) X self._check_length() X return data X X Xclass HTTPRequest(object): X """An HTTP Request (and response). X X A single HTTP connection may consist of multiple request/response pairs. X X send: the 'send' method from the connection's socket object. X wsgi_app: the WSGI application to call. X environ: a partial WSGI environ (server and connection entries). X The caller MUST set the following entries: X * All wsgi.* entries, including .input X * SERVER_NAME and SERVER_PORT X * Any SSL_* entries X * Any custom entries like REMOTE_ADDR and REMOTE_PORT X * SERVER_SOFTWARE: the value to write in the "Server" response header. X * ACTUAL_SERVER_PROTOCOL: the value to write in the Status-Line of X the response. From RFC 2145: "An HTTP server SHOULD send a X response version equal to the highest version for which the X server is at least conditionally compliant, and whose major X version is less than or equal to the one received in the X request. An HTTP server MUST NOT send a version for which X it is not at least conditionally compliant." X X outheaders: a list of header tuples to write in the response. X ready: when True, the request has been parsed and is ready to begin X generating the response. When False, signals the calling Connection X that the response should not be generated and the connection should X close. X close_connection: signals the calling Connection that the request X should close. This does not imply an error! The client and/or X server may each request that the connection be closed. X chunked_write: if True, output will be encoded with the "chunked" X transfer-coding. This value is set automatically inside X send_headers. X """ X X max_request_header_size = 0 X max_request_body_size = 0 X X def __init__(self, wfile, environ, wsgi_app): X self.rfile = environ['wsgi.input'] X self.wfile = wfile X self.environ = environ.copy() X self.wsgi_app = wsgi_app X X self.ready = False X self.started_response = False X self.status = "" X self.outheaders = [] X self.sent_headers = False X self.close_connection = False X self.chunked_write = False X X def parse_request(self): X """Parse the next HTTP request start-line and message-headers.""" X self.rfile.maxlen = self.max_request_header_size X self.rfile.bytes_read = 0 X X try: X self._parse_request() X except MaxSizeExceeded: X self.simple_response("413 Request Entity Too Large") X return X X def _parse_request(self): X # HTTP/1.1 connections are persistent by default. If a client X # requests a page, then idles (leaves the connection open), X # then rfile.readline() will raise socket.error("timed out"). X # Note that it does this based on the value given to settimeout(), X # and doesn't need the client to request or acknowledge the close X # (although your TCP stack might suffer for it: cf Apache's history X # with FIN_WAIT_2). X request_line = self.rfile.readline() X if not request_line: X # Force self.ready = False so the connection will close. X self.ready = False X return X X if request_line == "\r\n": X # RFC 2616 sec 4.1: "...if the server is reading the protocol X # stream at the beginning of a message and receives a CRLF X # first, it should ignore the CRLF." X # But only ignore one leading line! else we enable a DoS. X request_line = self.rfile.readline() X if not request_line: X self.ready = False X return X X environ = self.environ X X try: X method, path, req_protocol = request_line.strip().split(" ", 2) X except ValueError: X self.simple_response(400, "Malformed Request-Line") X return X X environ["REQUEST_METHOD"] = method X X # path may be an abs_path (including "http://host.domain.tld"); X scheme, location, path, params, qs, frag = urlparse(path) X X if frag: X self.simple_response("400 Bad Request", X "Illegal #fragment in Request-URI.") X return X X if scheme: X environ["wsgi.url_scheme"] = scheme X if params: X path = path + ";" + params X X environ["SCRIPT_NAME"] = "" X X # Unquote the path+params (e.g. "/this%20path" -> "this path"). X # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2 X # X # But note that "...a URI must be separated into its components X # before the escaped characters within those components can be X # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2 X atoms = [unquote(x) for x in quoted_slash.split(path)] X path = "%2F".join(atoms) X environ["PATH_INFO"] = path X X # Note that, like wsgiref and most other WSGI servers, X # we unquote the path but not the query string. X environ["QUERY_STRING"] = qs X X # Compare request and server HTTP protocol versions, in case our X # server does not support the requested protocol. Limit our output X # to min(req, server). We want the following output: X # request server actual written supported response X # protocol protocol response protocol feature set X # a 1.0 1.0 1.0 1.0 X # b 1.0 1.1 1.1 1.0 X # c 1.1 1.0 1.0 1.0 X # d 1.1 1.1 1.1 1.1 X # Notice that, in (b), the response will be "HTTP/1.1" even though X # the client only understands 1.0. RFC 2616 10.5.6 says we should X # only return 505 if the _major_ version is different. X rp = int(req_protocol[5]), int(req_protocol[7]) X server_protocol = environ["ACTUAL_SERVER_PROTOCOL"] X sp = int(server_protocol[5]), int(server_protocol[7]) X if sp[0] != rp[0]: X self.simple_response("505 HTTP Version Not Supported") X return X # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol. X environ["SERVER_PROTOCOL"] = req_protocol X self.response_protocol = "HTTP/%s.%s" % min(rp, sp) X X # If the Request-URI was an absoluteURI, use its location atom. X if location: X environ["SERVER_NAME"] = location X X # then all the http headers X try: X self.read_headers() X except ValueError, ex: X self.simple_response("400 Bad Request", repr(ex.args)) X return X X mrbs = self.max_request_body_size X if mrbs and int(environ.get("CONTENT_LENGTH", 0)) > mrbs: X self.simple_response("413 Request Entity Too Large") X return X X # Persistent connection support X if self.response_protocol == "HTTP/1.1": X # Both server and client are HTTP/1.1 X if environ.get("HTTP_CONNECTION", "") == "close": X self.close_connection = True X else: X # Either the server or client (or both) are HTTP/1.0 X if environ.get("HTTP_CONNECTION", "") != "Keep-Alive": X self.close_connection = True X X # Transfer-Encoding support X te = None X if self.response_protocol == "HTTP/1.1": X te = environ.get("HTTP_TRANSFER_ENCODING") X if te: X te = [x.strip().lower() for x in te.split(",") if x.strip()] X X self.chunked_read = False X X if te: X for enc in te: X if enc == "chunked": X self.chunked_read = True X else: X # Note that, even if we see "chunked", we must reject X # if there is an extension we don't recognize. X self.simple_response("501 Unimplemented") X self.close_connection = True X return X X # From PEP 333: X # "Servers and gateways that implement HTTP 1.1 must provide X # transparent support for HTTP 1.1's "expect/continue" mechanism. X # This may be done in any of several ways: X # 1. Respond to requests containing an Expect: 100-continue request X # with an immediate "100 Continue" response, and proceed normally. X # 2. Proceed with the request normally, but provide the application X # with a wsgi.input stream that will send the "100 Continue" X # response if/when the application first attempts to read from X # the input stream. The read request must then remain blocked X # until the client responds. X # 3. Wait until the client decides that the server does not support X # expect/continue, and sends the request body on its own. X # (This is suboptimal, and is not recommended.) X # X # We used to do 3, but are now doing 1. Maybe we'll do 2 someday, X # but it seems like it would be a big slowdown for such a rare case. X if environ.get("HTTP_EXPECT", "") == "100-continue": X self.simple_response(100) X X self.ready = True X X def read_headers(self): X """Read header lines from the incoming stream.""" X environ = self.environ X X while True: X line = self.rfile.readline() X if not line: X # No more data--illegal end of headers X raise ValueError("Illegal end of headers.") X X if line == '\r\n': X # Normal end of headers X break X X if line[0] in ' \t': X # It's a continuation line. X v = line.strip() X else: X k, v = line.split(":", 1) X k, v = k.strip().upper(), v.strip() X envname = "HTTP_" + k.replace("-", "_") X X if k in comma_separated_headers: X existing = environ.get(envname) X if existing: X v = ", ".join((existing, v)) X environ[envname] = v X X ct = environ.pop("HTTP_CONTENT_TYPE", None) X if ct is not None: X environ["CONTENT_TYPE"] = ct X cl = environ.pop("HTTP_CONTENT_LENGTH", None) X if cl is not None: X environ["CONTENT_LENGTH"] = cl X X def decode_chunked(self): X """Decode the 'chunked' transfer coding.""" X cl = 0 X data = StringIO.StringIO() X while True: X line = self.rfile.readline().strip().split(";", 1) X chunk_size = int(line.pop(0), 16) X if chunk_size <= 0: X break X## if line: chunk_extension = line[0] X cl += chunk_size X data.write(self.rfile.read(chunk_size)) X crlf = self.rfile.read(2) X if crlf != "\r\n": X self.simple_response("400 Bad Request", X "Bad chunked transfer coding " X "(expected '\\r\\n', got %r)" % crlf) X return X X # Grab any trailer headers X self.read_headers() X X data.seek(0) X self.environ["wsgi.input"] = data X self.environ["CONTENT_LENGTH"] = str(cl) or "" X return True X X def respond(self): X """Call the appropriate WSGI app and write its iterable output.""" X # Set rfile.maxlen to ensure we don't read past Content-Length. X # This will also be used to read the entire request body if errors X # are raised before the app can read the body. X if self.chunked_read: X # If chunked, Content-Length will be 0. X self.rfile.maxlen = self.max_request_body_size X else: X cl = int(self.environ.get("CONTENT_LENGTH", 0)) X if self.max_request_body_size: X self.rfile.maxlen = min(cl, self.max_request_body_size) X else: X self.rfile.maxlen = cl X self.rfile.bytes_read = 0 X X try: X self._respond() X except MaxSizeExceeded: X if not self.sent_headers: X self.simple_response("413 Request Entity Too Large") X return X X def _respond(self): X if self.chunked_read: X if not self.decode_chunked(): X self.close_connection = True X return X X response = self.wsgi_app(self.environ, self.start_response) X try: X for chunk in response: X # "The start_response callable must not actually transmit X # the response headers. Instead, it must store them for the X # server or gateway to transmit only after the first X # iteration of the application return value that yields X # a NON-EMPTY string, or upon the application's first X # invocation of the write() callable." (PEP 333) X if chunk: X self.write(chunk) X finally: X if hasattr(response, "close"): X response.close() X X if (self.ready and not self.sent_headers): X self.sent_headers = True X self.send_headers() X if self.chunked_write: X self.wfile.sendall("0\r\n\r\n") X X def simple_response(self, status, msg=""): X """Write a simple response back to the client.""" X status = str(status) X buf = ["%s %s\r\n" % (self.environ['ACTUAL_SERVER_PROTOCOL'], status), X "Content-Length: %s\r\n" % len(msg), X "Content-Type: text/plain\r\n"] X X if status[:3] == "413" and self.response_protocol == 'HTTP/1.1': X # Request Entity Too Large X self.close_connection = True X buf.append("Connection: close\r\n") X X buf.append("\r\n") X if msg: X buf.append(msg) X X try: X self.wfile.sendall("".join(buf)) X except socket.error, x: X if x.args[0] not in socket_errors_to_ignore: X raise X X def start_response(self, status, headers, exc_info = None): X """WSGI callable to begin the HTTP response.""" X # "The application may call start_response more than once, X # if and only if the exc_info argument is provided." X if self.started_response and not exc_info: X raise AssertionError("WSGI start_response called a second " X "time with no exc_info.") X X # "if exc_info is provided, and the HTTP headers have already been X # sent, start_response must raise an error, and should raise the X # exc_info tuple." X if self.sent_headers: X try: X raise exc_info[0], exc_info[1], exc_info[2] X finally: X exc_info = None X X self.started_response = True X self.status = status X self.outheaders.extend(headers) X return self.write X X def write(self, chunk): X """WSGI callable to write unbuffered data to the client. X X This method is also used internally by start_response (to write X data from the iterable returned by the WSGI application). X """ X if not self.started_response: X raise AssertionError("WSGI write called before start_response.") X X if not self.sent_headers: X self.sent_headers = True X self.send_headers() X X if self.chunked_write and chunk: X buf = [hex(len(chunk))[2:], "\r\n", chunk, "\r\n"] X self.wfile.sendall("".join(buf)) X else: X self.wfile.sendall(chunk) X X def send_headers(self): X """Assert, process, and send the HTTP response message-headers.""" X hkeys = [key.lower() for key, value in self.outheaders] X status = int(self.status[:3]) X X if status == 413: X # Request Entity Too Large. Close conn to avoid garbage. X self.close_connection = True X elif "content-length" not in hkeys: X # "All 1xx (informational), 204 (no content), X # and 304 (not modified) responses MUST NOT X # include a message-body." So no point chunking. X if status < 200 or status in (204, 205, 304): X pass X else: X if (self.response_protocol == 'HTTP/1.1' X and self.environ["REQUEST_METHOD"] != 'HEAD'): X # Use the chunked transfer-coding X self.chunked_write = True X self.outheaders.append(("Transfer-Encoding", "chunked")) X else: X # Closing the conn is the only way to determine len. X self.close_connection = True X X if "connection" not in hkeys: X if self.response_protocol == 'HTTP/1.1': X # Both server and client are HTTP/1.1 or better X if self.close_connection: X self.outheaders.append(("Connection", "close")) X else: X # Server and/or client are HTTP/1.0 X if not self.close_connection: X self.outheaders.append(("Connection", "Keep-Alive")) X X if (not self.close_connection) and (not self.chunked_read): X # Read any remaining request body data on the socket. X # "If an origin server receives a request that does not include an X # Expect request-header field with the "100-continue" expectation, X # the request includes a request body, and the server responds X # with a final status code before reading the entire request body X # from the transport connection, then the server SHOULD NOT close X # the transport connection until it has read the entire request, X # or until the client closes the connection. Otherwise, the client X # might not reliably receive the response message. However, this X # requirement is not be construed as preventing a server from X # defending itself against denial-of-service attacks, or from X # badly broken client implementations." X size = self.rfile.maxlen - self.rfile.bytes_read X if size > 0: X self.rfile.read(size) X X if "date" not in hkeys: X self.outheaders.append(("Date", rfc822.formatdate())) X X if "server" not in hkeys: X self.outheaders.append(("Server", self.environ['SERVER_SOFTWARE'])) X X buf = [self.environ['ACTUAL_SERVER_PROTOCOL'], " ", self.status, "\r\n"] X try: X buf += [k + ": " + v + "\r\n" for k, v in self.outheaders] X except TypeError: X if not isinstance(k, str): X raise TypeError("WSGI response header key %r is not a string.") X if not isinstance(v, str): X raise TypeError("WSGI response header value %r is not a string.") X else: X raise X buf.append("\r\n") X self.wfile.sendall("".join(buf)) X X Xclass NoSSLError(Exception): X """Exception raised when a client speaks HTTP to an HTTPS socket.""" X pass X X Xclass FatalSSLAlert(Exception): X """Exception raised when the SSL implementation signals a fatal alert.""" X pass X X Xif not _fileobject_uses_str_type: X class CP_fileobject(socket._fileobject): X """Faux file object attached to a socket object.""" X X def sendall(self, data): X """Sendall for non-blocking sockets.""" X while data: X try: X bytes_sent = self.send(data) X data = data[bytes_sent:] X except socket.error, e: X if e.args[0] not in socket_errors_nonblocking: X raise X X def send(self, data): X return self._sock.send(data) X X def flush(self): X if self._wbuf: X buffer = "".join(self._wbuf) X self._wbuf = [] X self.sendall(buffer) X X def recv(self, size): X while True: X try: X return self._sock.recv(size) X except socket.error, e: X if (e.args[0] not in socket_errors_nonblocking X and e.args[0] not in socket_error_eintr): X raise X X def read(self, size=-1): X # Use max, disallow tiny reads in a loop as they are very inefficient. X # We never leave read() with any leftover data from a new recv() call X # in our internal buffer. X rbufsize = max(self._rbufsize, self.default_bufsize) X # Our use of StringIO rather than lists of string objects returned by X # recv() minimizes memory usage and fragmentation that occurs when X # rbufsize is large compared to the typical return value of recv(). X buf = self._rbuf X buf.seek(0, 2) # seek end X if size < 0: X # Read until EOF X self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf. X while True: X data = self.recv(rbufsize) X if not data: X break X buf.write(data) X return buf.getvalue() X else: X # Read until size bytes or EOF seen, whichever comes first X buf_len = buf.tell() X if buf_len >= size: X # Already have size bytes in our buffer? Extract and return. X buf.seek(0) X rv = buf.read(size) X self._rbuf = StringIO.StringIO() X self._rbuf.write(buf.read()) X return rv X X self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf. X while True: X left = size - buf_len X # recv() will malloc the amount of memory given as its X # parameter even though it often returns much less data X # than that. The returned data string is short lived X # as we copy it into a StringIO and free it. This avoids X # fragmentation issues on many platforms. X data = self.recv(left) X if not data: X break X n = len(data) X if n == size and not buf_len: X # Shortcut. Avoid buffer data copies when: X # - We have no data in our buffer. X # AND X # - Our call to recv returned exactly the X # number of bytes we were asked to read. X return data X if n == left: X buf.write(data) X del data # explicit free X break X assert n <= left, "recv(%d) returned %d bytes" % (left, n) X buf.write(data) X buf_len += n X del data # explicit free X #assert buf_len == buf.tell() X return buf.getvalue() X X def readline(self, size=-1): X buf = self._rbuf X buf.seek(0, 2) # seek end X if buf.tell() > 0: X # check if we already have it in our buffer X buf.seek(0) X bline = buf.readline(size) X if bline.endswith('\n') or len(bline) == size: X self._rbuf = StringIO.StringIO() X self._rbuf.write(buf.read()) X return bline X del bline X if size < 0: X # Read until \n or EOF, whichever comes first X if self._rbufsize <= 1: X # Speed up unbuffered case X buf.seek(0) X buffers = [buf.read()] X self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf. X data = None X recv = self.recv X while data != "\n": X data = recv(1) X if not data: X break X buffers.append(data) X return "".join(buffers) X X buf.seek(0, 2) # seek end X self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf. X while True: X data = self.recv(self._rbufsize) X if not data: X break X nl = data.find('\n') X if nl >= 0: X nl += 1 X buf.write(data[:nl]) X self._rbuf.write(data[nl:]) X del data X break X buf.write(data) X return buf.getvalue() X else: X # Read until size bytes or \n or EOF seen, whichever comes first X buf.seek(0, 2) # seek end X buf_len = buf.tell() X if buf_len >= size: X buf.seek(0) X rv = buf.read(size) X self._rbuf = StringIO.StringIO() X self._rbuf.write(buf.read()) X return rv X self._rbuf = StringIO.StringIO() # reset _rbuf. we consume it via buf. X while True: X data = self.recv(self._rbufsize) X if not data: X break X left = size - buf_len X # did we just receive a newline? X nl = data.find('\n', 0, left) X if nl >= 0: X nl += 1 X # save the excess data to _rbuf X self._rbuf.write(data[nl:]) X if buf_len: X buf.write(data[:nl]) X break X else: X # Shortcut. Avoid data copy through buf when returning X # a substring of our first recv(). X return data[:nl] X n = len(data) X if n == size and not buf_len: X # Shortcut. Avoid data copy through buf when X # returning exactly all of our first recv(). X return data X if n >= left: X buf.write(data[:left]) X self._rbuf.write(data[left:]) X break X buf.write(data) X buf_len += n X #assert buf_len == buf.tell() X return buf.getvalue() X Xelse: X class CP_fileobject(socket._fileobject): X """Faux file object attached to a socket object.""" X X def sendall(self, data): X """Sendall for non-blocking sockets.""" X while data: X try: X bytes_sent = self.send(data) X data = data[bytes_sent:] X except socket.error, e: X if e.args[0] not in socket_errors_nonblocking: X raise X X def send(self, data): X return self._sock.send(data) X X def flush(self): X if self._wbuf: X buffer = "".join(self._wbuf) X self._wbuf = [] X self.sendall(buffer) X X def recv(self, size): X while True: X try: X return self._sock.recv(size) X except socket.error, e: X if (e.args[0] not in socket_errors_nonblocking X and e.args[0] not in socket_error_eintr): X raise X X def read(self, size=-1): X if size < 0: X # Read until EOF X buffers = [self._rbuf] X self._rbuf = "" X if self._rbufsize <= 1: X recv_size = self.default_bufsize X else: X recv_size = self._rbufsize X X while True: X data = self.recv(recv_size) X if not data: X break X buffers.append(data) X return "".join(buffers) X else: X # Read until size bytes or EOF seen, whichever comes first X data = self._rbuf X buf_len = len(data) X if buf_len >= size: X self._rbuf = data[size:] X return data[:size] X buffers = [] X if data: X buffers.append(data) X self._rbuf = "" X while True: X left = size - buf_len X recv_size = max(self._rbufsize, left) X data = self.recv(recv_size) X if not data: X break X buffers.append(data) X n = len(data) X if n >= left: X self._rbuf = data[left:] X buffers[-1] = data[:left] X break X buf_len += n X return "".join(buffers) X X def readline(self, size=-1): X data = self._rbuf X if size < 0: X # Read until \n or EOF, whichever comes first X if self._rbufsize <= 1: X # Speed up unbuffered case X assert data == "" X buffers = [] X while data != "\n": X data = self.recv(1) X if not data: X break X buffers.append(data) X return "".join(buffers) X nl = data.find('\n') X if nl >= 0: X nl += 1 X self._rbuf = data[nl:] X return data[:nl] X buffers = [] X if data: X buffers.append(data) X self._rbuf = "" X while True: X data = self.recv(self._rbufsize) X if not data: X break X buffers.append(data) X nl = data.find('\n') X if nl >= 0: X nl += 1 X self._rbuf = data[nl:] X buffers[-1] = data[:nl] X break X return "".join(buffers) X else: X # Read until size bytes or \n or EOF seen, whichever comes first X nl = data.find('\n', 0, size) X if nl >= 0: X nl += 1 X self._rbuf = data[nl:] X return data[:nl] X buf_len = len(data) X if buf_len >= size: X self._rbuf = data[size:] X return data[:size] X buffers = [] X if data: X buffers.append(data) X self._rbuf = "" X while True: X data = self.recv(self._rbufsize) X if not data: X break X buffers.append(data) X left = size - buf_len X nl = data.find('\n', 0, left) X if nl >= 0: X nl += 1 X self._rbuf = data[nl:] X buffers[-1] = data[:nl] X break X n = len(data) X if n >= left: X self._rbuf = data[left:] X buffers[-1] = data[:left] X break X buf_len += n X return "".join(buffers) X X Xclass SSL_fileobject(CP_fileobject): X """SSL file object attached to a socket object.""" X X ssl_timeout = 3 X ssl_retry = .01 X X def _safe_call(self, is_reader, call, *args, **kwargs): X """Wrap the given call with SSL error-trapping. X X is_reader: if False EOF errors will be raised. If True, EOF errors X will return "" (to emulate normal sockets). X """ X start = time.time() X while True: X try: X return call(*args, **kwargs) X except SSL.WantReadError: X # Sleep and try again. This is dangerous, because it means X # the rest of the stack has no way of differentiating X # between a "new handshake" error and "client dropped". X # Note this isn't an endless loop: there's a timeout below. X time.sleep(self.ssl_retry) X except SSL.WantWriteError: X time.sleep(self.ssl_retry) X except SSL.SysCallError, e: X if is_reader and e.args == (-1, 'Unexpected EOF'): X return "" X X errnum = e.args[0] X if is_reader and errnum in socket_errors_to_ignore: X return "" X raise socket.error(errnum) X except SSL.Error, e: X if is_reader and e.args == (-1, 'Unexpected EOF'): X return "" X X thirdarg = None X try: X thirdarg = e.args[0][0][2] X except IndexError: X pass X X if thirdarg == 'http request': X # The client is talking HTTP to an HTTPS server. X raise NoSSLError() X raise FatalSSLAlert(*e.args) X except: X raise X X if time.time() - start > self.ssl_timeout: X raise socket.timeout("timed out") X X def recv(self, *args, **kwargs): X buf = [] X r = super(SSL_fileobject, self).recv X while True: X data = self._safe_call(True, r, *args, **kwargs) X buf.append(data) X p = self._sock.pending() X if not p: X return "".join(buf) X X def sendall(self, *args, **kwargs): X return self._safe_call(False, super(SSL_fileobject, self).sendall, *args, **kwargs) X X def send(self, *args, **kwargs): X return self._safe_call(False, super(SSL_fileobject, self).send, *args, **kwargs) X X Xclass HTTPConnection(object): X """An HTTP connection (active socket). X X socket: the raw socket object (usually TCP) for this connection. X wsgi_app: the WSGI application for this server/connection. X environ: a WSGI environ template. This will be copied for each request. X X rfile: a fileobject for reading from the socket. X send: a function for writing (+ flush) to the socket. X """ X X rbufsize = -1 X RequestHandlerClass = HTTPRequest X environ = {"wsgi.version": (1, 0), X "wsgi.url_scheme": "http", X "wsgi.multithread": True, X "wsgi.multiprocess": False, X "wsgi.run_once": False, X "wsgi.errors": sys.stderr, X } X X def __init__(self, sock, wsgi_app, environ): X self.socket = sock X self.wsgi_app = wsgi_app X X # Copy the class environ into self. X self.environ = self.environ.copy() X self.environ.update(environ) X X if SSL and isinstance(sock, SSL.ConnectionType): X timeout = sock.gettimeout() X self.rfile = SSL_fileobject(sock, "rb", self.rbufsize) X self.rfile.ssl_timeout = timeout X self.wfile = SSL_fileobject(sock, "wb", -1) X self.wfile.ssl_timeout = timeout X else: X self.rfile = CP_fileobject(sock, "rb", self.rbufsize) X self.wfile = CP_fileobject(sock, "wb", -1) X X # Wrap wsgi.input but not HTTPConnection.rfile itself. X # We're also not setting maxlen yet; we'll do that separately X # for headers and body for each iteration of self.communicate X # (if maxlen is 0 the wrapper doesn't check length). X self.environ["wsgi.input"] = SizeCheckWrapper(self.rfile, 0) X X def communicate(self): X """Read each request and respond appropriately.""" X try: X while True: X # (re)set req to None so that if something goes wrong in X # the RequestHandlerClass constructor, the error doesn't X # get written to the previous request. X req = None X req = self.RequestHandlerClass(self.wfile, self.environ, X self.wsgi_app) X X # This order of operations should guarantee correct pipelining. X req.parse_request() X if not req.ready: X return X X req.respond() X if req.close_connection: X return X X except socket.error, e: X errnum = e.args[0] X if errnum == 'timed out': X if req and not req.sent_headers: X req.simple_response("408 Request Timeout") X elif errnum not in socket_errors_to_ignore: X if req and not req.sent_headers: X req.simple_response("500 Internal Server Error", X format_exc()) X return X except (KeyboardInterrupt, SystemExit): X raise X except FatalSSLAlert, e: X # Close the connection. X return X except NoSSLError: X if req and not req.sent_headers: X # Unwrap our wfile X req.wfile = CP_fileobject(self.socket._sock, "wb", -1) X req.simple_response("400 Bad Request", X "The client sent a plain HTTP request, but " X "this server only speaks HTTPS on this port.") X self.linger = True X except Exception, e: X if req and not req.sent_headers: X req.simple_response("500 Internal Server Error", format_exc()) X X linger = False X X def close(self): X """Close the socket underlying this connection.""" X self.rfile.close() X X if not self.linger: X # Python's socket module does NOT call close on the kernel socket X # when you call socket.close(). We do so manually here because we X # want this server to send a FIN TCP segment immediately. Note this X # must be called *before* calling socket.close(), because the latter X # drops its reference to the kernel socket. X self.socket._sock.close() X self.socket.close() X else: X # On the other hand, sometimes we want to hang around for a bit X # to make sure the client has a chance to read our entire X # response. Skipping the close() calls here delays the FIN X # packet until the socket object is garbage-collected later. X # Someday, perhaps, we'll do the full lingering_close that X # Apache does, but not today. X pass X X Xdef format_exc(limit=None): X """Like print_exc() but return a string. Backport for Python 2.3.""" X try: X etype, value, tb = sys.exc_info() X return ''.join(traceback.format_exception(etype, value, tb, limit)) X finally: X etype = value = tb = None X X X_SHUTDOWNREQUEST = None X Xclass WorkerThread(threading.Thread): X """Thread which continuously polls a Queue for Connection objects. X X server: the HTTP Server which spawned this thread, and which owns the X Queue and is placing active connections into it. X ready: a simple flag for the calling server to know when this thread X has begun polling the Queue. X X Due to the timing issues of polling a Queue, a WorkerThread does not X check its own 'ready' flag after it has started. To stop the thread, X it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue X (one for each running WorkerThread). X """ X X conn = None X X def __init__(self, server): X self.ready = False X self.server = server X threading.Thread.__init__(self) X X def run(self): X try: X self.ready = True X while True: X conn = self.server.requests.get() X if conn is _SHUTDOWNREQUEST: X return X X self.conn = conn X try: X conn.communicate() X finally: X conn.close() X self.conn = None X except (KeyboardInterrupt, SystemExit), exc: X self.server.interrupt = exc X X Xclass ThreadPool(object): X """A Request Queue for the CherryPyWSGIServer which pools threads. X X ThreadPool objects must provide min, get(), put(obj), start() X and stop(timeout) attributes. X """ X X def __init__(self, server, min=10, max=-1): X self.server = server X self.min = min X self.max = max X self._threads = [] X self._queue = Queue.Queue() X self.get = self._queue.get X X def start(self): X """Start the pool of threads.""" X for i in xrange(self.min): X self._threads.append(WorkerThread(self.server)) X for worker in self._threads: X worker.setName("CP WSGIServer " + worker.getName()) X worker.start() X for worker in self._threads: X while not worker.ready: X time.sleep(.1) X X def _get_idle(self): X """Number of worker threads which are idle. Read-only.""" X return len([t for t in self._threads if t.conn is None]) X idle = property(_get_idle, doc=_get_idle.__doc__) X X def put(self, obj): X self._queue.put(obj) X if obj is _SHUTDOWNREQUEST: X return X X def grow(self, amount): X """Spawn new worker threads (not above self.max).""" X for i in xrange(amount): X if self.max > 0 and len(self._threads) >= self.max: X break X worker = WorkerThread(self.server) X worker.setName("CP WSGIServer " + worker.getName()) X self._threads.append(worker) X worker.start() X X def shrink(self, amount): X """Kill off worker threads (not below self.min).""" X # Grow/shrink the pool if necessary. X # Remove any dead threads from our list X for t in self._threads: X if not t.isAlive(): X self._threads.remove(t) X amount -= 1 X X if amount > 0: X for i in xrange(min(amount, len(self._threads) - self.min)): X # Put a number of shutdown requests on the queue equal X # to 'amount'. Once each of those is processed by a worker, X # that worker will terminate and be culled from our list X # in self.put. X self._queue.put(_SHUTDOWNREQUEST) X X def stop(self, timeout=5): X # Must shut down threads here so the code that calls X # this method can know when all threads are stopped. X for worker in self._threads: X self._queue.put(_SHUTDOWNREQUEST) X X # Don't join currentThread (when stop is called inside a request). X current = threading.currentThread() X while self._threads: X worker = self._threads.pop() X if worker is not current and worker.isAlive(): X try: X if timeout is None or timeout < 0: X worker.join() X else: X worker.join(timeout) X if worker.isAlive(): X # We exhausted the timeout. X # Forcibly shut down the socket. X c = worker.conn X if c and not c.rfile.closed: X if SSL and isinstance(c.socket, SSL.ConnectionType): X # pyOpenSSL.socket.shutdown takes no args X c.socket.shutdown() X else: X c.socket.shutdown(socket.SHUT_RD) X worker.join() X except (AssertionError, X # Ignore repeated Ctrl-C. X # See http://www.cherrypy.org/ticket/691. X KeyboardInterrupt), exc1: X pass X X X Xclass SSLConnection: X """A thread-safe wrapper for an SSL.Connection. X X *args: the arguments to create the wrapped SSL.Connection(*args). X """ X X def __init__(self, *args): X self._ssl_conn = SSL.Connection(*args) X self._lock = threading.RLock() X X for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read', X 'renegotiate', 'bind', 'listen', 'connect', 'accept', X 'setblocking', 'fileno', 'shutdown', 'close', 'get_cipher_list', X 'getpeername', 'getsockname', 'getsockopt', 'setsockopt', X 'makefile', 'get_app_data', 'set_app_data', 'state_string', X 'sock_shutdown', 'get_peer_certificate', 'want_read', X 'want_write', 'set_connect_state', 'set_accept_state', X 'connect_ex', 'sendall', 'settimeout'): X exec """def %s(self, *args): X self._lock.acquire() X try: X return self._ssl_conn.%s(*args) X finally: X self._lock.release() X""" % (f, f) X X Xtry: X import fcntl Xexcept ImportError: X try: X from ctypes import windll, WinError X except ImportError: X def prevent_socket_inheritance(sock): X """Dummy function, since neither fcntl nor ctypes are available.""" X pass X else: X def prevent_socket_inheritance(sock): X """Mark the given socket fd as non-inheritable (Windows).""" X if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0): X raise WinError() Xelse: X def prevent_socket_inheritance(sock): X """Mark the given socket fd as non-inheritable (POSIX).""" X fd = sock.fileno() X old_flags = fcntl.fcntl(fd, fcntl.F_GETFD) X fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC) X X Xclass CherryPyWSGIServer(object): X """An HTTP server for WSGI. X X bind_addr: The interface on which to listen for connections. X For TCP sockets, a (host, port) tuple. Host values may be any IPv4 X or IPv6 address, or any valid hostname. The string 'localhost' is a X synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6). X The string '0.0.0.0' is a special IPv4 entry meaning "any active X interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for X IPv6. The empty string or None are not allowed. X X For UNIX sockets, supply the filename as a string. X wsgi_app: the WSGI 'application callable'; multiple WSGI applications X may be passed as (path_prefix, app) pairs. X numthreads: the number of worker threads to create (default 10). X server_name: the string to set for WSGI's SERVER_NAME environ entry. X Defaults to socket.gethostname(). X max: the maximum number of queued requests (defaults to -1 = no limit). X request_queue_size: the 'backlog' argument to socket.listen(); X specifies the maximum number of queued connections (default 5). X timeout: the timeout in seconds for accepted connections (default 10). X X nodelay: if True (the default since 3.1), sets the TCP_NODELAY socket X option. X X protocol: the version string to write in the Status-Line of all X HTTP responses. For example, "HTTP/1.1" (the default). This X also limits the supported features used in the response. X X X SSL/HTTPS X --------- X The OpenSSL module must be importable for SSL functionality. X You can obtain it from http://pyopenssl.sourceforge.net/ X X ssl_certificate: the filename of the server SSL certificate. X ssl_privatekey: the filename of the server's private key file. X X If either of these is None (both are None by default), this server X will not use SSL. If both are given and are valid, they will be read X on server start and used in the SSL context for the listening socket. X """ X X protocol = "HTTP/1.1" X _bind_addr = "127.0.0.1" X version = "CherryPy/3.1.2" X ready = False X _interrupt = None X X nodelay = True X X ConnectionClass = HTTPConnection X environ = {} X X # Paths to certificate and private key files X ssl_certificate = None X ssl_private_key = None X X def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None, X max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5): X self.requests = ThreadPool(self, min=numthreads or 1, max=max) X X if callable(wsgi_app): X # We've been handed a single wsgi_app, in CP-2.1 style. X # Assume it's mounted at "". X self.wsgi_app = wsgi_app X else: X # We've been handed a list of (path_prefix, wsgi_app) tuples, X # so that the server can call different wsgi_apps, and also X # correctly set SCRIPT_NAME. X warnings.warn("The ability to pass multiple apps is deprecated " X "and will be removed in 3.2. You should explicitly " X "include a WSGIPathInfoDispatcher instead.", X DeprecationWarning) X self.wsgi_app = WSGIPathInfoDispatcher(wsgi_app) X X self.bind_addr = bind_addr X if not server_name: X server_name = socket.gethostname() X self.server_name = server_name X self.request_queue_size = request_queue_size X X self.timeout = timeout X self.shutdown_timeout = shutdown_timeout X X def _get_numthreads(self): X return self.requests.min X def _set_numthreads(self, value): X self.requests.min = value X numthreads = property(_get_numthreads, _set_numthreads) X X def __str__(self): X return "%s.%s(%r)" % (self.__module__, self.__class__.__name__, X self.bind_addr) X X def _get_bind_addr(self): X return self._bind_addr X def _set_bind_addr(self, value): X if isinstance(value, tuple) and value[0] in ('', None): X # Despite the socket module docs, using '' does not X # allow AI_PASSIVE to work. Passing None instead X # returns '0.0.0.0' like we want. In other words: X # host AI_PASSIVE result X # '' Y 192.168.x.y X # '' N 192.168.x.y X # None Y 0.0.0.0 X # None N 127.0.0.1 X # But since you can get the same effect with an explicit X # '0.0.0.0', we deny both the empty string and None as values. X raise ValueError("Host values of '' or None are not allowed. " X "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead " X "to listen on all active interfaces.") X self._bind_addr = value X bind_addr = property(_get_bind_addr, _set_bind_addr, X doc="""The interface on which to listen for connections. X X For TCP sockets, a (host, port) tuple. Host values may be any IPv4 X or IPv6 address, or any valid hostname. The string 'localhost' is a X synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6). X The string '0.0.0.0' is a special IPv4 entry meaning "any active X interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for X IPv6. The empty string or None are not allowed. X X For UNIX sockets, supply the filename as a string.""") X X def start(self): X """Run the server forever.""" X # We don't have to trap KeyboardInterrupt or SystemExit here, X # because cherrpy.server already does so, calling self.stop() for us. X # If you're using this server with another framework, you should X # trap those exceptions in whatever code block calls start(). X self._interrupt = None X X # Select the appropriate socket X if isinstance(self.bind_addr, basestring): X # AF_UNIX socket X X # So we can reuse the socket... X try: os.unlink(self.bind_addr) X except: pass X X # So everyone can access the socket... X try: os.chmod(self.bind_addr, 0777) X except: pass X X info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)] X else: X # AF_INET or AF_INET6 socket X # Get the correct address family for our host (allows IPv6 addresses) X host, port = self.bind_addr X try: X info = socket.getaddrinfo(host, port, socket.AF_UNSPEC, X socket.SOCK_STREAM, 0, socket.AI_PASSIVE) X except socket.gaierror: X # Probably a DNS issue. Assume IPv4. X info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "", self.bind_addr)] X X self.socket = None X msg = "No socket could be created" X for res in info: X af, socktype, proto, canonname, sa = res X try: X self.bind(af, socktype, proto) X except socket.error, msg: X if self.socket: X self.socket.close() X self.socket = None X continue X break X if not self.socket: X raise socket.error, msg X X # Timeout so KeyboardInterrupt can be caught on Win32 X self.socket.settimeout(1) X self.socket.listen(self.request_queue_size) X X # Create worker threads X self.requests.start() X X self.ready = True X while self.ready: X self.tick() X if self.interrupt: X while self.interrupt is True: X # Wait for self.stop() to complete. See _set_interrupt. X time.sleep(0.1) X if self.interrupt: X raise self.interrupt X X def bind(self, family, type, proto=0): X """Create (or recreate) the actual socket object.""" X self.socket = socket.socket(family, type, proto) X prevent_socket_inheritance(self.socket) X self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) X if self.nodelay: X self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) X if self.ssl_certificate and self.ssl_private_key: X if SSL is None: X raise ImportError("You must install pyOpenSSL to use HTTPS.") X X # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473 X ctx = SSL.Context(SSL.SSLv23_METHOD) X ctx.use_privatekey_file(self.ssl_private_key) X ctx.use_certificate_file(self.ssl_certificate) X self.socket = SSLConnection(ctx, self.socket) X self.populate_ssl_environ() X X # If listening on the IPV6 any address ('::' = IN6ADDR_ANY), X # activate dual-stack. See http://www.cherrypy.org/ticket/871. X if (not isinstance(self.bind_addr, basestring) X and self.bind_addr[0] == '::' and family == socket.AF_INET6): X try: X self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0) X except (AttributeError, socket.error): X # Apparently, the socket option is not available in X # this machine's TCP stack X pass X X self.socket.bind(self.bind_addr) X X def tick(self): X """Accept a new connection and put it on the Queue.""" X try: X s, addr = self.socket.accept() X prevent_socket_inheritance(s) X if not self.ready: X return X if hasattr(s, 'settimeout'): X s.settimeout(self.timeout) X X environ = self.environ.copy() X # SERVER_SOFTWARE is common for IIS. It's also helpful for X # us to pass a default value for the "Server" response header. X if environ.get("SERVER_SOFTWARE") is None: X environ["SERVER_SOFTWARE"] = "%s WSGI Server" % self.version X # set a non-standard environ entry so the WSGI app can know what X # the *real* server protocol is (and what features to support). X # See http://www.faqs.org/rfcs/rfc2145.html. X environ["ACTUAL_SERVER_PROTOCOL"] = self.protocol X environ["SERVER_NAME"] = self.server_name X X if isinstance(self.bind_addr, basestring): X # AF_UNIX. This isn't really allowed by WSGI, which doesn't X # address unix domain sockets. But it's better than nothing. X environ["SERVER_PORT"] = "" X else: X environ["SERVER_PORT"] = str(self.bind_addr[1]) X # optional values X # Until we do DNS lookups, omit REMOTE_HOST X environ["REMOTE_ADDR"] = addr[0] X environ["REMOTE_PORT"] = str(addr[1]) X X conn = self.ConnectionClass(s, self.wsgi_app, environ) X self.requests.put(conn) X except socket.timeout: X # The only reason for the timeout in start() is so we can X # notice keyboard interrupts on Win32, which don't interrupt X # accept() by default X return X except socket.error, x: X if x.args[0] in socket_error_eintr: X # I *think* this is right. EINTR should occur when a signal X # is received during the accept() call; all docs say retry X # the call, and I *think* I'm reading it right that Python X # will then go ahead and poll for and handle the signal X # elsewhere. See http://www.cherrypy.org/ticket/707. X return X if x.args[0] in socket_errors_nonblocking: X # Just try again. See http://www.cherrypy.org/ticket/479. X return X if x.args[0] in socket_errors_to_ignore: X # Our socket was closed. X # See http://www.cherrypy.org/ticket/686. X return X raise X X def _get_interrupt(self): X return self._interrupt X def _set_interrupt(self, interrupt): X self._interrupt = True X self.stop() X self._interrupt = interrupt X interrupt = property(_get_interrupt, _set_interrupt, X doc="Set this to an Exception instance to " X "interrupt the server.") X X def stop(self): X """Gracefully shutdown a server that is serving forever.""" X self.ready = False X X sock = getattr(self, "socket", None) X if sock: X if not isinstance(self.bind_addr, basestring): X # Touch our own socket to make accept() return immediately. X try: X host, port = sock.getsockname()[:2] X except socket.error, x: X if x.args[0] not in socket_errors_to_ignore: X raise X else: X # Note that we're explicitly NOT using AI_PASSIVE, X # here, because we want an actual IP to touch. X # localhost won't work if we've bound to a public IP, X # but it will if we bound to '0.0.0.0' (INADDR_ANY). X for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC, X socket.SOCK_STREAM): X af, socktype, proto, canonname, sa = res X s = None X try: X s = socket.socket(af, socktype, proto) X # See http://groups.google.com/group/cherrypy-users/ X # browse_frm/thread/bbfe5eb39c904fe0 X s.settimeout(1.0) X s.connect((host, port)) X s.close() X except socket.error: X if s: X s.close() X if hasattr(sock, "close"): X sock.close() X self.socket = None X X self.requests.stop(self.shutdown_timeout) X X def populate_ssl_environ(self): X """Create WSGI environ entries to be merged into each request.""" X cert = open(self.ssl_certificate, 'rb').read() X cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert) X ssl_environ = { X "wsgi.url_scheme": "https", X "HTTPS": "on", X # pyOpenSSL doesn't provide access to any of these AFAICT X## 'SSL_PROTOCOL': 'SSLv2', X## SSL_CIPHER string The cipher specification name X## SSL_VERSION_INTERFACE string The mod_ssl program version X## SSL_VERSION_LIBRARY string The OpenSSL program version X } X X # Server certificate attributes X ssl_environ.update({ X 'SSL_SERVER_M_VERSION': cert.get_version(), X 'SSL_SERVER_M_SERIAL': cert.get_serial_number(), X## 'SSL_SERVER_V_START': Validity of server's certificate (start time), X## 'SSL_SERVER_V_END': Validity of server's certificate (end time), X }) X X for prefix, dn in [("I", cert.get_issuer()), X ("S", cert.get_subject())]: X # X509Name objects don't seem to have a way to get the X # complete DN string. Use str() and slice it instead, X # because str(dn) == "" X dnstr = str(dn)[18:-2] X X wsgikey = 'SSL_SERVER_%s_DN' % prefix X ssl_environ[wsgikey] = dnstr X X # The DN should be of the form: /k1=v1/k2=v2, but we must allow X # for any value to contain slashes itself (in a URL). X while dnstr: X pos = dnstr.rfind("=") X dnstr, value = dnstr[:pos], dnstr[pos + 1:] X pos = dnstr.rfind("/") X dnstr, key = dnstr[:pos], dnstr[pos + 1:] X if key and value: X wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key) X ssl_environ[wsgikey] = value X X self.environ.update(ssl_environ) X 6e9c2e3249a8dbcca82cb68cb765118f echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/cherrypy_wsgiserver/LICENSE.txt sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/cherrypy_wsgiserver/LICENSE.txt << '7eb818116f2bcf8b8a21ae613d319660' XCopyright (c) 2004-2009, CherryPy Team (team@cherrypy.org) XAll rights reserved. X XRedistribution and use in source and binary forms, with or without modification, Xare permitted provided that the following conditions are met: X X * Redistributions of source code must retain the above copyright notice, X this list of conditions and the following disclaimer. X * Redistributions in binary form must reproduce the above copyright notice, X this list of conditions and the following disclaimer in the documentation X and/or other materials provided with the distribution. X * Neither the name of the CherryPy Team nor the names of its contributors X may be used to endorse or promote products derived from this software X without specific prior written permission. X XTHIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND XANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED XWARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE XDISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE XFOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL XDAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR XSERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER XCAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, XOR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE XOF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 7eb818116f2bcf8b8a21ae613d319660 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/settings.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/settings.py << '62091a5eaeb7aad20d7f68d3c667dfe1' Ximport os Ximport ConfigParser Xfrom wsgidav.addons.seafile.domain_controller import SeafileDomainController Xfrom wsgidav.addons.seafile.seafile_dav_provider import SeafileProvider X Xdomaincontroller = SeafileDomainController() X Xacceptbasic = True Xacceptdigest = False Xdefaultdigest = False X Xshare_name = '/' X# haiwen X# - pro-data X# - seafdav.conf X# - seafile-pro-server-1.8.0 X# - pro X# - python X# - seafdav X# - WsgiDAV.egg X# - wsgidav X# - server X# - seafdav_settings.py X X##### a sample seafdav.conf, we only care: "share_name" X# [WEBDAV] X# enabled = true X# port = 8080 X# share_name = /seafdav X##### a sample seafdav.conf X Xdef load_seafdav_conf(): X global share_name X X seafdav_conf = os.environ['SEAFDAV_CONF'] X if not os.path.exists(seafdav_conf): X return X X config = ConfigParser.ConfigParser() X config.read(seafdav_conf) X section_name = 'WEBDAV' X X if config.has_option(section_name, 'share_name'): X share_name = config.get(section_name, 'share_name') X X Xtry: X load_seafdav_conf() Xexcept: X pass X Xprovider_mapping = {} Xprovider_mapping[share_name] = SeafileProvider() 62091a5eaeb7aad20d7f68d3c667dfe1 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/sample_wsgidav.conf sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/sample_wsgidav.conf << 'ea35059a2f36acd9ce090710f6793808' X X# Note: This file is in Python syntax and format X X################################################################################ X# WsgiDAV configuration file X# X################################################################################ X# INITIALIZATION - Do not modify this section X Xprovider_mapping = {} Xuser_mapping = {} X X Xdef addShare(shareName, davProvider): X provider_mapping[shareName] = davProvider X X Xdef addUser(realmName, user, password, description, roles=[]): X realmName = "/" + realmName.strip(r"\/") X userDict = user_mapping.setdefault(realmName, {}).setdefault(user, {}) X userDict["password"] = password X userDict["description"] = description X userDict["roles"] = roles X X X################################################################################ X# SERVER OPTIONS X#=============================================================================== X# 3rd party servers X# Try to run WsgiDAV inside these WSGI servers, in that order Xext_servers = ( X# "paste", X# "cherrypy", X# "wsgiref", X "cherrypy-bundled", X "wsgidav", X ) X X X#=============================================================================== X# Debugging X Xverbose = 2 # 0 - no output (excepting application exceptions) X # 1 - show single line request summaries (HTTP logging) X # 2 - show additional events X # 3 - show full request/response header info (HTTP Logging) X # request body and GET response bodies not shown X X X# Enable specific module loggers X# E.g. ["lock_manager", "property_manager", "http_authenticator", ...] X Xenable_loggers = [] X X X################################################################################ X# WsgiDavDirBrowser X Xdir_browser = { X "enable": True, # Render HTML listing for GET requests on collections X "response_trailer": "", # Raw HTML code, appended as footer X "davmount": False, # Send response if request URL contains '?davmount' X "msmount": False, # Add an 'open as webfolder' link (requires Windows) X# "app_class": MyBrowser, # Used instead of WsgiDavDirBrowser X} X X X################################################################################ X# DAV Provider X X#=============================================================================== X# Property Manager X# X# Uncomment this lines to specify your own property manager. X# Default: no support for dead properties X# Also available: wsgidav.property_manager.PropertyManager X# wsgidav.property_manager.ShelvePropertyManager X# X# Check the documentation on how to develop custom property managers. X# Note that the default PropertyManager works in-memory, and thus is NOT X# persistent. X X### Use in-memory property manager (NOT persistent) X# (this is the same as passing 'propsmanager = True') X#from wsgidav.property_manager import PropertyManager X#propsmanager = PropertyManager() X X### Use persistent shelve based property manager X#from wsgidav.property_manager import ShelvePropertyManager X#propsmanager = ShelvePropertyManager("wsgidav-props.shelve") X X### Use persistent MongoDB based property manager X#from wsgidav.addons.mongo_property_manager import MongoPropertyManager X#prop_man_opts = {} X#propsmanager = MongoPropertyManager(prop_man_opts) X X### Use persistent CouchDB based property manager X#from wsgidav.addons.couch_property_manager import CouchPropertyManager X#prop_man_opts = {} X#propsmanager = CouchPropertyManager(prop_man_opts) X X### Use in-memory property manager (NOT persistent) Xpropsmanager = True X X X#=============================================================================== X# Lock Manager X# X# Uncomment this lines to specify your own locks manager. X# Default: wsgidav.lock_storage.LockStorageDict X# Also available: wsgidav.lock_storage.LockStorageShelve X# X# Check the documentation on how to develop custom lock managers. X# Note that the default LockStorageDict works in-memory, and thus is NOT X# persistent. X X# Example: Use in-memory lock storage X# (this is the same as passing 'locksmanager = True', which is default) X#from wsgidav.lock_storage import LockStorageDict X#locksmanager = LockStorageDict() X X X# Example: Use PERSISTENT shelve based lock manager X#from wsgidav.lock_storage import LockStorageShelve X#locksmanager = LockStorageShelve("wsgidav-locks.shelve") X X X#=============================================================================== X# SHARES X# X# If you would like to publish files in the location '/v_root' through a X# WsgiDAV share 'files', so that it can be accessed by this URL: X# http://server:port/files X# insert the following line: X# addShare("files", "/v_root") X# or, on a Windows box: X# addShare("files", "c:\\v_root") X# X# To access the same directory using a root level share X# http://server:port/ X# insert this line: X# addShare("", "/v_root") X# X# The above examples use wsgidav.fs_dav_provider.FilesystemProvider, which is X# the default provider implementation. X# X# If you wish to use a custom provider, an object must be passed as second X# parameter. See the examples below. X X### Add a read-write file share: XaddShare("dav", r"C:\temp") X X### Add a read-only file share: X#from wsgidav.fs_dav_provider import FilesystemProvider X#addShare("tmp", FilesystemProvider("/tmp", readonly=True)) X X X### Publish an MySQL 'world' database as share '/world-db' X#from wsgidav.addons.mysql_dav_provider import MySQLBrowserProvider X#addShare("world-db", MySQLBrowserProvider("localhost", "root", "test", "world")) X X X### Publish a virtual structure X#from wsgidav.samples.virtual_dav_provider import VirtualResourceProvider X#addShare("virtres", VirtualResourceProvider()) X X X### Publish a Mercurial repository X#from wsgidav.addons.hg_dav_provider import HgResourceProvider X#addShare("hg", HgResourceProvider("PATH_OR_URL")) X X X### Publish a MongoDB X#from wsgidav.samples.mongo_dav_provider import MongoResourceProvider X#mongo_dav_opts = {} X#addShare("mongo", MongoResourceProvider(mongo_dav_opts)) X X X################################################################################ X# AUTHENTICATION X#=============================================================================== X# HTTP Authentication Options X Xacceptbasic = True # Allow basic authentication, True or False Xacceptdigest = True # Allow digest authentication, True or False Xdefaultdigest = True # True (default digest) or False (default basic) X X X#domaincontroller = # Uncomment this line to specify your own domain controller X # Default: wsgidav.domain_controller, which uses the USERS X # section below X X X# Example: use a domain controller that allows users to authenticate against X# a Windows NT domain or a local computer. X# Note: NTDomainController requires basic authentication: X# Set acceptbasic=True, acceptdigest=False, defaultdigest=False X X#from wsgidav.addons.nt_domain_controller import NTDomainController X#domaincontroller = NTDomainController(presetdomain=None, presetserver=None) X#acceptbasic = True X#acceptdigest = False X#defaultdigest = False X X X#=============================================================================== X# USERS X# X# This section is ONLY used by the DEFAULT Domain Controller. X# X# Users are defined per realm: X# addUser(, , , ) X# X# Note that the default Domain Controller uses the share name as realm name. X# X# If no users are specified for a realm, no authentication is required. X# Thus granting read-write access to anonymous! X# X# Note: If you wish to use Windows WebDAV support (such as Windows XP's My X# Network Places), you need to include the domain of the user as part of the X# username (note the DOUBLE slash), such as: X# addUser("v_root", "domain\\user", "password", "description") X XaddUser("", "tester", "tester", "") XaddUser("", "tester2", "tester2", "") X X#addUser("dav", "tester", "tester", "") X#addUser("dav", "tester2", "tester2", "") X X#addUser("virtres", "tester", "tester", "") ea35059a2f36acd9ce090710f6793808 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/server_sample.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/server_sample.py << '1022cbcd12c73c1034f73317a7a40bf0' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XSimple example how to a run WsgiDAV in a 3rd-party WSGI server. X""" Xfrom tempfile import gettempdir Xfrom wsgidav.fs_dav_provider import FilesystemProvider Xfrom wsgidav.version import __version__ Xfrom wsgidav.wsgidav_app import DEFAULT_CONFIG, WsgiDAVApp X X__docformat__ = "reStructuredText" X X Xrootpath = gettempdir() Xprovider = FilesystemProvider(rootpath) X Xconfig = DEFAULT_CONFIG.copy() Xconfig.update({ X "provider_mapping": {"/": provider}, X "user_mapping": {}, X "verbose": 1, X "enable_loggers": [], X "propsmanager": True, # True: use property_manager.PropertyManager X "locksmanager": True, # True: use lock_manager.LockManager X "domaincontroller": None, # None: domain_controller.WsgiDAVDomainController(user_mapping) X }) Xapp = WsgiDAVApp(config) X X# For an example. use paste.httpserver X# (See http://pythonpaste.org/modules/httpserver.html for more options) Xfrom paste import httpserver Xhttpserver.serve(app, X host="localhost", X port=8080, X server_version="WsgiDAV/%s" % __version__, X ) X X# Or we could use default the server that is part of the WsgiDAV package: X#from wsgidav.server import ext_wsgiutils_server X#ext_wsgiutils_server.serve(config, app) 1022cbcd12c73c1034f73317a7a40bf0 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/ext_wsgiutils_server.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/ext_wsgiutils_server.py << '72127094b9cef765dcf8628d9af40b13' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Original PyFileServer (c) 2005 Ho Chun Wei. X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" Xext_wsgiutils_server.py is an extension of the wsgiutils server in Paste. XIt supports passing all of the HTTP and WebDAV (rfc 2518) methods. X XIt includes code from the following sources: X``wsgiServer.py`` from wsgiKit under PSF license, X``wsgiutils_server.py`` from Paste under PSF license, Xflexible handler method under public domain. X X XRunning as standalone server X---------------------------- X XTo run as a standalone server using the bundled ext_wsgiutils_server.py:: X X usage: python ext_wsgiutils_server.py [options] [config-file] X X config-file: X The configuration file for WsgiDAV. if omitted, the application X will look for a file named 'WsgiDAV.conf' in the current directory X X options: X --port=PORT Port to serve on (default: 8080) X --host=HOST Host to serve from (default: localhost, which is only X accessible from the local computer; use 0.0.0.0 to make your X application public) X -h, --help show this help message and exit X X XRunning using other web servers X------------------------------- X XTo run it with other WSGI web servers, you can:: X X from wsgidav.mainappwrapper import PyFileApp X publish_app = PyFileApp('WsgiDAV.conf') X # construct the application with configuration file X # if configuration file is omitted, the application X # will look for a file named 'WsgiDAV.conf' X # in the current directory X Xwhere ``publish_app`` is the WSGI application to be run, it will be called with X``publish_app(environ, start_response)`` for each incoming request, as described in XWSGI X XNote: if you are using the paster development server (from Paste ), you can Xcopy ``ext_wsgi_server.py`` to ``/paste/servers`` and use this server to run the Xapplication by specifying ``server='ext_wsgiutils'`` in the ``server.conf`` or appropriate paste Xconfiguration. X""" X__docformat__ = "reStructuredText" X Xfrom wsgidav.version import __version__ Ximport time Ximport httplib Ximport socket Ximport threading X X Ximport SocketServer, BaseHTTPServer, urlparse Ximport sys, logging Ximport traceback Xfrom wsgidav import util Xtry: X from cStringIO import StringIO Xexcept ImportError: X from StringIO import StringIO X X_logger = util.getModuleLogger(__name__) X XSERVER_ERROR = """\ X X X Server Error X X X

Server Error

X A server error has occurred. Please contact the system administrator for X more information. X X X""" X Xclass ExtHandler (BaseHTTPServer.BaseHTTPRequestHandler): X X _SUPPORTED_METHODS = ["HEAD", "GET", "PUT", "POST", "OPTIONS", "TRACE", X "DELETE", "PROPFIND", "PROPPATCH", "MKCOL", "COPY", X "MOVE", "LOCK", "UNLOCK"] X X # Enable automatic keep-alive: X protocol_version = "HTTP/1.1" X X server_version = "WsgiDAV/%s %s" % (__version__, X BaseHTTPServer.BaseHTTPRequestHandler.server_version) X X def log_message (self, *args): X pass X# BaseHTTPServer.BaseHTTPRequestHandler.log_message(self, *args) X X def log_request (self, *args): X pass X# BaseHTTPServer.BaseHTTPRequestHandler.log_request(self, *args) X X def getApp (self): X # We want fragments to be returned as part of X _protocol, _host, path, _parameters, query, _fragment = urlparse.urlparse ("http://dummyhost%s" % self.path, X allow_fragments=False) X # Find any application we might have X for appPath, app in self.server.wsgiApplications: X if (path.startswith (appPath)): X # We found the application to use - work out the scriptName and pathInfo X pathInfo = path [len (appPath):] X if (len (pathInfo) > 0): X if (not pathInfo.startswith ("/")): X pathInfo = "/" + pathInfo X if (appPath.endswith ("/")): X scriptName = appPath[:-1] X else: X scriptName = appPath X # Return all this X return app, scriptName, pathInfo, query X return None, None, None, None X X def handlerFunctionClosure(self,name): X def handlerFunction(*args,**kwargs): X self.do_method() X return handlerFunction X X def do_method(self): X app, scriptName, pathInfo, query = self.getApp () X if (not app): X self.send_error (404, "Application not found.") X return X self.runWSGIApp (app, scriptName, pathInfo, query) X X def __getattr__(self, name): X if len(name)>3 and name[0:3] == "do_" and name[3:] in self._SUPPORTED_METHODS: X return self.handlerFunctionClosure(name) X else: X self.send_error (501, "Method Not Implemented.") X return X X def runWSGIApp (self, application, scriptName, pathInfo, query): X logging.info ("Running application with SCRIPT_NAME %s PATH_INFO %s" % (scriptName, pathInfo)) X X if self.command == "PUT": X pass # breakpoint X X env = {"wsgi.version": (1, 0), X "wsgi.url_scheme": "http", X "wsgi.input": self.rfile, X "wsgi.errors": sys.stderr, X "wsgi.multithread": 1, X "wsgi.multiprocess": 0, X "wsgi.run_once": 0, X "REQUEST_METHOD": self.command, X "SCRIPT_NAME": scriptName, X "PATH_INFO": pathInfo, X "QUERY_STRING": query, X "CONTENT_TYPE": self.headers.get("Content-Type", ""), X "CONTENT_LENGTH": self.headers.get("Content-Length", ""), X "REMOTE_ADDR": self.client_address[0], X "SERVER_NAME": self.server.server_address[0], X "SERVER_PORT": str(self.server.server_address[1]), X "SERVER_PROTOCOL": self.request_version, X } X for httpHeader, httpValue in self.headers.items(): X if not httpHeader.lower() in ("content-type", "content-length"): X env ["HTTP_%s" % httpHeader.replace ("-", "_").upper()] = httpValue X X # Setup the state X self.wsgiSentHeaders = 0 X self.wsgiHeaders = [] X X try: X # We have there environment, now invoke the application X _logger.debug("runWSGIApp application()...") X result = application (env, self.wsgiStartResponse) X try: X for data in result: X if data: X self.wsgiWriteData(data) X else: X _logger.debug("runWSGIApp empty data") X finally: X _logger.debug("runWSGIApp finally.") X if hasattr(result, "close"): X result.close() X except: X _logger.debug("runWSGIApp caught exception...") X errorMsg = StringIO() X traceback.print_exc(file=errorMsg) X logging.error (errorMsg.getvalue()) X if not self.wsgiSentHeaders: X self.wsgiStartResponse("500 Server Error", [("Content-type", "text/html")]) X self.wsgiWriteData(SERVER_ERROR) X X if not self.wsgiSentHeaders: X # issue 29 sending one byte, when content-length is '0' seems wrong X # We must write out something! X# self.wsgiWriteData (" ") X self.wsgiWriteData("") X return X X def wsgiStartResponse (self, response_status, response_headers, exc_info=None): X _logger.debug("wsgiStartResponse(%s, %s, %s)" % (response_status, response_headers, exc_info)) X if (self.wsgiSentHeaders): X raise Exception ("Headers already sent and start_response called again!") X # Should really take a copy to avoid changes in the application.... X self.wsgiHeaders = (response_status, response_headers) X return self.wsgiWriteData X X def wsgiWriteData (self, data): X if not self.wsgiSentHeaders: X status, headers = self.wsgiHeaders X # Need to send header prior to data X statusCode = status [:status.find (" ")] X statusMsg = status [status.find (" ") + 1:] X _logger.debug("wsgiWriteData: send headers '%r', %r" % (status, headers)) X self.send_response (int (statusCode), statusMsg) X for header, value in headers: X self.send_header (header, value) X self.end_headers() X self.wsgiSentHeaders = 1 X # Send the data X assert type(data) is str # If not, Content-Length is propably wrong! X try: X _logger.debug("wsgiWriteData: write %s bytes: '%r'..." % (len(data), data[:50])) X self.wfile.write(data) X except socket.error, e: X # Suppress stack trace when client aborts connection disgracefully: X # 10053: Software caused connection abort X # 10054: Connection reset by peer X if e[0] in (10053, 10054): X print >>sys.stderr, "*** Caught socket.error: ", e X else: X raise X X Xclass ExtServer (SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer): X X def handle_error(self, request, client_address): X """Handle an error gracefully. May be overridden. X X The default is to print a traceback and continue. X X """ X ei = sys.exc_info() X e = ei[1] X # Suppress stack trace when client aborts connection disgracefully: X # 10053: Software caused connection abort X # 10054: Connection reset by peer X if e[0] in (10053, 10054): X util.warn("*** Caught socket.error: %s" % e) X return X # This is what BaseHTTPServer.HTTPServer.handle_error does, but with X # added thread ID and using stderr X print >>sys.stderr, '-'*40 X print >>sys.stderr, '<%s> Exception happened during processing of request from %s' % (threading._get_ident(), client_address) X print >>sys.stderr, client_address X traceback.print_exc() X print >>sys.stderr, '-'*40 X print >>sys.stderr, request X# BaseHTTPServer.HTTPServer.handle_error(self, request, client_address) X X X def stop_serve_forever(self): X """Stop serve_forever_stoppable().""" X assert hasattr(self, "stop_request"), "serve_forever_stoppable() must be called before" X assert not self.stop_request, "stop_serve_forever() must only be called once" X X# # Flag stop request X self.stop_request = True X time.sleep(.01) X if self.stopped: X# print "stop_serve_forever() 'stopped'." X return X X # Add a do_SHUTDOWN method to to the ExtHandler class X def _shutdownHandler(self): X """Send 200 OK response, and set server.stop_request to True. X X http://code.activestate.com/recipes/336012/ X """ X# print "Handling do_SHUTDOWN request" X self.send_response(200) X self.end_headers() X self.server.stop_request = True X if not hasattr(ExtHandler, "do_SHUTDOWN"): X setattr(ExtHandler, "do_SHUTDOWN", _shutdownHandler) X X # Send request, so socket is unblocked X (host, port) = self.server_address X# print "stop_serve_forever() sending %s:%s/ SHUTDOWN..." % (host, port) X conn = httplib.HTTPConnection("%s:%d" % (host, port)) X conn.request("SHUTDOWN", "/") X# print "stop_serve_forever() sent SHUTDOWN request, reading response..." X conn.getresponse() X# print "stop_serve_forever() received SHUTDOWN response." X assert self.stop_request X X X def serve_forever_stoppable(self): X """Handle one request at a time until stop_serve_forever(). X X http://code.activestate.com/recipes/336012/ X """ X self.stop_request = False X self.stopped = False X X while not self.stop_request: X self.handle_request() X X# print "serve_forever_stoppable() stopped." X self.stopped = True X X X def __init__ (self, serverAddress, wsgiApplications, serveFiles=1): X BaseHTTPServer.HTTPServer.__init__ (self, serverAddress, ExtHandler) X appList = [] X for urlPath, wsgiApp in wsgiApplications.items(): X appList.append ((urlPath, wsgiApp)) X self.wsgiApplications = appList X self.serveFiles = serveFiles X self.serverShuttingDown = 0 X X Xdef serve(conf, app): X host = conf.get("host", "localhost") X port = int(conf.get("port", 8080)) X server = ExtServer((host, port), {"": app}) X if conf.get("verbose") >= 1: X if host in ("", "0.0.0.0"): X (hostname, _aliaslist, ipaddrlist) = socket.gethostbyname_ex(socket.gethostname()) X print "WsgiDAV %s serving at %s, port %s (host='%s' %s)..." % (__version__, host, port, hostname, ipaddrlist) X else: X print "WsgiDAV %s serving at %s, port %s..." % (__version__, host, port) X server.serve_forever() X# server.serve_forever_stoppable() X X Xif __name__ == "__main__": X raise RuntimeError("Use run_server.py") 72127094b9cef765dcf8628d9af40b13 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/run_reloading_server.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/run_reloading_server.py << 'db87f2805e81d4ced15da786c990d5ab' X# (c) 2009-2011 Martin Wendt and contributors; see WsgiDAV http://wsgidav.googlecode.com/ X# Licensed under the MIT license: http://www.opensource.org/licenses/mit-license.php X""" XWrapper for ``run_server``, that restarts the server when source code is Xmodified. X""" Ximport os Ximport sys Xfrom subprocess import Popen X Xdef run(): X args = sys.argv[1:] X if not "--reload" in args: X args.append("--reload") X X print "run_reloading_server", args X X try: X serverpath = os.path.join(os.path.dirname(__file__), "run_server.py") X while True: X p = Popen(["python", serverpath] + args, X# stdin=sys.stdin, X# stdout=subprocess.PIPE, X# stderr=subprocess.PIPE, X # preexec_fn, close_fds, shell, cwd, env, universal_newlines, startupinfo, creationflags X ) X sys.stdout = p.stdout X sys.stderr = p.stderr X p.wait() X sys.stdout = sys.__stdout__ X sys.stderr = sys.__stderr__ X X if p.returncode == 3: X print "run_server returned 3: restarting..." X else: X print "run_server returned %s: terminating." % p.returncode X break X except Exception, e: X raise e X X Xif __name__ == "__main__": X run() db87f2805e81d4ced15da786c990d5ab echo x - py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/run_server.py sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/wsgidav/server/run_server.py << 'e23e52ec2f24a145ea7988bb41cc83e7' X#coding: UTF-8 X""" Xrun_server X========== X X:Author: Ho Chun Wei, fuzzybr80(at)gmail.com (author of original PyFileServer) X:Author: Martin Wendt, moogle(at)wwwendt.de X:Copyright: Licensed under the MIT license, see LICENSE file in this package. X XStandalone server that runs WsgiDAV. X XThese tasks are performed: X X - Set up the configuration from defaults, configuration file, and command line X options. X - Instantiate the WsgiDAVApp object (which is a WSGI application) X - Start a WSGI server for this WsgiDAVApp object X XConfiguration is defined like this: X X 1. Get the name of a configuration file from command line option X ``--config-file=FILENAME`` (or short ``-cFILENAME``). X If this option is omitted, we use ``wsgidav.conf`` in the current X directory. X 2. Set reasonable default settings. X 3. If configuration file exists: read and use it to overwrite defaults. X 4. If command line options are passed, use them to override settings: X X ``--host`` option overrides ``hostname`` setting. X X ``--port`` option overrides ``port`` setting. X X ``--root=FOLDER`` option creates a FilesystemProvider that publishes X FOLDER on the '/' share. X XSee DEVELOPERS.txt_ for more information about the WsgiDAV architecture. X X.. _DEVELOPERS.txt: http://wiki.wsgidav-dev.googlecode.com/hg/DEVELOPERS.html X""" Xfrom optparse import OptionParser Xfrom pprint import pprint Xfrom inspect import isfunction Xfrom wsgidav.wsgidav_app import DEFAULT_CONFIG Ximport traceback Ximport sys Ximport os X Xif os.name != 'nt': X import daemon X Xfrom wsgidav import util X Xtry: X from wsgidav.version import __version__ X from wsgidav.wsgidav_app import WsgiDAVApp X from wsgidav.fs_dav_provider import FilesystemProvider Xexcept ImportError, e: X raise RuntimeError("Could not import wsgidav package:\n%s\nSee http://wsgidav.googlecode.com/." % e) X X__docformat__ = "reStructuredText" X X# Use this config file, if no --config_file option is specified XDEFAULT_CONFIG_FILE = os.path.join(os.path.dirname(__file__), "settings.py") X X Xdef _initCommandLineOptions(): X """Parse command line options into a dictionary.""" X X usage = """\ X%prog [options] X XExamples: XShare filesystem folder '/temp': X wsgidav --port=80 --host=0.0.0.0 --root=/temp XRun using a configuration file: X wsgidav --port=80 --host=0.0.0.0 --config=~/wsgidav.conf X XIf no config file is specified, the application will look for a file named X'wsgidav.conf' in the current directory. XSee sample_wsgidav.conf for some explanation of the configuration file format. XIf no config file is found, a default FilesystemProvider is used.""" X X# description = """\ X#%prog is a standalone server for WsgiDAV. X#It tries to use pre-installed WSGI servers (cherrypy.wsgiserver, X#paste.httpserver, wsgiref.simple_server) or uses our built-in X#ext_wsgiutils_server.py.""" X X# epilog = """Licensed under the MIT license. X#See http://wsgidav.googlecode.com for additional information.""" X X parser = OptionParser(usage=usage, X version=__version__, X# conflict_handler="error", X description=None, #description, X add_help_option=True, X# prog="wsgidav", X# epilog=epilog # TODO: Not available on Python 2.4? X ) X X parser.add_option("-p", "--port", X dest="port", X type="int", X default=8080, X help="port to serve on (default: %default)") X parser.add_option("-H", "--host", # '-h' conflicts with --help X dest="host", X default="localhost", X help="host to serve from (default: %default). 'localhost' is only accessible from the local computer. Use 0.0.0.0 to make your application public"), X parser.add_option("-r", "--root", X dest="root_path", X help="Path to a file system folder to publish as share '/'.") X X parser.add_option("-q", "--quiet", X action="store_const", const=0, dest="verbose", X help="suppress any output except for errors.") X parser.add_option("-v", "--verbose", X action="store_const", const=2, dest="verbose", default=2, X help="Set verbose = 2: print informational output.") X parser.add_option("-d", "--debug", X action="store_const", const=3, dest="verbose", X help="Set verbose = 3: print requests and responses.") X X parser.add_option("-c", "--config", X dest="config_file", X help="Configuration file (default: %s in current directory)." % DEFAULT_CONFIG_FILE) X X parser.add_option("", "--reload", X action="store_true", dest="reload", X help="Restart server when source files are changed. Used by run_reloading_server. (Requires paste.reloader.)") X X parser.add_option("-l", "--log-file", X dest="log_path", X help="Log file path.") X X parser.add_option("", "--daemon", X action="store_true", dest="daemonize", default=False, X help="Run as daemon") X X parser.add_option("", "--pid", X dest="pid_file", X help="PID file path") X X# parser.add_option("", "--profile", X# action="store_true", dest="profile", X# help="Profile ") X X X (options, args) = parser.parse_args() X X if options.config_file is None: X # If --config was omitted, use default (if it exists) X defPath = os.path.abspath(DEFAULT_CONFIG_FILE) X if os.path.exists(defPath): X if options.verbose >= 2: X print "Using default configuration file: %s" % defPath X options.config_file = defPath X else: X # If --config was specified convert to absolute path and assert it exists X options.config_file = os.path.abspath(options.config_file) X if not os.path.exists(options.config_file): X parser.error("Could not open specified configuration file: %s" % options.config_file) X X # Convert options object to dictionary X cmdLineOpts = options.__dict__.copy() X if options.verbose >= 3: X print "Command line options:" X for k, v in cmdLineOpts.items(): X print " %-12s: %s" % (k, v) X return (cmdLineOpts, args) X X X X Xdef _readConfigFile(config_file, verbose): X """Read configuration file options into a dictionary.""" X X if not os.path.exists(config_file): X raise RuntimeError("Couldn't open configuration file '%s'." % config_file) X X try: X import imp X conf = {} X configmodule = imp.load_source("configuration_module", config_file) X X for k, v in vars(configmodule).items(): X if k.startswith("__"): X continue X elif isfunction(v): X continue X conf[k] = v X except Exception, e: X# if verbose >= 1: X# traceback.print_exc() X exceptioninfo = traceback.format_exception_only(sys.exc_type, sys.exc_value) #@UndefinedVariable X exceptiontext = "" X for einfo in exceptioninfo: X exceptiontext += einfo + "\n" X# raise RuntimeError("Failed to read configuration file: " + config_file + "\nDue to " + exceptiontext) X print >>sys.stderr, "Failed to read configuration file: " + config_file + "\nDue to " + exceptiontext X raise X X return conf X X X X Xdef _initConfig(): X """Setup configuration dictionary from default, command line and configuration file.""" X cmdLineOpts, args = _initCommandLineOptions() X X # Set config defaults X config = DEFAULT_CONFIG.copy() X X # Configuration file overrides defaults X config_file = cmdLineOpts.get("config_file") X if config_file: X verbose = cmdLineOpts.get("verbose", 2) X fileConf = _readConfigFile(config_file, verbose) X config.update(fileConf) X else: X if cmdLineOpts["verbose"] >= 2: X print "Running without configuration file." X X # Command line overrides file X if cmdLineOpts.get("port"): X config["port"] = cmdLineOpts.get("port") X if cmdLineOpts.get("host"): X config["host"] = cmdLineOpts.get("host") X if cmdLineOpts.get("verbose") is not None: X config["verbose"] = cmdLineOpts.get("verbose") X if cmdLineOpts.get("profile") is not None: X config["profile"] = True X X if cmdLineOpts.get("root_path"): X root_path = os.path.abspath(cmdLineOpts.get("root_path")) X config["provider_mapping"]["/"] = FilesystemProvider(root_path) X X if cmdLineOpts["verbose"] >= 3: X print "Configuration(%s):" % cmdLineOpts["config_file"] X pprint(config) X X log_path = cmdLineOpts.get("log_path", "") X if log_path: X log_path = os.path.abspath(log_path) X config["log_path"] = log_path X X config["daemonize"] = cmdLineOpts.get("daemonize") X X pid_file = cmdLineOpts.get("pid_file", "") X if pid_file: X pid_file = os.path.abspath(pid_file) X config["pid_file"] = pid_file X X ssl_cert = config.get("ssl_cert", "") X ssl_privkey = config.get("ssl_privkey", "") X if (ssl_cert and not ssl_privkey) or (not ssl_cert and ssl_privkey): X print >>sys.stderr, "ERROR: SSL certificate and private key must be given together." X sys.exit(-1) X X if not config["provider_mapping"]: X print >>sys.stderr, "ERROR: No DAV provider defined. Try --help option." X sys.exit(-1) X# raise RuntimeWarning("At least one DAV provider must be specified by a --root option, or in a configuration file.") X X if cmdLineOpts.get("reload"): X print >>sys.stderr, "Installing paste.reloader." X from paste import reloader #@UnresolvedImport X reloader.install() X if config_file: X # Add config file changes X reloader.watch_file(config_file) X# import pydevd X# pydevd.settrace() X X return (config, args) X X X X Xdef _runPaste(app, config, mode): X """Run WsgiDAV using paste.httpserver, if Paste is installed. X X See http://pythonpaste.org/modules/httpserver.html for more options X """ X _logger = util.getModuleLogger(__name__, True) X try: X from paste import httpserver X version = "WsgiDAV/%s %s" % (__version__, httpserver.WSGIHandler.server_version) X if config["verbose"] >= 1: X print "Running %s..." % version X X # See http://pythonpaste.org/modules/httpserver.html for more options X server = httpserver.serve(app, X host=config["host"], X port=config["port"], X server_version=version, X # This option enables handling of keep-alive X # and expect-100: X protocol_version="HTTP/1.1", X start_loop=False X ) X X if config["verbose"] >=3: X __handle_one_request = server.RequestHandlerClass.handle_one_request X def handle_one_request(self): X __handle_one_request(self) X if self.close_connection == 1: X _logger.debug("HTTP Connection : close") X else: X _logger.debug("HTTP Connection : continue") X X server.RequestHandlerClass.handle_one_request = handle_one_request X X __handle = server.RequestHandlerClass.handle X def handle(self): X _logger.debug("open HTTP connection") X __handle(self) X X server.RequestHandlerClass.handle_one_request = handle_one_request X X X host, port = server.server_address X if host == '0.0.0.0': X print 'serving on 0.0.0.0:%s view at %s://127.0.0.1:%s' % \ X (port, 'http', port) X else: X print "serving on %s://%s:%s" % ('http', host, port) X try: X server.serve_forever() X except KeyboardInterrupt: X # allow CTRL+C to shutdown X pass X except ImportError, e: X if config["verbose"] >= 1: X print "Could not import paste.httpserver." X return False X return True X X X X Xdef _runCherryPy(app, config, mode): X """Run WsgiDAV using cherrypy.wsgiserver, if CherryPy is installed.""" X assert mode in ("cherrypy", "cherrypy-bundled") X try: X if mode == "cherrypy-bundled": X from wsgidav.server import cherrypy_wsgiserver as wsgiserver X else: X # http://cherrypy.org/apidocs/3.0.2/cherrypy.wsgiserver-module.html X from cherrypy import wsgiserver, __version__ as cp_version X version = "WsgiDAV/%s %s" % (__version__, wsgiserver.CherryPyWSGIServer.version) X wsgiserver.CherryPyWSGIServer.version = version X if config["verbose"] >= 1: X# print "Running %s..." % version X print("Runing %s, listening on %s://%s:%s" X % (version, 'http', config["host"], config["port"])) X server = wsgiserver.CherryPyWSGIServer( X (config["host"], config["port"]), X app, X# server_name=version X ) X X ssl_cert = config.get("ssl_cert", "") X ssl_privkey = config.get("ssl_privkey", "") X if ssl_cert: X server.ssl_certificate = ssl_cert X server.ssl_private_key = ssl_privkey X X try: X server.start() X except KeyboardInterrupt: X if config["verbose"] >= 1: X print "Caught Ctrl-C, shutting down..." X server.stop() X except ImportError, e: X if config["verbose"] >= 1: X print "Could not import wsgiserver.CherryPyWSGIServer." X return False X return True X X X X Xdef _runFlup(app, config, mode): X """Run WsgiDAV using flup.server.fcgi, if Flup is installed.""" X try: X # http://trac.saddi.com/flup/wiki/FlupServers X if mode == "flup-fcgi": X from flup.server.fcgi import WSGIServer, __version__ as flupver X elif mode == "flup-fcgi_fork": X from flup.server.fcgi_fork import WSGIServer, __version__ as flupver X else: X raise ValueError X X if config["verbose"] >= 2: X print "Running WsgiDAV/%s %s/%s..." % (__version__, X WSGIServer.__module__, X flupver) X server = WSGIServer(app, X bindAddress=(config["host"], config["port"]), X# bindAddress=("127.0.0.1", 8001), X# debug=True, X ) X server.run() X except ImportError, e: X if config["verbose"] >= 1: X print "Could not import flup.server.fcgi", e X return False X return True X X X X Xdef _runSimpleServer(app, config, mode): X """Run WsgiDAV using wsgiref.simple_server, on Python 2.5+.""" X try: X # http://www.python.org/doc/2.5.2/lib/module-wsgiref.html X from wsgiref.simple_server import make_server, software_version X# if config["verbose"] >= 1: X# print "Running WsgiDAV %s on wsgiref.simple_server (single threaded)..." % __version__ X version = "WsgiDAV/%s %s" % (__version__, software_version) X if config["verbose"] >= 1: X print "Running %s..." % version X httpd = make_server(config["host"], config["port"], app) X# print "Serving HTTP on port 8000..." X httpd.serve_forever() X except ImportError, e: X if config["verbose"] >= 1: X print "Could not import wsgiref.simple_server (part of standard lib since Python 2.5)." X return False X return True X X X X Xdef _runBuiltIn(app, config, mode): X """Run WsgiDAV using ext_wsgiutils_server from the wsgidav package.""" X try: X import ext_wsgiutils_server X if config["verbose"] >= 2: X print "Running WsgiDAV %s on wsgidav.ext_wsgiutils_server..." % __version__ X ext_wsgiutils_server.serve(config, app) X except ImportError, e: X if config["verbose"] >= 1: X print "Could not import wsgidav.ext_wsgiutils_server (part of WsgiDAV)." X return False X return True X X XSUPPORTED_SERVERS = {"paste": _runPaste, X "cherrypy": _runCherryPy, X "cherrypy-bundled": _runCherryPy, X "wsgiref": _runSimpleServer, X "flup-fcgi": _runFlup, X "flup-fcgi_fork": _runFlup, X "wsgidav": _runBuiltIn, X } X X X#def _run_real(config): X# app = WsgiDAVApp(config) X# X# # Try running WsgiDAV inside the following external servers: X# res = False X# for e in config["ext_servers"]: X# fn = SUPPORTED_SERVERS.get(e) X# if fn is None: X# print "Invalid external server '%s'. (expected: '%s')" % (e, "', '".join(SUPPORTED_SERVERS.keys())) X# X# elif fn(app, config, e): X# res = True X# break X# X# if not res: X# print "No supported WSGI server installed." X# X# X#def run(): X# config = _initConfig() X# if config.get("profile"): X# import cProfile, pstats, StringIO X# prof = cProfile.Profile() X# prof = prof.runctx("_run_real(config)", globals(), locals()) X# stream = StringIO.StringIO() X# stats = pstats.Stats(prof, stream=stream) X# stats.sort_stats("time") # Or cumulative X# stats.print_stats(80) # 80 = how many to print X# # The rest is optional. X# # stats.print_callees() X# # stats.print_callers() X## logging.info("Profile data:\n%s", stream.getvalue()) X# print stream.getvalue() X# return X# return _real_run(config) X Xdef davmain(config, args): X app = WsgiDAVApp(config) X X if len(args) > 0: X if args[0] == "runfcgi": X if len(args) >= 2 and args[1] == "method=threaded": X _runFlup(app, config, "flup-fcgi") X else: X _runFlup(app, config, "flup-fcgi_fork") X else: X util.warn("Unknown command %s" % args[0]) X exit(1) X else: X _runCherryPy(app, config, "cherrypy-bundled") X X Xdef run(): X config, args = _initConfig() X X if config["daemonize"]: X context = daemon.DaemonContext() X pid_file_name = config.get("pid_file", "") X with context: X if pid_file_name: X f = open(pid_file_name, "w+") X print >> f, os.getpid() X f.close() X try: X davmain(config, args) X finally: X if pid_file_name: X os.unlink(pid_file_name) X else: X pid_file_name = config.get("pid_file", "") X if pid_file_name: X f = open(pid_file_name, "w+") X print >> f, os.getpid() X f.close() X try: X davmain(config, args) X finally: X if pid_file_name: X os.unlink(pid_file_name) X Xif __name__ == "__main__": X run() e23e52ec2f24a145ea7988bb41cc83e7 echo x - py-seafdav/work/haiwen-seafdav-42dfd99/Makefile sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/Makefile << 'ef58a0629e432cc853633e9fd13ce58f' Xall: seafdav.tar.gz X Xseafdav.tar.gz: X git archive HEAD wsgidav | gzip > seafdav.tar.gz Xclean: X rm -f *.gz ef58a0629e432cc853633e9fd13ce58f echo x - py-seafdav/work/haiwen-seafdav-42dfd99/README.md sed 's/^X//' >py-seafdav/work/haiwen-seafdav-42dfd99/README.md << '3d20770cbca6832439b62cc954b0319a' XThis is the WebDAV server for seafile. 3d20770cbca6832439b62cc954b0319a echo x - py-seafdav/work/.license-catalog.mk sed 's/^X//' >py-seafdav/work/.license-catalog.mk << 'ce73153dd5f000c8df50322023e49742' X_LICENSE=APACHE20 X_LICENSE_NAME=Apache License 2.0 X_LICENSE_PERMS=dist-mirror dist-sell pkg-mirror pkg-sell auto-accept X_LICENSE_GROUPS=FSF OSI X_LICENSE_DISTFILES=seafdav-3.0.4.tar.gz ce73153dd5f000c8df50322023e49742 echo x - py-seafdav/work/.patch_done.seafdav._usr_local sed 's/^X//' >py-seafdav/work/.patch_done.seafdav._usr_local << '32638783967e3ff3556cdc6b443200e4' 32638783967e3ff3556cdc6b443200e4 echo x - py-seafdav/work/.configure_done.seafdav._usr_local sed 's/^X//' >py-seafdav/work/.configure_done.seafdav._usr_local << '374e517ef2147aba9f1c7b3002ee8b59' 374e517ef2147aba9f1c7b3002ee8b59 echo x - py-seafdav/work/.extract_done.seafdav._usr_local sed 's/^X//' >py-seafdav/work/.extract_done.seafdav._usr_local << '0eeb53544014e13eb0b5edc7aba1f5aa' 0eeb53544014e13eb0b5edc7aba1f5aa echo c - py-seafdav/work/stage mkdir -p py-seafdav/work/stage > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr mkdir -p py-seafdav/work/stage/usr > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local mkdir -p py-seafdav/work/stage/usr/local > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/lib mkdir -p py-seafdav/work/stage/usr/local/lib > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/lib/python2.7 mkdir -p py-seafdav/work/stage/usr/local/lib/python2.7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages mkdir -p py-seafdav/work/stage/usr/local/lib/python2.7/site-packages > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav mkdir -p py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons mkdir -p py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile mkdir -p py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile > /dev/null 2>&1 echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.py sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.py << '1615aa365c7a49b26276f683804a856e' X# Copyright 2013 Seafile, Inc X# Licensed under the terms of seafile-pro-license.txt. X# You are not allowed to modify or redistribute this file. X# X Ximport os Ximport ccnet Xfrom pysearpc import SearpcError Xfrom seaf_utils import CCNET_CONF_DIR X Xclass SeafileDomainController(object): X X def __init__(self): X ccnet_conf_dir = os.path.normpath(os.path.expanduser(CCNET_CONF_DIR)) X X pool = ccnet.ClientPool(ccnet_conf_dir) X self.ccnet_threaded_rpc = ccnet.CcnetThreadedRpcClient(pool, req_pool=True) X X def __repr__(self): X return self.__class__.__name__ X X def getDomainRealm(self, inputURL, environ): X return "Seafile Authentication" X X def requireAuthentication(self, realmname, envrion): X return True X X def isRealmUser(self, realmname, username, environ): X return True X X def getRealmUserPassword(self, realmname, username, environ): X """ X Not applicable to seafile. X """ X return "" X X def authDomainUser(self, realmname, username, password, environ): X if "'" in username: X return False X X try: X ret = self.ccnet_threaded_rpc.validate_emailuser(username, password) X except: X return False X X if ret == 0: X return True X else: X return False 1615aa365c7a49b26276f683804a856e echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyo sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyo << '5ba3ab678da287d0b87a84bdc3660aea' Xó X˜)ZSc@sddlmZmZmZmZmZddlmZmZm Z ddl Xj Z ddl Z ddl Z ddlZddlmZddlmZddlmZmZddlmZmZddlmZmZmZmZd Ze jeƒZ d XZ!d Z"d e fd „ƒYZ#defd„ƒYZ$defd„ƒYZ%defd„ƒYZ&d„Z'd„Z(d„Z)d„Z*d„Z+dS(iÿÿÿÿ(tDAVErrortHTTP_BAD_REQUESTtHTTP_FORBIDDENtHTTP_NOT_FOUNDtHTTP_INTERNAL_ERROR(t DAVProvidert DAVCollectiontDAVNonCollectionN(t seafile_api(t SearpcError(t Xcommit_mgrtfs_mgr(tSeafFiletSeafDir(tSEAFILE_CONF_DIRtUTF8Dicttutf8_path_joint utf8_wraptreStructuredTextiitSeafileResourcecBs’eZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z Xd „Z dd X„Z d „Zd „Zd „Zd„ZRS(cCsMtt|ƒj||ƒ||_||_||_|jddƒ|_dS(Nshttp_authenticator.usernamet(tsuperRt__init__trepotrel_pathtobjtgettusername(tselftpathRRRtenviron((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR!s X   cCs X|jjS(N(Rtsize(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetContentLength)scCstj|jƒS(N(tutilt guessMimeTypeR(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetContentType+scCsdS(N(tNone(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetCreationDate3scCs|jS(N(tname(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetDisplayName6scCst|jjƒS(N(RRtobj_id(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetEtag8scCs‰t|jddƒ}|r|Stjj|jƒ\}}tj|j j X|dƒ}x-|D]%}|j j dƒ|kr\|j Sq\WdS(Nt last_modifiediÿÿÿÿsutf-8(tgetattrRR$tosRtsplitRRtget_files_last_modifiedRtidt file_nametencodeR*(Rt cached_mtimetparenttfilenametmtimestmtime((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetLastModified;s  cCstS(N(tTrue(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt supportEtagHscCstS(N(tFalse(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt supportRangesJscCs |jjƒS(sTOpen content as a stream for reading. X X See DAVResource.getContent() X (Rt Xget_stream(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt XgetContentMscCs‚|jjrttƒ‚ntj|jj|jƒdkrKttƒ‚nt j Xd|jj ƒ\}}||_ t j|dƒS(sTOpen content as a stream for writing. X X See DAVResource.beginWrite() X trwtdirtwb(tprovidertreadonlyRRRtcheck_permissionRR/Rttempfiletmkstempttmpdirt tmpfile_pathR,tfdopen(Rt contentTypetfdR((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt XbeginWriteVs ! cCs`|sLtjj|jƒ\}}tj|jj|j|||j dƒntj |jƒdS(N( R,RR-RRtput_fileRR/RGRR$tunlink(Rt XwithErrorsR3R4((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytendWritefs XcCs‰|jjrttƒ‚ntj|jj|jƒdkrKttƒ‚nt j Xj |j ƒ\}}tj |jj|||jƒtS(NR>(RARBRRRRCRR/RR,RR-Rtdel_fileR8(RR3R4((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt handleDeletems !c XCs‹|jjrttƒ‚n|jdƒjddƒ}t|ƒdkrWttƒ‚n|d}|d}tj j|ƒ\}}t X||j ƒ}t j |j|j ƒdkrÂttƒ‚ntj j|jƒ\}} | sòttƒ‚nt j|j|ƒsttƒ‚nt j|j|ƒ} X| XdkrVt j|j|||j ƒnt j|jj|| |j|||j ttƒ tS(Nt/iiR>(RARBRRtstripR-tlenRR,Rt getRepoByNameRRRCR/Rtis_valid_filenametget_file_id_by_pathR$RPt move_fileRt NEED_PROGRESSt SYNCHRONOUSR8( RtdestPathtpartst repo_nameRtdest_dirt dest_filet dest_repotsrc_dirtsrc_filet file_id_dest((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt XhandleMoveys,  X X c XCsK|jjrttƒ‚n|jdƒjddƒ}t|ƒdkrWttƒ‚n|d}|d}tj j|ƒ\}}t X||j ƒ}t j |j|j ƒdkrÂttƒ‚ntj j|jƒ\} } X| Xsòttƒ‚nt j|j|ƒsttƒ‚nt j|jj| | X|j|||j ttƒ tS(NRRiiR>(RARBRRRSR-RTRR,RRURRRCR/RRVt copy_fileRRYRZR8( RR[t depthInfinityR\R]RR^R_R`RaRb((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt XhandleCopyšs&  X XN(t__name__t X__module__RR R#R%R'R)R7R9R;R=R$RKRORQRdRg(((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR s           !tSeafDirResourcecBs†eZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z Xd „Z d X„Z d „Z d „Zd „ZRS(cCsMtt|ƒj||ƒ||_||_||_|jddƒ|_dS(Nshttp_authenticator.usernameR(RRjRRRRRR(RRRRRR((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyRºs X   cCsdS(N(R$(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR%ÂscCs|jS(N(R&(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR'ÅscCsdS(N(R$(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetDirectoryInfoÇscCst|jjƒS(N(RRR((R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR)ÉscCsdS(N(R$(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR7ËscCsZg}x%|jjD]}|j|dƒqWx%|jjD]}|j|dƒq;W|S(Ni(Rtdirstappendtfiles(Rtnamelistte((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetMemberNamesÏs cCs¢dj|j|gƒ}dj|j|gƒ}|jj|ƒ}|sWttƒ‚nt|tƒr‚t ||j X|||j ƒSt ||j X|||j ƒSdS(NRR( tjoinRRRtlookupRRt XisinstanceR RRRRj(RR&tmember_rel_patht member_pathtmember((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt getMember×sc Cs¢g}|j}|jdkr‰g}y"tj|jj|jdƒ}Wnttƒ‚nXt ƒ}x!|D]}|j X||j sInvalid file nameRR(RARBRRRRCRR/Rtpost_empty_fileRR tmsgRtget_repoRRrRtresolveRepoPathRtR RR(RR&RpRRuRvR((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytcreateEmptyResource Xs& !&cCs˜|jjrttƒ‚ntj|jj|jƒdkrKttƒ‚ntj |jj|ƒsrtt Xƒ‚ntj |jj|j ||jƒdS(s_Create a new collection as member of self. X X See DAVResource.createCollection() X R>N( RARBRRRRCRR/RRVRtpost_dirR(RR&((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytcreateCollection*s !cCsž|jjrttƒ‚ntj|jj|jƒdkrKttƒ‚nt j Xj |j ƒ\}}|s{tt ƒ‚ntj|jj|||jƒtS(NR>(RARBRRRRCRR/RR,RR-RRRPR8(RR3R4((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyRQ;s !c X XCsK|jjrttƒ‚n|jdƒjddƒ}t|ƒdkrWttƒ‚n|d}|d}tj j|ƒ\}}t X||j ƒ}t j |j|j ƒdkrÂttƒ‚ntj j|jƒ\}} | sòttƒ‚nt j|j|ƒsttƒ‚nt j|jj|| |j|||j ttƒ tS(NRRiiR>(RARBRRRSR-RTRR,RRURRRCR/RRVRXRRYRZR8( XRR[R\R]RR^R_R`RaRb((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyRdKs&  X Xc XCsK|jjrttƒ‚n|jdƒjddƒ}t|ƒdkrWttƒ‚n|d}|d}tj j|ƒ\}}t X||j ƒ}t j |j|j ƒdkrÂttƒ‚ntj j|jƒ\} } X| Xsòttƒ‚nt j|j|ƒsttƒ‚nt j|jj| | X|j|||j ttƒ tS(NRRiiR>(RARBRRRSR-RTRR,RRURRRCR/RRVReRRYRZR8( RR[RfR\R]RR^R_R`RaRb((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyRggs&  X X(RhRiRR%R'RkR)R7RqRxR‡RŒRŽRQRdRg(((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyRj¹s        &   t RootResourcecBseZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z Xd „Z d X„Z d „Z d „Zd „Zd„ZRS(cCs&tt|ƒjd|ƒ||_dS(NRR(RRRR(RRR((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR„scCsdS(N(R$(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR%‰scCsdS(NR((R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR'ŒscCsdS(N(R$(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyRkŽscCsdS(N(R$(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR)scCsdS(N(R$(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR7’scCsÛt|jƒ}i}xA|D]9}||j}|sH|g||j((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt__repr__ñs  cCsØ|jd7_|jddƒ}|dks9|dkrFt||ƒS|jdƒ}yt||ƒ\}}}Wn)tk Xrœ}|jtkr–dS‚nXt |t XƒrÂt |||||ƒSt |||||ƒS(sTReturn info dictionary for path. X X See DAVProvider.getResourceInst() X ishttp_authenticator.usernameRRRN( t_count_getResourceInstRRtrstript resolvePathRtvalueRR$RtR RjR(RRRRRRRRp((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetResourceInstøs (RhRiR:RR R¥(((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyRšès  c XCsõ|jdƒjdƒ}t|ƒdkr9ttƒ‚n|jdƒ}t||ƒ}d}t|ƒ}t|ƒ}d}xj|D]b} |j| ƒ}| s½t |t XƒrÌ||dkrÌtt ƒ‚n|d| 7}|d7}q‚W|||fS(NRRiRi( RSR-RTRRtpopRUR™RsRtR R( XRRtsegmentsR]RRRtn_segstitsegment((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR£s    &cCsŽ|jdƒjdƒ}t|ƒ}t|ƒ}d}xQ|D]I}|j|ƒ}| sxt|tƒr|||dkr|dS|d7}q=W|S(NRRii(RSR-R™RTRsRtR R$(RRR§RR¨R©Rª((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR‹(s   &cCs7tj|j|j|jƒ}tj|j|j|ƒS(N(R Xtget_commit_root_idR/Ryt head_cmmt_idR R}R~(Rtroot_id((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR™9scCs“t|ƒ}d}x'|D]}|j|kr|}PqqW|sx2|D]*}|jd|j|krI|}PqIqIW|sttƒ‚qn|S(NR(R‘R$R&R/RR(R]Rtrepostret_repoR((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyRU=s   c sái‰‡fd†}ytj|ƒ}Wn'tk XrQ}tjd|jƒnXx|D]}||jƒqYWytj|ddƒ}Wn'tk Xr²}tjd|jƒnXx|D]}||jƒqºWyt j X|ƒ}Wn$tk Xr X}tjd|ƒnXx„|D]|}yLtj |jƒ} x3| D]+} Xˆj | XjƒrRq4n| Xˆ| Xjs.(   "™Êe(     5ba3ab678da287d0b87a84bdc3660aea echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.pyo sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.pyo << '7811e4ac533e3db9e3cc6f5b4699500b' Xó X˜)ZSc@sdS(N((((sI/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.pyts 7811e4ac533e3db9e3cc6f5b4699500b echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.py sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.py << 'ff1eaca37c811d14c21fcc95931b5ea2' ff1eaca37c811d14c21fcc95931b5ea2 echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyo sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyo << 'af39215ed8e2164451477a4c34cc4164' Xó X˜)ZSc@sRddlZddlZddlmZddlmZdefd„ƒYZdS(iÿÿÿÿN(t SearpcError(tCCNET_CONF_DIRtSeafileDomainControllercBsGeZd„Zd„Zd„Zd„Zd„Zd„Zd„ZRS(cCsItjjtjjtƒƒ}tj|ƒ}tj|dtƒ|_ dS(Ntreq_pool( Xtostpathtnormpatht XexpanduserRtccnett XClientPooltCcnetThreadedRpcClienttTruetccnet_threaded_rpc(tselftccnet_conf_dirtpool((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyt__init__ scCs X|jjS(N(t __class__t__name__(R ((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyt__repr__scCsdS(NsSeafile Authentication((R tinputURLtenviron((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pytgetDomainRealmscCstS(N(R (R t realmnametenvrion((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pytrequireAuthenticationscCstS(N(R (R RtusernameR((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyt isRealmUserscCsdS(s, X Not applicable to seafile. X t((R RRR((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pytgetRealmUserPasswordscCsLd|krtSy|jj||ƒ}WntSX|dkrDtStSdS(Nt'i(tFalseR tvalidate_emailuserR (R RRtpasswordRtret((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pytauthDomainUser%s  ( Rt X__module__RRRRRRR#(((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyR s      (RRtpysearpcRt Xseaf_utilsRtobjectR(((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyts   af39215ed8e2164451477a4c34cc4164 echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyo sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyo << '3a223ce47ea2c21d2cb37539e6de07e8' Xó X˜)ZSc@s\ddlZd„ZeƒZd„ZeƒZd„Zdefd„ƒYZd„ZdS(iÿÿÿÿNcCs5ytjd}Wntk Xr0tdƒ‚nX|S(NtSEAFILE_CONF_DIRsSEAFILE_CONF_DIR is not set(tostenvirontKeyErrort RuntimeError(R((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pytget_seafile_conf_dir s X cCs5ytjd}Wntk Xr0tdƒ‚nX|S(NtCCNET_CONF_DIRsCCNET_CONF_DIR is not set(RRRR(R((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pytget_ccnet_conf_dirs X cCs%t|tƒr!|jdƒ}n|S(Nsutf-8(t Xisinstancetunicodetencode(ts((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyt utf8_wrapstUTF8DictcBs)eZdZd„Zd„Zd„ZRS(s”A dict whose keys are always converted to utf8, so we don't need to X care whether the param for the key is in utf-8 or unicode when set/get X X cCstj|ƒdS(N(tdictt__init__(tself((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyR*scCstj|t|ƒ|ƒdS(N(Rt __setitem__R (Rtktv((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyR-scCstj|t|ƒƒS(N(Rt __getitem__R (RR((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyR0s(t__name__t X__module__t__doc__RRR(((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyR %s  cGs/g|D]}t|ƒ^q}tjj|ŒS(N(R Rtpathtjoin(targstarg((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pytutf8_path_join4s( RRRRRR RR R(((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyt s       3a223ce47ea2c21d2cb37539e6de07e8 echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyc sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyc << '57f2b011303c52ee503257ab7019187f' Xó X˜)ZSc@sddlmZmZmZmZmZddlmZmZm Z ddl Xj Z ddl Z ddl Z ddlZddlmZddlmZddlmZmZddlmZmZddlmZmZmZmZd Ze jeƒZ d XZ!d Z"d e fd „ƒYZ#defd„ƒYZ$defd„ƒYZ%defd„ƒYZ&d„Z'd„Z(d„Z)d„Z*d„Z+dS(iÿÿÿÿ(tDAVErrortHTTP_BAD_REQUESTtHTTP_FORBIDDENtHTTP_NOT_FOUNDtHTTP_INTERNAL_ERROR(t DAVProvidert DAVCollectiontDAVNonCollectionN(t seafile_api(t SearpcError(t Xcommit_mgrtfs_mgr(tSeafFiletSeafDir(tSEAFILE_CONF_DIRtUTF8Dicttutf8_path_joint utf8_wraptreStructuredTextiitSeafileResourcecBs’eZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z Xd „Z dd X„Z d „Zd „Zd „Zd„ZRS(cCsMtt|ƒj||ƒ||_||_||_|jddƒ|_dS(Nshttp_authenticator.usernamet(tsuperRt__init__trepotrel_pathtobjtgettusername(tselftpathRRRtenviron((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR!s X   cCs X|jjS(N(Rtsize(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetContentLength)scCstj|jƒS(N(tutilt guessMimeTypeR(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetContentType+scCsdS(N(tNone(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetCreationDate3scCs|jS(N(tname(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetDisplayName6scCst|jjƒS(N(RRtobj_id(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetEtag8scCs‰t|jddƒ}|r|Stjj|jƒ\}}tj|j j X|dƒ}x-|D]%}|j j dƒ|kr\|j Sq\WdS(Nt last_modifiediÿÿÿÿsutf-8(tgetattrRR$tosRtsplitRRtget_files_last_modifiedRtidt file_nametencodeR*(Rt cached_mtimetparenttfilenametmtimestmtime((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetLastModified;s  cCstS(N(tTrue(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt supportEtagHscCstS(N(tFalse(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt supportRangesJscCs|j st‚|jjƒS(sTOpen content as a stream for reading. X X See DAVResource.getContent() X (t isCollectiontAssertionErrorRt Xget_stream(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt XgetContentMscCs’|j st‚|jjr+ttƒ‚ntj|jj |j Xƒdkr[ttƒ‚nt j d|jj ƒ\}}||_tj|dƒS(sTOpen content as a stream for writing. X X See DAVResource.beginWrite() X trwtdirtwb(R<R=tprovidertreadonlyRRRtcheck_permissionRR/Rttempfiletmkstempttmpdirt tmpfile_pathR,tfdopen(Rt contentTypetfdR((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt XbeginWriteVs ! cCs`|sLtjj|jƒ\}}tj|jj|j|||j dƒntj |jƒdS(N( R,RR-RRtput_fileRR/RIRR$tunlink(Rt XwithErrorsR3R4((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytendWritefs XcCs‰|jjrttƒ‚ntj|jj|jƒdkrKttƒ‚nt j Xj |j ƒ\}}tj |jj|||jƒtS(NR@(RCRDRRRRERR/RR,RR-Rtdel_fileR8(RR3R4((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt handleDeletems !c XCs‹|jjrttƒ‚n|jdƒjddƒ}t|ƒdkrWttƒ‚n|d}|d}tj j|ƒ\}}t X||j ƒ}t j |j|j ƒdkrÂttƒ‚ntj j|jƒ\}} | sòttƒ‚nt j|j|ƒsttƒ‚nt j|j|ƒ} X| XdkrVt j|j|||j ƒnt j|jj|| |j|||j ttƒ tS(Nt/iiR@(RCRDRRtstripR-tlenRR,Rt getRepoByNameRRRER/Rtis_valid_filenametget_file_id_by_pathR$RRt move_fileRt NEED_PROGRESSt SYNCHRONOUSR8( RtdestPathtpartst repo_nameRtdest_dirt dest_filet dest_repotsrc_dirtsrc_filet file_id_dest((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt XhandleMoveys,  X X c XCsK|jjrttƒ‚n|jdƒjddƒ}t|ƒdkrWttƒ‚n|d}|d}tj j|ƒ\}}t X||j ƒ}t j |j|j ƒdkrÂttƒ‚ntj j|jƒ\} } X| Xsòttƒ‚nt j|j|ƒsttƒ‚nt j|jj| | X|j|||j ttƒ tS(NRTiiR@(RCRDRRRUR-RVRR,RRWRRRER/RRXt copy_fileRR[R\R8( RR]t depthInfinityR^R_RR`RaRbRcRd((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt XhandleCopyšs&  X XN(t__name__t X__module__RR R#R%R'R)R7R9R;R?R$RMRQRSRfRi(((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR s           !tSeafDirResourcecBs†eZd„Zd„Zd„Zd„Zd„Zd„Zd„Zd„Z d„Z Xd „Z d X„Z d „Z d „Zd „ZRS(cCsMtt|ƒj||ƒ||_||_||_|jddƒ|_dS(Nshttp_authenticator.usernameR(RRlRRRRRR(RRRRRR((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyRºs X   cCsdS(N(R$(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR%ÂscCs|jS(N(R&(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR'ÅscCsdS(N(R$(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetDirectoryInfoÇscCst|jjƒS(N(RRR((R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR)ÉscCsdS(N(R$(R((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyR7ËscCsZg}x%|jjD]}|j|dƒqWx%|jjD]}|j|dƒq;W|S(Ni(Rtdirstappendtfiles(Rtnamelistte((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pytgetMemberNamesÏs cCs¢dj|j|gƒ}dj|j|gƒ}|jj|ƒ}|sWttƒ‚nt|tƒr‚t ||j X|||j ƒSt ||j X|||j ƒSdS(NRT( tjoinRRRtlookupRRt XisinstanceR RRRRl(RR&tmember_rel_patht member_pathtmember((sU/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.pyt getMember×sc Cs¢g}|j}|jdkr‰g}y"tj|jj|jdƒ}Wnttƒ‚nXt ƒ}x!|D]}|j X||j s.(   "™Êe(     57f2b011303c52ee503257ab7019187f echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.py sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seafile_dav_provider.py << '9ecba76243042e877b9636beb158eca8' X# Copyright 2013 Seafile, Inc X# Licensed under the terms of seafile-pro-license.txt. X# You are not allowed to modify or redistribute this file. X# X Xfrom wsgidav.dav_error import DAVError, HTTP_BAD_REQUEST, HTTP_FORBIDDEN, \ X HTTP_NOT_FOUND, HTTP_INTERNAL_ERROR Xfrom wsgidav.dav_provider import DAVProvider, DAVCollection, DAVNonCollection X Ximport wsgidav.util as util Ximport os X#import mimetypes Ximport tempfile X Ximport seaserv Xfrom seaserv import seafile_api Xfrom pysearpc import SearpcError Xfrom seafobj import commit_mgr, fs_mgr Xfrom seafobj.fs import SeafFile, SeafDir Xfrom seaf_utils import SEAFILE_CONF_DIR, UTF8Dict, utf8_path_join, utf8_wrap X X__docformat__ = "reStructuredText" X X_logger = util.getModuleLogger(__name__) X XNEED_PROGRESS = 0 XSYNCHRONOUS = 1 X X#=============================================================================== X# SeafileResource X#=============================================================================== Xclass SeafileResource(DAVNonCollection): X def __init__(self, path, repo, rel_path, obj, environ): X super(SeafileResource, self).__init__(path, environ) X self.repo = repo X self.rel_path = rel_path X self.obj = obj X self.username = environ.get("http_authenticator.username", "") X X # Getter methods for standard live properties X def getContentLength(self): X return self.obj.size X def getContentType(self): X# (mimetype, _mimeencoding) = mimetypes.guess_type(self.path) X# print "mimetype(%s): %r, %r" % (self.path, mimetype, _mimeencoding) X# if not mimetype: X# mimetype = "application/octet-stream" X# print "mimetype(%s): return %r" % (self.path, mimetype) X# return mimetype X return util.guessMimeType(self.path) X def getCreationDate(self): X# return int(time.time()) X return None X def getDisplayName(self): X return self.name X def getEtag(self): X return utf8_wrap(self.obj.obj_id) X X def getLastModified(self): X cached_mtime = getattr(self.obj, 'last_modified', None) X if cached_mtime: X return cached_mtime X X parent, filename = os.path.split(self.rel_path) X mtimes = seafile_api.get_files_last_modified(self.repo.id, parent, -1) X for mtime in mtimes: X if (mtime.file_name.encode('utf-8') == filename): X return mtime.last_modified X X return None X X def supportEtag(self): X return True X def supportRanges(self): X return False X X def getContent(self): X """Open content as a stream for reading. X X See DAVResource.getContent() X """ X assert not self.isCollection X return self.obj.get_stream() X X X def beginWrite(self, contentType=None): X """Open content as a stream for writing. X X See DAVResource.beginWrite() X """ X assert not self.isCollection X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X if seafile_api.check_permission(self.repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X fd, path = tempfile.mkstemp(dir=self.provider.tmpdir) X self.tmpfile_path = path X return os.fdopen(fd, "wb") X X def endWrite(self, withErrors): X if not withErrors: X parent, filename = os.path.split(self.rel_path) X seafile_api.put_file(self.repo.id, self.tmpfile_path, parent, filename, X self.username, None) X os.unlink(self.tmpfile_path) X X def handleDelete(self): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X if seafile_api.check_permission(self.repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X parent, filename = os.path.split(self.rel_path) X seafile_api.del_file(self.repo.id, parent, filename, self.username) X X return True X X def handleMove(self, destPath): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X parts = destPath.strip("/").split("/", 1) X if len(parts) <= 1: X raise DAVError(HTTP_BAD_REQUEST) X repo_name = parts[0] X rel_path = parts[1] X X dest_dir, dest_file = os.path.split(rel_path) X dest_repo = getRepoByName(repo_name, self.username) X X if seafile_api.check_permission(dest_repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X src_dir, src_file = os.path.split(self.rel_path) X if not src_file: X raise DAVError(HTTP_BAD_REQUEST) X X if not seafile_api.is_valid_filename(dest_repo.id, dest_file): X raise DAVError(HTTP_BAD_REQUEST) X X # some clients such as GoodReader requires "overwrite" semantics X file_id_dest = seafile_api.get_file_id_by_path(dest_repo.id, rel_path) X if file_id_dest != None: X seafile_api.del_file(dest_repo.id, dest_dir, dest_file, self.username) X X seafile_api.move_file(self.repo.id, src_dir, src_file, X dest_repo.id, dest_dir, dest_file, self.username, NEED_PROGRESS, SYNCHRONOUS) X X return True X X def handleCopy(self, destPath, depthInfinity): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X parts = destPath.strip("/").split("/", 1) X if len(parts) <= 1: X raise DAVError(HTTP_BAD_REQUEST) X repo_name = parts[0] X rel_path = parts[1] X X dest_dir, dest_file = os.path.split(rel_path) X dest_repo = getRepoByName(repo_name, self.username) X X if seafile_api.check_permission(dest_repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X src_dir, src_file = os.path.split(self.rel_path) X if not src_file: X raise DAVError(HTTP_BAD_REQUEST) X X if not seafile_api.is_valid_filename(dest_repo.id, dest_file): X raise DAVError(HTTP_BAD_REQUEST) X X seafile_api.copy_file(self.repo.id, src_dir, src_file, X dest_repo.id, dest_dir, dest_file, self.username, NEED_PROGRESS, SYNCHRONOUS) X X return True X X#=============================================================================== X# SeafDirResource X#=============================================================================== Xclass SeafDirResource(DAVCollection): X def __init__(self, path, repo, rel_path, obj, environ): X super(SeafDirResource, self).__init__(path, environ) X self.repo = repo X self.rel_path = rel_path X self.obj = obj X self.username = environ.get("http_authenticator.username", "") X X # Getter methods for standard live properties X def getCreationDate(self): X# return int(time.time()) X return None X def getDisplayName(self): X return self.name X def getDirectoryInfo(self): X return None X def getEtag(self): X return utf8_wrap(self.obj.obj_id) X def getLastModified(self): X# return int(time.time()) X return None X X def getMemberNames(self): X namelist = [] X for e in self.obj.dirs: X namelist.append(e[0]) X for e in self.obj.files: X namelist.append(e[0]) X return namelist X X def getMember(self, name): X member_rel_path = "/".join([self.rel_path, name]) X member_path = "/".join([self.path, name]) X member = self.obj.lookup(name) X X if not member: X raise DAVError(HTTP_NOT_FOUND) X X if isinstance(member, SeafFile): X return SeafileResource(member_path, self.repo, member_rel_path, member, self.environ) X else: X return SeafDirResource(member_path, self.repo, member_rel_path, member, self.environ) X X def getMemberList(self): X member_list = [] X d = self.obj X X if d.version == 0: X file_mtimes = [] X try: X file_mtimes = seafile_api.get_files_last_modified(self.repo.id, self.rel_path, -1) X except: X raise DAVError(HTTP_INTERNAL_ERROR) X X mtimes = UTF8Dict() X for entry in file_mtimes: X mtimes[entry.file_name] = entry.last_modified X for name, dent in d.dirents.iteritems(): X member_path = utf8_path_join(self.path, name) X member_rel_path = utf8_path_join(self.rel_path, name) X X if dent.is_dir(): X obj = fs_mgr.load_seafdir(d.store_id, d.version, dent.id) X res = SeafDirResource(member_path, self.repo, member_rel_path, obj, self.environ) X elif dent.is_file(): X obj = fs_mgr.load_seafile(d.store_id, d.version, dent.id) X res = SeafileResource(member_path, self.repo, member_rel_path, obj, self.environ) X else: X continue X X if d.version == 1: X obj.last_modified = dent.mtime X else: X obj.last_modified = mtimes[name] X X member_list.append(res) X X return member_list X X # --- Read / write --------------------------------------------------------- X X def createEmptyResource(self, name): X """Create an empty (length-0) resource. X X See DAVResource.createEmptyResource() X """ X assert not "/" in name X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X if seafile_api.check_permission(self.repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X try: X seafile_api.post_empty_file(self.repo.id, self.rel_path, name, self.username) X except SearpcError, e: X if e.msg == 'Invalid file name': X raise DAVError(HTTP_BAD_REQUEST) X raise X X # Repo was updated, can't use self.repo X repo = seafile_api.get_repo(self.repo.id) X if not repo: X raise DAVError(HTTP_INTERNAL_ERROR) X X member_rel_path = "/".join([self.rel_path, name]) X member_path = "/".join([self.path, name]) X obj = resolveRepoPath(repo, member_rel_path) X if not obj or not isinstance(obj, SeafFile): X raise DAVError(HTTP_INTERNAL_ERROR) X X return SeafileResource(member_path, repo, member_rel_path, obj, self.environ) X X def createCollection(self, name): X """Create a new collection as member of self. X X See DAVResource.createCollection() X """ X assert not "/" in name X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X if seafile_api.check_permission(self.repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X if not seafile_api.is_valid_filename(self.repo.id, name): X raise DAVError(HTTP_BAD_REQUEST) X X seafile_api.post_dir(self.repo.id, self.rel_path, name, self.username) X X def handleDelete(self): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X if seafile_api.check_permission(self.repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X parent, filename = os.path.split(self.rel_path) X # Can't delete repo root X if not filename: X raise DAVError(HTTP_BAD_REQUEST) X X seafile_api.del_file(self.repo.id, parent, filename, self.username) X X return True X X def handleMove(self, destPath): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X parts = destPath.strip("/").split("/", 1) X if len(parts) <= 1: X raise DAVError(HTTP_BAD_REQUEST) X repo_name = parts[0] X rel_path = parts[1] X X dest_dir, dest_file = os.path.split(rel_path) X dest_repo = getRepoByName(repo_name, self.username) X X if seafile_api.check_permission(dest_repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X src_dir, src_file = os.path.split(self.rel_path) X if not src_file: X raise DAVError(HTTP_BAD_REQUEST) X X if not seafile_api.is_valid_filename(dest_repo.id, dest_file): X raise DAVError(HTTP_BAD_REQUEST) X X seafile_api.move_file(self.repo.id, src_dir, src_file, X dest_repo.id, dest_dir, dest_file, self.username, NEED_PROGRESS, SYNCHRONOUS) X X return True X X def handleCopy(self, destPath, depthInfinity): X if self.provider.readonly: X raise DAVError(HTTP_FORBIDDEN) X X parts = destPath.strip("/").split("/", 1) X if len(parts) <= 1: X raise DAVError(HTTP_BAD_REQUEST) X repo_name = parts[0] X rel_path = parts[1] X X dest_dir, dest_file = os.path.split(rel_path) X dest_repo = getRepoByName(repo_name, self.username) X X if seafile_api.check_permission(dest_repo.id, self.username) != "rw": X raise DAVError(HTTP_FORBIDDEN) X X src_dir, src_file = os.path.split(self.rel_path) X if not src_file: X raise DAVError(HTTP_BAD_REQUEST) X X if not seafile_api.is_valid_filename(dest_repo.id, dest_file): X raise DAVError(HTTP_BAD_REQUEST) X X seafile_api.copy_file(self.repo.id, src_dir, src_file, X dest_repo.id, dest_dir, dest_file, self.username, NEED_PROGRESS, SYNCHRONOUS) X X return True X Xclass RootResource(DAVCollection): X def __init__(self, username, environ): X super(RootResource, self).__init__("/", environ) X self.username = username X X # Getter methods for standard live properties X def getCreationDate(self): X# return int(time.time()) X return None X def getDisplayName(self): X return "" X def getDirectoryInfo(self): X return None X def getEtag(self): X return None X def getLastModified(self): X# return int(time.time()) X return None X X def getMemberNames(self): X all_repos = getAccessibleRepos(self.username) X X name_hash = {} X for r in all_repos: X r_list = name_hash[r.name] X if not r_list: X name_hash[r.name] = [r] X else: X r_list.append(r) X X namelist = [] X for r_list in name_hash.values(): X if len(r_list) == 1: X repo = r_list[0] X namelist.append(repo.name) X else: X for repo in r_list: X unique_name = repo.name + "-" + repo.id X namelist.append(unique_name) X X return namelist X X def getMember(self, name): X repo = getRepoByName(name, self.username) X return self._createRootRes(repo, name) X X def getMemberList(self): X """ X Overwrite this method for better performance. X The default implementation call getMemberNames() then call getMember() X for each name. This calls getAccessibleRepos() for too many times. X """ X all_repos = getAccessibleRepos(self.username) X X name_hash = {} X for r in all_repos: X r_list = name_hash.get(r.name, []) X if not r_list: X name_hash[r.name] = [r] X else: X r_list.append(r) X X member_list = [] X for r_list in name_hash.values(): X if len(r_list) == 1: X repo = r_list[0] X res = self._createRootRes(repo, repo.name) X member_list.append(res) X else: X for repo in r_list: X unique_name = repo.name + "-" + repo.id X res = self._createRootRes(repo, unique_name) X member_list.append(res) X X return member_list X X def _createRootRes(self, repo, name): X obj = get_repo_root_seafdir(repo) X return SeafDirResource("/"+name, repo, "", obj, self.environ) X X # --- Read / write --------------------------------------------------------- X X def createEmptyResource(self, name): X raise DAVError(HTTP_FORBIDDEN) X X def createCollection(self, name): X raise DAVError(HTTP_FORBIDDEN) X X def handleDelete(self): X raise DAVError(HTTP_FORBIDDEN) X X def handleMove(self, destPath): X raise DAVError(HTTP_FORBIDDEN) X X def handleCopy(self, destPath, depthInfinity): X raise DAVError(HTTP_FORBIDDEN) X X X#=============================================================================== X# SeafileProvider X#=============================================================================== Xclass SeafileProvider(DAVProvider): X X def __init__(self, readonly=False): X super(SeafileProvider, self).__init__() X self.readonly = readonly X self.tmpdir = os.path.join(SEAFILE_CONF_DIR, "webdavtmp") X if not os.access(self.tmpdir, os.F_OK): X os.mkdir(self.tmpdir) X X def __repr__(self): X rw = "Read-Write" X if self.readonly: X rw = "Read-Only" X return "%s for Seafile (%s)" % (self.__class__.__name__, rw) X X X def getResourceInst(self, path, environ): X """Return info dictionary for path. X X See DAVProvider.getResourceInst() X """ X self._count_getResourceInst += 1 X X username = environ.get("http_authenticator.username", "") X X if path == "/" or path == "": X return RootResource(username, environ) X X path = path.rstrip("/") X try: X repo, rel_path, obj = resolvePath(path, username) X except DAVError, e: X if e.value == HTTP_NOT_FOUND: X return None X raise X X if isinstance(obj, SeafDir): X return SeafDirResource(path, repo, rel_path, obj, environ) X return SeafileResource(path, repo, rel_path, obj, environ) X Xdef resolvePath(path, username): X segments = path.strip("/").split("/") X if len(segments) == 0: X raise DAVError(HTTP_BAD_REQUEST) X repo_name = segments.pop(0) X X repo = getRepoByName(repo_name, username) X X rel_path = "" X obj = get_repo_root_seafdir(repo) X X n_segs = len(segments) X i = 0 X for segment in segments: X obj = obj.lookup(segment) X X if not obj or (isinstance(obj, SeafFile) and i != n_segs-1): X raise DAVError(HTTP_NOT_FOUND) X X rel_path += "/" + segment X i += 1 X X return (repo, rel_path, obj) X Xdef resolveRepoPath(repo, path): X segments = path.strip("/").split("/") X X obj = get_repo_root_seafdir(repo) X X n_segs = len(segments) X i = 0 X for segment in segments: X obj = obj.lookup(segment) X X if not obj or (isinstance(obj, SeafFile) and i != n_segs-1): X return None X X i += 1 X X return obj X Xdef get_repo_root_seafdir(repo): X root_id = commit_mgr.get_commit_root_id(repo.id, repo.version, repo.head_cmmt_id) X return fs_mgr.load_seafdir(repo.store_id, repo.version, root_id) X Xdef getRepoByName(repo_name, username): X repos = getAccessibleRepos(username) X X ret_repo = None X for repo in repos: X if repo.name == repo_name: X ret_repo = repo X break X X if not ret_repo: X for repo in repos: X if repo.name + "-" + repo.id == repo_name: X ret_repo = repo X break X if not ret_repo: X raise DAVError(HTTP_NOT_FOUND) X X return ret_repo X Xdef getAccessibleRepos(username): X all_repos = {} X X def addRepo(repo_id): X try: X if all_repos.has_key(repo_id): X return X repo = seafile_api.get_repo(repo_id) X if repo: X all_repos[repo_id] = repo X except SearpcError, e: X util.warn("Failed to get repo %.8s: %s" % (repo_id, e.msg)) X X try: X owned_repos = seafile_api.get_owned_repo_list(username) X except SearpcError, e: X util.warn("Failed to list owned repos: %s" % e.msg) X X for orepo in owned_repos: X addRepo(orepo.id) X X try: X shared_repos = seafile_api.get_share_in_repo_list(username, -1, -1) X except SearpcError, e: X util.warn("Failed to list shared repos: %s" % e.msg) X X for srepo in shared_repos: X addRepo(srepo.repo_id) X X try: X joined_groups = seaserv.get_personal_groups_by_user(username) X except SearpcError, e: X util.warn("Failed to get groups for %s" % username) X for g in joined_groups: X try: X group_repos = seafile_api.get_group_repo_list(g.id) X for repo in group_repos: X if all_repos.has_key(repo.id): X continue X all_repos[repo.id] = repo X except SearpcError, e: X util.warn("Failed to list repos in group %d" % g.id) X X # Don't include encrypted repos X ret = [] X for repo in all_repos.values(): X if not repo.encrypted: X repo.name = repo.name.encode('utf-8') X ret.append(repo) X X return ret 9ecba76243042e877b9636beb158eca8 echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.pyc sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.pyc << '5362327cc40cc812a41c55b62ecf5a72' Xó X˜)ZSc@sdS(N((((sI/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/__init__.pyts 5362327cc40cc812a41c55b62ecf5a72 echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyc sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyc << '962b145bd1cf8b863a16974a95c2ab03' Xó X˜)ZSc@sRddlZddlZddlmZddlmZdefd„ƒYZdS(iÿÿÿÿN(t SearpcError(tCCNET_CONF_DIRtSeafileDomainControllercBsGeZd„Zd„Zd„Zd„Zd„Zd„Zd„ZRS(cCsItjjtjjtƒƒ}tj|ƒ}tj|dtƒ|_ dS(Ntreq_pool( Xtostpathtnormpatht XexpanduserRtccnett XClientPooltCcnetThreadedRpcClienttTruetccnet_threaded_rpc(tselftccnet_conf_dirtpool((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyt__init__ scCs X|jjS(N(t __class__t__name__(R ((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyt__repr__scCsdS(NsSeafile Authentication((R tinputURLtenviron((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pytgetDomainRealmscCstS(N(R (R t realmnametenvrion((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pytrequireAuthenticationscCstS(N(R (R RtusernameR((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyt isRealmUserscCsdS(s, X Not applicable to seafile. X t((R RRR((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pytgetRealmUserPasswordscCsLd|krtSy|jj||ƒ}WntSX|dkrDtStSdS(Nt'i(tFalseR tvalidate_emailuserR (R RRtpasswordRtret((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pytauthDomainUser%s  ( Rt X__module__RRRRRRR#(((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyR s      (RRtpysearpcRt Xseaf_utilsRtobjectR(((sR/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/domain_controller.pyts   962b145bd1cf8b863a16974a95c2ab03 echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.py sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.py << '374047ff7424bb83d84f2d1cfdc95de7' X#!/usr/bin/env python X# -*- coding: utf-8 -*- X X# Copyright 2013 Seafile, Inc X# Licensed under the terms of seafile-pro-license.txt. X# You are not allowed to modify or redistribute this file. X# X Ximport os X Xdef get_seafile_conf_dir(): X try: X SEAFILE_CONF_DIR = os.environ['SEAFILE_CONF_DIR'] X except KeyError: X raise RuntimeError('SEAFILE_CONF_DIR is not set') X X return SEAFILE_CONF_DIR X XSEAFILE_CONF_DIR = get_seafile_conf_dir() X Xdef get_ccnet_conf_dir(): X try: X CCNET_CONF_DIR = os.environ['CCNET_CONF_DIR'] X except KeyError: X raise RuntimeError('CCNET_CONF_DIR is not set') X X return CCNET_CONF_DIR X XCCNET_CONF_DIR = get_ccnet_conf_dir() X Xdef utf8_wrap(s): X if isinstance(s, unicode): X s = s.encode('utf-8') X X return s X Xclass UTF8Dict(dict): X '''A dict whose keys are always converted to utf8, so we don't need to X care whether the param for the key is in utf-8 or unicode when set/get X X ''' X def __init__(self): X dict.__init__(self) X X def __setitem__(self, k, v): X dict.__setitem__(self, utf8_wrap(k), v) X X def __getitem__(self, k): X return dict.__getitem__(self, utf8_wrap(k)) X X Xdef utf8_path_join(*args): X args = [ utf8_wrap(arg) for arg in args ] X return os.path.join(*args) X 374047ff7424bb83d84f2d1cfdc95de7 echo x - py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyc sed 's/^X//' >py-seafdav/work/stage/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyc << 'b7163a39b7e233f825fc3bbe3070eaac' Xó X˜)ZSc@s\ddlZd„ZeƒZd„ZeƒZd„Zdefd„ƒYZd„ZdS(iÿÿÿÿNcCs5ytjd}Wntk Xr0tdƒ‚nX|S(NtSEAFILE_CONF_DIRsSEAFILE_CONF_DIR is not set(tostenvirontKeyErrort RuntimeError(R((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pytget_seafile_conf_dir s X cCs5ytjd}Wntk Xr0tdƒ‚nX|S(NtCCNET_CONF_DIRsCCNET_CONF_DIR is not set(RRRR(R((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pytget_ccnet_conf_dirs X cCs%t|tƒr!|jdƒ}n|S(Nsutf-8(t Xisinstancetunicodetencode(ts((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyt utf8_wrapstUTF8DictcBs)eZdZd„Zd„Zd„ZRS(s”A dict whose keys are always converted to utf8, so we don't need to X care whether the param for the key is in utf-8 or unicode when set/get X X cCstj|ƒdS(N(tdictt__init__(tself((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyR*scCstj|t|ƒ|ƒdS(N(Rt __setitem__R (Rtktv((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyR-scCstj|t|ƒƒS(N(Rt __getitem__R (RR((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyR0s(t__name__t X__module__t__doc__RRR(((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyR %s  cGs/g|D]}t|ƒ^q}tjj|ŒS(N(R Rtpathtjoin(targstarg((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pytutf8_path_join4s( RRRRRR RR R(((sK/usr/local/lib/python2.7/site-packages/wsgidav/addons/seafile/seaf_utils.pyt s       b7163a39b7e233f825fc3bbe3070eaac echo c - py-seafdav/work/stage/usr/local/lib/X11 mkdir -p py-seafdav/work/stage/usr/local/lib/X11 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/lib/X11/fonts mkdir -p py-seafdav/work/stage/usr/local/lib/X11/fonts > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/lib/X11/fonts/local mkdir -p py-seafdav/work/stage/usr/local/lib/X11/fonts/local > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/lib/X11/app-defaults mkdir -p py-seafdav/work/stage/usr/local/lib/X11/app-defaults > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/info mkdir -p py-seafdav/work/stage/usr/local/info > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/www mkdir -p py-seafdav/work/stage/usr/local/www > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/etc mkdir -p py-seafdav/work/stage/usr/local/etc > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/etc/rc.d mkdir -p py-seafdav/work/stage/usr/local/etc/rc.d > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/etc/devd mkdir -p py-seafdav/work/stage/usr/local/etc/devd > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/etc/newsyslog.conf.d mkdir -p py-seafdav/work/stage/usr/local/etc/newsyslog.conf.d > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/etc/pam.d mkdir -p py-seafdav/work/stage/usr/local/etc/pam.d > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/etc/man.d mkdir -p py-seafdav/work/stage/usr/local/etc/man.d > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/libexec mkdir -p py-seafdav/work/stage/usr/local/libexec > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/bin mkdir -p py-seafdav/work/stage/usr/local/bin > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/libdata mkdir -p py-seafdav/work/stage/usr/local/libdata > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/libdata/pkgconfig mkdir -p py-seafdav/work/stage/usr/local/libdata/pkgconfig > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/libdata/ldconfig mkdir -p py-seafdav/work/stage/usr/local/libdata/ldconfig > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/libdata/ldconfig32 mkdir -p py-seafdav/work/stage/usr/local/libdata/ldconfig32 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/tests mkdir -p py-seafdav/work/stage/usr/local/tests > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/sbin mkdir -p py-seafdav/work/stage/usr/local/sbin > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/include mkdir -p py-seafdav/work/stage/usr/local/include > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/include/X11 mkdir -p py-seafdav/work/stage/usr/local/include/X11 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man mkdir -p py-seafdav/work/stage/usr/local/man > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/man3 mkdir -p py-seafdav/work/stage/usr/local/man/man3 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/man9 mkdir -p py-seafdav/work/stage/usr/local/man/man9 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/cat4 mkdir -p py-seafdav/work/stage/usr/local/man/cat4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/catn mkdir -p py-seafdav/work/stage/usr/local/man/catn > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/mann mkdir -p py-seafdav/work/stage/usr/local/man/mann > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/cat9 mkdir -p py-seafdav/work/stage/usr/local/man/cat9 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/cat3 mkdir -p py-seafdav/work/stage/usr/local/man/cat3 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat1aout mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat1aout > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat2 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat8 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat8/i386 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat8/i386 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat5 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat1 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat6 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat6 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/catn mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/catn > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat4 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat4/i386 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat4/i386 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat9 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat9 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat9/i386 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat9/i386 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat3 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat3 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat7 mkdir -p py-seafdav/work/stage/usr/local/man/en.ISO8859-1/cat7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/man4 mkdir -p py-seafdav/work/stage/usr/local/man/man4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/cat7 mkdir -p py-seafdav/work/stage/usr/local/man/cat7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja mkdir -p py-seafdav/work/stage/usr/local/man/ja > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/cat6 mkdir -p py-seafdav/work/stage/usr/local/man/ja/cat6 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/man1 mkdir -p py-seafdav/work/stage/usr/local/man/ja/man1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/catl mkdir -p py-seafdav/work/stage/usr/local/man/ja/catl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/manl mkdir -p py-seafdav/work/stage/usr/local/man/ja/manl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/man6 mkdir -p py-seafdav/work/stage/usr/local/man/ja/man6 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/cat1 mkdir -p py-seafdav/work/stage/usr/local/man/ja/cat1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/man8 mkdir -p py-seafdav/work/stage/usr/local/man/ja/man8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/man2 mkdir -p py-seafdav/work/stage/usr/local/man/ja/man2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/cat5 mkdir -p py-seafdav/work/stage/usr/local/man/ja/cat5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/cat2 mkdir -p py-seafdav/work/stage/usr/local/man/ja/cat2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/cat8 mkdir -p py-seafdav/work/stage/usr/local/man/ja/cat8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/man5 mkdir -p py-seafdav/work/stage/usr/local/man/ja/man5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/man7 mkdir -p py-seafdav/work/stage/usr/local/man/ja/man7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/cat7 mkdir -p py-seafdav/work/stage/usr/local/man/ja/cat7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/cat9 mkdir -p py-seafdav/work/stage/usr/local/man/ja/cat9 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/cat3 mkdir -p py-seafdav/work/stage/usr/local/man/ja/cat3 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/man4 mkdir -p py-seafdav/work/stage/usr/local/man/ja/man4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/mann mkdir -p py-seafdav/work/stage/usr/local/man/ja/mann > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/catn mkdir -p py-seafdav/work/stage/usr/local/man/ja/catn > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/man3 mkdir -p py-seafdav/work/stage/usr/local/man/ja/man3 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/man9 mkdir -p py-seafdav/work/stage/usr/local/man/ja/man9 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ja/cat4 mkdir -p py-seafdav/work/stage/usr/local/man/ja/cat4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/man7 mkdir -p py-seafdav/work/stage/usr/local/man/man7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man5 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat8 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat2 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat5 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man2 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man8 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat1 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man6 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man6 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/manl mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/manl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/catl mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/catl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man1 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat6 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat6 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat4 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man9 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man9 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man3 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man3 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/catn mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/catn > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/mann mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/mann > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man4 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat3 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat3 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat9 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat9 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat7 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/cat7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man7 mkdir -p py-seafdav/work/stage/usr/local/man/de.ISO8859-1/man7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/cat2 mkdir -p py-seafdav/work/stage/usr/local/man/cat2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/cat8 mkdir -p py-seafdav/work/stage/usr/local/man/cat8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/man5 mkdir -p py-seafdav/work/stage/usr/local/man/man5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/man8 mkdir -p py-seafdav/work/stage/usr/local/man/man8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/man2 mkdir -p py-seafdav/work/stage/usr/local/man/man2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/cat5 mkdir -p py-seafdav/work/stage/usr/local/man/cat5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/man6 mkdir -p py-seafdav/work/stage/usr/local/man/man6 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/cat1 mkdir -p py-seafdav/work/stage/usr/local/man/cat1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/manl mkdir -p py-seafdav/work/stage/usr/local/man/manl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/catl mkdir -p py-seafdav/work/stage/usr/local/man/catl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat6 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat6 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man1 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/catl mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/catl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/manl mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/manl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man6 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man6 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat1 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man8 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man2 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat5 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat2 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat8 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man5 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man7 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat7 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat9 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat9 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat3 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat3 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man4 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/mann mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/mann > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/catn mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/catn > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man3 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man3 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man9 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/man9 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat4 mkdir -p py-seafdav/work/stage/usr/local/man/ru.KOI8-R/cat4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/cat6 mkdir -p py-seafdav/work/stage/usr/local/man/cat6 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/man/man1 mkdir -p py-seafdav/work/stage/usr/local/man/man1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share mkdir -p py-seafdav/work/stage/usr/local/share > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/emacs mkdir -p py-seafdav/work/stage/usr/local/share/emacs > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/emacs/site-lisp mkdir -p py-seafdav/work/stage/usr/local/share/emacs/site-lisp > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale mkdir -p py-seafdav/work/stage/usr/local/share/locale > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/cs mkdir -p py-seafdav/work/stage/usr/local/share/locale/cs > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/cs/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/cs/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/cy mkdir -p py-seafdav/work/stage/usr/local/share/locale/cy > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/cy/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/cy/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/en_CA mkdir -p py-seafdav/work/stage/usr/local/share/locale/en_CA > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/en_CA/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/en_CA/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/uz mkdir -p py-seafdav/work/stage/usr/local/share/locale/uz > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/uz/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/uz/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/lt mkdir -p py-seafdav/work/stage/usr/local/share/locale/lt > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/lt/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/lt/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/bn mkdir -p py-seafdav/work/stage/usr/local/share/locale/bn > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/bn/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/bn/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/tg mkdir -p py-seafdav/work/stage/usr/local/share/locale/tg > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/tg/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/tg/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ar mkdir -p py-seafdav/work/stage/usr/local/share/locale/ar > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ar/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ar/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sr@Latn mkdir -p py-seafdav/work/stage/usr/local/share/locale/sr@Latn > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sr@Latn/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/sr@Latn/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/fa mkdir -p py-seafdav/work/stage/usr/local/share/locale/fa > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/fa/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/fa/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/af mkdir -p py-seafdav/work/stage/usr/local/share/locale/af > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/af/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/af/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/he mkdir -p py-seafdav/work/stage/usr/local/share/locale/he > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/he/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/he/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/es_ES mkdir -p py-seafdav/work/stage/usr/local/share/locale/es_ES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/es_ES/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/es_ES/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/fr mkdir -p py-seafdav/work/stage/usr/local/share/locale/fr > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/fr/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/fr/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/en mkdir -p py-seafdav/work/stage/usr/local/share/locale/en > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/en/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/en/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/nl mkdir -p py-seafdav/work/stage/usr/local/share/locale/nl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/nl/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/nl/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/mn mkdir -p py-seafdav/work/stage/usr/local/share/locale/mn > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/mn/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/mn/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/mt mkdir -p py-seafdav/work/stage/usr/local/share/locale/mt > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/mt/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/mt/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ga mkdir -p py-seafdav/work/stage/usr/local/share/locale/ga > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ga/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ga/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/nb mkdir -p py-seafdav/work/stage/usr/local/share/locale/nb > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/nb/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/nb/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/gu mkdir -p py-seafdav/work/stage/usr/local/share/locale/gu > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/gu/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/gu/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/zh_TW.Big5 mkdir -p py-seafdav/work/stage/usr/local/share/locale/zh_TW.Big5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/zh_TW.Big5/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/zh_TW.Big5/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/bg mkdir -p py-seafdav/work/stage/usr/local/share/locale/bg > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/bg/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/bg/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/li mkdir -p py-seafdav/work/stage/usr/local/share/locale/li > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/li/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/li/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/kn mkdir -p py-seafdav/work/stage/usr/local/share/locale/kn > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/kn/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/kn/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/zh_TW mkdir -p py-seafdav/work/stage/usr/local/share/locale/zh_TW > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/zh_TW/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/zh_TW/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/pa mkdir -p py-seafdav/work/stage/usr/local/share/locale/pa > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/pa/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/pa/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/et mkdir -p py-seafdav/work/stage/usr/local/share/locale/et > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/et/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/et/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/bs mkdir -p py-seafdav/work/stage/usr/local/share/locale/bs > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/bs/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/bs/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/hr mkdir -p py-seafdav/work/stage/usr/local/share/locale/hr > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/hr/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/hr/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/hu mkdir -p py-seafdav/work/stage/usr/local/share/locale/hu > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/hu/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/hu/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/or mkdir -p py-seafdav/work/stage/usr/local/share/locale/or > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/or/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/or/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/wa mkdir -p py-seafdav/work/stage/usr/local/share/locale/wa > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/wa/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/wa/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/es mkdir -p py-seafdav/work/stage/usr/local/share/locale/es > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/es/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/es/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/pl mkdir -p py-seafdav/work/stage/usr/local/share/locale/pl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/pl/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/pl/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/zh_CN mkdir -p py-seafdav/work/stage/usr/local/share/locale/zh_CN > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/zh_CN/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/zh_CN/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/no mkdir -p py-seafdav/work/stage/usr/local/share/locale/no > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/no/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/no/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/gl mkdir -p py-seafdav/work/stage/usr/local/share/locale/gl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/gl/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/gl/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ne mkdir -p py-seafdav/work/stage/usr/local/share/locale/ne > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ne/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ne/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ms mkdir -p py-seafdav/work/stage/usr/local/share/locale/ms > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ms/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ms/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/es_MX mkdir -p py-seafdav/work/stage/usr/local/share/locale/es_MX > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/es_MX/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/es_MX/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/de_AT mkdir -p py-seafdav/work/stage/usr/local/share/locale/de_AT > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/de_AT/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/de_AT/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ro mkdir -p py-seafdav/work/stage/usr/local/share/locale/ro > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ro/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ro/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/it mkdir -p py-seafdav/work/stage/usr/local/share/locale/it > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/it/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/it/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ca mkdir -p py-seafdav/work/stage/usr/local/share/locale/ca > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ca/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ca/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/en_GB mkdir -p py-seafdav/work/stage/usr/local/share/locale/en_GB > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/en_GB/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/en_GB/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sr mkdir -p py-seafdav/work/stage/usr/local/share/locale/sr > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sr/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/sr/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/hi mkdir -p py-seafdav/work/stage/usr/local/share/locale/hi > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/hi/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/hi/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ta mkdir -p py-seafdav/work/stage/usr/local/share/locale/ta > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ta/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ta/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/fr_FR mkdir -p py-seafdav/work/stage/usr/local/share/locale/fr_FR > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/fr_FR/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/fr_FR/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sl mkdir -p py-seafdav/work/stage/usr/local/share/locale/sl > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sl/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/sl/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/tk mkdir -p py-seafdav/work/stage/usr/local/share/locale/tk > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/tk/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/tk/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ka mkdir -p py-seafdav/work/stage/usr/local/share/locale/ka > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ka/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ka/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/eo mkdir -p py-seafdav/work/stage/usr/local/share/locale/eo > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/eo/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/eo/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ee mkdir -p py-seafdav/work/stage/usr/local/share/locale/ee > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ee/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ee/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/en_AU mkdir -p py-seafdav/work/stage/usr/local/share/locale/en_AU > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/en_AU/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/en_AU/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/be mkdir -p py-seafdav/work/stage/usr/local/share/locale/be > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/be/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/be/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/zh mkdir -p py-seafdav/work/stage/usr/local/share/locale/zh > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/zh/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/zh/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sk mkdir -p py-seafdav/work/stage/usr/local/share/locale/sk > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sk/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/sk/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/am mkdir -p py-seafdav/work/stage/usr/local/share/locale/am > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/am/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/am/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/tr mkdir -p py-seafdav/work/stage/usr/local/share/locale/tr > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/tr/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/tr/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/da mkdir -p py-seafdav/work/stage/usr/local/share/locale/da > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/da/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/da/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/dk mkdir -p py-seafdav/work/stage/usr/local/share/locale/dk > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/dk/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/dk/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/is mkdir -p py-seafdav/work/stage/usr/local/share/locale/is > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/is/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/is/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/uk mkdir -p py-seafdav/work/stage/usr/local/share/locale/uk > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/uk/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/uk/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/nn mkdir -p py-seafdav/work/stage/usr/local/share/locale/nn > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/nn/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/nn/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/vi mkdir -p py-seafdav/work/stage/usr/local/share/locale/vi > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/vi/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/vi/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ja mkdir -p py-seafdav/work/stage/usr/local/share/locale/ja > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ja/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ja/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/de mkdir -p py-seafdav/work/stage/usr/local/share/locale/de > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/de/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/de/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ml mkdir -p py-seafdav/work/stage/usr/local/share/locale/ml > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ml/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ml/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/th mkdir -p py-seafdav/work/stage/usr/local/share/locale/th > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/th/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/th/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/fa_IR mkdir -p py-seafdav/work/stage/usr/local/share/locale/fa_IR > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/fa_IR/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/fa_IR/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sq mkdir -p py-seafdav/work/stage/usr/local/share/locale/sq > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sq/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/sq/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/el mkdir -p py-seafdav/work/stage/usr/local/share/locale/el > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/el/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/el/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/pt mkdir -p py-seafdav/work/stage/usr/local/share/locale/pt > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/pt/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/pt/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ko mkdir -p py-seafdav/work/stage/usr/local/share/locale/ko > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ko/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ko/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/lv mkdir -p py-seafdav/work/stage/usr/local/share/locale/lv > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/lv/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/lv/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/br mkdir -p py-seafdav/work/stage/usr/local/share/locale/br > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/br/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/br/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/eu mkdir -p py-seafdav/work/stage/usr/local/share/locale/eu > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/eu/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/eu/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/pt_PT mkdir -p py-seafdav/work/stage/usr/local/share/locale/pt_PT > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/pt_PT/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/pt_PT/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sv mkdir -p py-seafdav/work/stage/usr/local/share/locale/sv > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/sv/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/sv/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/fi mkdir -p py-seafdav/work/stage/usr/local/share/locale/fi > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/fi/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/fi/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/az mkdir -p py-seafdav/work/stage/usr/local/share/locale/az > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/az/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/az/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/pt_BR mkdir -p py-seafdav/work/stage/usr/local/share/locale/pt_BR > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/pt_BR/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/pt_BR/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/mk mkdir -p py-seafdav/work/stage/usr/local/share/locale/mk > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/mk/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/mk/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/id mkdir -p py-seafdav/work/stage/usr/local/share/locale/id > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/id/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/id/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/zh_CN.GB2312 mkdir -p py-seafdav/work/stage/usr/local/share/locale/zh_CN.GB2312 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/zh_CN.GB2312/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/zh_CN.GB2312/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ru mkdir -p py-seafdav/work/stage/usr/local/share/locale/ru > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/locale/ru/LC_MESSAGES mkdir -p py-seafdav/work/stage/usr/local/share/locale/ru/LC_MESSAGES > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/java mkdir -p py-seafdav/work/stage/usr/local/share/java > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/java/classes mkdir -p py-seafdav/work/stage/usr/local/share/java/classes > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/licenses mkdir -p py-seafdav/work/stage/usr/local/share/licenses > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4 mkdir -p py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4 > /dev/null 2>&1 echo x - py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4/LICENSE sed 's/^X//' >py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4/LICENSE << 'd85303e58d67bea78930155df407cfd8' XThis package has a single license: APACHE20 (Apache License 2.0). d85303e58d67bea78930155df407cfd8 echo x - py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4/catalog.mk sed 's/^X//' >py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4/catalog.mk << '69901aa9820a64c55246efc55f454222' X_LICENSE=APACHE20 X_LICENSE_NAME=Apache License 2.0 X_LICENSE_PERMS=dist-mirror dist-sell pkg-mirror pkg-sell auto-accept X_LICENSE_GROUPS=FSF OSI X_LICENSE_DISTFILES=seafdav-3.0.4.tar.gz 69901aa9820a64c55246efc55f454222 echo x - py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4/APACHE20 sed 's/^X//' >py-seafdav/work/stage/usr/local/share/licenses/py27-seafdav-3.0.4/APACHE20 << 'a6801c07defab224dfda17df63a36331' X Apache License X Version 2.0, January 2004 X http://www.apache.org/licenses/ X X TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION X X 1. Definitions. X X "License" shall mean the terms and conditions for use, reproduction, X and distribution as defined by Sections 1 through 9 of this document. X X "Licensor" shall mean the copyright owner or entity authorized by X the copyright owner that is granting the License. X X "Legal Entity" shall mean the union of the acting entity and all X other entities that control, are controlled by, or are under common X control with that entity. For the purposes of this definition, X "control" means (i) the power, direct or indirect, to cause the X direction or management of such entity, whether by contract or X otherwise, or (ii) ownership of fifty percent (50%) or more of the X outstanding shares, or (iii) beneficial ownership of such entity. X X "You" (or "Your") shall mean an individual or Legal Entity X exercising permissions granted by this License. X X "Source" form shall mean the preferred form for making modifications, X including but not limited to software source code, documentation X source, and configuration files. X X "Object" form shall mean any form resulting from mechanical X transformation or translation of a Source form, including but X not limited to compiled object code, generated documentation, X and conversions to other media types. X X "Work" shall mean the work of authorship, whether in Source or X Object form, made available under the License, as indicated by a X copyright notice that is included in or attached to the work X (an example is provided in the Appendix below). X X "Derivative Works" shall mean any work, whether in Source or Object X form, that is based on (or derived from) the Work and for which the X editorial revisions, annotations, elaborations, or other modifications X represent, as a whole, an original work of authorship. For the purposes X of this License, Derivative Works shall not include works that remain X separable from, or merely link (or bind by name) to the interfaces of, X the Work and Derivative Works thereof. X X "Contribution" shall mean any work of authorship, including X the original version of the Work and any modifications or additions X to that Work or Derivative Works thereof, that is intentionally X submitted to Licensor for inclusion in the Work by the copyright owner X or by an individual or Legal Entity authorized to submit on behalf of X the copyright owner. For the purposes of this definition, "submitted" X means any form of electronic, verbal, or written communication sent X to the Licensor or its representatives, including but not limited to X communication on electronic mailing lists, source code control systems, X and issue tracking systems that are managed by, or on behalf of, the X Licensor for the purpose of discussing and improving the Work, but X excluding communication that is conspicuously marked or otherwise X designated in writing by the copyright owner as "Not a Contribution." X X "Contributor" shall mean Licensor and any individual or Legal Entity X on behalf of whom a Contribution has been received by Licensor and X subsequently incorporated within the Work. X X 2. Grant of Copyright License. Subject to the terms and conditions of X this License, each Contributor hereby grants to You a perpetual, X worldwide, non-exclusive, no-charge, royalty-free, irrevocable X copyright license to reproduce, prepare Derivative Works of, X publicly display, publicly perform, sublicense, and distribute the X Work and such Derivative Works in Source or Object form. X X 3. Grant of Patent License. Subject to the terms and conditions of X this License, each Contributor hereby grants to You a perpetual, X worldwide, non-exclusive, no-charge, royalty-free, irrevocable X (except as stated in this section) patent license to make, have made, X use, offer to sell, sell, import, and otherwise transfer the Work, X where such license applies only to those patent claims licensable X by such Contributor that are necessarily infringed by their X Contribution(s) alone or by combination of their Contribution(s) X with the Work to which such Contribution(s) was submitted. If You X institute patent litigation against any entity (including a X cross-claim or counterclaim in a lawsuit) alleging that the Work X or a Contribution incorporated within the Work constitutes direct X or contributory patent infringement, then any patent licenses X granted to You under this License for that Work shall terminate X as of the date such litigation is filed. X X 4. Redistribution. You may reproduce and distribute copies of the X Work or Derivative Works thereof in any medium, with or without X modifications, and in Source or Object form, provided that You X meet the following conditions: X X (a) You must give any other recipients of the Work or X Derivative Works a copy of this License; and X X (b) You must cause any modified files to carry prominent notices X stating that You changed the files; and X X (c) You must retain, in the Source form of any Derivative Works X that You distribute, all copyright, patent, trademark, and X attribution notices from the Source form of the Work, X excluding those notices that do not pertain to any part of X the Derivative Works; and X X (d) If the Work includes a "NOTICE" text file as part of its X distribution, then any Derivative Works that You distribute must X include a readable copy of the attribution notices contained X within such NOTICE file, excluding those notices that do not X pertain to any part of the Derivative Works, in at least one X of the following places: within a NOTICE text file distributed X as part of the Derivative Works; within the Source form or X documentation, if provided along with the Derivative Works; or, X within a display generated by the Derivative Works, if and X wherever such third-party notices normally appear. The contents X of the NOTICE file are for informational purposes only and X do not modify the License. You may add Your own attribution X notices within Derivative Works that You distribute, alongside X or as an addendum to the NOTICE text from the Work, provided X that such additional attribution notices cannot be construed X as modifying the License. X X You may add Your own copyright statement to Your modifications and X may provide additional or different license terms and conditions X for use, reproduction, or distribution of Your modifications, or X for any such Derivative Works as a whole, provided Your use, X reproduction, and distribution of the Work otherwise complies with X the conditions stated in this License. X X 5. Submission of Contributions. Unless You explicitly state otherwise, X any Contribution intentionally submitted for inclusion in the Work X by You to the Licensor shall be under the terms and conditions of X this License, without any additional terms or conditions. X Notwithstanding the above, nothing herein shall supersede or modify X the terms of any separate license agreement you may have executed X with Licensor regarding such Contributions. X X 6. Trademarks. This License does not grant permission to use the trade X names, trademarks, service marks, or product names of the Licensor, X except as required for reasonable and customary use in describing the X origin of the Work and reproducing the content of the NOTICE file. X X 7. Disclaimer of Warranty. Unless required by applicable law or X agreed to in writing, Licensor provides the Work (and each X Contributor provides its Contributions) on an "AS IS" BASIS, X WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or X implied, including, without limitation, any warranties or conditions X of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A X PARTICULAR PURPOSE. You are solely responsible for determining the X appropriateness of using or redistributing the Work and assume any X risks associated with Your exercise of permissions under this License. X X 8. Limitation of Liability. In no event and under no legal theory, X whether in tort (including negligence), contract, or otherwise, X unless required by applicable law (such as deliberate and grossly X negligent acts) or agreed to in writing, shall any Contributor be X liable to You for damages, including any direct, indirect, special, X incidental, or consequential damages of any character arising as a X result of this License or out of the use or inability to use the X Work (including but not limited to damages for loss of goodwill, X work stoppage, computer failure or malfunction, or any and all X other commercial damages or losses), even if such Contributor X has been advised of the possibility of such damages. X X 9. Accepting Warranty or Additional Liability. While redistributing X the Work or Derivative Works thereof, You may choose to offer, X and charge a fee for, acceptance of support, warranty, indemnity, X or other liability obligations and/or rights consistent with this X License. However, in accepting such obligations, You may act only X on Your own behalf and on Your sole responsibility, not on behalf X of any other Contributor, and only if You agree to indemnify, X defend, and hold each Contributor harmless for any liability X incurred by, or claims asserted against, such Contributor by reason X of your accepting any such warranty or additional liability. X X END OF TERMS AND CONDITIONS a6801c07defab224dfda17df63a36331 echo c - py-seafdav/work/stage/usr/local/share/xml mkdir -p py-seafdav/work/stage/usr/local/share/xml > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/aclocal mkdir -p py-seafdav/work/stage/usr/local/share/aclocal > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/applications mkdir -p py-seafdav/work/stage/usr/local/share/applications > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/doc mkdir -p py-seafdav/work/stage/usr/local/share/doc > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/doc/ja mkdir -p py-seafdav/work/stage/usr/local/share/doc/ja > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/misc mkdir -p py-seafdav/work/stage/usr/local/share/misc > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/skel mkdir -p py-seafdav/work/stage/usr/local/share/skel > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/examples mkdir -p py-seafdav/work/stage/usr/local/share/examples > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/pixmaps mkdir -p py-seafdav/work/stage/usr/local/share/pixmaps > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls mkdir -p py-seafdav/work/stage/usr/local/share/nls > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ru_RU.ISO8859-5 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ru_RU.ISO8859-5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/he_IL.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/he_IL.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_GB.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_GB.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_US.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_US.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/be_BY.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/be_BY.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_NZ.US-ASCII mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_NZ.US-ASCII > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fi_FI.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fi_FI.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/sl_SI.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/sl_SI.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/es_ES.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/es_ES.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/it_IT.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/it_IT.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/et_EE.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/et_EE.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_AU.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_AU.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/zh_CN.eucCN mkdir -p py-seafdav/work/stage/usr/local/share/nls/zh_CN.eucCN > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_IE.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_IE.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/zh_HK.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/zh_HK.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/hy_AM.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/hy_AM.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/kk_KZ.PT154 mkdir -p py-seafdav/work/stage/usr/local/share/nls/kk_KZ.PT154 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ca_ES.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ca_ES.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/de_DE.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/de_DE.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/sr_YU.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/sr_YU.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ru_RU.CP866 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ru_RU.CP866 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/pt_PT.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/pt_PT.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/cs_CZ.ISO8859-2 mkdir -p py-seafdav/work/stage/usr/local/share/nls/cs_CZ.ISO8859-2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fi_FI.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fi_FI.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/zh_CN.GB2312 mkdir -p py-seafdav/work/stage/usr/local/share/nls/zh_CN.GB2312 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ja_JP.eucJP mkdir -p py-seafdav/work/stage/usr/local/share/nls/ja_JP.eucJP > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_CA.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_CA.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ca_ES.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ca_ES.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_GB.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_GB.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/de_DE.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/de_DE.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_US.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_US.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_US.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_US.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_GB.US-ASCII mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_GB.US-ASCII > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_CH.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_CH.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_CA.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_CA.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/el_GR.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/el_GR.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/kk_KZ.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/kk_KZ.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/it_CH.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/it_CH.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/el_GR.ISO8859-7 mkdir -p py-seafdav/work/stage/usr/local/share/nls/el_GR.ISO8859-7 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_FR.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_FR.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/pl_PL.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/pl_PL.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/uk_UA.KOI8-U mkdir -p py-seafdav/work/stage/usr/local/share/nls/uk_UA.KOI8-U > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/pt_PT.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/pt_PT.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/lt_LT.ISO8859-4 mkdir -p py-seafdav/work/stage/usr/local/share/nls/lt_LT.ISO8859-4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ko_KR.eucKR mkdir -p py-seafdav/work/stage/usr/local/share/nls/ko_KR.eucKR > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_FR.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_FR.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_AU.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_AU.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/et_EE.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/et_EE.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/sv_SE.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/sv_SE.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ro_RO.ISO8859-2 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ro_RO.ISO8859-2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/de_CH.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/de_CH.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/da_DK.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/da_DK.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ru_RU.CP1251 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ru_RU.CP1251 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/de_CH.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/de_CH.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/sk_SK.ISO8859-2 mkdir -p py-seafdav/work/stage/usr/local/share/nls/sk_SK.ISO8859-2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_NZ.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_NZ.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/pt_BR.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/pt_BR.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/sk_SK.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/sk_SK.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/es_ES.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/es_ES.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ru_RU.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ru_RU.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/af_ZA.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/af_ZA.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ko_KR.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ko_KR.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_AU.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_AU.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/pl_PL.ISO8859-2 mkdir -p py-seafdav/work/stage/usr/local/share/nls/pl_PL.ISO8859-2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-4 mkdir -p py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-4 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/no_NO.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/no_NO.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/nl_BE.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/nl_BE.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/zh_HK.Big5HKSCS mkdir -p py-seafdav/work/stage/usr/local/share/nls/zh_HK.Big5HKSCS > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/sl_SI.ISO8859-2 mkdir -p py-seafdav/work/stage/usr/local/share/nls/sl_SI.ISO8859-2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/es_ES.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/es_ES.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/da_DK.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/da_DK.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_BE.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_BE.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/de_CH.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/de_CH.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/hr_HR.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/hr_HR.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/uk_UA.ISO8859-5 mkdir -p py-seafdav/work/stage/usr/local/share/nls/uk_UA.ISO8859-5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/pt_PT.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/pt_PT.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ko_KR.CP949 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ko_KR.CP949 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_AU.US-ASCII mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_AU.US-ASCII > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/bg_BG.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/bg_BG.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_CA.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_CA.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/af_ZA.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/af_ZA.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/lt_LT.ISO8859-13 mkdir -p py-seafdav/work/stage/usr/local/share/nls/lt_LT.ISO8859-13 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/nl_BE.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/nl_BE.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/it_CH.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/it_CH.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_CA.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_CA.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/cs_CZ.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/cs_CZ.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/is_IS.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/is_IS.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ja_JP.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ja_JP.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/hy_AM.ARMSCII-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/hy_AM.ARMSCII-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/am_ET.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/am_ET.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/lt_LT.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/lt_LT.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/hr_HR.ISO8859-2 mkdir -p py-seafdav/work/stage/usr/local/share/nls/hr_HR.ISO8859-2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ca_ES.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ca_ES.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/hu_HU.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/hu_HU.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_NZ.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_NZ.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/da_DK.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/da_DK.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/de_AT.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/de_AT.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/hu_HU.ISO8859-2 mkdir -p py-seafdav/work/stage/usr/local/share/nls/hu_HU.ISO8859-2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/nl_BE.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/nl_BE.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/zh_CN.GB18030 mkdir -p py-seafdav/work/stage/usr/local/share/nls/zh_CN.GB18030 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/bg_BG.CP1251 mkdir -p py-seafdav/work/stage/usr/local/share/nls/bg_BG.CP1251 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_CA.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_CA.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_CA.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_CA.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/zh_TW.Big5 mkdir -p py-seafdav/work/stage/usr/local/share/nls/zh_TW.Big5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_BE.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_BE.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/de_DE.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/de_DE.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_BE.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_BE.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/de_AT.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/de_AT.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/nl_NL.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/nl_NL.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/af_ZA.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/af_ZA.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/sr_YU.ISO8859-5 mkdir -p py-seafdav/work/stage/usr/local/share/nls/sr_YU.ISO8859-5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/hi_IN.ISCII-DEV mkdir -p py-seafdav/work/stage/usr/local/share/nls/hi_IN.ISCII-DEV > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_GB.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_GB.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/it_IT.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/it_IT.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/no_NO.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/no_NO.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_CH.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_CH.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/is_IS.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/is_IS.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/zh_CN.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/zh_CN.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/pt_BR.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/pt_BR.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fi_FI.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fi_FI.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/tr_TR.ISO8859-9 mkdir -p py-seafdav/work/stage/usr/local/share/nls/tr_TR.ISO8859-9 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/be_BY.CP1131 mkdir -p py-seafdav/work/stage/usr/local/share/nls/be_BY.CP1131 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/sv_SE.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/sv_SE.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/nl_NL.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/nl_NL.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/C mkdir -p py-seafdav/work/stage/usr/local/share/nls/C > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/be_BY.ISO8859-5 mkdir -p py-seafdav/work/stage/usr/local/share/nls/be_BY.ISO8859-5 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ro_RO.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/ro_RO.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-2 mkdir -p py-seafdav/work/stage/usr/local/share/nls/la_LN.ISO8859-2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_CA.US-ASCII mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_CA.US-ASCII > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ru_RU.KOI8-R mkdir -p py-seafdav/work/stage/usr/local/share/nls/ru_RU.KOI8-R > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/zh_CN.GBK mkdir -p py-seafdav/work/stage/usr/local/share/nls/zh_CN.GBK > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/sv_SE.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/sv_SE.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/ja_JP.SJIS mkdir -p py-seafdav/work/stage/usr/local/share/nls/ja_JP.SJIS > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/it_IT.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/it_IT.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/en_NZ.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/en_NZ.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/de_AT.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/de_AT.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/no_NO.ISO8859-15 mkdir -p py-seafdav/work/stage/usr/local/share/nls/no_NO.ISO8859-15 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/it_CH.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/it_CH.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/sr_YU.ISO8859-2 mkdir -p py-seafdav/work/stage/usr/local/share/nls/sr_YU.ISO8859-2 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/is_IS.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/is_IS.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/be_BY.CP1251 mkdir -p py-seafdav/work/stage/usr/local/share/nls/be_BY.CP1251 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/zh_TW.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/zh_TW.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/la_LN.US-ASCII mkdir -p py-seafdav/work/stage/usr/local/share/nls/la_LN.US-ASCII > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/nl_NL.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/nl_NL.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/tr_TR.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/tr_TR.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_FR.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_FR.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/uk_UA.UTF-8 mkdir -p py-seafdav/work/stage/usr/local/share/nls/uk_UA.UTF-8 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/nls/fr_CH.ISO8859-1 mkdir -p py-seafdav/work/stage/usr/local/share/nls/fr_CH.ISO8859-1 > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/dict mkdir -p py-seafdav/work/stage/usr/local/share/dict > /dev/null 2>&1 echo c - py-seafdav/work/stage/usr/local/share/sgml mkdir -p py-seafdav/work/stage/usr/local/share/sgml > /dev/null 2>&1 echo x - py-seafdav/work/.license-report sed 's/^X//' >py-seafdav/work/.license-report << '58ad44ddb6dff192a7004a4ceebfad54' XThis package has a single license: APACHE20 (Apache License 2.0). 58ad44ddb6dff192a7004a4ceebfad54 echo x - py-seafdav/work/.build_done.seafdav._usr_local sed 's/^X//' >py-seafdav/work/.build_done.seafdav._usr_local << 'e37941f1f5fe1b449980c044b9a4ccf1' e37941f1f5fe1b449980c044b9a4ccf1 exit