mirror of
https://github.com/inverse-inc/sogo.git
synced 2026-02-17 07:33:57 +00:00
test: migration from Python to JavaScript
This commit is contained in:
@@ -1,8 +0,0 @@
|
|||||||
include $(GNUSTEP_MAKEFILES)/common.make
|
|
||||||
|
|
||||||
TOOL_NAME = teststrings
|
|
||||||
teststrings_OBJC_FILES += \
|
|
||||||
teststrings.m
|
|
||||||
|
|
||||||
-include GNUmakefile.preamble
|
|
||||||
include $(GNUSTEP_MAKEFILES)/tool.make
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
# compile settings
|
|
||||||
|
|
||||||
ADDITIONAL_CPPFLAGS += \
|
|
||||||
-DSOGO_MAJOR_VERSION=$(MAJOR_VERSION) \
|
|
||||||
-DSOGO_MINOR_VERSION=$(MINOR_VERSION) \
|
|
||||||
-DSOGO_SUBMINOR_VERSION=$(SUBMINOR_VERSION) \
|
|
||||||
-DSOGO_LIBDIR="@\"$(SOGO_LIBDIR)\""
|
|
||||||
|
|
||||||
ADDITIONAL_INCLUDE_DIRS += \
|
|
||||||
-D_GNU_SOURCE -I../../SOPE/ -I../../SoObjects/
|
|
||||||
|
|
||||||
ADDITIONAL_LIB_DIRS += \
|
|
||||||
-L../../SoObjects/SOGo/SOGo.framework/Versions/Current/sogo -lSOGo \
|
|
||||||
-L../../SOPE/GDLContentStore/$(GNUSTEP_OBJ_DIR)/ -lGDLContentStore \
|
|
||||||
-L../../SOPE/NGCards/$(GNUSTEP_OBJ_DIR)/ -lNGCards \
|
|
||||||
-L/usr/local/lib/sogo -L/usr/lib/sogo -L/usr/lib64/sogo -lEOControl -lNGStreams -lNGMime -lNGExtensions
|
|
||||||
|
|
||||||
|
|
||||||
ADDITIONAL_LDFLAGS += -Wl,--no-as-needed -Wl,--rpath,$(GNUSTEP_SYSTEM_LIBRARIES)/sogo
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
setup
|
|
||||||
-----
|
|
||||||
|
|
||||||
(you need "python-simplejson", "python-xml", "python-vobject", "python-dateutil" and "python-m2crypto"
|
|
||||||
in order to run the scripts on Debian)
|
|
||||||
|
|
||||||
1) copy config.py.in to config.py (make sure to never EVER add it to git)
|
|
||||||
2) edit config.py to suit your environment
|
|
||||||
3) make sure that you use a fresh database, with no prior information in it
|
|
||||||
4) make sure that SOGoCalendarDefaultRoles and SOGoContactsDefaultRoles are empty or undefined
|
|
||||||
5) run the test scripts
|
|
||||||
|
|
||||||
runnable scripts
|
|
||||||
----------------
|
|
||||||
|
|
||||||
all.py - run all scripts below at once
|
|
||||||
test-webdavsync.py - explicit
|
|
||||||
test-davacl.py - dav acl tests for calendar and addressbook modules
|
|
||||||
|
|
||||||
other scripts
|
|
||||||
-------------
|
|
||||||
|
|
||||||
propfind.py - a sample implementation of a PROPFIND request using webdavlib
|
|
||||||
|
|
||||||
* developers
|
|
||||||
------------
|
|
||||||
|
|
||||||
- Test methods are always prefixed with "test". Sometimes, it's easier to
|
|
||||||
track down a problem by enabling only one test at a time. One possible method
|
|
||||||
is to replace "def test" with "def xtest" and replace it back when the
|
|
||||||
problems are solved.
|
|
||||||
|
|
||||||
- Test failures start with "FAIL:". Those are the ones that indicate possible
|
|
||||||
bugs in the application, if the test is itself known to work.
|
|
||||||
For example like this:
|
|
||||||
|
|
||||||
======================================================================
|
|
||||||
FAIL: 'modify' PUBLIC, 'view all' PRIVATE, 'view d&t' confidential
|
|
||||||
----------------------------------------------------------------------
|
|
||||||
Traceback (most recent call last):
|
|
||||||
File "./davacl.py", line 75, in testModifyPublicViewAllPrivateViewDConfidential
|
|
||||||
self._testRights({ "pu": "m", "pr": "v", "co": "d" })
|
|
||||||
File "./davacl.py", line 119, in _testRights
|
|
||||||
self._testCreate(rights)
|
|
||||||
File "./davacl.py", line 165, in _testCreate
|
|
||||||
exp_code)
|
|
||||||
File "./davacl.py", line 107, in _putEvent
|
|
||||||
% (exp_status, put.response["status"]))
|
|
||||||
AssertionError: event creation/modification: expected status code '403' (received '201')
|
|
||||||
|
|
||||||
- Test errors start with "ERRORS" and most likely indicate a bug in the test
|
|
||||||
code itself.
|
|
||||||
|
|
||||||
- Always set a doc string on the test methods, especially for complex test
|
|
||||||
cases.
|
|
||||||
|
|
||||||
- When writing tests, be aware that contrarily to unit tests, functional tests
|
|
||||||
often imply a logical order between the different steps.
|
|
||||||
@@ -1,64 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import os, sys, unittest, getopt, traceback, time
|
|
||||||
import preferences
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
unittest._TextTestResult.oldStartTest = unittest._TextTestResult.startTest
|
|
||||||
unittest._TextTestResult.startTest = sogotests.UnitTestTextTestResultNewStartTest
|
|
||||||
unittest._TextTestResult.stopTest = sogotests.UnitTestTextTestResultNewStopTest
|
|
||||||
|
|
||||||
loader = unittest.TestLoader()
|
|
||||||
modules = []
|
|
||||||
|
|
||||||
languages = preferences.SOGoSupportedLanguages
|
|
||||||
|
|
||||||
# We can disable testing all languages
|
|
||||||
testLanguages = False
|
|
||||||
opts, args = getopt.getopt (sys.argv[1:], [], ["enable-languages"])
|
|
||||||
for o, a in opts:
|
|
||||||
if o == "--enable-languages":
|
|
||||||
testLanguages = True
|
|
||||||
|
|
||||||
|
|
||||||
for mod in os.listdir("."):
|
|
||||||
if mod.startswith("test-") and mod.endswith(".py"):
|
|
||||||
modules.append(mod[:-3])
|
|
||||||
__import__(mod[:-3])
|
|
||||||
|
|
||||||
if len(modules) > 0:
|
|
||||||
suite = loader.loadTestsFromNames(modules)
|
|
||||||
print "%d tests in modules: '%s'" % (suite.countTestCases(),
|
|
||||||
"', '".join(modules))
|
|
||||||
runner = unittest.TextTestRunner(verbosity=2)
|
|
||||||
|
|
||||||
if testLanguages:
|
|
||||||
prefs = preferences.preferences()
|
|
||||||
# Get the current language
|
|
||||||
userLanguageString = prefs.get ("SOGoLanguage")
|
|
||||||
if userLanguageString:
|
|
||||||
userLanguage = languages.index (userLanguageString)
|
|
||||||
else:
|
|
||||||
userLanguage = languages.index ("English")
|
|
||||||
|
|
||||||
for i in range (0, len (languages)):
|
|
||||||
try:
|
|
||||||
prefs.set ("SOGoLanguage", i)
|
|
||||||
except Exception, inst:
|
|
||||||
print '-' * 60
|
|
||||||
traceback.print_exc ()
|
|
||||||
print '-' * 60
|
|
||||||
|
|
||||||
print "Running test in %s (%d/%d)" % \
|
|
||||||
(languages[i], i + 1, len (languages))
|
|
||||||
runner.verbosity = 2
|
|
||||||
runner.run(suite)
|
|
||||||
# Revert to the original language
|
|
||||||
prefs.set ("SOGoLanguage", userLanguage)
|
|
||||||
else:
|
|
||||||
runner.run(suite)
|
|
||||||
|
|
||||||
else:
|
|
||||||
print "No test available."
|
|
||||||
@@ -1,144 +0,0 @@
|
|||||||
from config import hostname, port, username, password
|
|
||||||
import webdavlib
|
|
||||||
import simplejson
|
|
||||||
import sogoLogin
|
|
||||||
|
|
||||||
|
|
||||||
DEBUG=True
|
|
||||||
DEBUG=False
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPPreferencesPOST (webdavlib.HTTPPOST):
|
|
||||||
cookie = None
|
|
||||||
|
|
||||||
def prepare_headers (self):
|
|
||||||
headers = webdavlib.HTTPPOST.prepare_headers(self)
|
|
||||||
if self.cookie:
|
|
||||||
headers["Cookie"] = self.cookie
|
|
||||||
return headers
|
|
||||||
|
|
||||||
class HTTPPreferencesGET (webdavlib.HTTPGET):
|
|
||||||
cookie = None
|
|
||||||
|
|
||||||
def prepare_headers (self):
|
|
||||||
headers = webdavlib.HTTPGET.prepare_headers(self)
|
|
||||||
if self.cookie:
|
|
||||||
headers["Cookie"] = self.cookie
|
|
||||||
return headers
|
|
||||||
|
|
||||||
class Carddav:
|
|
||||||
login = username
|
|
||||||
passw = password
|
|
||||||
|
|
||||||
def __init__(self, otherLogin = None, otherPassword = None):
|
|
||||||
if otherLogin and otherPassword:
|
|
||||||
self.login = otherLogin
|
|
||||||
self.passw = otherPassword
|
|
||||||
|
|
||||||
self.client = webdavlib.WebDAVClient(hostname, port)
|
|
||||||
authCookie = sogoLogin.getAuthCookie(hostname, port, self.login, self.passw)
|
|
||||||
self.cookie = authCookie
|
|
||||||
self.cards = None
|
|
||||||
self.calendars = None
|
|
||||||
self.fields = None
|
|
||||||
self.events = None
|
|
||||||
|
|
||||||
#- If this is not set, we CAN'T save preferences
|
|
||||||
self.preferences = None
|
|
||||||
|
|
||||||
def _get(self, url):
|
|
||||||
get = HTTPPreferencesGET(url)
|
|
||||||
get.cookie = self.cookie
|
|
||||||
self.client.execute(get)
|
|
||||||
if DEBUG: print "(url):", url
|
|
||||||
if DEBUG: print "(status):", get.response["status"]
|
|
||||||
if DEBUG: print "(body):", get.response['body']
|
|
||||||
content = simplejson.loads(get.response['body'])
|
|
||||||
return content
|
|
||||||
|
|
||||||
def _post(self, url, data, errormsg="failure POST"):
|
|
||||||
if DEBUG: print "URL:", url
|
|
||||||
post = HTTPPreferencesPOST(url, simplejson.dumps(data))
|
|
||||||
post.content_type = "application/json"
|
|
||||||
post.cookie = self.cookie
|
|
||||||
self.client.execute(post)
|
|
||||||
# Raise an exception if the pref wasn't properly set
|
|
||||||
if post.response["status"] != 200:
|
|
||||||
raise Exception ("%s, (code = %d)" % (errormsg, post.response["status"]))
|
|
||||||
|
|
||||||
def load_cards(self):
|
|
||||||
if not self.cards:
|
|
||||||
url = "/SOGo/so/%s/Contacts/personal/view" % (self.login)
|
|
||||||
content = self._get(url)
|
|
||||||
#print "\nCONTENT:", content
|
|
||||||
if 'headers' in content:
|
|
||||||
self.cards = []
|
|
||||||
fields = content['headers'][0]
|
|
||||||
for h in content['headers'][1:]:
|
|
||||||
card = {}
|
|
||||||
for i, f in enumerate(fields):
|
|
||||||
card[f] = h[i]
|
|
||||||
self.cards.append(card)
|
|
||||||
else:
|
|
||||||
self.cards = []
|
|
||||||
return self.cards
|
|
||||||
|
|
||||||
def get_cards(self, pattern):
|
|
||||||
self.load_cards()
|
|
||||||
return [a for a in self.cards if pattern in a.values()]
|
|
||||||
|
|
||||||
def get_card(self, idstr):
|
|
||||||
url = "/SOGo/so/%s/Contacts/personal/%s/view" % (self.login, idstr)
|
|
||||||
content = self._get(url)
|
|
||||||
return content
|
|
||||||
|
|
||||||
def save_card(self, card):
|
|
||||||
url = "/SOGo/so/%s/Contacts/personal/%s/saveAsContact" % (self.login, card['id'])
|
|
||||||
self._post(url, card, "failure saving card")
|
|
||||||
|
|
||||||
def load_calendars(self):
|
|
||||||
if not self.calendars:
|
|
||||||
url = "/SOGo/so/%s/Calendar/calendarslist" % (self.login)
|
|
||||||
content = self._get(url)
|
|
||||||
self.calendars = content['calendars']
|
|
||||||
return self.calendars
|
|
||||||
|
|
||||||
def get_calendars(self, pattern):
|
|
||||||
self.load_calendars()
|
|
||||||
return [a for a in self.calendars if pattern in a.values()]
|
|
||||||
|
|
||||||
def get_calendar(self, idstr):
|
|
||||||
self.load_calendars()
|
|
||||||
callist = [a for a in self.calendars if a['id'] == idstr]
|
|
||||||
if len(callist):
|
|
||||||
return callist[0]
|
|
||||||
return None
|
|
||||||
|
|
||||||
def save_calendar(self, calendar):
|
|
||||||
url = "/SOGo/so/%s/Contacts/personal/%s/saveAsContact" % (self.login, calendar['id'])
|
|
||||||
self._post(url, card, "failure saving calendar")
|
|
||||||
|
|
||||||
def load_events(self):
|
|
||||||
if not self.events:
|
|
||||||
url = "/SOGo/so/%s/Calendar/eventslist" % (self.login)
|
|
||||||
content = self._get(url)
|
|
||||||
self.fields = content['fields']
|
|
||||||
self.events = []
|
|
||||||
months = content['events']
|
|
||||||
for month in months.keys():
|
|
||||||
days = months[month]['days']
|
|
||||||
for day in days.keys():
|
|
||||||
tmp_events = days[day]['events']
|
|
||||||
self.events.extend(dict(zip(self.fields, event)) for event in tmp_events)
|
|
||||||
return self.events
|
|
||||||
|
|
||||||
def newguid(self, folderpath):
|
|
||||||
url = "/SOGo/so/%s/%s/newguid" % (self.login, folderpath)
|
|
||||||
content = self._get(url)
|
|
||||||
return content['id']
|
|
||||||
|
|
||||||
def save_event(self, event, folder, gid):
|
|
||||||
#url = "/SOGo/so/%s/%s/%s/save" % (self.login, event['c_folder'], event['c_name'])
|
|
||||||
url = "/SOGo/so/%s/%s/%s/saveAsAppointment" % (self.login, folder, gid)
|
|
||||||
self._post(url, event, "failure saving event")
|
|
||||||
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
# setup: 4 user are needed: username, superuser, attendee1, attendee1_delegate
|
|
||||||
# superuser must be a sogo superuser...
|
|
||||||
|
|
||||||
hostname = "localhost"
|
|
||||||
port = "80"
|
|
||||||
username = "myuser"
|
|
||||||
password = "mypass"
|
|
||||||
|
|
||||||
superuser = "super"
|
|
||||||
superuser_password="pass"
|
|
||||||
|
|
||||||
subscriber_username = "otheruser"
|
|
||||||
subscriber_password = "otherpass"
|
|
||||||
|
|
||||||
attendee1 = "user@domain.com"
|
|
||||||
attendee1_username = "user"
|
|
||||||
attendee1_password = "pass"
|
|
||||||
|
|
||||||
attendee1_delegate = "user2@domain.com"
|
|
||||||
attendee1_delegate_username = "sogo2"
|
|
||||||
attendee1_delegate_password = "sogo"
|
|
||||||
|
|
||||||
resource_no_overbook = "res"
|
|
||||||
resource_can_overbook = "res-nolimit"
|
|
||||||
|
|
||||||
white_listed_attendee = '{"sogo1":"John Doe <sogo1@example.com>"}'
|
|
||||||
|
|
||||||
mailserver = "imaphost"
|
|
||||||
|
|
||||||
testput_nbrdays = 30
|
|
||||||
|
|
||||||
sieve_server = "localhost"
|
|
||||||
sieve_port = 2000
|
|
||||||
|
|
||||||
sogo_user = "sogo"
|
|
||||||
sogo_tool_path = "/usr/local/sbin/sogo-tool"
|
|
||||||
|
|
||||||
webCalendarURL = "http://inverse.ca/sogo-integration-tests/CanadaHolidays.ics"
|
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
import time
|
|
||||||
|
|
||||||
def hours(nbr):
|
|
||||||
return nbr * 3600
|
|
||||||
|
|
||||||
def days(nbr):
|
|
||||||
return nbr * hours(24)
|
|
||||||
|
|
||||||
class ev_generator:
|
|
||||||
ev_templ = """
|
|
||||||
BEGIN:VCALENDAR\r
|
|
||||||
VERSION:2.0\r
|
|
||||||
PRODID:-//Inverse//Event Generator//EN\r
|
|
||||||
CALSCALE:GREGORIAN\r
|
|
||||||
BEGIN:VTIMEZONE\r
|
|
||||||
TZID:America/Montreal\r
|
|
||||||
BEGIN:DAYLIGHT\r
|
|
||||||
TZOFFSETFROM:-0500\r
|
|
||||||
TZOFFSETTO:-0400\r
|
|
||||||
DTSTART:20070311T020000\r
|
|
||||||
RRULE:FREQ=YEARLY;BYMONTH=3;BYDAY=2SU\r
|
|
||||||
TZNAME:EDT\r
|
|
||||||
END:DAYLIGHT\r
|
|
||||||
BEGIN:STANDARD\r
|
|
||||||
TZOFFSETFROM:-0400\r
|
|
||||||
TZOFFSETTO:-0500\r
|
|
||||||
DTSTART:20071104T020000\r
|
|
||||||
RRULE:FREQ=YEARLY;BYMONTH=11;BYDAY=1SU\r
|
|
||||||
TZNAME:EST\r
|
|
||||||
END:STANDARD\r
|
|
||||||
END:VTIMEZONE\r
|
|
||||||
BEGIN:VEVENT\r
|
|
||||||
SEQUENCE:4\r
|
|
||||||
TRANSP:OPAQUE\r
|
|
||||||
UID:%(uid)s\r
|
|
||||||
SUMMARY:%(summary)s\r
|
|
||||||
DTSTART;TZID=America/Montreal:%(start)s\r
|
|
||||||
DTEND;TZID=America/Montreal:%(end)s\r
|
|
||||||
CREATED:20080711T231608Z\r
|
|
||||||
DTSTAMP:20080711T231640Z\r
|
|
||||||
END:VEVENT\r
|
|
||||||
END:VCALENDAR\r
|
|
||||||
"""
|
|
||||||
def __init__(self, maxDays):
|
|
||||||
self.reset(maxDays)
|
|
||||||
|
|
||||||
def reset(self, maxDays):
|
|
||||||
self.maxDays = maxDays
|
|
||||||
self.currentDay = 0
|
|
||||||
self.currentStart = 0
|
|
||||||
today = time.mktime(time.localtime())
|
|
||||||
self.firstDay = today - days(maxDays + 30)
|
|
||||||
|
|
||||||
def _calendarDate(self, eventTime):
|
|
||||||
timeStruct = time.localtime(eventTime)
|
|
||||||
return time.strftime("%Y%m%dT%H0000", timeStruct)
|
|
||||||
|
|
||||||
def _iterValues(self):
|
|
||||||
event = None
|
|
||||||
|
|
||||||
if (self.currentDay < self.maxDays):
|
|
||||||
eventStart = (self.firstDay
|
|
||||||
+ days(self.currentDay)
|
|
||||||
+ hours(self.currentStart + 8))
|
|
||||||
eventEnd = eventStart + hours(1)
|
|
||||||
|
|
||||||
thatDay = time.localtime(int(eventStart))
|
|
||||||
uid = "Event%d%d" % (eventStart, eventEnd)
|
|
||||||
summary = "%s - event %d" % (time.strftime("%Y-%m-%d", thatDay),
|
|
||||||
self.currentStart)
|
|
||||||
start = self._calendarDate(eventStart)
|
|
||||||
end = self._calendarDate(eventEnd)
|
|
||||||
event = {'uid': uid,
|
|
||||||
'summary': summary,
|
|
||||||
'start': start,
|
|
||||||
'end': end}
|
|
||||||
|
|
||||||
self.currentStart = self.currentStart + 1
|
|
||||||
if (self.currentStart > 7):
|
|
||||||
self.currentStart = 0
|
|
||||||
self.currentDay = self.currentDay + 1
|
|
||||||
|
|
||||||
return event
|
|
||||||
|
|
||||||
def iter(self):
|
|
||||||
hasMore = False
|
|
||||||
entryValues = self._iterValues()
|
|
||||||
if (entryValues is not None):
|
|
||||||
self.event = (self.ev_templ % entryValues).strip()
|
|
||||||
hasMore = True
|
|
||||||
|
|
||||||
return hasMore
|
|
||||||
@@ -1,631 +0,0 @@
|
|||||||
"""Sieve management client.
|
|
||||||
|
|
||||||
A Protocol for Remotely Managing Sieve Scripts
|
|
||||||
Based on <draft-martin-managesieve-04.txt>
|
|
||||||
"""
|
|
||||||
|
|
||||||
__version__ = "0.4.2"
|
|
||||||
__author__ = """Hartmut Goebel <h.goebel@crazy-compilers.com>
|
|
||||||
Ulrich Eck <ueck@net-labs.de> April 2001
|
|
||||||
"""
|
|
||||||
|
|
||||||
import binascii, re, socket, time, random, sys
|
|
||||||
try:
|
|
||||||
import ssl
|
|
||||||
ssl_wrap_socket = ssl.wrap_socket
|
|
||||||
except ImportError:
|
|
||||||
ssl_wrap_socket = socket.ssl
|
|
||||||
|
|
||||||
__all__ = [ 'MANAGESIEVE', 'SIEVE_PORT', 'OK', 'NO', 'BYE', 'Debug']
|
|
||||||
|
|
||||||
Debug = 0
|
|
||||||
CRLF = '\r\n'
|
|
||||||
SIEVE_PORT = 2000
|
|
||||||
|
|
||||||
OK = 'OK'
|
|
||||||
NO = 'NO'
|
|
||||||
BYE = 'BYE'
|
|
||||||
|
|
||||||
AUTH_PLAIN = "PLAIN"
|
|
||||||
AUTH_LOGIN = "LOGIN"
|
|
||||||
# authentication mechanisms currently supported
|
|
||||||
# in order of preference
|
|
||||||
AUTHMECHS = [AUTH_PLAIN, AUTH_LOGIN]
|
|
||||||
|
|
||||||
# todo: return results or raise exceptions?
|
|
||||||
# todo: on result 'BYE' quit immediatly
|
|
||||||
# todo: raise exception on 'BYE'?
|
|
||||||
|
|
||||||
# Commands
|
|
||||||
commands = {
|
|
||||||
# name valid states
|
|
||||||
'STARTTLS': ('NONAUTH',),
|
|
||||||
'AUTHENTICATE': ('NONAUTH',),
|
|
||||||
'LOGOUT': ('NONAUTH', 'AUTH', 'LOGOUT'),
|
|
||||||
'CAPABILITY': ('NONAUTH', 'AUTH'),
|
|
||||||
'GETSCRIPT': ('AUTH', ),
|
|
||||||
'PUTSCRIPT': ('AUTH', ),
|
|
||||||
'SETACTIVE': ('AUTH', ),
|
|
||||||
'DELETESCRIPT': ('AUTH', ),
|
|
||||||
'LISTSCRIPTS': ('AUTH', ),
|
|
||||||
'HAVESPACE': ('AUTH', ),
|
|
||||||
# bogus command to receive a NO after STARTTLS (see starttls() )
|
|
||||||
'BOGUS': ('NONAUTH', 'AUTH', 'LOGOUT'),
|
|
||||||
}
|
|
||||||
|
|
||||||
### needed
|
|
||||||
Oknobye = re.compile(r'(?P<type>(OK|NO|BYE))'
|
|
||||||
r'( \((?P<code>.*)\))?'
|
|
||||||
r'( (?P<data>.*))?')
|
|
||||||
# draft-martin-managesieve-04.txt defines the size tag of literals to
|
|
||||||
# contain a '+' (plus sign) behind the digits, but timsieved does not
|
|
||||||
# send one. Thus we are less strikt here:
|
|
||||||
Literal = re.compile(r'.*{(?P<size>\d+)\+?}$')
|
|
||||||
re_dquote = re.compile(r'"(([^"\\]|\\.)*)"')
|
|
||||||
re_esc_quote = re.compile(r'\\([\\"])')
|
|
||||||
|
|
||||||
|
|
||||||
class SSLFakeSocket:
|
|
||||||
"""A fake socket object that really wraps a SSLObject.
|
|
||||||
|
|
||||||
It only supports what is needed in managesieve.
|
|
||||||
"""
|
|
||||||
def __init__(self, realsock, sslobj):
|
|
||||||
self.realsock = realsock
|
|
||||||
self.sslobj = sslobj
|
|
||||||
|
|
||||||
def send(self, str):
|
|
||||||
self.sslobj.write(str)
|
|
||||||
return len(str)
|
|
||||||
|
|
||||||
sendall = send
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.realsock.close()
|
|
||||||
|
|
||||||
class SSLFakeFile:
|
|
||||||
"""A fake file like object that really wraps a SSLObject.
|
|
||||||
|
|
||||||
It only supports what is needed in managesieve.
|
|
||||||
"""
|
|
||||||
def __init__(self, sslobj):
|
|
||||||
self.sslobj = sslobj
|
|
||||||
|
|
||||||
def readline(self):
|
|
||||||
str = ""
|
|
||||||
chr = None
|
|
||||||
while chr != "\n":
|
|
||||||
chr = self.sslobj.read(1)
|
|
||||||
str += chr
|
|
||||||
return str
|
|
||||||
|
|
||||||
def read(self, size=0):
|
|
||||||
if size == 0:
|
|
||||||
return ''
|
|
||||||
else:
|
|
||||||
return self.sslobj.read(size)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def sieve_name(name):
|
|
||||||
# todo: correct quoting
|
|
||||||
return '"%s"' % name
|
|
||||||
|
|
||||||
def sieve_string(string):
|
|
||||||
return '{%d+}%s%s' % ( len(string), CRLF, string )
|
|
||||||
|
|
||||||
|
|
||||||
class MANAGESIEVE:
|
|
||||||
"""Sieve client class.
|
|
||||||
|
|
||||||
Instantiate with: MANAGESIEVE(host [, port])
|
|
||||||
|
|
||||||
host - host's name (default: localhost)
|
|
||||||
port - port number (default: standard Sieve port).
|
|
||||||
|
|
||||||
use_tls - switch to TLS automatically, if server supports
|
|
||||||
keyfile - keyfile to use for TLS (optional)
|
|
||||||
certfile - certfile to use for TLS (optional)
|
|
||||||
|
|
||||||
All Sieve commands are supported by methods of the same
|
|
||||||
name (in lower-case).
|
|
||||||
|
|
||||||
Each command returns a tuple: (type, [data, ...]) where 'type'
|
|
||||||
is usually 'OK' or 'NO', and 'data' is either the text from the
|
|
||||||
tagged response, or untagged results from command.
|
|
||||||
|
|
||||||
All arguments to commands are converted to strings, except for
|
|
||||||
AUTHENTICATE.
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
However, the 'password' argument to the LOGIN command is always
|
|
||||||
quoted. If you want to avoid having an argument string quoted (eg:
|
|
||||||
the 'flags' argument to STORE) then enclose the string in
|
|
||||||
parentheses (eg: "(\Deleted)").
|
|
||||||
|
|
||||||
Errors raise the exception class <instance>.error("<reason>").
|
|
||||||
IMAP4 server errors raise <instance>.abort("<reason>"),
|
|
||||||
which is a sub-class of 'error'. Mailbox status changes
|
|
||||||
from READ-WRITE to READ-ONLY raise the exception class
|
|
||||||
<instance>.readonly("<reason>"), which is a sub-class of 'abort'.
|
|
||||||
|
|
||||||
"error" exceptions imply a program error.
|
|
||||||
"abort" exceptions imply the connection should be reset, and
|
|
||||||
the command re-tried.
|
|
||||||
"readonly" exceptions imply the command should be re-tried.
|
|
||||||
|
|
||||||
Note: to use this module, you must read the RFCs pertaining
|
|
||||||
to the IMAP4 protocol, as the semantics of the arguments to
|
|
||||||
each IMAP4 command are left to the invoker, not to mention
|
|
||||||
the results.
|
|
||||||
"""
|
|
||||||
|
|
||||||
class error(Exception): """Logical errors - debug required"""
|
|
||||||
class abort(error): """Service errors - close and retry"""
|
|
||||||
|
|
||||||
def __clear_knowledge(self):
|
|
||||||
"""clear/init any knowledge obtained from the server"""
|
|
||||||
self.capabilities = []
|
|
||||||
self.loginmechs = []
|
|
||||||
self.implementation = ''
|
|
||||||
self.supports_tls = 0
|
|
||||||
|
|
||||||
def __init__(self, host='', port=SIEVE_PORT,
|
|
||||||
use_tls=False, keyfile=None, certfile=None):
|
|
||||||
self.host = host
|
|
||||||
self.port = port
|
|
||||||
self.debug = Debug
|
|
||||||
self.state = 'NONAUTH'
|
|
||||||
|
|
||||||
self.response_text = self.response_code = None
|
|
||||||
self.__clear_knowledge()
|
|
||||||
|
|
||||||
# Open socket to server.
|
|
||||||
self._open(host, port)
|
|
||||||
|
|
||||||
if __debug__:
|
|
||||||
self._cmd_log_len = 10
|
|
||||||
self._cmd_log_idx = 0
|
|
||||||
self._cmd_log = {} # Last `_cmd_log_len' interactions
|
|
||||||
if self.debug >= 1:
|
|
||||||
self._mesg('managesieve version %s' % __version__)
|
|
||||||
|
|
||||||
# Get server welcome message,
|
|
||||||
# request and store CAPABILITY response.
|
|
||||||
typ, data = self._get_response()
|
|
||||||
if typ == 'OK':
|
|
||||||
self._parse_capabilities(data)
|
|
||||||
if use_tls and self.supports_tls:
|
|
||||||
typ, data = self.starttls(keyfile=keyfile, certfile=certfile)
|
|
||||||
if typ == 'OK':
|
|
||||||
self._parse_capabilities(data)
|
|
||||||
|
|
||||||
|
|
||||||
def _parse_capabilities(self, lines):
|
|
||||||
for line in lines:
|
|
||||||
if len(line) == 2:
|
|
||||||
typ, data = line
|
|
||||||
else:
|
|
||||||
assert len(line) == 1, 'Bad Capabilities line: %r' % line
|
|
||||||
typ = line[0]
|
|
||||||
data = None
|
|
||||||
if __debug__:
|
|
||||||
if self.debug >= 3:
|
|
||||||
self._mesg('%s: %r' % (typ, data))
|
|
||||||
if typ == "IMPLEMENTATION":
|
|
||||||
self.implementation = data
|
|
||||||
elif typ == "SASL":
|
|
||||||
self.loginmechs = data.split()
|
|
||||||
elif typ == "SIEVE":
|
|
||||||
self.capabilities = data.split()
|
|
||||||
elif typ == "STARTTLS":
|
|
||||||
self.supports_tls = 1
|
|
||||||
else:
|
|
||||||
# A client implementation MUST ignore any other
|
|
||||||
# capabilities given that it does not understand.
|
|
||||||
pass
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
def __getattr__(self, attr):
|
|
||||||
# Allow UPPERCASE variants of MANAGESIEVE command methods.
|
|
||||||
if commands.has_key(attr):
|
|
||||||
return getattr(self, attr.lower())
|
|
||||||
raise AttributeError("Unknown MANAGESIEVE command: '%s'" % attr)
|
|
||||||
|
|
||||||
|
|
||||||
#### Private methods ###
|
|
||||||
def _open(self, host, port):
|
|
||||||
"""Setup 'self.sock' and 'self.file'."""
|
|
||||||
self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
||||||
self.sock.connect((self.host, self.port))
|
|
||||||
self.file = self.sock.makefile('r')
|
|
||||||
|
|
||||||
def _close(self):
|
|
||||||
self.file.close()
|
|
||||||
self.sock.close()
|
|
||||||
|
|
||||||
def _read(self, size):
|
|
||||||
"""Read 'size' bytes from remote."""
|
|
||||||
data = ""
|
|
||||||
while len(data) < size:
|
|
||||||
data += self.file.read(size - len(data))
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _readline(self):
|
|
||||||
"""Read line from remote."""
|
|
||||||
return self.file.readline()
|
|
||||||
|
|
||||||
def _send(self, data):
|
|
||||||
return self.sock.send(data)
|
|
||||||
|
|
||||||
def _get_line(self):
|
|
||||||
line = self._readline()
|
|
||||||
if not line:
|
|
||||||
raise self.abort('socket error: EOF')
|
|
||||||
# Protocol mandates all lines terminated by CRLF
|
|
||||||
line = line[:-2]
|
|
||||||
if __debug__:
|
|
||||||
if self.debug >= 4:
|
|
||||||
self._mesg('< %s' % line)
|
|
||||||
else:
|
|
||||||
self._log('< %s' % line)
|
|
||||||
return line
|
|
||||||
|
|
||||||
def _simple_command(self, *args):
|
|
||||||
"""Execute a command which does only return status.
|
|
||||||
|
|
||||||
Returns (typ) with
|
|
||||||
typ = response type
|
|
||||||
|
|
||||||
The responce code and text may be found in <instance>.response_code
|
|
||||||
and <instance>.response_text, respectivly.
|
|
||||||
"""
|
|
||||||
return self._command(*args)[0] # only return typ, ignore data
|
|
||||||
|
|
||||||
|
|
||||||
def _command(self, name, arg1=None, arg2=None, *options):
|
|
||||||
"""
|
|
||||||
Returns (typ, data) with
|
|
||||||
typ = response type
|
|
||||||
data = list of lists of strings read (only meaningfull if OK)
|
|
||||||
|
|
||||||
The responce code and text may be found in <instance>.response_code
|
|
||||||
and <instance>.response_text, respectivly.
|
|
||||||
"""
|
|
||||||
if self.state not in commands[name]:
|
|
||||||
raise self.error(
|
|
||||||
'Command %s illegal in state %s' % (name, self.state))
|
|
||||||
# concatinate command and arguments (if any)
|
|
||||||
data = " ".join(filter(None, (name, arg1, arg2)))
|
|
||||||
if __debug__:
|
|
||||||
if self.debug >= 4: self._mesg('> %r' % data)
|
|
||||||
else: self._log('> %s' % data)
|
|
||||||
try:
|
|
||||||
try:
|
|
||||||
self._send('%s%s' % (data, CRLF))
|
|
||||||
for o in options:
|
|
||||||
if __debug__:
|
|
||||||
if self.debug >= 4: self._mesg('> %r' % o)
|
|
||||||
else: self._log('> %r' % data)
|
|
||||||
self._send('%s%s' % (o, CRLF))
|
|
||||||
except (socket.error, OSError), val:
|
|
||||||
raise self.abort('socket error: %s' % val)
|
|
||||||
return self._get_response()
|
|
||||||
except self.abort, val:
|
|
||||||
if __debug__:
|
|
||||||
if self.debug >= 1:
|
|
||||||
self.print_log()
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
def _readstring(self, data):
|
|
||||||
if data[0] == ' ': # space -> error
|
|
||||||
raise self.error('Unexpected space: %r' % data)
|
|
||||||
elif data[0] == '"': # handle double quote:
|
|
||||||
if not self._match(re_dquote, data):
|
|
||||||
raise self.error('Unmatched quote: %r' % data)
|
|
||||||
snippet = self.mo.group(1)
|
|
||||||
return re_esc_quote.sub(r'\1', snippet), data[self.mo.end():]
|
|
||||||
elif self._match(Literal, data):
|
|
||||||
# read a 'literal' string
|
|
||||||
size = int(self.mo.group('size'))
|
|
||||||
if __debug__:
|
|
||||||
if self.debug >= 4:
|
|
||||||
self._mesg('read literal size %s' % size)
|
|
||||||
return self._read(size), self._get_line()
|
|
||||||
else:
|
|
||||||
data = data.split(' ', 1)
|
|
||||||
if len(data) == 1:
|
|
||||||
data.append('')
|
|
||||||
return data
|
|
||||||
|
|
||||||
def _get_response(self):
|
|
||||||
"""
|
|
||||||
Returns (typ, data) with
|
|
||||||
typ = response type
|
|
||||||
data = list of lists of strings read (only meaningfull if OK)
|
|
||||||
|
|
||||||
The responce code and text may be found in <instance>.response_code
|
|
||||||
and <instance>.response_text, respectivly.
|
|
||||||
"""
|
|
||||||
|
|
||||||
"""
|
|
||||||
response-deletescript = response-oknobye
|
|
||||||
response-authenticate = *(string CRLF) (response-oknobye)
|
|
||||||
response-capability = *(string [SP string] CRLF) response-oknobye
|
|
||||||
response-listscripts = *(string [SP "ACTIVE"] CRLF) response-oknobye
|
|
||||||
response-oknobye = ("OK" / "NO" / "BYE") [SP "(" resp-code ")"] [SP string] CRLF
|
|
||||||
string = quoted / literal
|
|
||||||
quoted = <"> *QUOTED-CHAR <">
|
|
||||||
literal = "{" number "+}" CRLF *OCTET
|
|
||||||
;; The number represents the number of octets
|
|
||||||
;; MUST be literal-utf8 except for values
|
|
||||||
|
|
||||||
--> a response either starts with a quote-charakter, a left-bracket or
|
|
||||||
OK, NO, BYE
|
|
||||||
|
|
||||||
"quoted" CRLF
|
|
||||||
"quoted" SP "quoted" CRLF
|
|
||||||
{size} CRLF *OCTETS CRLF
|
|
||||||
{size} CRLF *OCTETS CRLF
|
|
||||||
[A-Z-]+ CRLF
|
|
||||||
|
|
||||||
"""
|
|
||||||
data = [] ; dat = None
|
|
||||||
resp = self._get_line()
|
|
||||||
while 1:
|
|
||||||
if self._match(Oknobye, resp):
|
|
||||||
typ, code, dat = self.mo.group('type','code','data')
|
|
||||||
if __debug__:
|
|
||||||
if self.debug >= 1:
|
|
||||||
self._mesg('%s response: %s %s' % (typ, code, dat))
|
|
||||||
self.response_code = code
|
|
||||||
self.response_text = None
|
|
||||||
if dat:
|
|
||||||
self.response_text = self._readstring(dat)[0]
|
|
||||||
|
|
||||||
# if server quits here, send code instead of empty data
|
|
||||||
if typ == "BYE":
|
|
||||||
return typ, code
|
|
||||||
|
|
||||||
return typ, data
|
|
||||||
## elif 0:
|
|
||||||
## dat2 = None
|
|
||||||
## dat, resp = self._readstring(resp)
|
|
||||||
## if resp.startswith(' '):
|
|
||||||
## dat2, resp = self._readstring(resp[1:])
|
|
||||||
## data.append( (dat, dat2))
|
|
||||||
## resp = self._get_line()
|
|
||||||
else:
|
|
||||||
dat = []
|
|
||||||
while 1:
|
|
||||||
dat1, resp = self._readstring(resp)
|
|
||||||
if __debug__:
|
|
||||||
if self.debug >= 4:
|
|
||||||
self._mesg('read: %r' % (dat1,))
|
|
||||||
if self.debug >= 5:
|
|
||||||
self._mesg('rest: %r' % (resp,))
|
|
||||||
dat.append(dat1)
|
|
||||||
if not resp.startswith(' '):
|
|
||||||
break
|
|
||||||
resp = resp[1:]
|
|
||||||
if len(dat) == 1:
|
|
||||||
dat.append(None)
|
|
||||||
data.append(dat)
|
|
||||||
resp = self._get_line()
|
|
||||||
return self.error('Should not come here')
|
|
||||||
|
|
||||||
|
|
||||||
def _match(self, cre, s):
|
|
||||||
# Run compiled regular expression match method on 's'.
|
|
||||||
# Save result, return success.
|
|
||||||
self.mo = cre.match(s)
|
|
||||||
if __debug__:
|
|
||||||
if self.mo is not None and self.debug >= 5:
|
|
||||||
self._mesg("\tmatched r'%s' => %s" % (cre.pattern, `self.mo.groups()`))
|
|
||||||
return self.mo is not None
|
|
||||||
|
|
||||||
|
|
||||||
if __debug__:
|
|
||||||
|
|
||||||
def _mesg(self, s, secs=None):
|
|
||||||
if secs is None:
|
|
||||||
secs = time.time()
|
|
||||||
tm = time.strftime('%M:%S', time.localtime(secs))
|
|
||||||
sys.stderr.write(' %s.%02d %s\n' % (tm, (secs*100)%100, s))
|
|
||||||
sys.stderr.flush()
|
|
||||||
|
|
||||||
def _log(self, line):
|
|
||||||
# Keep log of last `_cmd_log_len' interactions for debugging.
|
|
||||||
self._cmd_log[self._cmd_log_idx] = (line, time.time())
|
|
||||||
self._cmd_log_idx += 1
|
|
||||||
if self._cmd_log_idx >= self._cmd_log_len:
|
|
||||||
self._cmd_log_idx = 0
|
|
||||||
|
|
||||||
def print_log(self):
|
|
||||||
self.self._mesg('last %d SIEVE interactions:' % len(self._cmd_log))
|
|
||||||
i, n = self._cmd_log_idx, self._cmd_log_len
|
|
||||||
while n:
|
|
||||||
try:
|
|
||||||
self.self._mesg(*self._cmd_log[i])
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
i += 1
|
|
||||||
if i >= self._cmd_log_len:
|
|
||||||
i = 0
|
|
||||||
n -= 1
|
|
||||||
|
|
||||||
### Public methods ###
|
|
||||||
def authenticate(self, mechanism, *authobjects):
|
|
||||||
"""Authenticate command - requires response processing."""
|
|
||||||
# command-authenticate = "AUTHENTICATE" SP auth-type [SP string] *(CRLF string)
|
|
||||||
# response-authenticate = *(string CRLF) (response-oknobye)
|
|
||||||
mech = mechanism.upper()
|
|
||||||
if not mech in self.loginmechs:
|
|
||||||
raise self.error("Server doesn't allow %s authentication." % mech)
|
|
||||||
|
|
||||||
if mech == AUTH_LOGIN:
|
|
||||||
authobjects = [ sieve_name(binascii.b2a_base64(ao)[:-1])
|
|
||||||
for ao in authobjects
|
|
||||||
]
|
|
||||||
elif mech == AUTH_PLAIN:
|
|
||||||
if len(authobjects) < 3:
|
|
||||||
# assume authorization identity (authzid) is missing
|
|
||||||
# and these two authobjects are username and password
|
|
||||||
authobjects.insert(0, '')
|
|
||||||
ao = '\0'.join(authobjects)
|
|
||||||
ao = binascii.b2a_base64(ao)[:-1]
|
|
||||||
authobjects = [ sieve_string(ao) ]
|
|
||||||
else:
|
|
||||||
raise self.error("managesieve doesn't support %s authentication." % mech)
|
|
||||||
|
|
||||||
typ, data = self._command('AUTHENTICATE',
|
|
||||||
sieve_name(mech), *authobjects)
|
|
||||||
if typ == 'OK':
|
|
||||||
self.state = 'AUTH'
|
|
||||||
return typ
|
|
||||||
|
|
||||||
|
|
||||||
def login(self, auth, user, password):
|
|
||||||
"""
|
|
||||||
Authenticate to the Sieve server using the best mechanism available.
|
|
||||||
"""
|
|
||||||
for authmech in AUTHMECHS:
|
|
||||||
if authmech in self.loginmechs:
|
|
||||||
authobjs = [auth, user, password]
|
|
||||||
if authmech == AUTH_LOGIN:
|
|
||||||
authobjs = [user, password]
|
|
||||||
return self.authenticate(authmech, *authobjs)
|
|
||||||
else:
|
|
||||||
raise self.abort('No matching authentication mechanism found.')
|
|
||||||
|
|
||||||
def logout(self):
|
|
||||||
"""Terminate connection to server."""
|
|
||||||
# command-logout = "LOGOUT" CRLF
|
|
||||||
# response-logout = response-oknobye
|
|
||||||
typ = self._simple_command('LOGOUT')
|
|
||||||
self.state = 'LOGOUT'
|
|
||||||
self._close()
|
|
||||||
return typ
|
|
||||||
|
|
||||||
|
|
||||||
def listscripts(self):
|
|
||||||
"""Get a list of scripts on the server.
|
|
||||||
|
|
||||||
(typ, [data]) = <instance>.listscripts()
|
|
||||||
|
|
||||||
if 'typ' is 'OK', 'data' is list of (scriptname, active) tuples.
|
|
||||||
"""
|
|
||||||
# command-listscripts = "LISTSCRIPTS" CRLF
|
|
||||||
# response-listscripts = *(sieve-name [SP "ACTIVE"] CRLF) response-oknobye
|
|
||||||
typ, data = self._command('LISTSCRIPTS')
|
|
||||||
if typ != 'OK': return typ, data
|
|
||||||
scripts = []
|
|
||||||
for dat in data:
|
|
||||||
if __debug__:
|
|
||||||
if not len(dat) in (1, 2):
|
|
||||||
self.error("Unexpected result from LISTSCRIPTS: %r" (dat,))
|
|
||||||
scripts.append( (dat[0], dat[1] is not None ))
|
|
||||||
return typ, scripts
|
|
||||||
|
|
||||||
|
|
||||||
def getscript(self, scriptname):
|
|
||||||
"""Get a script from the server.
|
|
||||||
|
|
||||||
(typ, scriptdata) = <instance>.getscript(scriptname)
|
|
||||||
|
|
||||||
'scriptdata' is the script data.
|
|
||||||
"""
|
|
||||||
# command-getscript = "GETSCRIPT" SP sieve-name CRLF
|
|
||||||
# response-getscript = [string CRLF] response-oknobye
|
|
||||||
|
|
||||||
typ, data = self._command('GETSCRIPT', sieve_name(scriptname))
|
|
||||||
if typ != 'OK': return typ, data
|
|
||||||
if len(data) != 1:
|
|
||||||
self.error('GETSCRIPT returned more than one string/script')
|
|
||||||
# todo: decode data?
|
|
||||||
return typ, data[0][0]
|
|
||||||
|
|
||||||
|
|
||||||
def putscript(self, scriptname, scriptdata):
|
|
||||||
"""Put a script onto the server."""
|
|
||||||
# command-putscript = "PUTSCRIPT" SP sieve-name SP string CRLF
|
|
||||||
# response-putscript = response-oknobye
|
|
||||||
return self._simple_command('PUTSCRIPT',
|
|
||||||
sieve_name(scriptname),
|
|
||||||
sieve_string(scriptdata)
|
|
||||||
)
|
|
||||||
|
|
||||||
def deletescript(self, scriptname):
|
|
||||||
"""Delete a scripts at the server."""
|
|
||||||
# command-deletescript = "DELETESCRIPT" SP sieve-name CRLF
|
|
||||||
# response-deletescript = response-oknobye
|
|
||||||
return self._simple_command('DELETESCRIPT', sieve_name(scriptname))
|
|
||||||
|
|
||||||
|
|
||||||
def setactive(self, scriptname):
|
|
||||||
"""Mark a script as the 'active' one."""
|
|
||||||
# command-setactive = "SETACTIVE" SP sieve-name CRLF
|
|
||||||
# response-setactive = response-oknobye
|
|
||||||
return self._simple_command('SETACTIVE', sieve_name(scriptname))
|
|
||||||
|
|
||||||
|
|
||||||
def havespace(self, scriptname, size):
|
|
||||||
# command-havespace = "HAVESPACE" SP sieve-name SP number CRLF
|
|
||||||
# response-havespace = response-oknobye
|
|
||||||
return self._simple_command('HAVESPACE',
|
|
||||||
sieve_name(scriptname),
|
|
||||||
str(size))
|
|
||||||
|
|
||||||
|
|
||||||
def capability(self):
|
|
||||||
"""
|
|
||||||
Isse a CAPABILITY command and return the result.
|
|
||||||
|
|
||||||
As a side-effect, on succes these attributes are (re)set:
|
|
||||||
self.implementation
|
|
||||||
self.loginmechs
|
|
||||||
self.capabilities
|
|
||||||
self.supports_tls
|
|
||||||
"""
|
|
||||||
# command-capability = "CAPABILITY" CRLF
|
|
||||||
# response-capability = *(string [SP string] CRLF) response-oknobye
|
|
||||||
typ, data = self._command('CAPABILITY')
|
|
||||||
if typ == 'OK':
|
|
||||||
self._parse_capabilities(data)
|
|
||||||
return typ, data
|
|
||||||
|
|
||||||
|
|
||||||
def starttls(self, keyfile=None, certfile=None):
|
|
||||||
"""Puts the connection to the SIEVE server into TLS mode.
|
|
||||||
|
|
||||||
If the server supports TLS, this will encrypt the rest of the SIEVE
|
|
||||||
session. If you provide the keyfile and certfile parameters,
|
|
||||||
the identity of the SIEVE server and client can be checked. This,
|
|
||||||
however, depends on whether the socket module really checks the
|
|
||||||
certificates.
|
|
||||||
"""
|
|
||||||
# command-starttls = "STARTTLS" CRLF
|
|
||||||
# response-starttls = response-oknobye
|
|
||||||
typ, data = self._command('STARTTLS')
|
|
||||||
if typ == 'OK':
|
|
||||||
sslobj = ssl_wrap_socket(self.sock, keyfile, certfile)
|
|
||||||
self.sock = SSLFakeSocket(self.sock, sslobj)
|
|
||||||
self.file = SSLFakeFile(sslobj)
|
|
||||||
# MUST discard knowledge obtained from the server
|
|
||||||
self.__clear_knowledge()
|
|
||||||
# Some servers send capabilities after TLS handshake, some
|
|
||||||
# do not. We send a bogus command, and expect a NO. If you
|
|
||||||
# get something else instead, read the extra NO to clear
|
|
||||||
# the buffer.
|
|
||||||
typ, data = self._command('BOGUS')
|
|
||||||
if typ != 'NO':
|
|
||||||
typ, data = self._get_response()
|
|
||||||
# server may not advertise capabilities, thus we need to ask
|
|
||||||
self.capability()
|
|
||||||
if self.debug >= 3: self._mesg('started Transport Layer Security (TLS)')
|
|
||||||
return typ, data
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
from config import hostname, port, username, password
|
|
||||||
import webdavlib
|
|
||||||
import urllib
|
|
||||||
import base64
|
|
||||||
import simplejson
|
|
||||||
|
|
||||||
import sogoLogin
|
|
||||||
|
|
||||||
|
|
||||||
DEBUG=False
|
|
||||||
#DEBUG=True
|
|
||||||
|
|
||||||
|
|
||||||
# must be kept in sync with SoObjects/SOGo/SOGoDefaults.plist
|
|
||||||
# this should probably be fetched magically...
|
|
||||||
SOGoSupportedLanguages = [ "Arabic", "Basque", "Bulgarian", "Catalan", "ChineseChina", "ChineseTaiwan", "Croatian",
|
|
||||||
"Czech", "Dutch", "Danish", "Welsh", "English", "Finnish",
|
|
||||||
"SpanishSpain", "SpanishArgentina", "French", "German", "Hebrew",
|
|
||||||
"Hungarian", "Indonesian", "Icelandic", "Italian", "Japanese",
|
|
||||||
"Latvian", "Lithuanian", "Macedonian", "Montenegrin", "Portuguese", "BrazilianPortuguese",
|
|
||||||
"NorwegianBokmal", "NorwegianNynorsk", "Polish", "Romanian", "Russian",
|
|
||||||
"Serbian", "SerbianLatin", "Slovak", "Slovenian", "Swedish", "TurkishTurkey", "Ukrainian" ];
|
|
||||||
daysBetweenResponseList=[1,2,3,5,7,14,21,30]
|
|
||||||
|
|
||||||
class HTTPPreferencesPOST (webdavlib.HTTPPOST):
|
|
||||||
cookie = None
|
|
||||||
|
|
||||||
def prepare_headers (self):
|
|
||||||
headers = webdavlib.HTTPPOST.prepare_headers(self)
|
|
||||||
if self.cookie:
|
|
||||||
headers["Cookie"] = self.cookie
|
|
||||||
return headers
|
|
||||||
|
|
||||||
class HTTPPreferencesGET (webdavlib.HTTPGET):
|
|
||||||
cookie = None
|
|
||||||
|
|
||||||
def prepare_headers (self):
|
|
||||||
headers = webdavlib.HTTPGET.prepare_headers(self)
|
|
||||||
if self.cookie:
|
|
||||||
headers["Cookie"] = self.cookie
|
|
||||||
return headers
|
|
||||||
|
|
||||||
class preferences:
|
|
||||||
login = username
|
|
||||||
passw = password
|
|
||||||
|
|
||||||
def __init__(self, otherLogin = None, otherPassword = None):
|
|
||||||
if otherLogin and otherPassword:
|
|
||||||
self.login = otherLogin
|
|
||||||
self.passw = otherPassword
|
|
||||||
|
|
||||||
self.client = webdavlib.WebDAVClient(hostname, port)
|
|
||||||
|
|
||||||
authCookie = sogoLogin.getAuthCookie(hostname, port, self.login, self.passw)
|
|
||||||
self.cookie = authCookie
|
|
||||||
|
|
||||||
#- If this is not set, we CAN'T save preferences
|
|
||||||
self.preferences = None
|
|
||||||
|
|
||||||
def find_key(self, d, key):
|
|
||||||
if key in d:
|
|
||||||
return d
|
|
||||||
subdicts = [a[1] for a in d.iteritems() if type(a[1]) == dict]
|
|
||||||
for subd in subdicts:
|
|
||||||
ret = self.find_key(subd, key)
|
|
||||||
if ret:
|
|
||||||
return ret
|
|
||||||
return None
|
|
||||||
|
|
||||||
def load_preferences(self):
|
|
||||||
defaults = self.get_defaults()
|
|
||||||
settings = self.get_settings()
|
|
||||||
self.preferences = {'defaults': defaults, 'settings': settings}
|
|
||||||
#print "LOAD PREFS:", self.preferences
|
|
||||||
|
|
||||||
def get(self, preference=None):
|
|
||||||
if not self.preferences:
|
|
||||||
self.load_preferences()
|
|
||||||
#- Want the whole thing
|
|
||||||
if not preference:
|
|
||||||
return self.preferences
|
|
||||||
else:
|
|
||||||
tmpdict = self.find_key(self.preferences, preference)
|
|
||||||
if tmpdict:
|
|
||||||
return tmpdict[preference]
|
|
||||||
else:
|
|
||||||
return None
|
|
||||||
|
|
||||||
def _get(self, subtype='jsonDefault', preference=None):
|
|
||||||
url = "/SOGo/so/%s/%s" % (self.login, subtype)
|
|
||||||
get = HTTPPreferencesGET(url)
|
|
||||||
get.cookie = self.cookie
|
|
||||||
self.client.execute(get)
|
|
||||||
if DEBUG: print "DEBUG (url):", url
|
|
||||||
if DEBUG: print "DEBUG (status):", get.response["status"]
|
|
||||||
if DEBUG: print "DEBUG (body):", get.response['body']
|
|
||||||
content = simplejson.loads(get.response['body'])
|
|
||||||
result = None
|
|
||||||
try:
|
|
||||||
if preference:
|
|
||||||
result = content[preference]
|
|
||||||
else:
|
|
||||||
result = content
|
|
||||||
except:
|
|
||||||
pass
|
|
||||||
return result
|
|
||||||
|
|
||||||
def get_defaults(self, preference=None):
|
|
||||||
return self._get('jsonDefaults', preference)
|
|
||||||
|
|
||||||
def get_settings(self, preference=None):
|
|
||||||
return self._get('jsonSettings', preference)
|
|
||||||
|
|
||||||
def set_nosave(self, preference, value=None):
|
|
||||||
# First check if we did a get, if not, must get first
|
|
||||||
if not self.preferences:
|
|
||||||
self.load_preferences()
|
|
||||||
|
|
||||||
# Get the right sub-dict and change the key/value
|
|
||||||
subdict = self.find_key(self.preferences, preference)
|
|
||||||
if not subdict:
|
|
||||||
raise AttributeError("ERROR(nosubdict): looking for %s in: %s" %(preference, str(self.preferences)))
|
|
||||||
subdict[preference] = value
|
|
||||||
|
|
||||||
def set(self, preference, value=None):
|
|
||||||
self.set_nosave(preference, value)
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def set_multiple(self, preferences={}):
|
|
||||||
for key, value in preferences.iteritems():
|
|
||||||
self.set_nosave(key, value)
|
|
||||||
self.save()
|
|
||||||
|
|
||||||
def set_or_create(self, preference, value, paths=['defaults']):
|
|
||||||
if not self.preferences:
|
|
||||||
self.load_preferences()
|
|
||||||
subdict = self.find_key(self.preferences, preference)
|
|
||||||
#- Pref is not set
|
|
||||||
if not subdict:
|
|
||||||
subdict = self.preferences
|
|
||||||
for path in paths:
|
|
||||||
subdict = subdict.setdefault(path, {})
|
|
||||||
subdict[preference] = value
|
|
||||||
|
|
||||||
def save(self):
|
|
||||||
url = "/SOGo/so/%s/Preferences/save" % self.login
|
|
||||||
|
|
||||||
post = HTTPPreferencesPOST(url, simplejson.dumps(self.preferences))
|
|
||||||
post.content_type = "application/json"
|
|
||||||
post.cookie = self.cookie
|
|
||||||
self.client.execute(post)
|
|
||||||
|
|
||||||
# Raise an exception if the pref wasn't properly set
|
|
||||||
if post.response["status"] != 200:
|
|
||||||
raise Exception ("failure setting prefs, (code = %d)" \
|
|
||||||
% post.response["status"])
|
|
||||||
|
|
||||||
|
|
||||||
# Simple main to test this class
|
|
||||||
if __name__ == "__main__":
|
|
||||||
p = preferences ()
|
|
||||||
print p.get ("SOGoLanguage")
|
|
||||||
p.set ("SOGoLanguage", SOGoSupportedLanguages.index("French"))
|
|
||||||
print p.get ("SOGoLanguage")
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
from config import hostname, port, username, password
|
|
||||||
|
|
||||||
import webdavlib
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import getopt
|
|
||||||
import xml.dom.minidom
|
|
||||||
|
|
||||||
def parseArguments():
|
|
||||||
arguments = {}
|
|
||||||
|
|
||||||
depth = "0"
|
|
||||||
quiet = False
|
|
||||||
(opts, args) = getopt.getopt(sys.argv[1:], "d:q", ["depth=", "quiet"])
|
|
||||||
|
|
||||||
for pair in opts:
|
|
||||||
if (pair[0] == "-d" or pair[0] == "--depth"):
|
|
||||||
depth = pair[1]
|
|
||||||
elif (pair[0] == "-q" or pair[0] == "--quiet"):
|
|
||||||
quiet = True
|
|
||||||
|
|
||||||
# print "depth: " + depth
|
|
||||||
|
|
||||||
nargs = len(args)
|
|
||||||
if (nargs > 0):
|
|
||||||
resource = args[0]
|
|
||||||
if (nargs > 1):
|
|
||||||
properties = args[1:]
|
|
||||||
else:
|
|
||||||
properties = [ "allprop" ]
|
|
||||||
else:
|
|
||||||
print "resource required"
|
|
||||||
sys.exit(-1)
|
|
||||||
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, username, password)
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource, properties, depth)
|
|
||||||
client.execute(propfind)
|
|
||||||
|
|
||||||
sys.stderr.write("response:\n\n")
|
|
||||||
print propfind.response["body"]
|
|
||||||
|
|
||||||
if propfind.response.has_key("document"):
|
|
||||||
sys.stderr.write("document tree:\n")
|
|
||||||
elem = propfind.response["document"]
|
|
||||||
dom = xml.dom.minidom.parseString(xml.etree.ElementTree.tostring(elem))
|
|
||||||
print dom.toprettyxml()
|
|
||||||
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
from config import hostname, port, username, password
|
|
||||||
import sys, getopt
|
|
||||||
import webdavlib
|
|
||||||
import urllib
|
|
||||||
import urllib2
|
|
||||||
import base64
|
|
||||||
import simplejson
|
|
||||||
import cookielib
|
|
||||||
|
|
||||||
def getAuthCookie(hostname, port, username, password) :
|
|
||||||
cjar = cookielib.CookieJar();
|
|
||||||
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cjar))
|
|
||||||
urllib2.install_opener(opener)
|
|
||||||
|
|
||||||
creds2 = simplejson.dumps({"userName":username, "password": password})
|
|
||||||
req = urllib2.Request("http://%s:%s/SOGo/connect" % (hostname, port), creds2,
|
|
||||||
{'Content-Type': 'application/json'})
|
|
||||||
|
|
||||||
fd = urllib2.urlopen(req)
|
|
||||||
|
|
||||||
for cookie in cjar :
|
|
||||||
if cookie.name == "0xHIGHFLYxSOGo":
|
|
||||||
authinfo = cookie.value
|
|
||||||
break
|
|
||||||
|
|
||||||
return "0xHIGHFLYxSOGo="+authinfo
|
|
||||||
|
|
||||||
def usage() :
|
|
||||||
msg ="""Usage:
|
|
||||||
%s [-h] [-H | --host=hostname] [-p|--passwd=password] \
|
|
||||||
[-P|--port=port] [-u|--user=username]\n""" % sys.argv[0]
|
|
||||||
|
|
||||||
sys.stderr.write(msg);
|
|
||||||
|
|
||||||
if __name__ == "__main__" :
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt (sys.argv[1:], "hH:p:P:u:", \
|
|
||||||
("host=", "passwd=", "port=", "user="));
|
|
||||||
except getopt.GetoptError:
|
|
||||||
usage()
|
|
||||||
exit(1)
|
|
||||||
for o, v in opts :
|
|
||||||
if o == "-h" :
|
|
||||||
usage()
|
|
||||||
exit(1)
|
|
||||||
elif o == "-H" or o == "--host" :
|
|
||||||
hostname = v
|
|
||||||
elif o == "-p" or o == "--passwd" :
|
|
||||||
password = v
|
|
||||||
elif o == "-P" or o == "--port" :
|
|
||||||
port = v
|
|
||||||
elif o == "-u" or o == "--user" :
|
|
||||||
username = v
|
|
||||||
|
|
||||||
print getAuthCookie(hostname, port, username, password)
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import sys
|
|
||||||
import unittest
|
|
||||||
import time
|
|
||||||
|
|
||||||
def UnitTestTextTestResultNewStartTest(self, test):
|
|
||||||
self.xstartTime = time.time()
|
|
||||||
self.oldStartTest(test)
|
|
||||||
|
|
||||||
def UnitTestTextTestResultNewStopTest(self, test):
|
|
||||||
unittest.TestResult.stopTest(self, test)
|
|
||||||
endTime = time.time()
|
|
||||||
delta = endTime - self.xstartTime
|
|
||||||
print " %f ms" % delta
|
|
||||||
|
|
||||||
def runTests():
|
|
||||||
unittest._TextTestResult.oldStartTest = unittest._TextTestResult.startTest
|
|
||||||
unittest._TextTestResult.startTest = UnitTestTextTestResultNewStartTest
|
|
||||||
unittest._TextTestResult.stopTest = UnitTestTextTestResultNewStopTest
|
|
||||||
|
|
||||||
argv = []
|
|
||||||
argv.extend(sys.argv)
|
|
||||||
argv.append("-v")
|
|
||||||
unittest.main(argv=argv)
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,180 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
|
|
||||||
from config import hostname, port, username, password
|
|
||||||
|
|
||||||
import carddav
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
import webdavlib
|
|
||||||
import time
|
|
||||||
|
|
||||||
|
|
||||||
class JsonDavEventTests(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self._connect_as_user()
|
|
||||||
|
|
||||||
def _connect_as_user(self, newuser=username, newpassword=password):
|
|
||||||
self.dv = carddav.Carddav(newuser, newpassword)
|
|
||||||
|
|
||||||
def _create_new_event(self, path):
|
|
||||||
gid = self.dv.newguid(path)
|
|
||||||
event = {'startDate': "2015-12-25",
|
|
||||||
'startTime': "10:00",
|
|
||||||
'endDate': "2015-12-25",
|
|
||||||
'endTime': "23:00",
|
|
||||||
'isTransparent': 0,
|
|
||||||
'sendAppointmentNotifications': 0,
|
|
||||||
'summary': "Big party",
|
|
||||||
'alarm': {'action': 'display',
|
|
||||||
'quantity': 10,
|
|
||||||
'unit': "MINUTES",
|
|
||||||
'reference': "BEFORE",
|
|
||||||
'relation': "START",
|
|
||||||
'email': "sogo1@example.com"},
|
|
||||||
'organizer': {'name': u"Balthazar C\xe9sar",
|
|
||||||
'email': "sogo2@example.com"},
|
|
||||||
'c_name': gid,
|
|
||||||
'c_folder': path
|
|
||||||
}
|
|
||||||
return (event, path, gid)
|
|
||||||
|
|
||||||
def _get_dav_data(self, filename, user=username, passwd=password):
|
|
||||||
w = webdavlib.WebDAVClient(hostname, port, user, passwd)
|
|
||||||
query = webdavlib.HTTPGET("http://localhost/SOGo/dav/%s/Calendar/personal/%s" % (username, filename))
|
|
||||||
w.execute(query)
|
|
||||||
self.assertEquals(query.response['status'], 200)
|
|
||||||
return query.response['body'].split("\r\n")
|
|
||||||
|
|
||||||
def _get_dav_field(self, davdata, fieldname):
|
|
||||||
try:
|
|
||||||
data = [a.split(':')[1] for a in davdata if fieldname in a][0]
|
|
||||||
except IndexError:
|
|
||||||
data = ''
|
|
||||||
return data
|
|
||||||
|
|
||||||
def test_create_new_event(self):
|
|
||||||
path = 'Calendar/personal'
|
|
||||||
(event, folder, gid) = self._create_new_event(path)
|
|
||||||
#print "Saving Event to:", folder, gid
|
|
||||||
self.dv.save_event(event, folder, gid)
|
|
||||||
#- Get the event back with JSON
|
|
||||||
self._connect_as_user()
|
|
||||||
self.dv.load_events()
|
|
||||||
elist = [e for e in self.dv.events if e['c_name'] == gid]
|
|
||||||
#- MUST have this event -- only once
|
|
||||||
self.assertEquals(len(elist), 1)
|
|
||||||
strdate = "%d-%.02d-%.02d" % time.gmtime(elist[0]['c_startdate'])[0:3]
|
|
||||||
self.assertEquals(strdate, event['startDate'])
|
|
||||||
#- Get the event back with DAV
|
|
||||||
dav = self._get_dav_data(gid, username, password)
|
|
||||||
self.assertEquals(self._get_dav_field(dav, 'SUMMARY:'), event['summary'])
|
|
||||||
|
|
||||||
|
|
||||||
class JsonDavPhoneTests(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self._connect_as_user()
|
|
||||||
self.newphone = [{'type': 'home', 'value': '123.456.7890'}]
|
|
||||||
self.newphones_difftype = [{'type': 'home', 'value': '123.456.7890'},
|
|
||||||
{'type': 'work', 'value': '987.654.3210'},
|
|
||||||
{'type': 'fax', 'value': '555.666.7777'}]
|
|
||||||
self.newphones_sametype = [{'type': 'work', 'value': '123.456.7890'},
|
|
||||||
{'type': 'work', 'value': '987.654.3210'}]
|
|
||||||
# Easier to erase them all in tearDown
|
|
||||||
self.allphones = list(self.newphone)
|
|
||||||
self.allphones.extend(self.newphones_difftype)
|
|
||||||
self.allphones.extend(self.newphones_sametype)
|
|
||||||
#- In case there are no cards for this user
|
|
||||||
try:
|
|
||||||
self._get_card()
|
|
||||||
except IndexError:
|
|
||||||
path = 'Contacts/personal'
|
|
||||||
(card, path, gid) = self._create_new_card(path)
|
|
||||||
self._save_card(card)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self._connect_as_user()
|
|
||||||
self._get_card()
|
|
||||||
#- Remove the phones we just added
|
|
||||||
for phone in self.allphones:
|
|
||||||
try:
|
|
||||||
self.card['phones'].pop(self.card['phones'].index(phone))
|
|
||||||
except ValueError:
|
|
||||||
#print "Can't find", phone
|
|
||||||
pass
|
|
||||||
self._save_card()
|
|
||||||
|
|
||||||
|
|
||||||
def _connect_as_user(self, newuser=username, newpassword=password):
|
|
||||||
self.dv = carddav.Carddav(newuser, newpassword)
|
|
||||||
|
|
||||||
def _create_new_card(self, path):
|
|
||||||
gid = self.dv.newguid(path)
|
|
||||||
card = {'c_categories': None,
|
|
||||||
'c_cn': 'John Doe',
|
|
||||||
'c_component': 'vcard',
|
|
||||||
'c_givenname': 'John Doe',
|
|
||||||
'c_mail': 'johndoe@nothere.com',
|
|
||||||
'c_name': gid,
|
|
||||||
'c_o': '',
|
|
||||||
'c_screenname': '',
|
|
||||||
'c_sn': '',
|
|
||||||
'c_telephonenumber': '123.456.7890',
|
|
||||||
'emails': [{'type': 'pref', 'value': 'johndoe@nothere.com'}],
|
|
||||||
'phones': [{'type': 'home', 'value': '111.222.3333'}],
|
|
||||||
'id': gid}
|
|
||||||
return (card, path, gid)
|
|
||||||
|
|
||||||
def _get_card(self, name="John Doe"):
|
|
||||||
tmp_card = self.dv.get_cards(name)[0]
|
|
||||||
self.card = self.dv.get_card(tmp_card['c_name'])
|
|
||||||
|
|
||||||
def _save_card(self, card=None):
|
|
||||||
if card:
|
|
||||||
self.dv.save_card(card)
|
|
||||||
else:
|
|
||||||
self.dv.save_card(self.card)
|
|
||||||
|
|
||||||
def _get_dav_data(self, filename, user=username, passwd=password):
|
|
||||||
w = webdavlib.WebDAVClient(hostname, port, user, passwd)
|
|
||||||
query = webdavlib.HTTPGET("http://localhost/SOGo/dav/%s/Contacts/personal/%s" % (username, filename))
|
|
||||||
w.execute(query)
|
|
||||||
self.assertEquals(query.response['status'], 200)
|
|
||||||
return query.response['body'].split("\r\n")
|
|
||||||
|
|
||||||
def _phone_to_dav_str(self, phonedict):
|
|
||||||
return "TEL;TYPE=%s:%s" % (phonedict['type'], phonedict['value'])
|
|
||||||
|
|
||||||
def _testMultiplePhones(self, phones):
|
|
||||||
""" Add Multiple Phones to Contact JSON and verify with DAV """
|
|
||||||
#- Use JSON to get CARD and add a phone and save it back
|
|
||||||
self._get_card()
|
|
||||||
oldphones = self.card['phones']
|
|
||||||
oldphones.extend(phones)
|
|
||||||
self._save_card()
|
|
||||||
#- Make sure that the phone is there when using JSON
|
|
||||||
self._connect_as_user()
|
|
||||||
self._get_card()
|
|
||||||
#print "C:::", self.card
|
|
||||||
testphones = self.card['phones']
|
|
||||||
#print "P1:", oldphones
|
|
||||||
#print "P2:", testphones
|
|
||||||
self.assertEquals(sorted(oldphones), sorted(testphones))
|
|
||||||
#- Verify that DAV has the same values
|
|
||||||
dav = self._get_dav_data(self.card['id'], username, password)
|
|
||||||
for phone in phones:
|
|
||||||
found = dav.index(self._phone_to_dav_str(phone))
|
|
||||||
self.assertTrue(found > 0)
|
|
||||||
|
|
||||||
def testSinglePhone(self):
|
|
||||||
self._testMultiplePhones(self.newphone)
|
|
||||||
|
|
||||||
def testMultipleDifferentPhones(self):
|
|
||||||
self._testMultiplePhones(self.newphones_difftype)
|
|
||||||
|
|
||||||
def testMultipleSameTypePhones(self):
|
|
||||||
self._testMultiplePhones(self.newphones_sametype)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
from config import hostname, port, username, password, mailserver, subscriber_username, attendee1, attendee1_delegate
|
|
||||||
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
import time
|
|
||||||
|
|
||||||
class CalDAVITIPDelegationTest(unittest.TestCase):
|
|
||||||
def testConfigPY(self):
|
|
||||||
""" config.py validation """
|
|
||||||
try:
|
|
||||||
test = hostname
|
|
||||||
except:
|
|
||||||
self.fail("'hostname' is not defined")
|
|
||||||
|
|
||||||
try:
|
|
||||||
test = username
|
|
||||||
except:
|
|
||||||
self.fail("'username' is not defined")
|
|
||||||
|
|
||||||
try:
|
|
||||||
test = subscriber_username
|
|
||||||
except:
|
|
||||||
self.fail("'subscriber_username' is not defined")
|
|
||||||
|
|
||||||
try:
|
|
||||||
test = attendee1
|
|
||||||
except:
|
|
||||||
self.fail("'attendee1' is not defined")
|
|
||||||
|
|
||||||
try:
|
|
||||||
test = attendee1_delegate
|
|
||||||
except:
|
|
||||||
self.fail("'attendee1_delegate' is not defined")
|
|
||||||
|
|
||||||
self.assertEquals(subscriber_username, attendee1,
|
|
||||||
"'subscriber_username' and 'attendee1'"
|
|
||||||
+ " must be the same user")
|
|
||||||
|
|
||||||
try:
|
|
||||||
test = mailserver
|
|
||||||
except:
|
|
||||||
self.fail("'mailserver' is not defined")
|
|
||||||
|
|
||||||
userHash = {}
|
|
||||||
userList = [ username, subscriber_username, attendee1_delegate ]
|
|
||||||
for user in userList:
|
|
||||||
self.assertFalse(userHash.has_key(user),
|
|
||||||
"username, attendee1, attendee1_delegate must"
|
|
||||||
+ " all be different users ('%s')"
|
|
||||||
% user)
|
|
||||||
userHash[user] = True
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
import webdavlib
|
|
||||||
|
|
||||||
from config import *
|
|
||||||
|
|
||||||
class HTTPContactCategoriesTest(unittest.TestCase):
|
|
||||||
def _setCategories(self, user, categories = None):
|
|
||||||
resource = '/SOGo/dav/%s/Contacts/' % user
|
|
||||||
if categories is None:
|
|
||||||
categories = []
|
|
||||||
elements = [ { "{urn:inverse:params:xml:ns:inverse-dav}category": x }
|
|
||||||
for x in categories ]
|
|
||||||
props = { "{urn:inverse:params:xml:ns:inverse-dav}contacts-categories": elements }
|
|
||||||
proppatch = webdavlib.WebDAVPROPPATCH(resource, props)
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, username, password)
|
|
||||||
client.execute(proppatch)
|
|
||||||
self.assertEquals(proppatch.response["status"], 207,
|
|
||||||
"failure (%s) setting '%s' categories on %s's contacts"
|
|
||||||
% (proppatch.response["status"],
|
|
||||||
"', '".join(categories), user))
|
|
||||||
|
|
||||||
def _getCategories(self, user):
|
|
||||||
resource = '/SOGo/dav/%s/Contacts/' % user
|
|
||||||
props = [ "{urn:inverse:params:xml:ns:inverse-dav}contacts-categories" ]
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource, props, "0")
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, username, password)
|
|
||||||
client.execute(propfind)
|
|
||||||
self.assertEquals(propfind.response["status"], 207,
|
|
||||||
"failure (%s) getting categories on %s's contacts"
|
|
||||||
% (propfind.response["status"], user))
|
|
||||||
|
|
||||||
categories = []
|
|
||||||
prop_nodes = propfind.response["document"].findall("{DAV:}response/{DAV:}propstat/{DAV:}prop/{urn:inverse:params:xml:ns:inverse-dav}contacts-categories")
|
|
||||||
for prop_node in prop_nodes:
|
|
||||||
cat_nodes = prop_node.findall("{urn:inverse:params:xml:ns:inverse-dav}category")
|
|
||||||
if cat_nodes is not None:
|
|
||||||
for cat_node in cat_nodes:
|
|
||||||
categories.append(cat_node.text)
|
|
||||||
|
|
||||||
return categories
|
|
||||||
|
|
||||||
|
|
||||||
def test(self):
|
|
||||||
self._setCategories(username, [])
|
|
||||||
cats = self._getCategories(username)
|
|
||||||
self.assertTrue(cats is not None and len(cats) == 0)
|
|
||||||
|
|
||||||
self._setCategories(username, [ "Coucou" ])
|
|
||||||
cats = self._getCategories(username)
|
|
||||||
self.assertTrue(cats is not None and len(cats) == 1)
|
|
||||||
self.assertEquals(cats[0], "Coucou")
|
|
||||||
|
|
||||||
self._setCategories(username, [ "Toto", "Cuicui" ])
|
|
||||||
cats = self._getCategories(username)
|
|
||||||
self.assertTrue(cats is not None and len(cats) == 2)
|
|
||||||
self.assertEquals(cats[0], "Toto")
|
|
||||||
self.assertEquals(cats[1], "Cuicui")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -1,52 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
import webdavlib
|
|
||||||
|
|
||||||
from config import *
|
|
||||||
|
|
||||||
class HTTPDefaultClassificationTest(unittest.TestCase):
|
|
||||||
def _setClassification(self, user, component, classification = ""):
|
|
||||||
resource = '/SOGo/dav/%s/Calendar/' % user
|
|
||||||
props = { "{urn:inverse:params:xml:ns:inverse-dav}%s-default-classification" % component: classification }
|
|
||||||
proppatch = webdavlib.WebDAVPROPPATCH(resource, props)
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, username, password)
|
|
||||||
client.execute(proppatch)
|
|
||||||
|
|
||||||
return (proppatch.response["status"] == 207);
|
|
||||||
|
|
||||||
def _getClassification(self, user, component):
|
|
||||||
resource = '/SOGo/dav/%s/Calendar/' % user
|
|
||||||
property_name = "{urn:inverse:params:xml:ns:inverse-dav}%s-default-classification" % component
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource, [ property_name ], "0")
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, username, password)
|
|
||||||
client.execute(propfind)
|
|
||||||
classification = None
|
|
||||||
propstat_nodes = propfind.response["document"].findall("{DAV:}response/{DAV:}propstat")
|
|
||||||
for propstat_node in propstat_nodes:
|
|
||||||
status_nodes = propstat_node.findall("{DAV:}status")
|
|
||||||
if status_nodes[0].text.lower() == "http/1.1 200 ok":
|
|
||||||
property_nodes = propstat_node.findall("{DAV:}prop/%s" % property_name)
|
|
||||||
if len(property_nodes) > 0:
|
|
||||||
classification = property_nodes[0].text
|
|
||||||
|
|
||||||
return classification
|
|
||||||
|
|
||||||
def test(self):
|
|
||||||
self.assertFalse(self._setClassification(username, "123456", "PUBLIC"),
|
|
||||||
"expected failure when setting a classification with an invalid property")
|
|
||||||
self.assertFalse(self._setClassification(username, "events", ""),
|
|
||||||
"expected failure when setting an empty classification")
|
|
||||||
self.assertFalse(self._setClassification(username, "events", "pouet"),
|
|
||||||
"expected failure when setting an invalid classification")
|
|
||||||
for component in [ "events", "tasks" ]:
|
|
||||||
for classification in [ "PUBLIC", "PRIVATE", "CONFIDENTIAL" ]:
|
|
||||||
self.assertTrue(self._setClassification(username, component, classification),
|
|
||||||
"error when setting classification to '%s'" % classification)
|
|
||||||
fetched_class = self._getClassification(username, component)
|
|
||||||
self.assertTrue(classification == fetched_class,
|
|
||||||
"set and fetched classifications do not match (%s != %s)" % (classification, fetched_class))
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
# FIXME: we should avoid using superuser if possible
|
|
||||||
|
|
||||||
from config import hostname, port, username, password, subscriber_username, \
|
|
||||||
superuser, superuser_password
|
|
||||||
|
|
||||||
import unittest
|
|
||||||
import sogotests
|
|
||||||
import utilities
|
|
||||||
import webdavlib
|
|
||||||
|
|
||||||
class iCalTest(unittest.TestCase):
|
|
||||||
def testPrincipalCollectionSet(self):
|
|
||||||
"""principal-collection-set: 'DAV' header must be returned with iCal 4"""
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, username, password)
|
|
||||||
resource = '/SOGo/dav/%s/' % username
|
|
||||||
|
|
||||||
# NOT iCal4
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource,
|
|
||||||
["{DAV:}principal-collection-set"],
|
|
||||||
0)
|
|
||||||
client.execute(propfind)
|
|
||||||
self.assertEquals(propfind.response["status"], 207)
|
|
||||||
headers = propfind.response["headers"]
|
|
||||||
self.assertFalse(headers.has_key("dav"),
|
|
||||||
"DAV header must not be returned when user-agent is NOT iCal 4")
|
|
||||||
|
|
||||||
# iCal4
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource,
|
|
||||||
["{DAV:}principal-collection-set"],
|
|
||||||
0)
|
|
||||||
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
|
|
||||||
client.execute(propfind)
|
|
||||||
self.assertEquals(propfind.response["status"], 207)
|
|
||||||
headers = propfind.response["headers"]
|
|
||||||
self.assertTrue(headers.has_key("dav"),
|
|
||||||
"DAV header must be returned when user-agent is iCal 4")
|
|
||||||
|
|
||||||
expectedDAVClasses = ["1", "2", "access-control", "calendar-access",
|
|
||||||
"calendar-schedule", "calendar-auto-schedule",
|
|
||||||
"calendar-proxy"]
|
|
||||||
davClasses = [x.strip() for x in headers["dav"].split(",")]
|
|
||||||
for davClass in expectedDAVClasses:
|
|
||||||
self.assertTrue(davClass in davClasses,
|
|
||||||
"DAV class '%s' not found" % davClass)
|
|
||||||
|
|
||||||
def _setMemberSet(self, owner, members, perm):
|
|
||||||
resource = '/SOGo/dav/%s/calendar-proxy-%s/' % (owner, perm)
|
|
||||||
membersHref = [ { "{DAV:}href": '/SOGo/dav/%s/' % x }
|
|
||||||
for x in members ]
|
|
||||||
props = { "{DAV:}group-member-set": membersHref }
|
|
||||||
proppatch = webdavlib.WebDAVPROPPATCH(resource, props)
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, superuser, superuser_password)
|
|
||||||
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
|
|
||||||
client.execute(proppatch)
|
|
||||||
self.assertEquals(proppatch.response["status"], 207,
|
|
||||||
"failure (%s) setting '%s' permission for '%s' on %s's calendars"
|
|
||||||
% (proppatch.response["status"], perm,
|
|
||||||
"', '".join(members), owner))
|
|
||||||
|
|
||||||
def _getMembership(self, user):
|
|
||||||
resource = '/SOGo/dav/%s/' % user
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource,
|
|
||||||
["{DAV:}group-membership"], 0)
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, superuser, superuser_password)
|
|
||||||
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
|
|
||||||
client.execute(propfind)
|
|
||||||
|
|
||||||
hrefs = propfind.response["document"].findall("{DAV:}response/{DAV:}propstat/{DAV:}prop/{DAV:}group-membership/{DAV:}href")
|
|
||||||
members = [x.text for x in hrefs]
|
|
||||||
|
|
||||||
return members
|
|
||||||
|
|
||||||
def _getProxyFor(self, user, perm):
|
|
||||||
resource = '/SOGo/dav/%s/' % user
|
|
||||||
prop = "{http://calendarserver.org/ns/}calendar-proxy-%s-for" % perm
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource, [prop], 0)
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, superuser, superuser_password)
|
|
||||||
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
|
|
||||||
client.execute(propfind)
|
|
||||||
|
|
||||||
hrefs = propfind.response["document"].findall("{DAV:}response/{DAV:}propstat/{DAV:}prop/{http://calendarserver.org/ns/}calendar-proxy-%s-for/{DAV:}href"
|
|
||||||
% perm)
|
|
||||||
members = [x.text[len("/SOGo/dav/"):-1] for x in hrefs]
|
|
||||||
|
|
||||||
return members
|
|
||||||
|
|
||||||
def testCalendarProxy(self):
|
|
||||||
"""calendar-proxy as used from iCal"""
|
|
||||||
self._setMemberSet(username, [], "read")
|
|
||||||
self._setMemberSet(username, [], "write")
|
|
||||||
self._setMemberSet(subscriber_username, [], "read")
|
|
||||||
self._setMemberSet(subscriber_username, [], "write")
|
|
||||||
self.assertEquals([], self._getMembership(username),
|
|
||||||
"'%s' must have no membership"
|
|
||||||
% username)
|
|
||||||
self.assertEquals([], self._getMembership(subscriber_username),
|
|
||||||
"'%s' must have no membership"
|
|
||||||
% subscriber_username)
|
|
||||||
self.assertEquals([], self._getProxyFor(username, "read"),
|
|
||||||
"'%s' must not be a proxy for anyone" % username)
|
|
||||||
self.assertEquals([], self._getProxyFor(username, "write"),
|
|
||||||
"'%s' must not be a proxy for anyone" % username)
|
|
||||||
self.assertEquals([], self._getProxyFor(subscriber_username, "read"),
|
|
||||||
"'%s' must not be a proxy for anyone" % subscriber_username)
|
|
||||||
self.assertEquals([], self._getProxyFor(subscriber_username, "write"),
|
|
||||||
"'%s' must not be a proxy for anyone" % subscriber_username)
|
|
||||||
|
|
||||||
for perm in ("read", "write"):
|
|
||||||
for users in ((username, subscriber_username),
|
|
||||||
(subscriber_username, username)):
|
|
||||||
self._setMemberSet(users[0], [users[1]], perm)
|
|
||||||
membership = self._getMembership(users[1])
|
|
||||||
self.assertEquals(['/SOGo/dav/%s/calendar-proxy-%s/'
|
|
||||||
% (users[0], perm)],
|
|
||||||
membership,
|
|
||||||
"'%s' must have %s access to %s's calendars"
|
|
||||||
% (users[1], perm, users[0]))
|
|
||||||
proxyFor = self._getProxyFor(users[1], perm)
|
|
||||||
self.assertEquals([users[0]], proxyFor,
|
|
||||||
"'%s' expected to be %s proxy for %s: %s"
|
|
||||||
% (users[1], perm, users[0], proxyFor))
|
|
||||||
|
|
||||||
def _testMapping(self, client, perm, resource, rights):
|
|
||||||
dav_utility = utilities.TestCalendarACLUtility(self, client, resource)
|
|
||||||
dav_utility.setupRights(subscriber_username, rights)
|
|
||||||
|
|
||||||
membership = self._getMembership(subscriber_username)
|
|
||||||
self.assertEquals(['/SOGo/dav/%s/calendar-proxy-%s/'
|
|
||||||
% (username, perm)],
|
|
||||||
membership,
|
|
||||||
"'%s' must have %s access to %s's calendars:\n%s"
|
|
||||||
% (subscriber_username, perm, username, membership))
|
|
||||||
proxyFor = self._getProxyFor(subscriber_username, perm)
|
|
||||||
self.assertEquals([username], proxyFor,
|
|
||||||
"'%s' expected to be %s proxy for %s: %s"
|
|
||||||
% (subscriber_username, perm, username, proxyFor))
|
|
||||||
|
|
||||||
def testCalendarProxy2(self):
|
|
||||||
"""calendar-proxy as used from SOGo"""
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, superuser, superuser_password)
|
|
||||||
client.user_agent = "DAVKit/4.0.1 (730); CalendarStore/4.0.1 (973); iCal/4.0.1 (1374); Mac OS X/10.6.2 (10C540)"
|
|
||||||
personal_resource = "/SOGo/dav/%s/Calendar/personal/" % username
|
|
||||||
dav_utility = utilities.TestCalendarACLUtility(self,
|
|
||||||
client,
|
|
||||||
personal_resource)
|
|
||||||
dav_utility.setupRights(subscriber_username, {})
|
|
||||||
dav_utility.subscribe([subscriber_username])
|
|
||||||
|
|
||||||
other_resource = ("/SOGo/dav/%s/Calendar/test-calendar-proxy2/"
|
|
||||||
% username)
|
|
||||||
delete = webdavlib.WebDAVDELETE(other_resource)
|
|
||||||
client.execute(delete)
|
|
||||||
mkcol = webdavlib.WebDAVMKCOL(other_resource)
|
|
||||||
client.execute(mkcol)
|
|
||||||
dav_utility = utilities.TestCalendarACLUtility(self,
|
|
||||||
client,
|
|
||||||
other_resource)
|
|
||||||
dav_utility.setupRights(subscriber_username, {})
|
|
||||||
dav_utility.subscribe([subscriber_username])
|
|
||||||
|
|
||||||
## we test the rights mapping
|
|
||||||
# write: write on 'personal', none on 'test-calendar-proxy2'
|
|
||||||
self._testMapping(client, "write", personal_resource,
|
|
||||||
{ "c": True, "d": False, "pu": "v" })
|
|
||||||
self._testMapping(client, "write", personal_resource,
|
|
||||||
{ "c": False, "d": True, "pu": "v" })
|
|
||||||
self._testMapping(client, "write", personal_resource,
|
|
||||||
{ "c": False, "d": False, "pu": "m" })
|
|
||||||
self._testMapping(client, "write", personal_resource,
|
|
||||||
{ "c": False, "d": False, "pu": "r" })
|
|
||||||
|
|
||||||
# read: read on 'personal', none on 'test-calendar-proxy2'
|
|
||||||
self._testMapping(client, "read", personal_resource,
|
|
||||||
{ "c": False, "d": False, "pu": "d" })
|
|
||||||
self._testMapping(client, "read", personal_resource,
|
|
||||||
{ "c": False, "d": False, "pu": "v" })
|
|
||||||
|
|
||||||
# write: read on 'personal', write on 'test-calendar-proxy2'
|
|
||||||
self._testMapping(client, "write", other_resource,
|
|
||||||
{ "c": False, "d": False, "pu": "r" })
|
|
||||||
|
|
||||||
## we test the unsubscription
|
|
||||||
# unsubscribed from personal, subscribed to 'test-calendar-proxy2'
|
|
||||||
dav_utility = utilities.TestCalendarACLUtility(self, client,
|
|
||||||
personal_resource)
|
|
||||||
dav_utility.unsubscribe([subscriber_username])
|
|
||||||
membership = self._getMembership(subscriber_username)
|
|
||||||
self.assertEquals(['/SOGo/dav/%s/calendar-proxy-write/' % username],
|
|
||||||
membership,
|
|
||||||
"'%s' must have write access to %s's calendars"
|
|
||||||
% (subscriber_username, username))
|
|
||||||
# unsubscribed from personal, unsubscribed from 'test-calendar-proxy2'
|
|
||||||
dav_utility = utilities.TestCalendarACLUtility(self, client,
|
|
||||||
other_resource)
|
|
||||||
dav_utility.unsubscribe([subscriber_username])
|
|
||||||
membership = self._getMembership(subscriber_username)
|
|
||||||
self.assertEquals([],
|
|
||||||
membership,
|
|
||||||
"'%s' must have no access to %s's calendars"
|
|
||||||
% (subscriber_username, username))
|
|
||||||
|
|
||||||
delete = webdavlib.WebDAVDELETE(other_resource)
|
|
||||||
client.execute(delete)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,677 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
from config import hostname, port, username, password, mailserver, subscriber_username, subscriber_password
|
|
||||||
|
|
||||||
import sys
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
import webdavlib
|
|
||||||
import time
|
|
||||||
|
|
||||||
# TODO
|
|
||||||
# add test with multiple sort criterias
|
|
||||||
|
|
||||||
def fetchUserEmail(login):
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port,
|
|
||||||
username, password)
|
|
||||||
resource = '/SOGo/dav/%s/' % login
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource,
|
|
||||||
["{urn:ietf:params:xml:ns:caldav}calendar-user-address-set"],
|
|
||||||
0)
|
|
||||||
client.execute(propfind)
|
|
||||||
nodes = propfind.xpath_evaluate('{DAV:}response/{DAV:}propstat/{DAV:}prop/C:calendar-user-address-set/{DAV:}href',
|
|
||||||
None)
|
|
||||||
|
|
||||||
return nodes[0].childNodes[0].nodeValue
|
|
||||||
|
|
||||||
message1 = """Return-Path: <cyril@cyril.dev>
|
|
||||||
Received: from cyril.dev (localhost [127.0.0.1])
|
|
||||||
by cyril.dev (Cyrus v2.3.8-Debian-2.3.8-1) with LMTPA;
|
|
||||||
Tue, 17 Dec 2009 07:42:16 -0400
|
|
||||||
Received: from aloha.dev (localhost [127.0.0.1])
|
|
||||||
by aloha.dev (Cyrus v2.3.8-Debian-2.3.8-1) with LMTPA;
|
|
||||||
Tue, 29 Sep 2009 07:42:16 -0400
|
|
||||||
Message-ID: <4AC1F29sept6.5060801@cyril.dev>
|
|
||||||
Date: Mon, 28 Sep 2009 07:42:14 -0400
|
|
||||||
From: Cyril <message1from@cyril.dev>
|
|
||||||
User-Agent: Thunderbird 2.0.0.22 (Macintosh/20090605)
|
|
||||||
References: <4AC3BF1B.3010806@inverse.ca>
|
|
||||||
MIME-Version: 1.0
|
|
||||||
To: message1to@cyril.dev
|
|
||||||
CC: 2message1cc@cyril.dev, user10@cyril.dev
|
|
||||||
Subject: message1subject
|
|
||||||
Content-Type: text/plain; charset=us-ascii; format=flowed
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Reply-To: support@inverse.ca
|
|
||||||
|
|
||||||
Hello Jacques,
|
|
||||||
|
|
||||||
Can you read me?
|
|
||||||
|
|
||||||
--
|
|
||||||
Cyril <cyril@cyril.dev>
|
|
||||||
"""
|
|
||||||
|
|
||||||
message2 = """Return-Path: <cyril@cyril.dev>
|
|
||||||
Received: from cyril.dev (localhost [127.0.0.1])
|
|
||||||
by cyril.dev (Cyrus v2.3.8-Debian-2.3.8-1) with LMTPA;
|
|
||||||
Tue, 09 Dec 2009 07:42:16 -0400
|
|
||||||
Message-ID: <410sepAC1F296.5060801a@cyril.dev>
|
|
||||||
Date: Tue, 10 Sep 2009 07:42:14 -0400
|
|
||||||
User-Agent: Thunderbird 2.0.0.22 (Macintosh/20090605)
|
|
||||||
MIME-Version: 1.0
|
|
||||||
From: Cyril <message2from@cyril.dev>
|
|
||||||
To: message2to@cyril.dev
|
|
||||||
CC: 3message2cc@cyril.dev
|
|
||||||
Subject: message2subject
|
|
||||||
Content-Type: text/plain; charset=us-ascii; format=flowed
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Reply-To: support@inverse.ca
|
|
||||||
|
|
||||||
Hello Jacques,
|
|
||||||
|
|
||||||
Can you read me?
|
|
||||||
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
Stuff StuffStuffStuffStuff StuffStuffStuff StuffStuff
|
|
||||||
--
|
|
||||||
Cyril <cyril@cyril.dev>
|
|
||||||
"""
|
|
||||||
|
|
||||||
message3 = """Return-Path: <cyril@cyril.dev>
|
|
||||||
Received: from cyril.dev (localhost [127.0.0.1])
|
|
||||||
by cyril.dev (Cyrus v2.3.8-Debian-2.3.8-1) with LMTPA;
|
|
||||||
Tue, 15 Dec 2009 07:42:16 -0400
|
|
||||||
Message-ID: <4AC1aF2dec96.5060801a@cyril.dev>
|
|
||||||
Date: Tue, 10 Dec 2009 07:42:14 -0400
|
|
||||||
User-Agent: Thunderbird 2.0.0.22 (Macintosh/20090605)
|
|
||||||
MIME-Version: 1.0
|
|
||||||
From: Cyril <message3from@cyril.dev>
|
|
||||||
To: message3to@cyril.dev
|
|
||||||
CC: 1message3cc@cyril.dev
|
|
||||||
Subject: Hallo
|
|
||||||
Content-Type: text/plain; charset=us-ascii; format=flowed
|
|
||||||
Content-Transfer-Encoding: 7bit
|
|
||||||
Reply-To: support@inverse.ca
|
|
||||||
|
|
||||||
Hello Jacques,
|
|
||||||
|
|
||||||
Can you read me?
|
|
||||||
|
|
||||||
This message is just a bit larger than message1 but smaller than message2
|
|
||||||
--
|
|
||||||
Cyril <cyril@cyril.dev>
|
|
||||||
"""
|
|
||||||
message1_received = """Received: from cyril.dev (localhost [127.0.0.1])
|
|
||||||
by cyril.dev (Cyrus v2.3.8-Debian-2.3.8-1) with LMTPA;
|
|
||||||
Tue, 17 Dec 2009 07:42:16 -0400"""
|
|
||||||
|
|
||||||
class DAVMailCollectionTest():
|
|
||||||
resource = '/SOGo/dav/%s/Mail/' % username
|
|
||||||
user_email = None
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.client = webdavlib.WebDAVClient(hostname, port,
|
|
||||||
username, password)
|
|
||||||
if self.user_email is None:
|
|
||||||
self.user_email = fetchUserEmail(username)
|
|
||||||
if self.user_email.startswith("mailto:"):
|
|
||||||
self.user_email = self.user_email[7:]
|
|
||||||
|
|
||||||
self.resource = '/SOGo/dav/%s/Mail/%s_A_%s/' \
|
|
||||||
% (username,
|
|
||||||
username.replace("@", "_A_").replace(".", "_D_"),
|
|
||||||
mailserver.replace(".", "_D_"))
|
|
||||||
|
|
||||||
## helper methods
|
|
||||||
def _makeCollection(self, name, status = 201):
|
|
||||||
url = "%s%s" % (self.resource, name)
|
|
||||||
mkcol = webdavlib.WebDAVMKCOL(url)
|
|
||||||
self.client.execute(mkcol)
|
|
||||||
self.assertEquals(mkcol.response["status"], status,
|
|
||||||
"failure creating collection"
|
|
||||||
"(code = %d)" % mkcol.response["status"])
|
|
||||||
|
|
||||||
def _deleteCollection(self, name, status = 204):
|
|
||||||
url = "%sfolder%s" % (self.resource, name)
|
|
||||||
delete = webdavlib.WebDAVDELETE(url)
|
|
||||||
self.client.execute(delete)
|
|
||||||
self.assertEquals(delete.response["status"], status,
|
|
||||||
"failure deleting collection"
|
|
||||||
"(code = %d)" % delete.response["status"])
|
|
||||||
|
|
||||||
def _putMessage(self, client, folder, message,
|
|
||||||
exp_status = 201):
|
|
||||||
url = "%sfolder%s" % (self.resource, folder)
|
|
||||||
put = webdavlib.HTTPPUT(url, message)
|
|
||||||
put.content_type = "message/rfc822"
|
|
||||||
client.execute(put)
|
|
||||||
if (exp_status is not None):
|
|
||||||
self.assertEquals(put.response["status"], exp_status,
|
|
||||||
"message creation/modification:"
|
|
||||||
" expected status code '%d' (received '%d')"
|
|
||||||
% (exp_status, put.response["status"]))
|
|
||||||
return put.response["headers"]["location"]
|
|
||||||
|
|
||||||
def _testProperty (self, url, property, expected, isDate = 0):
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(url, (property, ), 0)
|
|
||||||
self.client.execute(propfind)
|
|
||||||
key = property.replace("{urn:schemas:httpmail:}", "a:")
|
|
||||||
key = key.replace("{urn:schemas:mailheader:}", "a:")
|
|
||||||
tmp = propfind.xpath_evaluate("{DAV:}response/{DAV:}propstat/{DAV:}prop")
|
|
||||||
prop = tmp[0].firstChild;
|
|
||||||
result = None
|
|
||||||
|
|
||||||
if prop:
|
|
||||||
result = prop._get_firstChild()._get_nodeValue()
|
|
||||||
#print key, result
|
|
||||||
|
|
||||||
if isDate:
|
|
||||||
tstruct = time.strptime (result, "%a, %d %b %Y %H:%M:%S %Z")
|
|
||||||
result = int (time.mktime (tstruct))
|
|
||||||
|
|
||||||
self.assertEquals(result, expected,
|
|
||||||
"failure in propfind"
|
|
||||||
"(%s != %s)" % (result, expected))
|
|
||||||
|
|
||||||
def testMKCOL(self):
|
|
||||||
"""Folder creation"""
|
|
||||||
self._deleteCollection("test-dav-mail-%40-abc")
|
|
||||||
self._deleteCollection("test-dav-mail-@-def")
|
|
||||||
self._deleteCollection("test-dav-mail-%20-ghi")
|
|
||||||
|
|
||||||
self._makeCollection("test-dav-mail-%40-abc")
|
|
||||||
self._makeCollection("test-dav-mail-@-def")
|
|
||||||
self._makeCollection("test-dav-mail-%20-ghi")
|
|
||||||
self._makeCollection("test-dav-mail-%25-jkl", 500)
|
|
||||||
|
|
||||||
# Test MOVE
|
|
||||||
# self._makeCollection ("test-dav-mail-movable")
|
|
||||||
# url = "%sfolder%s" % (self.resource, "test-dav-mail-movable")
|
|
||||||
# move = webdavlib.WebDAVMOVE (url)
|
|
||||||
# move.destination = "http://cyril.dev%s%s2" % (self.resource, "test-dav-mail-movable")
|
|
||||||
# move.host = "cyril.dev"
|
|
||||||
# self.client.execute (move)
|
|
||||||
# self.assertEquals(move.response["status"], 204,
|
|
||||||
# "failure creating collection"
|
|
||||||
# "(code = %d)" % move.response["status"])
|
|
||||||
|
|
||||||
def testPUT(self):
|
|
||||||
"""Message creation"""
|
|
||||||
self._deleteCollection("test-dav-mail")
|
|
||||||
self._makeCollection("test-dav-mail")
|
|
||||||
|
|
||||||
# message creation on collection url
|
|
||||||
url = "%s%s" % (self.resource, "foldertest-dav-mail/")
|
|
||||||
put = webdavlib.HTTPPUT(url, message1)
|
|
||||||
put.content_type = "message/rfc822"
|
|
||||||
self.client.execute(put)
|
|
||||||
self.assertEquals(put.response["status"], 201,
|
|
||||||
"failure putting message"
|
|
||||||
"(code = %d)" % put.response["status"])
|
|
||||||
|
|
||||||
itemLocation = put.response["headers"]["location"]
|
|
||||||
get = webdavlib.WebDAVGET(itemLocation)
|
|
||||||
self.client.execute(get)
|
|
||||||
self.assertEquals(get.response["status"], 200,
|
|
||||||
"failure getting item"
|
|
||||||
"(code = %d)" % get.response["status"])
|
|
||||||
|
|
||||||
# message creation with explicit filename
|
|
||||||
url = "%s%s" %(self.resource, "foldertest-dav-mail/blabla.eml")
|
|
||||||
put = webdavlib.HTTPPUT(url, message1)
|
|
||||||
put.content_type = "message/rfc822"
|
|
||||||
self.client.execute(put)
|
|
||||||
self.assertEquals(put.response["status"], 201,
|
|
||||||
"failure putting message"
|
|
||||||
"(code = %d)" % put.response["status"])
|
|
||||||
|
|
||||||
itemLocation = put.response["headers"]["location"]
|
|
||||||
get = webdavlib.WebDAVGET(itemLocation)
|
|
||||||
self.client.execute(get)
|
|
||||||
self.assertEquals(get.response["status"], 200,
|
|
||||||
"failure getting item"
|
|
||||||
"(code = %d)" % get.response["status"])
|
|
||||||
|
|
||||||
self._deleteCollection("test-dav-mail")
|
|
||||||
|
|
||||||
def _testFilters(self, filters):
|
|
||||||
for filter in filters:
|
|
||||||
self._testFilter(filter)
|
|
||||||
|
|
||||||
def _testFilter(self, filter):
|
|
||||||
expected_hrefs = {}
|
|
||||||
expected_count = len(filter[1])
|
|
||||||
for href in filter[1]:
|
|
||||||
expected_hrefs[href] = True
|
|
||||||
|
|
||||||
received_count = 0
|
|
||||||
url = "%sfolder%s" % (self.resource, "test-dav-mail")
|
|
||||||
query = webdavlib.MailDAVMailQuery(url, ["displayname"], filter[0])
|
|
||||||
self.client.execute(query)
|
|
||||||
self.assertEquals(query.response["status"], 207,
|
|
||||||
"filter %s:\n\tunexpected status: %d"
|
|
||||||
% (filter[0], query.response["status"]))
|
|
||||||
response_nodes = query.xpath_evaluate("{DAV:}response")
|
|
||||||
for response_node in response_nodes:
|
|
||||||
href_node = query.xpath_evaluate("{DAV:}href", response_node)[0]
|
|
||||||
href = href_node.childNodes[0].nodeValue
|
|
||||||
received_count = received_count + 1
|
|
||||||
self.assertTrue(expected_hrefs.has_key(href),
|
|
||||||
"filter %s:\n\tunexpected href: %s" % (filter[0], href))
|
|
||||||
|
|
||||||
self.assertEquals(len(filter[1]), received_count,
|
|
||||||
"filter %s:\n\tunexpected amount of refs: %d"
|
|
||||||
% (filter[0], received_count))
|
|
||||||
|
|
||||||
def _testSort(self, sortOrder, ascending = True):
|
|
||||||
expected_hrefs = sortOrder[1]
|
|
||||||
expected_count = len(expected_hrefs)
|
|
||||||
|
|
||||||
received_count = 0
|
|
||||||
url = "%sfolder%s" % (self.resource, "test-dav-mail")
|
|
||||||
query = webdavlib.MailDAVMailQuery(url, ["displayname"],
|
|
||||||
None, sortOrder[0], ascending)
|
|
||||||
self.client.execute(query)
|
|
||||||
self.assertEquals(query.response["status"], 207,
|
|
||||||
"sortOrder %s:\n\tunexpected status: %d"
|
|
||||||
% (sortOrder[0], query.response["status"]))
|
|
||||||
response_nodes = query.response["document"].findall("{DAV:}response")
|
|
||||||
for response_node in response_nodes:
|
|
||||||
href_node = response_node.find("{DAV:}href")
|
|
||||||
href = href_node.text
|
|
||||||
self.assertEquals(expected_hrefs[received_count], href,
|
|
||||||
"sortOrder %s:\n\tunexpected href: %s (expecting: %s)"
|
|
||||||
% (sortOrder[0], href,
|
|
||||||
expected_hrefs[received_count]))
|
|
||||||
received_count = received_count + 1
|
|
||||||
|
|
||||||
self.assertEquals(expected_count, received_count,
|
|
||||||
"sortOrder %s:\n\tunexpected amount of refs: %d"
|
|
||||||
% (sortOrder[0], received_count))
|
|
||||||
|
|
||||||
def testREPORTMailQueryFilters(self):
|
|
||||||
"""mail-query filters"""
|
|
||||||
self._deleteCollection("test-dav-mail")
|
|
||||||
self._makeCollection("test-dav-mail")
|
|
||||||
|
|
||||||
msg1Loc = self._putMessage(self.client, "test-dav-mail", message1)
|
|
||||||
parsed = webdavlib.HTTPUnparsedURL(msg1Loc)
|
|
||||||
msg1Path = parsed.path
|
|
||||||
msg2Loc = self._putMessage(self.client, "test-dav-mail", message2)
|
|
||||||
parsed = webdavlib.HTTPUnparsedURL(msg2Loc)
|
|
||||||
msg2Path = parsed.path
|
|
||||||
msg3Loc = self._putMessage(self.client, "test-dav-mail", message3)
|
|
||||||
parsed = webdavlib.HTTPUnparsedURL(msg3Loc)
|
|
||||||
msg3Path = parsed.path
|
|
||||||
|
|
||||||
properties = ["{DAV:}displayname"]
|
|
||||||
|
|
||||||
## 1. test filter: receive-date
|
|
||||||
# SINCE, BEFORE, ON
|
|
||||||
# q = MailDAVMailQuery(self.resource, properties, filters = None)
|
|
||||||
|
|
||||||
filters = (({ "receive-date": { "from": "20091201T000000Z",
|
|
||||||
"to": "20091208T000000Z" } },
|
|
||||||
[]),
|
|
||||||
({ "receive-date": { "from": "20091208T000000Z",
|
|
||||||
"to": "20091213T134300Z" } },
|
|
||||||
[ msg2Loc ]),
|
|
||||||
({ "receive-date": { "from": "20091208T000000Z",
|
|
||||||
"to": "20091216T134300Z" } },
|
|
||||||
[ msg2Loc, msg3Loc ]),
|
|
||||||
({ "receive-date": { "from": "20091216T000000Z",
|
|
||||||
"to": "20091220T134300Z" } },
|
|
||||||
[ msg1Loc ]),
|
|
||||||
({ "receive-date": { "from": "20091220T000000Z",
|
|
||||||
"to": "20091229T134300Z" } },
|
|
||||||
[]))
|
|
||||||
# receive-date seems to be considered the same as date by imapd
|
|
||||||
print "Warning, receive-date test disabled"
|
|
||||||
#self._testFilters(filters)
|
|
||||||
|
|
||||||
## 1. test filter: date
|
|
||||||
# SENTSINCE, SENTBEFORE, SENTON
|
|
||||||
|
|
||||||
filters = (({ "date": { "from": "20090101T000000Z",
|
|
||||||
"to": "20090201T000000Z" } },
|
|
||||||
[]),
|
|
||||||
({ "date": { "from": "20090912T000000Z",
|
|
||||||
"to": "20090929T134300Z" } },
|
|
||||||
[ msg1Loc ]),
|
|
||||||
({ "date": { "from": "20090929T134300Z",
|
|
||||||
"to": "20091209T000000Z" } },
|
|
||||||
[]),
|
|
||||||
({ "date": { "from": "20090901T134300Z",
|
|
||||||
"to": "20091209T000000Z" } },
|
|
||||||
[ msg1Loc, msg2Loc ]),
|
|
||||||
({ "date": { "from": "20091201T000000Z",
|
|
||||||
"to": "20091211T000000Z" } },
|
|
||||||
[ msg3Loc ]),
|
|
||||||
({ "date": { "from": "20091211T000000Z",
|
|
||||||
"to": "20101211T000000Z" } },
|
|
||||||
[]),
|
|
||||||
({ "date": { "from": "20090101T000000Z",
|
|
||||||
"to": "20100101T000000Z" } },
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]))
|
|
||||||
self._testFilters(filters)
|
|
||||||
|
|
||||||
## 1. test filter: sequence
|
|
||||||
# x:y
|
|
||||||
filters = (({ "sequence": { "from": "1" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "sequence": { "from": "5" }},
|
|
||||||
[]),
|
|
||||||
({ "sequence": { "to": "5" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "sequence": { "from": "1",
|
|
||||||
"to": "2" }},
|
|
||||||
[ msg1Loc, msg2Loc ]))
|
|
||||||
# Sequence not yet implemented
|
|
||||||
print "Warning, sequence test disabled"
|
|
||||||
#self._testFilters(filters)
|
|
||||||
|
|
||||||
## 1. test filter: uid
|
|
||||||
# UID
|
|
||||||
filters = (({ "uid": { "from": "1" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
# disabled because we get 3
|
|
||||||
#({ "uid": { "from": "5" }},
|
|
||||||
# []),
|
|
||||||
({ "uid": { "to": "5" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "uid": { "from": "1",
|
|
||||||
"to": "2" }},
|
|
||||||
[ msg1Loc, msg2Loc ]))
|
|
||||||
print "Warning, one of the uid tests is disabled"
|
|
||||||
self._testFilters(filters)
|
|
||||||
|
|
||||||
## 1. test filter: from
|
|
||||||
# FROM
|
|
||||||
filters = (({ "from": { "match": "message" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "from": { "match": "Cyril" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "from": { "match": "cyril.dev" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "from": { "match": "message1from" }},
|
|
||||||
[ msg1Loc ]),
|
|
||||||
({ "from": { "match": "message2from" }},
|
|
||||||
[ msg2Loc ]),
|
|
||||||
({ "from": { "match": "message3from" }},
|
|
||||||
[ msg3Loc ]))
|
|
||||||
self._testFilters(filters)
|
|
||||||
|
|
||||||
## 1. test filter: to
|
|
||||||
# TO
|
|
||||||
filters = (({ "to": { "match": "message" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "to": { "match": "Cyril" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "to": { "match": "cyril.dev" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "to": { "match": "message1to" }},
|
|
||||||
[ msg1Loc ]),
|
|
||||||
({ "to": { "match": "message2to" }},
|
|
||||||
[ msg2Loc ]),
|
|
||||||
({ "to": { "match": "message3to" }},
|
|
||||||
[ msg3Loc ]))
|
|
||||||
self._testFilters(filters)
|
|
||||||
|
|
||||||
## 1. test filter: cc
|
|
||||||
# CC
|
|
||||||
filters = (({ "cc": { "match": "message" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "cc": { "match": "Cyril" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "cc": { "match": "cyril.dev" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "cc": { "match": "message1cc" }},
|
|
||||||
[ msg1Loc ]),
|
|
||||||
({ "cc": { "match": "message2cc" }},
|
|
||||||
[ msg2Loc ]),
|
|
||||||
({ "cc": { "match": "message3cc" }},
|
|
||||||
[ msg3Loc ]))
|
|
||||||
self._testFilters(filters)
|
|
||||||
|
|
||||||
## 1. test filter: bcc
|
|
||||||
# BCC
|
|
||||||
## TODO
|
|
||||||
|
|
||||||
## 1. test filter: body
|
|
||||||
# BODY
|
|
||||||
filters = (({ "body": { "match": "Hello" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "body": { "match": "Stuff" }},
|
|
||||||
[ msg2Loc ]),
|
|
||||||
({ "body": { "match": "DOESNOT MATCH" }},
|
|
||||||
[]))
|
|
||||||
self._testFilters(filters)
|
|
||||||
|
|
||||||
## 1. test filter: size
|
|
||||||
# LARGER, SMALLER
|
|
||||||
#1 848
|
|
||||||
#2 4308
|
|
||||||
#3 699
|
|
||||||
filters = (({ "size": { "min": "300",
|
|
||||||
"max": "300" }},
|
|
||||||
[]),
|
|
||||||
({ "size": { "min": "800",
|
|
||||||
"max": "800" }},
|
|
||||||
[]),
|
|
||||||
({ "size": { "min": "5000",
|
|
||||||
"max": "5000" }},
|
|
||||||
[]),
|
|
||||||
({ "size": { "min": "838",
|
|
||||||
"max": "838" }},
|
|
||||||
[ msg1Loc ]),
|
|
||||||
({ "size": { "min": "699",
|
|
||||||
"max": "4308" }},
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]),
|
|
||||||
({ "size": { "min": "700",
|
|
||||||
"max": "4308" }},
|
|
||||||
[ msg1Loc, msg2Loc ]),
|
|
||||||
({ "size": { "min": "698",
|
|
||||||
"max": "848" }},
|
|
||||||
[ msg1Loc, msg3Loc ]),
|
|
||||||
({ "size": { "min": "300",
|
|
||||||
"max": "5000" },
|
|
||||||
"size": { "min": "840",
|
|
||||||
"max": "850",
|
|
||||||
"not": "true" }},
|
|
||||||
[ msg2Loc, msg3Loc ]))
|
|
||||||
|
|
||||||
print "message flags are not handled yet"
|
|
||||||
## 1. test filter: answered
|
|
||||||
# ANSWERED, UNANSWERED
|
|
||||||
## 1. test filter: draft
|
|
||||||
# DRAFT
|
|
||||||
## 1. test filter: flagged
|
|
||||||
# FLAGGED
|
|
||||||
## 1. test filter: recent
|
|
||||||
# RECENT
|
|
||||||
## 1. test filter: seen
|
|
||||||
# SEEN
|
|
||||||
## 1. test filter: deleted
|
|
||||||
# DELETED
|
|
||||||
## 1. test filter: keywords
|
|
||||||
# KEYWORD x
|
|
||||||
|
|
||||||
## 1. test filter: multiple combinations
|
|
||||||
filters = (({ "body": { "match": "Hello" },
|
|
||||||
"cc": { "match": "message1cc" }},
|
|
||||||
[ msg1Loc ]),
|
|
||||||
({ "to": { "match": "message" },
|
|
||||||
"uid": { "from": "1",
|
|
||||||
"to": "2" }},
|
|
||||||
[ msg1Loc, msg2Loc ]),
|
|
||||||
({ "to": { "match": "message" },
|
|
||||||
"uid": { "from": "1",
|
|
||||||
"to": "2" },
|
|
||||||
"cc": { "match": "message3cc" }},
|
|
||||||
[]))
|
|
||||||
self._testFilters(filters)
|
|
||||||
|
|
||||||
self._deleteCollection("test-dav-mail")
|
|
||||||
|
|
||||||
def testREPORTMailQuerySort(self):
|
|
||||||
"""mail-query sort"""
|
|
||||||
self._deleteCollection("test-dav-mail")
|
|
||||||
self._makeCollection("test-dav-mail")
|
|
||||||
|
|
||||||
msg1Loc = self._putMessage(self.client, "test-dav-mail", message1)
|
|
||||||
parsed = webdavlib.HTTPUnparsedURL(msg1Loc)
|
|
||||||
msg1Path = parsed.path
|
|
||||||
msg2Loc = self._putMessage(self.client, "test-dav-mail", message2)
|
|
||||||
parsed = webdavlib.HTTPUnparsedURL(msg2Loc)
|
|
||||||
msg2Path = parsed.path
|
|
||||||
msg3Loc = self._putMessage(self.client, "test-dav-mail", message3)
|
|
||||||
parsed = webdavlib.HTTPUnparsedURL(msg3Loc)
|
|
||||||
msg3Path = parsed.path
|
|
||||||
|
|
||||||
# 1. test sort: (receive-date) ARRIVAL
|
|
||||||
self._testSort(([ "{urn:schemas:mailheader:}received" ],
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]))
|
|
||||||
|
|
||||||
# 1. test sort: (date) DATE
|
|
||||||
self._testSort(([ "{urn:schemas:mailheader:}date" ],
|
|
||||||
[ msg2Loc, msg1Loc, msg3Loc ]))
|
|
||||||
|
|
||||||
# 1. test sort: FROM
|
|
||||||
self._testSort(([ "{urn:schemas:mailheader:}from" ],
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]))
|
|
||||||
|
|
||||||
# 1. test sort: TO
|
|
||||||
self._testSort(([ "{urn:schemas:mailheader:}to" ],
|
|
||||||
[ msg1Loc, msg2Loc, msg3Loc ]))
|
|
||||||
|
|
||||||
# 1. test sort: CC
|
|
||||||
self._testSort(([ "{urn:schemas:mailheader:}cc" ],
|
|
||||||
[ msg3Loc, msg1Loc, msg2Loc ]))
|
|
||||||
|
|
||||||
# 1. test sort: SUBJECT
|
|
||||||
self._testSort(([ "{DAV:}displayname" ],
|
|
||||||
[ msg3Loc, msg1Loc, msg2Loc ]))
|
|
||||||
self._testSort(([ "{urn:schemas:mailheader:}subject" ],
|
|
||||||
[ msg3Loc, msg1Loc, msg2Loc ]))
|
|
||||||
|
|
||||||
# 1. test sort: SIZE
|
|
||||||
self._testSort(([ "{DAV:}getcontentlength" ],
|
|
||||||
[ msg3Loc, msg1Loc, msg2Loc ]))
|
|
||||||
|
|
||||||
# 1. test sort: REVERSE CC
|
|
||||||
self._testSort(([ "{urn:schemas:mailheader:}cc" ],
|
|
||||||
[ msg2Loc, msg1Loc, msg3Loc ]),
|
|
||||||
False)
|
|
||||||
|
|
||||||
self._deleteCollection("test-dav-mail")
|
|
||||||
|
|
||||||
def testPROPFIND(self):
|
|
||||||
"""Message properties"""
|
|
||||||
self._deleteCollection ("test-dav-mail")
|
|
||||||
self._makeCollection ("test-dav-mail")
|
|
||||||
|
|
||||||
url = "%s%s" % (self.resource, "foldertest-dav-mail/")
|
|
||||||
put = webdavlib.HTTPPUT (url, message1)
|
|
||||||
put.content_type = "message/rfc822"
|
|
||||||
self.client.execute (put)
|
|
||||||
self.assertEquals(put.response["status"], 201,
|
|
||||||
"failure putting message"
|
|
||||||
"(code = %d)" % put.response["status"])
|
|
||||||
|
|
||||||
itemLocation = put.response["headers"]["location"]
|
|
||||||
tests = (("{urn:schemas:httpmail:}date", 1254156134, 1),
|
|
||||||
("{urn:schemas:httpmail:}hasattachment", "0", 0),
|
|
||||||
("{urn:schemas:httpmail:}read", "0", 0),
|
|
||||||
("{urn:schemas:httpmail:}textdescription",
|
|
||||||
"<![CDATA[%s]]>" % message1, 0),
|
|
||||||
("{urn:schemas:httpmail:}unreadcount", None, 0),
|
|
||||||
("{urn:schemas:mailheader:}cc",
|
|
||||||
"2message1cc@cyril.dev, user10@cyril.dev", 0),
|
|
||||||
("{urn:schemas:mailheader:}date",
|
|
||||||
"Mon, 28 Sep 2009 11:42:14 GMT", 0),
|
|
||||||
("{urn:schemas:mailheader:}from",
|
|
||||||
"Cyril <message1from@cyril.dev>", 0),
|
|
||||||
("{urn:schemas:mailheader:}in-reply-to", None, 0),
|
|
||||||
("{urn:schemas:mailheader:}message-id",
|
|
||||||
"<4AC1F29sept6.5060801@cyril.dev>", 0),
|
|
||||||
("{urn:schemas:mailheader:}received", message1_received, 0),
|
|
||||||
("{urn:schemas:mailheader:}references",
|
|
||||||
"<4AC3BF1B.3010806@inverse.ca>", 0),
|
|
||||||
("{urn:schemas:mailheader:}subject", "message1subject", 0),
|
|
||||||
("{urn:schemas:mailheader:}to", "message1to@cyril.dev", 0))
|
|
||||||
|
|
||||||
for test in tests:
|
|
||||||
property, expected, isDate = test
|
|
||||||
self._testProperty(itemLocation, property, expected, isDate)
|
|
||||||
|
|
||||||
self._deleteCollection ("test-dav-mail")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,60 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
from config import hostname, port, username, password, white_listed_attendee
|
|
||||||
|
|
||||||
import preferences
|
|
||||||
import simplejson
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
import utilities
|
|
||||||
|
|
||||||
class preferencesTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.prefs = preferences.preferences()
|
|
||||||
# because if not set in vacation will not be found later
|
|
||||||
# we must make sure they are there at the start
|
|
||||||
self.prefs.set_or_create("autoReplyText", '', ["defaults", "Vacation"])
|
|
||||||
self.prefs.set_or_create("PreventInvitations", 0, ["settings", "Calendar"])
|
|
||||||
self.prefs.set_or_create("PreventInvitationsWhitelist", [], ["settings", "Calendar"])
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self.prefs.set("autoReplyText", "")
|
|
||||||
|
|
||||||
def _setTextPref(self, prefText = None ):
|
|
||||||
""" set a text preference to a known value """
|
|
||||||
self.prefs.set("autoReplyText", prefText)
|
|
||||||
|
|
||||||
# make sure it was set correctly
|
|
||||||
prefData = self.prefs.get("Vacation")
|
|
||||||
|
|
||||||
self.assertEqual(prefData["autoReplyText"], prefText,
|
|
||||||
"%s != %s" % (prefData["autoReplyText"], prefText))
|
|
||||||
|
|
||||||
def testSetTextPreferences(self):
|
|
||||||
""" Set/get a text preference - normal characters"""
|
|
||||||
self._setTextPref("defaultText")
|
|
||||||
|
|
||||||
def testSetTextPreferencesWeirdChars(self):
|
|
||||||
""" Set/get a text preference - weird characters - used to crash on 1.3.12"""
|
|
||||||
prefText = "weird data \ ' \"; ^"
|
|
||||||
self._setTextPref(prefText)
|
|
||||||
|
|
||||||
def testSetPreventInvitation(self):
|
|
||||||
""" Set/get the PreventInvitation pref"""
|
|
||||||
self.prefs.set('PreventInvitations', 0)
|
|
||||||
notset = self.prefs.get('Calendar')['PreventInvitations']
|
|
||||||
self.assertEqual(notset, 0)
|
|
||||||
self.prefs.set('PreventInvitations', 1)
|
|
||||||
isset = self.prefs.get('Calendar')['PreventInvitations']
|
|
||||||
self.assertEqual(isset, 1)
|
|
||||||
|
|
||||||
def testPreventInvitationsWhiteList(self):
|
|
||||||
"""Add to the PreventInvitations Whitelist"""
|
|
||||||
self.prefs.set("PreventInvitationsWhitelist", white_listed_attendee)
|
|
||||||
whitelist = self.prefs.get('Calendar')['PreventInvitationsWhitelist']
|
|
||||||
self.assertEqual(whitelist, white_listed_attendee)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,219 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
from config import hostname, port, username, password, \
|
|
||||||
superuser, superuser_password, \
|
|
||||||
attendee1, attendee1_username, \
|
|
||||||
attendee1_password, \
|
|
||||||
attendee1_delegate, attendee1_delegate_username, \
|
|
||||||
attendee1_delegate_password, \
|
|
||||||
resource_no_overbook, resource_can_overbook, \
|
|
||||||
white_listed_attendee
|
|
||||||
|
|
||||||
import preferences
|
|
||||||
import simplejson
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
import utilities
|
|
||||||
import datetime
|
|
||||||
import dateutil.tz
|
|
||||||
import vobject
|
|
||||||
import vobject.base
|
|
||||||
import vobject.icalendar
|
|
||||||
import webdavlib
|
|
||||||
import StringIO
|
|
||||||
|
|
||||||
|
|
||||||
class preventInvitationsTest(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.prefs = preferences.preferences(attendee1, attendee1_password)
|
|
||||||
self.caldav = CalDAVSchedulingTest(self)
|
|
||||||
cal = self.prefs.get("Calendar")
|
|
||||||
if "PreventInvitationsWhitelist" not in cal:
|
|
||||||
cal["PreventInvitationsWhitelist"] = None
|
|
||||||
self.prefs.set("PreventInvitationsWhitelist", None)
|
|
||||||
if "PreventInvitations" not in cal:
|
|
||||||
cal["PreventInvitations"] = 0
|
|
||||||
self.prefs.set("PreventInvitations", 0)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
#self.prefs.set("autoReplyText", "")
|
|
||||||
self.prefs.set('PreventInvitations', 0)
|
|
||||||
self.prefs.set("PreventInvitationsWhitelist", None)
|
|
||||||
#- Manual Cleanup, not called because classs is not derived from unittest
|
|
||||||
self.caldav.tearDown()
|
|
||||||
|
|
||||||
def not_test_empty_string_instead_of_null(self):
|
|
||||||
self.prefs.set('PreventInvitationsWhitelist', "")
|
|
||||||
|
|
||||||
def testDontPreventInvitation(self):
|
|
||||||
""" Set/get the PreventInvitation pref"""
|
|
||||||
#- First accept the invitation
|
|
||||||
self.prefs.set('PreventInvitations', 0)
|
|
||||||
notset = self.prefs.get_settings('')['Calendar']['PreventInvitations']
|
|
||||||
self.assertEqual(notset, 0)
|
|
||||||
self.caldav.AddAttendee()
|
|
||||||
self.caldav.VerifyEvent()
|
|
||||||
|
|
||||||
def testPreventInvitation(self):
|
|
||||||
""" Set PreventInvitation and don't accept the Invitation"""
|
|
||||||
#- Second, enable PreventInviation and refuse it
|
|
||||||
self.prefs.set('PreventInvitations', 1)
|
|
||||||
isset = self.prefs.get_settings('')['Calendar']['PreventInvitations']
|
|
||||||
self.assertEqual(isset, 1)
|
|
||||||
self.caldav.AddAttendee(409)
|
|
||||||
self.caldav.VerifyEvent(404)
|
|
||||||
|
|
||||||
def testPreventInvitationWhiteList(self):
|
|
||||||
""" Set PreventInvitation add to WhiteList and accept the Invitation"""
|
|
||||||
#- First, add the Organiser to the Attendee's whitelist
|
|
||||||
self.prefs.set('PreventInvitations', 1)
|
|
||||||
self.prefs.set("PreventInvitationsWhitelist", simplejson.dumps(white_listed_attendee))
|
|
||||||
whitelist = self.prefs.get_settings('Calendar')['PreventInvitationsWhitelist']
|
|
||||||
self.assertEqual(whitelist, white_listed_attendee)
|
|
||||||
|
|
||||||
#- Second, try again to invite, it should work
|
|
||||||
self.prefs.set('PreventInvitations', 1)
|
|
||||||
isset = self.prefs.get_settings('')['Calendar']['PreventInvitations']
|
|
||||||
self.assertEqual(isset, 1)
|
|
||||||
self.caldav.AddAttendee()
|
|
||||||
self.caldav.VerifyEvent()
|
|
||||||
|
|
||||||
|
|
||||||
class CalDAVSchedulingTest(object):
|
|
||||||
def __init__(self, parent_self):
|
|
||||||
self.test = parent_self # used for utilities
|
|
||||||
self.setUp()
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.superuser_client = webdavlib.WebDAVClient(hostname, port,
|
|
||||||
superuser, superuser_password)
|
|
||||||
self.client = webdavlib.WebDAVClient(hostname, port,
|
|
||||||
username, password)
|
|
||||||
self.attendee1_client = webdavlib.WebDAVClient(hostname, port,
|
|
||||||
attendee1_username, attendee1_password)
|
|
||||||
self.attendee1_delegate_client = webdavlib.WebDAVClient(hostname, port,
|
|
||||||
attendee1_delegate_username, attendee1_delegate_password)
|
|
||||||
|
|
||||||
utility = utilities.TestUtility(self.test, self.client)
|
|
||||||
(self.user_name, self.user_email) = utility.fetchUserInfo(username)
|
|
||||||
(self.attendee1_name, self.attendee1_email) = utility.fetchUserInfo(attendee1)
|
|
||||||
(self.attendee1_delegate_name, self.attendee1_delegate_email) = utility.fetchUserInfo(attendee1_delegate)
|
|
||||||
|
|
||||||
self.user_calendar = "/SOGo/dav/%s/Calendar/personal/" % username
|
|
||||||
self.attendee1_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1
|
|
||||||
self.attendee1_delegate_calendar = "/SOGo/dav/%s/Calendar/personal/" % attendee1_delegate
|
|
||||||
|
|
||||||
# fetch non existing event to let sogo create the calendars in the db
|
|
||||||
self._getEvent(self.client, "%snonexistent" % self.user_calendar, exp_status=404)
|
|
||||||
self._getEvent(self.attendee1_client, "%snonexistent" % self.attendee1_calendar, exp_status=404)
|
|
||||||
self._getEvent(self.attendee1_delegate_client, "%snonexistent" %
|
|
||||||
self.attendee1_delegate_calendar, exp_status=404)
|
|
||||||
|
|
||||||
# list of ics used by the test.
|
|
||||||
# tearDown will loop over this and wipe them in all users' calendar
|
|
||||||
self.ics_list = []
|
|
||||||
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
# delete all created events from all users' calendar
|
|
||||||
for ics in self.ics_list:
|
|
||||||
self._deleteEvent(self.superuser_client,
|
|
||||||
"%s%s" % (self.user_calendar, ics), None)
|
|
||||||
self._deleteEvent(self.superuser_client,
|
|
||||||
"%s%s" % (self.attendee1_calendar, ics), None)
|
|
||||||
self._deleteEvent(self.superuser_client,
|
|
||||||
"%s%s" % (self.attendee1_delegate_calendar, ics), None)
|
|
||||||
|
|
||||||
def _newEvent(self, summary="test event", uid="test", transp=0):
|
|
||||||
transparency = ("OPAQUE", "TRANSPARENT")
|
|
||||||
|
|
||||||
newCal = vobject.iCalendar()
|
|
||||||
vevent = newCal.add('vevent')
|
|
||||||
vevent.add('summary').value = summary
|
|
||||||
vevent.add('transp').value = transparency[transp]
|
|
||||||
|
|
||||||
now = datetime.datetime.now(dateutil.tz.gettz("America/Montreal"))
|
|
||||||
startdate = vevent.add('dtstart')
|
|
||||||
startdate.value = now
|
|
||||||
enddate = vevent.add('dtend')
|
|
||||||
enddate.value = now + datetime.timedelta(0, 3600)
|
|
||||||
vevent.add('uid').value = uid
|
|
||||||
vevent.add('dtstamp').value = now
|
|
||||||
vevent.add('last-modified').value = now
|
|
||||||
vevent.add('created').value = now
|
|
||||||
vevent.add('class').value = "PUBLIC"
|
|
||||||
vevent.add('sequence').value = "0"
|
|
||||||
|
|
||||||
return newCal
|
|
||||||
|
|
||||||
def _putEvent(self, client, filename, event, exp_status = 201):
|
|
||||||
put = webdavlib.HTTPPUT(filename, event.serialize())
|
|
||||||
put.content_type = "text/calendar; charset=utf-8"
|
|
||||||
client.execute(put)
|
|
||||||
if exp_status is not None:
|
|
||||||
self.test.assertEquals(put.response["status"], exp_status)
|
|
||||||
|
|
||||||
def _getEvent(self, client, filename, exp_status = 200):
|
|
||||||
get = webdavlib.HTTPGET(filename)
|
|
||||||
client.execute(get)
|
|
||||||
|
|
||||||
if exp_status is not None:
|
|
||||||
self.test.assertEquals(get.response["status"], exp_status)
|
|
||||||
|
|
||||||
if get.response["headers"]["content-type"].startswith("text/calendar"):
|
|
||||||
stream = StringIO.StringIO(get.response["body"])
|
|
||||||
event = vobject.base.readComponents(stream).next()
|
|
||||||
else:
|
|
||||||
event = None
|
|
||||||
|
|
||||||
return event
|
|
||||||
|
|
||||||
def _deleteEvent(self, client, filename, exp_status = 204):
|
|
||||||
delete = webdavlib.WebDAVDELETE(filename)
|
|
||||||
client.execute(delete)
|
|
||||||
if exp_status is not None:
|
|
||||||
self.test.assertEquals(delete.response["status"], exp_status)
|
|
||||||
|
|
||||||
def AddAttendee(self, exp_status=204):
|
|
||||||
""" add attendee after event creation """
|
|
||||||
|
|
||||||
# make sure the event doesn't exist
|
|
||||||
ics_name = "test-add-attendee.ics"
|
|
||||||
self.ics_list += [ics_name]
|
|
||||||
|
|
||||||
self._deleteEvent(self.client,
|
|
||||||
"%s%s" % (self.user_calendar,ics_name), None)
|
|
||||||
self._deleteEvent(self.attendee1_client,
|
|
||||||
"%s%s" % (self.attendee1_calendar,ics_name), None)
|
|
||||||
|
|
||||||
# 1. create an event in the organiser's calendar
|
|
||||||
event = self._newEvent(summary="Test add attendee", uid="Test add attendee")
|
|
||||||
organizer = event.vevent.add('organizer')
|
|
||||||
organizer.cn_param = self.user_name
|
|
||||||
organizer.value = self.user_email
|
|
||||||
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event)
|
|
||||||
|
|
||||||
# 2. add an attendee
|
|
||||||
event.add("method").value = "REQUEST"
|
|
||||||
attendee = event.vevent.add('attendee')
|
|
||||||
attendee.cn_param = self.attendee1_name
|
|
||||||
attendee.rsvp_param = "TRUE"
|
|
||||||
attendee.partstat_param = "NEEDS-ACTION"
|
|
||||||
attendee.value = self.attendee1_email
|
|
||||||
self._putEvent(self.client, "%s%s" % (self.user_calendar, ics_name), event,
|
|
||||||
exp_status=exp_status)
|
|
||||||
|
|
||||||
#- save event for VerifyEvent
|
|
||||||
self.event = event
|
|
||||||
self.ics_name = ics_name
|
|
||||||
|
|
||||||
def VerifyEvent(self, exp_status=200):
|
|
||||||
# 1. verify that the attendee has the event
|
|
||||||
attendee_event = self._getEvent(self.attendee1_client, "%s%s" % (self.attendee1_calendar, self.ics_name), exp_status)
|
|
||||||
|
|
||||||
# 2. make sure the received event match the original one
|
|
||||||
if attendee_event:
|
|
||||||
self.test.assertEquals(self.event.vevent.uid, attendee_event.vevent.uid)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,164 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
from config import hostname, port, username, password, sieve_port, sieve_server
|
|
||||||
|
|
||||||
import managesieve
|
|
||||||
import preferences
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
import utilities
|
|
||||||
import webdavlib
|
|
||||||
|
|
||||||
sieve_simple_vacation="""require ["vacation"];\r\nvacation :days %(days)d :addresses ["%(mailaddr)s"] text:\r\n%(vacation_msg)s\r\n.\r\n;\r\n"""
|
|
||||||
sieve_vacation_ignoreLists="""require ["vacation"];\r\nif allof ( not exists ["list-help", "list-unsubscribe", "list-subscribe", "list-owner", "list-post", "list-archive", "list-id", "Mailing-List"], not header :comparator "i;ascii-casemap" :is "Precedence" ["list", "bulk", "junk"], not header :comparator "i;ascii-casemap" :matches "To" "Multiple recipients of*" ) { vacation :days %(days)d :addresses ["%(mailaddr)s"] text:\r\n%(vacation_msg)s\r\n.\r\n;\r\n}\r\n"""
|
|
||||||
sieve_simple_forward="""redirect "%(redirect_mailaddr)s";\r\n"""
|
|
||||||
sieve_forward_keep="""redirect "%(redirect_mailaddr)s";\r\nkeep;\r\n"""
|
|
||||||
sieve_simple_filter="""require ["fileinto"];\r\nif anyof (header :contains "subject" "%(subject)s") {\r\n fileinto "%(folderName)s";\r\n}\r\n"""
|
|
||||||
|
|
||||||
class sieveTest(unittest.TestCase):
|
|
||||||
def _killFilters(self):
|
|
||||||
self.prefs=preferences.preferences()
|
|
||||||
# kill existing filters
|
|
||||||
self.prefs.set_or_create("SOGoSieveFilters", [], ["defaults"])
|
|
||||||
# vacation filters
|
|
||||||
self.prefs.set_or_create("autoReplyText", "", ["defaults", "Vacation"])
|
|
||||||
self.prefs.set_or_create("autoReplyEmailAddresses", [], ["defaults", "Vacation"])
|
|
||||||
self.prefs.set_or_create("daysBetweenResponse", 7, ["defaults", "Vacation"])
|
|
||||||
self.prefs.set_or_create("ignoreLists", 0, ["defaults", "Vacation"])
|
|
||||||
self.prefs.set_or_create("enabled", 0, ["defaults", "Vacation"])
|
|
||||||
# forwarding filters
|
|
||||||
self.prefs.set_or_create("forwardAddress", [], ["defaults", "Forward"])
|
|
||||||
self.prefs.set_or_create("keepCopy", 0, ["defaults", "Forward"])
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
ret = ""
|
|
||||||
|
|
||||||
self.client = webdavlib.WebDAVClient(hostname, port,
|
|
||||||
username, password)
|
|
||||||
utility = utilities.TestUtility(self, self.client)
|
|
||||||
(self.user_name, self.user_email) = utility.fetchUserInfo(username)
|
|
||||||
self.user_email = self.user_email.replace("mailto:", "")
|
|
||||||
|
|
||||||
self.ms = managesieve.MANAGESIEVE(sieve_server, sieve_port)
|
|
||||||
self.assertEqual(self.ms.login("", username, password), "OK",
|
|
||||||
"Couldn't login")
|
|
||||||
|
|
||||||
self._killFilters()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
self._killFilters()
|
|
||||||
|
|
||||||
def _getSogoSieveScript(self):
|
|
||||||
sieveFoundsogo=0
|
|
||||||
createdSieveScript=""
|
|
||||||
(ret, sieveScriptList) = self.ms.listscripts()
|
|
||||||
self.assertEqual(ret, "OK", "Couldn't get sieve script list")
|
|
||||||
|
|
||||||
for (script, isActive) in sieveScriptList:
|
|
||||||
if (script == "sogo"):
|
|
||||||
sieveFoundsogo=1
|
|
||||||
self.assertEqual(isActive, True, "sogo sieve script is not active!")
|
|
||||||
(ret, createdSieveScript) = self.ms.getscript(script)
|
|
||||||
self.assertEqual(ret, "OK", "Couldn't get sogo sieve script")
|
|
||||||
|
|
||||||
self.assertEqual(sieveFoundsogo, 1, "sogo sieve script not found!")
|
|
||||||
|
|
||||||
return createdSieveScript
|
|
||||||
|
|
||||||
def testSieveSimpleVacation(self):
|
|
||||||
""" enable simple vacation script """
|
|
||||||
vacation_msg="vacation test"
|
|
||||||
daysSelect=3
|
|
||||||
|
|
||||||
sieveScript = sieve_simple_vacation % { "mailaddr": self.user_email,
|
|
||||||
"vacation_msg": vacation_msg,
|
|
||||||
"days": preferences.daysBetweenResponseList[daysSelect],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Enabling Vacation now is an 'enabled' setting in the subdict Vacation
|
|
||||||
# We need to get that subdict first -- next save/set will also save this
|
|
||||||
vacation = self.prefs.get("Vacation")
|
|
||||||
vacation['enabled'] = 1
|
|
||||||
|
|
||||||
self.prefs.set_nosave("autoReplyText", vacation_msg)
|
|
||||||
self.prefs.set_nosave("daysBetweenResponse", "%d" % preferences.daysBetweenResponseList[daysSelect])
|
|
||||||
self.prefs.set_nosave("autoReplyEmailAddresses", [self.user_email])
|
|
||||||
self.prefs.save()
|
|
||||||
|
|
||||||
createdSieveScript=self._getSogoSieveScript()
|
|
||||||
|
|
||||||
self.assertEqual(sieveScript, createdSieveScript)
|
|
||||||
|
|
||||||
def testSieveVacationIgnoreLists(self):
|
|
||||||
""" enable vacation script - ignore lists"""
|
|
||||||
vacation_msg="vacation test - ignore list"
|
|
||||||
daysSelect=2
|
|
||||||
|
|
||||||
sieveScript = sieve_vacation_ignoreLists % { "mailaddr": self.user_email,
|
|
||||||
"vacation_msg": vacation_msg,
|
|
||||||
"days": preferences.daysBetweenResponseList[daysSelect],
|
|
||||||
}
|
|
||||||
|
|
||||||
# Enabling Vacation now is an 'enabled' setting in the subdict Vacation
|
|
||||||
# We need to get that subdict first -- next save/set will also save this
|
|
||||||
vacation = self.prefs.get("Vacation")
|
|
||||||
vacation['enabled'] = 1
|
|
||||||
|
|
||||||
self.prefs.set_nosave("autoReplyText", vacation_msg)
|
|
||||||
self.prefs.set_nosave("daysBetweenResponse", "%d" % preferences.daysBetweenResponseList[daysSelect])
|
|
||||||
self.prefs.set_nosave("autoReplyEmailAddresses", [self.user_email])
|
|
||||||
self.prefs.set_nosave("ignoreLists", 1)
|
|
||||||
self.prefs.save()
|
|
||||||
|
|
||||||
createdSieveScript=self._getSogoSieveScript()
|
|
||||||
|
|
||||||
self.assertEqual(sieveScript, createdSieveScript)
|
|
||||||
|
|
||||||
def testSieveSimpleForward(self):
|
|
||||||
""" enable simple forwarding """
|
|
||||||
redirect_mailaddr="nonexistent@inverse.com"
|
|
||||||
|
|
||||||
sieveScript = sieve_simple_forward % { "redirect_mailaddr": redirect_mailaddr }
|
|
||||||
|
|
||||||
# Enabling Forward now is an 'enabled' setting in the subdict Forward
|
|
||||||
# We need to get that subdict first -- next save/set will also save this
|
|
||||||
forward = self.prefs.get("Forward")
|
|
||||||
forward['enabled'] = 1
|
|
||||||
|
|
||||||
self.prefs.set("forwardAddress", [redirect_mailaddr])
|
|
||||||
|
|
||||||
createdSieveScript = self._getSogoSieveScript()
|
|
||||||
self.assertEqual(sieveScript, createdSieveScript)
|
|
||||||
|
|
||||||
def testSieveForwardKeepCopy(self):
|
|
||||||
""" enable email forwarding - keep a copy """
|
|
||||||
redirect_mailaddr="nonexistent@inverse.com"
|
|
||||||
|
|
||||||
sieveScript = sieve_forward_keep % { "redirect_mailaddr": redirect_mailaddr }
|
|
||||||
|
|
||||||
# Enabling Forward now is an 'enabled' setting in the subdict Forward
|
|
||||||
# We need to get that subdict first -- next save/set will also save this
|
|
||||||
forward = self.prefs.get("Forward")
|
|
||||||
forward['enabled'] = 1
|
|
||||||
|
|
||||||
self.prefs.set_nosave("forwardAddress", [redirect_mailaddr])
|
|
||||||
self.prefs.set_nosave("keepCopy", 1)
|
|
||||||
self.prefs.save()
|
|
||||||
|
|
||||||
createdSieveScript = self._getSogoSieveScript()
|
|
||||||
self.assertEqual(sieveScript, createdSieveScript)
|
|
||||||
|
|
||||||
def testSieveSimpleFilter(self):
|
|
||||||
""" add simple sieve filter """
|
|
||||||
folderName="Sent"
|
|
||||||
subject=__name__
|
|
||||||
|
|
||||||
sieveScript=sieve_simple_filter % { "subject": subject, "folderName": folderName }
|
|
||||||
|
|
||||||
self.prefs.set("SOGoSieveFilters", [{"active": True, "actions": [{"method": "fileinto", "argument": "Sent"}], "rules": [{"operator": "contains", "field": "subject", "value": subject}], "match": "any", "name": folderName}])
|
|
||||||
|
|
||||||
createdSieveScript = self._getSogoSieveScript()
|
|
||||||
self.assertEqual(sieveScript, createdSieveScript)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
# XXX this script has to be run as root because it su to sogo_user
|
|
||||||
# in order to use its .GNUstepDefaults prefs
|
|
||||||
# Would be much better to have another way to specify which Defaults to use
|
|
||||||
|
|
||||||
from config import sogo_user, sogo_tool_path
|
|
||||||
|
|
||||||
import os
|
|
||||||
import pwd
|
|
||||||
import shutil
|
|
||||||
import sogotests
|
|
||||||
import tempfile
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
class sogoToolTest(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.backupdir = tempfile.mkdtemp()
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
os.chdir("/")
|
|
||||||
shutil.rmtree(self.backupdir)
|
|
||||||
|
|
||||||
def testBackupAll(self):
|
|
||||||
""" sogo-tool backup ALL """
|
|
||||||
|
|
||||||
(uid, gid) = pwd.getpwnam(sogo_user)[2:4]
|
|
||||||
|
|
||||||
# We need to run as root since there's no way
|
|
||||||
# of using another user's GNUstepDefaults
|
|
||||||
self.assertEqual(os.getuid(), 0, "this test must run as root...")
|
|
||||||
|
|
||||||
os.chown(self.backupdir, uid, gid)
|
|
||||||
cmd = "sudo -u %s bash -c \"(cd %s && %s backup . ALL >/dev/null 2>&1)\"" % (sogo_user, self.backupdir, sogo_tool_path)
|
|
||||||
#print "sogo-tool cmd to execute %s" % cmd
|
|
||||||
status = os.system(cmd)
|
|
||||||
#print "Exit status of os.system(): %d" % status
|
|
||||||
rc = os.WEXITSTATUS(status)
|
|
||||||
#self.assertEqual(rc, 0, "sogo-tool failed RC=%d" % rc)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,73 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
|
|
||||||
from config import hostname, port, username, password, \
|
|
||||||
webCalendarURL
|
|
||||||
|
|
||||||
import simplejson
|
|
||||||
import sogoLogin
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
import utilities
|
|
||||||
import webdavlib
|
|
||||||
import httplib
|
|
||||||
|
|
||||||
|
|
||||||
class UIPostsTests(unittest.TestCase):
|
|
||||||
|
|
||||||
def setUp(self):
|
|
||||||
self.client = webdavlib.WebDAVClient(hostname, port)
|
|
||||||
self.gcClient = webdavlib.WebDAVClient(hostname, port)
|
|
||||||
self.cookie = sogoLogin.getAuthCookie(hostname, port, username, password)
|
|
||||||
|
|
||||||
def _urlPostData(self, client, url, data, exp_status=200):
|
|
||||||
post = webdavlib.HTTPPOST(url, simplejson.dumps(data))
|
|
||||||
post.content_type = "application/json"
|
|
||||||
post.cookie = self.cookie
|
|
||||||
|
|
||||||
client.execute(post)
|
|
||||||
if (exp_status is not None):
|
|
||||||
self.assertEquals(post.response["status"], exp_status)
|
|
||||||
return post.response
|
|
||||||
|
|
||||||
def _urlGet(self, client, url, exp_status=200):
|
|
||||||
get = webdavlib.HTTPGET(url)
|
|
||||||
get.cookie = self.cookie
|
|
||||||
|
|
||||||
client.execute(get)
|
|
||||||
if (exp_status is not None):
|
|
||||||
self.assertEquals(get.response["status"], exp_status)
|
|
||||||
return get.response
|
|
||||||
|
|
||||||
def testAddWebCalendar(self):
|
|
||||||
""" Add Web Calendar """
|
|
||||||
|
|
||||||
ret=True
|
|
||||||
data = {"url":"%s" % webCalendarURL}
|
|
||||||
calendarBaseURL="/SOGo/so/%s/Calendar" % username
|
|
||||||
addWebCalendarURL = "%s/addWebCalendar" % calendarBaseURL
|
|
||||||
response = self._urlPostData(self.client, addWebCalendarURL, data)
|
|
||||||
|
|
||||||
respJSON = simplejson.loads(response['body'])
|
|
||||||
calID = respJSON['id']
|
|
||||||
|
|
||||||
self.assertNotEqual(calID, None)
|
|
||||||
|
|
||||||
# reload the cal
|
|
||||||
calURL = "%s/%s" % (calendarBaseURL, calID)
|
|
||||||
try:
|
|
||||||
response = self._urlGet(self.client, "%s/reload" % calURL, exp_status=None)
|
|
||||||
except httplib.BadStatusLine:
|
|
||||||
# that's bad, the server probably reset the connection. fake a 502
|
|
||||||
response['status'] = 502
|
|
||||||
|
|
||||||
# cleanup our trash
|
|
||||||
self._urlPostData(self.gcClient, "%s/delete" % calURL, "", exp_status=None)
|
|
||||||
|
|
||||||
# delayed assert to allow cal deletion on failure
|
|
||||||
self.assertEqual(response['status'], 200)
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,155 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
from config import hostname, port, username, password, subscriber_username
|
|
||||||
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
import utilities
|
|
||||||
import webdavlib
|
|
||||||
|
|
||||||
class WebDAVTest(unittest.TestCase):
|
|
||||||
def __init__(self, arg):
|
|
||||||
unittest.TestCase.__init__(self, arg)
|
|
||||||
self.client = webdavlib.WebDAVClient(hostname, port,
|
|
||||||
username, password)
|
|
||||||
self.dav_utility = utilities.TestUtility(self, self.client)
|
|
||||||
|
|
||||||
def testPrincipalCollectionSet(self):
|
|
||||||
"""property: 'principal-collection-set' on collection object"""
|
|
||||||
resource = '/SOGo/dav/%s/' % username
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource,
|
|
||||||
["{DAV:}principal-collection-set"],
|
|
||||||
0)
|
|
||||||
self.client.execute(propfind)
|
|
||||||
self.assertEquals(propfind.response["status"], 207)
|
|
||||||
nodes = propfind.response["document"] \
|
|
||||||
.findall('{DAV:}response/{DAV:}propstat/{DAV:}prop/{DAV:}principal-collection-set/{DAV:}href')
|
|
||||||
responseHref = nodes[0].text
|
|
||||||
if responseHref[0:4] == "http":
|
|
||||||
self.assertEquals("http://%s/SOGo/dav/" % hostname, responseHref,
|
|
||||||
"{DAV:}principal-collection-set returned %s instead of 'http../SOGo/dav/'"
|
|
||||||
% ( responseHref, resource ))
|
|
||||||
else:
|
|
||||||
self.assertEquals("/SOGo/dav/", responseHref,
|
|
||||||
"{DAV:}principal-collection-set returned %s instead of '/SOGo/dav/'"
|
|
||||||
% responseHref)
|
|
||||||
|
|
||||||
def testPrincipalCollectionSet2(self):
|
|
||||||
"""property: 'principal-collection-set' on non-collection object"""
|
|
||||||
resource = '/SOGo/dav/%s/freebusy.ifb' % username
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource,
|
|
||||||
["{DAV:}principal-collection-set"],
|
|
||||||
0)
|
|
||||||
self.client.execute(propfind)
|
|
||||||
self.assertEquals(propfind.response["status"], 207)
|
|
||||||
node = propfind.response["document"] \
|
|
||||||
.find('{DAV:}response/{DAV:}propstat/{DAV:}prop/{DAV:}principal-collection-set/{DAV:}href')
|
|
||||||
responseHref = node.text
|
|
||||||
expectedHref = '/SOGo/dav/'
|
|
||||||
if responseHref[0:4] == "http":
|
|
||||||
self.assertEquals("http://%s%s" % (hostname, expectedHref), responseHref,
|
|
||||||
"{DAV:}principal-collection-set returned %s instead of '%s'"
|
|
||||||
% ( responseHref, expectedHref ))
|
|
||||||
else:
|
|
||||||
self.assertEquals(expectedHref, responseHref,
|
|
||||||
"{DAV:}principal-collection-set returned %s instead of '%s'"
|
|
||||||
% ( responseHref, expectedHref ))
|
|
||||||
|
|
||||||
def _testPropfindURL(self, resource):
|
|
||||||
resourceWithSlash = resource[-1] == '/'
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource,
|
|
||||||
["{DAV:}displayname", "{DAV:}resourcetype"],
|
|
||||||
1)
|
|
||||||
self.client.execute(propfind)
|
|
||||||
self.assertEquals(propfind.response["status"], 207)
|
|
||||||
|
|
||||||
nodes = propfind.response["document"].findall('{DAV:}response')
|
|
||||||
for node in nodes:
|
|
||||||
responseHref = node.find('{DAV:}href').text
|
|
||||||
hasSlash = responseHref[-1] == '/'
|
|
||||||
resourcetype = node.find('{DAV:}propstat/{DAV:}prop/{DAV:}resourcetype')
|
|
||||||
isCollection = len(resourcetype.getchildren()) > 0
|
|
||||||
if isCollection:
|
|
||||||
self.assertEquals(hasSlash, resourceWithSlash,
|
|
||||||
"failure with href '%s' while querying '%s'"
|
|
||||||
% (responseHref, resource))
|
|
||||||
else:
|
|
||||||
self.assertEquals(hasSlash, False,
|
|
||||||
"failure with href '%s' while querying '%s'"
|
|
||||||
% (responseHref, resource))
|
|
||||||
|
|
||||||
def testPropfindURL(self):
|
|
||||||
"""propfind: ensure various NSURL work-arounds"""
|
|
||||||
# a collection without /
|
|
||||||
self._testPropfindURL('/SOGo/dav/%s' % username)
|
|
||||||
# a collection with /
|
|
||||||
self._testPropfindURL('/SOGo/dav/%s/' % username)
|
|
||||||
# a non-collection
|
|
||||||
self._testPropfindURL('/SOGo/dav/%s/freebusy.ifb' % username)
|
|
||||||
|
|
||||||
## REPORT
|
|
||||||
def testPrincipalPropertySearch(self):
|
|
||||||
"""principal-property-search"""
|
|
||||||
resource = '/SOGo/dav'
|
|
||||||
userInfo = self.dav_utility.fetchUserInfo(username)
|
|
||||||
# subscriber_userInfo = self.dav_utility.fetchUserInfo(subscriber_username)
|
|
||||||
matches = [["{urn:ietf:params:xml:ns:caldav}calendar-home-set",
|
|
||||||
"/SOGo/dav/%s/Calendar" % username]]
|
|
||||||
## the SOGo implementation does not support more than one
|
|
||||||
## property-search at a time:
|
|
||||||
# ["{urn:ietf:params:xml:ns:caldav}calendar-home-set",
|
|
||||||
# "/SOGo/dav/%s/Calendar" % subscriber_username]]
|
|
||||||
query = webdavlib.WebDAVPrincipalPropertySearch(resource,
|
|
||||||
["displayname"], matches)
|
|
||||||
self.client.execute(query)
|
|
||||||
self.assertEquals(query.response["status"], 207)
|
|
||||||
response = query.response["document"].findall('{DAV:}response')[0]
|
|
||||||
href = response.find('{DAV:}href').text
|
|
||||||
self.assertEquals("/SOGo/dav/%s/" % username, href)
|
|
||||||
displayname = response.find('{DAV:}propstat/{DAV:}prop/{DAV:}displayname')
|
|
||||||
value = displayname.text
|
|
||||||
if value is None:
|
|
||||||
value = ""
|
|
||||||
self.assertEquals(userInfo[0], value)
|
|
||||||
|
|
||||||
# http://tools.ietf.org/html/rfc3253.html#section-3.8
|
|
||||||
def testExpandProperty(self):
|
|
||||||
"""expand-property"""
|
|
||||||
resource = '/SOGo/dav/%s/' % username
|
|
||||||
userInfo = self.dav_utility.fetchUserInfo(username)
|
|
||||||
|
|
||||||
query_props = {"{DAV:}owner": { "{DAV:}href": resource,
|
|
||||||
"{DAV:}displayname": userInfo[0]},
|
|
||||||
"{DAV:}principal-collection-set": { "{DAV:}href": "/SOGo/dav/",
|
|
||||||
"{DAV:}displayname": "SOGo"}}
|
|
||||||
query = webdavlib.WebDAVExpandProperty(resource, query_props.keys(),
|
|
||||||
["displayname"])
|
|
||||||
self.client.execute(query)
|
|
||||||
self.assertEquals(query.response["status"], 207)
|
|
||||||
|
|
||||||
topResponse = query.response["document"].find('{DAV:}response')
|
|
||||||
topHref = topResponse.find('{DAV:}href')
|
|
||||||
self.assertEquals(resource, topHref.text)
|
|
||||||
for query_prop in query_props.keys():
|
|
||||||
propResponse = topResponse.find('{DAV:}propstat/{DAV:}prop/%s'
|
|
||||||
% query_prop)
|
|
||||||
propHref = propResponse.find('{DAV:}response/{DAV:}href')
|
|
||||||
self.assertEquals(query_props[query_prop]["{DAV:}href"],
|
|
||||||
propHref.text,
|
|
||||||
"'%s', href mismatch: exp. '%s', got '%s'"
|
|
||||||
% (query_prop,
|
|
||||||
query_props[query_prop]["{DAV:}href"],
|
|
||||||
propHref.text))
|
|
||||||
propDisplayname = propResponse.find('{DAV:}response/{DAV:}propstat/{DAV:}prop/{DAV:}displayname')
|
|
||||||
displayName = propDisplayname.text
|
|
||||||
if displayName is None:
|
|
||||||
displayName = ""
|
|
||||||
self.assertEquals(query_props[query_prop]["{DAV:}displayname"],
|
|
||||||
displayName,
|
|
||||||
"'%s', displayname mismatch: exp. '%s', got '%s'"
|
|
||||||
% (query_prop,
|
|
||||||
query_props[query_prop]["{DAV:}displayname"],
|
|
||||||
propDisplayname))
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import sogotests
|
|
||||||
import unittest
|
|
||||||
|
|
||||||
from webdavlib import *
|
|
||||||
|
|
||||||
class HTTPUnparsedURLTest(unittest.TestCase):
|
|
||||||
def testURLParse(self):
|
|
||||||
fullURL = "http://username:password@hostname:123/folder/folder/object?param1=value1¶m2=value2"
|
|
||||||
testURL = HTTPUnparsedURL(fullURL)
|
|
||||||
self.assertEquals(testURL.protocol, "http")
|
|
||||||
self.assertEquals(testURL.username, "username")
|
|
||||||
self.assertEquals(testURL.password, "password")
|
|
||||||
self.assertEquals(testURL.hostname, "hostname")
|
|
||||||
self.assertEquals(testURL.port, "123")
|
|
||||||
self.assertEquals(testURL.path, "/folder/folder/object")
|
|
||||||
|
|
||||||
exp_params = { "param1": "value1",
|
|
||||||
"param2": "value2" }
|
|
||||||
self.assertEquals(exp_params, testURL.parameters)
|
|
||||||
|
|
||||||
pathURL = "/folder/folder/simplereference"
|
|
||||||
testURL = HTTPUnparsedURL(pathURL)
|
|
||||||
self.assertEquals(testURL.protocol, None)
|
|
||||||
self.assertEquals(testURL.username, None)
|
|
||||||
self.assertEquals(testURL.password, None)
|
|
||||||
self.assertEquals(testURL.hostname, None)
|
|
||||||
self.assertEquals(testURL.port, None)
|
|
||||||
self.assertEquals(testURL.path, "/folder/folder/simplereference")
|
|
||||||
|
|
||||||
pathURL = "http://user:secret@bla.com/hooray"
|
|
||||||
testURL = HTTPUnparsedURL(pathURL)
|
|
||||||
self.assertEquals(testURL.protocol, "http")
|
|
||||||
self.assertEquals(testURL.username, "user")
|
|
||||||
self.assertEquals(testURL.password, "secret")
|
|
||||||
self.assertEquals(testURL.hostname, "bla.com")
|
|
||||||
self.assertEquals(testURL.port, None)
|
|
||||||
self.assertEquals(testURL.path, "/hooray")
|
|
||||||
|
|
||||||
pathURL = "http://user@bla.com:80/hooray"
|
|
||||||
testURL = HTTPUnparsedURL(pathURL)
|
|
||||||
self.assertEquals(testURL.protocol, "http")
|
|
||||||
self.assertEquals(testURL.username, "user")
|
|
||||||
self.assertEquals(testURL.password, None)
|
|
||||||
self.assertEquals(testURL.hostname, "bla.com")
|
|
||||||
self.assertEquals(testURL.port, "80")
|
|
||||||
self.assertEquals(testURL.path, "/hooray")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,71 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
from config import hostname, port, username, password
|
|
||||||
|
|
||||||
import math
|
|
||||||
import sys
|
|
||||||
import sogotests
|
|
||||||
import time
|
|
||||||
import unittest
|
|
||||||
import webdavlib
|
|
||||||
|
|
||||||
resource = '/SOGo/dav/%s/Calendar/test-webdavsync/' % username
|
|
||||||
|
|
||||||
class WebdavSyncTest(unittest.TestCase):
|
|
||||||
def setUp(self):
|
|
||||||
self.client = webdavlib.WebDAVClient(hostname, port,
|
|
||||||
username, password)
|
|
||||||
|
|
||||||
def tearDown(self):
|
|
||||||
delete = webdavlib.WebDAVDELETE(resource)
|
|
||||||
self.client.execute(delete)
|
|
||||||
|
|
||||||
def test(self):
|
|
||||||
"""webdav sync"""
|
|
||||||
# missing tests:
|
|
||||||
# invalid tokens: negative, non-numeric, > current timestamp
|
|
||||||
# non-empty collections: token validity, status codes for added,
|
|
||||||
# modified and removed elements
|
|
||||||
|
|
||||||
# preparation
|
|
||||||
mkcol = webdavlib.WebDAVMKCOL(resource)
|
|
||||||
self.client.execute(mkcol)
|
|
||||||
self.assertEquals(mkcol.response["status"], 201,
|
|
||||||
"preparation: failure creating collection (code != 201)")
|
|
||||||
|
|
||||||
# test queries:
|
|
||||||
# empty collection:
|
|
||||||
# without a token (query1)
|
|
||||||
# with a token (query2)
|
|
||||||
# (when done, non-empty collection:
|
|
||||||
# without a token (query3)
|
|
||||||
# with a token (query4))
|
|
||||||
|
|
||||||
query1 = webdavlib.WebDAVSyncQuery(resource, None, [ "getetag" ])
|
|
||||||
self.client.execute(query1)
|
|
||||||
self.assertEquals(query1.response["status"], 207,
|
|
||||||
("query1: invalid status code: %d (!= 207)"
|
|
||||||
% query1.response["status"]))
|
|
||||||
token_node = query1.response["document"].find("{DAV:}sync-token")
|
|
||||||
# Implicit "assertion": we expect SOGo to return a token node, with a
|
|
||||||
# non-empty numerical value. Anything else will trigger an exception
|
|
||||||
token = int(token_node.text)
|
|
||||||
|
|
||||||
self.assertTrue(token >= 0)
|
|
||||||
query1EndTime = int(math.ceil(query1.start + query1.duration))
|
|
||||||
self.assertTrue(token <= query1EndTime,
|
|
||||||
"token = %d > query1EndTime = %d" % (token, query1EndTime))
|
|
||||||
|
|
||||||
# we make sure that any token is accepted when the collection is
|
|
||||||
# empty, but that the returned token differs
|
|
||||||
query2 = webdavlib.WebDAVSyncQuery(resource, "1234", [ "getetag" ])
|
|
||||||
self.client.execute(query2)
|
|
||||||
self.assertEquals(query2.response["status"], 207)
|
|
||||||
token_node = query2.response["document"].find("{DAV:}sync-token")
|
|
||||||
self.assertTrue(token_node is not None,
|
|
||||||
"expected 'sync-token' tag")
|
|
||||||
token = int(token_node.text)
|
|
||||||
self.assertTrue(token > 0)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
sogotests.runTests()
|
|
||||||
@@ -1,82 +0,0 @@
|
|||||||
/* teststrings.m - this file is part of SOGO
|
|
||||||
*
|
|
||||||
* Copyright (C) 2010 Inverse inc.
|
|
||||||
*
|
|
||||||
* Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
|
||||||
*
|
|
||||||
* This file is free software; you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation; either version 2, or (at your option)
|
|
||||||
* any later version.
|
|
||||||
*
|
|
||||||
* This file is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program; see the file COPYING. If not, write to
|
|
||||||
* the Free Software Foundation, Inc., 59 Temple Place - Suite 330,
|
|
||||||
* Boston, MA 02111-1307, USA.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <Foundation/NSAutoreleasePool.h>
|
|
||||||
#include <Foundation/NSDictionary.h>
|
|
||||||
#include <Foundation/NSException.h>
|
|
||||||
#include <Foundation/NSString.h>
|
|
||||||
|
|
||||||
#include <SOGo/NSDictionary+Utilities.h>
|
|
||||||
|
|
||||||
static int
|
|
||||||
performTest (char *filename)
|
|
||||||
{
|
|
||||||
NSDictionary *testDict;
|
|
||||||
NSString *nsFilename;
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
nsFilename = [NSString stringWithFormat: @"%s", filename];
|
|
||||||
NS_DURING
|
|
||||||
{
|
|
||||||
testDict = [NSDictionary dictionaryFromStringsFile: nsFilename];
|
|
||||||
if ([testDict count] == 0)
|
|
||||||
{
|
|
||||||
NSLog (@"Bad or empty strings file");
|
|
||||||
rc = 2;
|
|
||||||
testDict = nil;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
rc = 0;
|
|
||||||
}
|
|
||||||
NS_HANDLER
|
|
||||||
{
|
|
||||||
NSLog (@"An exception was caught: %@", localException);
|
|
||||||
rc = 1;
|
|
||||||
testDict = nil;
|
|
||||||
}
|
|
||||||
NS_ENDHANDLER;
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
main (int argc, char *argv[])
|
|
||||||
{
|
|
||||||
NSAutoreleasePool *pool;
|
|
||||||
int rc;
|
|
||||||
|
|
||||||
pool = [NSAutoreleasePool new];
|
|
||||||
|
|
||||||
if (argc == 2)
|
|
||||||
{
|
|
||||||
rc = performTest (argv[1]);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
NSLog (@"Usage: %s file.strings", argv[0]);
|
|
||||||
rc = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
[pool release];
|
|
||||||
|
|
||||||
return rc;
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
|
|
||||||
TOPDIR=../..
|
|
||||||
RC=0
|
|
||||||
|
|
||||||
if [ ! -f teststrings ]
|
|
||||||
then
|
|
||||||
make
|
|
||||||
fi
|
|
||||||
|
|
||||||
for stringfile in ${TOPDIR}/*/*/*.lproj/Localizable.strings
|
|
||||||
do
|
|
||||||
./obj/teststrings "$stringfile" > /dev/null
|
|
||||||
code=$?
|
|
||||||
if test $code -eq 0;
|
|
||||||
then
|
|
||||||
echo "$stringfile: passed";
|
|
||||||
else
|
|
||||||
echo "$stringfile: FAILED (code: $code)";
|
|
||||||
RC=$(($RC+$code))
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
exit $RC
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import StringIO
|
|
||||||
import sys
|
|
||||||
import unittest
|
|
||||||
import vobject
|
|
||||||
import vobject.ics_diff
|
|
||||||
import webdavlib
|
|
||||||
import xml.sax.saxutils
|
|
||||||
|
|
||||||
class ics_compare():
|
|
||||||
|
|
||||||
def __init__(self, event1, event2):
|
|
||||||
self.event1 = event1
|
|
||||||
self.event2 = event2
|
|
||||||
self.diffs = None
|
|
||||||
|
|
||||||
def _vcalendarComponent(self, event):
|
|
||||||
event_component = None
|
|
||||||
for item in vobject.readComponents(event):
|
|
||||||
if item.name == "VCALENDAR":
|
|
||||||
event_component = item
|
|
||||||
return event_component
|
|
||||||
|
|
||||||
def areEqual(self):
|
|
||||||
s_event1 = StringIO.StringIO(self.event1)
|
|
||||||
s_event2 = StringIO.StringIO(self.event2)
|
|
||||||
|
|
||||||
event1_vcalendar = self._vcalendarComponent(s_event1)
|
|
||||||
if event1_vcalendar is None:
|
|
||||||
raise Exception("No VCALENDAR component in event1")
|
|
||||||
|
|
||||||
event2_vcalendar = self._vcalendarComponent(s_event2)
|
|
||||||
if event2_vcalendar is None:
|
|
||||||
raise Exception("No VCALENDAR component in event2")
|
|
||||||
|
|
||||||
self.diffs = vobject.ics_diff.diff(event1_vcalendar, event2_vcalendar)
|
|
||||||
if not self.diffs:
|
|
||||||
return True
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
|
|
||||||
def textDiff(self):
|
|
||||||
saved_stdout = sys.stdout
|
|
||||||
out = StringIO.StringIO()
|
|
||||||
sys.stdout = out
|
|
||||||
try :
|
|
||||||
if self.diffs is not None:
|
|
||||||
for (left, right) in self.diffs:
|
|
||||||
left.prettyPrint()
|
|
||||||
right.prettyPrint()
|
|
||||||
finally:
|
|
||||||
sys.stdout = saved_stdout
|
|
||||||
|
|
||||||
return out.getvalue().strip()
|
|
||||||
|
|
||||||
|
|
||||||
class TestUtility():
|
|
||||||
def __init__(self, test, client, resource = None):
|
|
||||||
self.test = test
|
|
||||||
self.client = client
|
|
||||||
self.userInfo = {}
|
|
||||||
|
|
||||||
def fetchUserInfo(self, login):
|
|
||||||
if not self.userInfo.has_key(login):
|
|
||||||
resource = "/SOGo/dav/%s/" % login
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(resource,
|
|
||||||
["displayname",
|
|
||||||
"{urn:ietf:params:xml:ns:caldav}calendar-user-address-set"],
|
|
||||||
0)
|
|
||||||
self.client.execute(propfind)
|
|
||||||
self.test.assertEquals(propfind.response["status"], 207)
|
|
||||||
common_tree = "{DAV:}response/{DAV:}propstat/{DAV:}prop"
|
|
||||||
name_nodes = propfind.response["document"] \
|
|
||||||
.findall('%s/{DAV:}displayname' % common_tree)
|
|
||||||
email_nodes = propfind.response["document"] \
|
|
||||||
.findall('%s/{urn:ietf:params:xml:ns:caldav}calendar-user-address-set/{DAV:}href'
|
|
||||||
% common_tree)
|
|
||||||
|
|
||||||
if len(name_nodes[0].text) > 0:
|
|
||||||
displayName = name_nodes[0].text
|
|
||||||
else:
|
|
||||||
displayName = ""
|
|
||||||
self.userInfo[login] = (displayName, email_nodes[0].text)
|
|
||||||
|
|
||||||
return self.userInfo[login]
|
|
||||||
|
|
||||||
class TestACLUtility(TestUtility):
|
|
||||||
def __init__(self, test, client, resource):
|
|
||||||
TestUtility.__init__(self, test, client, resource)
|
|
||||||
self.resource = resource
|
|
||||||
|
|
||||||
def _subscriptionOperation(self, subscribers, operation):
|
|
||||||
subscribeQuery = ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
||||||
+ "<%s" % operation
|
|
||||||
+ " xmlns=\"urn:inverse:params:xml:ns:inverse-dav\"")
|
|
||||||
if (subscribers is not None):
|
|
||||||
subscribeQuery = (subscribeQuery
|
|
||||||
+ " users=\"%s\"" % ",".join(subscribers))
|
|
||||||
subscribeQuery = subscribeQuery + "/>"
|
|
||||||
post = webdavlib.HTTPPOST(self.resource, subscribeQuery)
|
|
||||||
post.content_type = "application/xml; charset=\"utf-8\""
|
|
||||||
self.client.execute(post)
|
|
||||||
self.test.assertEquals(post.response["status"], 200,
|
|
||||||
"subscribtion failure to '%s' for '%s' (status: %d)"
|
|
||||||
% (self.resource, "', '".join(subscribers),
|
|
||||||
post.response["status"]))
|
|
||||||
|
|
||||||
def subscribe(self, subscribers=None):
|
|
||||||
self._subscriptionOperation(subscribers, "subscribe")
|
|
||||||
|
|
||||||
def unsubscribe(self, subscribers=None):
|
|
||||||
self._subscriptionOperation(subscribers, "unsubscribe")
|
|
||||||
|
|
||||||
def rightsToSOGoRights(self, rights):
|
|
||||||
self.fail("subclass must implement this method")
|
|
||||||
|
|
||||||
def setupRights(self, username, rights = None):
|
|
||||||
if rights is not None:
|
|
||||||
rights_str = "".join(["<%s/>"
|
|
||||||
% x for x in self.rightsToSOGoRights(rights) ])
|
|
||||||
aclQuery = ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
||||||
+ "<acl-query"
|
|
||||||
+ " xmlns=\"urn:inverse:params:xml:ns:inverse-dav\">"
|
|
||||||
+ "<set-roles user=\"%s\">%s</set-roles>" % (xml.sax.saxutils.escape(username),
|
|
||||||
rights_str)
|
|
||||||
+ "</acl-query>")
|
|
||||||
else:
|
|
||||||
aclQuery = ("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
|
|
||||||
+ "<acl-query"
|
|
||||||
+ " xmlns=\"urn:inverse:params:xml:ns:inverse-dav\">"
|
|
||||||
+ "<remove-user user=\"%s\"/>" % xml.sax.saxutils.escape(username)
|
|
||||||
+ "</acl-query>")
|
|
||||||
|
|
||||||
post = webdavlib.HTTPPOST(self.resource, aclQuery)
|
|
||||||
post.content_type = "application/xml; charset=\"utf-8\""
|
|
||||||
self.client.execute(post)
|
|
||||||
|
|
||||||
if rights is None:
|
|
||||||
err_msg = ("rights modification: failure to remove entry (status: %d)"
|
|
||||||
% post.response["status"])
|
|
||||||
else:
|
|
||||||
err_msg = ("rights modification: failure to set '%s' (status: %d)"
|
|
||||||
% (rights_str, post.response["status"]))
|
|
||||||
self.test.assertEquals(post.response["status"], 204, err_msg)
|
|
||||||
|
|
||||||
# Calendar:
|
|
||||||
# rights:
|
|
||||||
# v: view all
|
|
||||||
# d: view date and time
|
|
||||||
# m: modify
|
|
||||||
# r: respond
|
|
||||||
# short rights notation: { "c": create,
|
|
||||||
# "d": delete,
|
|
||||||
# "pu": public,
|
|
||||||
# "pr": private,
|
|
||||||
# "co": confidential }
|
|
||||||
class TestCalendarACLUtility(TestACLUtility):
|
|
||||||
def rightsToSOGoRights(self, rights):
|
|
||||||
sogoRights = []
|
|
||||||
if rights.has_key("c") and rights["c"]:
|
|
||||||
sogoRights.append("ObjectCreator")
|
|
||||||
if rights.has_key("d") and rights["d"]:
|
|
||||||
sogoRights.append("ObjectEraser")
|
|
||||||
|
|
||||||
classes = { "pu": "Public",
|
|
||||||
"pr": "Private",
|
|
||||||
"co": "Confidential" }
|
|
||||||
rights_table = { "v": "Viewer",
|
|
||||||
"d": "DAndTViewer",
|
|
||||||
"m": "Modifier",
|
|
||||||
"r": "Responder" }
|
|
||||||
for k in classes.keys():
|
|
||||||
if rights.has_key(k):
|
|
||||||
right = rights[k]
|
|
||||||
sogo_right = "%s%s" % (classes[k], rights_table[right])
|
|
||||||
sogoRights.append(sogo_right)
|
|
||||||
|
|
||||||
return sogoRights
|
|
||||||
|
|
||||||
# Addressbook:
|
|
||||||
# short rights notation: { "c": create,
|
|
||||||
# "d": delete,
|
|
||||||
# "e": edit,
|
|
||||||
# "v": view }
|
|
||||||
class TestAddressBookACLUtility(TestACLUtility):
|
|
||||||
def rightsToSOGoRights(self, rights):
|
|
||||||
sogoRightsTable = { "c": "ObjectCreator",
|
|
||||||
"d": "ObjectEraser",
|
|
||||||
"v": "ObjectViewer",
|
|
||||||
"e": "ObjectEditor" }
|
|
||||||
|
|
||||||
sogoRights = []
|
|
||||||
for k in rights.keys():
|
|
||||||
sogoRights.append(sogoRightsTable[k])
|
|
||||||
|
|
||||||
return sogoRights
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,580 +0,0 @@
|
|||||||
# webdavlib.py - A versatile WebDAV Python Library
|
|
||||||
#
|
|
||||||
# Copyright (C) 2009, 2010 Inverse inc.
|
|
||||||
#
|
|
||||||
# Author: Wolfgang Sourdeau <wsourdeau@inverse.ca>
|
|
||||||
#
|
|
||||||
# webdavlib is free software; you can redistribute it and/or modify it under
|
|
||||||
# the terms of the GNU Lesser General Public License as published by the Free
|
|
||||||
# Software Foundation; either version 2, or (at your option) any later
|
|
||||||
# version.
|
|
||||||
#
|
|
||||||
# webdavlib is distributed in the hope that it will be useful, but WITHOUT ANY
|
|
||||||
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
|
||||||
# FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
|
|
||||||
# details.
|
|
||||||
#
|
|
||||||
# You should have received a copy of the GNU Lesser General Public License
|
|
||||||
# along with webdavlib; see the file COPYING. If not, write to the Free
|
|
||||||
# Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
|
|
||||||
# USA.
|
|
||||||
|
|
||||||
import httplib
|
|
||||||
import re
|
|
||||||
import time
|
|
||||||
import xml.dom.expatbuilder
|
|
||||||
import xml.etree.cElementTree
|
|
||||||
import xml.sax.saxutils
|
|
||||||
import sys
|
|
||||||
|
|
||||||
xmlns_dav = "DAV:"
|
|
||||||
xmlns_caldav = "urn:ietf:params:xml:ns:caldav"
|
|
||||||
xmlns_carddav = "urn:ietf:params:xml:ns:carddav"
|
|
||||||
xmlns_inversedav = "urn:inverse:params:xml:ns:inverse-dav"
|
|
||||||
|
|
||||||
url_re = None
|
|
||||||
|
|
||||||
class HTTPUnparsedURL:
|
|
||||||
def __init__(self, url):
|
|
||||||
self._parse(url)
|
|
||||||
|
|
||||||
def _parse(self, url):
|
|
||||||
# ((proto)://((username(:(password)?)@)?hostname(:(port))))(path)?
|
|
||||||
# if url_re is None:
|
|
||||||
url_parts = url.split("?")
|
|
||||||
alpha_match = "[a-zA-Z0-9%\._-]+"
|
|
||||||
num_match = "[0-9]+"
|
|
||||||
pattern = ("((%s)://(((%s)(:(%s))?@)?(%s)(:(%s))?))?(/.*)"
|
|
||||||
% (alpha_match, alpha_match, alpha_match,
|
|
||||||
alpha_match, num_match))
|
|
||||||
url_re = re.compile(pattern)
|
|
||||||
re_match = url_re.match(url_parts[0])
|
|
||||||
if re_match is None:
|
|
||||||
raise Exception, "URL expression could not be parsed: %s" % url
|
|
||||||
|
|
||||||
(trash, self.protocol, trash, trash, self.username, trash,
|
|
||||||
self.password, self.hostname, trash, self.port, self.path) = re_match.groups()
|
|
||||||
|
|
||||||
self.parameters = {}
|
|
||||||
if len(url_parts) > 1:
|
|
||||||
param_elms = url_parts[1].split("&")
|
|
||||||
for param_pair in param_elms:
|
|
||||||
parameter = param_pair.split("=")
|
|
||||||
self.parameters[parameter[0]] = parameter[1]
|
|
||||||
|
|
||||||
class WebDAVClient:
|
|
||||||
user_agent = "Mozilla/5.0"
|
|
||||||
|
|
||||||
def __init__(self, hostname, port, username = None, password = "",
|
|
||||||
forcessl = False):
|
|
||||||
if int(port) == 443 or forcessl:
|
|
||||||
import M2Crypto.httpslib
|
|
||||||
self.conn = M2Crypto.httpslib.HTTPSConnection(hostname, int(port),
|
|
||||||
True)
|
|
||||||
else:
|
|
||||||
self.conn = httplib.HTTPConnection(hostname, port, True)
|
|
||||||
|
|
||||||
if username is None:
|
|
||||||
self.simpleauth_hash = None
|
|
||||||
else:
|
|
||||||
self.simpleauth_hash = (("%s:%s" % (username, password))
|
|
||||||
.encode('base64')[:-1])
|
|
||||||
|
|
||||||
def prepare_headers(self, query, body):
|
|
||||||
headers = { "User-Agent": self.user_agent }
|
|
||||||
if self.simpleauth_hash is not None:
|
|
||||||
headers["authorization"] = "Basic %s" % self.simpleauth_hash
|
|
||||||
if body is not None:
|
|
||||||
headers["content-length"] = len(body)
|
|
||||||
if query.__dict__.has_key("depth") and query.depth is not None:
|
|
||||||
headers["depth"] = query.depth
|
|
||||||
if query.__dict__.has_key("content_type"):
|
|
||||||
headers["content-type"] = query.content_type
|
|
||||||
if not query.__dict__.has_key("accept-language"):
|
|
||||||
headers["accept-language"] = 'en-us,en;q=0.5'
|
|
||||||
|
|
||||||
query_headers = query.prepare_headers()
|
|
||||||
if query_headers is not None:
|
|
||||||
for key in query_headers.keys():
|
|
||||||
headers[key] = query_headers[key]
|
|
||||||
|
|
||||||
return headers
|
|
||||||
|
|
||||||
def execute(self, query):
|
|
||||||
body = query.render()
|
|
||||||
|
|
||||||
query.start = time.time()
|
|
||||||
self.conn.request(query.method, query.url,
|
|
||||||
body, self.prepare_headers(query, body))
|
|
||||||
try:
|
|
||||||
query.set_response(self.conn.getresponse())
|
|
||||||
except httplib.BadStatusLine, e:
|
|
||||||
print e
|
|
||||||
query.set_response(self.conn.getresponse())
|
|
||||||
query.duration = time.time() - query.start
|
|
||||||
|
|
||||||
class HTTPSimpleQuery:
|
|
||||||
method = None
|
|
||||||
|
|
||||||
def __init__(self, url):
|
|
||||||
self.url = url
|
|
||||||
self.response = None
|
|
||||||
self.start = -1
|
|
||||||
self.duration = -1
|
|
||||||
|
|
||||||
def prepare_headers(self):
|
|
||||||
return {}
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
return None
|
|
||||||
|
|
||||||
def set_response(self, http_response):
|
|
||||||
headers = {}
|
|
||||||
for rk, rv in http_response.getheaders():
|
|
||||||
k = rk.lower()
|
|
||||||
headers[k] = rv
|
|
||||||
self.response = { "headers": headers,
|
|
||||||
"status": http_response.status,
|
|
||||||
"version": http_response.version,
|
|
||||||
"body": http_response.read() }
|
|
||||||
|
|
||||||
class HTTPGET(HTTPSimpleQuery):
|
|
||||||
method = "GET"
|
|
||||||
cookie = None
|
|
||||||
|
|
||||||
def prepare_headers (self):
|
|
||||||
headers = HTTPSimpleQuery.prepare_headers(self)
|
|
||||||
if self.cookie:
|
|
||||||
headers["Cookie"] = self.cookie
|
|
||||||
return headers
|
|
||||||
|
|
||||||
|
|
||||||
class HTTPOPTIONS(HTTPSimpleQuery):
|
|
||||||
method = "OPTIONS"
|
|
||||||
|
|
||||||
class HTTPQuery(HTTPSimpleQuery):
|
|
||||||
def __init__(self, url):
|
|
||||||
HTTPSimpleQuery.__init__(self, url)
|
|
||||||
self.content_type = "application/octet-stream"
|
|
||||||
|
|
||||||
class HTTPPUT(HTTPQuery):
|
|
||||||
method = "PUT"
|
|
||||||
|
|
||||||
def __init__(self, url, content,
|
|
||||||
content_type="application/octet-stream",
|
|
||||||
exclusive=False):
|
|
||||||
HTTPQuery.__init__(self, url)
|
|
||||||
self.content = content
|
|
||||||
self.content_type = content_type
|
|
||||||
self.exclusive = exclusive
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
return self.content
|
|
||||||
|
|
||||||
def prepare_headers(self):
|
|
||||||
headers = HTTPQuery.prepare_headers(self)
|
|
||||||
if self.exclusive:
|
|
||||||
headers["if-none-match"] = "*"
|
|
||||||
|
|
||||||
return headers
|
|
||||||
|
|
||||||
class HTTPPOST(HTTPPUT):
|
|
||||||
method = "POST"
|
|
||||||
cookie = None
|
|
||||||
|
|
||||||
def prepare_headers (self):
|
|
||||||
headers = HTTPPUT.prepare_headers(self)
|
|
||||||
if self.cookie:
|
|
||||||
headers["Cookie"] = self.cookie
|
|
||||||
return headers
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class WebDAVQuery(HTTPQuery):
|
|
||||||
method = None
|
|
||||||
|
|
||||||
def __init__(self, url, depth = None):
|
|
||||||
HTTPQuery.__init__(self, url)
|
|
||||||
self.content_type = "application/xml; charset=\"utf-8\""
|
|
||||||
self.depth = depth
|
|
||||||
self.ns_mgr = _WD_XMLNS_MGR()
|
|
||||||
self.top_node = None
|
|
||||||
|
|
||||||
# helper for PROPFIND and REPORT (only)
|
|
||||||
def _initProperties(self, properties):
|
|
||||||
props = _WD_XMLTreeElement("prop")
|
|
||||||
self.top_node.append(props)
|
|
||||||
for prop in properties:
|
|
||||||
prop_tag = self.render_tag(prop)
|
|
||||||
props.append(_WD_XMLTreeElement(prop_tag))
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
if self.top_node is not None:
|
|
||||||
text = ("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n%s"
|
|
||||||
% self.top_node.render(self.ns_mgr.render()))
|
|
||||||
else:
|
|
||||||
text = ""
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
def render_tag(self, tag):
|
|
||||||
cb = tag.find("}")
|
|
||||||
if cb > -1:
|
|
||||||
ns = tag[1:cb]
|
|
||||||
real_tag = tag[cb+1:]
|
|
||||||
new_tag = self.ns_mgr.register(real_tag, ns)
|
|
||||||
else:
|
|
||||||
new_tag = tag
|
|
||||||
|
|
||||||
return new_tag
|
|
||||||
|
|
||||||
def set_response(self, http_response):
|
|
||||||
HTTPQuery.set_response(self, http_response)
|
|
||||||
headers = self.response["headers"]
|
|
||||||
if (headers.has_key("content-type")
|
|
||||||
and headers.has_key("content-length")
|
|
||||||
and (headers["content-type"].startswith("application/xml")
|
|
||||||
or headers["content-type"].startswith("text/xml"))
|
|
||||||
and int(headers["content-length"]) > 0):
|
|
||||||
document = xml.etree.cElementTree.fromstring(self.response["body"])
|
|
||||||
self.response["document"] = document
|
|
||||||
|
|
||||||
class WebDAVMKCOL(WebDAVQuery):
|
|
||||||
method = "MKCOL"
|
|
||||||
|
|
||||||
class WebDAVDELETE(WebDAVQuery):
|
|
||||||
method = "DELETE"
|
|
||||||
|
|
||||||
class WebDAVREPORT(WebDAVQuery):
|
|
||||||
method = "REPORT"
|
|
||||||
|
|
||||||
class WebDAVGET(WebDAVQuery):
|
|
||||||
method = "GET"
|
|
||||||
|
|
||||||
class WebDAVPROPFIND(WebDAVQuery):
|
|
||||||
method = "PROPFIND"
|
|
||||||
|
|
||||||
def __init__(self, url, properties, depth = None):
|
|
||||||
WebDAVQuery.__init__(self, url, depth)
|
|
||||||
self.top_node = _WD_XMLTreeElement("propfind")
|
|
||||||
if properties is not None and len(properties) > 0:
|
|
||||||
self._initProperties(properties)
|
|
||||||
|
|
||||||
class WebDAVPROPPATCH(WebDAVQuery):
|
|
||||||
method = "PROPPATCH"
|
|
||||||
|
|
||||||
# <x0:propertyupdate xmlns:x1="urn:ietf:params:xml:ns:caldav" xmlns:x0="DAV:"><x0:set><x0:prop>
|
|
||||||
|
|
||||||
def __init__(self, url, properties):
|
|
||||||
WebDAVQuery.__init__(self, url, None)
|
|
||||||
self.top_node = _WD_XMLTreeElement("propertyupdate")
|
|
||||||
set_node = _WD_XMLTreeElement("set")
|
|
||||||
self.top_node.append(set_node)
|
|
||||||
prop_node = _WD_XMLTreeElement("prop")
|
|
||||||
set_node.append(prop_node)
|
|
||||||
|
|
||||||
prop_node.appendSubtree(self, properties)
|
|
||||||
|
|
||||||
class WebDAVMOVE(WebDAVQuery):
|
|
||||||
method = "MOVE"
|
|
||||||
destination = None
|
|
||||||
host = None
|
|
||||||
|
|
||||||
def prepare_headers(self):
|
|
||||||
headers = WebDAVQuery.prepare_headers(self)
|
|
||||||
print "DESTINATION", self.destination
|
|
||||||
if self.destination is not None:
|
|
||||||
headers["Destination"] = self.destination
|
|
||||||
if self.host is not None:
|
|
||||||
headers["Host"] = self.host
|
|
||||||
return headers
|
|
||||||
|
|
||||||
class WebDAVPrincipalPropertySearch(WebDAVREPORT):
|
|
||||||
def __init__(self, url, properties, matches):
|
|
||||||
WebDAVQuery.__init__(self, url)
|
|
||||||
ppsearch_tag = self.ns_mgr.register("principal-property-search",
|
|
||||||
xmlns_dav)
|
|
||||||
self.top_node = _WD_XMLTreeElement(ppsearch_tag)
|
|
||||||
self._initMatches(matches)
|
|
||||||
if properties is not None and len(properties) > 0:
|
|
||||||
self._initProperties(properties)
|
|
||||||
|
|
||||||
def _initMatches(self, matches):
|
|
||||||
for match in matches:
|
|
||||||
psearch = _WD_XMLTreeElement("property-search")
|
|
||||||
self.top_node.append(psearch)
|
|
||||||
prop = _WD_XMLTreeElement("prop")
|
|
||||||
psearch.append(prop)
|
|
||||||
match_tag = self.render_tag(match[0])
|
|
||||||
prop.append(_WD_XMLTreeElement(match_tag))
|
|
||||||
match_tag = _WD_XMLTreeElement("match")
|
|
||||||
psearch.append(match_tag)
|
|
||||||
match_tag.appendSubtree(self, match[1])
|
|
||||||
|
|
||||||
class WebDAVSyncQuery(WebDAVREPORT):
|
|
||||||
def __init__(self, url, token, properties):
|
|
||||||
WebDAVQuery.__init__(self, url)
|
|
||||||
self.top_node = _WD_XMLTreeElement("sync-collection")
|
|
||||||
|
|
||||||
sync_token = _WD_XMLTreeElement("sync-token")
|
|
||||||
self.top_node.append(sync_token)
|
|
||||||
if token is not None:
|
|
||||||
sync_token.append(_WD_XMLTreeTextNode(token))
|
|
||||||
|
|
||||||
if properties is not None and len(properties) > 0:
|
|
||||||
self._initProperties(properties)
|
|
||||||
|
|
||||||
class WebDAVExpandProperty(WebDAVREPORT):
|
|
||||||
def _parseTag(self, tag):
|
|
||||||
result = []
|
|
||||||
|
|
||||||
cb = tag.find("}")
|
|
||||||
if cb > -1:
|
|
||||||
result.append(tag[cb+1:])
|
|
||||||
result.append(tag[1:cb])
|
|
||||||
else:
|
|
||||||
result.append(tag)
|
|
||||||
result.append("DAV:")
|
|
||||||
|
|
||||||
return result;
|
|
||||||
|
|
||||||
def _propElement(self, tag):
|
|
||||||
parsedTag = self._parseTag(tag)
|
|
||||||
parameters = { "name": parsedTag[0] }
|
|
||||||
if len(parsedTag) > 1:
|
|
||||||
parameters["namespace"] = parsedTag[1]
|
|
||||||
|
|
||||||
return _WD_XMLTreeElement("property", parameters)
|
|
||||||
|
|
||||||
def __init__(self, url, query_properties, properties):
|
|
||||||
WebDAVQuery.__init__(self, url)
|
|
||||||
self.top_node = _WD_XMLTreeElement("expand-property")
|
|
||||||
|
|
||||||
for query_tag in query_properties:
|
|
||||||
property_query = self._propElement(query_tag)
|
|
||||||
self.top_node.append(property_query)
|
|
||||||
for tag in properties:
|
|
||||||
property = self._propElement(tag)
|
|
||||||
property_query.append(property)
|
|
||||||
|
|
||||||
class CalDAVPOST(WebDAVQuery):
|
|
||||||
method = "POST"
|
|
||||||
|
|
||||||
def __init__(self, url, content,
|
|
||||||
originator = None, recipients = None):
|
|
||||||
WebDAVQuery.__init__(self, url)
|
|
||||||
self.content_type = "text/calendar; charset=utf-8"
|
|
||||||
self.originator = originator
|
|
||||||
self.recipients = recipients
|
|
||||||
self.content = content
|
|
||||||
|
|
||||||
def prepare_headers(self):
|
|
||||||
headers = WebDAVQuery.prepare_headers(self)
|
|
||||||
|
|
||||||
if self.originator is not None:
|
|
||||||
headers["originator"] = self.originator
|
|
||||||
|
|
||||||
if self.recipients is not None:
|
|
||||||
headers["recipient"] = ",".join(self.recipients)
|
|
||||||
|
|
||||||
return headers
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
return self.content
|
|
||||||
|
|
||||||
class CalDAVCalendarMultiget(WebDAVREPORT):
|
|
||||||
def __init__(self, url, properties, hrefs, depth = None):
|
|
||||||
WebDAVQuery.__init__(self, url, depth)
|
|
||||||
multiget_tag = self.ns_mgr.register("calendar-multiget", xmlns_caldav)
|
|
||||||
self.top_node = _WD_XMLTreeElement(multiget_tag)
|
|
||||||
if properties is not None and len(properties) > 0:
|
|
||||||
self._initProperties(properties)
|
|
||||||
|
|
||||||
for href in hrefs:
|
|
||||||
href_node = _WD_XMLTreeElement("href")
|
|
||||||
self.top_node.append(href_node)
|
|
||||||
href_node.append(_WD_XMLTreeTextNode(href))
|
|
||||||
|
|
||||||
class CalDAVCalendarQuery(WebDAVREPORT):
|
|
||||||
def __init__(self, url, properties, component = None, timerange = None):
|
|
||||||
WebDAVQuery.__init__(self, url)
|
|
||||||
multiget_tag = self.ns_mgr.register("calendar-query", xmlns_caldav)
|
|
||||||
self.top_node = _WD_XMLTreeElement(multiget_tag)
|
|
||||||
if properties is not None and len(properties) > 0:
|
|
||||||
self._initProperties(properties)
|
|
||||||
|
|
||||||
if component is not None:
|
|
||||||
filter_tag = self.ns_mgr.register("filter",
|
|
||||||
xmlns_caldav)
|
|
||||||
compfilter_tag = self.ns_mgr.register("comp-filter",
|
|
||||||
xmlns_caldav)
|
|
||||||
filter_node = _WD_XMLTreeElement(filter_tag)
|
|
||||||
cal_filter_node = _WD_XMLTreeElement(compfilter_tag,
|
|
||||||
{ "name": "VCALENDAR" })
|
|
||||||
comp_node = _WD_XMLTreeElement(compfilter_tag,
|
|
||||||
{ "name": component })
|
|
||||||
## TODO
|
|
||||||
# if timerange is not None:
|
|
||||||
cal_filter_node.append(comp_node)
|
|
||||||
filter_node.append(cal_filter_node)
|
|
||||||
self.top_node.append(filter_node)
|
|
||||||
|
|
||||||
class CardDAVAddressBookQuery(WebDAVREPORT):
|
|
||||||
def __init__(self, url, properties, searchProperty = None, searchValue = None):
|
|
||||||
WebDAVQuery.__init__(self, url)
|
|
||||||
query_tag = self.ns_mgr.register("addressbook-query", xmlns_carddav)
|
|
||||||
ns_key = self.ns_mgr.xmlns[xmlns_carddav]
|
|
||||||
self.top_node = _WD_XMLTreeElement(query_tag)
|
|
||||||
if properties is not None and len(properties) > 0:
|
|
||||||
self._initProperties(properties)
|
|
||||||
|
|
||||||
if searchProperty is not None:
|
|
||||||
filter_node = _WD_XMLTreeElement("%s:filter" % ns_key)
|
|
||||||
self.top_node.append(filter_node)
|
|
||||||
propfilter_node = _WD_XMLTreeElement("%s:prop-filter" % ns_key, { "name": searchProperty })
|
|
||||||
filter_node.append(propfilter_node)
|
|
||||||
match_node = _WD_XMLTreeElement("%s:text-match" % ns_key,
|
|
||||||
{ "collation": "i;unicasemap", "match-type": "starts-with" })
|
|
||||||
propfilter_node.append(match_node)
|
|
||||||
match_node.appendSubtree(None, searchValue)
|
|
||||||
|
|
||||||
class MailDAVMailQuery(WebDAVREPORT):
|
|
||||||
def __init__(self, url, properties, filters = None,
|
|
||||||
sort = None, ascending = True):
|
|
||||||
WebDAVQuery.__init__(self, url)
|
|
||||||
mailquery_tag = self.ns_mgr.register("mail-query",
|
|
||||||
xmlns_inversedav)
|
|
||||||
self.top_node = _WD_XMLTreeElement(mailquery_tag)
|
|
||||||
if properties is not None and len(properties) > 0:
|
|
||||||
self._initProperties(properties)
|
|
||||||
|
|
||||||
if filters is not None and len(filters) > 0:
|
|
||||||
self._initFilters(filters)
|
|
||||||
|
|
||||||
if sort is not None and len(sort) > 0:
|
|
||||||
self._initSort(sort, ascending)
|
|
||||||
|
|
||||||
def _initFilters(self, filters):
|
|
||||||
mailfilter_tag = self.ns_mgr.register("mail-filters",
|
|
||||||
xmlns_inversedav)
|
|
||||||
mailfilter_node = _WD_XMLTreeElement(mailfilter_tag)
|
|
||||||
self.top_node.append(mailfilter_node)
|
|
||||||
for filterk in filters.keys():
|
|
||||||
filter_tag = self.ns_mgr.register(filterk,
|
|
||||||
xmlns_inversedav)
|
|
||||||
filter_node = _WD_XMLTreeElement(filter_tag,
|
|
||||||
filters[filterk])
|
|
||||||
mailfilter_node.append(filter_node)
|
|
||||||
|
|
||||||
def _initSort(self, sort, ascending):
|
|
||||||
sort_tag = self.ns_mgr.register("sort", xmlns_inversedav)
|
|
||||||
if ascending:
|
|
||||||
sort_attrs = { "order": "ascending" }
|
|
||||||
else:
|
|
||||||
sort_attrs = { "order": "descending" }
|
|
||||||
sort_node = _WD_XMLTreeElement(sort_tag, sort_attrs)
|
|
||||||
self.top_node.append(sort_node)
|
|
||||||
|
|
||||||
for item in sort:
|
|
||||||
sort_subnode = _WD_XMLTreeElement(self.render_tag(item))
|
|
||||||
sort_node.append(sort_subnode)
|
|
||||||
|
|
||||||
# private classes to handle XML stuff
|
|
||||||
class _WD_XMLNS_MGR:
|
|
||||||
def __init__(self):
|
|
||||||
self.xmlns = {}
|
|
||||||
self.counter = 0
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
text = " xmlns=\"DAV:\""
|
|
||||||
for k in self.xmlns:
|
|
||||||
text = text + " xmlns:%s=\"%s\"" % (self.xmlns[k], k)
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
def create_key(self, namespace):
|
|
||||||
new_nssym = "n%d" % self.counter
|
|
||||||
self.counter = self.counter + 1
|
|
||||||
self.xmlns[namespace] = new_nssym
|
|
||||||
|
|
||||||
return new_nssym
|
|
||||||
|
|
||||||
def register(self, tag, namespace):
|
|
||||||
if namespace != xmlns_dav:
|
|
||||||
if self.xmlns.has_key(namespace):
|
|
||||||
key = self.xmlns[namespace]
|
|
||||||
else:
|
|
||||||
key = self.create_key(namespace)
|
|
||||||
else:
|
|
||||||
key = None
|
|
||||||
|
|
||||||
if key is not None:
|
|
||||||
newTag = "%s:%s" % (key, tag)
|
|
||||||
else:
|
|
||||||
newTag = tag
|
|
||||||
|
|
||||||
return newTag
|
|
||||||
|
|
||||||
class _WD_XMLTreeElement:
|
|
||||||
typeNum = type(0)
|
|
||||||
typeStr = type("")
|
|
||||||
typeUnicode = type(u"")
|
|
||||||
typeList = type([])
|
|
||||||
typeDict = type({})
|
|
||||||
|
|
||||||
def __init__(self, tag, attributes = {}):
|
|
||||||
self.tag = tag
|
|
||||||
self.children = []
|
|
||||||
self.attributes = attributes
|
|
||||||
|
|
||||||
def append(self, child):
|
|
||||||
self.children.append(child)
|
|
||||||
|
|
||||||
def appendSubtree(self, query, subtree):
|
|
||||||
if type(subtree) == self.typeNum:
|
|
||||||
strValue = "%d" % subtree
|
|
||||||
textNode = _WD_XMLTreeTextNode(strValue)
|
|
||||||
self.append(textNode)
|
|
||||||
elif type(subtree) == self.typeUnicode:
|
|
||||||
textNode = _WD_XMLTreeTextNode(subtree.encode("utf-8"))
|
|
||||||
self.append(textNode)
|
|
||||||
elif type(subtree) == self.typeStr:
|
|
||||||
textNode = _WD_XMLTreeTextNode(subtree)
|
|
||||||
self.append(textNode)
|
|
||||||
elif type(subtree) == self.typeList:
|
|
||||||
for x in subtree:
|
|
||||||
self.appendSubtree(query, x)
|
|
||||||
elif type(subtree) == self.typeDict:
|
|
||||||
for x in subtree.keys():
|
|
||||||
tag = query.render_tag(x)
|
|
||||||
node = _WD_XMLTreeElement(tag)
|
|
||||||
node.appendSubtree(query, subtree[x])
|
|
||||||
self.append(node)
|
|
||||||
else:
|
|
||||||
None
|
|
||||||
|
|
||||||
def render(self, ns_text = None):
|
|
||||||
text = "<" + self.tag
|
|
||||||
|
|
||||||
if ns_text is not None:
|
|
||||||
text = text + ns_text
|
|
||||||
|
|
||||||
for k in self.attributes:
|
|
||||||
text = text + " %s=\"%s\"" % (k, self.attributes[k])
|
|
||||||
|
|
||||||
if len(self.children) > 0:
|
|
||||||
text = text + ">"
|
|
||||||
for child in self.children:
|
|
||||||
text = text + child.render()
|
|
||||||
text = text + "</" + self.tag + ">"
|
|
||||||
else:
|
|
||||||
text = text + "/>"
|
|
||||||
|
|
||||||
return text
|
|
||||||
|
|
||||||
class _WD_XMLTreeTextNode:
|
|
||||||
def __init__(self, text):
|
|
||||||
self.text = xml.sax.saxutils.escape(text)
|
|
||||||
|
|
||||||
def render(self):
|
|
||||||
return self.text
|
|
||||||
@@ -1,106 +0,0 @@
|
|||||||
#!/usr/bin/python
|
|
||||||
|
|
||||||
import getopt
|
|
||||||
import sys
|
|
||||||
import urlparse
|
|
||||||
import webdavlib
|
|
||||||
import xml.dom.minidom
|
|
||||||
|
|
||||||
def usage() :
|
|
||||||
msg ="""Usage:
|
|
||||||
%s [-h] [-s sync-token] -u uri\n""" % sys.argv[0]
|
|
||||||
|
|
||||||
sys.stderr.write(msg);
|
|
||||||
|
|
||||||
def getAllCollections(client, uri):
|
|
||||||
collections = []
|
|
||||||
depth = 1
|
|
||||||
|
|
||||||
propfind = webdavlib.WebDAVPROPFIND(uri, ["allprop"], depth)
|
|
||||||
client.execute(propfind)
|
|
||||||
client.conn.close()
|
|
||||||
doc = propfind.response["document"]
|
|
||||||
for response in doc.iter("{DAV:}response"):
|
|
||||||
propstat = response.find("{DAV:}propstat")
|
|
||||||
if propstat is not None:
|
|
||||||
prop = propstat.find("{DAV:}prop")
|
|
||||||
if prop is not None:
|
|
||||||
resourcetype = prop.find("{DAV:}resourcetype")
|
|
||||||
if resourcetype.find("{DAV:}collection") is not None:
|
|
||||||
href = prop.find("{DAV:}href")
|
|
||||||
if href is not None and href.text != uri:
|
|
||||||
collections.append(href.text)
|
|
||||||
return collections
|
|
||||||
|
|
||||||
def changedItemsFromCollection(client, collection, synctoken=None):
|
|
||||||
# get all changed hrefs since synctoken
|
|
||||||
hrefs = []
|
|
||||||
syncquery = webdavlib.WebDAVSyncQuery(collection, synctoken, [ "getcontenttype", "getetag" ])
|
|
||||||
client.execute(syncquery)
|
|
||||||
client.conn.close()
|
|
||||||
if (syncquery.response["status"] != 207):
|
|
||||||
raise Exception("Bad http response code: %d" % syncquery.response["status"])
|
|
||||||
doc = syncquery.response["document"]
|
|
||||||
|
|
||||||
# extract all hrefs
|
|
||||||
for syncResponse in doc.iter("{DAV:}response"):
|
|
||||||
href = syncResponse.find("{DAV:}href")
|
|
||||||
if href is not None:
|
|
||||||
hrefs.append(href.text)
|
|
||||||
|
|
||||||
return hrefs
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
depth = 1
|
|
||||||
synctoken = "1"
|
|
||||||
url = None
|
|
||||||
|
|
||||||
try:
|
|
||||||
opts, args = getopt.getopt (sys.argv[1:], "hs:u:", \
|
|
||||||
("sync-token=", "url="));
|
|
||||||
except getopt.GetoptError:
|
|
||||||
usage()
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
for o, v in opts :
|
|
||||||
if o == "-h" :
|
|
||||||
usage()
|
|
||||||
exit(1)
|
|
||||||
elif o == "-s" or o == "--sync-token" :
|
|
||||||
synctoken = v
|
|
||||||
elif o == "-u" or o == "--url" :
|
|
||||||
url = v
|
|
||||||
|
|
||||||
if url is None:
|
|
||||||
usage()
|
|
||||||
exit(1)
|
|
||||||
|
|
||||||
o = urlparse.urlparse(url)
|
|
||||||
hostname = o.hostname
|
|
||||||
port = o.port
|
|
||||||
username = o.username
|
|
||||||
password = o.password
|
|
||||||
uri = o.path
|
|
||||||
|
|
||||||
client = webdavlib.WebDAVClient(hostname, port, username, password)
|
|
||||||
|
|
||||||
collections = getAllCollections(client, uri)
|
|
||||||
if len(collections) == 0:
|
|
||||||
print "No collections found!"
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
for collection in collections:
|
|
||||||
changedItems = changedItemsFromCollection(client, collection)
|
|
||||||
# fetch the href data
|
|
||||||
if len(changedItems) > 0:
|
|
||||||
multiget = webdavlib.CalDAVCalendarMultiget(collection,
|
|
||||||
["getetag", "{%s}calendar-data" % webdavlib.xmlns_caldav],
|
|
||||||
changedItems, depth)
|
|
||||||
client.execute(multiget)
|
|
||||||
client.conn.close()
|
|
||||||
if (multiget.response["status"] != 207):
|
|
||||||
raise Exception("Bad http response code: %d" % multiget.response["status"])
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
This directory holds automated tests for SOGo.
|
|
||||||
|
|
||||||
We currrently have:
|
|
||||||
|
|
||||||
- Intregation holds all interated tests that are used to
|
|
||||||
validate overall DAV functionality right now
|
|
||||||
|
|
||||||
- Unit holds all unit tests
|
|
||||||
14
Tests/README.md
Normal file
14
Tests/README.md
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
# Tests
|
||||||
|
|
||||||
|
This directory holds automated tests for SOGo.
|
||||||
|
|
||||||
|
- `spec` and `lib`: hold JavaScript driven interated tests that are used to validate overall DAV functionality
|
||||||
|
- `Unit`: holds all unit tests
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
* [Jasmin](https://jasmine.github.io/) - testing framework
|
||||||
|
* [tsdav](https://tsdav.vercel.app/) - webdav request helper
|
||||||
|
* [ical.js](https://github.com/mozilla-comm/ical.js) - ics and vcard parser
|
||||||
|
* [cross-fetch](https://github.com/lquixada/cross-fetch) - fetch API
|
||||||
|
* [xml-js](https://github.com/nashwaan/xml-js) - convert JS object to XML
|
||||||
@@ -74,9 +74,6 @@ clean:
|
|||||||
dh_testdir
|
dh_testdir
|
||||||
dh_testroot
|
dh_testroot
|
||||||
rm -f build-arch-stamp
|
rm -f build-arch-stamp
|
||||||
( cd Tests/Integration; make clean )
|
|
||||||
rm -f Tests/Integration/config.py
|
|
||||||
-find Tests -name "*.pyc" -exec rm -f {} \;
|
|
||||||
if [ -f config.make ]; \
|
if [ -f config.make ]; \
|
||||||
then \
|
then \
|
||||||
$(MAKE) clean; \
|
$(MAKE) clean; \
|
||||||
|
|||||||
Reference in New Issue
Block a user