[CalendarServer-changes] [14120] CalendarServer/trunk/txweb2
source_changes at macosforge.org
source_changes at macosforge.org
Wed Oct 29 09:28:01 PDT 2014
Revision: 14120
http://trac.calendarserver.org//changeset/14120
Author: cdaboo at apple.com
Date: 2014-10-29 09:28:01 -0700 (Wed, 29 Oct 2014)
Log Message:
-----------
Whitespace.
Modified Paths:
--------------
CalendarServer/trunk/txweb2/auth/digest.py
CalendarServer/trunk/txweb2/auth/interfaces.py
CalendarServer/trunk/txweb2/auth/wrapper.py
CalendarServer/trunk/txweb2/channel/http.py
CalendarServer/trunk/txweb2/client/http.py
CalendarServer/trunk/txweb2/client/interfaces.py
CalendarServer/trunk/txweb2/dav/auth.py
CalendarServer/trunk/txweb2/dav/fileop.py
CalendarServer/trunk/txweb2/dav/http.py
CalendarServer/trunk/txweb2/dav/method/__init__.py
CalendarServer/trunk/txweb2/dav/method/acl.py
CalendarServer/trunk/txweb2/dav/method/copymove.py
CalendarServer/trunk/txweb2/dav/method/delete.py
CalendarServer/trunk/txweb2/dav/method/delete_common.py
CalendarServer/trunk/txweb2/dav/method/get.py
CalendarServer/trunk/txweb2/dav/method/lock.py
CalendarServer/trunk/txweb2/dav/method/mkcol.py
CalendarServer/trunk/txweb2/dav/method/prop_common.py
CalendarServer/trunk/txweb2/dav/method/put.py
CalendarServer/trunk/txweb2/dav/method/put_common.py
CalendarServer/trunk/txweb2/dav/method/report.py
CalendarServer/trunk/txweb2/dav/method/report_acl_principal_prop_set.py
CalendarServer/trunk/txweb2/dav/method/report_principal_match.py
CalendarServer/trunk/txweb2/dav/method/report_principal_property_search.py
CalendarServer/trunk/txweb2/dav/method/report_principal_search_property_set.py
CalendarServer/trunk/txweb2/dav/noneprops.py
CalendarServer/trunk/txweb2/dav/resource.py
CalendarServer/trunk/txweb2/dav/static.py
CalendarServer/trunk/txweb2/dav/test/__init__.py
CalendarServer/trunk/txweb2/dav/test/test_acl.py
CalendarServer/trunk/txweb2/dav/test/test_auth.py
CalendarServer/trunk/txweb2/dav/test/test_copy.py
CalendarServer/trunk/txweb2/dav/test/test_delete.py
CalendarServer/trunk/txweb2/dav/test/test_http.py
CalendarServer/trunk/txweb2/dav/test/test_lock.py
CalendarServer/trunk/txweb2/dav/test/test_mkcol.py
CalendarServer/trunk/txweb2/dav/test/test_move.py
CalendarServer/trunk/txweb2/dav/test/test_options.py
CalendarServer/trunk/txweb2/dav/test/test_prop.py
CalendarServer/trunk/txweb2/dav/test/test_put.py
CalendarServer/trunk/txweb2/dav/test/test_quota.py
CalendarServer/trunk/txweb2/dav/test/test_report.py
CalendarServer/trunk/txweb2/dav/test/test_report_expand.py
CalendarServer/trunk/txweb2/dav/test/test_resource.py
CalendarServer/trunk/txweb2/dav/test/test_static.py
CalendarServer/trunk/txweb2/dav/test/test_xattrprops.py
CalendarServer/trunk/txweb2/dav/test/tworequest_client.py
CalendarServer/trunk/txweb2/dav/xattrprops.py
CalendarServer/trunk/txweb2/error.py
CalendarServer/trunk/txweb2/fileupload.py
CalendarServer/trunk/txweb2/filter/gzip.py
CalendarServer/trunk/txweb2/filter/location.py
CalendarServer/trunk/txweb2/filter/range.py
CalendarServer/trunk/txweb2/http.py
CalendarServer/trunk/txweb2/http_headers.py
CalendarServer/trunk/txweb2/iweb.py
CalendarServer/trunk/txweb2/log.py
CalendarServer/trunk/txweb2/resource.py
CalendarServer/trunk/txweb2/responsecode.py
CalendarServer/trunk/txweb2/server.py
CalendarServer/trunk/txweb2/static.py
CalendarServer/trunk/txweb2/stream.py
CalendarServer/trunk/txweb2/test/__init__.py
CalendarServer/trunk/txweb2/test/simple_client.py
CalendarServer/trunk/txweb2/test/test_client.py
CalendarServer/trunk/txweb2/test/test_fileupload.py
CalendarServer/trunk/txweb2/test/test_http.py
CalendarServer/trunk/txweb2/test/test_http_headers.py
CalendarServer/trunk/txweb2/test/test_httpauth.py
CalendarServer/trunk/txweb2/test/test_log.py
CalendarServer/trunk/txweb2/test/test_resource.py
CalendarServer/trunk/txweb2/test/test_server.py
CalendarServer/trunk/txweb2/test/test_static.py
CalendarServer/trunk/txweb2/test/test_stream.py
Modified: CalendarServer/trunk/txweb2/auth/digest.py
===================================================================
--- CalendarServer/trunk/txweb2/auth/digest.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/auth/digest.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -81,6 +81,8 @@
return _origCalcHA1(pszAlg, pszUserName, pszRealm, pszPassword, pszNonce,
pszCNonce, preHA1)
+
+
# DigestCalcResponse
def calcResponse(
HA1,
Modified: CalendarServer/trunk/txweb2/auth/interfaces.py
===================================================================
--- CalendarServer/trunk/txweb2/auth/interfaces.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/auth/interfaces.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -60,6 +60,7 @@
"""
+
class IAuthenticatedRequest(Interface):
"""
A request that has been authenticated with the use of Cred,
@@ -73,6 +74,7 @@
"the application's realm")
+
class IHTTPUser(Interface):
"""
A generic interface that can implemented by an avatar to provide
Modified: CalendarServer/trunk/txweb2/auth/wrapper.py
===================================================================
--- CalendarServer/trunk/txweb2/auth/wrapper.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/auth/wrapper.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -45,6 +45,7 @@
responsecode.UNAUTHORIZED,
"You are not authorized to access this resource.")
+
def _generateHeaders(self, factories, remoteAddr=None):
"""
Set up the response's headers.
@@ -76,7 +77,7 @@
"""
response = UnauthorizedResponse()
d = response._generateHeaders(factories, remoteAddr)
- d.addCallback(lambda _:response)
+ d.addCallback(lambda _: response)
return d
@@ -115,6 +116,7 @@
self.portal = portal
self.interfaces = interfaces
+
def _loginSucceeded(self, avatar, request):
"""
Callback for successful login.
@@ -239,6 +241,7 @@
"""
return self.authenticate(request), seg
+
def renderHTTP(self, request):
"""
Authenticate the request then return the result of calling renderHTTP
Modified: CalendarServer/trunk/txweb2/channel/http.py
===================================================================
--- CalendarServer/trunk/txweb2/channel/http.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/channel/http.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -53,6 +53,7 @@
self.retryAfter = retryAfter
self.outstandingRequests = outstandingRequests
+
def connectionMade(self):
log.info(overloaded=self)
@@ -76,6 +77,7 @@
self.transport.loseConnection()
+
class SSLRedirectRequest(Request):
"""
An L{SSLRedirectRequest} prevents processing if the request is over plain
@@ -103,7 +105,7 @@
# >%
-PERSIST_NO_PIPELINE, PERSIST_PIPELINE = (1,2)
+PERSIST_NO_PIPELINE, PERSIST_PIPELINE = (1, 2)
_cachedHostNames = {}
def _cachedGetHostByAddr(hostaddr):
@@ -113,9 +115,11 @@
hostname = socket.gethostbyaddr(hostaddr)[0]
except socket.herror:
hostname = hostaddr
- _cachedHostNames[hostaddr]=hostname
+ _cachedHostNames[hostaddr] = hostname
return hostname
+
+
class StringTransport(object):
"""
I am a StringIO wrapper that conforms for the transport API. I support
@@ -123,15 +127,22 @@
"""
def __init__(self):
self.s = StringIO()
+
+
def writeSequence(self, seq):
self.s.write(''.join(seq))
+
+
def __getattr__(self, attr):
return getattr(self.__dict__['s'], attr)
+
+
class AbortedException(Exception):
pass
+
class HTTPParser(object):
"""This class handles the parsing side of HTTP processing. With a suitable
subclass, it can parse either the client side or the server side of the
@@ -179,6 +190,7 @@
self.inHeaders = http_headers.Headers()
self.channel = channel
+
def lineReceived(self, line):
if self.chunkedIn:
# Parsing a chunked input
@@ -246,6 +258,7 @@
self.headerReceived(self.partialHeader)
self.partialHeader = line
+
def rawDataReceived(self, data):
"""Handle incoming content."""
datalen = len(data)
@@ -294,6 +307,7 @@
self.setConnectionParams(connHeaders)
self.connHeaders = connHeaders
+
def allContentReceived(self):
self.finishedReading = True
self.channel.requestReadFinished(self)
@@ -342,7 +356,7 @@
connHeaders = http_headers.Headers()
move('connection')
- if self.version < (1,1):
+ if self.version < (1, 1):
# Remove all headers mentioned in Connection, because a HTTP 1.0
# proxy might have erroneously forwarded it from a 1.1 client.
for name in connHeaders.getHeader('connection', ()):
@@ -366,9 +380,10 @@
return connHeaders
+
def setConnectionParams(self, connHeaders):
# Figure out persistent connection stuff
- if self.version >= (1,1):
+ if self.version >= (1, 1):
if 'close' in connHeaders.getHeader('connection', ()):
readPersistent = False
else:
@@ -424,6 +439,7 @@
# Set the calculated persistence
self.channel.setReadPersistent(readPersistent)
+
def abortParse(self):
# If we're erroring out while still reading the request
if not self.finishedReading:
@@ -431,19 +447,24 @@
self.channel.setReadPersistent(False)
self.channel.requestReadFinished(self)
+
# producer interface
def pauseProducing(self):
if not self.finishedReading:
self.channel.pauseProducing()
+
def resumeProducing(self):
if not self.finishedReading:
self.channel.resumeProducing()
+
def stopProducing(self):
if not self.finishedReading:
self.channel.stopProducing()
+
+
class HTTPChannelRequest(HTTPParser):
"""This class handles the state and parsing for one HTTP request.
It is responsible for all the low-level connection oriented behavior.
@@ -458,7 +479,7 @@
def __init__(self, channel, queued=0):
HTTPParser.__init__(self, channel)
- self.queued=queued
+ self.queued = queued
# Buffer writes to a string until we're first in line
# to write a response
@@ -468,7 +489,7 @@
self.transport = self.channel.transport
# set the version to a fallback for error generation
- self.version = (1,0)
+ self.version = (1, 0)
def gotInitialLine(self, initialLine):
@@ -501,36 +522,44 @@
# simulate end of headers, as HTTP 0 doesn't have headers.
self.lineReceived('')
+
def lineLengthExceeded(self, line, wasFirst=False):
code = wasFirst and responsecode.REQUEST_URI_TOO_LONG or responsecode.BAD_REQUEST
self._abortWithError(code, 'Header line too long.')
+
def createRequest(self):
self.request = self.channel.requestFactory(self, self.command, self.path, self.version, self.length, self.inHeaders)
del self.inHeaders
+
def processRequest(self):
self.request.process()
+
def handleContentChunk(self, data):
self.request.handleContentChunk(data)
+
def handleContentComplete(self):
self.request.handleContentComplete()
-############## HTTPChannelRequest *RESPONSE* methods #############
+ # HTTPChannelRequest *RESPONSE* methods #
producer = None
chunkedOut = False
finished = False
- ##### Request Callbacks #####
+
+ # Request Callbacks #
def writeIntermediateResponse(self, code, headers=None):
- if self.version >= (1,1):
+ if self.version >= (1, 1):
self._writeHeaders(code, headers, False)
+
def writeHeaders(self, code, headers):
self._writeHeaders(code, headers, True)
+
def _writeHeaders(self, code, headers, addConnectionHeaders):
# HTTP 0.9 doesn't have headers.
if self.version[0] == 0:
@@ -549,9 +578,11 @@
if addConnectionHeaders:
# if we don't have a content length, we send data in
# chunked mode, so that we can support persistent connections.
- if (headers.getHeader('content-length') is None and
- self.command != "HEAD" and code not in http.NO_BODY_CODES):
- if self.version >= (1,1):
+ if (
+ headers.getHeader('content-length') is None and
+ self.command != "HEAD" and code not in http.NO_BODY_CODES
+ ):
+ if self.version >= (1, 1):
l.append("%s: %s\r\n" % ('Transfer-Encoding', 'chunked'))
self.chunkedOut = True
else:
@@ -560,7 +591,7 @@
if self.channel.isLastRequest(self):
l.append("%s: %s\r\n" % ('Connection', 'close'))
- elif self.version < (1,1):
+ elif self.version < (1, 1):
l.append("%s: %s\r\n" % ('Connection', 'Keep-Alive'))
l.append("\r\n")
@@ -575,6 +606,7 @@
else:
self.transport.write(data)
+
def finish(self):
"""We are finished writing data."""
if self.finished:
@@ -612,18 +644,21 @@
else:
self._cleanup()
+
def getHostInfo(self):
return self.channel._host, self.channel._secure
+
def getRemoteHost(self):
return self.channel.transport.getPeer()
- ##### End Request Callbacks #####
+ # End Request Callbacks #
+
def _abortWithError(self, errorcode, text=''):
"""Handle low level protocol errors."""
headers = http_headers.Headers()
- headers.setHeader('content-length', len(text)+1)
+ headers.setHeader('content-length', len(text) + 1)
self.abortConnection(closeWrite=False)
self.writeHeaders(errorcode, headers)
@@ -633,6 +668,7 @@
log.warn("Aborted request (%d) %s" % (errorcode, text))
raise AbortedException
+
def _cleanup(self):
"""Called when have finished responding and are no longer queued."""
if self.producer:
@@ -641,6 +677,7 @@
self.channel.requestWriteFinished(self)
del self.transport
+
# methods for channel - end users should not use these
def noLongerQueued(self):
@@ -685,12 +722,14 @@
else:
self.transport.registerProducer(producer, streaming)
+
def unregisterProducer(self):
"""Unregister the producer."""
if not self.queued:
self.transport.unregisterProducer()
self.producer = None
+
def connectionLost(self, reason):
"""connection was lost"""
if self.queued and self.producer:
@@ -699,6 +738,8 @@
if self.request:
self.request.connectionLost(reason)
+
+
class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin, object):
"""A receiver for HTTP requests. Handles splitting up the connection
for the multiple HTTPChannelRequests that may be in progress on this
@@ -717,7 +758,7 @@
implements(interfaces.IHalfCloseableProtocol)
- ## Configuration parameters. Set in instances or subclasses.
+ # Configuration parameters. Set in instances or subclasses.
# How many simultaneous requests to handle.
maxPipeline = 4
@@ -754,10 +795,12 @@
def _callLater(self, secs, fun):
reactor.callLater(secs, fun)
+
def __init__(self):
# the request queue
self.requests = []
+
def connectionMade(self):
self._secure = interfaces.ISSLTransport(self.transport, None) is not None
address = self.transport.getHost()
@@ -765,6 +808,7 @@
self.setTimeout(self.inputTimeOut)
self.factory.addConnectedChannel(self)
+
def lineReceived(self, line):
if self._first_line:
self.setTimeout(self.inputTimeOut)
@@ -798,6 +842,7 @@
except AbortedException:
pass
+
def lineLengthExceeded(self, line):
if self._first_line:
# Fabricate a request object to respond to the line length violation.
@@ -810,6 +855,7 @@
except AbortedException:
pass
+
def rawDataReceived(self, data):
self.setTimeout(self.inputTimeOut)
try:
@@ -817,6 +863,7 @@
except AbortedException:
pass
+
def requestReadFinished(self, request):
if(self.readPersistent is PERSIST_NO_PIPELINE or
len(self.requests) >= self.maxPipeline):
@@ -832,6 +879,7 @@
if len(self.requests) > 0:
self.setTimeout(self.idleTimeOut)
+
def _startNextRequest(self):
# notify next request, if present, it can start writing
del self.requests[0]
@@ -854,11 +902,13 @@
self.setTimeout(self.betweenRequestsTimeOut)
self.resumeProducing()
+
def setReadPersistent(self, persistent):
if self.readPersistent:
# only allow it to be set if it's not currently False
self.readPersistent = persistent
+
def dropQueuedRequests(self):
"""Called when a response is written that forces a connection close."""
self.readPersistent = False
@@ -867,13 +917,16 @@
request.connectionLost(None)
del self.requests[1:]
+
def isLastRequest(self, request):
# Is this channel handling the last possible request
return not self.readPersistent and self.requests[-1] == request
+
def requestWriteFinished(self, request):
"""Called by first request in queue when it is done."""
- if request != self.requests[0]: raise TypeError
+ if request != self.requests[0]:
+ raise TypeError
# Don't del because we haven't finished cleanup, so,
# don't want queue len to be 0 yet.
@@ -888,20 +941,23 @@
# Set an abort timer in case an orderly close hangs
self.setTimeout(None)
self._abortTimer = reactor.callLater(self.closeTimeOut, self._abortTimeout)
- #reactor.callLater(0.1, self.transport.loseConnection)
+ # eactor.callLater(0.1, self.transport.loseConnection)
self.transport.loseConnection()
+
def timeoutConnection(self):
- #log.info("Timing out client: %s" % str(self.transport.getPeer()))
+ # log.info("Timing out client: %s" % str(self.transport.getPeer()))
# Set an abort timer in case an orderly close hangs
self._abortTimer = reactor.callLater(self.closeTimeOut, self._abortTimeout)
policies.TimeoutMixin.timeoutConnection(self)
+
def _abortTimeout(self):
log.error("Connection aborted - took too long to close: {c}", c=str(self.transport.getPeer()))
self._abortTimer = None
self.transport.abortConnection()
+
def readConnectionLost(self):
"""Read connection lost"""
# If in the lingering-close state, lose the socket.
@@ -923,6 +979,7 @@
if self.chanRequest:
self.transport.loseConnection()
+
def connectionLost(self, reason):
self.factory.removeConnectedChannel(self)
@@ -935,6 +992,8 @@
if request is not None:
request.connectionLost(reason)
+
+
class OverloadedServerProtocol(protocol.Protocol):
def connectionMade(self):
self.transport.write("HTTP/1.0 503 Service Unavailable\r\n"
@@ -980,7 +1039,7 @@
p = protocol.ServerFactory.buildProtocol(self, addr)
- for arg,value in self.protocolArgs.iteritems():
+ for arg, value in self.protocolArgs.iteritems():
setattr(p, arg, value)
return p
@@ -1007,6 +1066,7 @@
if self.outstandingRequests == 0:
self.allConnectionsClosedDeferred.callback(None)
+
@property
def outstandingRequests(self):
return len(self.connectedChannels)
@@ -1033,9 +1093,10 @@
self.vary = vary
HTTPFactory.__init__(self, requestFactory, maxRequests, **kwargs)
+
def buildProtocol(self, addr):
if self.vary:
- retryAfter = randint(int(self.retryAfter * 1/2), int(self.retryAfter * 3/2))
+ retryAfter = randint(int(self.retryAfter * 1 / 2), int(self.retryAfter * 3 / 2))
else:
retryAfter = self.retryAfter
@@ -1044,11 +1105,13 @@
p = protocol.ServerFactory.buildProtocol(self, addr)
- for arg,value in self.protocolArgs.iteritems():
+ for arg, value in self.protocolArgs.iteritems():
setattr(p, arg, value)
return p
+
+
class HTTPLoggingChannelRequest(HTTPChannelRequest):
class TransportLoggingWrapper(object):
@@ -1071,11 +1134,13 @@
def __getattr__(self, attr):
return getattr(self.__dict__['transport'], attr)
+
class LogData(object):
def __init__(self):
self.request = []
self.response = []
+
def __init__(self, channel, queued=0):
super(HTTPLoggingChannelRequest, self).__init__(channel, queued)
@@ -1085,6 +1150,7 @@
else:
self.logData = None
+
def gotInitialLine(self, initialLine):
if self.logData is not None:
self.startTime = time.time()
@@ -1092,6 +1158,7 @@
self.logData.request.append("%s\r\n" % (initialLine,))
super(HTTPLoggingChannelRequest, self).gotInitialLine(initialLine)
+
def lineReceived(self, line):
if self.logData is not None:
@@ -1104,12 +1171,14 @@
self.logData.request.append("%s\r\n" % (loggedLine,))
super(HTTPLoggingChannelRequest, self).lineReceived(line)
+
def handleContentChunk(self, data):
if self.logData is not None:
self.logData.request.append(data)
super(HTTPLoggingChannelRequest, self).handleContentChunk(data)
+
def handleContentComplete(self):
if self.logData is not None:
@@ -1117,12 +1186,14 @@
self.logData.request.append("\r\n\r\n>>>> Request complete at: %.3f (elapsed: %.1f ms)" % (doneTime, 1000 * (doneTime - self.startTime),))
super(HTTPLoggingChannelRequest, self).handleContentComplete()
+
def writeHeaders(self, code, headers):
if self.logData is not None:
doneTime = time.time()
self.logData.response.append("\r\n\r\n<<<< Response sending at: %.3f (elapsed: %.1f ms)\r\n\r\n" % (doneTime, 1000 * (doneTime - self.startTime),))
super(HTTPLoggingChannelRequest, self).writeHeaders(code, headers)
+
def finish(self):
super(HTTPLoggingChannelRequest, self).finish()
@@ -1144,11 +1215,11 @@
L{LimitingHTTPFactory} will limit. This must be set externally.
"""
- def __init__(self, requestFactory, maxRequests=600, maxAccepts=100,
- **kwargs):
+ def __init__(self, requestFactory, maxRequests=600, maxAccepts=100, **kwargs):
HTTPFactory.__init__(self, requestFactory, maxRequests, **kwargs)
self.maxAccepts = maxAccepts
+
def buildProtocol(self, addr):
"""
Override L{HTTPFactory.buildProtocol} in order to avoid ever returning
@@ -1159,6 +1230,7 @@
setattr(p, arg, value)
return p
+
def addConnectedChannel(self, channel):
"""
Override L{HTTPFactory.addConnectedChannel} to pause listening on the
Modified: CalendarServer/trunk/txweb2/client/http.py
===================================================================
--- CalendarServer/trunk/txweb2/client/http.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/client/http.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -104,6 +104,7 @@
self.transport = self.channel.transport
self.responseDefer = Deferred()
+
def submit(self):
l = []
request = self.request
@@ -138,15 +139,18 @@
d = StreamProducer(request.stream).beginProducing(self)
d.addCallback(self._finish).addErrback(self._error)
+
def registerProducer(self, producer, streaming):
"""
Register a producer.
"""
self.transport.registerProducer(producer, streaming)
+
def unregisterProducer(self):
self.transport.unregisterProducer()
+
def write(self, data):
if not data:
return
@@ -155,6 +159,7 @@
else:
self.transport.write(data)
+
def _finish(self, x):
"""
We are finished writing data.
@@ -167,6 +172,7 @@
self.channel.requestWriteFinished(self)
del self.transport
+
def _error(self, err):
"""
Abort parsing, and depending of the status of the request, either fire
@@ -179,15 +185,18 @@
else:
self.responseDefer.errback(err)
+
def _abortWithError(self, errcode, text):
"""
Abort parsing by forwarding a C{ProtocolError} to C{_error}.
"""
self._error(ProtocolError(text))
+
def connectionLost(self, reason):
self._error(reason)
+
def gotInitialLine(self, initialLine):
parts = initialLine.split(' ', 2)
@@ -197,7 +206,7 @@
"Bad response line: %s" % (initialLine,))
return
- strversion, self.code, message = parts
+ strversion, self.code, _ignore_message = parts
try:
protovers = parseVersion(strversion)
@@ -216,7 +225,8 @@
'Only HTTP 1.x is supported.')
return
- ## FIXME: Actually creates Response, function is badly named!
+
+ # FIXME: Actually creates Response, function is badly named!
def createRequest(self):
self.stream = ProducerStream(self.length)
self.response = Response(self.code, self.inHeaders, self.stream)
@@ -224,13 +234,16 @@
del self.inHeaders
- ## FIXME: Actually processes Response, function is badly named!
+
+ # FIXME: Actually processes Response, function is badly named!
def processRequest(self):
self.responseDefer.callback(self.response)
+
def handleContentChunk(self, data):
self.stream.write(data)
+
def handleContentComplete(self):
self.stream.finish()
@@ -247,12 +260,15 @@
def clientBusy(self, proto):
pass
+
def clientIdle(self, proto):
pass
+
def clientPipelining(self, proto):
pass
+
def clientGone(self, proto):
pass
@@ -285,6 +301,7 @@
manager = EmptyHTTPClientManager()
self.manager = manager
+
def lineReceived(self, line):
if not self.inRequests:
# server sending random unrequested data.
@@ -301,6 +318,7 @@
else:
self.inRequests[0].lineReceived(line)
+
def rawDataReceived(self, data):
if not self.inRequests:
# Server sending random unrequested data.
@@ -313,6 +331,7 @@
self.inRequests[0].rawDataReceived(data)
+
def submitRequest(self, request, closeAfter=True):
"""
@param request: The request to send to a remote server.
@@ -337,13 +356,13 @@
if closeAfter:
self.readPersistent = False
- self.outRequest = chanRequest = HTTPClientChannelRequest(self,
- request, closeAfter)
+ self.outRequest = chanRequest = HTTPClientChannelRequest(self, request, closeAfter)
self.inRequests.append(chanRequest)
chanRequest.submit()
return chanRequest.responseDefer
+
def requestWriteFinished(self, request):
assert request is self.outRequest
@@ -353,6 +372,7 @@
if self.readPersistent is PERSIST_PIPELINE:
self.manager.clientPipelining(self)
+
def requestReadFinished(self, request):
assert self.inRequests[0] is request
@@ -366,6 +386,7 @@
else:
self.transport.loseConnection()
+
def setReadPersistent(self, persist):
self.readPersistent = persist
if not persist:
@@ -374,6 +395,7 @@
request.connectionLost(None)
del self.inRequests[1:]
+
def connectionLost(self, reason):
self.readPersistent = False
self.setTimeout(None)
@@ -382,4 +404,3 @@
for request in self.inRequests:
if request is not None:
request.connectionLost(reason)
-
Modified: CalendarServer/trunk/txweb2/client/interfaces.py
===================================================================
--- CalendarServer/trunk/txweb2/client/interfaces.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/client/interfaces.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -26,7 +26,7 @@
from zope.interface import Interface
class IHTTPClientManager(Interface):
- """I coordinate between multiple L{HTTPClientProtocol} objects connected to a
+ """I coordinate between multiple L{HTTPClientProtocol} objects connected to a
single server to facilite request queuing and pipelining.
"""
@@ -35,27 +35,30 @@
requests.
@param proto: The L{HTTPClientProtocol} that is changing state.
- @type proto: L{HTTPClientProtocol}
+ @type proto: L{HTTPClientProtocol}
"""
pass
-
+
+
def clientIdle(proto):
"""Called when an L{HTTPClientProtocol} is able to accept more requests.
-
+
@param proto: The L{HTTPClientProtocol} that is changing state.
@type proto: L{HTTPClientProtocol}
"""
pass
+
def clientPipelining(proto):
"""Called when the L{HTTPClientProtocol} determines that it is able to
support request pipelining.
-
+
@param proto: The L{HTTPClientProtocol} that is changing state.
@type proto: L{HTTPClientProtocol}
"""
pass
-
+
+
def clientGone(proto):
"""Called when the L{HTTPClientProtocol} disconnects from the server.
Modified: CalendarServer/trunk/txweb2/dav/auth.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/auth.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/auth.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -37,9 +37,11 @@
class AuthenticationWrapper(WrapperResource):
- def __init__(self, resource, portal,
+ def __init__(
+ self, resource, portal,
wireEncryptedCredentialFactories, wireUnencryptedCredentialFactories,
- loginInterfaces):
+ loginInterfaces
+ ):
"""
Wrap the given resource and use the parameters to set up the request
to allow anyone to challenge and handle authentication.
@@ -58,10 +60,14 @@
super(AuthenticationWrapper, self).__init__(resource)
self.portal = portal
- self.wireEncryptedCredentialFactories = dict([(factory.scheme, factory)
- for factory in wireEncryptedCredentialFactories])
- self.wireUnencryptedCredentialFactories = dict([(factory.scheme, factory)
- for factory in wireUnencryptedCredentialFactories])
+ self.wireEncryptedCredentialFactories = dict([
+ (factory.scheme, factory)
+ for factory in wireEncryptedCredentialFactories
+ ])
+ self.wireUnencryptedCredentialFactories = dict([
+ (factory.scheme, factory)
+ for factory in wireUnencryptedCredentialFactories
+ ])
self.loginInterfaces = loginInterfaces
# FIXME: some unit tests access self.credentialFactories, so assigning here
Modified: CalendarServer/trunk/txweb2/dav/fileop.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/fileop.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/fileop.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -170,6 +170,8 @@
return succeed(response)
+
+
def copy(source_filepath, destination_filepath, destination_uri, depth):
"""
Perform a X{COPY} from the given source and destination filepaths.
@@ -203,7 +205,7 @@
Failure(),
"opening file for reading: %s" % (source_filepath.path,)
))
-
+
source_stream = FileStream(source_file)
response = waitForDeferred(put(source_stream, destination_filepath, destination_uri))
yield response
@@ -257,7 +259,7 @@
"creating directory %s" % (destination_basename,)
))
- if depth == "0":
+ if depth == "0":
yield success_code
return
else:
@@ -279,7 +281,7 @@
def paths(basepath, subpath):
source_path = os.path.join(basepath, subpath)
assert source_path.startswith(source_basename)
- destination_path = os.path.join(destination_basename, source_path[source_basename_len+1:])
+ destination_path = os.path.join(destination_basename, source_path[source_basename_len + 1:])
return source_path, destination_path
for dir, subdirs, files in os.walk(source_filepath.path, topdown=True):
@@ -493,6 +495,8 @@
return succeed(responsecode.CREATED)
+
+
def rmdir(dirname):
"""
Removes the directory with the given name, as well as its contents.
@@ -510,6 +514,8 @@
os.rmdir(dirname)
+
+
def checkResponse(response, method, *codes):
- assert response in codes, \
+ assert response in codes, \
"%s() returned %r, but should have returned one of %r instead" % (method, response, codes)
Modified: CalendarServer/trunk/txweb2/dav/http.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/http.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/http.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -162,12 +162,13 @@
@param success_response: the response to return in lieu of a
L{MultiStatusResponse} if no responses are added to this queue.
"""
- self.responses = []
- self.path_basename = path_basename
+ self.responses = []
+ self.path_basename = path_basename
self.path_basename_len = len(path_basename)
- self.method = method
- self.success_response = success_response
+ self.method = method
+ self.success_response = success_response
+
def add(self, path, what):
"""
Add a response.
@@ -178,12 +179,12 @@
assert path.startswith(self.path_basename), "%s does not start with %s" % (path, self.path_basename)
if type(what) is int:
- code = what
- error = None
+ code = what
+ error = None
message = responsecode.RESPONSES[code]
elif isinstance(what, Failure):
- code = statusForFailure(what)
- error = errorForFailure(what)
+ code = statusForFailure(what)
+ error = errorForFailure(what)
message = messageForFailure(what)
else:
raise AssertionError("Unknown data type: %r" % (what,))
@@ -205,6 +206,7 @@
children.append(element.ResponseDescription(message))
self.responses.append(element.StatusResponse(*children))
+
def response(self):
"""
Generate a L{MultiStatusResponse} with the responses contained in the
@@ -231,11 +233,12 @@
@param success_response: the status to return if no
L{PropertyStatus} are added to this queue.
"""
- self.method = method
- self.uri = uri
- self.propstats = []
- self.success_response = success_response
+ self.method = method
+ self.uri = uri
+ self.propstats = []
+ self.success_response = success_response
+
def add(self, what, property):
"""
Add a response.
@@ -243,12 +246,12 @@
@param property: the property whose status is being reported.
"""
if type(what) is int:
- code = what
- error = None
+ code = what
+ error = None
message = responsecode.RESPONSES[code]
elif isinstance(what, Failure):
- code = statusForFailure(what)
- error = errorForFailure(what)
+ code = statusForFailure(what)
+ error = errorForFailure(what)
message = messageForFailure(what)
else:
raise AssertionError("Unknown data type: %r" % (what,))
@@ -274,6 +277,7 @@
children.append(element.ResponseDescription(message))
self.propstats.append(element.PropertyStatus(*children))
+
def error(self):
"""
Convert any 2xx codes in the propstat responses to 424 Failed
@@ -294,6 +298,7 @@
newchildren.append(child)
self.propstats[index] = element.PropertyStatus(*newchildren)
+
def response(self):
"""
Generate a response from the responses contained in the queue or, if
@@ -313,6 +318,7 @@
)
+
##
# Exceptions and response codes
##
@@ -355,6 +361,7 @@
failure.raiseException()
+
def errorForFailure(failure):
if failure.check(HTTPError) and isinstance(failure.value.response, ErrorResponse):
return element.Error(failure.value.response.error)
@@ -362,6 +369,7 @@
return None
+
def messageForFailure(failure):
if failure.check(HTTPError):
if isinstance(failure.value.response, ErrorResponse):
Modified: CalendarServer/trunk/txweb2/dav/method/__init__.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/__init__.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/__init__.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Modified: CalendarServer/trunk/txweb2/dav/method/acl.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/acl.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/acl.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Modified: CalendarServer/trunk/txweb2/dav/method/copymove.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/copymove.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/copymove.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -84,7 +84,7 @@
# May need to add a location header
addLocation(request, destination_uri)
- #x = waitForDeferred(copy(self.fp, destination.fp, destination_uri, depth))
+ # x = waitForDeferred(copy(self.fp, destination.fp, destination_uri, depth))
x = waitForDeferred(put_common.storeResource(request,
source=self,
source_uri=request.uri,
@@ -214,6 +214,8 @@
return d
+
+
def _prepareForCopy(destination, destination_uri, request, depth):
#
# Destination must be a DAV resource
Modified: CalendarServer/trunk/txweb2/dav/method/delete.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/delete.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/delete.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Modified: CalendarServer/trunk/txweb2/dav/method/delete_common.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/delete_common.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/delete_common.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -66,7 +66,7 @@
d = waitForDeferred(resource.quotaSizeAdjust(request, -old_size))
yield d
d.getResult()
-
+
yield result
deleteResource = deferredGenerator(deleteResource)
Modified: CalendarServer/trunk/txweb2/dav/method/get.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/get.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/get.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -39,16 +39,22 @@
d.addCallback(lambda _: super(txweb2.dav.resource.DAVResource, self).http_OPTIONS(request))
return d
+
+
def http_HEAD(self, request):
d = authorize(self, request)
d.addCallback(lambda _: super(txweb2.dav.resource.DAVResource, self).http_HEAD(request))
return d
+
+
def http_GET(self, request):
d = authorize(self, request)
d.addCallback(lambda _: super(txweb2.dav.resource.DAVResource, self).http_GET(request))
return d
+
+
def authorize(self, request):
if self.exists():
d = self.authorize(request, (davxml.Read(),))
Modified: CalendarServer/trunk/txweb2/dav/method/lock.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/lock.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/lock.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -37,6 +37,8 @@
"""
return responsecode.NOT_IMPLEMENTED
+
+
def http_UNLOCK(self, request):
"""
Respond to a UNLOCK request. (RFC 2518, section 8.11)
Modified: CalendarServer/trunk/txweb2/dav/method/mkcol.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/mkcol.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/mkcol.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Modified: CalendarServer/trunk/txweb2/dav/method/prop_common.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/prop_common.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/prop_common.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -59,9 +59,11 @@
@param resource: the L{DAVFile} for the targetted resource.
@return: a map of OK and NOT FOUND property values.
"""
-
+
return _namedPropertiesForResource(request, prop.children, resource)
+
+
def _namedPropertiesForResource(request, props, resource):
"""
Return the specified properties on the specified resource.
@@ -74,13 +76,13 @@
responsecode.OK : [],
responsecode.NOT_FOUND : [],
}
-
+
for property in props:
if isinstance(property, element.WebDAVElement):
qname = property.qname()
else:
qname = property
-
+
props = waitForDeferred(resource.listProperties(request))
yield props
props = props.getResult()
@@ -96,11 +98,12 @@
if status != responsecode.NOT_FOUND:
log.error("Error reading property %r for resource %s: %s" %
(qname, request.uri, f.value))
- if status not in properties_by_status: properties_by_status[status] = []
+ if status not in properties_by_status:
+ properties_by_status[status] = []
properties_by_status[status].append(propertyName(qname))
else:
properties_by_status[responsecode.NOT_FOUND].append(propertyName(qname))
-
+
yield properties_by_status
_namedPropertiesForResource = deferredGenerator(_namedPropertiesForResource)
Modified: CalendarServer/trunk/txweb2/dav/method/put.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/put.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/put.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -69,15 +69,15 @@
# Implemented error if we get a Content-* header which we don't
# recognize and handle properly.
#
- for header, value in request.headers.getAllRawHeaders():
+ for header, _ignore_value in request.headers.getAllRawHeaders():
if header.startswith("Content-") and header not in (
- #"Content-Base", # Doesn't make sense in PUT?
- #"Content-Encoding", # Requires that we decode it?
+ # "Content-Base", # Doesn't make sense in PUT?
+ # "Content-Encoding", # Requires that we decode it?
"Content-Language",
"Content-Length",
- #"Content-Location", # Doesn't make sense in PUT?
+ # "Content-Location", # Doesn't make sense in PUT?
"Content-MD5",
- #"Content-Range", # FIXME: Need to implement this
+ # "Content-Range", # FIXME: Need to implement this
"Content-Type",
):
log.error("Client sent unrecognized content header in PUT request: %s"
@@ -100,5 +100,5 @@
# to return a MULTI_STATUS response, which is WebDAV-specific (and PUT is
# not).
#
- #return put(request.stream, self.fp)
+ # return put(request.stream, self.fp)
return put_common.storeResource(request, destination=self, destination_uri=request.uri)
Modified: CalendarServer/trunk/txweb2/dav/method/put_common.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/put_common.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/put_common.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -55,7 +55,7 @@
):
"""
Function that does common PUT/COPY/MOVE behaviour.
-
+
@param request: the L{txweb2.server.Request} for the current HTTP request.
@param source: the L{DAVFile} for the source resource to copy from, or None if source data
is to be read from the request.
@@ -67,7 +67,7 @@
@param depth: a C{str} containing the COPY/MOVE Depth header value.
@return: status response.
"""
-
+
try:
assert request is not None and destination is not None and destination_uri is not None
assert (source is None) or (source is not None and source_uri is not None)
@@ -84,20 +84,22 @@
log.error("depth=%s\n" % (depth,))
raise
+
class RollbackState(object):
"""
This class encapsulates the state needed to rollback the entire PUT/COPY/MOVE
transaction, leaving the server state the same as it was before the request was
processed. The DoRollback method will actually execute the rollback operations.
"""
-
+
def __init__(self):
self.active = True
self.source_copy = None
self.destination_copy = None
self.destination_created = False
self.source_deleted = False
-
+
+
def Rollback(self):
"""
Rollback the server state. Do not allow this to raise another exception. If
@@ -142,7 +144,7 @@
self.destination_copy = None
self.destination_created = False
self.source_deleted = False
-
+
rollback = RollbackState()
try:
@@ -164,7 +166,7 @@
old_dest_size = old_dest_size.getResult()
else:
old_dest_size = 0
-
+
if source is not None:
sourcequota = waitForDeferred(source.quota(request))
yield sourcequota
@@ -193,7 +195,7 @@
rollback.source_copy = FilePath(source.fp.path)
rollback.source_copy.path += ".rollback"
source.fp.copyTo(rollback.source_copy)
-
+
"""
Handle actual store operations here.
"""
@@ -231,7 +233,7 @@
destination.writeDeadProperty(davxml.GETContentType.fromString(generateContentType(content_type)))
response = IResponse(response)
-
+
# Do quota check on destination
if destquota is not None:
# Get size of new/old resources
@@ -265,7 +267,7 @@
yield response
return
-
+
except:
# Roll back changes to original server state. Note this may do nothing
# if the rollback has already ocurred or changes already committed.
Modified: CalendarServer/trunk/txweb2/dav/method/report.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/report.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/report.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -52,15 +52,18 @@
max_number_of_matches = 500
class NumberOfMatchesWithinLimits(Exception):
-
+
def __init__(self, limit):
-
+
super(NumberOfMatchesWithinLimits, self).__init__()
self.limit = limit
-
+
+
def maxLimit(self):
return self.limit
+
+
def http_REPORT(self, request):
"""
Respond to a REPORT request. (RFC 3253, section 3.6)
@@ -124,7 +127,7 @@
try:
method = getattr(self, method_name)
-
+
# Also double-check via supported-reports property
reports = self.supportedReports()
test = lookupElement((namespace, name))
Modified: CalendarServer/trunk/txweb2/dav/method/report_acl_principal_prop_set.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/report_acl_principal_prop_set.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/report_acl_principal_prop_set.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -58,7 +58,7 @@
if depth != "0":
log.error("Error in prinicpal-prop-set REPORT, Depth set to %s" % (depth,))
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
-
+
#
# Check authentication and access controls
#
Modified: CalendarServer/trunk/txweb2/dav/method/report_principal_match.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/report_principal_match.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/report_principal_match.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -60,7 +60,7 @@
if depth != "0":
log.error("Non-zero depth is not allowed: %s" % (depth,))
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
-
+
# Get a single DAV:prop element from the REPORT request body
propertiesForResource = None
propElement = None
@@ -98,19 +98,19 @@
if lookForPrincipals:
# Find the set of principals that represent "self".
-
+
# First add "self"
principal = waitForDeferred(request.locateResource(str(myPrincipalURL)))
yield principal
principal = principal.getResult()
- selfItems = [principal,]
-
+ selfItems = [principal, ]
+
# Get group memberships for "self" and add each of those
d = waitForDeferred(principal.groupMemberships())
yield d
memberships = d.getResult()
selfItems.extend(memberships)
-
+
# Now add each principal found to the response provided the principal resource is a child of
# the current resource.
for principal in selfItems:
@@ -118,7 +118,7 @@
# FIXME: making the assumption that the principalURL() is the URL of the resource we found
principal_uris = [principal.principalURL()]
principal_uris.extend(principal.alternateURIs())
-
+
# Compare each one to the request URI and return at most one that matches
for uri in principal_uris:
if uri.startswith(request.uri):
@@ -126,7 +126,7 @@
matchcount += 1
if matchcount > max_number_of_matches:
raise NumberOfMatchesWithinLimits(max_number_of_matches)
-
+
d = waitForDeferred(prop_common.responseForHref(
request,
responses,
@@ -144,9 +144,9 @@
filteredaces = waitForDeferred(self.inheritedACEsforChildren(request))
yield filteredaces
filteredaces = filteredaces.getResult()
-
+
children = []
- d = waitForDeferred(self.findChildren("infinity", request, lambda x, y: children.append((x,y)),
+ d = waitForDeferred(self.findChildren("infinity", request, lambda x, y: children.append((x, y)),
privileges=(element.Read(),), inherited_aces=filteredaces))
yield d
d.getResult()
@@ -157,7 +157,8 @@
prop = waitForDeferred(child.readProperty(principalPropElement.qname(), request))
yield prop
prop = prop.getResult()
- if prop: prop.removeWhitespaceNodes()
+ if prop:
+ prop.removeWhitespaceNodes()
if prop and len(prop.children) == 1 and isinstance(prop.children[0], element.HRef):
# Find principal associated with this property and test it
Modified: CalendarServer/trunk/txweb2/dav/method/report_principal_property_search.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/report_principal_property_search.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/report_principal_property_search.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -61,7 +61,7 @@
if depth != "0":
log.error("Error in prinicpal-property-search REPORT, Depth set to %s" % (depth,))
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
-
+
# Get a single DAV:prop element from the REPORT request body
propertiesForResource = None
propElement = None
@@ -78,7 +78,8 @@
props.removeWhitespaceNodes()
match = child.childOfType(element.Match)
propertySearches.append((props.children, str(match).lower()))
-
+
+
def nodeMatch(node, match):
"""
See if the content of the supplied node matches the supplied text.
@@ -97,7 +98,8 @@
return nodeMatch(child, match)
else:
return False
-
+
+
def propertySearch(resource, request):
"""
Test the resource to see if it contains properties matching the
@@ -120,7 +122,7 @@
# No property => no match
yield False
return
-
+
yield True
propertySearch = deferredGenerator(propertySearch)
@@ -143,7 +145,7 @@
resources.append((self, request.uri))
# Loop over all collections and principal resources within
- for resource, ruri in resources:
+ for resource, _ignore_ruri in resources:
# Do some optimisation of access control calculation by determining any inherited ACLs outside of
# the child resource loop and supply those to the checkPrivileges on each child.
@@ -152,7 +154,7 @@
filteredaces = filteredaces.getResult()
children = []
- d = waitForDeferred(resource.findChildren("infinity", request, lambda x, y: children.append((x,y)),
+ d = waitForDeferred(resource.findChildren("infinity", request, lambda x, y: children.append((x, y)),
privileges=(element.Read(),), inherited_aces=filteredaces))
yield d
d.getResult()
@@ -167,7 +169,7 @@
matchcount += 1
if matchcount > max_number_of_matches:
raise NumberOfMatchesWithinLimits(max_number_of_matches)
-
+
d = waitForDeferred(prop_common.responseForHref(
request,
responses,
Modified: CalendarServer/trunk/txweb2/dav/method/report_principal_search_property_set.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/method/report_principal_search_property_set.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/method/report_principal_search_property_set.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -54,13 +54,13 @@
if depth != "0":
log.error("Error in principal-search-property-set REPORT, Depth set to %s" % (depth,))
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Depth %s not allowed" % (depth,)))
-
+
# Get details from the resource
result = self.principalSearchPropertySet()
if result is None:
log.error("Error in principal-search-property-set REPORT not supported on: %s" % (self,))
raise HTTPError(StatusResponse(responsecode.BAD_REQUEST, "Not allowed on this resource"))
-
+
yield Response(code=responsecode.OK, stream=MemoryStream(result.toxml()))
report_DAV__principal_search_property_set = deferredGenerator(report_DAV__principal_search_property_set)
Modified: CalendarServer/trunk/txweb2/dav/noneprops.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/noneprops.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/noneprops.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -48,28 +48,34 @@
NonePropertyStore.__singleton = object.__new__(clazz)
return NonePropertyStore.__singleton
+
def __init__(self, resource):
pass
+
def get(self, qname, uid=None):
raise HTTPError(StatusResponse(
responsecode.NOT_FOUND,
"No such property: %s" % (encodeXMLName(*qname),)
))
+
def set(self, property, uid=None):
raise HTTPError(StatusResponse(
responsecode.FORBIDDEN,
"Permission denied for setting property: %s" % (property,)
))
+
def delete(self, qname, uid=None):
# RFC 2518 Section 12.13.1 says that removal of
# non-existing property is not an error.
pass
+
def contains(self, qname, uid=None):
return False
+
def list(self, uid=None):
return ()
Modified: CalendarServer/trunk/txweb2/dav/resource.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/resource.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/resource.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -148,8 +148,8 @@
(dav_namespace, "displayname"),
(dav_namespace, "supportedlock"),
(dav_namespace, "supported-report-set"), # RFC 3253, section 3.1.5
- #(dav_namespace, "owner" ), # RFC 3744, section 5.1
- #(dav_namespace, "group" ), # RFC 3744, section 5.2
+ # (dav_namespace, "owner" ), # RFC 3744, section 5.1
+ # (dav_namespace, "group" ), # RFC 3744, section 5.2
(dav_namespace, "supported-privilege-set"), # RFC 3744, section 5.3
(dav_namespace, "current-user-privilege-set"), # RFC 3744, section 5.4
(dav_namespace, "current-user-principal"), # RFC 5397, Section 3
@@ -752,8 +752,8 @@
for name in names:
(names1 if name.rstrip("/").find("/") == -1 else namesDeep).append(name.rstrip("/"))
- #children = []
- #yield self.findChildren("1", request, lambda x, y: children.append((x, y)), privileges=None, inherited_aces=None)
+ # children = []
+ # yield self.findChildren("1", request, lambda x, y: children.append((x, y)), privileges=None, inherited_aces=None)
children = []
basepath = request.urlForResource(self)
@@ -794,7 +794,7 @@
)[2].append((resource, url))
# Now determine whether each ace satisfies privileges
- #print(aclmap)
+ # print(aclmap)
for items in aclmap.itervalues():
checked = (yield self.checkACLPrivilege(
request, items[0], items[1], privileges, inherited_aces
@@ -1237,7 +1237,7 @@
#
# Otherwise, we'd use this logic:
#
- #elif old_ace.inherited:
+ # elif old_ace.inherited:
# log.error("Attempt to overwrite inherited ace %r "
# "on resource %r" % (old_ace, self))
# returnValue((
@@ -1273,8 +1273,7 @@
returnValue((element.dav_namespace, "no-ace-conflict"))
if ace.inherited:
- log.error("Attempt to create inherited ace %r on resource %r"
- % (ace, self))
+ log.error("Attempt to create inherited ace %r on resource %r" % (ace, self))
returnValue((element.dav_namespace, "no-ace-conflict"))
# Step 6
@@ -1381,8 +1380,8 @@
)
for resource, uri in resources:
- acl = (yield
- resource.accessControlList(
+ acl = (
+ yield resource.accessControlList(
request,
inherited_aces=inherited_aces
)
@@ -1404,8 +1403,8 @@
):
continue
- match = (yield
- self.matchPrincipal(principal, ace.principal, request)
+ match = (
+ yield self.matchPrincipal(principal, ace.principal, request)
)
if match:
@@ -1525,8 +1524,8 @@
parent = (yield request.locateResource(parentURL))
if parent:
- parent_acl = (yield
- parent.accessControlList(
+ parent_acl = (
+ yield parent.accessControlList(
request, inheritance=True, expanding=True
)
)
@@ -1989,15 +1988,15 @@
# First see if the ace's principal affects the principal
# being tested. FIXME: support the DAV:invert operation
- match = (yield
- self.matchPrincipal(principal, ace.principal, request)
+ match = (
+ yield self.matchPrincipal(principal, ace.principal, request)
)
if match:
# Expand aggregate privileges
ps = []
- supportedPrivs = (yield
- self.supportedPrivileges(request)
+ supportedPrivs = (
+ yield self.supportedPrivileges(request)
)
for p in ace.privileges:
ps.extend(p.expandAggregate(supportedPrivs))
@@ -2319,8 +2318,8 @@
def renderHTTP(self, request):
# FIXME: This is for testing with litmus; comment out when not in use
- #litmus = request.headers.getRawHeaders("x-litmus")
- #if litmus: log.info("*** Litmus test: %s ***" % (litmus,))
+ # litmus = request.headers.getRawHeaders("x-litmus")
+ # if litmus: log.info("*** Litmus test: %s ***" % (litmus,))
#
# If this is a collection and the URI doesn't end in "/", redirect.
Modified: CalendarServer/trunk/txweb2/dav/static.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/static.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/static.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -8,10 +8,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -51,6 +51,8 @@
log.info("Setting of dead properties will not be allowed.")
from txweb2.dav.noneprops import NonePropertyStore as DeadPropertyStore
+
+
class DAVFile (DAVResource, File):
"""
WebDAV-accessible File resource.
@@ -71,41 +73,48 @@
"""
File.__init__(
self, path,
- defaultType = defaultType,
- ignoredExts = (),
- processors = None,
- indexNames = indexNames,
+ defaultType=defaultType,
+ ignoredExts=(),
+ processors=None,
+ indexNames=indexNames,
)
DAVResource.__init__(self, principalCollections=principalCollections)
+
def __repr__(self):
return "<%s: %s>" % (self.__class__.__name__, self.fp.path)
+
##
# WebDAV
##
def etag(self):
- if not self.fp.exists(): return succeed(None)
+ if not self.fp.exists():
+ return succeed(None)
if self.hasDeadProperty(TwistedGETContentMD5):
return succeed(http_headers.ETag(str(self.readDeadProperty(TwistedGETContentMD5))))
else:
return super(DAVFile, self).etag()
+
def davComplianceClasses(self):
return ("1", "access-control") # Add "2" when we have locking
+
def deadProperties(self):
if not hasattr(self, "_dead_properties"):
self._dead_properties = DeadPropertyStore(self)
return self._dead_properties
+
def isCollection(self):
"""
See L{IDAVResource.isCollection}.
"""
return self.fp.isdir()
+
##
# ACL
##
@@ -113,6 +122,7 @@
def supportedPrivileges(self, request):
return succeed(davPrivilegeSet)
+
##
# Quota
##
@@ -130,10 +140,10 @@
"""
Recursively descend the directory tree rooted at top,
calling the callback function for each regular file
-
+
@param top: L{FilePath} for the directory to walk.
"""
-
+
total = 0
for f in top.listdir():
child = top.child(f)
@@ -148,15 +158,16 @@
else:
# Unknown file type, print a message
pass
-
+
yield total
-
+
walktree = deferredGenerator(walktree)
-
+
return walktree(self.fp)
else:
return succeed(self.fp.getsize())
+
##
# Workarounds for issues with File
##
@@ -167,6 +178,7 @@
"""
pass
+
def locateChild(self, req, segments):
"""
See L{IResource}C{.locateChild}.
@@ -178,7 +190,7 @@
return (child, segments[1:])
except InsecurePath:
raise HTTPError(StatusResponse(responsecode.FORBIDDEN, "Invalid URL path"))
-
+
# If we're not backed by a directory, we have no children.
# But check for existance first; we might be a collection resource
# that the request wants created.
@@ -188,13 +200,14 @@
# OK, we need to return a child corresponding to the first segment
path = segments[0]
-
+
if path == "":
# Request is for a directory (collection) resource
return (self, ())
return (self.createSimilarFile(self.fp.child(path).path), segments[1:])
+
def createSimilarFile(self, path):
return self.__class__(
path, defaultType=self.defaultType, indexNames=self.indexNames[:],
Modified: CalendarServer/trunk/txweb2/dav/test/__init__.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/__init__.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/__init__.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Modified: CalendarServer/trunk/txweb2/dav/test/test_acl.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_acl.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_acl.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -54,9 +54,13 @@
principalCollection = TestPrincipalsCollection(
"/principals/",
- children={"users": TestPrincipalsCollection(
+ children={
+ "users": TestPrincipalsCollection(
"/principals/users/",
- children={"user01": userResource})})
+ children={"user01": userResource}
+ )
+ }
+ )
rootResource = self.resource_class(
docroot, principalCollections=(principalCollection,))
@@ -112,6 +116,7 @@
rmdir(self._docroot)
del self._docroot
+
def test_COPY_MOVE_source(self):
"""
Verify source access controls during COPY and MOVE.
@@ -122,7 +127,7 @@
for src, status in (
("nobind", responsecode.FORBIDDEN),
- ("bind", responsecode.FORBIDDEN),
+ ("bind", responsecode.FORBIDDEN),
("unbind", responsecode.CREATED),
):
src_path = os.path.join(self.docroot, "src_" + src)
@@ -149,30 +154,31 @@
for method in ("COPY", "MOVE"):
for name, code in (
- ("none" , {"COPY": responsecode.FORBIDDEN, "MOVE": status}[method]),
- ("read" , {"COPY": responsecode.CREATED, "MOVE": status}[method]),
- ("read-write" , {"COPY": responsecode.CREATED, "MOVE": status}[method]),
- ("unlock" , {"COPY": responsecode.FORBIDDEN, "MOVE": status}[method]),
- ("all" , {"COPY": responsecode.CREATED, "MOVE": status}[method]),
+ ("none", {"COPY": responsecode.FORBIDDEN, "MOVE": status}[method]),
+ ("read", {"COPY": responsecode.CREATED, "MOVE": status}[method]),
+ ("read-write" , {"COPY": responsecode.CREATED, "MOVE": status}[method]),
+ ("unlock", {"COPY": responsecode.FORBIDDEN, "MOVE": status}[method]),
+ ("all", {"COPY": responsecode.CREATED, "MOVE": status}[method]),
):
path = os.path.join(src_path, name)
uri = src_uri + "/" + name
-
+
request = SimpleRequest(self.site, method, uri)
request.headers.setHeader("destination", dst_uri)
_add_auth_header(request)
-
+
def test(response, code=code, path=path):
if os.path.isfile(dst_path):
os.remove(dst_path)
-
+
if response.code != code:
return self.oops(request, response, code, method, name)
-
+
yield (request, test)
return serialize(self.send, work())
+
def test_COPY_MOVE_dest(self):
"""
Verify destination access controls during COPY and MOVE.
@@ -206,6 +212,7 @@
return serialize(self.send, work())
+
def test_DELETE(self):
"""
Verify access controls during DELETE.
@@ -232,6 +239,7 @@
return serialize(self.send, work())
+
def test_UNLOCK(self):
"""
Verify access controls during UNLOCK of unowned lock.
@@ -270,6 +278,7 @@
return serialize(self.send, work())
+
def test_PUT_exists(self):
"""
Verify access controls during PUT of existing file.
@@ -295,6 +304,7 @@
return serialize(self.send, work())
+
def test_PROPFIND(self):
"""
Verify access controls during PROPFIND.
@@ -331,6 +341,7 @@
return serialize(self.send, work())
+
def test_GET_REPORT(self):
"""
Verify access controls during GET and REPORT.
@@ -367,13 +378,14 @@
return serialize(self.send, work())
+
def oops(self, request, response, code, method, name):
def gotResponseData(doc):
if doc is None:
doc_xml = None
else:
doc_xml = doc.toxml()
-
+
def fail(acl):
self.fail("Incorrect status code %s (!= %s) for %s of resource %s with %s ACL: %s\nACL: %s"
% (response.code, code, method, request.uri, name, doc_xml, acl.toxml()))
@@ -391,6 +403,8 @@
d.addCallback(gotResponseData)
return d
+
+
def _add_auth_header(request):
request.headers.setHeader(
"authorization",
Modified: CalendarServer/trunk/txweb2/dav/test/test_auth.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_auth.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_auth.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -51,8 +51,7 @@
self.credentialFactories = None
self.chanRequest = FakeChannel(secure)
- wrapper = AuthenticationWrapper(None, None,
- wireEncryptedfactories, wireUnencryptedfactories, None)
+ wrapper = AuthenticationWrapper(None, None, wireEncryptedfactories, wireUnencryptedfactories, None)
req = FakeRequest(True) # Connection is over SSL
wrapper.hook(req)
self.assertEquals(
Modified: CalendarServer/trunk/txweb2/dav/test/test_copy.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_copy.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_copy.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -70,7 +70,8 @@
for filename in os.listdir(dst_path):
self.fail("COPY %s (depth=%r) shouldn't copy directory contents (eg. %s)" % (uri, depth, filename))
- else: raise AssertionError("Unknown depth: %r" % (depth,))
+ else:
+ raise AssertionError("Unknown depth: %r" % (depth,))
rmdir(dst_path)
@@ -80,6 +81,7 @@
return serialize(self.send, work(self, test))
+
def test_COPY_exists(self):
"""
COPY to existing resource.
@@ -94,6 +96,7 @@
return serialize(self.send, work(self, test, overwrite=False))
+
def test_COPY_overwrite(self):
"""
COPY to existing resource with overwrite header.
@@ -110,6 +113,7 @@
return serialize(self.send, work(self, test, overwrite=True))
+
def test_COPY_no_parent(self):
"""
COPY to resource with no parent.
@@ -124,13 +128,16 @@
return serialize(self.send, work(self, test, dst=os.path.join(self.docroot, "elvislives!")))
+
+
def work(self, test, overwrite=None, dst=None, depths=("0", "infinity", None)):
if dst is None:
dst = os.path.join(self.docroot, "dst")
os.mkdir(dst)
for basename in os.listdir(self.docroot):
- if basename == "dst": continue
+ if basename == "dst":
+ continue
uri = urllib.quote("/" + basename)
path = os.path.join(self.docroot, basename)
@@ -160,6 +167,8 @@
yield (request, do_test)
+
+
def sumFile(path):
m = md5()
Modified: CalendarServer/trunk/txweb2/dav/test/test_delete.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_delete.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_delete.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -58,7 +58,8 @@
path = os.path.join(self.docroot, filename)
uri = urllib.quote("/" + filename)
- if os.path.isdir(path): uri = uri + "/"
+ if os.path.isdir(path):
+ uri = uri + "/"
def do_test(response, path=path):
return check_result(response, path)
Modified: CalendarServer/trunk/txweb2/dav/test/test_http.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_http.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_http.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -40,13 +40,14 @@
"""
for ex_class in (IOError, OSError):
for exception, result in (
- (ex_class(errno.EACCES, "Permission denied" ), responsecode.FORBIDDEN),
- (ex_class(errno.EPERM , "Permission denied" ), responsecode.FORBIDDEN),
+ (ex_class(errno.EACCES, "Permission denied"), responsecode.FORBIDDEN),
+ (ex_class(errno.EPERM , "Permission denied"), responsecode.FORBIDDEN),
(ex_class(errno.ENOSPC, "No space available"), responsecode.INSUFFICIENT_STORAGE_SPACE),
- (ex_class(errno.ENOENT, "No such file" ), responsecode.NOT_FOUND),
+ (ex_class(errno.ENOENT, "No such file"), responsecode.NOT_FOUND),
):
self._check_exception(exception, result)
+
def test_statusForFailure_HTTPError(self):
"""
statusForFailure() for HTTPErrors
@@ -55,6 +56,7 @@
self._check_exception(HTTPError(code), code)
self._check_exception(HTTPError(ErrorResponse(code, ("http://twistedmatrix.com/", "bar"))), code)
+
def test_statusForFailure_exception(self):
"""
statusForFailure() for known/unknown exceptions
@@ -74,6 +76,7 @@
else:
self.fail("Unknown exception should have re-raised.")
+
def _check_exception(self, exception, result):
try:
raise exception
Modified: CalendarServer/trunk/txweb2/dav/test/test_lock.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_lock.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_lock.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Modified: CalendarServer/trunk/txweb2/dav/test/test_mkcol.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_mkcol.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_mkcol.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -60,6 +60,7 @@
return self.send(request, check_result)
+
def test_MKCOL_invalid_body(self):
"""
MKCOL request with invalid request body
Modified: CalendarServer/trunk/txweb2/dav/test/test_move.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_move.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_move.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -62,6 +62,7 @@
return serialize(self.send, work(self, test))
+
def test_MOVE_exists(self):
"""
MOVE to existing resource.
@@ -76,6 +77,7 @@
return serialize(self.send, work(self, test, overwrite=False))
+
def test_MOVE_overwrite(self):
"""
MOVE to existing resource with overwrite header.
@@ -90,6 +92,7 @@
return serialize(self.send, work(self, test, overwrite=True))
+
def test_MOVE_no_parent(self):
"""
MOVE to resource with no parent.
@@ -104,5 +107,7 @@
return serialize(self.send, work(self, test, dst=os.path.join(self.docroot, "elvislives!")))
+
+
def work(self, test, overwrite=None, dst=None):
return txweb2.dav.test.test_copy.work(self, test, overwrite, dst, depths=(None,))
Modified: CalendarServer/trunk/txweb2/dav/test/test_options.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_options.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_options.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -37,6 +37,7 @@
"""
return self._test_level("1")
+
def test_DAV2(self):
"""
DAV level 2
@@ -51,12 +52,14 @@
"""
return self._test_level("access-control")
+
def _test_level(self, level):
def doTest(response):
response = IResponse(response)
dav = response.headers.getHeader("dav")
- if not dav: self.fail("no DAV header: %s" % (response.headers,))
+ if not dav:
+ self.fail("no DAV header: %s" % (response.headers,))
self.assertIn(level, dav, "no DAV level %s header" % (level,))
return response
Modified: CalendarServer/trunk/txweb2/dav/test/test_prop.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_prop.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_prop.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -37,8 +37,8 @@
# Remove dynamic live properties that exist
dynamicLiveProperties = (
- (dav_namespace, "quota-available-bytes" ),
- (dav_namespace, "quota-used-bytes" ),
+ (dav_namespace, "quota-available-bytes"),
+ (dav_namespace, "quota-used-bytes"),
)
@@ -50,6 +50,7 @@
def liveProperties(self):
return [lookupElement(qname)() for qname in self.site.resource.liveProperties() if (qname[0] == dav_namespace) and qname not in dynamicLiveProperties]
+
def test_PROPFIND_basic(self):
"""
PROPFIND request
@@ -115,6 +116,7 @@
return self.send(request, check_result)
+
def test_PROPFIND_list(self):
"""
PROPFIND with allprop, propname
@@ -196,6 +198,7 @@
return serialize(self.send, work())
+
def test_PROPPATCH_basic(self):
"""
PROPPATCH
@@ -268,6 +271,7 @@
request.stream = MemoryStream(patch.toxml())
return self.send(request, check_patch_response)
+
def test_PROPPATCH_liveprop(self):
"""
PROPPATCH on a live property
@@ -277,6 +281,7 @@
return self._simple_PROPPATCH(patch, prop, responsecode.FORBIDDEN, "edit of live property")
+
def test_PROPPATCH_exists_not(self):
"""
PROPPATCH remove a non-existant property
@@ -286,6 +291,7 @@
return self._simple_PROPPATCH(patch, prop, responsecode.OK, "remove of non-existant property")
+
def _simple_PROPPATCH(self, patch, prop, expected_code, what):
def check_result(response):
response = IResponse(response)
@@ -320,6 +326,8 @@
request.stream = MemoryStream(patch.toxml())
return self.send(request, check_result)
+
+
class SpiffyProperty (davxml.WebDAVTextElement):
namespace = "http://twistedmatrix.com/ns/private/tests"
name = "spiffyproperty"
Modified: CalendarServer/trunk/txweb2/dav/test/test_put.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_put.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_put.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -77,17 +77,20 @@
path = os.path.join(self.docroot, name)
# Can't really PUT something you can't read
- if not os.path.isfile(path): continue
-
- def do_test(response): checkResult(response, path)
-
+ if not os.path.isfile(path):
+ continue
+
+ def do_test(response):
+ checkResult(response, path)
+
request = SimpleRequest(self.site, "PUT", dst_uri)
request.stream = FileStream(file(path, "rb"))
-
+
yield (request, do_test)
return serialize(self.send, work())
+
def test_PUT_again(self):
"""
PUT on existing resource with If-None-Match header
@@ -117,18 +120,19 @@
request = SimpleRequest(self.site, "PUT", dst_uri)
request.stream = FileStream(file(__file__, "rb"))
-
+
if code == responsecode.CREATED:
if os.path.isfile(dst_path):
os.remove(dst_path)
request.headers.setHeader("if-none-match", ("*",))
elif code == responsecode.PRECONDITION_FAILED:
request.headers.setHeader("if-none-match", ("*",))
-
+
yield (request, (checkResult, onError))
return serialize(self.send, work())
+
def test_PUT_no_parent(self):
"""
PUT with no parent
Modified: CalendarServer/trunk/txweb2/dav/test/test_quota.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_quota.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_quota.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -52,12 +52,16 @@
d.addCallback(_defer)
return d
+
+
class QuotaEmpty(QuotaBase):
def test_Empty_Quota(self):
return self.checkQuota(0)
+
+
class QuotaPUT(QuotaBase):
def test_Quota_PUT(self):
@@ -79,6 +83,8 @@
request.stream = FileStream(file(os.path.join(os.path.dirname(__file__), "data", "quota_100.txt"), "rb"))
return self.send(request, checkResult)
+
+
class QuotaDELETE(QuotaBase):
def test_Quota_DELETE(self):
@@ -115,6 +121,8 @@
request.stream = FileStream(file(os.path.join(os.path.dirname(__file__), "data", "quota_100.txt"), "rb"))
return self.send(request, checkPUTResult)
+
+
class OverQuotaPUT(QuotaBase):
def test_Quota_PUT(self):
@@ -138,6 +146,8 @@
request.stream = FileStream(file(os.path.join(os.path.dirname(__file__), "data", "quota_100.txt"), "rb"))
return self.send(request, checkResult)
+
+
class QuotaOKAdjustment(QuotaBase):
def test_Quota_OK_Adjustment(self):
@@ -169,6 +179,8 @@
request.stream = FileStream(file(os.path.join(os.path.dirname(__file__), "data", "quota_100.txt"), "rb"))
return self.send(request, checkPUTResult)
+
+
class QuotaBadAdjustment(QuotaBase):
def test_Quota_Bad_Adjustment(self):
Modified: CalendarServer/trunk/txweb2/dav/test/test_report.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_report.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_report.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@@ -50,6 +50,7 @@
return self.send(request, do_test)
+
def test_REPORT_unknown(self):
"""
Unknown/bogus report type
@@ -62,8 +63,9 @@
% (response.code,))
class GoofyReport (davxml.WebDAVUnknownElement):
namespace = "GOOFY:"
- name = "goofy-report"
- def __init__(self): super(GoofyReport, self).__init__()
+ name = "goofy-report"
+ def __init__(self):
+ super(GoofyReport, self).__init__()
request = SimpleRequest(self.site, "REPORT", "/")
request.stream = MemoryStream(GoofyReport().toxml())
Modified: CalendarServer/trunk/txweb2/dav/test/test_report_expand.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_report_expand.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_report_expand.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Modified: CalendarServer/trunk/txweb2/dav/test/test_resource.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_resource.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_resource.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -215,10 +215,11 @@
baduser.writeDeadProperty(TwistedPasswordProperty("badpass"))
rootresource = TestPrincipalsCollection("/", {
- "users": TestResource("/users/",
- {"gooduser": gooduser,
- "baduser": baduser})
- })
+ "users": TestResource(
+ "/users/",
+ {"gooduser": gooduser,
+ "baduser": baduser})
+ })
protected = TestResource(
"/protected", principalCollections=[rootresource])
Modified: CalendarServer/trunk/txweb2/dav/test/test_static.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_static.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_static.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -7,10 +7,10 @@
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
-#
+#
# The above copyright notice and this permission notice shall be included in all
# copies or substantial portions of the Software.
-#
+#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
Modified: CalendarServer/trunk/txweb2/dav/test/test_xattrprops.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/test_xattrprops.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/test_xattrprops.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -24,6 +24,8 @@
else:
from xattr import xattr
+
+
class ExtendedAttributesPropertyStoreTests(TestCase):
"""
Tests for L{xattrPropertyStore}.
@@ -31,6 +33,7 @@
if xattrPropertyStore is None:
skip = "xattr package missing, cannot test xattr property store"
+
def setUp(self):
"""
Create a resource and a xattr property store for it.
@@ -71,6 +74,7 @@
# of the EPERM failure.
self.assertEquals(error.response.code, FORBIDDEN)
+
def _missingTest(self, method):
# Remove access to the directory containing the file so that getting
# extended attributes from it fails with EPERM.
@@ -99,6 +103,7 @@
"""
self._forbiddenTest('get')
+
def test_getMissing(self):
"""
Test missing file.
@@ -118,6 +123,7 @@
# Make sure that the status is NOT FOUND.
self.assertEquals(error.response.code, NOT_FOUND)
+
def _makeValue(self, uid=None):
"""
Create and return any old WebDAVDocument for use by the get tests.
@@ -324,6 +330,7 @@
document = self._makeValue()
self.assertFalse(propertyStore.contains(document.root_element.qname()))
+
def test_list(self):
"""
L{xattrPropertyStore.list} returns a C{list} of property names
@@ -359,6 +366,7 @@
# of the EPERM failure.
self.assertEquals(error.response.code, FORBIDDEN)
+
def test_listMissing(self):
"""
Test missing file.
@@ -371,12 +379,13 @@
# Try to get a property from it - and fail.
self.assertEqual(propertyStore.list(), [])
+
def test_get_uids(self):
"""
L{xattrPropertyStore.get} accepts a L{WebDAVElement} and stores a
compressed XML document representing it in an extended attribute.
"""
-
+
for uid in (None, "123", "456",):
document = self._makeValue(uid)
self._setValue(document, document.toxml(), uid=uid)
@@ -391,19 +400,20 @@
L{xattrPropertyStore.set} accepts a L{WebDAVElement} and stores a
compressed XML document representing it in an extended attribute.
"""
-
+
for uid in (None, "123", "456",):
document = self._makeValue(uid)
self.propertyStore.set(document.root_element, uid=uid)
self.assertEquals(
decompress(self._getValue(document, uid)), document.root_element.toxml(pretty=False))
+
def test_delete_uids(self):
"""
L{xattrPropertyStore.set} accepts a L{WebDAVElement} and stores a
compressed XML document representing it in an extended attribute.
"""
-
+
for delete_uid in (None, "123", "456",):
for uid in (None, "123", "456",):
document = self._makeValue(uid)
@@ -416,7 +426,8 @@
document = self._makeValue(uid)
self.assertEquals(
decompress(self._getValue(document, uid)), document.root_element.toxml(pretty=False))
-
+
+
def test_contains_uids(self):
"""
L{xattrPropertyStore.contains} returns C{True} if the given property
@@ -430,6 +441,7 @@
self.assertTrue(
self.propertyStore.contains(document.root_element.qname(), uid=uid))
+
def test_list_uids(self):
"""
L{xattrPropertyStore.list} returns a C{list} of property names
@@ -465,4 +477,3 @@
(u'bar', u'baz', "456"),
(u'moo', u'mar456', "456"),
]))
-
Modified: CalendarServer/trunk/txweb2/dav/test/tworequest_client.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/test/tworequest_client.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/test/tworequest_client.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -33,8 +33,8 @@
send("Content-Length: 100\r\n\r\n")
send("X" * 100)
-#import time
-#time.sleep(5)
+# import time
+# time.sleep(5)
print >> sys.stderr, ">> Getting data"
data = ''
while len(data) < 299999:
Modified: CalendarServer/trunk/txweb2/dav/xattrprops.py
===================================================================
--- CalendarServer/trunk/txweb2/dav/xattrprops.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/dav/xattrprops.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -64,6 +64,8 @@
if hasattr(errno, "ENODATA"):
_ATTR_MISSING += (errno.ENODATA,)
+
+
class xattrPropertyStore (object):
"""
@@ -86,6 +88,7 @@
if sys.platform == "linux2":
deadPropertyXattrPrefix = "user."
+
def _encode(clazz, name, uid=None):
result = urllib.quote(encodeXMLName(*name), safe='{}:')
if uid:
@@ -93,6 +96,7 @@
r = clazz.deadPropertyXattrPrefix + result
return r
+
def _decode(clazz, name):
name = urllib.unquote(name[len(clazz.deadPropertyXattrPrefix):])
@@ -105,8 +109,8 @@
uid = None
else:
uid = name[:index1]
- propnamespace = name[index1+1:index2]
- propname = name[index2+1:]
+ propnamespace = name[index1 + 1:index2]
+ propname = name[index2 + 1:]
return (propnamespace, propname, uid)
@@ -134,7 +138,7 @@
@return: A L{WebDAVDocument} representing the value associated with the
given property.
"""
-
+
try:
data = self.attrs.get(self._encode(qname, uid))
except KeyError:
@@ -247,7 +251,7 @@
@return: C{True} if the property exists, C{False} otherwise.
"""
-
+
key = self._encode(qname, uid)
try:
self.attrs.get(key)
@@ -274,7 +278,7 @@
@return: A C{list} of property names as two-tuples of namespace URI and
local name.
"""
-
+
prefix = self.deadPropertyXattrPrefix
try:
attrs = iter(self.attrs)
@@ -292,7 +296,7 @@
if name.startswith(prefix)
]
if filterByUID:
- return [
+ return [
(namespace, name)
for namespace, name, propuid in results
if propuid == uid
Modified: CalendarServer/trunk/txweb2/error.py
===================================================================
--- CalendarServer/trunk/txweb2/error.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/error.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -186,10 +186,11 @@
def loadMessage(self, code):
- tag = XMLString(('<t:transparent xmlns:t="http://twistedmatrix.com/'
- 'ns/twisted.web.template/0.1">') +
- ERROR_MESSAGES.get(code, "") +
- '</t:transparent>').load()[0]
+ tag = XMLString(
+ ('<t:transparent xmlns:t="http://twistedmatrix.com/'
+ 'ns/twisted.web.template/0.1">') +
+ ERROR_MESSAGES.get(code, "") +
+ '</t:transparent>').load()[0]
return tag
@@ -234,7 +235,7 @@
subtype = 'error'
body = 'Error in default error handler:\n' + error[0].getTraceback()
- ctype = http_headers.MimeType('text', subtype, {'charset':'utf-8'})
+ ctype = http_headers.MimeType('text', subtype, {'charset': 'utf-8'})
response.headers.setHeader("content-type", ctype)
response.stream = stream.MemoryStream(body)
return response
@@ -242,5 +243,4 @@
defaultErrorHandler.handleErrors = True
-__all__ = ['defaultErrorHandler',]
-
+__all__ = ['defaultErrorHandler', ]
Modified: CalendarServer/trunk/txweb2/fileupload.py
===================================================================
--- CalendarServer/trunk/txweb2/fileupload.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/fileupload.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -34,9 +34,9 @@
from txweb2 import http_headers
from cStringIO import StringIO
-###################################
-##### Multipart MIME Reader #####
-###################################
+#
+# Multipart MIME Reader
+#
class MimeFormatError(Exception):
pass
@@ -52,21 +52,22 @@
def parseContentDispositionFormData(value):
match = cd_regexp.match(value)
if not match:
- # Error parsing.
+ # Error parsing.
raise ValueError("Unknown content-disposition format.")
- name=match.group(1)
- filename=match.group(2)
+ name = match.group(1)
+ filename = match.group(2)
return name, filename
-#@defer.deferredGenerator
+
+# @defer.deferredGenerator
def _readHeaders(stream):
"""Read the MIME headers. Assumes we've just finished reading in the
boundary string."""
ctype = fieldname = filename = None
headers = []
-
+
# Now read headers
while 1:
line = stream.readline(size=1024)
@@ -74,7 +75,7 @@
line = defer.waitForDeferred(line)
yield line
line = line.getResult()
- #print("GOT", line)
+ # print("GOT", line)
if not line.endswith('\r\n'):
if line == "":
raise MimeFormatError("Unexpected end of stream.")
@@ -84,21 +85,21 @@
line = line[:-2] # strip \r\n
if line == "":
break # End of headers
-
+
parts = line.split(':', 1)
if len(parts) != 2:
raise MimeFormatError("Header did not have a :")
name, value = parts
name = name.lower()
headers.append((name, value))
-
+
if name == "content-type":
ctype = http_headers.parseContentType(http_headers.tokenize((value,), foldCase=False))
elif name == "content-disposition":
fieldname, filename = parseContentDispositionFormData(value)
-
+
if ctype is None:
- ctype == http_headers.MimeType('application', 'octet-stream')
+ ctype = http_headers.MimeType('application', 'octet-stream')
if fieldname is None:
raise MimeFormatError('Content-disposition invalid or omitted.')
@@ -114,7 +115,7 @@
self.boundary = boundary
self.data = ''
self.deferred = defer.Deferred()
-
+
length = None # unknown
def read(self):
if self.stream is None:
@@ -128,6 +129,7 @@
return newdata.addCallbacks(self._gotRead, self._gotError)
return self._gotRead(newdata)
+
def _gotRead(self, newdata):
if not newdata:
raise MimeFormatError("Unexpected EOF")
@@ -136,10 +138,10 @@
data = self.data
boundary = self.boundary
off = data.find(boundary)
-
+
if off == -1:
# No full boundary, check for the first character
- off = data.rfind(boundary[0], max(0, len(data)-len(boundary)))
+ off = data.rfind(boundary[0], max(0, len(data) - len(boundary)))
if off != -1:
# We could have a partial boundary, store it for next time
self.data = data[off:]
@@ -148,10 +150,11 @@
self.data = ''
return data
else:
- self.stream.pushback(data[off+len(boundary):])
+ self.stream.pushback(data[off + len(boundary):])
self.stream = None
return data[:off]
+
def _gotError(self, err):
# Propogate error back to MultipartMimeStream also
if self.deferred is not None:
@@ -159,26 +162,30 @@
self.deferred = None
deferred.errback(err)
return err
-
+
+
def close(self):
# Assume error will be raised again and handled by MMS?
readAndDiscard(self).addErrback(lambda _: None)
-
+
+
+
class MultipartMimeStream(object):
implements(IStream)
def __init__(self, stream, boundary):
self.stream = BufferedStream(stream)
- self.boundary = "--"+boundary
+ self.boundary = "--" + boundary
self.first = True
-
+
+
def read(self):
"""
Return a deferred which will fire with a tuple of:
(fieldname, filename, ctype, dataStream)
or None when all done.
-
+
Format errors will be sent to the errback.
-
+
Returns None when all done.
IMPORTANT: you *must* exhaust dataStream returned by this call
@@ -193,8 +200,9 @@
d.addCallback(self._gotHeaders)
return d
+
def _readFirstBoundary(self):
- #print("_readFirstBoundary")
+ # print("_readFirstBoundary")
line = self.stream.readline(size=1024)
if isinstance(line, defer.Deferred):
line = defer.waitForDeferred(line)
@@ -202,20 +210,21 @@
line = line.getResult()
if line != self.boundary + '\r\n':
raise MimeFormatError("Extra data before first boundary: %r looking for: %r" % (line, self.boundary + '\r\n'))
-
- self.boundary = "\r\n"+self.boundary
+
+ self.boundary = "\r\n" + self.boundary
yield True
return
_readFirstBoundary = defer.deferredGenerator(_readFirstBoundary)
+
def _readBoundaryLine(self):
- #print("_readBoundaryLine")
+ # print("_readBoundaryLine")
line = self.stream.readline(size=1024)
if isinstance(line, defer.Deferred):
line = defer.waitForDeferred(line)
yield line
line = line.getResult()
-
+
if line == "--\r\n":
# THE END!
yield False
@@ -226,22 +235,25 @@
return
_readBoundaryLine = defer.deferredGenerator(_readBoundaryLine)
+
def _doReadHeaders(self, morefields):
- #print("_doReadHeaders", morefields)
+ # print("_doReadHeaders", morefields)
if not morefields:
return None
return _readHeaders(self.stream)
-
+
+
def _gotHeaders(self, headers):
if headers is None:
return None
bws = _BoundaryWatchingStream(self.stream, self.boundary)
self.deferred = bws.deferred
- ret=list(headers)
+ ret = list(headers)
ret.append(bws)
return tuple(ret)
+
def readIntoFile(stream, outFile, maxlen):
"""Read the stream into a file, but not if it's longer than maxlen.
Returns Deferred which will be triggered on finish.
@@ -249,29 +261,32 @@
curlen = [0]
def done(_):
return _
+
+
def write(data):
curlen[0] += len(data)
if curlen[0] > maxlen:
raise MimeFormatError("Maximum length of %d bytes exceeded." %
maxlen)
-
+
outFile.write(data)
return readStream(stream, write).addBoth(done)
-#@defer.deferredGenerator
+
+
+# @defer.deferredGenerator
def parseMultipartFormData(stream, boundary,
- maxMem=100*1024, maxFields=1024, maxSize=10*1024*1024):
+ maxMem=100 * 1024, maxFields=1024, maxSize=10 * 1024 * 1024):
# If the stream length is known to be too large upfront, abort immediately
-
+
if stream.length is not None and stream.length > maxSize:
- raise MimeFormatError("Maximum length of %d bytes exceeded." %
- maxSize)
-
+ raise MimeFormatError("Maximum length of %d bytes exceeded." % maxSize)
+
mms = MultipartMimeStream(stream, boundary)
numFields = 0
args = {}
files = {}
-
+
while 1:
datas = mms.read()
if isinstance(datas, defer.Deferred):
@@ -280,11 +295,11 @@
datas = datas.getResult()
if datas is None:
break
-
- numFields+=1
+
+ numFields += 1
if numFields == maxFields:
- raise MimeFormatError("Maximum number of fields %d exceeded"%maxFields)
-
+ raise MimeFormatError("Maximum number of fields %d exceeded" % maxFields)
+
# Parse data
fieldname, filename, ctype, stream = datas
if filename is None:
@@ -311,29 +326,32 @@
maxSize -= outfile.tell()
outfile.seek(0)
files.setdefault(fieldname, []).append((filename, ctype, outfile))
-
-
+
+
yield args, files
return
parseMultipartFormData = defer.deferredGenerator(parseMultipartFormData)
-###################################
-##### x-www-urlencoded reader #####
-###################################
+#
+# x-www-urlencoded reader
+#
-def parse_urlencoded_stream(input, maxMem=100*1024,
- keep_blank_values=False, strict_parsing=False):
+def parse_urlencoded_stream(
+ input, maxMem=100 * 1024,
+ keep_blank_values=False, strict_parsing=False
+):
+
lastdata = ''
- still_going=1
-
+ still_going = 1
+
while still_going:
try:
yield input.wait
data = input.next()
except StopIteration:
pairs = [lastdata]
- still_going=0
+ still_going = 0
else:
maxMem -= len(data)
if maxMem < 0:
@@ -341,13 +359,13 @@
maxMem)
pairs = str(data).split('&')
pairs[0] = lastdata + pairs[0]
- lastdata=pairs.pop()
-
+ lastdata = pairs.pop()
+
for name_value in pairs:
nv = name_value.split('=', 1)
if len(nv) != 2:
if strict_parsing:
- raise MimeFormatError("bad query field: %s") % `name_value`
+ raise MimeFormatError("bad query field: %s") % repr(name_value)
continue
if len(nv[1]) or keep_blank_values:
name = urllib.unquote(nv[0].replace('+', ' '))
@@ -355,13 +373,13 @@
yield name, value
parse_urlencoded_stream = generatorToStream(parse_urlencoded_stream)
-def parse_urlencoded(stream, maxMem=100*1024, maxFields=1024,
+def parse_urlencoded(stream, maxMem=100 * 1024, maxFields=1024,
keep_blank_values=False, strict_parsing=False):
d = {}
numFields = 0
- s=parse_urlencoded_stream(stream, maxMem, keep_blank_values, strict_parsing)
-
+ s = parse_urlencoded_stream(stream, maxMem, keep_blank_values, strict_parsing)
+
while 1:
datas = s.read()
if isinstance(datas, defer.Deferred):
@@ -371,11 +389,11 @@
if datas is None:
break
name, value = datas
-
+
numFields += 1
if numFields == maxFields:
- raise MimeFormatError("Maximum number of fields %d exceeded"%maxFields)
-
+ raise MimeFormatError("Maximum number of fields %d exceeded" % maxFields)
+
if name in d:
d[name].append(value)
else:
Modified: CalendarServer/trunk/txweb2/filter/gzip.py
===================================================================
--- CalendarServer/trunk/txweb2/filter/gzip.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/filter/gzip.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -15,11 +15,11 @@
# uh.. stuff
header += '\002\377'
yield header
-
+
compress = zlib.compressobj(compressLevel, zlib.DEFLATED, -zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0)
_compress = compress.compress
_crc32 = zlib.crc32
-
+
yield input.wait
for buf in input:
if len(buf) != 0:
@@ -27,16 +27,16 @@
size += len(buf)
yield _compress(buf)
yield input.wait
-
+
yield compress.flush()
yield struct.pack('<LL', crc & 0xFFFFFFFFL, size & 0xFFFFFFFFL)
-gzipStream=stream.generatorToStream(gzipStream)
+gzipStream = stream.generatorToStream(gzipStream)
def deflateStream(input, compressLevel=6):
# NOTE: this produces RFC-conformant but some-browser-incompatible output.
# The RFC says that you're supposed to output zlib-format data, but many
# browsers expect raw deflate output. Luckily all those browsers support
- # gzip, also, so they won't even see deflate output.
+ # gzip, also, so they won't even see deflate output.
compress = zlib.compressobj(compressLevel, zlib.DEFLATED, zlib.MAX_WBITS, zlib.DEF_MEM_LEVEL, 0)
_compress = compress.compress
yield input.wait
@@ -46,24 +46,24 @@
yield input.wait
yield compress.flush()
-deflateStream=stream.generatorToStream(deflateStream)
+deflateStream = stream.generatorToStream(deflateStream)
def gzipfilter(request, response):
if response.stream is None or response.headers.getHeader('content-encoding'):
# Empty stream, or already compressed.
return response
-
+
# FIXME: make this a more flexible matching scheme
mimetype = response.headers.getHeader('content-type')
if not mimetype or mimetype.mediaType != 'text':
return response
-
+
# Make sure to note we're going to return different content depending on
# the accept-encoding header.
vary = response.headers.getHeader('vary', [])
if 'accept-encoding' not in vary:
- response.headers.setHeader('vary', vary+['accept-encoding'])
-
+ response.headers.setHeader('vary', vary + ['accept-encoding'])
+
ae = request.headers.getHeader('accept-encoding', {})
# Always prefer gzip over deflate no matter what their q-values are.
if ae.get('gzip', 0):
@@ -72,7 +72,7 @@
elif ae.get('deflate', 0):
response.stream = deflateStream(response.stream)
response.headers.setHeader('content-encoding', ['deflate'])
-
+
return response
__all__ = ['gzipfilter']
Modified: CalendarServer/trunk/txweb2/filter/location.py
===================================================================
--- CalendarServer/trunk/txweb2/filter/location.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/filter/location.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -16,13 +16,13 @@
# Check to see whether we have an absolute URI or not.
# If not, have the request turn it into an absolute URI.
#
- (scheme, host, path, params, querystring, fragment) = urlparse.urlparse(location)
+ (scheme, _ignore_host, _ignore_path, _ignore_params, _ignore_querystring, _ignore_fragment) = urlparse.urlparse(location)
if scheme == "":
uri = request.unparseURL(path=location)
else:
uri = location
-
+
response.headers.setHeader("location", uri)
return response
Modified: CalendarServer/trunk/txweb2/filter/range.py
===================================================================
--- CalendarServer/trunk/txweb2/filter/range.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/filter/range.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -1,6 +1,7 @@
# -*- test-case-name: txweb2.test.test_stream -*-
-import time, os
+import os
+import time
from txweb2 import http, http_headers, responsecode, stream
@@ -9,26 +10,30 @@
class UnsatisfiableRangeRequest(Exception):
pass
+
+
def canonicalizeRange((start, end), size):
"""Return canonicalized (start, end) or raises UnsatisfiableRangeRequest
exception.
NOTE: end is the last byte *inclusive*, which is not the usual convention
in python! Be very careful! A range of 0,1 should return 2 bytes."""
-
+
# handle "-500" ranges
if start is None:
- start = max(0, size-end)
+ start = max(0, size - end)
end = None
-
+
if end is None or end >= size:
end = size - 1
-
+
if start >= size:
raise UnsatisfiableRangeRequest
-
- return start,end
+ return start, end
+
+
+
def makeUnsatisfiable(request, oldresponse):
if request.headers.hasHeader('if-range'):
return oldresponse # Return resource instead of error
@@ -36,15 +41,19 @@
response.headers.setHeader("content-range", ('bytes', None, None, oldresponse.stream.length))
return response
+
+
def makeSegment(inputStream, lastOffset, start, end):
offset = start - lastOffset
length = end + 1 - start
-
+
if offset != 0:
before, inputStream = inputStream.split(offset)
before.close()
return inputStream.split(length)
+
+
def rangefilter(request, oldresponse):
if oldresponse.stream is None:
return oldresponse
@@ -53,24 +62,26 @@
# Does not deal with indeterminate length outputs
return oldresponse
- oldresponse.headers.setHeader('accept-ranges',('bytes',))
-
+ oldresponse.headers.setHeader('accept-ranges', ('bytes',))
+
rangespec = request.headers.getHeader('range')
-
+
# If we've got a range header and the If-Range header check passes, and
# the range type is bytes, do a partial response.
- if (rangespec is not None and http.checkIfRange(request, oldresponse) and
- rangespec[0] == 'bytes'):
+ if (
+ rangespec is not None and http.checkIfRange(request, oldresponse) and
+ rangespec[0] == 'bytes'
+ ):
# If it's a single range, return a simple response
if len(rangespec[1]) == 1:
try:
- start,end = canonicalizeRange(rangespec[1][0], size)
+ start, end = canonicalizeRange(rangespec[1][0], size)
except UnsatisfiableRangeRequest:
return makeUnsatisfiable(request, oldresponse)
response = http.Response(responsecode.PARTIAL_CONTENT, oldresponse.headers)
- response.headers.setHeader('content-range',('bytes',start, end, size))
-
+ response.headers.setHeader('content-range', ('bytes', start, end, size))
+
content, after = makeSegment(oldresponse.stream, 0, start, end)
after.close()
response.stream = content
@@ -81,40 +92,44 @@
offsetList = []
for arange in rangespec[1]:
try:
- start,end = canonicalizeRange(arange, size)
+ start, end = canonicalizeRange(arange, size)
except UnsatisfiableRangeRequest:
continue
if start <= lastOffset:
# Stupid client asking for out-of-order or overlapping ranges, PUNT!
return oldresponse
- offsetList.append((start,end))
+ offsetList.append((start, end))
lastOffset = end
if not offsetList:
return makeUnsatisfiable(request, oldresponse)
-
+
content_type = oldresponse.headers.getRawHeaders('content-type')
- boundary = "%x%x" % (int(time.time()*1000000), os.getpid())
+ boundary = "%x%x" % (int(time.time() * 1000000), os.getpid())
response = http.Response(responsecode.PARTIAL_CONTENT, oldresponse.headers)
-
- response.headers.setHeader('content-type',
+
+ response.headers.setHeader(
+ 'content-type',
http_headers.MimeType('multipart', 'byteranges',
- [('boundary', boundary)]))
+ [('boundary', boundary)])
+ )
response.stream = out = stream.CompoundStream()
-
-
+
+
lastOffset = 0
origStream = oldresponse.stream
headerString = "\r\n--%s" % boundary
if len(content_type) == 1:
- headerString+='\r\nContent-Type: %s' % content_type[0]
- headerString+="\r\nContent-Range: %s\r\n\r\n"
-
- for start,end in offsetList:
- out.addStream(headerString %
- http_headers.generateContentRange(('bytes', start, end, size)))
+ headerString += '\r\nContent-Type: %s' % content_type[0]
+ headerString += "\r\nContent-Range: %s\r\n\r\n"
+ for start, end in offsetList:
+ out.addStream(
+ headerString %
+ http_headers.generateContentRange(('bytes', start, end, size))
+ )
+
content, origStream = makeSegment(origStream, lastOffset, start, end)
lastOffset = end + 1
out.addStream(content)
@@ -124,5 +139,5 @@
else:
return oldresponse
-
+
__all__ = ['rangefilter']
Modified: CalendarServer/trunk/txweb2/http.py
===================================================================
--- CalendarServer/trunk/txweb2/http.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/http.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -236,7 +236,8 @@
'date', 'etag', 'content-location', 'expires',
'cache-control', 'vary',
# Others:
- 'server', 'proxy-authenticate', 'www-authenticate', 'warning'):
+ 'server', 'proxy-authenticate', 'www-authenticate', 'warning'
+ ):
value = oldResponse.headers.getRawHeaders(header)
if value is not None:
headers.setRawHeaders(header, value)
Modified: CalendarServer/trunk/txweb2/http_headers.py
===================================================================
--- CalendarServer/trunk/txweb2/http_headers.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/http_headers.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -243,7 +243,7 @@
-##### HTTP tokenizer
+# HTTP tokenizer
class Token(str):
__slots__ = []
tokens = {}
@@ -385,7 +385,7 @@
-##### parser utilities:
+# parser utilities:
def checkSingleToken(tokens):
if len(tokens) != 1:
raise ValueError("Expected single token, not %s." % (tokens,))
@@ -430,7 +430,7 @@
-##### Generation utilities
+# Generation utilities
def quoteString(s):
"""
Quote a string according to the rules for the I{quoted-string} production
@@ -589,7 +589,7 @@
-##### Specific header parsers.
+# Specific header parsers.
def parseAccept(field):
atype, args = parseArgs(field)
@@ -863,7 +863,7 @@
-#### Header generators
+# Header generators
def generateAccept(accept):
mimeType, q = accept
@@ -1126,7 +1126,7 @@
-#### Cookies. Blech!
+# Cookies. Blech!
class Cookie(object):
# __slots__ = ['name', 'value', 'path', 'domain', 'ports', 'expires', 'discard', 'secure', 'comment', 'commenturl', 'version']
@@ -1501,7 +1501,7 @@
# MS definition uses lower case
return "t" if brief else "f"
-##### Random stuff that looks useful.
+# Random stuff that looks useful.
# def sortMimeQuality(s):
# def sorter(item1, item2):
# if item1[0] == '*':
@@ -1716,24 +1716,24 @@
'Cache-Control': (tokenize, listParser(parseCacheControl), dict),
'Connection': (tokenize, filterTokens),
'Date': (last, parseDateTime),
-# 'Pragma': tokenize
-# 'Trailer': tokenize
+ # 'Pragma': tokenize
+ # 'Trailer': tokenize
'Transfer-Encoding': (tokenize, filterTokens),
-# 'Upgrade': tokenize
-# 'Via': tokenize,stripComment
-# 'Warning': tokenize
+ # 'Upgrade': tokenize
+ # 'Via': tokenize,stripComment
+ # 'Warning': tokenize
}
generator_general_headers = {
'Cache-Control': (iteritems, listGenerator(generateCacheControl), singleHeader),
'Connection': (generateList, singleHeader),
'Date': (generateDateTime, singleHeader),
-# 'Pragma':
-# 'Trailer':
+ # 'Pragma':
+ # 'Trailer':
'Transfer-Encoding': (generateList, singleHeader),
-# 'Upgrade':
-# 'Via':
-# 'Warning':
+ # 'Upgrade':
+ # 'Via':
+ # 'Warning':
}
parser_request_headers = {
@@ -1753,7 +1753,7 @@
'If-Unmodified-Since': (last, parseDateTime),
'Max-Forwards': (last, int),
'Prefer': (tokenize, listParser(parsePrefer), list),
-# 'Proxy-Authorization': str, # what is "credentials"
+ # 'Proxy-Authorization': str, # what is "credentials"
'Range': (tokenize, parseRange),
'Referer': (last, str), # TODO: URI object?
'TE': (tokenize, listParser(parseAcceptQvalue), dict),
@@ -1777,7 +1777,7 @@
'If-Unmodified-Since': (generateDateTime, singleHeader),
'Max-Forwards': (str, singleHeader),
'Prefer': (listGenerator(generatePrefer), singleHeader),
-# 'Proxy-Authorization': str, # what is "credentials"
+ # 'Proxy-Authorization': str, # what is "credentials"
'Range': (generateRange, singleHeader),
'Referer': (str, singleHeader),
'TE': (iteritems, listGenerator(generateAcceptQvalue), singleHeader),
@@ -1789,7 +1789,7 @@
'Age': (last, int),
'ETag': (tokenize, ETag.parse),
'Location': (last,), # TODO: URI object?
-# 'Proxy-Authenticate'
+ # 'Proxy-Authenticate'
'Retry-After': (last, parseRetryAfter),
'Server': (last,),
'Set-Cookie': (parseSetCookie,),
@@ -1804,7 +1804,7 @@
'Age': (str, singleHeader),
'ETag': (ETag.generate, singleHeader),
'Location': (str, singleHeader),
-# 'Proxy-Authenticate'
+ # 'Proxy-Authenticate'
'Retry-After': (generateRetryAfter, singleHeader),
'Server': (str, singleHeader),
'Set-Cookie': (generateSetCookie,),
@@ -1825,7 +1825,7 @@
'Content-Type': (lambda hdr: tokenize(hdr, foldCase=False), parseContentType),
'Expires': (last, parseExpires),
'Last-Modified': (last, parseDateTime),
- }
+}
generator_entity_headers = {
'Allow': (generateList, singleHeader),
@@ -1839,18 +1839,18 @@
'Content-Type': (generateContentType, singleHeader),
'Expires': (generateDateTime, singleHeader),
'Last-Modified': (generateDateTime, singleHeader),
- }
+}
parser_dav_headers = {
'Brief' : (last, parseBrief),
'DAV' : (tokenize, list),
'Depth' : (last, parseDepth),
'Destination' : (last,), # TODO: URI object?
- # 'If' : (),
- # 'Lock-Token' : (),
+ # 'If' : (),
+ # 'Lock-Token' : (),
'Overwrite' : (last, parseOverWrite),
- # 'Status-URI' : (),
- # 'Timeout' : (),
+ # 'Status-URI' : (),
+ # 'Timeout' : (),
}
generator_dav_headers = {
@@ -1858,11 +1858,11 @@
'DAV' : (generateList, singleHeader),
'Depth' : (singleHeader),
'Destination' : (singleHeader),
- # 'If' : (),
- # 'Lock-Token' : (),
+ # 'If' : (),
+ # 'Lock-Token' : (),
'Overwrite' : (),
- # 'Status-URI' : (),
- # 'Timeout' : (),
+ # 'Status-URI' : (),
+ # 'Timeout' : (),
}
DefaultHTTPHandler.updateParsers(parser_general_headers)
Modified: CalendarServer/trunk/txweb2/iweb.py
===================================================================
--- CalendarServer/trunk/txweb2/iweb.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/iweb.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -46,11 +46,11 @@
@return: A 2-tuple of (resource, remaining-path-segments),
or a deferred which will fire the above.
-
+
Causes the object publishing machinery to continue on
with specified resource and segments, calling the
appropriate method on the specified resource.
-
+
If you return (self, L{server.StopTraversal}), this
instructs web2 to immediately stop the lookup stage,
and switch to the rendering stage, leaving the
@@ -74,7 +74,7 @@
result = super(SpecialAdaptInterfaceClass, self).__call__(other, alternate)
if result is not alternate:
return result
-
+
result = IOldNevowResource(other, alternate)
if result is not alternate:
result = IResource(result)
@@ -106,8 +106,10 @@
string instead of a response object.
"""
+
+
class ICanHandleException(Interface):
-
+
# Shared interface with inevow.ICanHandleException
def renderHTTP_exception(request, failure):
"""
@@ -120,6 +122,7 @@
not replacing the page."""
+
# http.py interfaces
class IResponse(Interface):
"""
@@ -129,6 +132,8 @@
headers = Attribute("A http_headers.Headers instance of headers to send")
stream = Attribute("A stream.IByteStream of outgoing data, or else None.")
+
+
class IRequest(Interface):
"""
I'm a request for a web resource.
@@ -137,21 +142,22 @@
method = Attribute("The HTTP method from the request line, e.g. GET")
uri = Attribute("The raw URI from the request line. May or may not include host.")
clientproto = Attribute("Protocol from the request line, e.g. HTTP/1.1")
-
+
headers = Attribute("A http_headers.Headers instance of incoming headers.")
stream = Attribute("A stream.IByteStream of incoming data.")
-
+
def writeResponse(response):
"""
Write an IResponse object to the client.
"""
-
+
chanRequest = Attribute("The ChannelRequest. I wonder if this is public really?")
-from twisted.web.iweb import IRequest as IOldRequest
+# from twisted.web.iweb import IRequest as IOldRequest
+
class IChanRequestCallbacks(Interface):
"""
The bits that are required of a Request for interfacing with a
@@ -161,7 +167,7 @@
def __init__(chanRequest, command, path, version, contentLength, inHeaders):
"""
Create a new Request object.
-
+
@param chanRequest: the IChanRequest object creating this request
@param command: the HTTP command e.g. GET
@param path: the HTTP path e.g. /foo/bar.html
@@ -175,38 +181,39 @@
to return a response. L{handleContentComplete} may or may not
have been called already.
"""
-
+
def handleContentChunk(data):
"""
Called when a piece of incoming data has been received.
"""
-
+
def handleContentComplete():
"""
Called when the incoming data stream is finished.
"""
-
+
def connectionLost(reason):
"""
Called if the connection was lost.
"""
-
-
+
+
+
class IChanRequest(Interface):
-
+
def writeIntermediateResponse(code, headers=None):
"""
Write a non-terminating response.
-
+
Intermediate responses cannot contain data.
If the channel does not support intermediate responses, do nothing.
-
+
@param code: The response code. Should be in the 1xx range.
@type code: int
@param headers: the headers to send in the response
@type headers: C{twisted.web.http_headers.Headers}
"""
-
+
def writeHeaders(code, headers):
"""
Write a final response.
@@ -218,7 +225,7 @@
necessary for the protocol.
@type headers: C{twisted.web.http_headers.Headers}
"""
-
+
def write(data):
"""
Write some data.
@@ -226,16 +233,16 @@
@param data: the data bytes
@type data: str
"""
-
+
def finish():
"""
Finish the request, and clean up the connection if necessary.
"""
-
+
def abortConnection():
"""
Forcibly abort the connection without cleanly closing.
-
+
Use if, for example, you can't write all the data you promised.
"""
@@ -243,7 +250,7 @@
"""
Register a producer with the standard API.
"""
-
+
def unregisterProducer():
"""
Unregister a producer.
@@ -270,6 +277,7 @@
persistent = Attribute("""Whether this request supports HTTP connection persistence. May be set to False. Should not be set to other values.""")
+
class ISite(Interface):
pass
Modified: CalendarServer/trunk/txweb2/log.py
===================================================================
--- CalendarServer/trunk/txweb2/log.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/log.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -39,65 +39,73 @@
class _LogByteCounter(object):
implements(stream.IByteStream)
-
+
def __init__(self, stream, done):
- self.stream=stream
- self.done=done
- self.len=0
-
- length=property(lambda self: self.stream.length)
-
+ self.stream = stream
+ self.done = done
+ self.len = 0
+
+ length = property(lambda self: self.stream.length)
+
def _callback(self, data):
if data is None:
if self.done:
- done=self.done; self.done=None
+ done = self.done
+ self.done = None
done(True, self.len)
else:
self.len += len(data)
return data
-
+
+
def read(self):
data = self.stream.read()
if isinstance(data, defer.Deferred):
return data.addCallback(self._callback)
return self._callback(data)
-
+
+
def close(self):
if self.done:
- done=self.done; self.done=None
+ done = self.done
+ self.done = None
done(False, self.len)
self.stream.close()
-
+
+
class ILogInfo(Interface):
"""Auxilliary information about the response useful for logging."""
-
- bytesSent=Attribute("Number of bytes sent.")
- responseCompleted=Attribute("Whether or not the response was completed.")
- secondsTaken=Attribute("Number of seconds taken to serve the request.")
- startTime=Attribute("Time at which the request started")
-
+ bytesSent = Attribute("Number of bytes sent.")
+ responseCompleted = Attribute("Whether or not the response was completed.")
+ secondsTaken = Attribute("Number of seconds taken to serve the request.")
+ startTime = Attribute("Time at which the request started")
+
+
+
class LogInfo(object):
implements(ILogInfo)
- responseCompleted=None
- secondsTaken=None
- bytesSent=None
- startTime=None
+ responseCompleted = None
+ secondsTaken = None
+ bytesSent = None
+ startTime = None
-
+
+
def logFilter(request, response, startTime=None):
if startTime is None:
startTime = time.time()
-
+
+
def _log(success, length):
- loginfo=LogInfo()
- loginfo.bytesSent=length
- loginfo.responseCompleted=success
- loginfo.secondsTaken=time.time()-startTime
+ loginfo = LogInfo()
+ loginfo.bytesSent = length
+ loginfo.responseCompleted = success
+ loginfo.secondsTaken = time.time() - startTime
- if length:
+ if length:
request.timeStamp("t-resp-wr")
log.info(interface=iweb.IRequest, request=request, response=response,
loginfo=loginfo)
@@ -106,7 +114,7 @@
request.timeStamp("t-resp-gen")
if response.stream:
- response.stream=_LogByteCounter(response.stream, _log)
+ response.stream = _LogByteCounter(response.stream, _log)
else:
_log(True, 0)
@@ -137,6 +145,7 @@
def logMessage(self, message):
raise NotImplemented, 'You must provide an implementation.'
+
def computeTimezoneForLog(self, tz):
if tz > 0:
neg = 1
@@ -156,7 +165,7 @@
def logDateString(self, when):
logtime = time.localtime(when)
Y, M, D, h, m, s = logtime[:6]
-
+
if not time.daylight:
tz = self.tzForLog
if tz is None:
@@ -171,6 +180,7 @@
return '%02d/%s/%02d:%02d:%02d:%02d %s' % (
D, monthname[M], Y, h, m, s, tz)
+
def emit(self, eventDict):
if eventDict.get('interface') is not iweb.IRequest:
return
@@ -178,11 +188,11 @@
request = eventDict['request']
response = eventDict['response']
loginfo = eventDict['loginfo']
- firstLine = '%s %s HTTP/%s' %(
+ firstLine = '%s %s HTTP/%s' % (
request.method,
request.uri,
'.'.join([str(x) for x in request.clientproto]))
-
+
self.logMessage(
'%s - %s [%s] "%s" %s %d "%s" "%s"' % (
request.remoteAddr.host,
@@ -195,38 +205,45 @@
loginfo.bytesSent,
request.headers.getHeader('referer', '-'),
request.headers.getHeader('user-agent', '-')
- )
)
+ )
+
def start(self):
"""Start observing log events."""
# Use the root publisher to bypass log level filtering
log.publisher.addObserver(self.emit, filtered=False)
+
def stop(self):
"""Stop observing log events."""
log.publisher.removeObserver(self.emit)
+
class FileAccessLoggingObserver(BaseCommonAccessLoggingObserver):
"""I log requests to a single logfile
"""
-
+
def __init__(self, logpath):
self.logpath = logpath
-
+
+
def logMessage(self, message):
self.f.write(message + '\n')
+
def start(self):
super(FileAccessLoggingObserver, self).start()
self.f = open(self.logpath, 'a', 1)
-
+
+
def stop(self):
super(FileAccessLoggingObserver, self).stop()
self.f.close()
-
+
+
class DefaultCommonAccessLoggingObserver(BaseCommonAccessLoggingObserver):
"""Log requests to default twisted logfile."""
def logMessage(self, message):
Modified: CalendarServer/trunk/txweb2/resource.py
===================================================================
--- CalendarServer/trunk/txweb2/resource.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/resource.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -55,6 +55,7 @@
)
return self._allowed_methods
+
def checkPreconditions(self, request):
"""
Checks all preconditions imposed by this resource upon a request made
@@ -148,6 +149,7 @@
# """
# return server.doTrace(request)
+
def http_HEAD(self, request):
"""
Respond to a HEAD request.
@@ -156,6 +158,7 @@
"""
return self.http_GET(request)
+
def http_GET(self, request):
"""
Respond to a GET request.
@@ -171,6 +174,7 @@
return self.render(request)
+
def render(self, request):
"""
Subclasses should implement this method to do page rendering.
@@ -216,6 +220,7 @@
return None, []
+
def child_(self, request):
"""
This method locates a child with a trailing C{"/"} in the URL.
@@ -225,6 +230,7 @@
return self
return None
+
def getChild(self, path):
"""
Get a static child - when registered using L{putChild}.
@@ -253,6 +259,7 @@
"""
setattr(self, 'child_%s' % (path,), child)
+
def http_GET(self, request):
if self.addSlash and request.prepath[-1] != '':
# If this is a directory-ish resource...
@@ -320,6 +327,7 @@
self._args = args
self._kwargs = kwargs
+
def renderHTTP(self, request):
return http.RedirectResponse(
request.unparseURL(*self._args, **self._kwargs)
@@ -338,6 +346,7 @@
def __init__(self, resource):
self.resource = resource
+
def hook(self, request):
"""
Override this method in order to do something before passing control on
@@ -348,18 +357,21 @@
"""
raise NotImplementedError()
+
def locateChild(self, request, segments):
x = self.hook(request)
if x is not None:
return x.addCallback(lambda data: (self.resource, segments))
return self.resource, segments
+
def renderHTTP(self, request):
x = self.hook(request)
if x is not None:
return x.addCallback(lambda data: self.resource)
return self.resource
+
def getChild(self, name):
return self.resource.getChild(name)
Modified: CalendarServer/trunk/txweb2/responsecode.py
===================================================================
--- CalendarServer/trunk/txweb2/responsecode.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/responsecode.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -23,57 +23,57 @@
#
##
-CONTINUE = 100
-SWITCHING = 101
+CONTINUE = 100
+SWITCHING = 101
-OK = 200
-CREATED = 201
-ACCEPTED = 202
-NON_AUTHORITATIVE_INFORMATION = 203
-NO_CONTENT = 204
-RESET_CONTENT = 205
-PARTIAL_CONTENT = 206
-MULTI_STATUS = 207
+OK = 200
+CREATED = 201
+ACCEPTED = 202
+NON_AUTHORITATIVE_INFORMATION = 203
+NO_CONTENT = 204
+RESET_CONTENT = 205
+PARTIAL_CONTENT = 206
+MULTI_STATUS = 207
-MULTIPLE_CHOICE = 300
-MOVED_PERMANENTLY = 301
-FOUND = 302
-SEE_OTHER = 303
-NOT_MODIFIED = 304
-USE_PROXY = 305
-TEMPORARY_REDIRECT = 307
+MULTIPLE_CHOICE = 300
+MOVED_PERMANENTLY = 301
+FOUND = 302
+SEE_OTHER = 303
+NOT_MODIFIED = 304
+USE_PROXY = 305
+TEMPORARY_REDIRECT = 307
-BAD_REQUEST = 400
-UNAUTHORIZED = 401
-PAYMENT_REQUIRED = 402
-FORBIDDEN = 403
-NOT_FOUND = 404
-NOT_ALLOWED = 405
-NOT_ACCEPTABLE = 406
-PROXY_AUTH_REQUIRED = 407
-REQUEST_TIMEOUT = 408
-CONFLICT = 409
-GONE = 410
-LENGTH_REQUIRED = 411
-PRECONDITION_FAILED = 412
-REQUEST_ENTITY_TOO_LARGE = 413
-REQUEST_URI_TOO_LONG = 414
-UNSUPPORTED_MEDIA_TYPE = 415
+BAD_REQUEST = 400
+UNAUTHORIZED = 401
+PAYMENT_REQUIRED = 402
+FORBIDDEN = 403
+NOT_FOUND = 404
+NOT_ALLOWED = 405
+NOT_ACCEPTABLE = 406
+PROXY_AUTH_REQUIRED = 407
+REQUEST_TIMEOUT = 408
+CONFLICT = 409
+GONE = 410
+LENGTH_REQUIRED = 411
+PRECONDITION_FAILED = 412
+REQUEST_ENTITY_TOO_LARGE = 413
+REQUEST_URI_TOO_LONG = 414
+UNSUPPORTED_MEDIA_TYPE = 415
REQUESTED_RANGE_NOT_SATISFIABLE = 416
-EXPECTATION_FAILED = 417
-UNPROCESSABLE_ENTITY = 422 # RFC 2518
-LOCKED = 423 # RFC 2518
-FAILED_DEPENDENCY = 424 # RFC 2518
+EXPECTATION_FAILED = 417
+UNPROCESSABLE_ENTITY = 422 # RFC 2518
+LOCKED = 423 # RFC 2518
+FAILED_DEPENDENCY = 424 # RFC 2518
-INTERNAL_SERVER_ERROR = 500
-NOT_IMPLEMENTED = 501
-BAD_GATEWAY = 502
-SERVICE_UNAVAILABLE = 503
-GATEWAY_TIMEOUT = 504
-HTTP_VERSION_NOT_SUPPORTED = 505
-LOOP_DETECTED = 506
-INSUFFICIENT_STORAGE_SPACE = 507
-NOT_EXTENDED = 510
+INTERNAL_SERVER_ERROR = 500
+NOT_IMPLEMENTED = 501
+BAD_GATEWAY = 502
+SERVICE_UNAVAILABLE = 503
+GATEWAY_TIMEOUT = 504
+HTTP_VERSION_NOT_SUPPORTED = 505
+LOOP_DETECTED = 506
+INSUFFICIENT_STORAGE_SPACE = 507
+NOT_EXTENDED = 510
RESPONSES = {
# 100
@@ -133,6 +133,6 @@
LOOP_DETECTED: "Loop In Linked or Bound Resource",
INSUFFICIENT_STORAGE_SPACE: "Insufficient Storage Space",
NOT_EXTENDED: "Not Extended"
- }
+}
# No __all__ necessary -- everything is exported
Modified: CalendarServer/trunk/txweb2/server.py
===================================================================
--- CalendarServer/trunk/txweb2/server.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/server.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -29,7 +29,9 @@
"""
from __future__ import print_function
-import cgi, time, urlparse
+import cgi
+import time
+import urlparse
from urllib import quote, unquote
from urlparse import urlsplit
import weakref
@@ -67,12 +69,14 @@
http.checkPreconditions(request, response)
return response
+
+
def doTrace(request):
request = iweb.IRequest(request)
txt = "%s %s HTTP/%d.%d\r\n" % (request.method, request.uri,
request.clientproto[0], request.clientproto[1])
- l=[]
+ l = []
for name, valuelist in request.headers.getAllRawHeaders():
for value in valuelist:
l.append("%s: %s\r\n" % (name, value))
@@ -84,8 +88,9 @@
txt)
-def parsePOSTData(request, maxMem=100*1024, maxFields=1024,
- maxSize=10*1024*1024):
+
+def parsePOSTData(request, maxMem=100 * 1024, maxFields=1024,
+ maxSize=10 * 1024 * 1024):
"""
Parse data of a POST request.
@@ -110,22 +115,27 @@
if ctype is None:
return defer.succeed(None)
+
def updateArgs(data):
args = data
request.args.update(args)
+
def updateArgsAndFiles(data):
args, files = data
request.args.update(args)
request.files.update(files)
+
def error(f):
f.trap(fileupload.MimeFormatError)
raise http.HTTPError(
http.StatusResponse(responsecode.BAD_REQUEST, str(f.value)))
- if (ctype.mediaType == 'application'
- and ctype.mediaSubtype == 'x-www-form-urlencoded'):
+ if (
+ ctype.mediaType == 'application'
+ and ctype.mediaSubtype == 'x-www-form-urlencoded'
+ ):
d = fileupload.parse_urlencoded(request.stream)
d.addCallbacks(updateArgs, error)
return d
@@ -134,9 +144,9 @@
boundary = ctype.params.get('boundary')
if boundary is None:
return defer.fail(http.HTTPError(
- http.StatusResponse(
- responsecode.BAD_REQUEST,
- "Boundary not specified in Content-Type.")))
+ http.StatusResponse(
+ responsecode.BAD_REQUEST,
+ "Boundary not specified in Content-Type.")))
d = fileupload.parseMultipartFormData(request.stream, boundary,
maxMem, maxFields, maxSize)
d.addCallbacks(updateArgsAndFiles, error)
@@ -149,6 +159,7 @@
ctype.mediaType, ctype.mediaSubtype))))
+
class StopTraversal(object):
"""
Indicates to Request._handleSegment that it should stop handling
@@ -157,6 +168,7 @@
pass
+
class Request(http.Request):
"""
vars:
@@ -195,10 +207,10 @@
self.timeStamps = [("t", time.time(),)]
- if kw.has_key('site'):
+ if 'site' in kw:
self.site = kw['site']
del kw['site']
- if kw.has_key('prepathuri'):
+ if 'prepathuri' in kw:
self._initialprepath = kw['prepathuri']
del kw['prepathuri']
@@ -215,9 +227,11 @@
except AttributeError:
self.serverInstance = "Unknown"
+
def timeStamp(self, tag):
self.timeStamps.append((tag, time.time(),))
+
def addResponseFilter(self, filter, atEnd=False, onlyOnce=False):
"""
Add a response filter to this request.
@@ -236,6 +250,7 @@
else:
self.responseFilters.insert(0, filter)
+
def unparseURL(self, scheme=None, host=None, port=None,
path=None, params=None, querystring=None, fragment=None):
"""Turn the request path into a url string. For any pieces of
@@ -243,13 +258,20 @@
request. The arguments have the same meaning as the same named
attributes of Request."""
- if scheme is None: scheme = self.scheme
- if host is None: host = self.host
- if port is None: port = self.port
- if path is None: path = self.path
- if params is None: params = self.params
- if querystring is None: querystring = self.querystring
- if fragment is None: fragment = ''
+ if scheme is None:
+ scheme = self.scheme
+ if host is None:
+ host = self.host
+ if port is None:
+ port = self.port
+ if path is None:
+ path = self.path
+ if params is None:
+ params = self.params
+ if querystring is None:
+ querystring = self.querystring
+ if fragment is None:
+ fragment = ''
if port == http.defaultPortForScheme.get(scheme, 0):
hostport = host
@@ -260,6 +282,7 @@
scheme, hostport, path,
params, querystring, fragment))
+
def _parseURL(self):
if self.uri[0] == '/':
# Can't use urlparse for request_uri because urlparse
@@ -275,7 +298,7 @@
else:
# It is an absolute uri, use standard urlparse
(self.scheme, self.host, self.path,
- self.params, self.querystring, fragment) = urlparse.urlparse(self.uri)
+ self.params, self.querystring, _ignore_fragment) = urlparse.urlparse(self.uri)
if self.querystring:
self.args = cgi.parse_qs(self.querystring, True)
@@ -298,8 +321,9 @@
else:
self.prepath = []
self.postpath = path
- #print("_parseURL", self.uri, (self.uri, self.scheme, self.host, self.path, self.params, self.querystring))
+ # print("_parseURL", self.uri, (self.uri, self.scheme, self.host, self.path, self.params, self.querystring))
+
def _schemeFromPort(self, port):
"""
Try to determine the scheme matching the supplied server port. This is needed in case
@@ -316,7 +340,7 @@
@rtype: C{bool}
"""
- #from twistedcaldav.config import config
+ # from twistedcaldav.config import config
if hasattr(self.site, "EnableSSL") and self.site.EnableSSL:
if port == self.site.SSLPort:
return True
@@ -325,6 +349,7 @@
return False
+
def _fixupURLParts(self):
hostaddr, secure = self.chanRequest.getHostInfo()
if not self.scheme:
@@ -343,7 +368,7 @@
# When no hostname specified anywhere, either raise an
# error, or use the interface hostname, depending on
# protocol version
- if self.clientproto >= (1,1):
+ if self.clientproto >= (1, 1):
raise http.HTTPError(responsecode.BAD_REQUEST)
self.host = hostaddr.host
self.port = hostaddr.port
@@ -380,10 +405,12 @@
d.callback(None)
return d
+
def _processTimeStamp(self, res):
self.timeStamp("t-req-proc")
return res
+
def preprocessRequest(self):
"""Do any request processing that doesn't follow the normal
resource lookup procedure. "OPTIONS *" is handled here, for
@@ -399,7 +426,7 @@
# Allow other methods to tunnel through using POST and a request header.
# See http://code.google.com/apis/gdata/docs/2.0/basics.html
if self.headers.hasHeader("X-HTTP-Method-Override"):
- intendedMethod = self.headers.getRawHeaders("X-HTTP-Method-Override")[0];
+ intendedMethod = self.headers.getRawHeaders("X-HTTP-Method-Override")[0]
if intendedMethod:
self.originalMethod = self.method
self.method = intendedMethod
@@ -407,6 +434,7 @@
# This is where CONNECT would go if we wanted it
return None
+
def _getChild(self, _, res, path, updatepaths=True):
"""Call res.locateChild, and pass the result on to _handleSegment."""
@@ -421,6 +449,7 @@
else:
return self._handleSegment(result, res, path, updatepaths)
+
def _handleSegment(self, result, res, path, updatepaths):
"""Handle the result of a locateChild call done in _getChild."""
@@ -435,7 +464,7 @@
return newres.addCallback(
lambda actualRes: self._handleSegment(
(actualRes, newpath), res, path, updatepaths)
- )
+ )
if path:
url = quote("/" + "/".join(path))
@@ -444,19 +473,19 @@
if newpath is StopTraversal:
# We need to rethink how to do this.
- #if newres is res:
+ # if newres is res:
return res
- #else:
+ # else:
# raise ValueError("locateChild must not return StopTraversal with a resource other than self.")
newres = iweb.IResource(newres)
if newres is res:
- assert not newpath is path, "URL traversal cycle detected when attempting to locateChild %r from resource %r." % (path, res)
+ assert newpath is not path, "URL traversal cycle detected when attempting to locateChild %r from resource %r." % (path, res)
assert len(newpath) < len(path), "Infinite loop impending..."
if updatepaths:
# We found a Resource... update the request.prepath and postpath
- for x in xrange(len(path) - len(newpath)):
+ for _ in xrange(len(path) - len(newpath)):
self.prepath.append(self.postpath.pop(0))
url = quote("/" + "/".join(self.prepath) + ("/" if self.prepath and self.prepath[-1] else ""))
self._rememberResource(newres, url)
@@ -474,6 +503,7 @@
_urlsByResource = weakref.WeakKeyDictionary()
+
def _rememberResource(self, resource, url):
"""
Remember the URL of a visited resource.
@@ -482,6 +512,7 @@
self._urlsByResource[resource] = url
return resource
+
def _forgetResource(self, resource, url):
"""
Remember the URL of a visited resource.
@@ -489,6 +520,7 @@
del self._resourcesByURL[url]
del self._urlsByResource[resource]
+
def urlForResource(self, resource):
"""
Looks up the URL of the given resource if this resource was found while
@@ -512,6 +544,7 @@
raise NoURLForResourceError(resource)
return url
+
def locateResource(self, url):
"""
Looks up the resource with the given URL.
@@ -531,7 +564,7 @@
#
# Parse the URL
#
- (scheme, host, path, query, fragment) = urlsplit(url)
+ (_ignore_scheme, _ignore_host, path, query, fragment) = urlsplit(url)
if query or fragment:
raise http.HTTPError(http.StatusResponse(
@@ -574,6 +607,7 @@
d.addErrback(notFound)
return d
+
def locateChildResource(self, parent, childName):
"""
Looks up the child resource with the given name given the parent
@@ -612,6 +646,7 @@
d.addErrback(notFound)
return d
+
def _processingFailed(self, reason):
if reason.check(http.HTTPError) is not None:
# If the exception was an HTTPError, leave it alone
@@ -647,7 +682,7 @@
)
response = http.Response(
responsecode.INTERNAL_SERVER_ERROR,
- {'content-type': http_headers.MimeType('text','html')},
+ {'content-type': http_headers.MimeType('text', 'html')},
body
)
self.writeResponse(response)
@@ -664,8 +699,10 @@
def _cbFinishRender(self, result):
def filterit(response, f):
- if (hasattr(f, 'handleErrors') or
- (response.code >= 200 and response.code < 300)):
+ if (
+ hasattr(f, 'handleErrors') or
+ (response.code >= 200 and response.code < 300)
+ ):
return f(self, response)
else:
return response
@@ -688,6 +725,7 @@
raise TypeError("html is not a resource or a response")
+
def renderHTTP_exception(self, req, reason):
log.failure("Exception rendering request: {request}", reason, request=req)
@@ -696,19 +734,23 @@
return http.Response(
responsecode.INTERNAL_SERVER_ERROR,
- {'content-type': http_headers.MimeType('text','html')},
+ {'content-type': http_headers.MimeType('text', 'html')},
body)
+
+
class Site(object):
def __init__(self, resource):
"""Initialize.
"""
self.resource = iweb.IResource(resource)
+
def __call__(self, *args, **kwargs):
return Request(site=self, *args, **kwargs)
+
class NoURLForResourceError(RuntimeError):
def __init__(self, resource):
RuntimeError.__init__(self, "Resource %r has no URL in this request." % (resource,))
Modified: CalendarServer/trunk/txweb2/static.py
===================================================================
--- CalendarServer/trunk/txweb2/static.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/static.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -29,8 +29,9 @@
"""
# System Imports
-import os, time
+import os
import tempfile
+import time
# Sibling Imports
from txweb2 import http_headers, resource
@@ -53,48 +54,57 @@
"""
return succeed(None)
+
def lastModified(self):
"""
@return: The last modified time of the resource if available, None otherwise.
"""
return None
+
def creationDate(self):
"""
@return: The creation date of the resource if available, None otherwise.
"""
return None
+
def contentLength(self):
"""
@return: The size in bytes of the resource if available, None otherwise.
"""
return None
+
def contentType(self):
"""
@return: The MIME type of the resource if available, None otherwise.
"""
return None
+
def contentEncoding(self):
"""
@return: The encoding of the resource if available, None otherwise.
"""
return None
+
def displayName(self):
"""
@return: The display name of the resource if available, None otherwise.
"""
return None
+
def exists(self):
"""
@return: True if the resource exists on the server, False otherwise.
"""
return True
+
+
class StaticRenderMixin(resource.RenderMixin, MetaDataMixin):
@@ -105,9 +115,9 @@
etag = (yield self.etag())
http.checkPreconditions(
request,
- entityExists = self.exists(),
- etag = etag,
- lastModified = self.lastModified(),
+ entityExists=self.exists(),
+ etag=etag,
+ lastModified=self.lastModified(),
)
# Check per-method preconditions
@@ -155,23 +165,30 @@
self.type = http_headers.MimeType.fromString(type)
self.created_time = time.time()
+
def etag(self):
lastModified = self.lastModified()
- return succeed(http_headers.ETag("%X-%X" % (lastModified, hash(self.data)),
- weak=(time.time() - lastModified <= 1)))
+ return succeed(http_headers.ETag(
+ "%X-%X" % (lastModified, hash(self.data)),
+ weak=(time.time() - lastModified <= 1)))
+
def lastModified(self):
return self.creationDate()
+
def creationDate(self):
return self.created_time
+
def contentLength(self):
return len(self.data)
+
def contentType(self):
return self.type
+
def render(self, req):
return http.Response(
responsecode.OK,
@@ -179,6 +196,7 @@
stream=self.data)
+
class File(StaticRenderMixin):
"""
File is a resource that represents a plain non-interpreted file
@@ -207,7 +225,7 @@
contentEncodings = {
".gz" : "gzip",
".bz2": "bzip2"
- }
+ }
processors = {}
@@ -233,23 +251,27 @@
self.processors = dict([
(key.lower(), value)
for key, value in processors.items()
- ])
+ ])
if indexNames is not None:
self.indexNames = indexNames
+
def comparePath(self, path):
-
+
if isinstance(path, FilePath):
return path.path == self.fp.path
else:
return path == self.fp.path
+
def exists(self):
return self.fp.exists()
+
def etag(self):
- if not self.fp.exists(): return succeed(None)
+ if not self.fp.exists():
+ return succeed(None)
st = self.fp.statinfo
@@ -265,18 +287,21 @@
weak=weak
))
+
def lastModified(self):
if self.fp.exists():
return self.fp.getmtime()
else:
return None
+
def creationDate(self):
if self.fp.exists():
return self.fp.getmtime()
else:
return None
+
def contentLength(self):
if self.fp.exists():
if self.fp.isfile():
@@ -288,6 +313,7 @@
else:
return None
+
def _initTypeAndEncoding(self):
self._type, self._encoding = getTypeAndEncoding(
self.fp.basename(),
@@ -297,24 +323,29 @@
)
# Handle cases not covered by getTypeAndEncoding()
- if self.fp.isdir(): self._type = "httpd/unix-directory"
+ if self.fp.isdir():
+ self._type = "httpd/unix-directory"
+
def contentType(self):
if not hasattr(self, "_type"):
self._initTypeAndEncoding()
return http_headers.MimeType.fromString(self._type)
+
def contentEncoding(self):
if not hasattr(self, "_encoding"):
self._initTypeAndEncoding()
return self._encoding
+
def displayName(self):
if self.fp.exists():
return self.fp.basename()
else:
return None
+
def ignoreExt(self, ext):
"""Ignore the given extension.
@@ -331,6 +362,7 @@
"""
self.putChildren[name] = child
+
def getChild(self, name):
"""
Look up a child resource.
@@ -340,7 +372,8 @@
return self
child = self.putChildren.get(name, None)
- if child: return child
+ if child:
+ return child
child_fp = self.fp.child(name)
if hasattr(self, "knownChildren"):
@@ -351,6 +384,7 @@
else:
return None
+
def listChildren(self):
"""
@return: a sequence of the names of all known children of this resource.
@@ -361,19 +395,22 @@
self.knownChildren = set(children)
return children
+
def locateChild(self, req, segments):
"""
See L{IResource}C{.locateChild}.
"""
# If getChild() finds a child resource, return it
child = self.getChild(segments[0])
- if child is not None: return (child, segments[1:])
+ if child is not None:
+ return (child, segments[1:])
# If we're not backed by a directory, we have no children.
# But check for existance first; we might be a collection resource
# that the request wants created.
self.fp.restat(False)
- if self.fp.exists() and not self.fp.isdir(): return (None, ())
+ if self.fp.exists() and not self.fp.isdir():
+ return (None, ())
# OK, we need to return a child corresponding to the first segment
path = segments[0]
@@ -400,10 +437,12 @@
return self.createSimilarFile(fpath.path), segments[1:]
+
def renderHTTP(self, req):
self.fp.changed()
return super(File, self).renderHTTP(req)
+
def render(self, req):
"""You know what you doing."""
if not self.fp.exists():
@@ -412,7 +451,7 @@
if self.fp.isdir():
if req.path[-1] != "/":
# Redirect to include trailing '/' in URI
- return http.RedirectResponse(req.unparseURL(path=req.path+'/'))
+ return http.RedirectResponse(req.unparseURL(path=req.path + '/'))
else:
ifp = self.fp.childSearchPreauth(*self.indexNames)
if ifp:
@@ -450,11 +489,13 @@
return response
+
def createSimilarFile(self, path):
return self.__class__(path, self.defaultType, self.ignoredExts,
self.processors, self.indexNames[:])
+
class FileSaver(resource.PostableResource):
allowedTypes = (http_headers.MimeType('text', 'plain'),
http_headers.MimeType('text', 'html'),
@@ -467,6 +508,7 @@
self.expectedFields = expectedFields
self.permissions = permissions
+
def makeUniqueName(self, filename):
"""Called when a unique filename is needed.
@@ -478,6 +520,7 @@
return tempfile.mktemp(suffix=os.path.splitext(filename)[1], dir=self.destination)
+
def isSafeToWrite(self, filename, mimetype, filestream):
"""Returns True if it's "safe" to write this file,
otherwise it raises an exception.
@@ -493,6 +536,7 @@
return True
+
def writeFile(self, filename, mimetype, fileobject):
"""Does the I/O dirty work after it calls isSafeToWrite to make
sure it's safe to write this file.
@@ -505,11 +549,12 @@
flags = os.O_WRONLY | os.O_CREAT | os.O_EXCL | getattr(os, "O_BINARY", 0)
fileobject = os.fdopen(os.open(outname, flags, self.permissions), 'wb', 0)
-
+
stream.readIntoFile(filestream, fileobject)
return outname
+
def render(self, req):
content = ["<html><body>"]
@@ -560,12 +605,16 @@
def isDangerous(path):
return path == '..' or '/' in path or os.sep in path
+
+
def addSlash(request):
return "http%s://%s%s/" % (
request.isSecure() and 's' or '',
request.getHeader("host"),
(request.uri.split('?')[0]))
+
+
def loadMimeTypes(mimetype_locations=['/etc/mime.types']):
"""
Multiple file locations containing mime-types can be passed as a list.
@@ -580,18 +629,18 @@
# usual suspects.
contentTypes.update(
{
- '.conf': 'text/plain',
- '.diff': 'text/plain',
- '.exe': 'application/x-executable',
- '.flac': 'audio/x-flac',
- '.java': 'text/plain',
- '.ogg': 'application/ogg',
- '.oz': 'text/x-oz',
- '.swf': 'application/x-shockwave-flash',
- '.tgz': 'application/x-gtar',
- '.wml': 'text/vnd.wap.wml',
- '.xul': 'application/vnd.mozilla.xul+xml',
- '.py': 'text/plain',
+ '.conf': 'text/plain',
+ '.diff': 'text/plain',
+ '.exe': 'application/x-executable',
+ '.flac': 'audio/x-flac',
+ '.java': 'text/plain',
+ '.ogg': 'application/ogg',
+ '.oz': 'text/x-oz',
+ '.swf': 'application/x-shockwave-flash',
+ '.tgz': 'application/x-gtar',
+ '.wml': 'text/vnd.wap.wml',
+ '.xul': 'application/vnd.mozilla.xul+xml',
+ '.py': 'text/plain',
'.patch': 'text/plain',
}
)
@@ -603,10 +652,12 @@
return contentTypes
+
+
def getTypeAndEncoding(filename, types, encodings, defaultType):
p, ext = os.path.splitext(filename)
ext = ext.lower()
- if encodings.has_key(ext):
+ if ext in encodings:
enc = encodings[ext]
ext = os.path.splitext(p)[1].lower()
else:
Modified: CalendarServer/trunk/txweb2/stream.py
===================================================================
--- CalendarServer/trunk/txweb2/stream.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/stream.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -36,12 +36,12 @@
underlying resources and causes read to return None forevermore.
IByteStream adds a bit more to the API:
-1) read is required to return objects conforming to the buffer interface.
+1) read is required to return objects conforming to the buffer interface.
2) .length, which may either an integer number of bytes remaining, or
None if unknown
3) .split(position). Split takes a position, and splits the
stream in two pieces, returning the two new streams. Using the
-original stream after calling split is not allowed.
+original stream after calling split is not allowed.
There are two builtin source stream classes: FileStream and
MemoryStream. The first produces data from a file object, the second
@@ -55,7 +55,10 @@
from __future__ import generators
-import copy, os, types, sys
+import copy
+import os
+import types
+import sys
from zope.interface import Interface, Attribute, implements
from twisted.internet.defer import Deferred
from twisted.internet import interfaces as ti_interfaces, defer, reactor, protocol, error as ti_error
@@ -70,21 +73,23 @@
# Python 2.4.2 (only) has a broken mmap that leaks a fd every time you call it.
-if sys.version_info[0:3] != (2,4,2):
+if sys.version_info[0:3] != (2, 4, 2):
try:
import mmap
except ImportError:
mmap = None
else:
mmap = None
-
-##############################
-#### Interfaces ####
-##############################
+
+
+#
+# Interfaces
+#
+
class IStream(Interface):
"""A stream of arbitrary data."""
-
+
def read():
"""Read some data.
@@ -94,19 +99,21 @@
Errors may be indicated by exception or by a Deferred of a Failure.
"""
-
+
def close():
"""Prematurely close. Should also cause further reads to
return None."""
+
+
class IByteStream(IStream):
"""A stream which is of bytes."""
-
+
length = Attribute("""How much data is in this stream. Can be None if unknown.""")
-
+
def read():
"""Read some data.
-
+
Returns an object conforming to the buffer interface, or
if there is no more data available, returns None.
Can also return a Deferred resulting in one of the above.
@@ -130,6 +137,8 @@
return None. Additionally, .length should be set to 0.
"""
+
+
class ISendfileableStream(Interface):
def read(sendfile=False):
"""
@@ -139,21 +148,25 @@
If sendfile == True, returns either the above, or a SendfileBuffer.
"""
-
+
+
+
class SimpleStream(object):
"""Superclass of simple streams with a single buffer and a offset and length
into that buffer."""
implements(IByteStream)
-
+
length = None
start = None
-
+
def read(self):
return None
+
def close(self):
self.length = 0
-
+
+
def split(self, point):
if self.length is not None:
if point > self.length:
@@ -165,14 +178,14 @@
b.start += point
return (self, b)
-##############################
-#### FileStream ####
-##############################
-
+#
+# FileStream
+#
+
# maximum mmap size
-MMAP_LIMIT = 4*1024*1024
+MMAP_LIMIT = 4 * 1024 * 1024
# minimum mmap size
-MMAP_THRESHOLD = 8*1024
+MMAP_THRESHOLD = 8 * 1024
# maximum sendfile length
SENDFILE_LIMIT = 16777216
@@ -184,7 +197,7 @@
Python's mmap call sucks and ommitted the "offset" argument for no
discernable reason. Replace this with a mmap module that has offset.
"""
-
+
offset = kwargs.get('offset', None)
if offset in [None, 0]:
if 'offset' in kwargs:
@@ -193,6 +206,8 @@
raise mmap.error("mmap: Python sucks and does not support offset.")
return mmap.mmap(*args, **kwargs)
+
+
class FileStream(SimpleStream):
implements(ISendfileableStream)
"""A stream that reads data from a file. File must be a normal
@@ -213,7 +228,8 @@
else:
self.length = length
self.useMMap = useMMap
-
+
+
def read(self, sendfile=False):
if self.f is None:
return None
@@ -223,7 +239,7 @@
self.f = None
return None
- #if sendfile and length > SENDFILE_THRESHOLD:
+ # if sendfile and length > SENDFILE_THRESHOLD:
# # XXX: Yay using non-existent sendfile support!
# # FIXME: if we return a SendfileBuffer, and then sendfile
# # fails, then what? Or, what if file is too short?
@@ -238,7 +254,7 @@
try:
res = mmapwrapper(self.f.fileno(), readSize,
access=mmap.ACCESS_READ, offset=self.start)
- #madvise(res, MADV_SEQUENTIAL)
+ # madvise(res, MADV_SEQUENTIAL)
self.length -= readSize
self.start += readSize
return res
@@ -258,15 +274,16 @@
self.start += bytesRead
return b
+
def close(self):
self.f = None
SimpleStream.close(self)
components.registerAdapter(FileStream, file, IByteStream)
-##############################
-#### MemoryStream ####
-##############################
+#
+# MemoryStream
+#
class MemoryStream(SimpleStream):
"""A stream that reads data from a buffer object."""
@@ -284,6 +301,7 @@
raise ValueError("len(mem) < start + length")
self.length = length
+
def read(self):
if self.mem is None:
return None
@@ -295,6 +313,7 @@
self.length = 0
return result
+
def close(self):
self.mem = None
SimpleStream.close(self)
@@ -302,23 +321,24 @@
components.registerAdapter(MemoryStream, str, IByteStream)
components.registerAdapter(MemoryStream, types.BufferType, IByteStream)
-##############################
-#### CompoundStream ####
-##############################
+#
+# CompoundStream
+#
class CompoundStream(object):
"""A stream which is composed of many other streams.
Call addStream to add substreams.
"""
-
+
implements(IByteStream, ISendfileableStream)
deferred = None
length = 0
-
+
def __init__(self, buckets=()):
self.buckets = [IByteStream(s) for s in buckets]
-
+
+
def addStream(self, bucket):
"""Add a stream to the output"""
bucket = IByteStream(bucket)
@@ -329,13 +349,14 @@
else:
self.length += bucket.length
+
def read(self, sendfile=False):
if self.deferred is not None:
raise RuntimeError("Call to read while read is already outstanding")
if not self.buckets:
return None
-
+
if sendfile and ISendfileableStream.providedBy(self.buckets[0]):
try:
result = self.buckets[0].read(sendfile)
@@ -346,60 +367,64 @@
result = self.buckets[0].read()
except:
return self._gotFailure(Failure())
-
+
if isinstance(result, Deferred):
self.deferred = result
result.addCallbacks(self._gotRead, self._gotFailure, (sendfile,))
return result
-
+
return self._gotRead(result, sendfile)
+
def _gotFailure(self, f):
self.deferred = None
del self.buckets[0]
self.close()
return f
-
+
+
def _gotRead(self, result, sendfile):
self.deferred = None
if result is None:
del self.buckets[0]
# Next bucket
return self.read(sendfile)
-
+
if self.length is not None:
self.length -= len(result)
return result
-
+
+
def split(self, point):
num = 0
origPoint = point
for bucket in self.buckets:
- num+=1
+ num += 1
if point == 0:
b = CompoundStream()
b.buckets = self.buckets[num:]
del self.buckets[num:]
- return self,b
-
+ return self, b
+
if bucket.length is None:
# Indeterminate length bucket.
# give up and use fallback splitter.
return fallbackSplit(self, origPoint)
-
+
if point < bucket.length:
- before,after = bucket.split(point)
+ before, after = bucket.split(point)
b = CompoundStream()
b.buckets = self.buckets[num:]
b.buckets[0] = after
-
- del self.buckets[num+1:]
+
+ del self.buckets[num + 1:]
self.buckets[num] = before
- return self,b
-
+ return self, b
+
point -= bucket.length
-
+
+
def close(self):
for bucket in self.buckets:
bucket.close()
@@ -407,10 +432,11 @@
self.length = 0
-##############################
-#### readStream ####
-##############################
+#
+# readStream
+#
+
class _StreamReader(object):
"""Process a stream's data using callbacks for data and stream finish."""
@@ -419,12 +445,14 @@
self.gotDataCallback = gotDataCallback
self.result = Deferred()
+
def run(self):
# self.result may be del'd in _read()
result = self.result
self._read()
return result
-
+
+
def _read(self):
try:
result = self.stream.read()
@@ -436,11 +464,13 @@
else:
self._gotData(result)
+
def _gotError(self, failure):
result = self.result
del self.result, self.gotDataCallback, self.stream
result.errback(failure)
-
+
+
def _gotData(self, data):
if data is None:
result = self.result
@@ -454,6 +484,8 @@
return
reactor.callLater(0, self._read)
+
+
def readStream(stream, gotDataCallback):
"""Pass a stream's data to a callback.
@@ -464,6 +496,7 @@
return _StreamReader(stream, gotDataCallback).run()
+
def readAndDiscard(stream):
"""Read all the data from the given stream, and throw it out.
@@ -471,6 +504,8 @@
"""
return readStream(stream, lambda _: None)
+
+
def readIntoFile(stream, outFile):
"""Read a stream and write it into a file.
@@ -481,6 +516,8 @@
return _
return readStream(stream, outFile.write).addBoth(done)
+
+
def connectStream(inputStream, factory):
"""Connect a protocol constructed from a factory to stream.
@@ -498,21 +535,26 @@
lambda _: p.connectionLost(ti_error.ConnectionDone()), lambda _: p.connectionLost(_))
return out
-##############################
-#### fallbackSplit ####
-##############################
+
+#
+# fallbackSplit
+#
+
def fallbackSplit(stream, point):
after = PostTruncaterStream(stream, point)
before = TruncaterStream(stream, point, after)
return (before, after)
+
+
class TruncaterStream(object):
def __init__(self, stream, point, postTruncater):
self.stream = stream
self.length = point
self.postTruncater = postTruncater
-
+
+
def read(self):
if self.length == 0:
if self.postTruncater is not None:
@@ -521,13 +563,14 @@
postTruncater.sendInitialSegment(self.stream.read())
self.stream = None
return None
-
+
result = self.stream.read()
if isinstance(result, Deferred):
return result.addCallback(self._gotRead)
else:
return self._gotRead(result)
-
+
+
def _gotRead(self, data):
if data is None:
raise ValueError("Ran out of data for a split of a indeterminate length source")
@@ -544,7 +587,8 @@
postTruncater.sendInitialSegment(after)
self.stream = None
return before
-
+
+
def split(self, point):
if point > self.length:
raise ValueError("split point (%d) > length (%d)" % (point, self.length))
@@ -554,7 +598,8 @@
self.length = point
self.postTruncater = post
return self, trunc
-
+
+
def close(self):
if self.postTruncater is not None:
self.postTruncater.notifyClosed(self)
@@ -563,14 +608,15 @@
self.stream.close()
self.stream = None
self.length = 0
-
+
+
class PostTruncaterStream(object):
deferred = None
sentInitialSegment = False
truncaterClosed = None
closed = False
-
+
length = None
def __init__(self, stream, point):
self.stream = stream
@@ -578,6 +624,7 @@
if stream.length is not None:
self.length = stream.length - point
+
def read(self):
if not self.sentInitialSegment:
self.sentInitialSegment = True
@@ -585,12 +632,14 @@
readAndDiscard(self.truncaterClosed)
self.truncaterClosed = None
return self.deferred
-
+
return self.stream.read()
-
+
+
def split(self, point):
return fallbackSplit(self, point)
-
+
+
def close(self):
self.closed = True
if self.truncaterClosed is not None:
@@ -600,9 +649,10 @@
elif self.sentInitialSegment:
# first half already finished up
self.stream.close()
-
+
self.deferred = None
-
+
+
# Callbacks from TruncaterStream
def sendInitialSegment(self, data):
if self.closed:
@@ -614,7 +664,8 @@
data.chainDeferred(self.deferred)
else:
self.deferred.callback(data)
-
+
+
def notifyClosed(self, truncater):
if self.closed:
# we are closed, have first half really close
@@ -627,10 +678,12 @@
# Idle, store closed info.
self.truncaterClosed = truncater
-########################################
-#### ProducerStream/StreamProducer ####
-########################################
-
+
+
+#
+# ProducerStream/StreamProducer
+#
+
class ProducerStream(object):
"""Turns producers into a IByteStream.
Thus, implements IConsumer and IByteStream."""
@@ -642,13 +695,14 @@
producer = None
producerPaused = False
deferred = None
-
+
bufferSize = 5
-
+
def __init__(self, length=None):
self.buffer = []
self.length = length
-
+
+
# IByteStream implementation
def read(self):
if self.buffer:
@@ -666,26 +720,29 @@
or self.producerPaused):
self.producerPaused = False
self.producer.resumeProducing()
-
+
return deferred
-
+
+
def split(self, point):
return fallbackSplit(self, point)
-
+
+
def close(self):
"""Called by reader of stream when it is done reading."""
- self.buffer=[]
+ self.buffer = []
self.closed = True
if self.producer is not None:
self.producer.stopProducing()
self.producer = None
self.deferred = None
-
+
+
# IConsumer implementation
def write(self, data):
if self.closed:
return
-
+
if self.deferred:
deferred = self.deferred
self.deferred = None
@@ -697,6 +754,7 @@
self.producer.pauseProducing()
self.producerPaused = True
+
def finish(self, failure=None):
"""Called by producer when it is done.
@@ -716,13 +774,14 @@
deferred.callback(None)
else:
if failure is not None:
- self.failed = True
- self.failure = failure
-
+ self.failed = True
+ self.failure = failure
+
+
def registerProducer(self, producer, streaming):
if self.producer is not None:
raise RuntimeError("Cannot register producer %s, because producer %s was never unregistered." % (producer, self.producer))
-
+
if self.closed:
producer.stopProducing()
else:
@@ -731,9 +790,12 @@
if not streaming:
producer.resumeProducing()
+
def unregisterProducer(self):
self.producer = None
-
+
+
+
class StreamProducer(object):
"""A push producer which gets its data by reading a stream."""
implements(ti_interfaces.IPushProducer)
@@ -742,21 +804,23 @@
finishedCallback = None
paused = False
consumer = None
-
+
def __init__(self, stream, enforceStr=True):
self.stream = stream
self.enforceStr = enforceStr
-
+
+
def beginProducing(self, consumer):
if self.stream is None:
return defer.succeed(None)
-
+
self.consumer = consumer
finishedCallback = self.finishedCallback = Deferred()
self.consumer.registerProducer(self, True)
self.resumeProducing()
return finishedCallback
-
+
+
def resumeProducing(self):
self.paused = False
if self.deferred is not None:
@@ -767,13 +831,14 @@
except:
self.stopProducing(Failure())
return
-
+
if isinstance(data, Deferred):
self.deferred = data
self.deferred.addCallbacks(self._doWrite, self.stopProducing)
else:
self._doWrite(data)
+
def _doWrite(self, data):
if self.consumer is None:
return
@@ -785,19 +850,21 @@
self.finishedCallback.callback(None)
self.finishedCallback = self.deferred = self.consumer = self.stream = None
return
-
+
self.deferred = None
if self.enforceStr:
# XXX: sucks that we have to do this. make transport.write(buffer) work!
data = str(buffer(data))
self.consumer.write(data)
-
+
if not self.paused:
self.resumeProducing()
-
+
+
def pauseProducing(self):
self.paused = True
+
def stopProducing(self, failure=ti_error.ConnectionLost()):
if self.consumer is not None:
self.consumer.unregisterProducer()
@@ -810,13 +877,15 @@
self.paused = True
if self.stream is not None:
self.stream.close()
-
+
self.finishedCallback = self.deferred = self.consumer = self.stream = None
-##############################
-#### ProcessStreamer ####
-##############################
+
+#
+# ProcessStreamer
+#
+
class _ProcessStreamerProtocol(protocol.ProcessProtocol):
def __init__(self, inputStream, outStream, errStream):
@@ -824,39 +893,47 @@
self.outStream = outStream
self.errStream = errStream
self.resultDeferred = defer.Deferred()
-
+
+
def connectionMade(self):
p = StreamProducer(self.inputStream)
# if the process stopped reading from the input stream,
# this is not an error condition, so it oughtn't result
# in a ConnectionLost() from the input stream:
- p.stopProducing = lambda err=None: StreamProducer.stopProducing(p, err)
-
+ p.stopProducing = lambda err = None: StreamProducer.stopProducing(p, err)
+
d = p.beginProducing(self.transport)
d.addCallbacks(lambda _: self.transport.closeStdin(),
self._inputError)
+
def _inputError(self, f):
log.failure("Error in input stream for transport {transport}", f, transport=self.transport)
self.transport.closeStdin()
-
+
+
def outReceived(self, data):
self.outStream.write(data)
+
def errReceived(self, data):
self.errStream.write(data)
+
def outConnectionLost(self):
self.outStream.finish()
+
def errConnectionLost(self):
self.errStream.finish()
-
+
+
def processEnded(self, reason):
self.resultDeferred.errback(reason)
del self.resultDeferred
+
class ProcessStreamer(object):
"""Runs a process hooked up to streams.
@@ -874,7 +951,8 @@
self._program = program
self._args = args
self._env = env
-
+
+
def run(self):
"""Run the process.
@@ -886,29 +964,36 @@
del self._env
return self._protocol.resultDeferred.addErrback(lambda _: _.trap(ti_error.ProcessDone))
-##############################
-#### generatorToStream ####
-##############################
+
+#
+# generatorToStream
+#
+
class _StreamIterator(object):
- done=False
+ done = False
def __iter__(self):
return self
+
+
def next(self):
if self.done:
raise StopIteration
return self.value
- wait=object()
+ wait = object()
+
+
class _IteratorStream(object):
length = None
-
+
def __init__(self, fun, stream, args, kwargs):
- self._stream=stream
+ self._stream = stream
self._streamIterator = _StreamIterator()
self._gen = fun(self._streamIterator, *args, **kwargs)
-
+
+
def read(self):
try:
val = self._gen.next()
@@ -922,34 +1007,39 @@
else:
return self._gotRead(newdata)
return val
-
+
+
def _gotRead(self, data):
if data is None:
- self._streamIterator.done=True
+ self._streamIterator.done = True
else:
- self._streamIterator.value=data
+ self._streamIterator.value = data
return self.read()
+
def close(self):
self._stream.close()
del self._gen, self._stream, self._streamIterator
+
def split(self):
return fallbackSplit(self)
-
+
+
+
def generatorToStream(fun):
"""Converts a generator function into a stream.
-
+
The function should take an iterator as its first argument,
which will be converted *from* a stream by this wrapper, and
yield items which are turned *into* the results from the
stream's 'read' call.
-
+
One important point: before every call to input.next(), you
*MUST* do a "yield input.wait" first. Yielding this magic value
takes care of ensuring that the input is not a deferred before
you see it.
-
+
>>> from txweb2 import stream
>>> from string import maketrans
>>> alphabet = 'abcdefghijklmnopqrstuvwxyz'
@@ -974,61 +1064,67 @@
>>> evenMoreEncryptedStream = encrypt(encryptedStream, 13)
>>> evenMoreEncryptedStream.read()
'SampleSampleSample'
-
+
"""
def generatorToStream_inner(stream, *args, **kwargs):
return _IteratorStream(fun, stream, args, kwargs)
return generatorToStream_inner
-##############################
-#### BufferedStream ####
-##############################
+#
+# BufferedStream
+#
+
class BufferedStream(object):
"""A stream which buffers its data to provide operations like
readline and readExactly."""
-
+
data = ""
def __init__(self, stream):
self.stream = stream
+
def _readUntil(self, f):
"""Internal helper function which repeatedly calls f each time
after more data has been received, until it returns non-None."""
while True:
r = f()
if r is not None:
- yield r; return
-
+ yield r
+ return
+
newdata = self.stream.read()
if isinstance(newdata, defer.Deferred):
newdata = defer.waitForDeferred(newdata)
- yield newdata; newdata = newdata.getResult()
-
+ yield newdata
+ newdata = newdata.getResult()
+
if newdata is None:
# End Of File
newdata = self.data
self.data = ''
- yield newdata; return
+ yield newdata
+ return
self.data += str(newdata)
_readUntil = defer.deferredGenerator(_readUntil)
+
def readExactly(self, size=None):
"""Read exactly size bytes of data, or, if size is None, read
the entire stream into a string."""
if size is not None and size < 0:
raise ValueError("readExactly: size cannot be negative: %s", size)
-
+
def gotdata():
data = self.data
if size is not None and len(data) >= size:
- pre,post = data[:size], data[size:]
+ pre, post = data[:size], data[size:]
self.data = post
return pre
return self._readUntil(gotdata)
-
-
+
+
def readline(self, delimiter='\r\n', size=None):
"""
Read a line of data from the string, bounded by
@@ -1040,7 +1136,7 @@
the next line will be returned together.
"""
if size is not None and size < 0:
- raise ValueError("readline: size cannot be negative: %s" % (size, ))
+ raise ValueError("readline: size cannot be negative: %s" % (size,))
def gotdata():
data = self.data
@@ -1055,18 +1151,20 @@
splitpoint = data.find(delimiter)
if splitpoint != -1:
splitpoint += len(delimiter)
-
+
if splitpoint != -1:
pre = data[:splitpoint]
self.data = data[splitpoint:]
return pre
return self._readUntil(gotdata)
-
+
+
def pushback(self, pushed):
"""Push data back into the buffer."""
-
+
self.data = pushed + self.data
-
+
+
def read(self):
data = self.data
if data:
@@ -1074,17 +1172,18 @@
return data
return self.stream.read()
+
def _len(self):
l = self.stream.length
if l is None:
return None
return l + len(self.data)
-
+
length = property(_len)
-
+
def split(self, offset):
off = offset - len(self.data)
-
+
pre, post = self.stream.split(max(0, off))
pre = BufferedStream(pre)
post = BufferedStream(post)
@@ -1093,14 +1192,15 @@
post.data = self.data[-off:]
else:
pre.data = self.data
-
+
return pre, post
-
-#########################
-#### MD5Stream ####
-#########################
+
+#
+# MD5Stream
+#
+
class MD5Stream(SimpleStream):
"""
An wrapper which computes the MD5 hash of the data read from the
Modified: CalendarServer/trunk/txweb2/test/__init__.py
===================================================================
--- CalendarServer/trunk/txweb2/test/__init__.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/__init__.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -6,4 +6,3 @@
txweb2.test: unittests for the Twext.Web2, Web Server Framework
"""
-
Modified: CalendarServer/trunk/txweb2/test/simple_client.py
===================================================================
--- CalendarServer/trunk/txweb2/test/simple_client.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/simple_client.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -1,4 +1,5 @@
-import socket, sys
+import socket
+import sys
test_type = sys.argv[1]
port = int(sys.argv[2])
@@ -10,12 +11,12 @@
if socket_type == 'ssl':
s2 = socket.ssl(s)
- send=s2.write
- recv=s2.read
+ send = s2.write
+ recv = s2.read
else:
- send=s.send
- recv=s.recv
-
+ send = s.send
+ recv = s.recv
+
print >> sys.stderr, ">> Making %s request to port %d" % (socket_type, port)
send("GET /error HTTP/1.0\r\n")
@@ -24,20 +25,20 @@
if test_type == "lingeringClose":
print >> sys.stderr, ">> Sending lots of data"
send("Content-Length: 1000000\r\n\r\n")
- send("X"*1000000)
+ send("X" * 1000000)
else:
send('\r\n')
-#import time
-#time.sleep(5)
+# import time
+# time.sleep(5)
print >> sys.stderr, ">> Getting data"
-data=''
+data = ''
while len(data) < 299999:
try:
- x=recv(10000)
+ x = recv(10000)
except:
break
if x == '':
break
- data+=x
+ data += x
sys.stdout.write(data)
Modified: CalendarServer/trunk/txweb2/test/test_client.py
===================================================================
--- CalendarServer/trunk/txweb2/test/test_client.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/test_client.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -22,13 +22,16 @@
def dataReceived(self, data):
self.data += data
+
def write(self, data):
self.transport.write(data)
+
def connectionLost(self, reason):
self.done = True
self.transport.loseConnection()
+
def loseConnection(self):
self.done = True
self.transport.loseConnection()
@@ -52,15 +55,20 @@
return cxn
+
def writeToClient(self, cxn, data):
cxn.server.write(data)
self.iterate(cxn)
+
def writeLines(self, cxn, lines):
self.writeToClient(cxn, '\r\n'.join(lines))
- def assertReceived(self, cxn, expectedStatus, expectedHeaders,
- expectedContent=None):
+
+ def assertReceived(
+ self, cxn, expectedStatus, expectedHeaders,
+ expectedContent=None
+ ):
self.iterate(cxn)
headers, content = cxn.server.data.split('\r\n\r\n', 1)
@@ -80,15 +88,18 @@
self.assertEquals(content, expectedContent)
+
def assertDone(self, cxn):
self.iterate(cxn)
self.assertEquals(cxn.server.done, True, 'Connection not closed.')
+
def assertHeaders(self, resp, expectedHeaders):
headers = list(resp.headers.getAllRawHeaders())
headers.sort()
self.assertEquals(headers, expectedHeaders)
+
def checkResponse(self, resp, code, headers, length, data):
"""
Assert various things about a response: http code, headers, stream
@@ -130,6 +141,7 @@
return d.addCallback(lambda _: self.assertDone(cxn))
+
def test_delayedContent(self):
"""
Make sure that the client returns the response object as soon as the
@@ -164,6 +176,7 @@
return d.addCallback(lambda _: self.assertDone(cxn))
+
def test_prematurePipelining(self):
"""
Ensure that submitting a second request before it's allowed results
@@ -189,6 +202,7 @@
return d
+
def test_userHeaders(self):
"""
Make sure that headers get through in both directions.
@@ -237,6 +251,7 @@
return d.addCallback(lambda _: self.assertDone(cxn))
+
def test_streamedUpload(self):
"""
Make sure that sending request content works.
@@ -260,6 +275,7 @@
return d.addCallback(lambda _: self.assertDone(cxn))
+
def test_sentHead(self):
"""
Ensure that HEAD requests work, and return Content-Length.
@@ -282,6 +298,7 @@
return d.addCallback(lambda _: self.assertDone(cxn))
+
def test_sentHeadKeepAlive(self):
"""
Ensure that keepalive works right after a HEAD request.
@@ -309,9 +326,9 @@
didIt[0] = second
if second:
- keepAlive='close'
+ keepAlive = 'close'
else:
- keepAlive='Keep-Alive'
+ keepAlive = 'Keep-Alive'
cxn.server.data = ''
@@ -319,10 +336,10 @@
self.checkResponse, 200, [('Content-Length', ['5'])], 0, None)
self.assertReceived(cxn, 'HEAD / HTTP/1.1',
- ['Connection: '+ keepAlive])
+ ['Connection: ' + keepAlive])
self.writeLines(cxn, ('HTTP/1.1 200 OK',
- 'Connection: '+ keepAlive,
+ 'Connection: ' + keepAlive,
'Content-Length: 5',
'\r\n'))
@@ -332,6 +349,7 @@
return d.addCallback(lambda _: self.assertDone(cxn))
+
def test_chunkedUpload(self):
"""
Ensure chunked data is correctly decoded on upload.
@@ -385,6 +403,7 @@
return d.addCallback(lambda _: self.assertDone(cxn))
+
def test_serverIsntHttp(self):
"""
Check that an error is returned if the server doesn't talk HTTP.
@@ -435,6 +454,7 @@
self.writeLines(cxn, ('HTTP/1.1 200',
'\r\n'))
+
def test_errorReadingRequestStream(self):
"""
Ensure that stream errors are propagated to the response.
@@ -492,4 +512,3 @@
return self.assertFailure(response.stream.read(), ValueError)
d.addCallback(cb)
return d
-
Modified: CalendarServer/trunk/txweb2/test/test_fileupload.py
===================================================================
--- CalendarServer/trunk/txweb2/test/test_fileupload.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/test_fileupload.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -29,6 +29,7 @@
raise ValueError("len(mem) < start + length")
self.length = length
+
def read(self):
if self.mem is None:
return None
@@ -41,6 +42,7 @@
self.start += amtToRead
return result
+
def close(self):
self.mem = None
stream.SimpleStream.close(self)
@@ -58,21 +60,24 @@
d.addCallback(self._assertFailures, expected_error)
return d
+
def _assertFailures(self, failures, *expectedFailures):
for flag, failure in failures:
self.failUnlessEqual(flag, defer.FAILURE)
failure.trap(*expectedFailures)
+
def doTest(self, boundary, data, expected_args, expected_files):
- #import time, gc, cgi, cStringIO
+ # import time, gc, cgi, cStringIO
for bytes in range(1, 20):
- #s = TestStream(data, maxReturn=bytes)
+ # s = TestStream(data, maxReturn=bytes)
s = stream.IStream(data)
- #t=time.time()
+ # t=time.time()
d = waitForDeferred(fileupload.parseMultipartFormData(s, boundary))
- yield d; args, files = d.getResult()
- #e=time.time()
- #print "%.2g"%(e-t)
+ yield d
+ args, files = d.getResult()
+ # e=time.time()
+ # print "%.2g"%(e-t)
self.assertEquals(args, expected_args)
# Read file data back into memory to compare.
@@ -81,17 +86,17 @@
out[name] = [(filename, ctype, f.read()) for (filename, ctype, f) in l]
self.assertEquals(out, expected_files)
- #data=cStringIO.StringIO(data)
- #t=time.time()
- #d=cgi.parse_multipart(data, {'boundary':boundary})
- #e=time.time()
- #print "CGI: %.2g"%(e-t)
+ # data=cStringIO.StringIO(data)
+ # t=time.time()
+ # d=cgi.parse_multipart(data, {'boundary':boundary})
+ # e=time.time()
+ # print "CGI: %.2g"%(e-t)
doTest = deferredGenerator(doTest)
def testNormalUpload(self):
return self.doTest(
'---------------------------155781040421463194511908194298',
-"""-----------------------------155781040421463194511908194298\r
+ """-----------------------------155781040421463194511908194298\r
Content-Disposition: form-data; name="foo"\r
\r
Foo Bar\r
@@ -104,14 +109,15 @@
blah\r
-----------------------------155781040421463194511908194298--\r
""",
- {'foo':['Foo Bar']},
- {'file':[('filename', MimeType('text', 'html'),
+ {'foo': ['Foo Bar']},
+ {'file': [('filename', MimeType('text', 'html'),
"Contents of a file\nblah\nblah")]})
+
def testMultipleUpload(self):
return self.doTest(
'xyz',
-"""--xyz\r
+ """--xyz\r
Content-Disposition: form-data; name="foo"\r
\r
Foo Bar\r
@@ -131,15 +137,15 @@
bleh\r
--xyz--\r
""",
- {'foo':['Foo Bar', 'Baz']},
- {'file':[('filename', MimeType('text', 'html'), "blah"),
- ('filename', MimeType('text', 'plain'), "bleh")]})
+ {'foo': ['Foo Bar', 'Baz']},
+ {'file': [('filename', MimeType('text', 'html'), "blah"),
+ ('filename', MimeType('text', 'plain'), "bleh")]})
def testStupidFilename(self):
return self.doTest(
'----------0xKhTmLbOuNdArY',
-"""------------0xKhTmLbOuNdArY\r
+ """------------0xKhTmLbOuNdArY\r
Content-Disposition: form-data; name="file"; filename="foo"; name="foobar.txt"\r
Content-Type: text/plain\r
\r
@@ -149,14 +155,14 @@
------------0xKhTmLbOuNdArY--\r
""",
{},
- {'file':[('foo"; name="foobar.txt', MimeType('text', 'plain'),
+ {'file': [('foo"; name="foobar.txt', MimeType('text', 'plain'),
"Contents of a file\nblah\nblah")]})
def testEmptyFilename(self):
return self.doTest(
'curlPYafCMnsamUw9kSkJJkSen41sAV',
-"""--curlPYafCMnsamUw9kSkJJkSen41sAV\r
+ """--curlPYafCMnsamUw9kSkJJkSen41sAV\r
cONTENT-tYPE: application/octet-stream\r
cONTENT-dISPOSITION: FORM-DATA; NAME="foo"; FILENAME=""\r
\r
@@ -164,7 +170,7 @@
--curlPYafCMnsamUw9kSkJJkSen41sAV--\r
""",
{},
- {'foo':[('', MimeType('application', 'octet-stream'),
+ {'foo': [('', MimeType('application', 'octet-stream'),
"qwertyuiop")]})
@@ -172,7 +178,7 @@
def testMissingContentDisposition(self):
return self.doTestError(
'----------0xKhTmLbOuNdArY',
-"""------------0xKhTmLbOuNdArY\r
+ """------------0xKhTmLbOuNdArY\r
Content-Type: text/html\r
\r
Blah blah I am a stupid webbrowser\r
@@ -184,7 +190,7 @@
def testRandomData(self):
return self.doTestError(
'boundary',
-"""--sdkjsadjlfjlj skjsfdkljsd
+ """--sdkjsadjlfjlj skjsfdkljsd
sfdkjsfdlkjhsfadklj sffkj""",
fileupload.MimeFormatError)
@@ -275,16 +281,17 @@
for bytes in range(1, 20):
s = TestStream(data, maxReturn=bytes)
d = waitForDeferred(fileupload.parse_urlencoded(s))
- yield d; args = d.getResult()
+ yield d
+ args = d.getResult()
self.assertEquals(args, expected_args)
doTest = deferredGenerator(doTest)
def test_parseValid(self):
- self.doTest("a=b&c=d&c=e", {'a':['b'], 'c':['d', 'e']})
- self.doTest("a=b&c=d&c=e", {'a':['b'], 'c':['d', 'e']})
- self.doTest("a=b+c%20d", {'a':['b c d']})
+ self.doTest("a=b&c=d&c=e", {'a': ['b'], 'c': ['d', 'e']})
+ self.doTest("a=b&c=d&c=e", {'a': ['b'], 'c': ['d', 'e']})
+ self.doTest("a=b+c%20d", {'a': ['b c d']})
def test_parseInvalid(self):
- self.doTest("a&b=c", {'b':['c']})
+ self.doTest("a&b=c", {'b': ['c']})
Modified: CalendarServer/trunk/txweb2/test/test_http.py
===================================================================
--- CalendarServer/trunk/txweb2/test/test_http.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/test_http.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -1,7 +1,9 @@
from __future__ import nested_scopes
-import time, sys, os
+import os
+import sys
+import time
from zope.interface import implements
@@ -20,6 +22,7 @@
class RedirectResponseTestCase(unittest.TestCase):
+
def testTemporary(self):
"""
Verify the "temporary" parameter sets the appropriate response code
@@ -29,6 +32,8 @@
req = http.RedirectResponse("http://example.com/", temporary=True)
self.assertEquals(req.code, responsecode.TEMPORARY_REDIRECT)
+
+
class PreconditionTestCase(unittest.TestCase):
def checkPreconditions(self, request, response, expectedResult, expectedCode,
**kw):
@@ -41,6 +46,7 @@
self.assertEquals(e.response.code, expectedCode)
self.assertEquals(preconditionsPass, expectedResult)
+
def testWithoutHeaders(self):
request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
out_headers = http_headers.Headers()
@@ -58,6 +64,7 @@
out_headers.setHeader("ETag", http_headers.ETag('foo'))
self.checkPreconditions(request, response, True, responsecode.OK)
+
def testIfMatch(self):
request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
out_headers = http_headers.Headers()
@@ -72,7 +79,7 @@
request.headers.setRawHeaders("If-Match", ('"frob"',))
self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
- ## Actually set the ETag header
+ # Actually set the ETag header
out_headers.setHeader("ETag", http_headers.ETag('foo'))
out_headers.setHeader("Last-Modified", 946771200) # Sun, 02 Jan 2000 00:00:00 GMT
@@ -99,6 +106,7 @@
request.headers.setRawHeaders("If-Match", ('W/"foo"',))
self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
+
def testIfUnmodifiedSince(self):
request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
out_headers = http_headers.Headers()
@@ -149,9 +157,9 @@
self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
# With a non-GET method
- request.method="PUT"
+ request.method = "PUT"
self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
- request.method="GET"
+ request.method = "GET"
request.headers.setRawHeaders("If-Modified-Since", ('Sat, 01 Jan 2000 00:00:00 GMT',))
self.checkPreconditions(request, response, True, responsecode.OK)
@@ -169,6 +177,7 @@
request.headers.setHeader("If-Modified-Since", time.time() + 500)
self.checkPreconditions(request, response, True, responsecode.OK)
+
def testIfNoneMatch(self):
request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
out_headers = http_headers.Headers()
@@ -182,24 +191,24 @@
# behavior of entityExists
request.headers.setRawHeaders("If-None-Match", ('*',))
- request.method="PUT"
+ request.method = "PUT"
self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
- request.method="GET"
+ request.method = "GET"
self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
self.checkPreconditions(request, response, True, responsecode.OK, entityExists=False)
# tag matches
request.headers.setRawHeaders("If-None-Match", ('"frob", "foo"',))
- request.method="PUT"
+ request.method = "PUT"
self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
- request.method="GET"
+ request.method = "GET"
self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
# now with IMS, also:
request.headers.setRawHeaders("If-Modified-Since", ('Mon, 03 Jan 2000 00:00:00 GMT',))
- request.method="PUT"
+ request.method = "PUT"
self.checkPreconditions(request, response, False, responsecode.PRECONDITION_FAILED)
- request.method="GET"
+ request.method = "GET"
self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
request.headers.setRawHeaders("If-Modified-Since", ('Sat, 01 Jan 2000 00:00:00 GMT',))
@@ -232,15 +241,16 @@
self.checkPreconditions(request, response, False, responsecode.NOT_MODIFIED)
# Weak tags not okay for other methods
- request.method="PUT"
+ request.method = "PUT"
out_headers.setHeader("ETag", http_headers.ETag('foo', weak=True))
request.headers.setRawHeaders("If-None-Match", ('W/"foo"',))
self.checkPreconditions(request, response, True, responsecode.OK)
+
def testNoResponse(self):
# Ensure that passing etag/lastModified arguments instead of response works.
request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
- request.method="PUT"
+ request.method = "PUT"
request.headers.setRawHeaders("If-None-Match", ('"foo"',))
self.checkPreconditions(request, None, True, responsecode.OK)
@@ -249,10 +259,12 @@
lastModified=946771200)
# Make sure that, while you shoudn't do this, that it doesn't cause an error
- request.method="GET"
+ request.method = "GET"
self.checkPreconditions(request, None, False, responsecode.NOT_MODIFIED,
etag=http_headers.ETag('foo'))
+
+
class IfRangeTestCase(unittest.TestCase):
def testIfRange(self):
request = http.Request(None, "GET", "/", "HTTP/1.1", 0, http_headers.Headers())
@@ -302,6 +314,7 @@
class LoopbackRelay(loopback.LoopbackRelay):
implements(interfaces.IProducer)
+
def pauseProducing(self):
self.paused = True
@@ -330,6 +343,7 @@
return address.IPv4Address('TCP', 'localhost', 4321)
+
class TestRequestMixin(object):
def __init__(self, *args, **kwargs):
super(TestRequestMixin, self).__init__(*args, **kwargs)
@@ -338,81 +352,105 @@
headers.sort()
self.cmds.append(('init', self.method, self.uri, self.clientproto, self.stream.length, tuple(headers)))
+
def process(self):
pass
+
+
def handleContentChunk(self, data):
self.cmds.append(('contentChunk', data))
+
def handleContentComplete(self):
self.cmds.append(('contentComplete',))
+
def connectionLost(self, reason):
self.cmds.append(('connectionLost', reason))
+
def _finished(self, x):
self._reallyFinished(x)
+
class TestRequest(TestRequestMixin, http.Request):
"""
Stub request for testing.
"""
+
class TestSSLRedirectRequest(TestRequestMixin, SSLRedirectRequest):
"""
Stub request for HSTS testing.
"""
+
class TestResponse(object):
implements(iweb.IResponse)
code = responsecode.OK
headers = None
+
def __init__(self):
self.headers = http_headers.Headers()
self.stream = stream.ProducerStream()
+
def write(self, data):
self.stream.write(data)
+
def finish(self):
self.stream.finish()
+
+
class TestClient(protocol.Protocol):
data = ""
done = False
+
def dataReceived(self, data):
- self.data+=data
+ self.data += data
+
def write(self, data):
self.transport.write(data)
+
def connectionLost(self, reason):
self.done = True
self.transport.loseConnection()
+
def loseConnection(self):
self.done = True
self.transport.loseConnection()
+
+
class TestConnection:
def __init__(self):
self.requests = []
self.client = None
self.callLaters = []
+
def fakeCallLater(self, secs, f):
assert secs == 0
self.callLaters.append(f)
+
+
class HTTPTests(unittest.TestCase):
requestClass = TestRequest
+
def setUp(self):
super(HTTPTests, self).setUp()
@@ -441,6 +479,7 @@
return cxn
+
def iterate(self, cxn):
callLaters = cxn.callLaters
cxn.callLaters = []
@@ -453,6 +492,7 @@
if cxn.clientToServer.shouldLose:
cxn.clientToServer.clearBuffer()
+
def compareResult(self, cxn, cmds, data):
self.iterate(cxn)
for receivedRequest, expectedCommands in map(None, cxn.requests, cmds):
@@ -467,16 +507,20 @@
self.assertEquals(receivedRequest.cmds, sortedHeaderCommands)
self.assertEquals(cxn.client.data, data)
+
def assertDone(self, cxn, done=True):
self.iterate(cxn)
self.assertEquals(cxn.client.done, done)
+
class GracefulShutdownTestCase(HTTPTests):
+
def _callback(self, result):
self.callbackFired = True
+
def testAllConnectionsClosedWithoutConnectedChannels(self):
"""
allConnectionsClosed( ) should fire right away if no connected channels
@@ -487,6 +531,7 @@
factory.allConnectionsClosed().addCallback(self._callback)
self.assertTrue(self.callbackFired) # now!
+
def testallConnectionsClosedWithConnectedChannels(self):
"""
allConnectionsClosed( ) should only fire after all connected channels
@@ -511,11 +556,13 @@
self.assertTrue(self.callbackFired) # now!
+
class CoreHTTPTestCase(HTTPTests):
# Note: these tests compare the client output using string
# matching. It is acceptable for this to change and break
# the test if you know what you are doing.
+
def testHTTP0_9(self, nouri=False):
cxn = self.connect()
cmds = [[]]
@@ -528,7 +575,7 @@
# Second request which should not be handled
cxn.client.write("GET /two\r\n")
- cmds[0] += [('init', 'GET', '/', (0,9), 0, ()), ('contentComplete',)]
+ cmds[0] += [('init', 'GET', '/', (0, 9), 0, ()), ('contentComplete',)]
self.compareResult(cxn, cmds, data)
response = TestResponse()
@@ -547,9 +594,11 @@
self.assertDone(cxn)
+
def testHTTP0_9_nouri(self):
self.testHTTP0_9(True)
+
def testHTTP1_0(self):
cxn = self.connect()
cmds = [[]]
@@ -559,7 +608,7 @@
# Second request which should not be handled
cxn.client.write("GET /two HTTP/1.0\r\n\r\n")
- cmds[0] += [('init', 'GET', '/', (1,0), 5,
+ cmds[0] += [('init', 'GET', '/', (1, 0), 5,
(('Host', ['localhost']),)),
('contentChunk', 'Input'),
('contentComplete',)]
@@ -582,6 +631,7 @@
self.assertDone(cxn)
+
def testHTTP1_0_keepalive(self):
cxn = self.connect()
cmds = [[]]
@@ -592,14 +642,14 @@
# Third request shouldn't be handled
cxn.client.write("GET /three HTTP/1.0\r\n\r\n")
- cmds[0] += [('init', 'GET', '/', (1,0), 5,
+ cmds[0] += [('init', 'GET', '/', (1, 0), 5,
(('Host', ['localhost']),)),
('contentChunk', 'Input'),
('contentComplete',)]
self.compareResult(cxn, cmds, data)
response0 = TestResponse()
- response0.headers.setRawHeaders("Content-Length", ("6", ))
+ response0.headers.setRawHeaders("Content-Length", ("6",))
response0.headers.setRawHeaders("Yo", ("One", "Two"))
cxn.requests[0].writeResponse(response0)
response0.write("")
@@ -615,13 +665,13 @@
# Now for second request:
cmds.append([])
- cmds[1] += [('init', 'GET', '/two', (1,0), 0, ()),
+ cmds[1] += [('init', 'GET', '/two', (1, 0), 0, ()),
('contentComplete',)]
self.compareResult(cxn, cmds, data)
response1 = TestResponse()
- response1.headers.setRawHeaders("Content-Length", ("0", ))
+ response1.headers.setRawHeaders("Content-Length", ("0",))
cxn.requests[1].writeResponse(response1)
response1.write("")
@@ -631,6 +681,7 @@
self.assertDone(cxn)
+
def testHTTP1_1_pipelining(self):
cxn = self.connect(maxPipeline=2)
cmds = []
@@ -645,19 +696,19 @@
cxn.client.write("GET /four HTTP/1.1\r\nHost: localhost\r\n\r\n")
cmds.append([])
- cmds[0] += [('init', 'GET', '/', (1,1), 5,
+ cmds[0] += [('init', 'GET', '/', (1, 1), 5,
(('Host', ['localhost']),)),
('contentChunk', 'Input'),
('contentComplete',)]
cmds.append([])
- cmds[1] += [('init', 'GET', '/two', (1,1), 0,
+ cmds[1] += [('init', 'GET', '/two', (1, 1), 0,
(('Host', ['localhost']),)),
('contentComplete',)]
self.compareResult(cxn, cmds, data)
response0 = TestResponse()
- response0.headers.setRawHeaders("Content-Length", ("6", ))
+ response0.headers.setRawHeaders("Content-Length", ("6",))
cxn.requests[0].writeResponse(response0)
response0.write("")
@@ -672,7 +723,7 @@
# Now the third request gets read:
cmds.append([])
- cmds[2] += [('init', 'GET', '/three', (1,1), 0,
+ cmds[2] += [('init', 'GET', '/three', (1, 1), 0,
(('Host', ['localhost']),)),
('contentComplete',)]
self.compareResult(cxn, cmds, data)
@@ -680,7 +731,7 @@
# Let's write out the third request before the second.
# This should not cause anything to be written to the client.
response2 = TestResponse()
- response2.headers.setRawHeaders("Content-Length", ("5", ))
+ response2.headers.setRawHeaders("Content-Length", ("5",))
cxn.requests[2].writeResponse(response2)
response2.write("Three")
@@ -689,7 +740,7 @@
self.compareResult(cxn, cmds, data)
response1 = TestResponse()
- response1.headers.setRawHeaders("Content-Length", ("3", ))
+ response1.headers.setRawHeaders("Content-Length", ("3",))
cxn.requests[1].writeResponse(response1)
response1.write("Two")
@@ -700,7 +751,7 @@
# Fourth request shows up
cmds.append([])
- cmds[3] += [('init', 'GET', '/four', (1,1), 0,
+ cmds[3] += [('init', 'GET', '/four', (1, 1), 0,
(('Host', ['localhost']),)),
('contentComplete',)]
data += "HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nThree"
@@ -718,20 +769,21 @@
cxn.client.loseConnection()
self.assertDone(cxn)
+
def testHTTP1_1_chunking(self, extraHeaders=""):
cxn = self.connect()
cmds = [[]]
data = ""
cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\nHost: localhost\r\n\r\n5\r\nInput\r\n")
- cmds[0] += [('init', 'GET', '/', (1,1), None,
+ cmds[0] += [('init', 'GET', '/', (1, 1), None,
(('Host', ['localhost']),)),
('contentChunk', 'Input')]
self.compareResult(cxn, cmds, data)
cxn.client.write("1; blahblahblah\r\na\r\n10\r\nabcdefghijklmnop\r\n")
- cmds[0] += [('contentChunk', 'a'),('contentChunk', 'abcdefghijklmnop')]
+ cmds[0] += [('contentChunk', 'a'), ('contentChunk', 'abcdefghijklmnop')]
self.compareResult(cxn, cmds, data)
cxn.client.write("0\r\nRandom-Ignored-Trailer: foo\r\n\r\n")
@@ -765,12 +817,13 @@
cxn.client.loseConnection()
self.assertDone(cxn)
+
def testHTTP1_1_expect_continue(self):
cxn = self.connect()
cmds = [[]]
data = ""
cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\nExpect: 100-continue\r\n\r\n")
- cmds[0] += [('init', 'GET', '/', (1,1), 5,
+ cmds[0] += [('init', 'GET', '/', (1, 1), 5,
(('Expect', ['100-continue']), ('Host', ['localhost'])))]
self.compareResult(cxn, cmds, data)
@@ -795,12 +848,13 @@
cxn.client.loseConnection()
self.assertDone(cxn)
+
def testHTTP1_1_expect_continue_early_reply(self):
cxn = self.connect()
cmds = [[]]
data = ""
cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\nExpect: 100-continue\r\n\r\n")
- cmds[0] += [('init', 'GET', '/', (1,1), 5,
+ cmds[0] += [('init', 'GET', '/', (1, 1), 5,
(('Host', ['localhost']), ('Expect', ['100-continue'])))]
self.compareResult(cxn, cmds, data)
@@ -817,13 +871,14 @@
cxn.client.loseConnection()
self.assertDone(cxn)
+
def testHeaderContinuation(self):
cxn = self.connect()
cmds = [[]]
data = ""
cxn.client.write("GET / HTTP/1.1\r\nHost: localhost\r\nFoo: yada\r\n yada\r\n\r\n")
- cmds[0] += [('init', 'GET', '/', (1,1), 0,
+ cmds[0] += [('init', 'GET', '/', (1, 1), 0,
(('Host', ['localhost']), ('Foo', ['yada yada']),)),
('contentComplete',)]
self.compareResult(cxn, cmds, data)
@@ -831,23 +886,26 @@
cxn.client.loseConnection()
self.assertDone(cxn)
+
def testTimeout_immediate(self):
# timeout 0 => timeout on first iterate call
- cxn = self.connect(inputTimeOut = 0)
+ cxn = self.connect(inputTimeOut=0)
return deferLater(reactor, 0, self.assertDone, cxn)
+
def testTimeout_inRequest(self):
- cxn = self.connect(inputTimeOut = 0.3)
+ cxn = self.connect(inputTimeOut=0.3)
cxn.client.write("GET / HTTP/1.1\r\n")
return deferLater(reactor, 0.5, self.assertDone, cxn)
+
def testTimeout_betweenRequests(self):
- cxn = self.connect(betweenRequestsTimeOut = 0.3)
+ cxn = self.connect(betweenRequestsTimeOut=0.3)
cmds = [[]]
data = ""
cxn.client.write("GET / HTTP/1.1\r\n\r\n")
- cmds[0] += [('init', 'GET', '/', (1,1), 0, ()),
+ cmds[0] += [('init', 'GET', '/', (1, 1), 0, ()),
('contentComplete',)]
self.compareResult(cxn, cmds, data)
@@ -861,6 +919,7 @@
self.compareResult(cxn, cmds, data)
return deferLater(reactor, 0.5, self.assertDone, cxn) # Wait for timeout
+
def testTimeout_idleRequest(self):
cxn = self.connect(idleTimeOut=0.3)
cmds = [[]]
@@ -873,6 +932,7 @@
return deferLater(reactor, 0.5, self.assertDone, cxn) # Wait for timeout
+
def testTimeout_abortRequest(self):
cxn = self.connect(allowPersistentConnections=False, closeTimeOut=0.3)
cxn.client.transport.loseConnection = lambda : None
@@ -897,19 +957,20 @@
self.assertTrue(cxn.serverToClient.aborted)
return deferLater(reactor, 0.5, self.assertDone, cxn) # Wait for timeout
+
def testConnectionCloseRequested(self):
cxn = self.connect()
cmds = [[]]
data = ""
cxn.client.write("GET / HTTP/1.1\r\n\r\n")
- cmds[0] += [('init', 'GET', '/', (1,1), 0, ()),
+ cmds[0] += [('init', 'GET', '/', (1, 1), 0, ()),
('contentComplete',)]
self.compareResult(cxn, cmds, data)
cxn.client.write("GET / HTTP/1.1\r\nConnection: close\r\n\r\n")
cmds.append([])
- cmds[1] += [('init', 'GET', '/', (1,1), 0, ()),
+ cmds[1] += [('init', 'GET', '/', (1, 1), 0, ()),
('contentComplete',)]
self.compareResult(cxn, cmds, data)
@@ -930,6 +991,7 @@
self.compareResult(cxn, cmds, data)
self.assertDone(cxn)
+
def testConnectionKeepAliveOff(self):
cxn = self.connect(allowPersistentConnections=False)
cmds = [[]]
@@ -950,6 +1012,7 @@
self.compareResult(cxn, cmds, data)
self.assertDone(cxn)
+
def testExtraCRLFs(self):
cxn = self.connect()
cmds = [[]]
@@ -957,7 +1020,7 @@
# Some broken clients (old IEs) send an extra CRLF after post
cxn.client.write("POST / HTTP/1.1\r\nContent-Length: 5\r\nHost: localhost\r\n\r\nInput\r\n")
- cmds[0] += [('init', 'POST', '/', (1,1), 5,
+ cmds[0] += [('init', 'POST', '/', (1, 1), 5,
(('Host', ['localhost']),)),
('contentChunk', 'Input'),
('contentComplete',)]
@@ -966,20 +1029,21 @@
cxn.client.write("GET /two HTTP/1.1\r\n\r\n")
cmds.append([])
- cmds[1] += [('init', 'GET', '/two', (1,1), 0, ()),
+ cmds[1] += [('init', 'GET', '/two', (1, 1), 0, ()),
('contentComplete',)]
self.compareResult(cxn, cmds, data)
cxn.client.loseConnection()
self.assertDone(cxn)
+
def testDisallowPersistentConnections(self):
cxn = self.connect(allowPersistentConnections=False)
cmds = [[]]
data = ""
cxn.client.write("GET / HTTP/1.1\r\nHost: localhost\r\n\r\nGET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
- cmds[0] += [('init', 'GET', '/', (1,1), 0,
+ cmds[0] += [('init', 'GET', '/', (1, 1), 0,
(('Host', ['localhost']),)),
('contentComplete',)]
self.compareResult(cxn, cmds, data)
@@ -991,6 +1055,7 @@
self.compareResult(cxn, cmds, data)
self.assertDone(cxn)
+
def testIgnoreBogusContentLength(self):
# Ensure that content-length is ignored when transfer-encoding
# is also specified.
@@ -999,7 +1064,7 @@
data = ""
cxn.client.write("GET / HTTP/1.1\r\nContent-Length: 100\r\nTransfer-Encoding: chunked\r\nHost: localhost\r\n\r\n5\r\nInput\r\n")
- cmds[0] += [('init', 'GET', '/', (1,1), None,
+ cmds[0] += [('init', 'GET', '/', (1, 1), None,
(('Host', ['localhost']),)),
('contentChunk', 'Input')]
@@ -1018,56 +1083,67 @@
cxn.client.loseConnection()
self.assertDone(cxn)
+
+
class ErrorTestCase(HTTPTests):
+
def assertStartsWith(self, first, second, msg=None):
self.assert_(first.startswith(second), '%r.startswith(%r)' % (first, second))
+
def checkError(self, cxn, code):
self.iterate(cxn)
- self.assertStartsWith(cxn.client.data, "HTTP/1.1 %d "%code)
+ self.assertStartsWith(cxn.client.data, "HTTP/1.1 %d " % code)
self.assertIn("\r\nConnection: close\r\n", cxn.client.data)
# Ensure error messages have a defined content-length.
self.assertIn("\r\nContent-Length:", cxn.client.data)
self.assertDone(cxn)
+
def testChunkingError1(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\nasdf\r\n")
self.checkError(cxn, 400)
+
def testChunkingError2(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n1\r\nblahblah\r\n")
self.checkError(cxn, 400)
+
def testChunkingError3(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n-1\r\nasdf\r\n")
self.checkError(cxn, 400)
+
def testTooManyHeaders(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/1.1\r\n")
- cxn.client.write("Foo: Bar\r\n"*5000)
+ cxn.client.write("Foo: Bar\r\n" * 5000)
self.checkError(cxn, 400)
+
def testLineTooLong(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/1.1\r\n")
- cxn.client.write("Foo: "+("Bar"*10000))
+ cxn.client.write("Foo: " + ("Bar" * 10000))
self.checkError(cxn, 400)
+
def testLineTooLong2(self):
cxn = self.connect()
- cxn.client.write("GET "+("/Bar")*10000 +" HTTP/1.1\r\n")
+ cxn.client.write("GET " + ("/Bar") * 10000 + " HTTP/1.1\r\n")
self.checkError(cxn, 414)
+
def testNoColon(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/1.1\r\n")
@@ -1096,57 +1172,67 @@
self.checkError(cxn, 400)
+
def testWrongProtocol(self):
cxn = self.connect()
cxn.client.write("GET / Foobar/1.0\r\n")
self.checkError(cxn, 400)
+
def testBadProtocolVersion(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/1\r\n")
self.checkError(cxn, 400)
+
def testBadProtocolVersion2(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/-1.0\r\n")
self.checkError(cxn, 400)
+
def testWrongProtocolVersion(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/2.0\r\n")
self.checkError(cxn, 505)
+
def testUnsupportedTE(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/1.1\r\n")
cxn.client.write("Transfer-Encoding: blahblahblah, chunked\r\n\r\n")
self.checkError(cxn, 501)
+
def testTEWithoutChunked(self):
cxn = self.connect()
cxn.client.write("GET / HTTP/1.1\r\n")
cxn.client.write("Transfer-Encoding: gzip\r\n\r\n")
self.checkError(cxn, 400)
+
+
class PipelinedErrorTestCase(ErrorTestCase):
# Make sure that even low level reading errors don't corrupt the data stream,
# but always wait until their turn to respond.
+
def connect(self):
cxn = ErrorTestCase.connect(self)
cxn.client.write("GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
- cmds = [[('init', 'GET', '/', (1,1), 0,
+ cmds = [[('init', 'GET', '/', (1, 1), 0,
(('Host', ['localhost']),)),
- ('contentComplete', )]]
+ ('contentComplete',)]]
data = ""
self.compareResult(cxn, cmds, data)
return cxn
+
def checkError(self, cxn, code):
self.iterate(cxn)
self.assertEquals(cxn.client.data, '')
@@ -1167,7 +1253,9 @@
ErrorTestCase.checkError(self, cxn, code)
+
class SimpleFactory(channel.HTTPFactory):
+
def buildProtocol(self, addr):
# Do a bunch of crazy crap just so that the test case can know when the
# connection is done.
@@ -1180,21 +1268,28 @@
self.conn = p
return p
+
+
class SimpleRequest(http.Request):
+
def process(self):
response = TestResponse()
if self.uri == "/error":
- response.code=402
+ response.code = 402
elif self.uri == "/forbidden":
- response.code=403
+ response.code = 403
else:
- response.code=404
+ response.code = 404
response.write("URI %s unrecognized." % self.uri)
response.finish()
self.writeResponse(response)
+
+
class AbstractServerTestMixin:
+
type = None
+
def testBasicWorkingness(self):
args = ('-u', util.sibpath(__file__, "simple_client.py"), "basic",
str(self.port), self.type)
@@ -1202,12 +1297,14 @@
utils.getProcessOutputAndValue(sys.executable, args=args,
env=os.environ)
)
- yield d; out,err,code = d.getResult()
+ yield d
+ out, err, code = d.getResult()
self.assertEquals(code, 0, "Error output:\n%s" % (err,))
self.assertEquals(out, "HTTP/1.1 402 Payment Required\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
testBasicWorkingness = deferredGenerator(testBasicWorkingness)
+
def testLingeringClose(self):
args = ('-u', util.sibpath(__file__, "simple_client.py"),
"lingeringClose", str(self.port), self.type)
@@ -1215,15 +1312,20 @@
utils.getProcessOutputAndValue(sys.executable, args=args,
env=os.environ)
)
- yield d; out,err,code = d.getResult()
+ yield d
+ out, err, code = d.getResult()
self.assertEquals(code, 0, "Error output:\n%s" % (err,))
self.assertEquals(out, "HTTP/1.1 402 Payment Required\r\nContent-Length: 0\r\nConnection: close\r\n\r\n")
testLingeringClose = deferredGenerator(testLingeringClose)
+
+
class TCPServerTest(unittest.TestCase, AbstractServerTestMixin):
+
type = 'tcp'
+
def setUp(self):
- factory=SimpleFactory(requestFactory=SimpleRequest)
+ factory = SimpleFactory(requestFactory=SimpleRequest)
factory.testcase = self
self.factory = factory
@@ -1232,6 +1334,7 @@
self.socket = reactor.listenTCP(0, factory)
self.port = self.socket.getHost().port
+
def tearDown(self):
# Make sure the listening port is closed
d = defer.maybeDeferred(self.socket.stopListening)
@@ -1260,7 +1363,7 @@
def setUp(self):
HTTPChannel.allowPersistentConnections = True
sCTX = ssl.DefaultOpenSSLContextFactory(certPath, certPath)
- factory=SimpleFactory(requestFactory=SimpleRequest)
+ factory = SimpleFactory(requestFactory=SimpleRequest)
factory.testcase = self
self.factory = factory
@@ -1269,6 +1372,7 @@
self.socket = reactor.listenSSL(0, factory, sCTX)
self.port = self.socket.getHost().port
+
def tearDown(self):
# Make sure the listening port is closed
d = defer.maybeDeferred(self.socket.stopListening)
@@ -1279,6 +1383,7 @@
return self.connlost
return d.addCallback(finish)
+
def testLingeringClose(self):
return super(SSLServerTest, self).testLingeringClose()
Modified: CalendarServer/trunk/txweb2/test/test_http_headers.py
===================================================================
--- CalendarServer/trunk/txweb2/test/test_http_headers.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/test_http_headers.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -244,7 +244,7 @@
{'private': ['set-cookie', 'set-cookie2'],
'no-cache': ['proxy-authenticate']},
['private="Set-Cookie, Set-Cookie2"', 'no-cache="Proxy-Authenticate"']),
- )
+ )
self.runRoundtripTest("Cache-Control", table)
@@ -252,7 +252,7 @@
table = (
("close", ['close', ]),
("close, foo-bar", ['close', 'foo-bar'])
- )
+ )
self.runRoundtripTest("Connection", table)
@@ -271,7 +271,7 @@
table = (
('chunked', ['chunked']),
('gzip, chunked', ['gzip', 'chunked'])
- )
+ )
self.runRoundtripTest("Transfer-Encoding", table)
# def testUpgrade(self):
@@ -305,13 +305,13 @@
http_headers.MimeType('text', 'html', (('level', '1'),)): 1.0,
http_headers.MimeType('*', '*'): 1.0}),
- ("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5",
+ ("text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4, */*;q=0.5",
{http_headers.MimeType('text', '*'): 0.3,
http_headers.MimeType('text', 'html'): 0.7,
http_headers.MimeType('text', 'html', (('level', '1'),)): 1.0,
http_headers.MimeType('text', 'html', (('level', '2'),)): 0.4,
http_headers.MimeType('*', '*'): 0.5}),
- )
+ )
self.runRoundtripTest("Accept", table)
@@ -329,7 +329,7 @@
("",
{'iso-8859-1': 1.0},
["iso-8859-1"]), # Yes this is an actual change -- we'll say that's okay. :)
- )
+ )
self.runRoundtripTest("Accept-Charset", table)
@@ -347,7 +347,7 @@
("gzip;q=1.0, identity;q=0.5, *;q=0",
{'gzip': 1.0, 'identity': 0.5, '*': 0},
["gzip", "identity;q=0.5", "*;q=0"]),
- )
+ )
self.runRoundtripTest("Accept-Encoding", table)
@@ -357,7 +357,7 @@
{'da': 1.0, 'en-gb': 0.8, 'en': 0.7}),
("*",
{'*': 1}),
- )
+ )
self.runRoundtripTest("Accept-Language", table)
@@ -369,7 +369,7 @@
('Digest nonce="bar", realm="foo", username="baz", response="bax"',
('digest', 'nonce="bar", realm="foo", username="baz", response="bax"'),
['digest', 'nonce="bar"', 'realm="foo"', 'username="baz"', 'response="bax"'])
- )
+ )
self.runRoundtripTest("Authorization", table)
@@ -381,10 +381,12 @@
('name,"blah=value,"', [Cookie('name,"blah', 'value,"')]),
('name,"blah = value," ', [Cookie('name,"blah', 'value,"')], ['name,"blah=value,"']),
("`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?=`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?", [Cookie("`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?", "`~!@#$%^&*()-_+[{]}\\|:'\",<.>/?")]),
- ('name,"blah = value," ; name2=val2',
- [Cookie('name,"blah', 'value,"'), Cookie('name2', 'val2')],
- ['name,"blah=value,"', 'name2=val2']),
- )
+ (
+ 'name,"blah = value," ; name2=val2',
+ [Cookie('name,"blah', 'value,"'), Cookie('name2', 'val2')],
+ ['name,"blah=value,"', 'name2=val2']
+ ),
+ )
self.runRoundtripTest("Cookie", table)
# newstyle RFC2965 Cookie
@@ -399,7 +401,7 @@
('$Version = 1, NAME = "qq\\"qq",Frob=boo',
[Cookie('name', 'qq"qq', version=1), Cookie('frob', 'boo', version=1)],
['$Version="1";name="qq\\"qq";frob="boo"']),
- )
+ )
self.runRoundtripTest("Cookie", table2)
# Generate only!
@@ -413,7 +415,7 @@
'$Version="1";name2="value2"'),
([Cookie('name', 'qq"qq'), Cookie('name2', 'value2', version=1)],
'$Version="1";name="qq\\"qq";name2="value2"'),
- )
+ )
for row in table3:
self.assertEquals(generateHeader("Cookie", row[0]), [row[1], ])
@@ -425,7 +427,7 @@
('name,"blah = value, ; expires="Sun, 09 Sep 2001 01:46:40 GMT"',
[Cookie('name,"blah', 'value,', expires=1000000000)],
['name,"blah=value,', 'expires=Sun, 09 Sep 2001 01:46:40 GMT']),
- )
+ )
self.runRoundtripTest("Set-Cookie", table)
@@ -433,7 +435,7 @@
table = (
('name="value"; Comment="YadaYada"; CommentURL="http://frobnotz/"; Discard; Domain="blah.blah"; Max-Age=10; Path="/foo"; Port="80,8080"; Secure; Version="1"',
[Cookie("name", "value", comment="YadaYada", commenturl="http://frobnotz/", discard=True, domain="blah.blah", expires=1000000000, path="/foo", ports=(80, 8080), secure=True, version=1)]),
- )
+ )
self.runRoundtripTest("Set-Cookie2", table)
@@ -445,7 +447,7 @@
{'foobar': ('twiddle',)}),
("foo=bar;a=b;c",
{'foo': ('bar', ('a', 'b'), ('c', None))})
- )
+ )
self.runRoundtripTest("Expect", table)
@@ -457,7 +459,7 @@
[("return", "representation", [])]),
("return =minimal;arg1;arg2=val2",
[("return", "minimal", [("arg1", None), ("arg2", "val2")])]),
- )
+ )
self.runRoundtripTest("Prefer", table)
@@ -473,10 +475,10 @@
table = (
('"xyzzy"', [http_headers.ETag('xyzzy')]),
('"xyzzy", "r2d2xxxx", "c3piozzzz"', [http_headers.ETag('xyzzy'),
- http_headers.ETag('r2d2xxxx'),
- http_headers.ETag('c3piozzzz')]),
+ http_headers.ETag('r2d2xxxx'),
+ http_headers.ETag('c3piozzzz')]),
('*', ['*']),
- )
+ )
self.runRoundtripTest("If-Match", table)
@@ -486,7 +488,7 @@
table = (
("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),
("Sun, 09 Sep 2001 01:46:40 GMT; length=500", 1000000000, ["Sun, 09 Sep 2001 01:46:40 GMT"]),
- )
+ )
self.runRoundtripTest("If-Modified-Since", table)
@@ -501,7 +503,7 @@
http_headers.ETag('r2d2xxxx', weak=True),
http_headers.ETag('c3piozzzz', weak=True)]),
('*', ['*']),
- )
+ )
self.runRoundtripTest("If-None-Match", table)
@@ -511,7 +513,7 @@
('W/"xyzzy"', http_headers.ETag('xyzzy', weak=True)),
('W/"xyzzy"', http_headers.ETag('xyzzy', weak=True)),
("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000),
- )
+ )
self.runRoundtripTest("If-Range", table)
@@ -534,7 +536,7 @@
("bytes=-500", ('bytes', [(None, 500), ])),
("bytes=9500-", ('bytes', [(9500, None), ])),
("bytes=0-0,-1", ('bytes', [(0, 0), (None, 1)])),
- )
+ )
self.runRoundtripTest("Range", table)
@@ -548,7 +550,7 @@
("deflate", {'deflate': 1}),
("", {}),
("trailers, deflate;q=0.5", {'trailers': 1, 'deflate': 0.5}),
- )
+ )
self.runRoundtripTest("TE", table)
@@ -571,7 +573,7 @@
('"xyzzy"', http_headers.ETag('xyzzy')),
('W/"xyzzy"', http_headers.ETag('xyzzy', weak=True)),
('""', http_headers.ETag('')),
- )
+ )
self.runRoundtripTest("ETag", table)
@@ -589,7 +591,7 @@
table = (
("Sun, 09 Sep 2001 01:46:40 GMT", 1000000000, ["10"]),
("120", 999999990 + 120),
- )
+ )
self.runRoundtripTest("Retry-After", table)
@@ -601,7 +603,7 @@
table = (
("*", ["*"]),
("Accept, Accept-Encoding", ["accept", "accept-encoding"], ["accept", "accept-encoding"])
- )
+ )
self.runRoundtripTest("Vary", table)
@@ -685,14 +687,14 @@
table = (
("GET", ['GET', ]),
("GET, HEAD, PUT", ['GET', 'HEAD', 'PUT']),
- )
+ )
self.runRoundtripTest("Allow", table)
def testContentEncoding(self):
table = (
("gzip", ['gzip', ]),
- )
+ )
self.runRoundtripTest("Content-Encoding", table)
@@ -700,7 +702,7 @@
table = (
("da", ['da', ]),
("mi, en", ['mi', 'en']),
- )
+ )
self.runRoundtripTest("Content-Language", table)
@@ -729,7 +731,7 @@
("bytes 734-1233/*", ("bytes", 734, 1233, None)),
("bytes */1234", ("bytes", None, None, 1234)),
("bytes */*", ("bytes", None, None, None))
- )
+ )
self.runRoundtripTest("Content-Range", table)
@@ -737,7 +739,7 @@
table = (
("text/html;charset=iso-8859-4", http_headers.MimeType('text', 'html', (('charset', 'iso-8859-4'),))),
("text/html", http_headers.MimeType('text', 'html')),
- )
+ )
self.runRoundtripTest("Content-Type", table)
@@ -745,7 +747,7 @@
table = (
("attachment;filename=foo.txt", http_headers.MimeDisposition('attachment', (('filename', 'foo.txt'),))),
("inline", http_headers.MimeDisposition('inline')),
- )
+ )
self.runRoundtripTest("Content-Disposition", table)
@@ -837,12 +839,9 @@
"""Test that various uses of the constructer are equal
"""
- kwargMime = http_headers.MimeDisposition('attachment',
- key='value')
- dictMime = http_headers.MimeDisposition('attachment',
- {'key': 'value'})
- tupleMime = http_headers.MimeDisposition('attachment',
- (('key', 'value'),))
+ kwargMime = http_headers.MimeDisposition('attachment', key='value')
+ dictMime = http_headers.MimeDisposition('attachment', {'key': 'value'})
+ tupleMime = http_headers.MimeDisposition('attachment', (('key', 'value'),))
stringMime = http_headers.MimeDisposition.fromString('attachment;key=value')
Modified: CalendarServer/trunk/txweb2/test/test_httpauth.py
===================================================================
--- CalendarServer/trunk/txweb2/test/test_httpauth.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/test_httpauth.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -32,6 +32,7 @@
"""
return nonce
+
def _fakeStaticTime():
"""
Return a stable time
@@ -77,8 +78,7 @@
Test acceptance of username/password in basic auth.
"""
response = base64.encodestring('%s:%s' % (
- self.username,
- self.password))
+ self.username, self.password))
d = self.credentialFactory.decode(response, _trivial_GET)
return d.addCallback(
@@ -90,8 +90,7 @@
Incorrect passwords cause auth to fail.
"""
response = base64.encodestring('%s:%s' % (
- self.username,
- 'incorrectPassword'))
+ self.username, 'incorrectPassword'))
d = self.credentialFactory.decode(response, _trivial_GET)
return d.addCallback(
@@ -103,8 +102,7 @@
Responses that have incorrect padding cause auth to fail.
"""
response = base64.encodestring('%s:%s' % (
- self.username,
- self.password))
+ self.username, self.password))
response = response.strip('=')
@@ -164,6 +162,7 @@
self.credentialFactory = digest.DigestCredentialFactory('md5',
'test realm')
+
def getDigestResponse(self, challenge, ncount):
"""
Calculate the response for the given challenge
@@ -180,9 +179,10 @@
nonce,
cnonce),
algo, nonce, ncount, cnonce, qop, "GET", "/write/", None
- )
+ )
return expected
+
def test_getChallenge(self):
"""
Test that all the required fields exist in the challenge,
@@ -194,8 +194,8 @@
self.assertEquals(challenge['qop'], 'auth')
self.assertEquals(challenge['realm'], 'test realm')
self.assertEquals(challenge['algorithm'], 'md5')
- self.assertTrue(challenge.has_key("nonce"))
- self.assertTrue(challenge.has_key("opaque"))
+ self.assertTrue("nonce" in challenge)
+ self.assertTrue("opaque" in challenge)
return d.addCallback(_test)
@@ -266,6 +266,7 @@
_trivial_GET)
self.assertEquals(str(e), "Invalid response, no username given.")
+
def test_noNonce(self):
"""
Test that login fails when our response does not contain a nonce
@@ -277,6 +278,7 @@
_trivial_GET)
self.assertEquals(str(e), "Invalid response, no nonce given.")
+
def test_noOpaque(self):
"""
Test that login fails when our response does not contain a nonce
@@ -288,6 +290,7 @@
_trivial_GET)
self.assertEquals(str(e), "Invalid response, no opaque given.")
+
def test_checkHash(self):
"""
Check that given a hash of the form 'username:realm:password'
@@ -296,9 +299,9 @@
d = self._createAndDecodeChallenge()
def _test(creds):
self.failUnless(creds.checkHash(
- md5('username:test realm:password').hexdigest()))
+ md5('username:test realm:password').hexdigest()))
self.failIf(creds.checkHash(
- md5('username:test realm:bogus').hexdigest()))
+ md5('username:test realm:bogus').hexdigest()))
return d.addCallback(_test)
@@ -319,7 +322,7 @@
clientAddress.host)
badOpaque = ('foo-%s' % (
- 'nonce,clientip'.encode('base64').strip('\n'),))
+ 'nonce,clientip'.encode('base64').strip('\n'),))
self.assertRaises(
error.LoginFailed,
@@ -454,7 +457,7 @@
("user", "realm", "password", "preHA1"),
(None, "realm", None, "preHA1"),
(None, None, "password", "preHA1"),
- )
+ )
for pszUsername, pszRealm, pszPassword, preHA1 in arguments:
self.assertRaises(
@@ -467,7 +470,7 @@
"nonce",
"cnonce",
preHA1=preHA1
- )
+ )
def test_noNewlineOpaque(self):
@@ -500,6 +503,7 @@
self.username = username
+
class TestAuthRealm(object):
"""
Test realm that supports the IHTTPUser interface
@@ -517,6 +521,7 @@
raise NotImplementedError("Only IHTTPUser interface is supported")
+
class ProtectedResource(test_server.BaseTestResource):
"""
A test resource for use with HTTPAuthWrapper that holds on to it's
@@ -531,11 +536,13 @@
self.request = req
return super(ProtectedResource, self).render(req)
+
def locateChild(self, req, segments):
self.segments = segments
return super(ProtectedResource, self).locateChild(req, segments)
+
class NonAnonymousResource(test_server.BaseTestResource):
"""
A resource that forces authentication by raising an
@@ -558,6 +565,7 @@
return super(NonAnonymousResource, self).render(req)
+
class HTTPAuthResourceTest(test_server.BaseCase):
"""
Tests for the HTTPAuthWrapper Resource
@@ -580,6 +588,7 @@
self.protectedResource = ProtectedResource()
self.protectedResource.responseText = "You shouldn't see me."
+
def tearDown(self):
"""
Clean up by getting rid of the portal, credentialFactory, and
@@ -589,6 +598,7 @@
del self.credFactory
del self.protectedResource
+
def test_authenticatedRequest(self):
"""
Test that after successful authentication the request provides
@@ -627,6 +637,7 @@
d.addCallback(checkRequest)
return d
+
def test_allowedMethods(self):
"""
Test that unknown methods result in a 401 instead of a 405 when
@@ -650,6 +661,7 @@
return d
+
def test_unauthorizedResponse(self):
"""
Test that a request with no credentials results in a
@@ -678,6 +690,7 @@
return d.addCallback(makeDeepRequest)
+
def test_badCredentials(self):
"""
Test that a request with bad credentials results in a valid
@@ -700,6 +713,7 @@
return d
+
def test_successfulLogin(self):
"""
Test that a request with good credentials results in the
@@ -721,6 +735,7 @@
return d
+
def test_wrongScheme(self):
"""
Test that a request with credentials for a scheme that is not
@@ -743,6 +758,7 @@
return d
+
def test_multipleWWWAuthenticateSchemes(self):
"""
Test that our unauthorized response can contain challenges for
@@ -764,6 +780,7 @@
return d
+
def test_authorizationAgainstMultipleSchemes(self):
"""
Test that we can successfully authenticate when presented
@@ -774,7 +791,7 @@
self.protectedResource,
(basic.BasicCredentialFactory('test realm'),
FakeDigestCredentialFactory('md5', 'test realm')),
- self.portal,
+ self.portal,
interfaces=(IHTTPUser,))
def respondBasic(ign):
@@ -782,9 +799,8 @@
d = self.assertResponse((root, 'http://localhost/',
{'authorization':
- ('basic', credentials)}),
- (200,
- {}, None))
+ ('basic', credentials)}),
+ (200, {}, None))
return d
@@ -805,6 +821,7 @@
return d
+
def test_wrappedResourceGetsFullSegments(self):
"""
Test that the wrapped resource gets all the URL segments in it's
@@ -833,6 +850,7 @@
return d
+
def test_invalidCredentials(self):
"""
Malformed or otherwise invalid credentials (as determined by
@@ -854,6 +872,7 @@
return d
+
def test_anonymousAuthentication(self):
"""
If our portal has a credentials checker for IAnonymous credentials
@@ -883,10 +902,11 @@
return d
+
def test_forceAuthentication(self):
"""
Test that if an HTTPError with an Unauthorized status code is raised
- from within our protected resource, we add the WWW-Authenticate
+ from within our protected resource, we add the WWW-Authenticate
headers if they do not already exist.
"""
self.portal.registerChecker(checkers.AllowAnonymousAccess())
@@ -897,7 +917,7 @@
root = wrapper.HTTPAuthResource(nonAnonResource,
[self.credFactory],
self.portal,
- interfaces = (IHTTPUser,))
+ interfaces=(IHTTPUser,))
def _tryAuthenticate(result):
credentials = base64.encodestring('username:password')
@@ -923,6 +943,7 @@
return d
+
def test_responseFilterDoesntClobberHeaders(self):
"""
Test that if an UNAUTHORIZED response is returned and
@@ -937,7 +958,7 @@
root = wrapper.HTTPAuthResource(nonAnonResource,
[self.credFactory],
self.portal,
- interfaces = (IHTTPUser,))
+ interfaces=(IHTTPUser,))
d = self.assertResponse(
(root, 'http://localhost/',
@@ -949,6 +970,7 @@
return d
+
def test_renderHTTP(self):
"""
Test that if the renderHTTP method is ever called we authenticate
@@ -960,7 +982,7 @@
root = wrapper.HTTPAuthResource(self.protectedResource,
[self.credFactory],
self.portal,
- interfaces = (IHTTPUser,))
+ interfaces=(IHTTPUser,))
request = SimpleRequest(None, "GET", "/")
request.prepath = ['']
Modified: CalendarServer/trunk/txweb2/test/test_log.py
===================================================================
--- CalendarServer/trunk/txweb2/test/test_log.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/test_log.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -16,6 +16,8 @@
def logMessage(self, message):
self.messages.append(message)
+
+
class SetDateWrapperResource(WrapperResource):
"""
A resource wrapper which sets the date header.
@@ -28,6 +30,8 @@
req.addResponseFilter(_filter, atEnd=True)
+
+
class NoneStreamResource(Resource):
"""
A basic empty resource.
@@ -35,6 +39,8 @@
def render(self, req):
return Response(200)
+
+
class TestLogging(BaseCase):
def setUp(self):
self.patch(theLogPublisher, "observers", [])
@@ -47,6 +53,7 @@
self.root = SetDateWrapperResource(LogWrapperResource(self.resrc))
+
def assertLogged(self, **expected):
"""
Check that logged messages matches expected format.
@@ -84,6 +91,7 @@
else:
self.assertEquals(len(messages), 0, "len(%r) != 0" % (messages, ))
+
def test_logSimpleRequest(self):
"""
Check the log for a simple request.
@@ -100,6 +108,7 @@
return d
+
def test_logErrors(self):
"""
Test the error log.
@@ -130,6 +139,7 @@
return d
+
def test_logNoneResponseStream(self):
"""
Test the log of an empty resource.
@@ -145,4 +155,3 @@
d.addCallback(_cbCheckLog)
return d
-
Modified: CalendarServer/trunk/txweb2/test/test_resource.py
===================================================================
--- CalendarServer/trunk/txweb2/test/test_resource.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/test_resource.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -22,6 +22,8 @@
class PreconditionError (Exception):
"Precondition Failure"
+
+
class TestResource (RenderMixin):
implements(IResource)
@@ -38,12 +40,15 @@
def preconditions_BLEARGH(self, request):
raise PreconditionError()
+
def precondition_HUCKHUCKBLORP(self, request):
return fail(None)
+
def preconditions_SWEETHOOKUPS(self, request):
return None
+
def preconditions_HOOKUPS(self, request):
return succeed(None)
@@ -54,11 +59,15 @@
response.stream = MemoryStream(self.renderOutput)
return response
+
+
def generateResponse(method):
resource = TestResource()
method = getattr(resource, "http_" + method)
return method(SimpleRequest(Site(resource), method, "/"))
+
+
class RenderMixInTestCase (unittest.TestCase):
"""
Test RenderMixin.
@@ -130,6 +139,7 @@
d = resource.renderHTTP(request)
d.addCallback(checkResponse)
+
def test_OPTIONS_status(self):
"""
RenderMixin.http_OPTIONS()
@@ -138,6 +148,7 @@
response = generateResponse("OPTIONS")
self.assertEquals(response.code, responsecode.OK)
+
def test_OPTIONS_allow(self):
"""
RenderMixin.http_OPTIONS()
@@ -149,6 +160,7 @@
self._my_allowed_methods
)
+
def test_TRACE_status(self):
"""
RenderMixin.http_TRACE()
@@ -177,6 +189,7 @@
response = generateResponse("HEAD")
self.assertEquals(response.code, responsecode.OK)
+
def test_HEAD_body(self):
"""
RenderMixin.http_HEAD()
@@ -198,6 +211,7 @@
response = generateResponse("GET")
self.assertEquals(response.code, responsecode.OK)
+
def test_GET_body(self):
"""
RenderMixin.http_GET()
@@ -209,6 +223,8 @@
TestResource.renderOutput
)
+
+
class ResourceTestCase (unittest.TestCase):
"""
Test Resource.
@@ -226,6 +242,8 @@
raise NotImplementedError()
test_child_nonsense.todo = "Someone should write this test"
+
+
class PostableResourceTestCase (unittest.TestCase):
"""
Test PostableResource.
@@ -234,6 +252,8 @@
raise NotImplementedError()
test_POST.todo = "Someone should write this test"
+
+
class LeafResourceTestCase (unittest.TestCase):
"""
Test LeafResource.
@@ -249,6 +269,8 @@
self.assertEquals(child, resource)
self.assertEquals(segments, StopTraversal)
+
+
class WrapperResourceTestCase (unittest.TestCase):
"""
Test WrapperResource.
Modified: CalendarServer/trunk/txweb2/test/test_server.py
===================================================================
--- CalendarServer/trunk/txweb2/test/test_server.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/test_server.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -27,6 +27,7 @@
"""
+
@implementer(iweb.IResource)
class ResourceAdapter(object):
"""
@@ -56,6 +57,7 @@
"""
+
@implementer(iweb.IOldNevowResource)
class OldResourceAdapter(object):
"""
@@ -99,7 +101,8 @@
IResource returns the same object.
"""
@implementer(iweb.IResource)
- class Resource(object): ""
+ class Resource(object):
+ ""
resource = Resource()
self.assertIdentical(iweb.IResource(resource), resource)
@@ -123,7 +126,7 @@
def __init__(self, site, method, prepath, uri, length=None,
- headers=None, version=(1,1), content=None):
+ headers=None, version=(1, 1), content=None):
self.producer = None
self.site = site
self.method = method
@@ -152,39 +155,49 @@
self.data = ''
self.deferredFinish = defer.Deferred()
+
def writeIntermediateResponse(code, headers=None):
pass
+
def writeHeaders(self, code, headers):
self.responseHeaders = headers
self.code = code
+
def write(self, data):
self.data += data
+
def finish(self, failed=False):
result = self.code, self.responseHeaders, self.data, failed
self.finished = True
self.deferredFinish.callback(result)
+
def abortConnection(self):
self.finish(failed=True)
+
def registerProducer(self, producer, streaming):
if self.producer is not None:
raise ValueError("Producer still set: " + repr(self.producer))
self.producer = producer
+
def unregisterProducer(self):
self.producer = None
+
def getHostInfo(self):
return self.hostInfo
+
def getRemoteHost(self):
return self.remoteHost
+
class BaseTestResource(resource.Resource):
responseCode = 200
responseText = 'This is a fake resource.'
@@ -199,18 +212,22 @@
for i in children:
self.putChild(i[0], i[1])
+
def render(self, req):
return http.Response(self.responseCode, headers=self.responseHeaders,
stream=self.responseStream())
+
def responseStream(self):
return stream.MemoryStream(self.responseText)
+
class MyRenderError(Exception):
""
+
class ErrorWithProducerResource(BaseTestResource):
addSlash = True
@@ -225,7 +242,6 @@
-
_unset = object()
class BaseCase(unittest.TestCase):
"""
@@ -241,6 +257,7 @@
site = server.Site(root)
return TestChanRequest(site, method, prepath, uri, length, headers, version, content)
+
def getResponseFor(self, root, uri, headers={},
method=None, version=None, prepath='', content=None, length=_unset):
if not isinstance(headers, http_headers.Headers):
@@ -260,6 +277,7 @@
cr.request.process()
return cr.deferredFinish
+
def assertResponse(self, request_data, expected_response, failure=False):
"""
@type request_data: C{tuple}
@@ -276,6 +294,7 @@
return d
+
def _cbGotResponse(self, (code, headers, data, failed), expected_response, expectedfailure=False):
expected_code, expected_headers, expected_data = expected_response
self.assertEquals(code, expected_code)
@@ -286,6 +305,7 @@
self.assertEquals(failed, expectedfailure)
+
class ErrorHandlingTest(BaseCase):
"""
Tests for error handling.
@@ -299,10 +319,10 @@
root = ErrorWithProducerResource()
site = server.Site(root)
tcr = TestChanRequest(site, "GET", "/", "http://localhost/")
- request = server.Request(tcr, "GET", "/", (1, 1),
- 0, http_headers.Headers(
- {"host": "localhost"}),
- site=site)
+ request = server.Request(
+ tcr, "GET", "/", (1, 1),
+ 0, http_headers.Headers({"host": "localhost"}),
+ site=site)
proc = request.process()
done = []
proc.addBoth(done.append)
@@ -336,40 +356,47 @@
f.responseText = 'Remote Addr: %r' % req.remoteAddr.host
return f
+
def setUp(self):
self.root = self.SampleTestResource()
+
def test_root(self):
return self.assertResponse(
(self.root, 'http://host/'),
(200, {}, 'This is a fake resource.'))
+
def test_validChild(self):
return self.assertResponse(
(self.root, 'http://host/validChild'),
(200, {}, 'This is a valid child resource.'))
+
def test_invalidChild(self):
return self.assertResponse(
(self.root, 'http://host/invalidChild'),
(404, {}, None))
+
def test_remoteAddrExposure(self):
return self.assertResponse(
(self.root, 'http://host/remoteAddr'),
(200, {}, "Remote Addr: 'remotehost'"))
+
def test_leafresource(self):
class TestResource(resource.LeafResource):
def render(self, req):
return http.Response(stream="prepath:%s postpath:%s" % (
- req.prepath,
- req.postpath))
+ req.prepath,
+ req.postpath))
return self.assertResponse(
(TestResource(), 'http://host/consumed/path/segments'),
(200, {}, "prepath:[] postpath:['consumed', 'path', 'segments']"))
+
def test_redirectResource(self):
"""
Make sure a redirect response has the correct status and Location header.
@@ -384,6 +411,7 @@
(redirectResource, 'http://localhost/'),
(301, {'location': 'https://localhost/foo?bar=baz'}, None))
+
def test_redirectResourceWithSchemeRemapping(self):
"""
Make sure a redirect response has the correct status and Location header, when
@@ -396,7 +424,7 @@
site.SSLPort = 8443
site.BindSSLPorts = []
return TestChanRequest(site, method, prepath, uri, length, headers, version, content)
-
+
self.patch(self, "chanrequest", chanrequest2)
redirectResource = resource.RedirectResource(path='/foo')
@@ -405,6 +433,7 @@
(redirectResource, 'http://localhost:8443/'),
(301, {'location': 'https://localhost:8443/foo'}, None))
+
def test_redirectResourceWithoutSchemeRemapping(self):
"""
Make sure a redirect response has the correct status and Location header, when
@@ -417,7 +446,7 @@
site.SSLPort = 8443
site.BindSSLPorts = []
return TestChanRequest(site, method, prepath, uri, length, headers, version, content)
-
+
self.patch(self, "chanrequest", chanrequest2)
redirectResource = resource.RedirectResource(path='/foo')
@@ -426,6 +455,7 @@
(redirectResource, 'http://localhost:8008/'),
(301, {'location': 'http://localhost:8008/foo'}, None))
+
def test_redirectResourceWithoutSSLSchemeRemapping(self):
"""
Make sure a redirect response has the correct status and Location header, when
@@ -438,7 +468,7 @@
site.SSLPort = 8443
site.BindSSLPorts = []
return TestChanRequest(site, method, prepath, uri, length, headers, version, content)
-
+
self.patch(self, "chanrequest", chanrequest2)
redirectResource = resource.RedirectResource(path='/foo')
@@ -448,30 +478,36 @@
(301, {'location': 'http://localhost:8443/foo'}, None))
+
class URLParsingTest(BaseCase):
class TestResource(resource.LeafResource):
def render(self, req):
- return http.Response(stream="Host:%s, Path:%s"%(req.host, req.path))
+ return http.Response(stream="Host:%s, Path:%s" % (req.host, req.path))
+
def setUp(self):
self.root = self.TestResource()
+
def test_normal(self):
return self.assertResponse(
- (self.root, '/path', {'Host':'host'}),
+ (self.root, '/path', {'Host': 'host'}),
(200, {}, 'Host:host, Path:/path'))
+
def test_fullurl(self):
return self.assertResponse(
(self.root, 'http://host/path'),
(200, {}, 'Host:host, Path:/path'))
+
def test_strangepath(self):
# Ensure that the double slashes don't confuse it
return self.assertResponse(
- (self.root, '//path', {'Host':'host'}),
+ (self.root, '//path', {'Host': 'host'}),
(200, {}, 'Host:host, Path://path'))
+
def test_strangepathfull(self):
return self.assertResponse(
(self.root, 'http://host//path'),
@@ -481,7 +517,7 @@
class TestDeferredRendering(BaseCase):
class ResourceWithDeferreds(BaseTestResource):
- addSlash=True
+ addSlash = True
responseText = 'I should be wrapped in a Deferred.'
def render(self, req):
d = defer.Deferred()
@@ -494,11 +530,13 @@
reactor.callLater(0, d.callback, BaseTestResource())
return d
+
def test_deferredRootResource(self):
return self.assertResponse(
(self.ResourceWithDeferreds(), 'http://host/'),
(200, {}, 'I should be wrapped in a Deferred.'))
+
def test_deferredChild(self):
return self.assertResponse(
(self.ResourceWithDeferreds(), 'http://host/deferred'),
@@ -521,6 +559,7 @@
))
return defer.DeferredList(ds, fireOnOneErrback=True)
+
def test_hostRedirect(self):
ds = []
for url1, url2 in (
@@ -533,6 +572,7 @@
))
return defer.DeferredList(ds, fireOnOneErrback=True)
+
def test_pathRedirect(self):
root = BaseTestResource()
redirect = resource.RedirectResource(path="/other")
@@ -555,6 +595,7 @@
def __init__(self, test):
self.test = test
+
def render(self, request):
self.test.assertEquals(request.urlForResource(self), self.expectedURI)
return 201
@@ -589,12 +630,13 @@
for uri in (foo.expectedURI, bar.expectedURI, baz.expectedURI):
ds.append(self.assertResponse(
- (root, uri, {'Host':'host'}),
+ (root, uri, {'Host': 'host'}),
(201, {}, None),
))
return defer.DeferredList(ds, fireOnOneErrback=True)
+
def test_urlEncoding(self):
"""
Test to make sure that URL encoding is working.
@@ -608,10 +650,11 @@
root.putChild("foo bar", child)
return self.assertResponse(
- (root, child.expectedURI, {'Host':'host'}),
+ (root, child.expectedURI, {'Host': 'host'}),
(201, {}, None)
)
+
def test_locateResource(self):
"""
Test urlForResource() on resource looked up via a locateResource() call.
@@ -629,6 +672,7 @@
d.addCallback(gotResource)
return d
+
def test_unknownResource(self):
"""
Test urlForResource() on unknown resource.
@@ -639,6 +683,7 @@
self.assertRaises(server.NoURLForResourceError, request.urlForResource, child)
+
def test_locateChildResource(self):
"""
Test urlForResource() on deeply nested resource looked up via
@@ -677,6 +722,7 @@
d.addCallback(gotResource)
return d
+
def test_deferredLocateChild(self):
"""
Test deferred value from locateChild()
@@ -742,11 +788,13 @@
ctype = http_headers.MimeType('application', 'x-www-form-urlencoded')
content = "key=value&multiple=two+words&multiple=more%20words"
root = resource.Resource()
- request = SimpleRequest(server.Site(root), "GET", "/",
- http_headers.Headers({'content-type': ctype}), content)
+ request = SimpleRequest(
+ server.Site(root), "GET", "/",
+ http_headers.Headers({'content-type': ctype}), content)
def cb(ign):
self.assertEquals(request.files, {})
- self.assertEquals(request.args,
+ self.assertEquals(
+ request.args,
{'multiple': ['two words', 'more words'], 'key': ['value']})
return server.parsePOSTData(request).addCallback(cb)
@@ -758,7 +806,7 @@
"""
ctype = http_headers.MimeType('multipart', 'form-data',
(('boundary', '---weeboundary'),))
- content="""-----weeboundary\r
+ content = """-----weeboundary\r
Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
Content-Type: text/html\r
\r
@@ -766,13 +814,15 @@
-----weeboundary--\r
"""
root = resource.Resource()
- request = SimpleRequest(server.Site(root), "GET", "/",
- http_headers.Headers({'content-type': ctype}), content)
+ request = SimpleRequest(
+ server.Site(root), "GET", "/",
+ http_headers.Headers({'content-type': ctype}), content)
def cb(ign):
self.assertEquals(request.args, {})
self.assertEquals(request.files.keys(), ['FileNameOne'])
- self.assertEquals(request.files.values()[0][0][:2],
- ('myfilename', http_headers.MimeType('text', 'html', {})))
+ self.assertEquals(
+ request.files.values()[0][0][:2],
+ ('myfilename', http_headers.MimeType('text', 'html', {})))
f = request.files.values()[0][0][2]
self.assertEquals(f.read(), "my great content wooo")
return server.parsePOSTData(request).addCallback(cb)
@@ -784,7 +834,7 @@
C{http.HTTPError}.
"""
ctype = http_headers.MimeType('multipart', 'form-data')
- content="""-----weeboundary\r
+ content = """-----weeboundary\r
Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
Content-Type: text/html\r
\r
@@ -792,10 +842,10 @@
-----weeboundary--\r
"""
root = resource.Resource()
- request = SimpleRequest(server.Site(root), "GET", "/",
- http_headers.Headers({'content-type': ctype}), content)
- return self.assertFailure(server.parsePOSTData(request),
- http.HTTPError)
+ request = SimpleRequest(
+ server.Site(root), "GET", "/",
+ http_headers.Headers({'content-type': ctype}), content)
+ return self.assertFailure(server.parsePOSTData(request), http.HTTPError)
def test_wrongContentType(self):
@@ -805,21 +855,21 @@
ctype = http_headers.MimeType('application', 'foobar')
content = "key=value&multiple=two+words&multiple=more%20words"
root = resource.Resource()
- request = SimpleRequest(server.Site(root), "GET", "/",
- http_headers.Headers({'content-type': ctype}), content)
- return self.assertFailure(server.parsePOSTData(request),
- http.HTTPError)
+ request = SimpleRequest(
+ server.Site(root), "GET", "/",
+ http_headers.Headers({'content-type': ctype}), content)
+ return self.assertFailure(server.parsePOSTData(request), http.HTTPError)
def test_mimeParsingError(self):
"""
A malformed content should result in a C{http.HTTPError}.
-
+
The tested content has an invalid closing boundary.
"""
ctype = http_headers.MimeType('multipart', 'form-data',
(('boundary', '---weeboundary'),))
- content="""-----weeboundary\r
+ content = """-----weeboundary\r
Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
Content-Type: text/html\r
\r
@@ -827,10 +877,10 @@
-----weeoundary--\r
"""
root = resource.Resource()
- request = SimpleRequest(server.Site(root), "GET", "/",
- http_headers.Headers({'content-type': ctype}), content)
- return self.assertFailure(server.parsePOSTData(request),
- http.HTTPError)
+ request = SimpleRequest(
+ server.Site(root), "GET", "/",
+ http_headers.Headers({'content-type': ctype}), content)
+ return self.assertFailure(server.parsePOSTData(request), http.HTTPError)
def test_multipartMaxMem(self):
@@ -840,7 +890,7 @@
"""
ctype = http_headers.MimeType('multipart', 'form-data',
(('boundary', '---weeboundary'),))
- content="""-----weeboundary\r
+ content = """-----weeboundary\r
Content-Disposition: form-data; name="FileNameOne"\r
Content-Type: text/html\r
\r
@@ -849,12 +899,15 @@
-----weeboundary--\r
"""
root = resource.Resource()
- request = SimpleRequest(server.Site(root), "GET", "/",
- http_headers.Headers({'content-type': ctype}), content)
+ request = SimpleRequest(
+ server.Site(root), "GET", "/",
+ http_headers.Headers({'content-type': ctype}), content)
def cb(res):
- self.assertEquals(res.response.description,
+ self.assertEquals(
+ res.response.description,
"Maximum length of 10 bytes exceeded.")
- return self.assertFailure(server.parsePOSTData(request, maxMem=10),
+ return self.assertFailure(
+ server.parsePOSTData(request, maxMem=10),
http.HTTPError).addCallback(cb)
@@ -865,7 +918,7 @@
"""
ctype = http_headers.MimeType('multipart', 'form-data',
(('boundary', '---weeboundary'),))
- content="""-----weeboundary\r
+ content = """-----weeboundary\r
Content-Disposition: form-data; name="FileNameOne"; filename="myfilename"\r
Content-Type: text/html\r
\r
@@ -874,12 +927,15 @@
-----weeboundary--\r
"""
root = resource.Resource()
- request = SimpleRequest(server.Site(root), "GET", "/",
- http_headers.Headers({'content-type': ctype}), content)
+ request = SimpleRequest(
+ server.Site(root), "GET", "/",
+ http_headers.Headers({'content-type': ctype}), content)
def cb(res):
- self.assertEquals(res.response.description,
+ self.assertEquals(
+ res.response.description,
"Maximum length of 10 bytes exceeded.")
- return self.assertFailure(server.parsePOSTData(request, maxSize=10),
+ return self.assertFailure(
+ server.parsePOSTData(request, maxSize=10),
http.HTTPError).addCallback(cb)
@@ -911,12 +967,15 @@
-----xyz--\r
"""
root = resource.Resource()
- request = SimpleRequest(server.Site(root), "GET", "/",
- http_headers.Headers({'content-type': ctype}), content)
+ request = SimpleRequest(
+ server.Site(root), "GET", "/",
+ http_headers.Headers({'content-type': ctype}), content)
def cb(res):
- self.assertEquals(res.response.description,
+ self.assertEquals(
+ res.response.description,
"Maximum number of fields 3 exceeded")
- return self.assertFailure(server.parsePOSTData(request, maxFields=3),
+ return self.assertFailure(
+ server.parsePOSTData(request, maxFields=3),
http.HTTPError).addCallback(cb)
@@ -929,13 +988,12 @@
(('boundary', '---weeboundary'),))
# XXX: maybe this is not a good example
# parseContentDispositionFormData could handle this problem
- content="""-----weeboundary\r
+ content = """-----weeboundary\r
Content-Disposition: form-data; name="FileNameOne"; filename="myfilename and invalid data \r
-----weeboundary--\r
"""
root = resource.Resource()
- request = SimpleRequest(server.Site(root), "GET", "/",
- http_headers.Headers({'content-type': ctype}), content)
- return self.assertFailure(server.parsePOSTData(request),
- ValueError)
-
+ request = SimpleRequest(
+ server.Site(root), "GET", "/",
+ http_headers.Headers({'content-type': ctype}), content)
+ return self.assertFailure(server.parsePOSTData(request), ValueError)
Modified: CalendarServer/trunk/txweb2/test/test_static.py
===================================================================
--- CalendarServer/trunk/txweb2/test/test_static.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/test_static.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -20,6 +20,7 @@
self.text = "Hello, World\n"
self.data = static.Data(self.text, "text/plain")
+
def test_dataState(self):
"""
Test the internal state of the Data object
@@ -67,11 +68,13 @@
self.tempdir = self.mktemp()
os.mkdir(self.tempdir)
- self.root = static.FileSaver(self.tempdir,
- expectedFields=['FileNameOne'],
- maxBytes=16)
+ self.root = static.FileSaver(
+ self.tempdir,
+ expectedFields=['FileNameOne'],
+ maxBytes=16)
self.root.addSlash = True
+
def uploadFile(self, fieldname, filename, mimetype, content, resrc=None,
host='foo', path='/'):
if not resrc:
@@ -80,12 +83,12 @@
ctype = http_headers.MimeType('multipart', 'form-data',
(('boundary', '---weeboundary'),))
- return self.getResponseFor(resrc, '/',
- headers={'host': 'foo',
- 'content-type': ctype },
- length=len(content),
- method='POST',
- content="""-----weeboundary\r
+ return self.getResponseFor(
+ resrc, '/',
+ headers={'host': 'foo', 'content-type': ctype},
+ length=len(content),
+ method='POST',
+ content="""-----weeboundary\r
Content-Disposition: form-data; name="%s"; filename="%s"\r
Content-Type: %s\r
\r
@@ -93,6 +96,7 @@
-----weeboundary--\r
""" % (fieldname, filename, mimetype, content))
+
def _CbAssertInResponse(self, (code, headers, data, failed),
expected_response, expectedFailure=False):
@@ -107,36 +111,43 @@
self.assertEquals(failed, expectedFailure)
+
def fileNameFromResponse(self, response):
- (code, headers, data, failure) = response
- return data[data.index('Saved file')+11:data.index('<br />')]
+ (_ignore_code, _ignore_headers, data, _ignore_failure) = response
+ return data[data.index('Saved file') + 11:data.index('<br />')]
+
def assertInResponse(self, response, expected_response, failure=False):
d = response
d.addCallback(self._CbAssertInResponse, expected_response, failure)
return d
+
def test_enforcesMaxBytes(self):
return self.assertInResponse(
- self.uploadFile('FileNameOne', 'myfilename', 'text/html', 'X'*32),
+ self.uploadFile('FileNameOne', 'myfilename', 'text/html', 'X' * 32),
(200, {}, 'exceeds maximum length'))
+
def test_enforcesMimeType(self):
return self.assertInResponse(
self.uploadFile('FileNameOne', 'myfilename',
'application/x-python', 'X'),
(200, {}, 'type not allowed'))
+
def test_invalidField(self):
return self.assertInResponse(
self.uploadFile('NotARealField', 'myfilename', 'text/html', 'X'),
(200, {}, 'not a valid field'))
+
def test_reportFileSave(self):
return self.assertInResponse(
self.uploadFile('FileNameOne', 'myfilename', 'text/plain', 'X'),
(200, {}, 'Saved file'))
+
def test_compareFileContents(self):
def gotFname(fname):
contents = file(fname, 'rb').read()
Modified: CalendarServer/trunk/txweb2/test/test_stream.py
===================================================================
--- CalendarServer/trunk/txweb2/test/test_stream.py 2014-10-29 02:35:02 UTC (rev 14119)
+++ CalendarServer/trunk/txweb2/test/test_stream.py 2014-10-29 16:28:01 UTC (rev 14120)
@@ -5,7 +5,9 @@
Tests for the stream implementations in L{txweb2}.
"""
-import tempfile, sys, os
+import os
+import sys
+import tempfile
from zope.interface import implements
@@ -24,6 +26,7 @@
raise TypeError("%s doesn't conform to the buffer interface" % (data,))
+
class SimpleStreamTests:
text = '1234567890'
def test_split(self):
@@ -48,6 +51,7 @@
self.assertEquals(bufstr(b.read()), self.text[point + 2:8])
self.assertEquals(b.read(), None)
+
def test_read(self):
s = self.makeStream()
self.assertEquals(s.length, len(self.text))
@@ -66,10 +70,13 @@
self.assertEquals(s.read(), None)
self.assertEquals(s.length, 0)
+
+
class FileStreamTest(SimpleStreamTests, unittest.TestCase):
def makeStream(self, *args, **kw):
return stream.FileStream(self.f, *args, **kw)
+
def setUp(self):
"""
Create a file containing C{self.text} to be streamed.
@@ -79,6 +86,7 @@
f.seek(0, 0)
self.f = f
+
def test_close(self):
s = self.makeStream()
s.close()
@@ -88,6 +96,7 @@
# would raise exception if f is closed
self.f.seek(0, 0)
+
def test_read2(self):
s = self.makeStream(0)
s.CHUNK_SIZE = 6
@@ -108,6 +117,8 @@
self.assertEquals(bufstr(s.read()), self.text)
self.assertRaises(RuntimeError, s.read) # ran out of data
+
+
class MMapFileStreamTest(SimpleStreamTests, unittest.TestCase):
text = SimpleStreamTests.text
text = text * (stream.MMAP_THRESHOLD // len(text) + 1)
@@ -115,6 +126,7 @@
def makeStream(self, *args, **kw):
return stream.FileStream(self.f, *args, **kw)
+
def setUp(self):
"""
Create a file containing C{self.text}, which should be long enough to
@@ -125,6 +137,7 @@
f.seek(0, 0)
self.f = f
+
def test_mmapwrapper(self):
self.assertRaises(TypeError, stream.mmapwrapper)
self.assertRaises(TypeError, stream.mmapwrapper, offset=0)
@@ -133,15 +146,19 @@
if not stream.mmap:
test_mmapwrapper.skip = 'mmap not supported here'
+
+
class MemoryStreamTest(SimpleStreamTests, unittest.TestCase):
def makeStream(self, *args, **kw):
return stream.MemoryStream(self.text, *args, **kw)
+
def test_close(self):
s = self.makeStream()
s.close()
self.assertEquals(s.length, 0)
+
def test_read2(self):
self.assertRaises(ValueError, self.makeStream, 0, 20)
@@ -174,46 +191,54 @@
s = stream.MemoryStream(self.data)
self.s = stream.BufferedStream(s)
+
def _cbGotData(self, data, expected):
self.assertEqual(data, expected)
+
def test_readline(self):
"""Test that readline reads a line."""
d = self.s.readline()
d.addCallback(self._cbGotData, 'I was angry with my friend:\r\n')
return d
+
def test_readlineWithSize(self):
"""Test the size argument to readline"""
d = self.s.readline(size=5)
d.addCallback(self._cbGotData, 'I was')
return d
+
def test_readlineWithBigSize(self):
"""Test the size argument when it's bigger than the length of the line."""
d = self.s.readline(size=40)
d.addCallback(self._cbGotData, 'I was angry with my friend:\r\n')
return d
+
def test_readlineWithZero(self):
"""Test readline with size = 0."""
d = self.s.readline(size=0)
d.addCallback(self._cbGotData, '')
return d
+
def test_readlineFinished(self):
"""Test readline on a finished stream."""
nolines = len(self.data.split('\r\n'))
- for i in range(nolines):
+ for _ in range(nolines):
self.s.readline()
d = self.s.readline()
d.addCallback(self._cbGotData, '')
return d
+
def test_readlineNegSize(self):
"""Ensure that readline with a negative size raises an exception."""
self.assertRaises(ValueError, self.s.readline, size=-1)
+
def test_readlineSizeInDelimiter(self):
"""
Test behavior of readline when size falls inside the
@@ -224,12 +249,14 @@
d.addCallback(lambda _: self.s.readline())
d.addCallback(self._cbGotData, "\nI told my wrath, my wrath did end.\r\n")
+
def test_readExactly(self):
"""Make sure readExactly with no arg reads all the data."""
d = self.s.readExactly()
d.addCallback(self._cbGotData, self.data)
return d
+
def test_readExactlyLimited(self):
"""
Test readExactly with a number.
@@ -238,6 +265,7 @@
d.addCallback(self._cbGotData, self.data[:10])
return d
+
def test_readExactlyBig(self):
"""
Test readExactly with a number larger than the size of the
@@ -247,6 +275,7 @@
d.addCallback(self._cbGotData, self.data)
return d
+
def test_read(self):
"""
Make sure read() also functions. (note that this test uses
@@ -255,6 +284,8 @@
"""
self.assertEqual(str(self.s.read()), self.data)
+
+
class TestStreamer:
implements(stream.IStream, stream.IByteStream)
@@ -266,17 +297,22 @@
def __init__(self, list):
self.list = list
+
def read(self):
self.readCalled += 1
if self.list:
return self.list.pop(0)
return None
+
def close(self):
self.closeCalled += 1
self.list = []
+
+
class FallbackSplitTest(unittest.TestCase):
+
def test_split(self):
s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
left, right = stream.fallbackSplit(s, 5)
@@ -287,6 +323,7 @@
d.addCallback(self._cbSplit, left, right)
return d
+
def _cbSplit(self, result, left, right):
self.assertEquals(bufstr(result), 'e')
self.assertEquals(left.read(), None)
@@ -295,6 +332,7 @@
self.assertEquals(bufstr(right.read()), 'ijkl')
self.assertEquals(right.read(), None)
+
def test_split2(self):
s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
left, right = stream.fallbackSplit(s, 4)
@@ -309,6 +347,7 @@
self.assertEquals(bufstr(right.read()), 'ijkl')
self.assertEquals(right.read(), None)
+
def test_splitsplit(self):
s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
left, right = stream.fallbackSplit(s, 5)
@@ -329,6 +368,7 @@
self.assertEquals(bufstr(right.read()), 'ijkl')
self.assertEquals(right.read(), None)
+
def test_closeboth(self):
s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
left, right = stream.fallbackSplit(s, 5)
@@ -340,6 +380,7 @@
self.assertEquals(s.readCalled, 0)
self.assertEquals(s.closeCalled, 1)
+
def test_closeboth_rev(self):
s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
left, right = stream.fallbackSplit(s, 5)
@@ -351,6 +392,7 @@
self.assertEquals(s.readCalled, 0)
self.assertEquals(s.closeCalled, 1)
+
def test_closeleft(self):
s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
left, right = stream.fallbackSplit(s, 5)
@@ -359,11 +401,13 @@
d.addCallback(self._cbCloseleft, right)
return d
+
def _cbCloseleft(self, result, right):
self.assertEquals(bufstr(result), 'fgh')
self.assertEquals(bufstr(right.read()), 'ijkl')
self.assertEquals(right.read(), None)
+
def test_closeright(self):
s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
left, right = stream.fallbackSplit(s, 3)
@@ -375,11 +419,13 @@
self.assertEquals(s.closeCalled, 1)
+
class ProcessStreamerTest(unittest.TestCase):
if interfaces.IReactorProcess(reactor, None) is None:
skip = "Platform lacks spawnProcess support, can't test process streaming."
+
def runCode(self, code, inputStream=None):
if inputStream is None:
inputStream = stream.MemoryStream("")
@@ -387,6 +433,7 @@
[sys.executable, "-u", "-c", code],
os.environ)
+
def test_output(self):
p = self.runCode("import sys\nfor i in range(100): sys.stdout.write('x' * 1000)")
l = []
@@ -396,6 +443,7 @@
d2 = p.run()
return d.addCallback(verify).addCallback(lambda _: d2)
+
def test_errouput(self):
p = self.runCode("import sys\nfor i in range(100): sys.stderr.write('x' * 1000)")
l = []
@@ -405,6 +453,7 @@
p.run()
return d.addCallback(verify)
+
def test_input(self):
p = self.runCode("import sys\nsys.stdout.write(sys.stdin.read())",
"hello world")
@@ -416,6 +465,7 @@
return d2
return d.addCallback(verify)
+
def test_badexit(self):
p = self.runCode("raise ValueError")
l = []
@@ -426,6 +476,7 @@
self.assert_(p.errStream.closed)
return p.run().addErrback(lambda _: _.trap(ProcessTerminated) and l.append(1)).addCallback(verify)
+
def test_inputerror(self):
p = self.runCode("import sys\nsys.stdout.write(sys.stdin.read())",
TestStreamer(["hello", defer.fail(ZeroDivisionError())]))
@@ -440,6 +491,7 @@
self.assertEqual(len(excs), 1)
return d.addCallback(verify).addCallback(cbVerified)
+
def test_processclosedinput(self):
p = self.runCode("import sys; sys.stdout.write(sys.stdin.read(3));" +
"sys.stdin.close(); sys.stdout.write('def')",
@@ -452,6 +504,7 @@
return d.addCallback(verify).addCallback(lambda _: d2)
+
class AdapterTestCase(unittest.TestCase):
def test_adapt(self):
@@ -465,14 +518,17 @@
self.assertEquals(s.read(), None)
+
class ReadStreamTestCase(unittest.TestCase):
+
def test_pull(self):
l = []
s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
return stream.readStream(s, l.append).addCallback(
lambda _: self.assertEquals(l, ["abcd", "efgh", "ijkl"]))
+
def test_pullFailure(self):
l = []
s = TestStreamer(['abcd', defer.fail(RuntimeError()), 'ijkl'])
@@ -481,12 +537,15 @@
self.assertEquals(l, ["abcd"])
return stream.readStream(s, l.append).addErrback(test)
+
def test_pullException(self):
class Failer:
- def read(self): raise RuntimeError
+ def read(self):
+ raise RuntimeError
return stream.readStream(Failer(), lambda _: None).addErrback(
lambda _: _.trap(RuntimeError))
+
def test_processingException(self):
s = TestStreamer(['abcd', defer.succeed('efgh'), 'ijkl'])
return stream.readStream(s, lambda x: 1 / 0).addErrback(
@@ -508,6 +567,7 @@
return d
+
class CompoundStreamTest:
"""
CompoundStream lets you combine many streams into one continuous stream.
@@ -583,6 +643,7 @@
"""
+
class AsynchronousDummyStream(object):
"""
An L{IByteStream} implementation which always returns a
@@ -592,14 +653,18 @@
def __init__(self):
self._readResults = []
+
def read(self):
result = defer.Deferred()
self._readResults.append(result)
return result
+
def _write(self, bytes):
self._readResults.pop(0).callback(bytes)
+
+
class MD5StreamTest(unittest.TestCase):
"""
Tests for L{stream.MD5Stream}.
@@ -622,6 +687,7 @@
self.assertEquals(self.digest, md5Stream.getMD5())
+
def test_asynchronous(self):
"""
L{stream.MD5Stream} also supports L{IByteStream} providers which return
@@ -648,6 +714,7 @@
return result
+
def test_getMD5FailsBeforeClose(self):
"""
L{stream.MD5Stream.getMD5} raises L{RuntimeError} if called before
@@ -657,6 +724,7 @@
md5Stream = stream.MD5Stream(dataStream)
self.assertRaises(RuntimeError, md5Stream.getMD5)
+
def test_initializationFailsWithoutStream(self):
"""
L{stream.MD5Stream.__init__} raises L{ValueError} if passed C{None} as
@@ -664,6 +732,7 @@
"""
self.assertRaises(ValueError, stream.MD5Stream, None)
+
def test_readAfterClose(self):
"""
L{stream.MD5Stream.read} raises L{RuntimeError} if called after
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.macosforge.org/pipermail/calendarserver-changes/attachments/20141029/1d8883a1/attachment-0001.html>
More information about the calendarserver-changes
mailing list