def testMultipleUpload(self): return self.doTest( 'xyz', """--xyz\r Content-Disposition: form-data; name="foo"\r \r Foo Bar\r --xyz\r Content-Disposition: form-data; name="foo"\r \r Baz\r --xyz\r Content-Disposition: form-data; name="file"; filename="filename"\r Content-Type: text/html\r \r blah\r --xyz\r Content-Disposition: form-data; name="file"; filename="filename"\r Content-Type: text/plain\r \r bleh\r --xyz--\r """, {'foo': ['Foo Bar', 'Baz']}, { 'file': [('filename', MimeType('text', 'html'), "blah"), ('filename', MimeType('text', 'plain'), "bleh")] })
def test_fail_dot_file_put_in_calendar(self): """ Make (regular) collection in calendar """ calendar_uri = "/calendars/users/wsanchez/dot_file_in_calendar/" principal = yield self.actualRoot.findPrincipalForAuthID("wsanchez") request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authPrincipal=principal) response = yield self.send(request) response = IResponse(response) if response.code != responsecode.CREATED: self.fail("MKCALENDAR failed: %s" % (response.code, )) stream = self.dataPath.child("Holidays").child( "C318AA54-1ED0-11D9-A5E0-000A958A3252.ics").open() try: calendar = str(Component.fromStream(stream)) finally: stream.close() event_uri = "/".join([calendar_uri, ".event.ics"]) request = SimpleStoreRequest(self, "PUT", event_uri, authPrincipal=principal) request.headers.setHeader("content-type", MimeType("text", "calendar")) request.stream = MemoryStream(calendar) response = yield self.send(request) response = IResponse(response) if response.code != responsecode.FORBIDDEN: self.fail("Incorrect response to dot file PUT: %s" % (response.code, ))
def __init__(self, code, error, description=None): """ @param code: a response code. @param error: an L{WebDAVElement} identifying the error, or a tuple C{(namespace, name)} with which to create an empty element denoting the error. (The latter is useful in the case of preconditions ans postconditions, not all of which have defined XML element classes.) @param description: an optional string that, if present, will get wrapped in a (twisted_dav_namespace, error-description) element. """ if type(error) is tuple: xml_namespace, xml_name = error error = element.WebDAVUnknownElement() error.namespace = xml_namespace error.name = xml_name self.description = description if self.description: output = element.Error(error, element.ErrorDescription( self.description)).toxml() else: output = element.Error(error).toxml() Response.__init__(self, code=code, stream=output) self.headers.setHeader("content-type", MimeType("text", "xml")) self.error = error
def test_generateSignature(self): data = "Hello World!" for algorithm, hash_method in ( ("rsa-sha1", hashlib.sha1,), ("rsa-sha256", hashlib.sha256,), ): stream = MemoryStream(data) headers = Headers() headers.addRawHeader("Originator", "mailto:[email protected]") headers.addRawHeader("Recipient", "mailto:[email protected]") headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component": "VEVENT", "charset": "utf-8"})) request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600) # Manually create what should be the correct thing to sign bodyhash = base64.b64encode(hash_method(data).digest()) sign_this = """originator:mailto:[email protected] recipient:mailto:[email protected] content-type:%s ischedule-version:1.0 dkim-signature:v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=dns/txt:http/well-known; c=ischedule-relaxed/simple; h=Originator:Recipient; bh=%s; b=""".replace("\n", "\r\n") % (headers.getRawHeaders("Content-Type")[0], str(int(time.time())), str(int(time.time() + 3600)), algorithm, bodyhash) result = request.generateSignature(sign_this) key = RSA.importKey(open(self.private_keyfile).read()) signature = DKIMUtils.sign(sign_this, key, DKIMUtils.hash_func(algorithm)) self.assertEqual(result, signature)
def _test_file_in_calendar(self, what, *work): """ Creates a calendar collection, then PUTs a resource into that collection with the data from given stream and verifies that the response code from the PUT request matches the given response_code. """ calendar_uri = "/calendars/users/wsanchez/testing_calendar/" principal = yield self.actualRoot.findPrincipalForAuthID("wsanchez") request = SimpleStoreRequest(self, "MKCALENDAR", calendar_uri, authPrincipal=principal) response = yield self.send(request) response = IResponse(response) if response.code != responsecode.CREATED: self.fail("MKCALENDAR failed: %s" % (response.code,)) c = 0 for stream, response_code in work: dst_uri = joinURL(calendar_uri, "dst%d.ics" % (c,)) request = SimpleStoreRequest(self, "PUT", dst_uri, authPrincipal=principal) request.headers.setHeader("if-none-match", "*") request.headers.setHeader("content-type", MimeType("text", "calendar")) request.stream = stream response = yield self.send(request) response = IResponse(response) if response.code != response_code: self.fail("Incorrect response to %s: %s (!= %s)" % (what, response.code, response_code)) c += 1
def test_sign(self): data = "Hello World!" for algorithm, hash_method in ( ("rsa-sha1", hashlib.sha1,), ("rsa-sha256", hashlib.sha256,), ): stream = MemoryStream(data) headers = Headers() headers.addRawHeader("Originator", "mailto:[email protected]") headers.addRawHeader("Recipient", "mailto:[email protected]") headers.setHeader("Content-Type", MimeType("text", "calendar", **{"component": "VEVENT", "charset": "utf-8"})) request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", self.private_keyfile, algorithm, ("Originator", "Recipient", "Content-Type",), True, True, True, 3600) result = (yield request.sign()) # Manually create what should be the correct thing to sign and make sure signatures match bodyhash = base64.b64encode(hash_method(DKIMUtils.canonicalizeBody(data)).digest()) sign_this = """originator:mailto:[email protected] recipient:mailto:[email protected] content-type:%s ischedule-version:1.0 ischedule-message-id:%s dkim-signature:v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=private-exchange:http/well-known:dns/txt; c=ischedule-relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID; bh=%s; b=""".replace("\n", "\r\n") % (headers.getRawHeaders("Content-Type")[0], request.message_id, request.time, request.expire, algorithm, bodyhash) key = RSA.importKey(open(self.private_keyfile).read()) signature = DKIMUtils.sign(sign_this, key, DKIMUtils.hash_func(algorithm)) self.assertEqual(result, signature) # Make sure header is updated in the request updated_header = "v=1; d=example.com; s=dkim; t=%s; x=%s; a=%s; q=private-exchange:http/well-known:dns/txt; c=ischedule-relaxed/simple; h=Originator:Recipient:Content-Type:iSchedule-Version:iSchedule-Message-ID; bh=%s; b=%s" % (request.time, request.expire, algorithm, bodyhash, signature,) self.assertEqual(request.headers.getRawHeaders("DKIM-Signature")[0], updated_header) # Try to verify result using public key pubkey = RSA.importKey(open(self.public_keyfile).read()) self.assertEqual(DKIMUtils.verify(sign_this, result, pubkey, DKIMUtils.hash_func(algorithm)), None)
def test_body_hash(self): data = "Hello World!" for algorithm, hash_method in ( ( "rsa-sha1", hashlib.sha1, ), ( "rsa-sha256", hashlib.sha256, ), ): stream = str(data) headers = Headers() headers.addRawHeader("Originator", "mailto:[email protected]") headers.addRawHeader("Recipient", "mailto:[email protected]") headers.setHeader( "Content-Type", MimeType("text", "calendar", **{ "component": "VEVENT", "charset": "utf-8" })) request = DKIMRequest("POST", "/", headers, stream, "example.com", "dkim", "/tmp/key", algorithm, ( "Originator", "Recipient", "Content-Type", ), True, True, True, 3600) hash = base64.b64encode( hash_method(DKIMUtils.canonicalizeBody(data)).digest()) result = (yield request.bodyHash()) self.assertEqual(result, hash)
def gotBody(output): mime_params = {"charset": "utf-8"} response = Response(200, {}, output) response.headers.setHeader( "content-type", MimeType("text", "html", mime_params) ) return response
def readProperty(self, property, request): if type(property) is tuple: qname = property else: qname = property.qname() namespace, name = qname if namespace == dav_namespace: if name == "resourcetype": result = davxml.ResourceType.empty # @UndefinedVariable return result elif name == "getetag": result = davxml.GETETag( ETag(hashlib.md5(self.vCardText()).hexdigest()).generate()) return result elif name == "getcontenttype": mimeType = MimeType('text', 'vcard', {}) result = davxml.GETContentType(generateContentType(mimeType)) return result elif name == "getcontentlength": result = davxml.GETContentLength.fromString( str(len(self.vCardText()))) return result elif name == "getlastmodified": if self.vCard().hasProperty("REV"): modDatetime = parse_date(self.vCard().propertyValue("REV")) else: modDatetime = datetime.datetime.utcnow() # strip time zone because time zones are unimplemented in davxml.GETLastModified.fromDate d = modDatetime.date() t = modDatetime.time() modDatetimeNoTZ = datetime.datetime(d.year, d.month, d.day, t.hour, t.minute, t.second, t.microsecond, None) result = davxml.GETLastModified.fromDate(modDatetimeNoTZ) return result elif name == "creationdate": if self.vCard().hasProperty( "REV"): # use modification date property if it exists creationDatetime = parse_date( self.vCard().propertyValue("REV")) else: creationDatetime = datetime.datetime.utcnow() result = davxml.CreationDate.fromDate(creationDatetime) return result elif name == "displayname": # AddressBook.app uses N. Use FN or UID instead? result = davxml.DisplayName.fromString( self.vCard().propertyValue("N")) return result elif namespace == twisted_dav_namespace: return super(ABDirectoryQueryResult, self).readProperty(property, request) return self._directoryBackedAddressBook.readProperty(property, request)
def __init__(self, xml_responses): """ @param xml_responses: an interable of element.Response objects. """ Response.__init__(self, code=responsecode.MULTI_STATUS, stream=element.MultiStatus(*xml_responses).toxml()) self.headers.setHeader("content-type", MimeType("text", "xml"))
def doRequest(self, txn): # Generate an HTTP client request try: if "xpod" not in txn.logItems: txn.logItems["xpod"] = 0 txn.logItems["xpod"] += 1 response = (yield self._processRequest()) if accountingEnabledForCategory("xPod"): self.loggedResponse = yield self.logResponse(response) emitAccounting("xPod", "", self.loggedRequest + "\n" + self.loggedResponse, "POST") if response.code == responsecode.OK: if self.writeStream is None: data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: yield readStream(response.stream, self.writeStream.write) content_type = response.headers.getHeader("content-type") if content_type is None: content_type = MimeType("application", "octet-stream") content_disposition = response.headers.getHeader( "content-disposition") if content_disposition is None or "filename" not in content_disposition.params: filename = "" else: filename = content_disposition.params["filename"] self.writeStream.resetDetails(content_type, filename) yield self.writeStream.loseConnection() data = { "result": "ok", "content-type": content_type, "name": filename, } elif response.code == responsecode.BAD_REQUEST: data = (yield allDataFromStream(response.stream)) data = json.loads(data) else: raise ValueError( "Incorrect cross-pod response status code: {}".format( response.code)) except Exception as e: # Request failed log.error("Could not do cross-pod request : {request} {ex}", request=self, ex=e) raise ValueError("Failed cross-pod request: {}".format(e)) returnValue(data)
def simpleSend(self, method, path="/", body="", mimetype="text", subtype="xml", resultcode=responsecode.OK, headers=()): """ Assemble and send a simple request using L{SimpleRequest}. This L{SimpleRequest} is associated with this L{TestCase}'s C{site} attribute. @param method: the HTTP method @type method: L{bytes} @param path: the absolute path portion of the HTTP URI @type path: L{bytes} @param body: the content body of the request @type body: L{bytes} @param mimetype: the main type of the mime type of the body of the request @type mimetype: L{bytes} @param subtype: the subtype of the mimetype of the body of the request @type subtype: L{bytes} @param resultcode: The expected result code for the response to the request. @type resultcode: L{int} @param headers: An iterable of 2-tuples of C{(header, value)}; headers to set on the outgoing request. @return: a L{Deferred} which fires with a L{bytes} if the request was successfully processed and fails with an L{HTTPError} if not; or, if the resultcode does not match the response's code, fails with L{FailTest}. """ request = SimpleRequest(self.site, method, path, content=body) if headers is not None: for k, v in headers: request.headers.setHeader(k, v) request.headers.setHeader("content-type", MimeType(mimetype, subtype)) def checkResult(response): self.assertEqual(response.code, resultcode) if response.stream is None: return None return allDataFromStream(response.stream) return self.send(request, None).addCallback(checkResult)
def testEmptyFilename(self): return self.doTest( 'curlPYafCMnsamUw9kSkJJkSen41sAV', """--curlPYafCMnsamUw9kSkJJkSen41sAV\r cONTENT-tYPE: application/octet-stream\r cONTENT-dISPOSITION: FORM-DATA; NAME="foo"; FILENAME=""\r \r qwertyuiop\r --curlPYafCMnsamUw9kSkJJkSen41sAV--\r """, {}, { 'foo': [('', MimeType('application', 'octet-stream'), "qwertyuiop")] })
def render(self, request): output = """<html> <head> <title>{rtype} Inbox Resource</title> </head> <body> <h1>{rtype} Inbox Resource.</h1> </body </html>""".format(rtype="Podding" if self._podding else "iSchedule") response = Response(200, {}, output) response.headers.setHeader("content-type", MimeType("text", "html")) return response
def render(self, request): output = """<html> <head> <title>Podding Conduit Resource</title> </head> <body> <h1>Podding Conduit Resource.</h1> </body </html>""" response = Response(200, {}, output) response.headers.setHeader("content-type", MimeType("text", "html")) return response
def render(self, request): output = """<html> <head> <title>Timezone Standard Service Resource</title> </head> <body> <h1>Timezone Standard Service Resource.</h1> </body </html>""" response = Response(200, {}, output) response.headers.setHeader("content-type", MimeType("text", "html")) return response
def testStupidFilename(self): return self.doTest( '----------0xKhTmLbOuNdArY', """------------0xKhTmLbOuNdArY\r Content-Disposition: form-data; name="file"; filename="foo"; name="foobar.txt"\r Content-Type: text/plain\r \r Contents of a file blah blah\r ------------0xKhTmLbOuNdArY--\r """, {}, { 'file': [('foo"; name="foobar.txt', MimeType( 'text', 'plain'), "Contents of a file\nblah\nblah")] })
def __init__(self, schedule_response_element, xml_responses, location=None): """ @param xml_responses: an iterable of davxml.Response objects. @param location: the value of the location header to return in the response, or None. """ Response.__init__(self, code=responsecode.OK, stream=schedule_response_element(*xml_responses).toxml()) self.headers.setHeader("content-type", MimeType("text", "xml")) if location is not None: self.headers.setHeader("location", location)
def render(self, request): output = """<html> <head> <title>DomainKey Resource</title> </head> <body> <h1>DomainKey Resource.</h1> <a href="%s">Domain: %s<br> Selector: %s</a> </body </html>""" % (joinURL(request.uri, self.domain, self.selector), self.domain, self.selector,) response = Response(200, {}, output) response.headers.setHeader("content-type", MimeType("text", "html")) return response
def _addManagedAttachment(self, home, calendar, event, name): txn = self._sqlCalendarStore.newTransaction() # Create an event with an attachment home = (yield txn.calendarHomeWithUID(home)) calendar = (yield home.calendarWithName(calendar)) event = (yield calendar.calendarObjectWithName(event)) attachment = (yield event.createManagedAttachment()) t = attachment.store(MimeType("text", "x-fixture"), name) t.write("%s/%s/%s/%s" % (home, calendar, event, name,)) t.write(" managed attachment") (yield t.loseConnection()) (yield txn.commit()) returnValue(attachment)
def testNormalUpload(self): return self.doTest( '---------------------------155781040421463194511908194298', """-----------------------------155781040421463194511908194298\r Content-Disposition: form-data; name="foo"\r \r Foo Bar\r -----------------------------155781040421463194511908194298\r Content-Disposition: form-data; name="file"; filename="filename"\r Content-Type: text/html\r \r Contents of a file blah blah\r -----------------------------155781040421463194511908194298--\r """, {'foo': ['Foo Bar']}, { 'file': [('filename', MimeType( 'text', 'html'), "Contents of a file\nblah\nblah")] })
def _processRequest(self): """ Process the request by sending it to the relevant server. @return: the HTTP response. @rtype: L{Response} """ ssl, host, port, _ignore_path = self.server.details() path = "/" + config.Servers.ConduitName headers = Headers() headers.setHeader("Host", utf8String(host + ":{}".format(port))) if self.streamType: # For attachments we put the base64-encoded JSON data into a header headers.setHeader("Content-Type", self.streamType) headers.addRawHeader("XPOD", base64.b64encode(self.data)) else: headers.setHeader( "Content-Type", MimeType("application", "json", params={ "charset": "utf-8", })) headers.setHeader("User-Agent", "CalendarServer/{}".format(version)) headers.addRawHeader(*self.server.secretHeader()) from twisted.internet import reactor f = Factory() f.protocol = HTTPClientProtocol ep = GAIEndpoint( reactor, host, port, _configuredClientContextFactory(host) if ssl else None) proto = (yield ep.connect(f)) request = ClientRequest( "POST", path, headers, self.stream if self.stream is not None else self.data) if accountingEnabledForCategory("xPod"): self.loggedRequest = yield self.logRequest(request) response = (yield proto.submitRequest(request)) returnValue(response)
def setUp(self): yield super(PurgePrincipalTests, self).setUp() txn = self._sqlCalendarStore.newTransaction() # Add attachment to attachment.ics self._sqlCalendarStore._dropbox_ok = True home = yield txn.calendarHomeWithUID(self.uid) calendar = yield home.calendarWithName("calendar1") event = yield calendar.calendarObjectWithName("attachment.ics") attachment = yield event.createAttachmentWithName("attachment.txt") t = attachment.store(MimeType("text", "x-fixture")) t.write("attachment") t.write(" text") yield t.loseConnection() self._sqlCalendarStore._dropbox_ok = False # Share calendars each way home2 = yield txn.calendarHomeWithUID(self.uid2) calendar2 = yield home2.calendarWithName("calendar2") self.sharedName = yield calendar2.shareWith(home, _BIND_MODE_WRITE) self.sharedName2 = yield calendar.shareWith(home2, _BIND_MODE_WRITE) yield txn.commit() txn = self._sqlCalendarStore.newTransaction() home = yield txn.calendarHomeWithUID(self.uid) calendar2 = yield home.childWithName(self.sharedName) self.assertNotEquals(calendar2, None) home2 = yield txn.calendarHomeWithUID(self.uid2) calendar1 = yield home2.childWithName(self.sharedName2) self.assertNotEquals(calendar1, None) yield txn.commit() # Now remove user01 yield self.directory.removeRecords((self.uid,)) self.patch(config.Scheduling.Options.WorkQueues, "Enabled", False) self.patch(config.AutomaticPurging, "Enabled", True) self.patch(config.AutomaticPurging, "PollingIntervalSeconds", -1) self.patch(config.AutomaticPurging, "CheckStaggerSeconds", 1) self.patch(config.AutomaticPurging, "PurgeIntervalSeconds", 3) self.patch(config.AutomaticPurging, "HomePurgeDelaySeconds", 1)
def _doPOST(self, body, resultcode=responsecode.OK): authPrincipal = yield self.actualRoot.findPrincipalForAuthID("user01") request = SimpleStoreRequest(self, "POST", "/calendars/__uids__/user01/calendar/", content=body, authPrincipal=authPrincipal) request.headers.setHeader("content-type", MimeType("text", "xml")) response = yield self.send(request) response = IResponse(response) self.assertEqual(response.code, resultcode) # Reload resource self.resource = yield self._getResource() if response.stream: data = yield allDataFromStream(response.stream) returnValue(data) else: returnValue(None)
def _doPOSTSharerAccept(self, body, resultcode=responsecode.OK, sharer="user02"): authPrincipal = yield self.actualRoot.findPrincipalForAuthID(sharer) request = SimpleStoreRequest(self, "POST", "/calendars/__uids__/{}/".format(sharer), content=body, authPrincipal=authPrincipal) request.headers.setHeader("content-type", MimeType("text", "xml")) response = yield self.send(request) response = IResponse(response) self.assertEqual(response.code, resultcode) if response.stream: xmldata = yield allDataFromStream(response.stream) doc = WebDAVDocument.fromString(xmldata) returnValue(doc) else: returnValue(None)
def contentType(self): return MimeType("httpd", "unix-directory")
def renderResponse(self, code, body=None): response = Response(code, {}, body) response.headers.setHeader("content-type", MimeType("text", "html")) return response
def contentType(self): return MimeType("text", "calendar", charset="utf-8")
def contentType(self): return MimeType("httpd", "unix-directory") if self.isCollection() else None