add Linux_i686

This commit is contained in:
j 2014-05-17 18:11:40 +00:00 committed by Ubuntu
commit 95cd9b11f2
1644 changed files with 564260 additions and 0 deletions

View file

@ -0,0 +1,15 @@
# -*- test-case-name: twisted.web.test -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Twisted Web: a L{web server<twisted.web.server>} (including an
L{HTTP implementation<twisted.web.http>} and a
L{resource model<twisted.web.resource>}) and
a L{web client<twisted.web.client>}.
"""
from twisted.web._version import version
__version__ = version.short()

View file

@ -0,0 +1,7 @@
# -*- test-case-name: twisted.web.test.test_httpauth -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
HTTP header-based authentication migrated from web2
"""

View file

@ -0,0 +1,59 @@
# -*- test-case-name: twisted.web.test.test_httpauth -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
HTTP BASIC authentication.
@see: U{http://tools.ietf.org/html/rfc1945}
@see: U{http://tools.ietf.org/html/rfc2616}
@see: U{http://tools.ietf.org/html/rfc2617}
"""
import binascii
from zope.interface import implements
from twisted.cred import credentials, error
from twisted.web.iweb import ICredentialFactory
class BasicCredentialFactory(object):
"""
Credential Factory for HTTP Basic Authentication
@type authenticationRealm: C{str}
@ivar authenticationRealm: The HTTP authentication realm which will be issued in
challenges.
"""
implements(ICredentialFactory)
scheme = 'basic'
def __init__(self, authenticationRealm):
self.authenticationRealm = authenticationRealm
def getChallenge(self, request):
"""
Return a challenge including the HTTP authentication realm with which
this factory was created.
"""
return {'realm': self.authenticationRealm}
def decode(self, response, request):
"""
Parse the base64-encoded, colon-separated username and password into a
L{credentials.UsernamePassword} instance.
"""
try:
creds = binascii.a2b_base64(response + '===')
except binascii.Error:
raise error.LoginFailed('Invalid credentials')
creds = creds.split(':', 1)
if len(creds) == 2:
return credentials.UsernamePassword(*creds)
else:
raise error.LoginFailed('Invalid credentials')

View file

@ -0,0 +1,54 @@
# -*- test-case-name: twisted.web.test.test_httpauth -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Implementation of RFC2617: HTTP Digest Authentication
@see: U{http://www.faqs.org/rfcs/rfc2617.html}
"""
from zope.interface import implements
from twisted.cred import credentials
from twisted.web.iweb import ICredentialFactory
class DigestCredentialFactory(object):
"""
Wrapper for L{digest.DigestCredentialFactory} that implements the
L{ICredentialFactory} interface.
"""
implements(ICredentialFactory)
scheme = 'digest'
def __init__(self, algorithm, authenticationRealm):
"""
Create the digest credential factory that this object wraps.
"""
self.digest = credentials.DigestCredentialFactory(algorithm,
authenticationRealm)
def getChallenge(self, request):
"""
Generate the challenge for use in the WWW-Authenticate header
@param request: The L{IRequest} to with access was denied and for the
response to which this challenge is being generated.
@return: The C{dict} that can be used to generate a WWW-Authenticate
header.
"""
return self.digest.getChallenge(request.getClientIP())
def decode(self, response, request):
"""
Create a L{twisted.cred.digest.DigestedCredentials} object from the
given response and request.
@see: L{ICredentialFactory.decode}
"""
return self.digest.decode(response,
request.method,
request.getClientIP())

View file

@ -0,0 +1,225 @@
# -*- test-case-name: twisted.web.test.test_httpauth -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
A guard implementation which supports HTTP header-based authentication
schemes.
If no I{Authorization} header is supplied, an anonymous login will be
attempted by using a L{Anonymous} credentials object. If such a header is
supplied and does not contain allowed credentials, or if anonymous login is
denied, a 401 will be sent in the response along with I{WWW-Authenticate}
headers for each of the allowed authentication schemes.
"""
from zope.interface import implements
from twisted.python import log
from twisted.python.components import proxyForInterface
from twisted.web.resource import IResource, ErrorPage
from twisted.web import util
from twisted.cred import error
from twisted.cred.credentials import Anonymous
class UnauthorizedResource(object):
"""
Simple IResource to escape Resource dispatch
"""
implements(IResource)
isLeaf = True
def __init__(self, factories):
self._credentialFactories = factories
def render(self, request):
"""
Send www-authenticate headers to the client
"""
def generateWWWAuthenticate(scheme, challenge):
l = []
for k,v in challenge.iteritems():
l.append("%s=%s" % (k, quoteString(v)))
return "%s %s" % (scheme, ", ".join(l))
def quoteString(s):
return '"%s"' % (s.replace('\\', '\\\\').replace('"', '\\"'),)
request.setResponseCode(401)
for fact in self._credentialFactories:
challenge = fact.getChallenge(request)
request.responseHeaders.addRawHeader(
'www-authenticate',
generateWWWAuthenticate(fact.scheme, challenge))
if request.method == 'HEAD':
return ''
return 'Unauthorized'
def getChildWithDefault(self, path, request):
"""
Disable resource dispatch
"""
return self
class HTTPAuthSessionWrapper(object):
"""
Wrap a portal, enforcing supported header-based authentication schemes.
@ivar _portal: The L{Portal} which will be used to retrieve L{IResource}
avatars.
@ivar _credentialFactories: A list of L{ICredentialFactory} providers which
will be used to decode I{Authorization} headers into L{ICredentials}
providers.
"""
implements(IResource)
isLeaf = False
def __init__(self, portal, credentialFactories):
"""
Initialize a session wrapper
@type portal: C{Portal}
@param portal: The portal that will authenticate the remote client
@type credentialFactories: C{Iterable}
@param credentialFactories: The portal that will authenticate the
remote client based on one submitted C{ICredentialFactory}
"""
self._portal = portal
self._credentialFactories = credentialFactories
def _authorizedResource(self, request):
"""
Get the L{IResource} which the given request is authorized to receive.
If the proper authorization headers are present, the resource will be
requested from the portal. If not, an anonymous login attempt will be
made.
"""
authheader = request.getHeader('authorization')
if not authheader:
return util.DeferredResource(self._login(Anonymous()))
factory, respString = self._selectParseHeader(authheader)
if factory is None:
return UnauthorizedResource(self._credentialFactories)
try:
credentials = factory.decode(respString, request)
except error.LoginFailed:
return UnauthorizedResource(self._credentialFactories)
except:
log.err(None, "Unexpected failure from credentials factory")
return ErrorPage(500, None, None)
else:
return util.DeferredResource(self._login(credentials))
def render(self, request):
"""
Find the L{IResource} avatar suitable for the given request, if
possible, and render it. Otherwise, perhaps render an error page
requiring authorization or describing an internal server failure.
"""
return self._authorizedResource(request).render(request)
def getChildWithDefault(self, path, request):
"""
Inspect the Authorization HTTP header, and return a deferred which,
when fired after successful authentication, will return an authorized
C{Avatar}. On authentication failure, an C{UnauthorizedResource} will
be returned, essentially halting further dispatch on the wrapped
resource and all children
"""
# Don't consume any segments of the request - this class should be
# transparent!
request.postpath.insert(0, request.prepath.pop())
return self._authorizedResource(request)
def _login(self, credentials):
"""
Get the L{IResource} avatar for the given credentials.
@return: A L{Deferred} which will be called back with an L{IResource}
avatar or which will errback if authentication fails.
"""
d = self._portal.login(credentials, None, IResource)
d.addCallbacks(self._loginSucceeded, self._loginFailed)
return d
def _loginSucceeded(self, (interface, avatar, logout)):
"""
Handle login success by wrapping the resulting L{IResource} avatar
so that the C{logout} callback will be invoked when rendering is
complete.
"""
class ResourceWrapper(proxyForInterface(IResource, 'resource')):
"""
Wrap an L{IResource} so that whenever it or a child of it
completes rendering, the cred logout hook will be invoked.
An assumption is made here that exactly one L{IResource} from
among C{avatar} and all of its children will be rendered. If
more than one is rendered, C{logout} will be invoked multiple
times and probably earlier than desired.
"""
def getChildWithDefault(self, name, request):
"""
Pass through the lookup to the wrapped resource, wrapping
the result in L{ResourceWrapper} to ensure C{logout} is
called when rendering of the child is complete.
"""
return ResourceWrapper(self.resource.getChildWithDefault(name, request))
def render(self, request):
"""
Hook into response generation so that when rendering has
finished completely (with or without error), C{logout} is
called.
"""
request.notifyFinish().addBoth(lambda ign: logout())
return super(ResourceWrapper, self).render(request)
return ResourceWrapper(avatar)
def _loginFailed(self, result):
"""
Handle login failure by presenting either another challenge (for
expected authentication/authorization-related failures) or a server
error page (for anything else).
"""
if result.check(error.Unauthorized, error.LoginFailed):
return UnauthorizedResource(self._credentialFactories)
else:
log.err(
result,
"HTTPAuthSessionWrapper.getChildWithDefault encountered "
"unexpected error")
return ErrorPage(500, None, None)
def _selectParseHeader(self, header):
"""
Choose an C{ICredentialFactory} from C{_credentialFactories}
suitable to use to decode the given I{Authenticate} header.
@return: A two-tuple of a factory and the remaining portion of the
header value to be decoded or a two-tuple of C{None} if no
factory can decode the header value.
"""
elements = header.split(' ')
scheme = elements[0].lower()
for fact in self._credentialFactories:
if fact.scheme == scheme:
return (fact, ' '.join(elements[1:]))
return (None, None)

View file

@ -0,0 +1,185 @@
# -*- test-case-name: twisted.web.test.test_template -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
from zope.interface import implements
from twisted.web.iweb import IRenderable
from twisted.web.error import MissingRenderMethod, UnexposedMethodError
from twisted.web.error import MissingTemplateLoader
class Expose(object):
"""
Helper for exposing methods for various uses using a simple decorator-style
callable.
Instances of this class can be called with one or more functions as
positional arguments. The names of these functions will be added to a list
on the class object of which they are methods.
@ivar attributeName: The attribute with which exposed methods will be
tracked.
"""
def __init__(self, doc=None):
self.doc = doc
def __call__(self, *funcObjs):
"""
Add one or more functions to the set of exposed functions.
This is a way to declare something about a class definition, similar to
L{zope.interface.implements}. Use it like this::
magic = Expose('perform extra magic')
class Foo(Bar):
def twiddle(self, x, y):
...
def frob(self, a, b):
...
magic(twiddle, frob)
Later you can query the object::
aFoo = Foo()
magic.get(aFoo, 'twiddle')(x=1, y=2)
The call to C{get} will fail if the name it is given has not been
exposed using C{magic}.
@param funcObjs: One or more function objects which will be exposed to
the client.
@return: The first of C{funcObjs}.
"""
if not funcObjs:
raise TypeError("expose() takes at least 1 argument (0 given)")
for fObj in funcObjs:
fObj.exposedThrough = getattr(fObj, 'exposedThrough', [])
fObj.exposedThrough.append(self)
return funcObjs[0]
_nodefault = object()
def get(self, instance, methodName, default=_nodefault):
"""
Retrieve an exposed method with the given name from the given instance.
@raise UnexposedMethodError: Raised if C{default} is not specified and
there is no exposed method with the given name.
@return: A callable object for the named method assigned to the given
instance.
"""
method = getattr(instance, methodName, None)
exposedThrough = getattr(method, 'exposedThrough', [])
if self not in exposedThrough:
if default is self._nodefault:
raise UnexposedMethodError(self, methodName)
return default
return method
@classmethod
def _withDocumentation(cls, thunk):
"""
Slight hack to make users of this class appear to have a docstring to
documentation generators, by defining them with a decorator. (This hack
should be removed when epydoc can be convinced to use some other method
for documenting.)
"""
return cls(thunk.__doc__)
# Avoid exposing the ugly, private classmethod name in the docs. Luckily this
# namespace is private already so this doesn't leak further.
exposer = Expose._withDocumentation
@exposer
def renderer():
"""
Decorate with L{renderer} to use methods as template render directives.
For example::
class Foo(Element):
@renderer
def twiddle(self, request, tag):
return tag('Hello, world.')
<div xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
<span t:render="twiddle" />
</div>
Will result in this final output::
<div>
<span>Hello, world.</span>
</div>
"""
class Element(object):
"""
Base for classes which can render part of a page.
An Element is a renderer that can be embedded in a stan document and can
hook its template (from the loader) up to render methods.
An Element might be used to encapsulate the rendering of a complex piece of
data which is to be displayed in multiple different contexts. The Element
allows the rendering logic to be easily re-used in different ways.
Element returns render methods which are registered using
L{twisted.web.element.renderer}. For example::
class Menu(Element):
@renderer
def items(self, request, tag):
....
Render methods are invoked with two arguments: first, the
L{twisted.web.http.Request} being served and second, the tag object which
"invoked" the render method.
@type loader: L{ITemplateLoader} provider
@ivar loader: The factory which will be used to load documents to
return from C{render}.
"""
implements(IRenderable)
loader = None
def __init__(self, loader=None):
if loader is not None:
self.loader = loader
def lookupRenderMethod(self, name):
"""
Look up and return the named render method.
"""
method = renderer.get(self, name, None)
if method is None:
raise MissingRenderMethod(self, name)
return method
def render(self, request):
"""
Implement L{IRenderable} to allow one L{Element} to be embedded in
another's template or rendering output.
(This will simply load the template from the C{loader}; when used in a
template, the flattening engine will keep track of this object
separately as the object to lookup renderers on and call
L{Element.renderer} to look them up. The resulting object from this
method is not directly associated with this L{Element}.)
"""
loader = self.loader
if loader is None:
raise MissingTemplateLoader(self)
return loader.load()

View file

@ -0,0 +1,422 @@
# -*- test-case-name: twisted.web.test.test_flatten -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Context-free flattener/serializer for rendering Python objects, possibly
complex or arbitrarily nested, as strings.
"""
from cStringIO import StringIO
from sys import exc_info
from types import GeneratorType
from traceback import extract_tb
from twisted.internet.defer import Deferred
from twisted.web.error import UnfilledSlot, UnsupportedType, FlattenerError
from twisted.web.iweb import IRenderable
from twisted.web._stan import (
Tag, slot, voidElements, Comment, CDATA, CharRef)
def escapeForContent(data):
"""
Escape some character or UTF-8 byte data for inclusion in an HTML or XML
document, by replacing metacharacters (C{&<>}) with their entity
equivalents (C{&amp;&lt;&gt;}).
This is used as an input to L{_flattenElement}'s C{dataEscaper} parameter.
@type data: C{bytes} or C{unicode}
@param data: The string to escape.
@rtype: C{bytes}
@return: The quoted form of C{data}. If C{data} is unicode, return a utf-8
encoded string.
"""
if isinstance(data, unicode):
data = data.encode('utf-8')
data = data.replace('&', '&amp;'
).replace('<', '&lt;'
).replace('>', '&gt;')
return data
def attributeEscapingDoneOutside(data):
"""
Escape some character or UTF-8 byte data for inclusion in the top level of
an attribute. L{attributeEscapingDoneOutside} actually passes the data
through unchanged, because L{flattenWithAttributeEscaping} handles the
quoting of the text within attributes outside the generator returned by
L{_flattenElement}; this is used as the C{dataEscaper} argument to that
L{_flattenElement} call so that that generator does not redundantly escape
its text output.
@type data: C{bytes} or C{unicode}
@param data: The string to escape.
@return: The string, unchanged, except for encoding.
@rtype: C{bytes}
"""
if isinstance(data, unicode):
return data.encode("utf-8")
return data
def flattenWithAttributeEscaping(root):
"""
Decorate the generator returned by L{_flattenElement} so that its output is
properly quoted for inclusion within an XML attribute value.
If a L{Tag <twisted.web.template.Tag>} C{x} is flattened within the context
of the contents of another L{Tag <twisted.web.template.Tag>} C{y}, the
metacharacters (C{<>&"}) delimiting C{x} should be passed through
unchanged, but the textual content of C{x} should still be quoted, as
usual. For example: C{<y><x>&amp;</x></y>}. That is the default behavior
of L{_flattenElement} when L{escapeForContent} is passed as the
C{dataEscaper}.
However, when a L{Tag <twisted.web.template.Tag>} C{x} is flattened within
the context of an I{attribute} of another L{Tag <twisted.web.template.Tag>}
C{y}, then the metacharacters delimiting C{x} should be quoted so that it
can be parsed from the attribute's value. In the DOM itself, this is not a
valid thing to do, but given that renderers and slots may be freely moved
around in a L{twisted.web.template} template, it is a condition which may
arise in a document and must be handled in a way which produces valid
output. So, for example, you should be able to get C{<y attr="&lt;x /&gt;"
/>}. This should also be true for other XML/HTML meta-constructs such as
comments and CDATA, so if you were to serialize a L{comment
<twisted.web.template.Comment>} in an attribute you should get C{<y
attr="&lt;-- comment --&gt;" />}. Therefore in order to capture these
meta-characters, the attribute generator from L{_flattenElement} context is
wrapped with an L{flattenWithAttributeEscaping}.
Because I{all} characters serialized in the context of an attribute are
quoted before they are yielded by the generator returned by
L{flattenWithAttributeEscaping}, on the "outside" of the L{_flattenElement}
call, the L{_flattenElement} generator therefore no longer needs to quote
text that appears directly within the attribute itself.
The final case, and hopefully the much more common one as compared to
serializing L{Tag <twisted.web.template.Tag>} and arbitrary L{IRenderable}
objects within an attribute, is to serialize a simple string, and those
should be passed through for L{flattenWithAttributeEscaping} to quote
without applying a second, redundant level of quoting.
@param root: A value that may be yielded by L{_flattenElement}; either an
iterable yielding L{bytes} (or more iterables), or bytes itself.
@type root: L{bytes} or C{iterable}
@return: The same type as L{_flattenElement} returns, with all the bytes
encoded for representation within an attribute.
@rtype: the same type as the C{subFlatten} argument
"""
if isinstance(root, bytes):
root = escapeForContent(root)
root = root.replace('"', '&quot;')
yield root
elif isinstance(root, Deferred):
yield root.addCallback(flattenWithAttributeEscaping)
else:
for subroot in root:
yield flattenWithAttributeEscaping(subroot)
def escapedCDATA(data):
"""
Escape CDATA for inclusion in a document.
@type data: C{str} or C{unicode}
@param data: The string to escape.
@rtype: C{str}
@return: The quoted form of C{data}. If C{data} is unicode, return a utf-8
encoded string.
"""
if isinstance(data, unicode):
data = data.encode('utf-8')
return data.replace(']]>', ']]]]><![CDATA[>')
def escapedComment(data):
"""
Escape a comment for inclusion in a document.
@type data: C{str} or C{unicode}
@param data: The string to escape.
@rtype: C{str}
@return: The quoted form of C{data}. If C{data} is unicode, return a utf-8
encoded string.
"""
if isinstance(data, unicode):
data = data.encode('utf-8')
data = data.replace('--', '- - ').replace('>', '&gt;')
if data and data[-1] == '-':
data += ' '
return data
def _getSlotValue(name, slotData, default=None):
"""
Find the value of the named slot in the given stack of slot data.
"""
for slotFrame in slotData[::-1]:
if slotFrame is not None and name in slotFrame:
return slotFrame[name]
else:
if default is not None:
return default
raise UnfilledSlot(name)
def _flattenElement(request, root, slotData, renderFactory, dataEscaper):
"""
Make C{root} slightly more flat by yielding all its immediate contents as
strings, deferreds or generators that are recursive calls to itself.
@param request: A request object which will be passed to
L{IRenderable.render}.
@param root: An object to be made flatter. This may be of type C{unicode},
C{str}, L{slot}, L{Tag <twisted.web.template.Tag>}, L{URL}, L{tuple},
L{list}, L{GeneratorType}, L{Deferred}, or an object that implements
L{IRenderable}.
@param slotData: A C{list} of C{dict} mapping C{str} slot names to data
with which those slots will be replaced.
@param renderFactory: If not C{None}, an object that provides
L{IRenderable}.
@param dataEscaper: A 1-argument callable which takes L{bytes} or
L{unicode} and returns L{bytes}, quoted as appropriate for the
rendering context. This is really only one of two values:
L{attributeEscapingDoneOutside} or L{escapeForContent}, depending on
whether the rendering context is within an attribute or not. See the
explanation in L{flattenWithAttributeEscaping}.
@return: An iterator that eventually yields L{bytes} that should be written
to the output. However it may also yield other iterators or
L{Deferred}s; if it yields another iterator, the caller will iterate
it; if it yields a L{Deferred}, the result of that L{Deferred} will
either be L{bytes}, in which case it's written, or another generator,
in which case it is iterated. See L{_flattenTree} for the trampoline
that consumes said values.
@rtype: An iterator which yields L{bytes}, L{Deferred}, and more iterators
of the same type.
"""
def keepGoing(newRoot, dataEscaper=dataEscaper,
renderFactory=renderFactory):
return _flattenElement(request, newRoot, slotData, renderFactory,
dataEscaper)
if isinstance(root, (bytes, unicode)):
yield dataEscaper(root)
elif isinstance(root, slot):
slotValue = _getSlotValue(root.name, slotData, root.default)
yield keepGoing(slotValue)
elif isinstance(root, CDATA):
yield '<![CDATA['
yield escapedCDATA(root.data)
yield ']]>'
elif isinstance(root, Comment):
yield '<!--'
yield escapedComment(root.data)
yield '-->'
elif isinstance(root, Tag):
slotData.append(root.slotData)
if root.render is not None:
rendererName = root.render
rootClone = root.clone(False)
rootClone.render = None
renderMethod = renderFactory.lookupRenderMethod(rendererName)
result = renderMethod(request, rootClone)
yield keepGoing(result)
slotData.pop()
return
if not root.tagName:
yield keepGoing(root.children)
return
yield '<'
if isinstance(root.tagName, unicode):
tagName = root.tagName.encode('ascii')
else:
tagName = str(root.tagName)
yield tagName
for k, v in root.attributes.iteritems():
if isinstance(k, unicode):
k = k.encode('ascii')
yield ' ' + k + '="'
# Serialize the contents of the attribute, wrapping the results of
# that serialization so that _everything_ is quoted.
attribute = keepGoing(v, attributeEscapingDoneOutside)
yield flattenWithAttributeEscaping(attribute)
yield '"'
if root.children or tagName not in voidElements:
yield '>'
# Regardless of whether we're in an attribute or not, switch back
# to the escapeForContent dataEscaper. The contents of a tag must
# be quoted no matter what; in the top-level document, just so
# they're valid, and if they're within an attribute, they have to
# be quoted so that after applying the *un*-quoting required to re-
# parse the tag within the attribute, all the quoting is still
# correct.
yield keepGoing(root.children, escapeForContent)
yield '</' + tagName + '>'
else:
yield ' />'
elif isinstance(root, (tuple, list, GeneratorType)):
for element in root:
yield keepGoing(element)
elif isinstance(root, CharRef):
yield '&#%d;' % (root.ordinal,)
elif isinstance(root, Deferred):
yield root.addCallback(lambda result: (result, keepGoing(result)))
elif IRenderable.providedBy(root):
result = root.render(request)
yield keepGoing(result, renderFactory=root)
else:
raise UnsupportedType(root)
def _flattenTree(request, root):
"""
Make C{root} into an iterable of L{bytes} and L{Deferred} by doing a depth
first traversal of the tree.
@param request: A request object which will be passed to
L{IRenderable.render}.
@param root: An object to be made flatter. This may be of type C{unicode},
L{bytes}, L{slot}, L{Tag <twisted.web.template.Tag>}, L{tuple},
L{list}, L{GeneratorType}, L{Deferred}, or something providing
L{IRenderable}.
@return: An iterator which yields objects of type L{bytes} and L{Deferred}.
A L{Deferred} is only yielded when one is encountered in the process of
flattening C{root}. The returned iterator must not be iterated again
until the L{Deferred} is called back.
"""
stack = [_flattenElement(request, root, [], None, escapeForContent)]
while stack:
try:
# In Python 2.5, after an exception, a generator's gi_frame is
# None.
frame = stack[-1].gi_frame
element = stack[-1].next()
except StopIteration:
stack.pop()
except Exception, e:
stack.pop()
roots = []
for generator in stack:
roots.append(generator.gi_frame.f_locals['root'])
roots.append(frame.f_locals['root'])
raise FlattenerError(e, roots, extract_tb(exc_info()[2]))
else:
if type(element) is str:
yield element
elif isinstance(element, Deferred):
def cbx((original, toFlatten)):
stack.append(toFlatten)
return original
yield element.addCallback(cbx)
else:
stack.append(element)
def _writeFlattenedData(state, write, result):
"""
Take strings from an iterator and pass them to a writer function.
@param state: An iterator of C{str} and L{Deferred}. C{str} instances will
be passed to C{write}. L{Deferred} instances will be waited on before
resuming iteration of C{state}.
@param write: A callable which will be invoked with each C{str}
produced by iterating C{state}.
@param result: A L{Deferred} which will be called back when C{state} has
been completely flattened into C{write} or which will be errbacked if
an exception in a generator passed to C{state} or an errback from a
L{Deferred} from state occurs.
@return: C{None}
"""
while True:
try:
element = state.next()
except StopIteration:
result.callback(None)
except:
result.errback()
else:
if type(element) is str:
write(element)
continue
else:
def cby(original):
_writeFlattenedData(state, write, result)
return original
element.addCallbacks(cby, result.errback)
break
def flatten(request, root, write):
"""
Incrementally write out a string representation of C{root} using C{write}.
In order to create a string representation, C{root} will be decomposed into
simpler objects which will themselves be decomposed and so on until strings
or objects which can easily be converted to strings are encountered.
@param request: A request object which will be passed to the C{render}
method of any L{IRenderable} provider which is encountered.
@param root: An object to be made flatter. This may be of type L{unicode},
L{bytes}, L{slot}, L{Tag <twisted.web.template.Tag>}, L{tuple},
L{list}, L{GeneratorType}, L{Deferred}, or something that provides
L{IRenderable}.
@param write: A callable which will be invoked with each L{bytes} produced
by flattening C{root}.
@return: A L{Deferred} which will be called back when C{root} has been
completely flattened into C{write} or which will be errbacked if an
unexpected exception occurs.
"""
result = Deferred()
state = _flattenTree(request, root)
_writeFlattenedData(state, write, result)
return result
def flattenString(request, root):
"""
Collate a string representation of C{root} into a single string.
This is basically gluing L{flatten} to a C{StringIO} and returning the
results. See L{flatten} for the exact meanings of C{request} and
C{root}.
@return: A L{Deferred} which will be called back with a single string as
its result when C{root} has been completely flattened into C{write} or
which will be errbacked if an unexpected exception occurs.
"""
io = StringIO()
d = flatten(request, root, io.write)
d.addCallback(lambda _: io.getvalue())
return d

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,114 @@
# -*- test-case-name: twisted.web.test.test_http -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
HTTP response code definitions.
"""
from __future__ import division, absolute_import
_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
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
REQUESTED_RANGE_NOT_SATISFIABLE = 416
EXPECTATION_FAILED = 417
INTERNAL_SERVER_ERROR = 500
NOT_IMPLEMENTED = 501
BAD_GATEWAY = 502
SERVICE_UNAVAILABLE = 503
GATEWAY_TIMEOUT = 504
HTTP_VERSION_NOT_SUPPORTED = 505
INSUFFICIENT_STORAGE_SPACE = 507
NOT_EXTENDED = 510
RESPONSES = {
# 100
_CONTINUE: "Continue",
SWITCHING: "Switching Protocols",
# 200
OK: "OK",
CREATED: "Created",
ACCEPTED: "Accepted",
NON_AUTHORITATIVE_INFORMATION: "Non-Authoritative Information",
NO_CONTENT: "No Content",
RESET_CONTENT: "Reset Content.",
PARTIAL_CONTENT: "Partial Content",
MULTI_STATUS: "Multi-Status",
# 300
MULTIPLE_CHOICE: "Multiple Choices",
MOVED_PERMANENTLY: "Moved Permanently",
FOUND: "Found",
SEE_OTHER: "See Other",
NOT_MODIFIED: "Not Modified",
USE_PROXY: "Use Proxy",
# 306 not defined??
TEMPORARY_REDIRECT: "Temporary Redirect",
# 400
BAD_REQUEST: "Bad Request",
UNAUTHORIZED: "Unauthorized",
PAYMENT_REQUIRED: "Payment Required",
FORBIDDEN: "Forbidden",
NOT_FOUND: "Not Found",
NOT_ALLOWED: "Method Not Allowed",
NOT_ACCEPTABLE: "Not Acceptable",
PROXY_AUTH_REQUIRED: "Proxy Authentication Required",
REQUEST_TIMEOUT: "Request Time-out",
CONFLICT: "Conflict",
GONE: "Gone",
LENGTH_REQUIRED: "Length Required",
PRECONDITION_FAILED: "Precondition Failed",
REQUEST_ENTITY_TOO_LARGE: "Request Entity Too Large",
REQUEST_URI_TOO_LONG: "Request-URI Too Long",
UNSUPPORTED_MEDIA_TYPE: "Unsupported Media Type",
REQUESTED_RANGE_NOT_SATISFIABLE: "Requested Range not satisfiable",
EXPECTATION_FAILED: "Expectation Failed",
# 500
INTERNAL_SERVER_ERROR: "Internal Server Error",
NOT_IMPLEMENTED: "Not Implemented",
BAD_GATEWAY: "Bad Gateway",
SERVICE_UNAVAILABLE: "Service Unavailable",
GATEWAY_TIMEOUT: "Gateway Time-out",
HTTP_VERSION_NOT_SUPPORTED: "HTTP Version not supported",
INSUFFICIENT_STORAGE_SPACE: "Insufficient Storage Space",
NOT_EXTENDED: "Not Extended"
}

View file

@ -0,0 +1,325 @@
# -*- test-case-name: twisted.web.test.test_stan -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
An s-expression-like syntax for expressing xml in pure python.
Stan tags allow you to build XML documents using Python.
Stan is a DOM, or Document Object Model, implemented using basic Python types
and functions called "flatteners". A flattener is a function that knows how to
turn an object of a specific type into something that is closer to an HTML
string. Stan differs from the W3C DOM by not being as cumbersome and heavy
weight. Since the object model is built using simple python types such as lists,
strings, and dictionaries, the API is simpler and constructing a DOM less
cumbersome.
@var voidElements: the names of HTML 'U{void
elements<http://www.whatwg.org/specs/web-apps/current-work/multipage/syntax.html#void-elements>}';
those which can't have contents and can therefore be self-closing in the
output.
"""
class slot(object):
"""
Marker for markup insertion in a template.
@type name: C{str}
@ivar name: The name of this slot. The key which must be used in
L{Tag.fillSlots} to fill it.
@type children: C{list}
@ivar children: The L{Tag} objects included in this L{slot}'s template.
@type default: anything flattenable, or C{NoneType}
@ivar default: The default contents of this slot, if it is left unfilled.
If this is C{None}, an L{UnfilledSlot} will be raised, rather than
C{None} actually being used.
@type filename: C{str} or C{NoneType}
@ivar filename: The name of the XML file from which this tag was parsed.
If it was not parsed from an XML file, C{None}.
@type lineNumber: C{int} or C{NoneType}
@ivar lineNumber: The line number on which this tag was encountered in the
XML file from which it was parsed. If it was not parsed from an XML
file, C{None}.
@type columnNumber: C{int} or C{NoneType}
@ivar columnNumber: The column number at which this tag was encountered in
the XML file from which it was parsed. If it was not parsed from an
XML file, C{None}.
"""
def __init__(self, name, default=None, filename=None, lineNumber=None,
columnNumber=None):
self.name = name
self.children = []
self.default = default
self.filename = filename
self.lineNumber = lineNumber
self.columnNumber = columnNumber
def __repr__(self):
return "slot(%r)" % (self.name,)
class Tag(object):
"""
A L{Tag} represents an XML tags with a tag name, attributes, and children.
A L{Tag} can be constructed using the special L{twisted.web.template.tags}
object, or it may be constructed directly with a tag name. L{Tag}s have a
special method, C{__call__}, which makes representing trees of XML natural
using pure python syntax.
@ivar tagName: The name of the represented element. For a tag like
C{<div></div>}, this would be C{"div"}.
@type tagName: C{str}
@ivar attributes: The attributes of the element.
@type attributes: C{dict} mapping C{str} to renderable objects.
@ivar children: The child L{Tag}s of this C{Tag}.
@type children: C{list} of renderable objects.
@ivar render: The name of the render method to use for this L{Tag}. This
name will be looked up at render time by the
L{twisted.web.template.Element} doing the rendering, via
L{twisted.web.template.Element.lookupRenderMethod}, to determine which
method to call.
@type render: C{str}
@type filename: C{str} or C{NoneType}
@ivar filename: The name of the XML file from which this tag was parsed.
If it was not parsed from an XML file, C{None}.
@type lineNumber: C{int} or C{NoneType}
@ivar lineNumber: The line number on which this tag was encountered in the
XML file from which it was parsed. If it was not parsed from an XML
file, C{None}.
@type columnNumber: C{int} or C{NoneType}
@ivar columnNumber: The column number at which this tag was encountered in
the XML file from which it was parsed. If it was not parsed from an
XML file, C{None}.
@type slotData: C{dict} or C{NoneType}
@ivar slotData: The data which can fill slots. If present, a dictionary
mapping slot names to renderable values. The values in this dict might
be anything that can be present as the child of a L{Tag}; strings,
lists, L{Tag}s, generators, etc.
"""
slotData = None
filename = None
lineNumber = None
columnNumber = None
def __init__(self, tagName, attributes=None, children=None, render=None,
filename=None, lineNumber=None, columnNumber=None):
self.tagName = tagName
self.render = render
if attributes is None:
self.attributes = {}
else:
self.attributes = attributes
if children is None:
self.children = []
else:
self.children = children
if filename is not None:
self.filename = filename
if lineNumber is not None:
self.lineNumber = lineNumber
if columnNumber is not None:
self.columnNumber = columnNumber
def fillSlots(self, **slots):
"""
Remember the slots provided at this position in the DOM.
During the rendering of children of this node, slots with names in
C{slots} will be rendered as their corresponding values.
@return: C{self}. This enables the idiom C{return tag.fillSlots(...)} in
renderers.
"""
if self.slotData is None:
self.slotData = {}
self.slotData.update(slots)
return self
def __call__(self, *children, **kw):
"""
Add children and change attributes on this tag.
This is implemented using __call__ because it then allows the natural
syntax::
table(tr1, tr2, width="100%", height="50%", border="1")
Children may be other tag instances, strings, functions, or any other
object which has a registered flatten.
Attributes may be 'transparent' tag instances (so that
C{a(href=transparent(data="foo", render=myhrefrenderer))} works),
strings, functions, or any other object which has a registered
flattener.
If the attribute is a python keyword, such as 'class', you can add an
underscore to the name, like 'class_'.
There is one special keyword argument, 'render', which will be used as
the name of the renderer and saved as the 'render' attribute of this
instance, rather than the DOM 'render' attribute in the attributes
dictionary.
"""
self.children.extend(children)
for k, v in kw.iteritems():
if k[-1] == '_':
k = k[:-1]
if k == 'render':
self.render = v
else:
self.attributes[k] = v
return self
def _clone(self, obj, deep):
"""
Clone an arbitrary object; used by L{Tag.clone}.
@param obj: an object with a clone method, a list or tuple, or something
which should be immutable.
@param deep: whether to continue cloning child objects; i.e. the
contents of lists, the sub-tags within a tag.
@return: a clone of C{obj}.
"""
if hasattr(obj, 'clone'):
return obj.clone(deep)
elif isinstance(obj, (list, tuple)):
return [self._clone(x, deep) for x in obj]
else:
return obj
def clone(self, deep=True):
"""
Return a clone of this tag. If deep is True, clone all of this tag's
children. Otherwise, just shallow copy the children list without copying
the children themselves.
"""
if deep:
newchildren = [self._clone(x, True) for x in self.children]
else:
newchildren = self.children[:]
newattrs = self.attributes.copy()
for key in newattrs:
newattrs[key] = self._clone(newattrs[key], True)
newslotdata = None
if self.slotData:
newslotdata = self.slotData.copy()
for key in newslotdata:
newslotdata[key] = self._clone(newslotdata[key], True)
newtag = Tag(
self.tagName,
attributes=newattrs,
children=newchildren,
render=self.render,
filename=self.filename,
lineNumber=self.lineNumber,
columnNumber=self.columnNumber)
newtag.slotData = newslotdata
return newtag
def clear(self):
"""
Clear any existing children from this tag.
"""
self.children = []
return self
def __repr__(self):
rstr = ''
if self.attributes:
rstr += ', attributes=%r' % self.attributes
if self.children:
rstr += ', children=%r' % self.children
return "Tag(%r%s)" % (self.tagName, rstr)
voidElements = ('img', 'br', 'hr', 'base', 'meta', 'link', 'param', 'area',
'input', 'col', 'basefont', 'isindex', 'frame', 'command',
'embed', 'keygen', 'source', 'track', 'wbs')
class CDATA(object):
"""
A C{<![CDATA[]]>} block from a template. Given a separate representation in
the DOM so that they may be round-tripped through rendering without losing
information.
@ivar data: The data between "C{<![CDATA[}" and "C{]]>}".
@type data: C{unicode}
"""
def __init__(self, data):
self.data = data
def __repr__(self):
return 'CDATA(%r)' % (self.data,)
class Comment(object):
"""
A C{<!-- -->} comment from a template. Given a separate representation in
the DOM so that they may be round-tripped through rendering without losing
information.
@ivar data: The data between "C{<!--}" and "C{-->}".
@type data: C{unicode}
"""
def __init__(self, data):
self.data = data
def __repr__(self):
return 'Comment(%r)' % (self.data,)
class CharRef(object):
"""
A numeric character reference. Given a separate representation in the DOM
so that non-ASCII characters may be output as pure ASCII.
@ivar ordinal: The ordinal value of the unicode character to which this is
object refers.
@type ordinal: C{int}
@since: 12.0
"""
def __init__(self, ordinal):
self.ordinal = ordinal
def __repr__(self):
return "CharRef(%d)" % (self.ordinal,)

View file

@ -0,0 +1,11 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
# This is an auto-generated file. Do not edit it.
"""
Provides Twisted version information.
"""
from twisted.python import versions
version = versions.Version('twisted.web', 14, 0, 0)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,24 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
I am a simple test resource.
"""
from twisted.web import static
class Test(static.Data):
isLeaf = True
def __init__(self):
static.Data.__init__(
self,
"""
<html>
<head><title>Twisted Web Demo</title><head>
<body>
Hello! This is a Twisted Web test page.
</body>
</html>
""",
"text/html")

View file

@ -0,0 +1,373 @@
# -*- test-case-name: twisted.web.test.test_distrib -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Distributed web servers.
This is going to have to be refactored so that argument parsing is done
by each subprocess and not by the main web server (i.e. GET, POST etc.).
"""
# System Imports
import types, os, copy, cStringIO
try:
import pwd
except ImportError:
pwd = None
from xml.dom.minidom import Element, Text
# Twisted Imports
from twisted.spread import pb
from twisted.spread.banana import SIZE_LIMIT
from twisted.web import http, resource, server, html, static
from twisted.web.http_headers import Headers
from twisted.python import log
from twisted.persisted import styles
from twisted.internet import address, reactor
class _ReferenceableProducerWrapper(pb.Referenceable):
def __init__(self, producer):
self.producer = producer
def remote_resumeProducing(self):
self.producer.resumeProducing()
def remote_pauseProducing(self):
self.producer.pauseProducing()
def remote_stopProducing(self):
self.producer.stopProducing()
class Request(pb.RemoteCopy, server.Request):
"""
A request which was received by a L{ResourceSubscription} and sent via
PB to a distributed node.
"""
def setCopyableState(self, state):
"""
Initialize this L{twisted.web.distrib.Request} based on the copied
state so that it closely resembles a L{twisted.web.server.Request}.
"""
for k in 'host', 'client':
tup = state[k]
addrdesc = {'INET': 'TCP', 'UNIX': 'UNIX'}[tup[0]]
addr = {'TCP': lambda: address.IPv4Address(addrdesc,
tup[1], tup[2]),
'UNIX': lambda: address.UNIXAddress(tup[1])}[addrdesc]()
state[k] = addr
state['requestHeaders'] = Headers(dict(state['requestHeaders']))
pb.RemoteCopy.setCopyableState(self, state)
# Emulate the local request interface --
self.content = cStringIO.StringIO(self.content_data)
self.finish = self.remote.remoteMethod('finish')
self.setHeader = self.remote.remoteMethod('setHeader')
self.addCookie = self.remote.remoteMethod('addCookie')
self.setETag = self.remote.remoteMethod('setETag')
self.setResponseCode = self.remote.remoteMethod('setResponseCode')
self.setLastModified = self.remote.remoteMethod('setLastModified')
# To avoid failing if a resource tries to write a very long string
# all at once, this one will be handled slightly differently.
self._write = self.remote.remoteMethod('write')
def write(self, bytes):
"""
Write the given bytes to the response body.
@param bytes: The bytes to write. If this is longer than 640k, it
will be split up into smaller pieces.
"""
start = 0
end = SIZE_LIMIT
while True:
self._write(bytes[start:end])
start += SIZE_LIMIT
end += SIZE_LIMIT
if start >= len(bytes):
break
def registerProducer(self, producer, streaming):
self.remote.callRemote("registerProducer",
_ReferenceableProducerWrapper(producer),
streaming).addErrback(self.fail)
def unregisterProducer(self):
self.remote.callRemote("unregisterProducer").addErrback(self.fail)
def fail(self, failure):
log.err(failure)
pb.setUnjellyableForClass(server.Request, Request)
class Issue:
def __init__(self, request):
self.request = request
def finished(self, result):
if result != server.NOT_DONE_YET:
assert isinstance(result, types.StringType),\
"return value not a string"
self.request.write(result)
self.request.finish()
def failed(self, failure):
#XXX: Argh. FIXME.
failure = str(failure)
self.request.write(
resource.ErrorPage(http.INTERNAL_SERVER_ERROR,
"Server Connection Lost",
"Connection to distributed server lost:" +
html.PRE(failure)).
render(self.request))
self.request.finish()
log.msg(failure)
class ResourceSubscription(resource.Resource):
isLeaf = 1
waiting = 0
def __init__(self, host, port):
resource.Resource.__init__(self)
self.host = host
self.port = port
self.pending = []
self.publisher = None
def __getstate__(self):
"""Get persistent state for this ResourceSubscription.
"""
# When I unserialize,
state = copy.copy(self.__dict__)
# Publisher won't be connected...
state['publisher'] = None
# I won't be making a connection
state['waiting'] = 0
# There will be no pending requests.
state['pending'] = []
return state
def connected(self, publisher):
"""I've connected to a publisher; I'll now send all my requests.
"""
log.msg('connected to publisher')
publisher.broker.notifyOnDisconnect(self.booted)
self.publisher = publisher
self.waiting = 0
for request in self.pending:
self.render(request)
self.pending = []
def notConnected(self, msg):
"""I can't connect to a publisher; I'll now reply to all pending
requests.
"""
log.msg("could not connect to distributed web service: %s" % msg)
self.waiting = 0
self.publisher = None
for request in self.pending:
request.write("Unable to connect to distributed server.")
request.finish()
self.pending = []
def booted(self):
self.notConnected("connection dropped")
def render(self, request):
"""Render this request, from my server.
This will always be asynchronous, and therefore return NOT_DONE_YET.
It spins off a request to the pb client, and either adds it to the list
of pending issues or requests it immediately, depending on if the
client is already connected.
"""
if not self.publisher:
self.pending.append(request)
if not self.waiting:
self.waiting = 1
bf = pb.PBClientFactory()
timeout = 10
if self.host == "unix":
reactor.connectUNIX(self.port, bf, timeout)
else:
reactor.connectTCP(self.host, self.port, bf, timeout)
d = bf.getRootObject()
d.addCallbacks(self.connected, self.notConnected)
else:
i = Issue(request)
self.publisher.callRemote('request', request).addCallbacks(i.finished, i.failed)
return server.NOT_DONE_YET
class ResourcePublisher(pb.Root, styles.Versioned):
"""
L{ResourcePublisher} exposes a remote API which can be used to respond
to request.
@ivar site: The site which will be used for resource lookup.
@type site: L{twisted.web.server.Site}
"""
def __init__(self, site):
self.site = site
persistenceVersion = 2
def upgradeToVersion2(self):
self.application.authorizer.removeIdentity("web")
del self.application.services[self.serviceName]
del self.serviceName
del self.application
del self.perspectiveName
def getPerspectiveNamed(self, name):
return self
def remote_request(self, request):
"""
Look up the resource for the given request and render it.
"""
res = self.site.getResourceFor(request)
log.msg( request )
result = res.render(request)
if result is not server.NOT_DONE_YET:
request.write(result)
request.finish()
return server.NOT_DONE_YET
class UserDirectory(resource.Resource):
"""
A resource which lists available user resources and serves them as
children.
@ivar _pwd: An object like L{pwd} which is used to enumerate users and
their home directories.
"""
userDirName = 'public_html'
userSocketName = '.twistd-web-pb'
template = """
<html>
<head>
<title>twisted.web.distrib.UserDirectory</title>
<style>
a
{
font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
color: #369;
text-decoration: none;
}
th
{
font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
font-weight: bold;
text-decoration: none;
text-align: left;
}
pre, code
{
font-family: "Courier New", Courier, monospace;
}
p, body, td, ol, ul, menu, blockquote, div
{
font-family: Lucida, Verdana, Helvetica, Arial, sans-serif;
color: #000;
}
</style>
</head>
<body>
<h1>twisted.web.distrib.UserDirectory</h1>
%(users)s
</body>
</html>
"""
def __init__(self, userDatabase=None):
resource.Resource.__init__(self)
if userDatabase is None:
userDatabase = pwd
self._pwd = userDatabase
def _users(self):
"""
Return a list of two-tuples giving links to user resources and text to
associate with those links.
"""
users = []
for user in self._pwd.getpwall():
name, passwd, uid, gid, gecos, dir, shell = user
realname = gecos.split(',')[0]
if not realname:
realname = name
if os.path.exists(os.path.join(dir, self.userDirName)):
users.append((name, realname + ' (file)'))
twistdsock = os.path.join(dir, self.userSocketName)
if os.path.exists(twistdsock):
linkName = name + '.twistd'
users.append((linkName, realname + ' (twistd)'))
return users
def render_GET(self, request):
"""
Render as HTML a listing of all known users with links to their
personal resources.
"""
listing = Element('ul')
for link, text in self._users():
linkElement = Element('a')
linkElement.setAttribute('href', link + '/')
textNode = Text()
textNode.data = text
linkElement.appendChild(textNode)
item = Element('li')
item.appendChild(linkElement)
listing.appendChild(item)
return self.template % {'users': listing.toxml()}
def getChild(self, name, request):
if name == '':
return self
td = '.twistd'
if name[-len(td):] == td:
username = name[:-len(td)]
sub = 1
else:
username = name
sub = 0
try:
pw_name, pw_passwd, pw_uid, pw_gid, pw_gecos, pw_dir, pw_shell \
= self._pwd.getpwnam(username)
except KeyError:
return resource.NoResource()
if sub:
twistdsock = os.path.join(pw_dir, self.userSocketName)
rs = ResourceSubscription('unix',twistdsock)
self.putChild(name, rs)
return rs
else:
path = os.path.join(pw_dir, self.userDirName)
if not os.path.exists(path):
return resource.NoResource()
return static.File(path)

View file

@ -0,0 +1,268 @@
# -*- test-case-name: twisted.web.test.test_domhelpers -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
A library for performing interesting tasks with DOM objects.
"""
import StringIO
from twisted.web import microdom
from twisted.web.microdom import getElementsByTagName, escape, unescape
class NodeLookupError(Exception):
pass
def substitute(request, node, subs):
"""
Look through the given node's children for strings, and
attempt to do string substitution with the given parameter.
"""
for child in node.childNodes:
if hasattr(child, 'nodeValue') and child.nodeValue:
child.replaceData(0, len(child.nodeValue), child.nodeValue % subs)
substitute(request, child, subs)
def _get(node, nodeId, nodeAttrs=('id','class','model','pattern')):
"""
(internal) Get a node with the specified C{nodeId} as any of the C{class},
C{id} or C{pattern} attributes.
"""
if hasattr(node, 'hasAttributes') and node.hasAttributes():
for nodeAttr in nodeAttrs:
if (str (node.getAttribute(nodeAttr)) == nodeId):
return node
if node.hasChildNodes():
if hasattr(node.childNodes, 'length'):
length = node.childNodes.length
else:
length = len(node.childNodes)
for childNum in range(length):
result = _get(node.childNodes[childNum], nodeId)
if result: return result
def get(node, nodeId):
"""
Get a node with the specified C{nodeId} as any of the C{class},
C{id} or C{pattern} attributes. If there is no such node, raise
L{NodeLookupError}.
"""
result = _get(node, nodeId)
if result: return result
raise NodeLookupError, nodeId
def getIfExists(node, nodeId):
"""
Get a node with the specified C{nodeId} as any of the C{class},
C{id} or C{pattern} attributes. If there is no such node, return
C{None}.
"""
return _get(node, nodeId)
def getAndClear(node, nodeId):
"""Get a node with the specified C{nodeId} as any of the C{class},
C{id} or C{pattern} attributes. If there is no such node, raise
L{NodeLookupError}. Remove all child nodes before returning.
"""
result = get(node, nodeId)
if result:
clearNode(result)
return result
def clearNode(node):
"""
Remove all children from the given node.
"""
node.childNodes[:] = []
def locateNodes(nodeList, key, value, noNesting=1):
"""
Find subnodes in the given node where the given attribute
has the given value.
"""
returnList = []
if not isinstance(nodeList, type([])):
return locateNodes(nodeList.childNodes, key, value, noNesting)
for childNode in nodeList:
if not hasattr(childNode, 'getAttribute'):
continue
if str(childNode.getAttribute(key)) == value:
returnList.append(childNode)
if noNesting:
continue
returnList.extend(locateNodes(childNode, key, value, noNesting))
return returnList
def superSetAttribute(node, key, value):
if not hasattr(node, 'setAttribute'): return
node.setAttribute(key, value)
if node.hasChildNodes():
for child in node.childNodes:
superSetAttribute(child, key, value)
def superPrependAttribute(node, key, value):
if not hasattr(node, 'setAttribute'): return
old = node.getAttribute(key)
if old:
node.setAttribute(key, value+'/'+old)
else:
node.setAttribute(key, value)
if node.hasChildNodes():
for child in node.childNodes:
superPrependAttribute(child, key, value)
def superAppendAttribute(node, key, value):
if not hasattr(node, 'setAttribute'): return
old = node.getAttribute(key)
if old:
node.setAttribute(key, old + '/' + value)
else:
node.setAttribute(key, value)
if node.hasChildNodes():
for child in node.childNodes:
superAppendAttribute(child, key, value)
def gatherTextNodes(iNode, dounescape=0, joinWith=""):
"""Visit each child node and collect its text data, if any, into a string.
For example::
>>> doc=microdom.parseString('<a>1<b>2<c>3</c>4</b></a>')
>>> gatherTextNodes(doc.documentElement)
'1234'
With dounescape=1, also convert entities back into normal characters.
@return: the gathered nodes as a single string
@rtype: str
"""
gathered=[]
gathered_append=gathered.append
slice=[iNode]
while len(slice)>0:
c=slice.pop(0)
if hasattr(c, 'nodeValue') and c.nodeValue is not None:
if dounescape:
val=unescape(c.nodeValue)
else:
val=c.nodeValue
gathered_append(val)
slice[:0]=c.childNodes
return joinWith.join(gathered)
class RawText(microdom.Text):
"""This is an evil and horrible speed hack. Basically, if you have a big
chunk of XML that you want to insert into the DOM, but you don't want to
incur the cost of parsing it, you can construct one of these and insert it
into the DOM. This will most certainly only work with microdom as the API
for converting nodes to xml is different in every DOM implementation.
This could be improved by making this class a Lazy parser, so if you
inserted this into the DOM and then later actually tried to mutate this
node, it would be parsed then.
"""
def writexml(self, writer, indent="", addindent="", newl="", strip=0, nsprefixes=None, namespace=None):
writer.write("%s%s%s" % (indent, self.data, newl))
def findNodes(parent, matcher, accum=None):
if accum is None:
accum = []
if not parent.hasChildNodes():
return accum
for child in parent.childNodes:
# print child, child.nodeType, child.nodeName
if matcher(child):
accum.append(child)
findNodes(child, matcher, accum)
return accum
def findNodesShallowOnMatch(parent, matcher, recurseMatcher, accum=None):
if accum is None:
accum = []
if not parent.hasChildNodes():
return accum
for child in parent.childNodes:
# print child, child.nodeType, child.nodeName
if matcher(child):
accum.append(child)
if recurseMatcher(child):
findNodesShallowOnMatch(child, matcher, recurseMatcher, accum)
return accum
def findNodesShallow(parent, matcher, accum=None):
if accum is None:
accum = []
if not parent.hasChildNodes():
return accum
for child in parent.childNodes:
if matcher(child):
accum.append(child)
else:
findNodes(child, matcher, accum)
return accum
def findElementsWithAttributeShallow(parent, attribute):
"""
Return an iterable of the elements which are direct children of C{parent}
and which have the C{attribute} attribute.
"""
return findNodesShallow(parent,
lambda n: getattr(n, 'tagName', None) is not None and
n.hasAttribute(attribute))
def findElements(parent, matcher):
"""
Return an iterable of the elements which are children of C{parent} for
which the predicate C{matcher} returns true.
"""
return findNodes(
parent,
lambda n, matcher=matcher: getattr(n, 'tagName', None) is not None and
matcher(n))
def findElementsWithAttribute(parent, attribute, value=None):
if value:
return findElements(
parent,
lambda n, attribute=attribute, value=value:
n.hasAttribute(attribute) and n.getAttribute(attribute) == value)
else:
return findElements(
parent,
lambda n, attribute=attribute: n.hasAttribute(attribute))
def findNodesNamed(parent, name):
return findNodes(parent, lambda n, name=name: n.nodeName == name)
def writeNodeData(node, oldio):
for subnode in node.childNodes:
if hasattr(subnode, 'data'):
oldio.write(subnode.data)
else:
writeNodeData(subnode, oldio)
def getNodeText(node):
oldio = StringIO.StringIO()
writeNodeData(node, oldio)
return oldio.getvalue()
def getParents(node):
l = []
while node:
l.append(node)
node = node.parentNode
return l
def namedChildren(parent, nodeName):
"""namedChildren(parent, nodeName) -> children (not descendants) of parent
that have tagName == nodeName
"""
return [n for n in parent.childNodes if getattr(n, 'tagName', '')==nodeName]

View file

@ -0,0 +1,380 @@
# -*- test-case-name: twisted.web.test.test_error -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Exception definitions for L{twisted.web}.
"""
from __future__ import division, absolute_import
__all__ = [
'Error', 'PageRedirect', 'InfiniteRedirection', 'RenderError',
'MissingRenderMethod', 'MissingTemplateLoader', 'UnexposedMethodError',
'UnfilledSlot', 'UnsupportedType', 'FlattenerError',
'RedirectWithNoLocation',
]
from collections import Sequence
from twisted.web._responses import RESPONSES
class Error(Exception):
"""
A basic HTTP error.
@type status: C{str}
@ivar status: Refers to an HTTP status code, for example C{http.NOT_FOUND}.
@type message: C{str}
@param message: A short error message, for example "NOT FOUND".
@type response: C{bytes}
@ivar response: A complete HTML document for an error page.
"""
def __init__(self, code, message=None, response=None):
"""
Initializes a basic exception.
@type code: C{str}
@param code: Refers to an HTTP status code, for example
C{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
descriptive bytestring that is used instead.
@type message: C{str}
@param message: A short error message, for example "NOT FOUND".
@type response: C{bytes}
@param response: A complete HTML document for an error page.
"""
if not message:
try:
message = RESPONSES.get(int(code))
except ValueError:
# If code wasn't a stringified int, can't map the
# status code to a descriptive string so keep message
# unchanged.
pass
Exception.__init__(self, code, message, response)
self.status = code
self.message = message
self.response = response
def __str__(self):
return '%s %s' % (self.status, self.message)
class PageRedirect(Error):
"""
A request resulted in an HTTP redirect.
@type location: C{str}
@ivar location: The location of the redirect which was not followed.
"""
def __init__(self, code, message=None, response=None, location=None):
"""
Initializes a page redirect exception.
@type code: C{str}
@param code: Refers to an HTTP status code, for example
C{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
descriptive string that is used instead.
@type message: C{str}
@param message: A short error message, for example "NOT FOUND".
@type response: C{str}
@param response: A complete HTML document for an error page.
@type location: C{str}
@param location: The location response-header field value. It is an
absolute URI used to redirect the receiver to a location other than
the Request-URI so the request can be completed.
"""
if not message:
try:
message = RESPONSES.get(int(code))
except ValueError:
# If code wasn't a stringified int, can't map the
# status code to a descriptive string so keep message
# unchanged.
pass
if location and message:
message = "%s to %s" % (message, location)
Error.__init__(self, code, message, response)
self.location = location
class InfiniteRedirection(Error):
"""
HTTP redirection is occurring endlessly.
@type location: C{str}
@ivar location: The first URL in the series of redirections which was
not followed.
"""
def __init__(self, code, message=None, response=None, location=None):
"""
Initializes an infinite redirection exception.
@type code: C{str}
@param code: Refers to an HTTP status code, for example
C{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to a
descriptive string that is used instead.
@type message: C{str}
@param message: A short error message, for example "NOT FOUND".
@type response: C{str}
@param response: A complete HTML document for an error page.
@type location: C{str}
@param location: The location response-header field value. It is an
absolute URI used to redirect the receiver to a location other than
the Request-URI so the request can be completed.
"""
if not message:
try:
message = RESPONSES.get(int(code))
except ValueError:
# If code wasn't a stringified int, can't map the
# status code to a descriptive string so keep message
# unchanged.
pass
if location and message:
message = "%s to %s" % (message, location)
Error.__init__(self, code, message, response)
self.location = location
class RedirectWithNoLocation(Error):
"""
Exception passed to L{ResponseFailed} if we got a redirect without a
C{Location} header field.
@since: 11.1
"""
def __init__(self, code, message, uri):
"""
Initializes a page redirect exception when no location is given.
@type code: C{str}
@param code: Refers to an HTTP status code, for example
C{http.NOT_FOUND}. If no C{message} is given, C{code} is mapped to
a descriptive string that is used instead.
@type message: C{str}
@param message: A short error message.
@type uri: C{str}
@param uri: The URI which failed to give a proper location header
field.
"""
message = "%s to %s" % (message, uri)
Error.__init__(self, code, message)
self.uri = uri
class UnsupportedMethod(Exception):
"""
Raised by a resource when faced with a strange request method.
RFC 2616 (HTTP 1.1) gives us two choices when faced with this situtation:
If the type of request is known to us, but not allowed for the requested
resource, respond with NOT_ALLOWED. Otherwise, if the request is something
we don't know how to deal with in any case, respond with NOT_IMPLEMENTED.
When this exception is raised by a Resource's render method, the server
will make the appropriate response.
This exception's first argument MUST be a sequence of the methods the
resource *does* support.
"""
allowedMethods = ()
def __init__(self, allowedMethods, *args):
Exception.__init__(self, allowedMethods, *args)
self.allowedMethods = allowedMethods
if not isinstance(allowedMethods, Sequence):
raise TypeError(
"First argument must be a sequence of supported methods, "
"but my first argument is not a sequence.")
class SchemeNotSupported(Exception):
"""
The scheme of a URI was not one of the supported values.
"""
class RenderError(Exception):
"""
Base exception class for all errors which can occur during template
rendering.
"""
class MissingRenderMethod(RenderError):
"""
Tried to use a render method which does not exist.
@ivar element: The element which did not have the render method.
@ivar renderName: The name of the renderer which could not be found.
"""
def __init__(self, element, renderName):
RenderError.__init__(self, element, renderName)
self.element = element
self.renderName = renderName
def __repr__(self):
return '%r: %r had no render method named %r' % (
self.__class__.__name__, self.element, self.renderName)
class MissingTemplateLoader(RenderError):
"""
L{MissingTemplateLoader} is raised when trying to render an Element without
a template loader, i.e. a C{loader} attribute.
@ivar element: The Element which did not have a document factory.
"""
def __init__(self, element):
RenderError.__init__(self, element)
self.element = element
def __repr__(self):
return '%r: %r had no loader' % (self.__class__.__name__,
self.element)
class UnexposedMethodError(Exception):
"""
Raised on any attempt to get a method which has not been exposed.
"""
class UnfilledSlot(Exception):
"""
During flattening, a slot with no associated data was encountered.
"""
class UnsupportedType(Exception):
"""
During flattening, an object of a type which cannot be flattened was
encountered.
"""
class FlattenerError(Exception):
"""
An error occurred while flattening an object.
@ivar _roots: A list of the objects on the flattener's stack at the time
the unflattenable object was encountered. The first element is least
deeply nested object and the last element is the most deeply nested.
"""
def __init__(self, exception, roots, traceback):
self._exception = exception
self._roots = roots
self._traceback = traceback
Exception.__init__(self, exception, roots, traceback)
def _formatRoot(self, obj):
"""
Convert an object from C{self._roots} to a string suitable for
inclusion in a render-traceback (like a normal Python traceback, but
can include "frame" source locations which are not in Python source
files).
@param obj: Any object which can be a render step I{root}.
Typically, L{Tag}s, strings, and other simple Python types.
@return: A string representation of C{obj}.
@rtype: L{str}
"""
# There's a circular dependency between this class and 'Tag', although
# only for an isinstance() check.
from twisted.web.template import Tag
if isinstance(obj, (str, unicode)):
# It's somewhat unlikely that there will ever be a str in the roots
# list. However, something like a MemoryError during a str.replace
# call (eg, replacing " with &quot;) could possibly cause this.
# Likewise, UTF-8 encoding a unicode string to a byte string might
# fail like this.
if len(obj) > 40:
if isinstance(obj, str):
prefix = 1
else:
prefix = 2
return repr(obj[:20])[:-1] + '<...>' + repr(obj[-20:])[prefix:]
else:
return repr(obj)
elif isinstance(obj, Tag):
if obj.filename is None:
return 'Tag <' + obj.tagName + '>'
else:
return "File \"%s\", line %d, column %d, in \"%s\"" % (
obj.filename, obj.lineNumber,
obj.columnNumber, obj.tagName)
else:
return repr(obj)
def __repr__(self):
"""
Present a string representation which includes a template traceback, so
we can tell where this error occurred in the template, as well as in
Python.
"""
# Avoid importing things unnecessarily until we actually need them;
# since this is an 'error' module we should be extra paranoid about
# that.
from traceback import format_list
if self._roots:
roots = ' ' + '\n '.join([
self._formatRoot(r) for r in self._roots]) + '\n'
else:
roots = ''
if self._traceback:
traceback = '\n'.join([
line
for entry in format_list(self._traceback)
for line in entry.splitlines()]) + '\n'
else:
traceback = ''
return (
'Exception while flattening:\n' +
roots + traceback +
self._exception.__class__.__name__ + ': ' +
str(self._exception) + '\n')
def __str__(self):
return repr(self)

View file

@ -0,0 +1,71 @@
<div xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
<style type="text/css">
div.error {
color: red;
font-family: Verdana, Arial, helvetica, sans-serif;
font-weight: bold;
}
div {
font-family: Verdana, Arial, helvetica, sans-serif;
}
div.stackTrace {
}
div.frame {
padding: 1em;
background: white;
border-bottom: thin black dashed;
}
div.frame:first-child {
padding: 1em;
background: white;
border-top: thin black dashed;
border-bottom: thin black dashed;
}
div.location {
}
span.function {
font-weight: bold;
font-family: "Courier New", courier, monospace;
}
div.snippet {
margin-bottom: 0.5em;
margin-left: 1em;
background: #FFFFDD;
}
div.snippetHighlightLine {
color: red;
}
span.code {
font-family: "Courier New", courier, monospace;
}
</style>
<div class="error">
<span t:render="type" />: <span t:render="value" />
</div>
<div class="stackTrace" t:render="traceback">
<div class="frame" t:render="frames">
<div class="location">
<span t:render="filename" />:<span t:render="lineNumber" /> in <span class="function" t:render="function" />
</div>
<div class="snippet" t:render="source">
<div t:render="sourceLines">
<span class="lineno" t:render="lineNumber" />
<code class="code" t:render="sourceLine" />
</div>
</div>
</div>
</div>
<div class="error">
<span t:render="type" />: <span t:render="value" />
</div>
</div>

View file

@ -0,0 +1,17 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Resource traversal integration with L{twisted.cred} to allow for
authentication and authorization of HTTP requests.
"""
# Expose HTTP authentication classes here.
from twisted.web._auth.wrapper import HTTPAuthSessionWrapper
from twisted.web._auth.basic import BasicCredentialFactory
from twisted.web._auth.digest import DigestCredentialFactory
__all__ = [
"HTTPAuthSessionWrapper",
"BasicCredentialFactory", "DigestCredentialFactory"]

View file

@ -0,0 +1,46 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""I hold HTML generation helpers.
"""
from cgi import escape
from twisted.python import log
from twisted.python.compat import NativeStringIO as StringIO
def PRE(text):
"Wrap <pre> tags around some text and HTML-escape it."
return "<pre>"+escape(text)+"</pre>"
def UL(lst):
io = StringIO()
io.write("<ul>\n")
for el in lst:
io.write("<li> %s</li>\n" % el)
io.write("</ul>")
return io.getvalue()
def linkList(lst):
io = StringIO()
io.write("<ul>\n")
for hr, el in lst:
io.write('<li> <a href="%s">%s</a></li>\n' % (hr, el))
io.write("</ul>")
return io.getvalue()
def output(func, *args, **kw):
"""output(func, *args, **kw) -> html string
Either return the result of a function (which presumably returns an
HTML-legal string) or a sparse HTMLized error message and a message
in the server log.
"""
try:
return func(*args, **kw)
except:
log.msg("Error calling %r:" % (func,))
log.err()
return PRE("An error occurred.")

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,261 @@
# -*- test-case-name: twisted.web.test.test_http_headers
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
An API for storing HTTP header names and values.
"""
from __future__ import division, absolute_import
from collections import MutableMapping
from twisted.python.compat import comparable, cmp
def _dashCapitalize(name):
"""
Return a byte string which is capitalized using '-' as a word separator.
@param name: The name of the header to capitalize.
@type name: C{bytes}
@return: The given header capitalized using '-' as a word separator.
@rtype: C{bytes}
"""
return b'-'.join([word.capitalize() for word in name.split(b'-')])
class _DictHeaders(MutableMapping):
"""
A C{dict}-like wrapper around L{Headers} to provide backwards compatibility
for L{twisted.web.http.Request.received_headers} and
L{twisted.web.http.Request.headers} which used to be plain C{dict}
instances.
@type _headers: L{Headers}
@ivar _headers: The real header storage object.
"""
def __init__(self, headers):
self._headers = headers
def __getitem__(self, key):
"""
Return the last value for header of C{key}.
"""
if self._headers.hasHeader(key):
return self._headers.getRawHeaders(key)[-1]
raise KeyError(key)
def __setitem__(self, key, value):
"""
Set the given header.
"""
self._headers.setRawHeaders(key, [value])
def __delitem__(self, key):
"""
Delete the given header.
"""
if self._headers.hasHeader(key):
self._headers.removeHeader(key)
else:
raise KeyError(key)
def __iter__(self):
"""
Return an iterator of the lowercase name of each header present.
"""
for k, v in self._headers.getAllRawHeaders():
yield k.lower()
def __len__(self):
"""
Return the number of distinct headers present.
"""
# XXX Too many _
return len(self._headers._rawHeaders)
# Extra methods that MutableMapping doesn't care about but that we do.
def copy(self):
"""
Return a C{dict} mapping each header name to the last corresponding
header value.
"""
return dict(self.items())
def has_key(self, key):
"""
Return C{True} if C{key} is a header in this collection, C{False}
otherwise.
"""
return key in self
@comparable
class Headers(object):
"""
This class stores the HTTP headers as both a parsed representation
and the raw string representation. It converts between the two on
demand.
@cvar _caseMappings: A C{dict} that maps lowercase header names
to their canonicalized representation.
@ivar _rawHeaders: A C{dict} mapping header names as C{bytes} to C{lists} of
header values as C{bytes}.
"""
_caseMappings = {
b'content-md5': b'Content-MD5',
b'dnt': b'DNT',
b'etag': b'ETag',
b'p3p': b'P3P',
b'te': b'TE',
b'www-authenticate': b'WWW-Authenticate',
b'x-xss-protection': b'X-XSS-Protection'}
def __init__(self, rawHeaders=None):
self._rawHeaders = {}
if rawHeaders is not None:
for name, values in rawHeaders.items():
self.setRawHeaders(name, values[:])
def __repr__(self):
"""
Return a string fully describing the headers set on this object.
"""
return '%s(%r)' % (self.__class__.__name__, self._rawHeaders,)
def __cmp__(self, other):
"""
Define L{Headers} instances as being equal to each other if they have
the same raw headers.
"""
if isinstance(other, Headers):
return cmp(
sorted(self._rawHeaders.items()),
sorted(other._rawHeaders.items()))
return NotImplemented
def copy(self):
"""
Return a copy of itself with the same headers set.
"""
return self.__class__(self._rawHeaders)
def hasHeader(self, name):
"""
Check for the existence of a given header.
@type name: C{bytes}
@param name: The name of the HTTP header to check for.
@rtype: C{bool}
@return: C{True} if the header exists, otherwise C{False}.
"""
return name.lower() in self._rawHeaders
def removeHeader(self, name):
"""
Remove the named header from this header object.
@type name: C{bytes}
@param name: The name of the HTTP header to remove.
@return: C{None}
"""
self._rawHeaders.pop(name.lower(), None)
def setRawHeaders(self, name, values):
"""
Sets the raw representation of the given header.
@type name: C{bytes}
@param name: The name of the HTTP header to set the values for.
@type values: C{list}
@param values: A list of strings each one being a header value of
the given name.
@return: C{None}
"""
if not isinstance(values, list):
raise TypeError("Header entry %r should be list but found "
"instance of %r instead" % (name, type(values)))
self._rawHeaders[name.lower()] = values
def addRawHeader(self, name, value):
"""
Add a new raw value for the given header.
@type name: C{bytes}
@param name: The name of the header for which to set the value.
@type value: C{bytes}
@param value: The value to set for the named header.
"""
values = self.getRawHeaders(name)
if values is None:
self.setRawHeaders(name, [value])
else:
values.append(value)
def getRawHeaders(self, name, default=None):
"""
Returns a list of headers matching the given name as the raw string
given.
@type name: C{bytes}
@param name: The name of the HTTP header to get the values of.
@param default: The value to return if no header with the given C{name}
exists.
@rtype: C{list}
@return: A C{list} of values for the given header.
"""
return self._rawHeaders.get(name.lower(), default)
def getAllRawHeaders(self):
"""
Return an iterator of key, value pairs of all headers contained in this
object, as strings. The keys are capitalized in canonical
capitalization.
"""
for k, v in self._rawHeaders.items():
yield self._canonicalNameCaps(k), v
def _canonicalNameCaps(self, name):
"""
Return the canonical name for the given header.
@type name: C{bytes}
@param name: The all-lowercase header name to capitalize in its
canonical form.
@rtype: C{bytes}
@return: The canonical name of the header.
"""
return self._caseMappings.get(name, _dashCapitalize(name))
__all__ = ['Headers']

View file

@ -0,0 +1,760 @@
# -*- test-case-name: twisted.web.test -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Interface definitions for L{twisted.web}.
@var UNKNOWN_LENGTH: An opaque object which may be used as the value of
L{IBodyProducer.length} to indicate that the length of the entity
body is not known in advance.
"""
from zope.interface import Interface, Attribute
from twisted.python.compat import _PY3
from twisted.internet.interfaces import IPushProducer
if not _PY3:
# Re-enable when cred is ported to Python 3. Fix as part of #6176:
from twisted.cred.credentials import IUsernameDigestHash
class IRequest(Interface):
"""
An HTTP request.
@since: 9.0
"""
method = Attribute("A C{str} giving the HTTP method that was used.")
uri = Attribute(
"A C{str} giving the full encoded URI which was requested (including "
"query arguments).")
path = Attribute(
"A C{str} giving the encoded query path of the request URI.")
args = Attribute(
"A mapping of decoded query argument names as C{str} to "
"corresponding query argument values as C{list}s of C{str}. "
"For example, for a URI with C{'foo=bar&foo=baz&quux=spam'} "
"for its query part, C{args} will be C{{'foo': ['bar', 'baz'], "
"'quux': ['spam']}}.")
received_headers = Attribute(
"Backwards-compatibility access to C{requestHeaders}, deprecated in "
"Twisted 13.2.0. Use C{requestHeaders} instead. C{received_headers} "
"behaves mostly like a C{dict} and does not provide access to all "
"header values.")
requestHeaders = Attribute(
"A L{http_headers.Headers} instance giving all received HTTP request "
"headers.")
content = Attribute(
"A file-like object giving the request body. This may be a file on "
"disk, a C{StringIO}, or some other type. The implementation is free "
"to decide on a per-request basis.")
headers = Attribute(
"Backwards-compatibility access to C{responseHeaders}, deprecated in "
"Twisted 13.2.0. Use C{responseHeaders} instead. C{headers} behaves "
"mostly like a C{dict} and does not provide access to all header "
"values nor does it allow multiple values for one header to be set.")
responseHeaders = Attribute(
"A L{http_headers.Headers} instance holding all HTTP response "
"headers to be sent.")
def getHeader(key):
"""
Get an HTTP request header.
@type key: C{str}
@param key: The name of the header to get the value of.
@rtype: C{str} or C{NoneType}
@return: The value of the specified header, or C{None} if that header
was not present in the request.
"""
def getCookie(key):
"""
Get a cookie that was sent from the network.
"""
def getAllHeaders():
"""
Return dictionary mapping the names of all received headers to the last
value received for each.
Since this method does not return all header information,
C{requestHeaders.getAllRawHeaders()} may be preferred.
"""
def getRequestHostname():
"""
Get the hostname that the user passed in to the request.
This will either use the Host: header (if it is available) or the
host we are listening on if the header is unavailable.
@returns: the requested hostname
@rtype: C{str}
"""
def getHost():
"""
Get my originally requesting transport's host.
@return: An L{IAddress<twisted.internet.interfaces.IAddress>}.
"""
def getClientIP():
"""
Return the IP address of the client who submitted this request.
@returns: the client IP address or C{None} if the request was submitted
over a transport where IP addresses do not make sense.
@rtype: L{str} or C{NoneType}
"""
def getClient():
"""
Return the hostname of the IP address of the client who submitted this
request, if possible.
This method is B{deprecated}. See L{getClientIP} instead.
@rtype: C{NoneType} or L{str}
@return: The canonical hostname of the client, as determined by
performing a name lookup on the IP address of the client.
"""
def getUser():
"""
Return the HTTP user sent with this request, if any.
If no user was supplied, return the empty string.
@returns: the HTTP user, if any
@rtype: C{str}
"""
def getPassword():
"""
Return the HTTP password sent with this request, if any.
If no password was supplied, return the empty string.
@returns: the HTTP password, if any
@rtype: C{str}
"""
def isSecure():
"""
Return True if this request is using a secure transport.
Normally this method returns True if this request's HTTPChannel
instance is using a transport that implements ISSLTransport.
This will also return True if setHost() has been called
with ssl=True.
@returns: True if this request is secure
@rtype: C{bool}
"""
def getSession(sessionInterface=None):
"""
Look up the session associated with this request or create a new one if
there is not one.
@return: The L{Session} instance identified by the session cookie in
the request, or the C{sessionInterface} component of that session
if C{sessionInterface} is specified.
"""
def URLPath():
"""
@return: A L{URLPath} instance which identifies the URL for which this
request is.
"""
def prePathURL():
"""
@return: At any time during resource traversal, a L{str} giving an
absolute URL to the most nested resource which has yet been
reached.
"""
def rememberRootURL():
"""
Remember the currently-processed part of the URL for later
recalling.
"""
def getRootURL():
"""
Get a previously-remembered URL.
"""
# Methods for outgoing response
def finish():
"""
Indicate that the response to this request is complete.
"""
def write(data):
"""
Write some data to the body of the response to this request. Response
headers are written the first time this method is called, after which
new response headers may not be added.
"""
def addCookie(k, v, expires=None, domain=None, path=None, max_age=None, comment=None, secure=None):
"""
Set an outgoing HTTP cookie.
In general, you should consider using sessions instead of cookies, see
L{twisted.web.server.Request.getSession} and the
L{twisted.web.server.Session} class for details.
"""
def setResponseCode(code, message=None):
"""
Set the HTTP response code.
"""
def setHeader(k, v):
"""
Set an HTTP response header. Overrides any previously set values for
this header.
@type name: C{str}
@param name: The name of the header for which to set the value.
@type value: C{str}
@param value: The value to set for the named header.
"""
def redirect(url):
"""
Utility function that does a redirect.
The request should have finish() called after this.
"""
def setLastModified(when):
"""
Set the C{Last-Modified} time for the response to this request.
If I am called more than once, I ignore attempts to set Last-Modified
earlier, only replacing the Last-Modified time if it is to a later
value.
If I am a conditional request, I may modify my response code to
L{NOT_MODIFIED<http.NOT_MODIFIED>} if appropriate for the time given.
@param when: The last time the resource being returned was modified, in
seconds since the epoch.
@type when: L{int}, L{long} or L{float}
@return: If I am a C{If-Modified-Since} conditional request and the time
given is not newer than the condition, I return
L{CACHED<http.CACHED>} to indicate that you should write no body.
Otherwise, I return a false value.
"""
def setETag(etag):
"""
Set an C{entity tag} for the outgoing response.
That's "entity tag" as in the HTTP/1.1 I{ETag} header, "used for
comparing two or more entities from the same requested resource."
If I am a conditional request, I may modify my response code to
L{NOT_MODIFIED<http.NOT_MODIFIED>} or
L{PRECONDITION_FAILED<http.PRECONDITION_FAILED>}, if appropriate for the
tag given.
@param etag: The entity tag for the resource being returned.
@type etag: C{str}
@return: If I am a C{If-None-Match} conditional request and the tag
matches one in the request, I return L{CACHED<http.CACHED>} to
indicate that you should write no body. Otherwise, I return a
false value.
"""
def setHost(host, port, ssl=0):
"""
Change the host and port the request thinks it's using.
This method is useful for working with reverse HTTP proxies (e.g. both
Squid and Apache's mod_proxy can do this), when the address the HTTP
client is using is different than the one we're listening on.
For example, Apache may be listening on https://www.example.com, and
then forwarding requests to http://localhost:8080, but we don't want
HTML produced by Twisted to say 'http://localhost:8080', they should
say 'https://www.example.com', so we do::
request.setHost('www.example.com', 443, ssl=1)
"""
class IAccessLogFormatter(Interface):
"""
An object which can represent an HTTP request as a line of text for
inclusion in an access log file.
"""
def __call__(timestamp, request):
"""
Generate a line for the access log.
@param timestamp: The time at which the request was completed in the
standard format for access logs.
@type timestamp: L{unicode}
@param request: The request object about which to log.
@type request: L{twisted.web.server.Request}
@return: One line describing the request without a trailing newline.
@rtype: L{unicode}
"""
class ICredentialFactory(Interface):
"""
A credential factory defines a way to generate a particular kind of
authentication challenge and a way to interpret the responses to these
challenges. It creates
L{ICredentials<twisted.cred.credentials.ICredentials>} providers from
responses. These objects will be used with L{twisted.cred} to authenticate
an authorize requests.
"""
scheme = Attribute(
"A C{str} giving the name of the authentication scheme with which "
"this factory is associated. For example, C{'basic'} or C{'digest'}.")
def getChallenge(request):
"""
Generate a new challenge to be sent to a client.
@type peer: L{twisted.web.http.Request}
@param peer: The request the response to which this challenge will be
included.
@rtype: C{dict}
@return: A mapping from C{str} challenge fields to associated C{str}
values.
"""
def decode(response, request):
"""
Create a credentials object from the given response.
@type response: C{str}
@param response: scheme specific response string
@type request: L{twisted.web.http.Request}
@param request: The request being processed (from which the response
was taken).
@raise twisted.cred.error.LoginFailed: If the response is invalid.
@rtype: L{twisted.cred.credentials.ICredentials} provider
@return: The credentials represented by the given response.
"""
class IBodyProducer(IPushProducer):
"""
Objects which provide L{IBodyProducer} write bytes to an object which
provides L{IConsumer<twisted.internet.interfaces.IConsumer>} by calling its
C{write} method repeatedly.
L{IBodyProducer} providers may start producing as soon as they have an
L{IConsumer<twisted.internet.interfaces.IConsumer>} provider. That is, they
should not wait for a C{resumeProducing} call to begin writing data.
L{IConsumer.unregisterProducer<twisted.internet.interfaces.IConsumer.unregisterProducer>}
must not be called. Instead, the
L{Deferred<twisted.internet.defer.Deferred>} returned from C{startProducing}
must be fired when all bytes have been written.
L{IConsumer.write<twisted.internet.interfaces.IConsumer.write>} may
synchronously invoke any of C{pauseProducing}, C{resumeProducing}, or
C{stopProducing}. These methods must be implemented with this in mind.
@since: 9.0
"""
# Despite the restrictions above and the additional requirements of
# stopProducing documented below, this interface still needs to be an
# IPushProducer subclass. Providers of it will be passed to IConsumer
# providers which only know about IPushProducer and IPullProducer, not
# about this interface. This interface needs to remain close enough to one
# of those interfaces for consumers to work with it.
length = Attribute(
"""
C{length} is a C{int} indicating how many bytes in total this
L{IBodyProducer} will write to the consumer or L{UNKNOWN_LENGTH}
if this is not known in advance.
""")
def startProducing(consumer):
"""
Start producing to the given
L{IConsumer<twisted.internet.interfaces.IConsumer>} provider.
@return: A L{Deferred<twisted.internet.defer.Deferred>} which fires with
C{None} when all bytes have been produced or with a
L{Failure<twisted.python.failure.Failure>} if there is any problem
before all bytes have been produced.
"""
def stopProducing():
"""
In addition to the standard behavior of
L{IProducer.stopProducing<twisted.internet.interfaces.IProducer.stopProducing>}
(stop producing data), make sure the
L{Deferred<twisted.internet.defer.Deferred>} returned by
C{startProducing} is never fired.
"""
class IRenderable(Interface):
"""
An L{IRenderable} is an object that may be rendered by the
L{twisted.web.template} templating system.
"""
def lookupRenderMethod(name):
"""
Look up and return the render method associated with the given name.
@type name: C{str}
@param name: The value of a render directive encountered in the
document returned by a call to L{IRenderable.render}.
@return: A two-argument callable which will be invoked with the request
being responded to and the tag object on which the render directive
was encountered.
"""
def render(request):
"""
Get the document for this L{IRenderable}.
@type request: L{IRequest} provider or C{NoneType}
@param request: The request in response to which this method is being
invoked.
@return: An object which can be flattened.
"""
class ITemplateLoader(Interface):
"""
A loader for templates; something usable as a value for
L{twisted.web.template.Element}'s C{loader} attribute.
"""
def load():
"""
Load a template suitable for rendering.
@return: a C{list} of C{list}s, C{unicode} objects, C{Element}s and
other L{IRenderable} providers.
"""
class IResponse(Interface):
"""
An object representing an HTTP response received from an HTTP server.
@since: 11.1
"""
version = Attribute(
"A three-tuple describing the protocol and protocol version "
"of the response. The first element is of type C{str}, the second "
"and third are of type C{int}. For example, C{('HTTP', 1, 1)}.")
code = Attribute("The HTTP status code of this response, as a C{int}.")
phrase = Attribute(
"The HTTP reason phrase of this response, as a C{str}.")
headers = Attribute("The HTTP response L{Headers} of this response.")
length = Attribute(
"The C{int} number of bytes expected to be in the body of this "
"response or L{UNKNOWN_LENGTH} if the server did not indicate how "
"many bytes to expect. For I{HEAD} responses, this will be 0; if "
"the response includes a I{Content-Length} header, it will be "
"available in C{headers}.")
request = Attribute(
"The L{IClientRequest} that resulted in this response.")
previousResponse = Attribute(
"The previous L{IResponse} from a redirect, or C{None} if there was no "
"previous response. This can be used to walk the response or request "
"history for redirections.")
def deliverBody(protocol):
"""
Register an L{IProtocol<twisted.internet.interfaces.IProtocol>} provider
to receive the response body.
The protocol will be connected to a transport which provides
L{IPushProducer}. The protocol's C{connectionLost} method will be
called with:
- ResponseDone, which indicates that all bytes from the response
have been successfully delivered.
- PotentialDataLoss, which indicates that it cannot be determined
if the entire response body has been delivered. This only occurs
when making requests to HTTP servers which do not set
I{Content-Length} or a I{Transfer-Encoding} in the response.
- ResponseFailed, which indicates that some bytes from the response
were lost. The C{reasons} attribute of the exception may provide
more specific indications as to why.
"""
def setPreviousResponse(response):
"""
Set the reference to the previous L{IResponse}.
The value of the previous response can be read via
L{IResponse.previousResponse}.
"""
class _IRequestEncoder(Interface):
"""
An object encoding data passed to L{IRequest.write}, for example for
compression purpose.
@since: 12.3
"""
def encode(data):
"""
Encode the data given and return the result.
@param data: The content to encode.
@type data: C{str}
@return: The encoded data.
@rtype: C{str}
"""
def finish():
"""
Callback called when the request is closing.
@return: If necessary, the pending data accumulated from previous
C{encode} calls.
@rtype: C{str}
"""
class _IRequestEncoderFactory(Interface):
"""
A factory for returing L{_IRequestEncoder} instances.
@since: 12.3
"""
def encoderForRequest(request):
"""
If applicable, returns a L{_IRequestEncoder} instance which will encode
the request.
"""
class IClientRequest(Interface):
"""
An object representing an HTTP request to make to an HTTP server.
@since: 13.1
"""
method = Attribute(
"The HTTP method for this request, as L{bytes}. For example: "
"C{b'GET'}, C{b'HEAD'}, C{b'POST'}, etc.")
absoluteURI = Attribute(
"The absolute URI of the requested resource, as L{bytes}; or C{None} "
"if the absolute URI cannot be determined.")
headers = Attribute(
"Headers to be sent to the server, as "
"a L{twisted.web.http_headers.Headers} instance.")
class IAgent(Interface):
"""
An agent makes HTTP requests.
The way in which requests are issued is left up to each implementation.
Some may issue them directly to the server indicated by the net location
portion of the request URL. Others may use a proxy specified by system
configuration.
Processing of responses is also left very widely specified. An
implementation may perform no special handling of responses, or it may
implement redirect following or content negotiation, it may implement a
cookie store or automatically respond to authentication challenges. It may
implement many other unforeseen behaviors as well.
It is also intended that L{IAgent} implementations be composable. An
implementation which provides cookie handling features should re-use an
implementation that provides connection pooling and this combination could
be used by an implementation which adds content negotiation functionality.
Some implementations will be completely self-contained, such as those which
actually perform the network operations to send and receive requests, but
most or all other implementations should implement a small number of new
features (perhaps one new feature) and delegate the rest of the
request/response machinery to another implementation.
This allows for great flexibility in the behavior an L{IAgent} will
provide. For example, an L{IAgent} with web browser-like behavior could be
obtained by combining a number of (hypothetical) implementations::
baseAgent = Agent(reactor)
redirect = BrowserLikeRedirectAgent(baseAgent, limit=10)
authenticate = AuthenticateAgent(
redirect, [diskStore.credentials, GtkAuthInterface()])
cookie = CookieAgent(authenticate, diskStore.cookie)
decode = ContentDecoderAgent(cookie, [(b"gzip", GzipDecoder())])
cache = CacheAgent(decode, diskStore.cache)
doSomeRequests(cache)
"""
def request(method, uri, headers=None, bodyProducer=None):
"""
Request the resource at the given location.
@param method: The request method to use, such as C{"GET"}, C{"HEAD"},
C{"PUT"}, C{"POST"}, etc.
@type method: L{bytes}
@param uri: The location of the resource to request. This should be an
absolute URI but some implementations may support relative URIs
(with absolute or relative paths). I{HTTP} and I{HTTPS} are the
schemes most likely to be supported but others may be as well.
@type uri: L{bytes}
@param headers: The headers to send with the request (or C{None} to
send no extra headers). An implementation may add its own headers
to this (for example for client identification or content
negotiation).
@type headers: L{Headers} or L{NoneType}
@param bodyProducer: An object which can generate bytes to make up the
body of this request (for example, the properly encoded contents of
a file for a file upload). Or, C{None} if the request is to have
no body.
@type bodyProducer: L{IBodyProducer} provider
@return: A L{Deferred} that fires with an L{IResponse} provider when
the header of the response has been received (regardless of the
response status code) or with a L{Failure} if there is any problem
which prevents that response from being received (including
problems that prevent the request from being sent).
@rtype: L{Deferred}
"""
class IPolicyForHTTPS(Interface):
"""
An L{IPolicyForHTTPS} provides a policy for verifying the certificates of
HTTPS connections, in the form of a L{client connection creator
<twisted.internet.interfaces.IOpenSSLClientConnectionCreator>} per network
location.
@since: 14.0
"""
def creatorForNetloc(hostname, port):
"""
Create a L{client connection creator
<twisted.internet.interfaces.IOpenSSLClientConnectionCreator>}
appropriate for the given URL "netloc"; i.e. hostname and port number
pair.
@param hostname: The name of the requested remote host.
@type hostname: L{unicode}
@param port: The number of the requested remote port.
@type port: L{int}
@return: A client connection creator expressing the security
requirements for the given remote host.
@rtype: L{client connection creator
<twisted.internet.interfaces.IOpenSSLClientConnectionCreator>}
"""
UNKNOWN_LENGTH = u"twisted.web.iweb.UNKNOWN_LENGTH"
__all__ = [
"IUsernameDigestHash", "ICredentialFactory", "IRequest",
"IBodyProducer", "IRenderable", "IResponse", "_IRequestEncoder",
"_IRequestEncoderFactory", "IClientRequest",
"UNKNOWN_LENGTH"]

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,303 @@
# -*- test-case-name: twisted.web.test.test_proxy -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Simplistic HTTP proxy support.
This comes in two main variants - the Proxy and the ReverseProxy.
When a Proxy is in use, a browser trying to connect to a server (say,
www.yahoo.com) will be intercepted by the Proxy, and the proxy will covertly
connect to the server, and return the result.
When a ReverseProxy is in use, the client connects directly to the ReverseProxy
(say, www.yahoo.com) which farms off the request to one of a pool of servers,
and returns the result.
Normally, a Proxy is used on the client end of an Internet connection, while a
ReverseProxy is used on the server end.
"""
import urlparse
from urllib import quote as urlquote
from twisted.internet import reactor
from twisted.internet.protocol import ClientFactory
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET
from twisted.web.http import HTTPClient, Request, HTTPChannel
class ProxyClient(HTTPClient):
"""
Used by ProxyClientFactory to implement a simple web proxy.
@ivar _finished: A flag which indicates whether or not the original request
has been finished yet.
"""
_finished = False
def __init__(self, command, rest, version, headers, data, father):
self.father = father
self.command = command
self.rest = rest
if "proxy-connection" in headers:
del headers["proxy-connection"]
headers["connection"] = "close"
headers.pop('keep-alive', None)
self.headers = headers
self.data = data
def connectionMade(self):
self.sendCommand(self.command, self.rest)
for header, value in self.headers.items():
self.sendHeader(header, value)
self.endHeaders()
self.transport.write(self.data)
def handleStatus(self, version, code, message):
self.father.setResponseCode(int(code), message)
def handleHeader(self, key, value):
# t.web.server.Request sets default values for these headers in its
# 'process' method. When these headers are received from the remote
# server, they ought to override the defaults, rather than append to
# them.
if key.lower() in ['server', 'date', 'content-type']:
self.father.responseHeaders.setRawHeaders(key, [value])
else:
self.father.responseHeaders.addRawHeader(key, value)
def handleResponsePart(self, buffer):
self.father.write(buffer)
def handleResponseEnd(self):
"""
Finish the original request, indicating that the response has been
completely written to it, and disconnect the outgoing transport.
"""
if not self._finished:
self._finished = True
self.father.finish()
self.transport.loseConnection()
class ProxyClientFactory(ClientFactory):
"""
Used by ProxyRequest to implement a simple web proxy.
"""
protocol = ProxyClient
def __init__(self, command, rest, version, headers, data, father):
self.father = father
self.command = command
self.rest = rest
self.headers = headers
self.data = data
self.version = version
def buildProtocol(self, addr):
return self.protocol(self.command, self.rest, self.version,
self.headers, self.data, self.father)
def clientConnectionFailed(self, connector, reason):
"""
Report a connection failure in a response to the incoming request as
an error.
"""
self.father.setResponseCode(501, "Gateway error")
self.father.responseHeaders.addRawHeader("Content-Type", "text/html")
self.father.write("<H1>Could not connect</H1>")
self.father.finish()
class ProxyRequest(Request):
"""
Used by Proxy to implement a simple web proxy.
@ivar reactor: the reactor used to create connections.
@type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
"""
protocols = {'http': ProxyClientFactory}
ports = {'http': 80}
def __init__(self, channel, queued, reactor=reactor):
Request.__init__(self, channel, queued)
self.reactor = reactor
def process(self):
parsed = urlparse.urlparse(self.uri)
protocol = parsed[0]
host = parsed[1]
port = self.ports[protocol]
if ':' in host:
host, port = host.split(':')
port = int(port)
rest = urlparse.urlunparse(('', '') + parsed[2:])
if not rest:
rest = rest + '/'
class_ = self.protocols[protocol]
headers = self.getAllHeaders().copy()
if 'host' not in headers:
headers['host'] = host
self.content.seek(0, 0)
s = self.content.read()
clientFactory = class_(self.method, rest, self.clientproto, headers,
s, self)
self.reactor.connectTCP(host, port, clientFactory)
class Proxy(HTTPChannel):
"""
This class implements a simple web proxy.
Since it inherits from L{twisted.web.http.HTTPChannel}, to use it you
should do something like this::
from twisted.web import http
f = http.HTTPFactory()
f.protocol = Proxy
Make the HTTPFactory a listener on a port as per usual, and you have
a fully-functioning web proxy!
"""
requestFactory = ProxyRequest
class ReverseProxyRequest(Request):
"""
Used by ReverseProxy to implement a simple reverse proxy.
@ivar proxyClientFactoryClass: a proxy client factory class, used to create
new connections.
@type proxyClientFactoryClass: L{ClientFactory}
@ivar reactor: the reactor used to create connections.
@type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
"""
proxyClientFactoryClass = ProxyClientFactory
def __init__(self, channel, queued, reactor=reactor):
Request.__init__(self, channel, queued)
self.reactor = reactor
def process(self):
"""
Handle this request by connecting to the proxied server and forwarding
it there, then forwarding the response back as the response to this
request.
"""
self.requestHeaders.setRawHeaders(b"host", [self.factory.host])
clientFactory = self.proxyClientFactoryClass(
self.method, self.uri, self.clientproto, self.getAllHeaders(),
self.content.read(), self)
self.reactor.connectTCP(self.factory.host, self.factory.port,
clientFactory)
class ReverseProxy(HTTPChannel):
"""
Implements a simple reverse proxy.
For details of usage, see the file examples/reverse-proxy.py.
"""
requestFactory = ReverseProxyRequest
class ReverseProxyResource(Resource):
"""
Resource that renders the results gotten from another server
Put this resource in the tree to cause everything below it to be relayed
to a different server.
@ivar proxyClientFactoryClass: a proxy client factory class, used to create
new connections.
@type proxyClientFactoryClass: L{ClientFactory}
@ivar reactor: the reactor used to create connections.
@type reactor: object providing L{twisted.internet.interfaces.IReactorTCP}
"""
proxyClientFactoryClass = ProxyClientFactory
def __init__(self, host, port, path, reactor=reactor):
"""
@param host: the host of the web server to proxy.
@type host: C{str}
@param port: the port of the web server to proxy.
@type port: C{port}
@param path: the base path to fetch data from. Note that you shouldn't
put any trailing slashes in it, it will be added automatically in
request. For example, if you put B{/foo}, a request on B{/bar} will
be proxied to B{/foo/bar}. Any required encoding of special
characters (such as " " or "/") should have been done already.
@type path: C{str}
"""
Resource.__init__(self)
self.host = host
self.port = port
self.path = path
self.reactor = reactor
def getChild(self, path, request):
"""
Create and return a proxy resource with the same proxy configuration
as this one, except that its path also contains the segment given by
C{path} at the end.
"""
return ReverseProxyResource(
self.host, self.port, self.path + '/' + urlquote(path, safe=""),
self.reactor)
def render(self, request):
"""
Render a request by forwarding it to the proxied server.
"""
# RFC 2616 tells us that we can omit the port if it's the default port,
# but we have to provide it otherwise
if self.port == 80:
host = self.host
else:
host = "%s:%d" % (self.host, self.port)
request.requestHeaders.setRawHeaders(b"host", [host])
request.content.seek(0, 0)
qs = urlparse.urlparse(request.uri)[4]
if qs:
rest = self.path + '?' + qs
else:
rest = self.path
clientFactory = self.proxyClientFactoryClass(
request.method, rest, request.clientproto,
request.getAllHeaders(), request.content.read(), request)
self.reactor.connectTCP(self.host, self.port, clientFactory)
return NOT_DONE_YET

View file

@ -0,0 +1,405 @@
# -*- test-case-name: twisted.web.test.test_web -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Implementation of the lowest-level Resource class.
"""
from __future__ import division, absolute_import
__all__ = [
'IResource', 'getChildForRequest',
'Resource', 'ErrorPage', 'NoResource', 'ForbiddenResource',
'EncodingResourceWrapper']
import warnings
from zope.interface import Attribute, Interface, implementer
from twisted.python.compat import nativeString, unicode
from twisted.python.reflect import prefixedMethodNames
from twisted.python.components import proxyForInterface
from twisted.web._responses import FORBIDDEN, NOT_FOUND
from twisted.web.error import UnsupportedMethod
class IResource(Interface):
"""
A web resource.
"""
isLeaf = Attribute(
"""
Signal if this IResource implementor is a "leaf node" or not. If True,
getChildWithDefault will not be called on this Resource.
""")
def getChildWithDefault(name, request):
"""
Return a child with the given name for the given request.
This is the external interface used by the Resource publishing
machinery. If implementing IResource without subclassing
Resource, it must be provided. However, if subclassing Resource,
getChild overridden instead.
@param name: A single path component from a requested URL. For example,
a request for I{http://example.com/foo/bar} will result in calls to
this method with C{b"foo"} and C{b"bar"} as values for this
argument.
@type name: C{bytes}
@param request: A representation of all of the information about the
request that is being made for this child.
@type request: L{twisted.web.server.Request}
"""
def putChild(path, child):
"""
Put a child IResource implementor at the given path.
@param path: A single path component, to be interpreted relative to the
path this resource is found at, at which to put the given child.
For example, if resource A can be found at I{http://example.com/foo}
then a call like C{A.putChild(b"bar", B)} will make resource B
available at I{http://example.com/foo/bar}.
@type path: C{bytes}
"""
def render(request):
"""
Render a request. This is called on the leaf resource for a request.
@return: Either C{server.NOT_DONE_YET} to indicate an asynchronous or a
C{bytes} instance to write as the response to the request. If
C{NOT_DONE_YET} is returned, at some point later (for example, in a
Deferred callback) call C{request.write(b"<html>")} to write data to
the request, and C{request.finish()} to send the data to the
browser.
@raise twisted.web.error.UnsupportedMethod: If the HTTP verb
requested is not supported by this resource.
"""
def getChildForRequest(resource, request):
"""
Traverse resource tree to find who will handle the request.
"""
while request.postpath and not resource.isLeaf:
pathElement = request.postpath.pop(0)
request.prepath.append(pathElement)
resource = resource.getChildWithDefault(pathElement, request)
return resource
@implementer(IResource)
class Resource:
"""
Define a web-accessible resource.
This serves 2 main purposes; one is to provide a standard representation
for what HTTP specification calls an 'entity', and the other is to provide
an abstract directory structure for URL retrieval.
"""
entityType = IResource
server = None
def __init__(self):
"""
Initialize.
"""
self.children = {}
isLeaf = 0
### Abstract Collection Interface
def listStaticNames(self):
return list(self.children.keys())
def listStaticEntities(self):
return list(self.children.items())
def listNames(self):
return list(self.listStaticNames()) + self.listDynamicNames()
def listEntities(self):
return list(self.listStaticEntities()) + self.listDynamicEntities()
def listDynamicNames(self):
return []
def listDynamicEntities(self, request=None):
return []
def getStaticEntity(self, name):
return self.children.get(name)
def getDynamicEntity(self, name, request):
if not self.children.has_key(name):
return self.getChild(name, request)
else:
return None
def delEntity(self, name):
del self.children[name]
def reallyPutEntity(self, name, entity):
self.children[name] = entity
# Concrete HTTP interface
def getChild(self, path, request):
"""
Retrieve a 'child' resource from me.
Implement this to create dynamic resource generation -- resources which
are always available may be registered with self.putChild().
This will not be called if the class-level variable 'isLeaf' is set in
your subclass; instead, the 'postpath' attribute of the request will be
left as a list of the remaining path elements.
For example, the URL /foo/bar/baz will normally be::
| site.resource.getChild('foo').getChild('bar').getChild('baz').
However, if the resource returned by 'bar' has isLeaf set to true, then
the getChild call will never be made on it.
Parameters and return value have the same meaning and requirements as
those defined by L{IResource.getChildWithDefault}.
"""
return NoResource("No such child resource.")
def getChildWithDefault(self, path, request):
"""
Retrieve a static or dynamically generated child resource from me.
First checks if a resource was added manually by putChild, and then
call getChild to check for dynamic resources. Only override if you want
to affect behaviour of all child lookups, rather than just dynamic
ones.
This will check to see if I have a pre-registered child resource of the
given name, and call getChild if I do not.
@see: L{IResource.getChildWithDefault}
"""
if path in self.children:
return self.children[path]
return self.getChild(path, request)
def getChildForRequest(self, request):
warnings.warn("Please use module level getChildForRequest.", DeprecationWarning, 2)
return getChildForRequest(self, request)
def putChild(self, path, child):
"""
Register a static child.
You almost certainly don't want '/' in your path. If you
intended to have the root of a folder, e.g. /foo/, you want
path to be ''.
@see: L{IResource.putChild}
"""
self.children[path] = child
child.server = self.server
def render(self, request):
"""
Render a given resource. See L{IResource}'s render method.
I delegate to methods of self with the form 'render_METHOD'
where METHOD is the HTTP that was used to make the
request. Examples: render_GET, render_HEAD, render_POST, and
so on. Generally you should implement those methods instead of
overriding this one.
render_METHOD methods are expected to return a byte string which will be
the rendered page, unless the return value is C{server.NOT_DONE_YET}, in
which case it is this class's responsibility to write the results using
C{request.write(data)} and then call C{request.finish()}.
Old code that overrides render() directly is likewise expected
to return a byte string or NOT_DONE_YET.
@see: L{IResource.render}
"""
m = getattr(self, 'render_' + nativeString(request.method), None)
if not m:
try:
allowedMethods = self.allowedMethods
except AttributeError:
allowedMethods = _computeAllowedMethods(self)
raise UnsupportedMethod(allowedMethods)
return m(request)
def render_HEAD(self, request):
"""
Default handling of HEAD method.
I just return self.render_GET(request). When method is HEAD,
the framework will handle this correctly.
"""
return self.render_GET(request)
def _computeAllowedMethods(resource):
"""
Compute the allowed methods on a C{Resource} based on defined render_FOO
methods. Used when raising C{UnsupportedMethod} but C{Resource} does
not define C{allowedMethods} attribute.
"""
allowedMethods = []
for name in prefixedMethodNames(resource.__class__, "render_"):
# Potentially there should be an API for encode('ascii') in this
# situation - an API for taking a Python native string (bytes on Python
# 2, text on Python 3) and returning a socket-compatible string type.
allowedMethods.append(name.encode('ascii'))
return allowedMethods
class ErrorPage(Resource):
"""
L{ErrorPage} is a resource which responds with a particular
(parameterized) status and a body consisting of HTML containing some
descriptive text. This is useful for rendering simple error pages.
@ivar template: A native string which will have a dictionary interpolated
into it to generate the response body. The dictionary has the following
keys:
- C{"code"}: The status code passed to L{ErrorPage.__init__}.
- C{"brief"}: The brief description passed to L{ErrorPage.__init__}.
- C{"detail"}: The detailed description passed to
L{ErrorPage.__init__}.
@ivar code: An integer status code which will be used for the response.
@ivar brief: A short string which will be included in the response body.
@type brief: C{str}
@ivar detail: A longer string which will be included in the response body.
@ivar detail: C{str}
"""
template = """
<html>
<head><title>%(code)s - %(brief)s</title></head>
<body>
<h1>%(brief)s</h1>
<p>%(detail)s</p>
</body>
</html>
"""
def __init__(self, status, brief, detail):
Resource.__init__(self)
self.code = status
self.brief = brief
self.detail = detail
def render(self, request):
request.setResponseCode(self.code)
request.setHeader(b"content-type", b"text/html; charset=utf-8")
interpolated = self.template % dict(
code=self.code, brief=self.brief, detail=self.detail)
if isinstance(interpolated, unicode):
return interpolated.encode('utf-8')
return interpolated
def getChild(self, chnam, request):
return self
class NoResource(ErrorPage):
"""
L{NoResource} is a specialization of L{ErrorPage} which returns the HTTP
response code I{NOT FOUND}.
"""
def __init__(self, message="Sorry. No luck finding that resource."):
ErrorPage.__init__(self, NOT_FOUND, "No Such Resource", message)
class ForbiddenResource(ErrorPage):
"""
L{ForbiddenResource} is a specialization of L{ErrorPage} which returns the
I{FORBIDDEN} HTTP response code.
"""
def __init__(self, message="Sorry, resource is forbidden."):
ErrorPage.__init__(self, FORBIDDEN, "Forbidden Resource", message)
class _IEncodingResource(Interface):
"""
A resource which knows about L{_IRequestEncoderFactory}.
@since: 12.3
"""
def getEncoder(request):
"""
Parse the request and return an encoder if applicable, using
L{_IRequestEncoderFactory.encoderForRequest}.
@return: A L{_IRequestEncoder}, or C{None}.
"""
@implementer(_IEncodingResource)
class EncodingResourceWrapper(proxyForInterface(IResource)):
"""
Wrap a L{IResource}, potentially applying an encoding to the response body
generated.
Note that the returned children resources won't be wrapped, so you have to
explicitly wrap them if you want the encoding to be applied.
@ivar encoders: A list of
L{_IRequestEncoderFactory<twisted.web.iweb._IRequestEncoderFactory>}
returning L{_IRequestEncoder<twisted.web.iweb._IRequestEncoder>} that
may transform the data passed to C{Request.write}. The list must be
sorted in order of priority: the first encoder factory handling the
request will prevent the others from doing the same.
@type encoders: C{list}.
@since: 12.3
"""
def __init__(self, original, encoders):
super(EncodingResourceWrapper, self).__init__(original)
self._encoders = encoders
def getEncoder(self, request):
"""
Browser the list of encoders looking for one applicable encoder.
"""
for encoderFactory in self._encoders:
encoder = encoderFactory.encoderForRequest(request)
if encoder is not None:
return encoder

View file

@ -0,0 +1,52 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
#
from twisted.web import resource
class RewriterResource(resource.Resource):
def __init__(self, orig, *rewriteRules):
resource.Resource.__init__(self)
self.resource = orig
self.rewriteRules = list(rewriteRules)
def _rewrite(self, request):
for rewriteRule in self.rewriteRules:
rewriteRule(request)
def getChild(self, path, request):
request.postpath.insert(0, path)
request.prepath.pop()
self._rewrite(request)
path = request.postpath.pop(0)
request.prepath.append(path)
return self.resource.getChildWithDefault(path, request)
def render(self, request):
self._rewrite(request)
return self.resource.render(request)
def tildeToUsers(request):
if request.postpath and request.postpath[0][:1]=='~':
request.postpath[:1] = ['users', request.postpath[0][1:]]
request.path = '/'+'/'.join(request.prepath+request.postpath)
def alias(aliasPath, sourcePath):
"""
I am not a very good aliaser. But I'm the best I can be. If I'm
aliasing to a Resource that generates links, and it uses any parts
of request.prepath to do so, the links will not be relative to the
aliased path, but rather to the aliased-to path. That I can't
alias static.File directory listings that nicely. However, I can
still be useful, as many resources will play nice.
"""
sourcePath = sourcePath.split('/')
aliasPath = aliasPath.split('/')
def rewriter(request):
if request.postpath[:len(aliasPath)] == aliasPath:
after = request.postpath[len(aliasPath):]
request.postpath = sourcePath + after
request.path = '/'+'/'.join(request.prepath+request.postpath)
return rewriter

View file

@ -0,0 +1,170 @@
# -*- test-case-name: twisted.web.test.test_script -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
I contain PythonScript, which is a very simple python script resource.
"""
import os, traceback
try:
import cStringIO as StringIO
except ImportError:
import StringIO
from twisted import copyright
from twisted.python.compat import execfile
from twisted.web import http, server, static, resource, html
rpyNoResource = """<p>You forgot to assign to the variable "resource" in your script. For example:</p>
<pre>
# MyCoolWebApp.rpy
import mygreatresource
resource = mygreatresource.MyGreatResource()
</pre>
"""
class AlreadyCached(Exception):
"""This exception is raised when a path has already been cached.
"""
class CacheScanner:
def __init__(self, path, registry):
self.path = path
self.registry = registry
self.doCache = 0
def cache(self):
c = self.registry.getCachedPath(self.path)
if c is not None:
raise AlreadyCached(c)
self.recache()
def recache(self):
self.doCache = 1
noRsrc = resource.ErrorPage(500, "Whoops! Internal Error", rpyNoResource)
def ResourceScript(path, registry):
"""
I am a normal py file which must define a 'resource' global, which should
be an instance of (a subclass of) web.resource.Resource; it will be
renderred.
"""
cs = CacheScanner(path, registry)
glob = {'__file__': path,
'resource': noRsrc,
'registry': registry,
'cache': cs.cache,
'recache': cs.recache}
try:
execfile(path, glob, glob)
except AlreadyCached, ac:
return ac.args[0]
rsrc = glob['resource']
if cs.doCache and rsrc is not noRsrc:
registry.cachePath(path, rsrc)
return rsrc
def ResourceTemplate(path, registry):
from quixote import ptl_compile
glob = {'__file__': path,
'resource': resource.ErrorPage(500, "Whoops! Internal Error",
rpyNoResource),
'registry': registry}
e = ptl_compile.compile_template(open(path), path)
exec e in glob
return glob['resource']
class ResourceScriptWrapper(resource.Resource):
def __init__(self, path, registry=None):
resource.Resource.__init__(self)
self.path = path
self.registry = registry or static.Registry()
def render(self, request):
res = ResourceScript(self.path, self.registry)
return res.render(request)
def getChildWithDefault(self, path, request):
res = ResourceScript(self.path, self.registry)
return res.getChildWithDefault(path, request)
class ResourceScriptDirectory(resource.Resource):
"""
L{ResourceScriptDirectory} is a resource which serves scripts from a
filesystem directory. File children of a L{ResourceScriptDirectory} will
be served using L{ResourceScript}. Directory children will be served using
another L{ResourceScriptDirectory}.
@ivar path: A C{str} giving the filesystem path in which children will be
looked up.
@ivar registry: A L{static.Registry} instance which will be used to decide
how to interpret scripts found as children of this resource.
"""
def __init__(self, pathname, registry=None):
resource.Resource.__init__(self)
self.path = pathname
self.registry = registry or static.Registry()
def getChild(self, path, request):
fn = os.path.join(self.path, path)
if os.path.isdir(fn):
return ResourceScriptDirectory(fn, self.registry)
if os.path.exists(fn):
return ResourceScript(fn, self.registry)
return resource.NoResource()
def render(self, request):
return resource.NoResource().render(request)
class PythonScript(resource.Resource):
"""I am an extremely simple dynamic resource; an embedded python script.
This will execute a file (usually of the extension '.epy') as Python code,
internal to the webserver.
"""
isLeaf = 1
def __init__(self, filename, registry):
"""Initialize me with a script name.
"""
self.filename = filename
self.registry = registry
def render(self, request):
"""Render me to a web client.
Load my file, execute it in a special namespace (with 'request' and
'__file__' global vars) and finish the request. Output to the web-page
will NOT be handled with print - standard output goes to the log - but
with request.write.
"""
request.setHeader("x-powered-by","Twisted/%s" % copyright.version)
namespace = {'request': request,
'__file__': self.filename,
'registry': self.registry}
try:
execfile(self.filename, namespace, namespace)
except IOError, e:
if e.errno == 2: #file not found
request.setResponseCode(http.NOT_FOUND)
request.write(resource.NoResource("File not found.").render(request))
except:
io = StringIO.StringIO()
traceback.print_exc(file=io)
request.write(html.PRE(io.getvalue()))
request.finish()
return server.NOT_DONE_YET

View file

@ -0,0 +1,706 @@
# -*- test-case-name: twisted.web.test.test_web -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
This is a web-server which integrates with the twisted.internet
infrastructure.
"""
from __future__ import division, absolute_import
import copy
import os
try:
from urllib import quote
except ImportError:
from urllib.parse import quote as _quote
def quote(string, *args, **kwargs):
return _quote(string.decode('charmap'), *args, **kwargs).encode('charmap')
import zlib
from zope.interface import implementer
from twisted.python.compat import _PY3, networkString, nativeString, intToBytes
if _PY3:
class Copyable:
"""
Fake mixin, until twisted.spread is ported.
"""
else:
from twisted.spread.pb import Copyable, ViewPoint
from twisted.internet import address
from twisted.web import iweb, http, html
from twisted.web.http import unquote
from twisted.python import log, reflect, failure, components
from twisted import copyright
# Re-enable as part of #6178 when twisted.web.util is ported to Python 3:
if not _PY3:
from twisted.web import util as webutil
from twisted.web import resource
from twisted.web.error import UnsupportedMethod
from twisted.python.versions import Version
from twisted.python.deprecate import deprecatedModuleAttribute
if _PY3:
# cgi.escape is deprecated in Python 3.
from html import escape
else:
from cgi import escape
NOT_DONE_YET = 1
__all__ = [
'supportedMethods',
'Request',
'Session',
'Site',
'version',
'NOT_DONE_YET',
'GzipEncoderFactory'
]
# backwards compatability
deprecatedModuleAttribute(
Version("Twisted", 12, 1, 0),
"Please use twisted.web.http.datetimeToString instead",
"twisted.web.server",
"date_time_string")
deprecatedModuleAttribute(
Version("Twisted", 12, 1, 0),
"Please use twisted.web.http.stringToDatetime instead",
"twisted.web.server",
"string_date_time")
date_time_string = http.datetimeToString
string_date_time = http.stringToDatetime
# Support for other methods may be implemented on a per-resource basis.
supportedMethods = ('GET', 'HEAD', 'POST')
def _addressToTuple(addr):
if isinstance(addr, address.IPv4Address):
return ('INET', addr.host, addr.port)
elif isinstance(addr, address.UNIXAddress):
return ('UNIX', addr.name)
else:
return tuple(addr)
@implementer(iweb.IRequest)
class Request(Copyable, http.Request, components.Componentized):
"""
An HTTP request.
@ivar defaultContentType: A C{bytes} giving the default I{Content-Type}
value to send in responses if no other value is set. C{None} disables
the default.
"""
defaultContentType = b"text/html"
site = None
appRootURL = None
__pychecker__ = 'unusednames=issuer'
_inFakeHead = False
_encoder = None
def __init__(self, *args, **kw):
http.Request.__init__(self, *args, **kw)
components.Componentized.__init__(self)
def getStateToCopyFor(self, issuer):
x = self.__dict__.copy()
del x['transport']
# XXX refactor this attribute out; it's from protocol
# del x['server']
del x['channel']
del x['content']
del x['site']
self.content.seek(0, 0)
x['content_data'] = self.content.read()
x['remote'] = ViewPoint(issuer, self)
# Address objects aren't jellyable
x['host'] = _addressToTuple(x['host'])
x['client'] = _addressToTuple(x['client'])
# Header objects also aren't jellyable.
x['requestHeaders'] = list(x['requestHeaders'].getAllRawHeaders())
return x
# HTML generation helpers
def sibLink(self, name):
"""
Return the text that links to a sibling of the requested resource.
"""
if self.postpath:
return (len(self.postpath)*b"../") + name
else:
return name
def childLink(self, name):
"""
Return the text that links to a child of the requested resource.
"""
lpp = len(self.postpath)
if lpp > 1:
return ((lpp-1)*b"../") + name
elif lpp == 1:
return name
else: # lpp == 0
if len(self.prepath) and self.prepath[-1]:
return self.prepath[-1] + b'/' + name
else:
return name
def process(self):
"""
Process a request.
"""
# get site from channel
self.site = self.channel.site
# set various default headers
self.setHeader(b'server', version)
self.setHeader(b'date', http.datetimeToString())
# Resource Identification
self.prepath = []
self.postpath = list(map(unquote, self.path[1:].split(b'/')))
try:
resrc = self.site.getResourceFor(self)
if resource._IEncodingResource.providedBy(resrc):
encoder = resrc.getEncoder(self)
if encoder is not None:
self._encoder = encoder
self.render(resrc)
except:
self.processingFailed(failure.Failure())
def write(self, data):
"""
Write data to the transport (if not responding to a HEAD request).
@param data: A string to write to the response.
"""
if not self.startedWriting:
# Before doing the first write, check to see if a default
# Content-Type header should be supplied.
modified = self.code != http.NOT_MODIFIED
contentType = self.responseHeaders.getRawHeaders(b'content-type')
if modified and contentType is None and self.defaultContentType is not None:
self.responseHeaders.setRawHeaders(
b'content-type', [self.defaultContentType])
# Only let the write happen if we're not generating a HEAD response by
# faking out the request method. Note, if we are doing that,
# startedWriting will never be true, and the above logic may run
# multiple times. It will only actually change the responseHeaders once
# though, so it's still okay.
if not self._inFakeHead:
if self._encoder:
data = self._encoder.encode(data)
http.Request.write(self, data)
def finish(self):
"""
Override C{http.Request.finish} for possible encoding.
"""
if self._encoder:
data = self._encoder.finish()
if data:
http.Request.write(self, data)
return http.Request.finish(self)
def render(self, resrc):
"""
Ask a resource to render itself.
@param resrc: a L{twisted.web.resource.IResource}.
"""
try:
body = resrc.render(self)
except UnsupportedMethod as e:
allowedMethods = e.allowedMethods
if (self.method == b"HEAD") and (b"GET" in allowedMethods):
# We must support HEAD (RFC 2616, 5.1.1). If the
# resource doesn't, fake it by giving the resource
# a 'GET' request and then return only the headers,
# not the body.
log.msg("Using GET to fake a HEAD request for %s" %
(resrc,))
self.method = b"GET"
self._inFakeHead = True
body = resrc.render(self)
if body is NOT_DONE_YET:
log.msg("Tried to fake a HEAD request for %s, but "
"it got away from me." % resrc)
# Oh well, I guess we won't include the content length.
else:
self.setHeader(b'content-length', intToBytes(len(body)))
self._inFakeHead = False
self.method = b"HEAD"
self.write(b'')
self.finish()
return
if self.method in (supportedMethods):
# We MUST include an Allow header
# (RFC 2616, 10.4.6 and 14.7)
self.setHeader('Allow', ', '.join(allowedMethods))
s = ('''Your browser approached me (at %(URI)s) with'''
''' the method "%(method)s". I only allow'''
''' the method%(plural)s %(allowed)s here.''' % {
'URI': escape(self.uri),
'method': self.method,
'plural': ((len(allowedMethods) > 1) and 's') or '',
'allowed': ', '.join(allowedMethods)
})
epage = resource.ErrorPage(http.NOT_ALLOWED,
"Method Not Allowed", s)
body = epage.render(self)
else:
epage = resource.ErrorPage(
http.NOT_IMPLEMENTED, "Huh?",
"I don't know how to treat a %s request." %
(escape(self.method.decode("charmap")),))
body = epage.render(self)
# end except UnsupportedMethod
if body == NOT_DONE_YET:
return
if not isinstance(body, bytes):
body = resource.ErrorPage(
http.INTERNAL_SERVER_ERROR,
"Request did not return bytes",
"Request: " + html.PRE(reflect.safe_repr(self)) + "<br />" +
"Resource: " + html.PRE(reflect.safe_repr(resrc)) + "<br />" +
"Value: " + html.PRE(reflect.safe_repr(body))).render(self)
if self.method == b"HEAD":
if len(body) > 0:
# This is a Bad Thing (RFC 2616, 9.4)
log.msg("Warning: HEAD request %s for resource %s is"
" returning a message body."
" I think I'll eat it."
% (self, resrc))
self.setHeader(b'content-length',
intToBytes(len(body)))
self.write(b'')
else:
self.setHeader(b'content-length',
intToBytes(len(body)))
self.write(body)
self.finish()
def processingFailed(self, reason):
log.err(reason)
# Re-enable on Python 3 as part of #6178:
if not _PY3 and self.site.displayTracebacks:
body = ("<html><head><title>web.Server Traceback (most recent call last)</title></head>"
"<body><b>web.Server Traceback (most recent call last):</b>\n\n"
"%s\n\n</body></html>\n"
% webutil.formatFailure(reason))
else:
body = (b"<html><head><title>Processing Failed</title></head><body>"
b"<b>Processing Failed</b></body></html>")
self.setResponseCode(http.INTERNAL_SERVER_ERROR)
self.setHeader(b'content-type', b"text/html")
self.setHeader(b'content-length', intToBytes(len(body)))
self.write(body)
self.finish()
return reason
def view_write(self, issuer, data):
"""Remote version of write; same interface.
"""
self.write(data)
def view_finish(self, issuer):
"""Remote version of finish; same interface.
"""
self.finish()
def view_addCookie(self, issuer, k, v, **kwargs):
"""Remote version of addCookie; same interface.
"""
self.addCookie(k, v, **kwargs)
def view_setHeader(self, issuer, k, v):
"""Remote version of setHeader; same interface.
"""
self.setHeader(k, v)
def view_setLastModified(self, issuer, when):
"""Remote version of setLastModified; same interface.
"""
self.setLastModified(when)
def view_setETag(self, issuer, tag):
"""Remote version of setETag; same interface.
"""
self.setETag(tag)
def view_setResponseCode(self, issuer, code, message=None):
"""
Remote version of setResponseCode; same interface.
"""
self.setResponseCode(code, message)
def view_registerProducer(self, issuer, producer, streaming):
"""Remote version of registerProducer; same interface.
(requires a remote producer.)
"""
self.registerProducer(_RemoteProducerWrapper(producer), streaming)
def view_unregisterProducer(self, issuer):
self.unregisterProducer()
### these calls remain local
session = None
def getSession(self, sessionInterface = None):
# Session management
if not self.session:
cookiename = b"_".join([b'TWISTED_SESSION'] + self.sitepath)
sessionCookie = self.getCookie(cookiename)
if sessionCookie:
try:
self.session = self.site.getSession(sessionCookie)
except KeyError:
pass
# if it still hasn't been set, fix it up.
if not self.session:
self.session = self.site.makeSession()
self.addCookie(cookiename, self.session.uid, path=b'/')
self.session.touch()
if sessionInterface:
return self.session.getComponent(sessionInterface)
return self.session
def _prePathURL(self, prepath):
port = self.getHost().port
if self.isSecure():
default = 443
else:
default = 80
if port == default:
hostport = ''
else:
hostport = ':%d' % port
prefix = networkString('http%s://%s%s/' % (
self.isSecure() and 's' or '',
nativeString(self.getRequestHostname()),
hostport))
path = b'/'.join([quote(segment, safe=b'') for segment in prepath])
return prefix + path
def prePathURL(self):
return self._prePathURL(self.prepath)
def URLPath(self):
from twisted.python import urlpath
return urlpath.URLPath.fromRequest(self)
def rememberRootURL(self):
"""
Remember the currently-processed part of the URL for later
recalling.
"""
url = self._prePathURL(self.prepath[:-1])
self.appRootURL = url
def getRootURL(self):
"""
Get a previously-remembered URL.
"""
return self.appRootURL
@implementer(iweb._IRequestEncoderFactory)
class GzipEncoderFactory(object):
"""
@cvar compressLevel: The compression level used by the compressor, default
to 9 (highest).
@since: 12.3
"""
compressLevel = 9
def encoderForRequest(self, request):
"""
Check the headers if the client accepts gzip encoding, and encodes the
request if so.
"""
acceptHeaders = request.requestHeaders.getRawHeaders(
'accept-encoding', [])
supported = ','.join(acceptHeaders).split(',')
if 'gzip' in supported:
encoding = request.responseHeaders.getRawHeaders(
'content-encoding')
if encoding:
encoding = '%s,gzip' % ','.join(encoding)
else:
encoding = 'gzip'
request.responseHeaders.setRawHeaders('content-encoding',
[encoding])
return _GzipEncoder(self.compressLevel, request)
@implementer(iweb._IRequestEncoder)
class _GzipEncoder(object):
"""
An encoder which supports gzip.
@ivar _zlibCompressor: The zlib compressor instance used to compress the
stream.
@ivar _request: A reference to the originating request.
@since: 12.3
"""
_zlibCompressor = None
def __init__(self, compressLevel, request):
self._zlibCompressor = zlib.compressobj(
compressLevel, zlib.DEFLATED, 16 + zlib.MAX_WBITS)
self._request = request
def encode(self, data):
"""
Write to the request, automatically compressing data on the fly.
"""
if not self._request.startedWriting:
# Remove the content-length header, we can't honor it
# because we compress on the fly.
self._request.responseHeaders.removeHeader(b'content-length')
return self._zlibCompressor.compress(data)
def finish(self):
"""
Finish handling the request request, flushing any data from the zlib
buffer.
"""
remain = self._zlibCompressor.flush()
self._zlibCompressor = None
return remain
class _RemoteProducerWrapper:
def __init__(self, remote):
self.resumeProducing = remote.remoteMethod("resumeProducing")
self.pauseProducing = remote.remoteMethod("pauseProducing")
self.stopProducing = remote.remoteMethod("stopProducing")
class Session(components.Componentized):
"""
A user's session with a system.
This utility class contains no functionality, but is used to
represent a session.
@ivar uid: A unique identifier for the session, C{bytes}.
@ivar _reactor: An object providing L{IReactorTime} to use for scheduling
expiration.
@ivar sessionTimeout: timeout of a session, in seconds.
"""
sessionTimeout = 900
_expireCall = None
def __init__(self, site, uid, reactor=None):
"""
Initialize a session with a unique ID for that session.
"""
components.Componentized.__init__(self)
if reactor is None:
from twisted.internet import reactor
self._reactor = reactor
self.site = site
self.uid = uid
self.expireCallbacks = []
self.touch()
self.sessionNamespaces = {}
def startCheckingExpiration(self):
"""
Start expiration tracking.
@return: C{None}
"""
self._expireCall = self._reactor.callLater(
self.sessionTimeout, self.expire)
def notifyOnExpire(self, callback):
"""
Call this callback when the session expires or logs out.
"""
self.expireCallbacks.append(callback)
def expire(self):
"""
Expire/logout of the session.
"""
del self.site.sessions[self.uid]
for c in self.expireCallbacks:
c()
self.expireCallbacks = []
if self._expireCall and self._expireCall.active():
self._expireCall.cancel()
# Break reference cycle.
self._expireCall = None
def touch(self):
"""
Notify session modification.
"""
self.lastModified = self._reactor.seconds()
if self._expireCall is not None:
self._expireCall.reset(self.sessionTimeout)
version = networkString("TwistedWeb/%s" % (copyright.version,))
class Site(http.HTTPFactory):
"""
A web site: manage log, sessions, and resources.
@ivar counter: increment value used for generating unique sessions ID.
@ivar requestFactory: factory creating requests objects. Default to
L{Request}.
@ivar displayTracebacks: if set, Twisted internal errors are displayed on
rendered pages. Default to C{True}.
@ivar sessionFactory: factory for sessions objects. Default to L{Session}.
@ivar sessionCheckTime: Deprecated. See L{Session.sessionTimeout} instead.
"""
counter = 0
requestFactory = Request
displayTracebacks = True
sessionFactory = Session
sessionCheckTime = 1800
def __init__(self, resource, *args, **kwargs):
"""
@param resource: The root of the resource hierarchy. All request
traversal for requests received by this factory will begin at this
resource.
@type resource: L{IResource} provider
@see: L{twisted.web.http.HTTPFactory.__init__}
"""
http.HTTPFactory.__init__(self, *args, **kwargs)
self.sessions = {}
self.resource = resource
def _openLogFile(self, path):
from twisted.python import logfile
return logfile.LogFile(os.path.basename(path), os.path.dirname(path))
def __getstate__(self):
d = self.__dict__.copy()
d['sessions'] = {}
return d
def _mkuid(self):
"""
(internal) Generate an opaque, unique ID for a user's session.
"""
from hashlib import md5
import random
self.counter = self.counter + 1
return md5(networkString(
"%s_%s" % (str(random.random()) , str(self.counter)))
).hexdigest()
def makeSession(self):
"""
Generate a new Session instance, and store it for future reference.
"""
uid = self._mkuid()
session = self.sessions[uid] = self.sessionFactory(self, uid)
session.startCheckingExpiration()
return session
def getSession(self, uid):
"""
Get a previously generated session, by its unique ID.
This raises a KeyError if the session is not found.
"""
return self.sessions[uid]
def buildProtocol(self, addr):
"""
Generate a channel attached to this site.
"""
channel = http.HTTPFactory.buildProtocol(self, addr)
channel.requestFactory = self.requestFactory
channel.site = self
return channel
isLeaf = 0
def render(self, request):
"""
Redirect because a Site is always a directory.
"""
request.redirect(request.prePathURL() + b'/')
request.finish()
def getChildWithDefault(self, pathEl, request):
"""
Emulate a resource's getChild method.
"""
request.site = self
return self.resource.getChildWithDefault(pathEl, request)
def getResourceFor(self, request):
"""
Get a resource for a request.
This iterates through the resource heirarchy, calling
getChildWithDefault on each resource it finds for a path element,
stopping when it hits an element where isLeaf is true.
"""
request.site = self
# Sitepath is used to determine cookie names between distributed
# servers and disconnected sites.
request.sitepath = copy.copy(request.prepath)
return resource.getChildForRequest(self.resource, request)

View file

@ -0,0 +1,154 @@
# -*- test-case-name: twisted.web.test.test_soap -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
SOAP support for twisted.web.
Requires SOAPpy 0.10.1 or later.
Maintainer: Itamar Shtull-Trauring
Future plans:
SOAPContext support of some kind.
Pluggable method lookup policies.
"""
# SOAPpy
import SOAPpy
# twisted imports
from twisted.web import server, resource, client
from twisted.internet import defer
class SOAPPublisher(resource.Resource):
"""Publish SOAP methods.
By default, publish methods beginning with 'soap_'. If the method
has an attribute 'useKeywords', it well get the arguments passed
as keyword args.
"""
isLeaf = 1
# override to change the encoding used for responses
encoding = "UTF-8"
def lookupFunction(self, functionName):
"""Lookup published SOAP function.
Override in subclasses. Default behaviour - publish methods
starting with soap_.
@return: callable or None if not found.
"""
return getattr(self, "soap_%s" % functionName, None)
def render(self, request):
"""Handle a SOAP command."""
data = request.content.read()
p, header, body, attrs = SOAPpy.parseSOAPRPC(data, 1, 1, 1)
methodName, args, kwargs, ns = p._name, p._aslist, p._asdict, p._ns
# deal with changes in SOAPpy 0.11
if callable(args):
args = args()
if callable(kwargs):
kwargs = kwargs()
function = self.lookupFunction(methodName)
if not function:
self._methodNotFound(request, methodName)
return server.NOT_DONE_YET
else:
if hasattr(function, "useKeywords"):
keywords = {}
for k, v in kwargs.items():
keywords[str(k)] = v
d = defer.maybeDeferred(function, **keywords)
else:
d = defer.maybeDeferred(function, *args)
d.addCallback(self._gotResult, request, methodName)
d.addErrback(self._gotError, request, methodName)
return server.NOT_DONE_YET
def _methodNotFound(self, request, methodName):
response = SOAPpy.buildSOAP(SOAPpy.faultType("%s:Client" %
SOAPpy.NS.ENV_T, "Method %s not found" % methodName),
encoding=self.encoding)
self._sendResponse(request, response, status=500)
def _gotResult(self, result, request, methodName):
if not isinstance(result, SOAPpy.voidType):
result = {"Result": result}
response = SOAPpy.buildSOAP(kw={'%sResponse' % methodName: result},
encoding=self.encoding)
self._sendResponse(request, response)
def _gotError(self, failure, request, methodName):
e = failure.value
if isinstance(e, SOAPpy.faultType):
fault = e
else:
fault = SOAPpy.faultType("%s:Server" % SOAPpy.NS.ENV_T,
"Method %s failed." % methodName)
response = SOAPpy.buildSOAP(fault, encoding=self.encoding)
self._sendResponse(request, response, status=500)
def _sendResponse(self, request, response, status=200):
request.setResponseCode(status)
if self.encoding is not None:
mimeType = 'text/xml; charset="%s"' % self.encoding
else:
mimeType = "text/xml"
request.setHeader("Content-type", mimeType)
request.setHeader("Content-length", str(len(response)))
request.write(response)
request.finish()
class Proxy:
"""A Proxy for making remote SOAP calls.
Pass the URL of the remote SOAP server to the constructor.
Use proxy.callRemote('foobar', 1, 2) to call remote method
'foobar' with args 1 and 2, proxy.callRemote('foobar', x=1)
will call foobar with named argument 'x'.
"""
# at some point this should have encoding etc. kwargs
def __init__(self, url, namespace=None, header=None):
self.url = url
self.namespace = namespace
self.header = header
def _cbGotResult(self, result):
result = SOAPpy.parseSOAPRPC(result)
if hasattr(result, 'Result'):
return result.Result
elif len(result) == 1:
## SOAPpy 0.11.6 wraps the return results in a containing structure.
## This check added to make Proxy behaviour emulate SOAPProxy, which
## flattens the structure by default.
## This behaviour is OK because even singleton lists are wrapped in
## another singleton structType, which is almost always useless.
return result[0]
else:
return result
def callRemote(self, method, *args, **kwargs):
payload = SOAPpy.buildSOAP(args=args, kw=kwargs, method=method,
header=self.header, namespace=self.namespace)
return client.getPage(self.url, postdata=payload, method="POST",
headers={'content-type': 'text/xml',
'SOAPAction': method}
).addCallback(self._cbGotResult)

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,636 @@
# -*- test-case-name: twisted.web.test.test_xml -*-
#
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
*S*mall, *U*ncomplicated *X*ML.
This is a very simple implementation of XML/HTML as a network
protocol. It is not at all clever. Its main features are that it
does not:
- support namespaces
- mung mnemonic entity references
- validate
- perform *any* external actions (such as fetching URLs or writing files)
under *any* circumstances
- has lots and lots of horrible hacks for supporting broken HTML (as an
option, they're not on by default).
"""
from twisted.internet.protocol import Protocol
from twisted.python.reflect import prefixedMethodNames
# Elements of the three-tuples in the state table.
BEGIN_HANDLER = 0
DO_HANDLER = 1
END_HANDLER = 2
identChars = '.-_:'
lenientIdentChars = identChars + ';+#/%~'
def nop(*args, **kw):
"Do nothing."
def unionlist(*args):
l = []
for x in args:
l.extend(x)
d = dict([(x, 1) for x in l])
return d.keys()
def zipfndict(*args, **kw):
default = kw.get('default', nop)
d = {}
for key in unionlist(*[fndict.keys() for fndict in args]):
d[key] = tuple([x.get(key, default) for x in args])
return d
def prefixedMethodClassDict(clazz, prefix):
return dict([(name, getattr(clazz, prefix + name)) for name in prefixedMethodNames(clazz, prefix)])
def prefixedMethodObjDict(obj, prefix):
return dict([(name, getattr(obj, prefix + name)) for name in prefixedMethodNames(obj.__class__, prefix)])
class ParseError(Exception):
def __init__(self, filename, line, col, message):
self.filename = filename
self.line = line
self.col = col
self.message = message
def __str__(self):
return "%s:%s:%s: %s" % (self.filename, self.line, self.col,
self.message)
class XMLParser(Protocol):
state = None
encodings = None
filename = "<xml />"
beExtremelyLenient = 0
_prepend = None
# _leadingBodyData will sometimes be set before switching to the
# 'bodydata' state, when we "accidentally" read a byte of bodydata
# in a different state.
_leadingBodyData = None
def connectionMade(self):
self.lineno = 1
self.colno = 0
self.encodings = []
def saveMark(self):
'''Get the line number and column of the last character parsed'''
# This gets replaced during dataReceived, restored afterwards
return (self.lineno, self.colno)
def _parseError(self, message):
raise ParseError(*((self.filename,)+self.saveMark()+(message,)))
def _buildStateTable(self):
'''Return a dictionary of begin, do, end state function tuples'''
# _buildStateTable leaves something to be desired but it does what it
# does.. probably slowly, so I'm doing some evil caching so it doesn't
# get called more than once per class.
stateTable = getattr(self.__class__, '__stateTable', None)
if stateTable is None:
stateTable = self.__class__.__stateTable = zipfndict(
*[prefixedMethodObjDict(self, prefix)
for prefix in ('begin_', 'do_', 'end_')])
return stateTable
def _decode(self, data):
if 'UTF-16' in self.encodings or 'UCS-2' in self.encodings:
assert not len(data) & 1, 'UTF-16 must come in pairs for now'
if self._prepend:
data = self._prepend + data
for encoding in self.encodings:
data = unicode(data, encoding)
return data
def maybeBodyData(self):
if self.endtag:
return 'bodydata'
# Get ready for fun! We're going to allow
# <script>if (foo < bar)</script> to work!
# We do this by making everything between <script> and
# </script> a Text
# BUT <script src="foo"> will be special-cased to do regular,
# lenient behavior, because those may not have </script>
# -radix
if (self.tagName == 'script' and 'src' not in self.tagAttributes):
# we do this ourselves rather than having begin_waitforendscript
# becuase that can get called multiple times and we don't want
# bodydata to get reset other than the first time.
self.begin_bodydata(None)
return 'waitforendscript'
return 'bodydata'
def dataReceived(self, data):
stateTable = self._buildStateTable()
if not self.state:
# all UTF-16 starts with this string
if data.startswith('\xff\xfe'):
self._prepend = '\xff\xfe'
self.encodings.append('UTF-16')
data = data[2:]
elif data.startswith('\xfe\xff'):
self._prepend = '\xfe\xff'
self.encodings.append('UTF-16')
data = data[2:]
self.state = 'begin'
if self.encodings:
data = self._decode(data)
# bring state, lineno, colno into local scope
lineno, colno = self.lineno, self.colno
curState = self.state
# replace saveMark with a nested scope function
_saveMark = self.saveMark
def saveMark():
return (lineno, colno)
self.saveMark = saveMark
# fetch functions from the stateTable
beginFn, doFn, endFn = stateTable[curState]
try:
for byte in data:
# do newline stuff
if byte == '\n':
lineno += 1
colno = 0
else:
colno += 1
newState = doFn(byte)
if newState is not None and newState != curState:
# this is the endFn from the previous state
endFn()
curState = newState
beginFn, doFn, endFn = stateTable[curState]
beginFn(byte)
finally:
self.saveMark = _saveMark
self.lineno, self.colno = lineno, colno
# state doesn't make sense if there's an exception..
self.state = curState
def connectionLost(self, reason):
"""
End the last state we were in.
"""
stateTable = self._buildStateTable()
stateTable[self.state][END_HANDLER]()
# state methods
def do_begin(self, byte):
if byte.isspace():
return
if byte != '<':
if self.beExtremelyLenient:
self._leadingBodyData = byte
return 'bodydata'
self._parseError("First char of document [%r] wasn't <" % (byte,))
return 'tagstart'
def begin_comment(self, byte):
self.commentbuf = ''
def do_comment(self, byte):
self.commentbuf += byte
if self.commentbuf.endswith('-->'):
self.gotComment(self.commentbuf[:-3])
return 'bodydata'
def begin_tagstart(self, byte):
self.tagName = '' # name of the tag
self.tagAttributes = {} # attributes of the tag
self.termtag = 0 # is the tag self-terminating
self.endtag = 0
def do_tagstart(self, byte):
if byte.isalnum() or byte in identChars:
self.tagName += byte
if self.tagName == '!--':
return 'comment'
elif byte.isspace():
if self.tagName:
if self.endtag:
# properly strict thing to do here is probably to only
# accept whitespace
return 'waitforgt'
return 'attrs'
else:
self._parseError("Whitespace before tag-name")
elif byte == '>':
if self.endtag:
self.gotTagEnd(self.tagName)
return 'bodydata'
else:
self.gotTagStart(self.tagName, {})
return (not self.beExtremelyLenient) and 'bodydata' or self.maybeBodyData()
elif byte == '/':
if self.tagName:
return 'afterslash'
else:
self.endtag = 1
elif byte in '!?':
if self.tagName:
if not self.beExtremelyLenient:
self._parseError("Invalid character in tag-name")
else:
self.tagName += byte
self.termtag = 1
elif byte == '[':
if self.tagName == '!':
return 'expectcdata'
else:
self._parseError("Invalid '[' in tag-name")
else:
if self.beExtremelyLenient:
self.bodydata = '<'
return 'unentity'
self._parseError('Invalid tag character: %r'% byte)
def begin_unentity(self, byte):
self.bodydata += byte
def do_unentity(self, byte):
self.bodydata += byte
return 'bodydata'
def end_unentity(self):
self.gotText(self.bodydata)
def begin_expectcdata(self, byte):
self.cdatabuf = byte
def do_expectcdata(self, byte):
self.cdatabuf += byte
cdb = self.cdatabuf
cd = '[CDATA['
if len(cd) > len(cdb):
if cd.startswith(cdb):
return
elif self.beExtremelyLenient:
## WHAT THE CRAP!? MSWord9 generates HTML that includes these
## bizarre <![if !foo]> <![endif]> chunks, so I've gotta ignore
## 'em as best I can. this should really be a separate parse
## state but I don't even have any idea what these _are_.
return 'waitforgt'
else:
self._parseError("Mal-formed CDATA header")
if cd == cdb:
self.cdatabuf = ''
return 'cdata'
self._parseError("Mal-formed CDATA header")
def do_cdata(self, byte):
self.cdatabuf += byte
if self.cdatabuf.endswith("]]>"):
self.cdatabuf = self.cdatabuf[:-3]
return 'bodydata'
def end_cdata(self):
self.gotCData(self.cdatabuf)
self.cdatabuf = ''
def do_attrs(self, byte):
if byte.isalnum() or byte in identChars:
# XXX FIXME really handle !DOCTYPE at some point
if self.tagName == '!DOCTYPE':
return 'doctype'
if self.tagName[0] in '!?':
return 'waitforgt'
return 'attrname'
elif byte.isspace():
return
elif byte == '>':
self.gotTagStart(self.tagName, self.tagAttributes)
return (not self.beExtremelyLenient) and 'bodydata' or self.maybeBodyData()
elif byte == '/':
return 'afterslash'
elif self.beExtremelyLenient:
# discard and move on? Only case I've seen of this so far was:
# <foo bar="baz"">
return
self._parseError("Unexpected character: %r" % byte)
def begin_doctype(self, byte):
self.doctype = byte
def do_doctype(self, byte):
if byte == '>':
return 'bodydata'
self.doctype += byte
def end_doctype(self):
self.gotDoctype(self.doctype)
self.doctype = None
def do_waitforgt(self, byte):
if byte == '>':
if self.endtag or not self.beExtremelyLenient:
return 'bodydata'
return self.maybeBodyData()
def begin_attrname(self, byte):
self.attrname = byte
self._attrname_termtag = 0
def do_attrname(self, byte):
if byte.isalnum() or byte in identChars:
self.attrname += byte
return
elif byte == '=':
return 'beforeattrval'
elif byte.isspace():
return 'beforeeq'
elif self.beExtremelyLenient:
if byte in '"\'':
return 'attrval'
if byte in lenientIdentChars or byte.isalnum():
self.attrname += byte
return
if byte == '/':
self._attrname_termtag = 1
return
if byte == '>':
self.attrval = 'True'
self.tagAttributes[self.attrname] = self.attrval
self.gotTagStart(self.tagName, self.tagAttributes)
if self._attrname_termtag:
self.gotTagEnd(self.tagName)
return 'bodydata'
return self.maybeBodyData()
# something is really broken. let's leave this attribute where it
# is and move on to the next thing
return
self._parseError("Invalid attribute name: %r %r" % (self.attrname, byte))
def do_beforeattrval(self, byte):
if byte in '"\'':
return 'attrval'
elif byte.isspace():
return
elif self.beExtremelyLenient:
if byte in lenientIdentChars or byte.isalnum():
return 'messyattr'
if byte == '>':
self.attrval = 'True'
self.tagAttributes[self.attrname] = self.attrval
self.gotTagStart(self.tagName, self.tagAttributes)
return self.maybeBodyData()
if byte == '\\':
# I saw this in actual HTML once:
# <font size=\"3\"><sup>SM</sup></font>
return
self._parseError("Invalid initial attribute value: %r; Attribute values must be quoted." % byte)
attrname = ''
attrval = ''
def begin_beforeeq(self,byte):
self._beforeeq_termtag = 0
def do_beforeeq(self, byte):
if byte == '=':
return 'beforeattrval'
elif byte.isspace():
return
elif self.beExtremelyLenient:
if byte.isalnum() or byte in identChars:
self.attrval = 'True'
self.tagAttributes[self.attrname] = self.attrval
return 'attrname'
elif byte == '>':
self.attrval = 'True'
self.tagAttributes[self.attrname] = self.attrval
self.gotTagStart(self.tagName, self.tagAttributes)
if self._beforeeq_termtag:
self.gotTagEnd(self.tagName)
return 'bodydata'
return self.maybeBodyData()
elif byte == '/':
self._beforeeq_termtag = 1
return
self._parseError("Invalid attribute")
def begin_attrval(self, byte):
self.quotetype = byte
self.attrval = ''
def do_attrval(self, byte):
if byte == self.quotetype:
return 'attrs'
self.attrval += byte
def end_attrval(self):
self.tagAttributes[self.attrname] = self.attrval
self.attrname = self.attrval = ''
def begin_messyattr(self, byte):
self.attrval = byte
def do_messyattr(self, byte):
if byte.isspace():
return 'attrs'
elif byte == '>':
endTag = 0
if self.attrval.endswith('/'):
endTag = 1
self.attrval = self.attrval[:-1]
self.tagAttributes[self.attrname] = self.attrval
self.gotTagStart(self.tagName, self.tagAttributes)
if endTag:
self.gotTagEnd(self.tagName)
return 'bodydata'
return self.maybeBodyData()
else:
self.attrval += byte
def end_messyattr(self):
if self.attrval:
self.tagAttributes[self.attrname] = self.attrval
def begin_afterslash(self, byte):
self._after_slash_closed = 0
def do_afterslash(self, byte):
# this state is only after a self-terminating slash, e.g. <foo/>
if self._after_slash_closed:
self._parseError("Mal-formed")#XXX When does this happen??
if byte != '>':
if self.beExtremelyLenient:
return
else:
self._parseError("No data allowed after '/'")
self._after_slash_closed = 1
self.gotTagStart(self.tagName, self.tagAttributes)
self.gotTagEnd(self.tagName)
# don't need maybeBodyData here because there better not be
# any javascript code after a <script/>... we'll see :(
return 'bodydata'
def begin_bodydata(self, byte):
if self._leadingBodyData:
self.bodydata = self._leadingBodyData
del self._leadingBodyData
else:
self.bodydata = ''
def do_bodydata(self, byte):
if byte == '<':
return 'tagstart'
if byte == '&':
return 'entityref'
self.bodydata += byte
def end_bodydata(self):
self.gotText(self.bodydata)
self.bodydata = ''
def do_waitforendscript(self, byte):
if byte == '<':
return 'waitscriptendtag'
self.bodydata += byte
def begin_waitscriptendtag(self, byte):
self.temptagdata = ''
self.tagName = ''
self.endtag = 0
def do_waitscriptendtag(self, byte):
# 1 enforce / as first byte read
# 2 enforce following bytes to be subset of "script" until
# tagName == "script"
# 2a when that happens, gotText(self.bodydata) and gotTagEnd(self.tagName)
# 3 spaces can happen anywhere, they're ignored
# e.g. < / script >
# 4 anything else causes all data I've read to be moved to the
# bodydata, and switch back to waitforendscript state
# If it turns out this _isn't_ a </script>, we need to
# remember all the data we've been through so we can append it
# to bodydata
self.temptagdata += byte
# 1
if byte == '/':
self.endtag = True
elif not self.endtag:
self.bodydata += "<" + self.temptagdata
return 'waitforendscript'
# 2
elif byte.isalnum() or byte in identChars:
self.tagName += byte
if not 'script'.startswith(self.tagName):
self.bodydata += "<" + self.temptagdata
return 'waitforendscript'
elif self.tagName == 'script':
self.gotText(self.bodydata)
self.gotTagEnd(self.tagName)
return 'waitforgt'
# 3
elif byte.isspace():
return 'waitscriptendtag'
# 4
else:
self.bodydata += "<" + self.temptagdata
return 'waitforendscript'
def begin_entityref(self, byte):
self.erefbuf = ''
self.erefextra = '' # extra bit for lenient mode
def do_entityref(self, byte):
if byte.isspace() or byte == "<":
if self.beExtremelyLenient:
# '&foo' probably was '&amp;foo'
if self.erefbuf and self.erefbuf != "amp":
self.erefextra = self.erefbuf
self.erefbuf = "amp"
if byte == "<":
return "tagstart"
else:
self.erefextra += byte
return 'spacebodydata'
self._parseError("Bad entity reference")
elif byte != ';':
self.erefbuf += byte
else:
return 'bodydata'
def end_entityref(self):
self.gotEntityReference(self.erefbuf)
# hacky support for space after & in entityref in beExtremelyLenient
# state should only happen in that case
def begin_spacebodydata(self, byte):
self.bodydata = self.erefextra
self.erefextra = None
do_spacebodydata = do_bodydata
end_spacebodydata = end_bodydata
# Sorta SAX-ish API
def gotTagStart(self, name, attributes):
'''Encountered an opening tag.
Default behaviour is to print.'''
print 'begin', name, attributes
def gotText(self, data):
'''Encountered text
Default behaviour is to print.'''
print 'text:', repr(data)
def gotEntityReference(self, entityRef):
'''Encountered mnemonic entity reference
Default behaviour is to print.'''
print 'entityRef: &%s;' % entityRef
def gotComment(self, comment):
'''Encountered comment.
Default behaviour is to ignore.'''
pass
def gotCData(self, cdata):
'''Encountered CDATA
Default behaviour is to call the gotText method'''
self.gotText(cdata)
def gotDoctype(self, doctype):
"""Encountered DOCTYPE
This is really grotty: it basically just gives you everything between
'<!DOCTYPE' and '>' as an argument.
"""
print '!DOCTYPE', repr(doctype)
def gotTagEnd(self, name):
'''Encountered closing tag
Default behaviour is to print.'''
print 'end', name

View file

@ -0,0 +1,232 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Support for creating a service which runs a web server.
"""
import os
# Twisted Imports
from twisted.web import server, static, twcgi, script, demo, distrib, wsgi
from twisted.internet import interfaces, reactor
from twisted.python import usage, reflect, threadpool
from twisted.spread import pb
from twisted.application import internet, service, strports
class Options(usage.Options):
"""
Define the options accepted by the I{twistd web} plugin.
"""
synopsis = "[web options]"
optParameters = [["port", "p", None, "strports description of the port to "
"start the server on."],
["logfile", "l", None, "Path to web CLF (Combined Log Format) log file."],
["https", None, None, "Port to listen on for Secure HTTP."],
["certificate", "c", "server.pem", "SSL certificate to use for HTTPS. "],
["privkey", "k", "server.pem", "SSL certificate to use for HTTPS."],
]
optFlags = [["personal", "",
"Instead of generating a webserver, generate a "
"ResourcePublisher which listens on the port given by "
"--port, or ~/%s " % (distrib.UserDirectory.userSocketName,) +
"if --port is not specified."],
["notracebacks", "n", "Do not display tracebacks in broken web pages. " +
"Displaying tracebacks to users may be security risk!"],
]
compData = usage.Completions(
optActions={"logfile" : usage.CompleteFiles("*.log"),
"certificate" : usage.CompleteFiles("*.pem"),
"privkey" : usage.CompleteFiles("*.pem")}
)
longdesc = """\
This starts a webserver. If you specify no arguments, it will be a
demo webserver that has the Test class from twisted.web.demo in it."""
def __init__(self):
usage.Options.__init__(self)
self['indexes'] = []
self['root'] = None
def opt_index(self, indexName):
"""
Add the name of a file used to check for directory indexes.
[default: index, index.html]
"""
self['indexes'].append(indexName)
opt_i = opt_index
def opt_user(self):
"""
Makes a server with ~/public_html and ~/.twistd-web-pb support for
users.
"""
self['root'] = distrib.UserDirectory()
opt_u = opt_user
def opt_path(self, path):
"""
<path> is either a specific file or a directory to be set as the root
of the web server. Use this if you have a directory full of HTML, cgi,
epy, or rpy files or any other files that you want to be served up raw.
"""
self['root'] = static.File(os.path.abspath(path))
self['root'].processors = {
'.cgi': twcgi.CGIScript,
'.epy': script.PythonScript,
'.rpy': script.ResourceScript,
}
def opt_processor(self, proc):
"""
`ext=class' where `class' is added as a Processor for files ending
with `ext'.
"""
if not isinstance(self['root'], static.File):
raise usage.UsageError("You can only use --processor after --path.")
ext, klass = proc.split('=', 1)
self['root'].processors[ext] = reflect.namedClass(klass)
def opt_class(self, className):
"""
Create a Resource subclass with a zero-argument constructor.
"""
classObj = reflect.namedClass(className)
self['root'] = classObj()
def opt_resource_script(self, name):
"""
An .rpy file to be used as the root resource of the webserver.
"""
self['root'] = script.ResourceScriptWrapper(name)
def opt_wsgi(self, name):
"""
The FQPN of a WSGI application object to serve as the root resource of
the webserver.
"""
try:
application = reflect.namedAny(name)
except (AttributeError, ValueError):
raise usage.UsageError("No such WSGI application: %r" % (name,))
pool = threadpool.ThreadPool()
reactor.callWhenRunning(pool.start)
reactor.addSystemEventTrigger('after', 'shutdown', pool.stop)
self['root'] = wsgi.WSGIResource(reactor, pool, application)
def opt_mime_type(self, defaultType):
"""
Specify the default mime-type for static files.
"""
if not isinstance(self['root'], static.File):
raise usage.UsageError("You can only use --mime_type after --path.")
self['root'].defaultType = defaultType
opt_m = opt_mime_type
def opt_allow_ignore_ext(self):
"""
Specify whether or not a request for 'foo' should return 'foo.ext'
"""
if not isinstance(self['root'], static.File):
raise usage.UsageError("You can only use --allow_ignore_ext "
"after --path.")
self['root'].ignoreExt('*')
def opt_ignore_ext(self, ext):
"""
Specify an extension to ignore. These will be processed in order.
"""
if not isinstance(self['root'], static.File):
raise usage.UsageError("You can only use --ignore_ext "
"after --path.")
self['root'].ignoreExt(ext)
def postOptions(self):
"""
Set up conditional defaults and check for dependencies.
If SSL is not available but an HTTPS server was configured, raise a
L{UsageError} indicating that this is not possible.
If no server port was supplied, select a default appropriate for the
other options supplied.
"""
if self['https']:
try:
from twisted.internet.ssl import DefaultOpenSSLContextFactory
except ImportError:
raise usage.UsageError("SSL support not installed")
if self['port'] is None:
if self['personal']:
path = os.path.expanduser(
os.path.join('~', distrib.UserDirectory.userSocketName))
self['port'] = 'unix:' + path
else:
self['port'] = 'tcp:8080'
def makePersonalServerFactory(site):
"""
Create and return a factory which will respond to I{distrib} requests
against the given site.
@type site: L{twisted.web.server.Site}
@rtype: L{twisted.internet.protocol.Factory}
"""
return pb.PBServerFactory(distrib.ResourcePublisher(site))
def makeService(config):
s = service.MultiService()
if config['root']:
root = config['root']
if config['indexes']:
config['root'].indexNames = config['indexes']
else:
# This really ought to be web.Admin or something
root = demo.Test()
if isinstance(root, static.File):
root.registry.setComponent(interfaces.IServiceCollection, s)
if config['logfile']:
site = server.Site(root, logPath=config['logfile'])
else:
site = server.Site(root)
site.displayTracebacks = not config["notracebacks"]
if config['personal']:
personal = strports.service(
config['port'], makePersonalServerFactory(site))
personal.setServiceParent(s)
else:
if config['https']:
from twisted.internet.ssl import DefaultOpenSSLContextFactory
i = internet.SSLServer(int(config['https']), site,
DefaultOpenSSLContextFactory(config['privkey'],
config['certificate']))
i.setServiceParent(s)
strports.service(config['port'], site).setServiceParent(s)
return s

View file

@ -0,0 +1,566 @@
# -*- test-case-name: twisted.web.test.test_template -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
HTML rendering for twisted.web.
@var VALID_HTML_TAG_NAMES: A list of recognized HTML tag names, used by the
L{tag} object.
@var TEMPLATE_NAMESPACE: The XML namespace used to identify attributes and
elements used by the templating system, which should be removed from the
final output document.
@var tags: A convenience object which can produce L{Tag} objects on demand via
attribute access. For example: C{tags.div} is equivalent to C{Tag("div")}.
Tags not specified in L{VALID_HTML_TAG_NAMES} will result in an
L{AttributeError}.
"""
__all__ = [
'TEMPLATE_NAMESPACE', 'VALID_HTML_TAG_NAMES', 'Element', 'TagLoader',
'XMLString', 'XMLFile', 'renderer', 'flatten', 'flattenString', 'tags',
'Comment', 'CDATA', 'Tag', 'slot', 'CharRef', 'renderElement'
]
import warnings
from zope.interface import implements
from cStringIO import StringIO
from xml.sax import make_parser, handler
from twisted.web._stan import Tag, slot, Comment, CDATA, CharRef
from twisted.python.filepath import FilePath
TEMPLATE_NAMESPACE = 'http://twistedmatrix.com/ns/twisted.web.template/0.1'
from twisted.web.iweb import ITemplateLoader
from twisted.python import log
# Go read the definition of NOT_DONE_YET. For lulz. This is totally
# equivalent. And this turns out to be necessary, because trying to import
# NOT_DONE_YET in this module causes a circular import which we cannot escape
# from. From which we cannot escape. Etc. glyph is okay with this solution for
# now, and so am I, as long as this comment stays to explain to future
# maintainers what it means. ~ C.
#
# See http://twistedmatrix.com/trac/ticket/5557 for progress on fixing this.
NOT_DONE_YET = 1
class _NSContext(object):
"""
A mapping from XML namespaces onto their prefixes in the document.
"""
def __init__(self, parent=None):
"""
Pull out the parent's namespaces, if there's no parent then default to
XML.
"""
self.parent = parent
if parent is not None:
self.nss = dict(parent.nss)
else:
self.nss = {'http://www.w3.org/XML/1998/namespace':'xml'}
def get(self, k, d=None):
"""
Get a prefix for a namespace.
@param d: The default prefix value.
"""
return self.nss.get(k, d)
def __setitem__(self, k, v):
"""
Proxy through to setting the prefix for the namespace.
"""
self.nss.__setitem__(k, v)
def __getitem__(self, k):
"""
Proxy through to getting the prefix for the namespace.
"""
return self.nss.__getitem__(k)
class _ToStan(handler.ContentHandler, handler.EntityResolver):
"""
A SAX parser which converts an XML document to the Twisted STAN
Document Object Model.
"""
def __init__(self, sourceFilename):
"""
@param sourceFilename: the filename to load the XML out of.
"""
self.sourceFilename = sourceFilename
self.prefixMap = _NSContext()
self.inCDATA = False
def setDocumentLocator(self, locator):
"""
Set the document locator, which knows about line and character numbers.
"""
self.locator = locator
def startDocument(self):
"""
Initialise the document.
"""
self.document = []
self.current = self.document
self.stack = []
self.xmlnsAttrs = []
def endDocument(self):
"""
Document ended.
"""
def processingInstruction(self, target, data):
"""
Processing instructions are ignored.
"""
def startPrefixMapping(self, prefix, uri):
"""
Set up the prefix mapping, which maps fully qualified namespace URIs
onto namespace prefixes.
This gets called before startElementNS whenever an C{xmlns} attribute
is seen.
"""
self.prefixMap = _NSContext(self.prefixMap)
self.prefixMap[uri] = prefix
# Ignore the template namespace; we'll replace those during parsing.
if uri == TEMPLATE_NAMESPACE:
return
# Add to a list that will be applied once we have the element.
if prefix is None:
self.xmlnsAttrs.append(('xmlns',uri))
else:
self.xmlnsAttrs.append(('xmlns:%s'%prefix,uri))
def endPrefixMapping(self, prefix):
"""
"Pops the stack" on the prefix mapping.
Gets called after endElementNS.
"""
self.prefixMap = self.prefixMap.parent
def startElementNS(self, namespaceAndName, qname, attrs):
"""
Gets called when we encounter a new xmlns attribute.
@param namespaceAndName: a (namespace, name) tuple, where name
determines which type of action to take, if the namespace matches
L{TEMPLATE_NAMESPACE}.
@param qname: ignored.
@param attrs: attributes on the element being started.
"""
filename = self.sourceFilename
lineNumber = self.locator.getLineNumber()
columnNumber = self.locator.getColumnNumber()
ns, name = namespaceAndName
if ns == TEMPLATE_NAMESPACE:
if name == 'transparent':
name = ''
elif name == 'slot':
try:
# Try to get the default value for the slot
default = attrs[(None, 'default')]
except KeyError:
# If there wasn't one, then use None to indicate no
# default.
default = None
el = slot(
attrs[(None, 'name')], default=default,
filename=filename, lineNumber=lineNumber,
columnNumber=columnNumber)
self.stack.append(el)
self.current.append(el)
self.current = el.children
return
render = None
attrs = dict(attrs)
for k, v in attrs.items():
attrNS, justTheName = k
if attrNS != TEMPLATE_NAMESPACE:
continue
if justTheName == 'render':
render = v
del attrs[k]
# nonTemplateAttrs is a dictionary mapping attributes that are *not* in
# TEMPLATE_NAMESPACE to their values. Those in TEMPLATE_NAMESPACE were
# just removed from 'attrs' in the loop immediately above. The key in
# nonTemplateAttrs is either simply the attribute name (if it was not
# specified as having a namespace in the template) or prefix:name,
# preserving the xml namespace prefix given in the document.
nonTemplateAttrs = {}
for (attrNs, attrName), v in attrs.items():
nsPrefix = self.prefixMap.get(attrNs)
if nsPrefix is None:
attrKey = attrName
else:
attrKey = '%s:%s' % (nsPrefix, attrName)
nonTemplateAttrs[attrKey] = v
if ns == TEMPLATE_NAMESPACE and name == 'attr':
if not self.stack:
# TODO: define a better exception for this?
raise AssertionError(
'<{%s}attr> as top-level element' % (TEMPLATE_NAMESPACE,))
if 'name' not in nonTemplateAttrs:
# TODO: same here
raise AssertionError(
'<{%s}attr> requires a name attribute' % (TEMPLATE_NAMESPACE,))
el = Tag('', render=render, filename=filename,
lineNumber=lineNumber, columnNumber=columnNumber)
self.stack[-1].attributes[nonTemplateAttrs['name']] = el
self.stack.append(el)
self.current = el.children
return
# Apply any xmlns attributes
if self.xmlnsAttrs:
nonTemplateAttrs.update(dict(self.xmlnsAttrs))
self.xmlnsAttrs = []
# Add the prefix that was used in the parsed template for non-template
# namespaces (which will not be consumed anyway).
if ns != TEMPLATE_NAMESPACE and ns is not None:
prefix = self.prefixMap[ns]
if prefix is not None:
name = '%s:%s' % (self.prefixMap[ns],name)
el = Tag(
name, attributes=dict(nonTemplateAttrs), render=render,
filename=filename, lineNumber=lineNumber,
columnNumber=columnNumber)
self.stack.append(el)
self.current.append(el)
self.current = el.children
def characters(self, ch):
"""
Called when we receive some characters. CDATA characters get passed
through as is.
@type ch: C{string}
"""
if self.inCDATA:
self.stack[-1].append(ch)
return
self.current.append(ch)
def endElementNS(self, name, qname):
"""
A namespace tag is closed. Pop the stack, if there's anything left in
it, otherwise return to the document's namespace.
"""
self.stack.pop()
if self.stack:
self.current = self.stack[-1].children
else:
self.current = self.document
def startDTD(self, name, publicId, systemId):
"""
DTDs are ignored.
"""
def endDTD(self, *args):
"""
DTDs are ignored.
"""
def startCDATA(self):
"""
We're starting to be in a CDATA element, make a note of this.
"""
self.inCDATA = True
self.stack.append([])
def endCDATA(self):
"""
We're no longer in a CDATA element. Collect up the characters we've
parsed and put them in a new CDATA object.
"""
self.inCDATA = False
comment = ''.join(self.stack.pop())
self.current.append(CDATA(comment))
def comment(self, content):
"""
Add an XML comment which we've encountered.
"""
self.current.append(Comment(content))
def _flatsaxParse(fl):
"""
Perform a SAX parse of an XML document with the _ToStan class.
@param fl: The XML document to be parsed.
@type fl: A file object or filename.
@return: a C{list} of Stan objects.
"""
parser = make_parser()
parser.setFeature(handler.feature_validation, 0)
parser.setFeature(handler.feature_namespaces, 1)
parser.setFeature(handler.feature_external_ges, 0)
parser.setFeature(handler.feature_external_pes, 0)
s = _ToStan(getattr(fl, "name", None))
parser.setContentHandler(s)
parser.setEntityResolver(s)
parser.setProperty(handler.property_lexical_handler, s)
parser.parse(fl)
return s.document
class TagLoader(object):
"""
An L{ITemplateLoader} that loads existing L{IRenderable} providers.
@ivar tag: The object which will be loaded.
@type tag: An L{IRenderable} provider.
"""
implements(ITemplateLoader)
def __init__(self, tag):
"""
@param tag: The object which will be loaded.
@type tag: An L{IRenderable} provider.
"""
self.tag = tag
def load(self):
return [self.tag]
class XMLString(object):
"""
An L{ITemplateLoader} that loads and parses XML from a string.
@ivar _loadedTemplate: The loaded document.
@type _loadedTemplate: a C{list} of Stan objects.
"""
implements(ITemplateLoader)
def __init__(self, s):
"""
Run the parser on a StringIO copy of the string.
@param s: The string from which to load the XML.
@type s: C{str}
"""
self._loadedTemplate = _flatsaxParse(StringIO(s))
def load(self):
"""
Return the document.
@return: the loaded document.
@rtype: a C{list} of Stan objects.
"""
return self._loadedTemplate
class XMLFile(object):
"""
An L{ITemplateLoader} that loads and parses XML from a file.
@ivar _loadedTemplate: The loaded document, or C{None}, if not loaded.
@type _loadedTemplate: a C{list} of Stan objects, or C{None}.
@ivar _path: The L{FilePath}, file object, or filename that is being
loaded from.
"""
implements(ITemplateLoader)
def __init__(self, path):
"""
Run the parser on a file.
@param path: The file from which to load the XML.
@type path: L{FilePath}
"""
if not isinstance(path, FilePath):
warnings.warn(
"Passing filenames or file objects to XMLFile is deprecated "
"since Twisted 12.1. Pass a FilePath instead.",
category=DeprecationWarning, stacklevel=2)
self._loadedTemplate = None
self._path = path
def _loadDoc(self):
"""
Read and parse the XML.
@return: the loaded document.
@rtype: a C{list} of Stan objects.
"""
if not isinstance(self._path, FilePath):
return _flatsaxParse(self._path)
else:
f = self._path.open('r')
try:
return _flatsaxParse(f)
finally:
f.close()
def __repr__(self):
return '<XMLFile of %r>' % (self._path,)
def load(self):
"""
Return the document, first loading it if necessary.
@return: the loaded document.
@rtype: a C{list} of Stan objects.
"""
if self._loadedTemplate is None:
self._loadedTemplate = self._loadDoc()
return self._loadedTemplate
# Last updated October 2011, using W3Schools as a reference. Link:
# http://www.w3schools.com/html5/html5_reference.asp
# Note that <xmp> is explicitly omitted; its semantics do not work with
# t.w.template and it is officially deprecated.
VALID_HTML_TAG_NAMES = set([
'a', 'abbr', 'acronym', 'address', 'applet', 'area', 'article', 'aside',
'audio', 'b', 'base', 'basefont', 'bdi', 'bdo', 'big', 'blockquote',
'body', 'br', 'button', 'canvas', 'caption', 'center', 'cite', 'code',
'col', 'colgroup', 'command', 'datalist', 'dd', 'del', 'details', 'dfn',
'dir', 'div', 'dl', 'dt', 'em', 'embed', 'fieldset', 'figcaption',
'figure', 'font', 'footer', 'form', 'frame', 'frameset', 'h1', 'h2', 'h3',
'h4', 'h5', 'h6', 'head', 'header', 'hgroup', 'hr', 'html', 'i', 'iframe',
'img', 'input', 'ins', 'isindex', 'keygen', 'kbd', 'label', 'legend',
'li', 'link', 'map', 'mark', 'menu', 'meta', 'meter', 'nav', 'noframes',
'noscript', 'object', 'ol', 'optgroup', 'option', 'output', 'p', 'param',
'pre', 'progress', 'q', 'rp', 'rt', 'ruby', 's', 'samp', 'script',
'section', 'select', 'small', 'source', 'span', 'strike', 'strong',
'style', 'sub', 'summary', 'sup', 'table', 'tbody', 'td', 'textarea',
'tfoot', 'th', 'thead', 'time', 'title', 'tr', 'tt', 'u', 'ul', 'var',
'video', 'wbr',
])
class _TagFactory(object):
"""
A factory for L{Tag} objects; the implementation of the L{tags} object.
This allows for the syntactic convenience of C{from twisted.web.html import
tags; tags.a(href="linked-page.html")}, where 'a' can be basically any HTML
tag.
The class is not exposed publicly because you only ever need one of these,
and we already made it for you.
@see: L{tags}
"""
def __getattr__(self, tagName):
if tagName == 'transparent':
return Tag('')
# allow for E.del as E.del_
tagName = tagName.rstrip('_')
if tagName not in VALID_HTML_TAG_NAMES:
raise AttributeError('unknown tag %r' % (tagName,))
return Tag(tagName)
tags = _TagFactory()
def renderElement(request, element,
doctype='<!DOCTYPE html>', _failElement=None):
"""
Render an element or other C{IRenderable}.
@param request: The C{Request} being rendered to.
@param element: An C{IRenderable} which will be rendered.
@param doctype: A C{str} which will be written as the first line of
the request, or C{None} to disable writing of a doctype. The C{string}
should not include a trailing newline and will default to the HTML5
doctype C{'<!DOCTYPE html>'}.
@returns: NOT_DONE_YET
@since: 12.1
"""
if doctype is not None:
request.write(doctype)
request.write('\n')
if _failElement is None:
_failElement = twisted.web.util.FailureElement
d = flatten(request, element, request.write)
def eb(failure):
log.err(failure, "An error occurred while rendering the response.")
if request.site.displayTracebacks:
return flatten(request, _failElement(failure), request.write)
else:
request.write(
('<div style="font-size:800%;'
'background-color:#FFF;'
'color:#F00'
'">An error occurred while rendering the response.</div>'))
d.addErrback(eb)
d.addBoth(lambda _: request.finish())
return NOT_DONE_YET
from twisted.web._element import Element, renderer
from twisted.web._flatten import flatten, flattenString
import twisted.web.util

View file

@ -0,0 +1,7 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web}.
"""

View file

@ -0,0 +1,82 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
General helpers for L{twisted.web} unit tests.
"""
from twisted.internet.defer import succeed
from twisted.web import server
from twisted.trial.unittest import TestCase
from twisted.python.failure import Failure
from twisted.web._flatten import flattenString
from twisted.web.error import FlattenerError
def _render(resource, request):
result = resource.render(request)
if isinstance(result, str):
request.write(result)
request.finish()
return succeed(None)
elif result is server.NOT_DONE_YET:
if request.finished:
return succeed(None)
else:
return request.notifyFinish()
else:
raise ValueError("Unexpected return value: %r" % (result,))
class FlattenTestCase(TestCase):
"""
A test case that assists with testing L{twisted.web._flatten}.
"""
def assertFlattensTo(self, root, target):
"""
Assert that a root element, when flattened, is equal to a string.
"""
d = flattenString(None, root)
d.addCallback(lambda s: self.assertEqual(s, target))
return d
def assertFlattensImmediately(self, root, target):
"""
Assert that a root element, when flattened, is equal to a string, and
performs no asynchronus Deferred anything.
This version is more convenient in tests which wish to make multiple
assertions about flattening, since it can be called multiple times
without having to add multiple callbacks.
@return: the result of rendering L{root}, which should be equivalent to
L{target}.
@rtype: L{bytes}
"""
results = []
it = self.assertFlattensTo(root, target)
it.addBoth(results.append)
# Do our best to clean it up if something goes wrong.
self.addCleanup(it.cancel)
if not results:
self.fail("Rendering did not complete immediately.")
result = results[0]
if isinstance(result, Failure):
result.raiseException()
return results[0]
def assertFlatteningRaises(self, root, exn):
"""
Assert flattening a root element raises a particular exception.
"""
d = self.assertFailure(self.assertFlattensTo(root, ''), FlattenerError)
d.addCallback(lambda exc: self.assertIsInstance(exc._exception, exn))
return d

View file

@ -0,0 +1,278 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Helpers related to HTTP requests, used by tests.
"""
from __future__ import division, absolute_import
__all__ = ['DummyChannel', 'DummyRequest']
from io import BytesIO
from zope.interface import implementer
from twisted.internet.defer import Deferred
from twisted.internet.address import IPv4Address
from twisted.internet.interfaces import ISSLTransport
from twisted.web.http_headers import Headers
from twisted.web.resource import Resource
from twisted.web.server import NOT_DONE_YET, Session, Site
class DummyChannel:
class TCP:
port = 80
disconnected = False
def __init__(self):
self.written = BytesIO()
self.producers = []
def getPeer(self):
return IPv4Address("TCP", '192.168.1.1', 12344)
def write(self, data):
if not isinstance(data, bytes):
raise TypeError("Can only write bytes to a transport, not %r" % (data,))
self.written.write(data)
def writeSequence(self, iovec):
for data in iovec:
self.write(data)
def getHost(self):
return IPv4Address("TCP", '10.0.0.1', self.port)
def registerProducer(self, producer, streaming):
self.producers.append((producer, streaming))
def loseConnection(self):
self.disconnected = True
@implementer(ISSLTransport)
class SSL(TCP):
pass
site = Site(Resource())
def __init__(self):
self.transport = self.TCP()
def requestDone(self, request):
pass
class DummyRequest(object):
"""
Represents a dummy or fake request.
@ivar _finishedDeferreds: C{None} or a C{list} of L{Deferreds} which will
be called back with C{None} when C{finish} is called or which will be
errbacked if C{processingFailed} is called.
@type headers: C{dict}
@ivar headers: A mapping of header name to header value for all request
headers.
@type outgoingHeaders: C{dict}
@ivar outgoingHeaders: A mapping of header name to header value for all
response headers.
@type responseCode: C{int}
@ivar responseCode: The response code which was passed to
C{setResponseCode}.
@type written: C{list} of C{bytes}
@ivar written: The bytes which have been written to the request.
"""
uri = b'http://dummy/'
method = b'GET'
client = None
def registerProducer(self, prod,s):
self.go = 1
while self.go:
prod.resumeProducing()
def unregisterProducer(self):
self.go = 0
def __init__(self, postpath, session=None):
self.sitepath = []
self.written = []
self.finished = 0
self.postpath = postpath
self.prepath = []
self.session = None
self.protoSession = session or Session(0, self)
self.args = {}
self.outgoingHeaders = {}
self.requestHeaders = Headers()
self.responseHeaders = Headers()
self.responseCode = None
self.headers = {}
self._finishedDeferreds = []
self._serverName = b"dummy"
self.clientproto = b"HTTP/1.0"
def getHeader(self, name):
"""
Retrieve the value of a request header.
@type name: C{bytes}
@param name: The name of the request header for which to retrieve the
value. Header names are compared case-insensitively.
@rtype: C{bytes} or L{NoneType}
@return: The value of the specified request header.
"""
return self.headers.get(name.lower(), None)
def getAllHeaders(self):
"""
Retrieve all the values of the request headers as a dictionary.
@return: The entire C{headers} L{dict}.
"""
return self.headers
def setHeader(self, name, value):
"""TODO: make this assert on write() if the header is content-length
"""
self.outgoingHeaders[name.lower()] = value
def getSession(self):
if self.session:
return self.session
assert not self.written, "Session cannot be requested after data has been written."
self.session = self.protoSession
return self.session
def render(self, resource):
"""
Render the given resource as a response to this request.
This implementation only handles a few of the most common behaviors of
resources. It can handle a render method that returns a string or
C{NOT_DONE_YET}. It doesn't know anything about the semantics of
request methods (eg HEAD) nor how to set any particular headers.
Basically, it's largely broken, but sufficient for some tests at least.
It should B{not} be expanded to do all the same stuff L{Request} does.
Instead, L{DummyRequest} should be phased out and L{Request} (or some
other real code factored in a different way) used.
"""
result = resource.render(self)
if result is NOT_DONE_YET:
return
self.write(result)
self.finish()
def write(self, data):
if not isinstance(data, bytes):
raise TypeError("write() only accepts bytes")
self.written.append(data)
def notifyFinish(self):
"""
Return a L{Deferred} which is called back with C{None} when the request
is finished. This will probably only work if you haven't called
C{finish} yet.
"""
finished = Deferred()
self._finishedDeferreds.append(finished)
return finished
def finish(self):
"""
Record that the request is finished and callback and L{Deferred}s
waiting for notification of this.
"""
self.finished = self.finished + 1
if self._finishedDeferreds is not None:
observers = self._finishedDeferreds
self._finishedDeferreds = None
for obs in observers:
obs.callback(None)
def processingFailed(self, reason):
"""
Errback and L{Deferreds} waiting for finish notification.
"""
if self._finishedDeferreds is not None:
observers = self._finishedDeferreds
self._finishedDeferreds = None
for obs in observers:
obs.errback(reason)
def addArg(self, name, value):
self.args[name] = [value]
def setResponseCode(self, code, message=None):
"""
Set the HTTP status response code, but takes care that this is called
before any data is written.
"""
assert not self.written, "Response code cannot be set after data has been written: %s." % "@@@@".join(self.written)
self.responseCode = code
self.responseMessage = message
def setLastModified(self, when):
assert not self.written, "Last-Modified cannot be set after data has been written: %s." % "@@@@".join(self.written)
def setETag(self, tag):
assert not self.written, "ETag cannot be set after data has been written: %s." % "@@@@".join(self.written)
def getClientIP(self):
"""
Return the IPv4 address of the client which made this request, if there
is one, otherwise C{None}.
"""
if isinstance(self.client, IPv4Address):
return self.client.host
return None
def getRequestHostname(self):
"""
Get a dummy hostname associated to the HTTP request.
@rtype: C{bytes}
@returns: a dummy hostname
"""
return self._serverName
def getHost(self):
"""
Get a dummy transport's host.
@rtype: C{IPv4Address}
@returns: a dummy transport's host
"""
return IPv4Address('TCP', '127.0.0.1', 80)
def getClient(self):
"""
Stub to get the client doing the HTTP request.
This merely just ensures that this method exists here. Feel free to
extend it.
"""

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,364 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web.twcgi}.
"""
import sys, os
from twisted.trial import unittest
from twisted.internet import reactor, interfaces, error
from twisted.python import util, failure, log
from twisted.web.http import NOT_FOUND, INTERNAL_SERVER_ERROR
from twisted.web import client, twcgi, server, resource
from twisted.web.test._util import _render
from twisted.web.test.test_web import DummyRequest
DUMMY_CGI = '''\
print "Header: OK"
print
print "cgi output"
'''
DUAL_HEADER_CGI = '''\
print "Header: spam"
print "Header: eggs"
print
print "cgi output"
'''
BROKEN_HEADER_CGI = '''\
print "XYZ"
print
print "cgi output"
'''
SPECIAL_HEADER_CGI = '''\
print "Server: monkeys"
print "Date: last year"
print
print "cgi output"
'''
READINPUT_CGI = '''\
# this is an example of a correctly-written CGI script which reads a body
# from stdin, which only reads env['CONTENT_LENGTH'] bytes.
import os, sys
body_length = int(os.environ.get('CONTENT_LENGTH',0))
indata = sys.stdin.read(body_length)
print "Header: OK"
print
print "readinput ok"
'''
READALLINPUT_CGI = '''\
# this is an example of the typical (incorrect) CGI script which expects
# the server to close stdin when the body of the request is complete.
# A correct CGI should only read env['CONTENT_LENGTH'] bytes.
import sys
indata = sys.stdin.read()
print "Header: OK"
print
print "readallinput ok"
'''
NO_DUPLICATE_CONTENT_TYPE_HEADER_CGI = '''\
print "content-type: text/cgi-duplicate-test"
print
print "cgi output"
'''
class PythonScript(twcgi.FilteredScript):
filter = sys.executable
class CGI(unittest.TestCase):
"""
Tests for L{twcgi.FilteredScript}.
"""
if not interfaces.IReactorProcess.providedBy(reactor):
skip = "CGI tests require a functional reactor.spawnProcess()"
def startServer(self, cgi):
root = resource.Resource()
cgipath = util.sibpath(__file__, cgi)
root.putChild("cgi", PythonScript(cgipath))
site = server.Site(root)
self.p = reactor.listenTCP(0, site)
return self.p.getHost().port
def tearDown(self):
if getattr(self, 'p', None):
return self.p.stopListening()
def writeCGI(self, source):
cgiFilename = os.path.abspath(self.mktemp())
cgiFile = file(cgiFilename, 'wt')
cgiFile.write(source)
cgiFile.close()
return cgiFilename
def testCGI(self):
cgiFilename = self.writeCGI(DUMMY_CGI)
portnum = self.startServer(cgiFilename)
d = client.getPage("http://localhost:%d/cgi" % portnum)
d.addCallback(self._testCGI_1)
return d
def _testCGI_1(self, res):
self.assertEqual(res, "cgi output" + os.linesep)
def test_protectedServerAndDate(self):
"""
If the CGI script emits a I{Server} or I{Date} header, these are
ignored.
"""
cgiFilename = self.writeCGI(SPECIAL_HEADER_CGI)
portnum = self.startServer(cgiFilename)
url = "http://localhost:%d/cgi" % (portnum,)
factory = client.HTTPClientFactory(url)
reactor.connectTCP('localhost', portnum, factory)
def checkResponse(ignored):
self.assertNotIn('monkeys', factory.response_headers['server'])
self.assertNotIn('last year', factory.response_headers['date'])
factory.deferred.addCallback(checkResponse)
return factory.deferred
def test_noDuplicateContentTypeHeaders(self):
"""
If the CGI script emits a I{content-type} header, make sure that the
server doesn't add an additional (duplicate) one, as per ticket 4786.
"""
cgiFilename = self.writeCGI(NO_DUPLICATE_CONTENT_TYPE_HEADER_CGI)
portnum = self.startServer(cgiFilename)
url = "http://localhost:%d/cgi" % (portnum,)
factory = client.HTTPClientFactory(url)
reactor.connectTCP('localhost', portnum, factory)
def checkResponse(ignored):
self.assertEqual(
factory.response_headers['content-type'], ['text/cgi-duplicate-test'])
factory.deferred.addCallback(checkResponse)
return factory.deferred
def test_duplicateHeaderCGI(self):
"""
If a CGI script emits two instances of the same header, both are sent in
the response.
"""
cgiFilename = self.writeCGI(DUAL_HEADER_CGI)
portnum = self.startServer(cgiFilename)
url = "http://localhost:%d/cgi" % (portnum,)
factory = client.HTTPClientFactory(url)
reactor.connectTCP('localhost', portnum, factory)
def checkResponse(ignored):
self.assertEqual(
factory.response_headers['header'], ['spam', 'eggs'])
factory.deferred.addCallback(checkResponse)
return factory.deferred
def test_malformedHeaderCGI(self):
"""
Check for the error message in the duplicated header
"""
cgiFilename = self.writeCGI(BROKEN_HEADER_CGI)
portnum = self.startServer(cgiFilename)
url = "http://localhost:%d/cgi" % (portnum,)
factory = client.HTTPClientFactory(url)
reactor.connectTCP('localhost', portnum, factory)
loggedMessages = []
def addMessage(eventDict):
loggedMessages.append(log.textFromEventDict(eventDict))
log.addObserver(addMessage)
self.addCleanup(log.removeObserver, addMessage)
def checkResponse(ignored):
self.assertEqual(loggedMessages[0],
"ignoring malformed CGI header: 'XYZ'")
factory.deferred.addCallback(checkResponse)
return factory.deferred
def testReadEmptyInput(self):
cgiFilename = os.path.abspath(self.mktemp())
cgiFile = file(cgiFilename, 'wt')
cgiFile.write(READINPUT_CGI)
cgiFile.close()
portnum = self.startServer(cgiFilename)
d = client.getPage("http://localhost:%d/cgi" % portnum)
d.addCallback(self._testReadEmptyInput_1)
return d
testReadEmptyInput.timeout = 5
def _testReadEmptyInput_1(self, res):
self.assertEqual(res, "readinput ok%s" % os.linesep)
def testReadInput(self):
cgiFilename = os.path.abspath(self.mktemp())
cgiFile = file(cgiFilename, 'wt')
cgiFile.write(READINPUT_CGI)
cgiFile.close()
portnum = self.startServer(cgiFilename)
d = client.getPage("http://localhost:%d/cgi" % portnum,
method="POST",
postdata="Here is your stdin")
d.addCallback(self._testReadInput_1)
return d
testReadInput.timeout = 5
def _testReadInput_1(self, res):
self.assertEqual(res, "readinput ok%s" % os.linesep)
def testReadAllInput(self):
cgiFilename = os.path.abspath(self.mktemp())
cgiFile = file(cgiFilename, 'wt')
cgiFile.write(READALLINPUT_CGI)
cgiFile.close()
portnum = self.startServer(cgiFilename)
d = client.getPage("http://localhost:%d/cgi" % portnum,
method="POST",
postdata="Here is your stdin")
d.addCallback(self._testReadAllInput_1)
return d
testReadAllInput.timeout = 5
def _testReadAllInput_1(self, res):
self.assertEqual(res, "readallinput ok%s" % os.linesep)
def test_useReactorArgument(self):
"""
L{twcgi.FilteredScript.runProcess} uses the reactor passed as an
argument to the constructor.
"""
class FakeReactor:
"""
A fake reactor recording whether spawnProcess is called.
"""
called = False
def spawnProcess(self, *args, **kwargs):
"""
Set the C{called} flag to C{True} if C{spawnProcess} is called.
@param args: Positional arguments.
@param kwargs: Keyword arguements.
"""
self.called = True
fakeReactor = FakeReactor()
request = DummyRequest(['a', 'b'])
resource = twcgi.FilteredScript("dummy-file", reactor=fakeReactor)
_render(resource, request)
self.assertTrue(fakeReactor.called)
class CGIScriptTests(unittest.TestCase):
"""
Tests for L{twcgi.CGIScript}.
"""
def test_pathInfo(self):
"""
L{twcgi.CGIScript.render} sets the process environment I{PATH_INFO} from
the request path.
"""
class FakeReactor:
"""
A fake reactor recording the environment passed to spawnProcess.
"""
def spawnProcess(self, process, filename, args, env, wdir):
"""
Store the C{env} L{dict} to an instance attribute.
@param process: Ignored
@param filename: Ignored
@param args: Ignored
@param env: The environment L{dict} which will be stored
@param wdir: Ignored
"""
self.process_env = env
_reactor = FakeReactor()
resource = twcgi.CGIScript(self.mktemp(), reactor=_reactor)
request = DummyRequest(['a', 'b'])
_render(resource, request)
self.assertEqual(_reactor.process_env["PATH_INFO"],
"/a/b")
class CGIDirectoryTests(unittest.TestCase):
"""
Tests for L{twcgi.CGIDirectory}.
"""
def test_render(self):
"""
L{twcgi.CGIDirectory.render} sets the HTTP response code to I{NOT
FOUND}.
"""
resource = twcgi.CGIDirectory(self.mktemp())
request = DummyRequest([''])
d = _render(resource, request)
def cbRendered(ignored):
self.assertEqual(request.responseCode, NOT_FOUND)
d.addCallback(cbRendered)
return d
def test_notFoundChild(self):
"""
L{twcgi.CGIDirectory.getChild} returns a resource which renders an
response with the HTTP I{NOT FOUND} status code if the indicated child
does not exist as an entry in the directory used to initialized the
L{twcgi.CGIDirectory}.
"""
path = self.mktemp()
os.makedirs(path)
resource = twcgi.CGIDirectory(path)
request = DummyRequest(['foo'])
child = resource.getChild("foo", request)
d = _render(child, request)
def cbRendered(ignored):
self.assertEqual(request.responseCode, NOT_FOUND)
d.addCallback(cbRendered)
return d
class CGIProcessProtocolTests(unittest.TestCase):
"""
Tests for L{twcgi.CGIProcessProtocol}.
"""
def test_prematureEndOfHeaders(self):
"""
If the process communicating with L{CGIProcessProtocol} ends before
finishing writing out headers, the response has I{INTERNAL SERVER
ERROR} as its status code.
"""
request = DummyRequest([''])
protocol = twcgi.CGIProcessProtocol(request)
protocol.processEnded(failure.Failure(error.ProcessTerminated()))
self.assertEqual(request.responseCode, INTERNAL_SERVER_ERROR)

View file

@ -0,0 +1,434 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web.distrib}.
"""
from os.path import abspath
from xml.dom.minidom import parseString
try:
import pwd
except ImportError:
pwd = None
from zope.interface.verify import verifyObject
from twisted.python import log, filepath
from twisted.internet import reactor, defer
from twisted.trial import unittest
from twisted.spread import pb
from twisted.spread.banana import SIZE_LIMIT
from twisted.web import http, distrib, client, resource, static, server
from twisted.web.test.test_web import DummyRequest
from twisted.web.test._util import _render
from twisted.test import proto_helpers
class MySite(server.Site):
pass
class PBServerFactory(pb.PBServerFactory):
"""
A PB server factory which keeps track of the most recent protocol it
created.
@ivar proto: L{None} or the L{Broker} instance most recently returned
from C{buildProtocol}.
"""
proto = None
def buildProtocol(self, addr):
self.proto = pb.PBServerFactory.buildProtocol(self, addr)
return self.proto
class DistribTest(unittest.TestCase):
port1 = None
port2 = None
sub = None
f1 = None
def tearDown(self):
"""
Clean up all the event sources left behind by either directly by
test methods or indirectly via some distrib API.
"""
dl = [defer.Deferred(), defer.Deferred()]
if self.f1 is not None and self.f1.proto is not None:
self.f1.proto.notifyOnDisconnect(lambda: dl[0].callback(None))
else:
dl[0].callback(None)
if self.sub is not None and self.sub.publisher is not None:
self.sub.publisher.broker.notifyOnDisconnect(
lambda: dl[1].callback(None))
self.sub.publisher.broker.transport.loseConnection()
else:
dl[1].callback(None)
if self.port1 is not None:
dl.append(self.port1.stopListening())
if self.port2 is not None:
dl.append(self.port2.stopListening())
return defer.gatherResults(dl)
def testDistrib(self):
# site1 is the publisher
r1 = resource.Resource()
r1.putChild("there", static.Data("root", "text/plain"))
site1 = server.Site(r1)
self.f1 = PBServerFactory(distrib.ResourcePublisher(site1))
self.port1 = reactor.listenTCP(0, self.f1)
self.sub = distrib.ResourceSubscription("127.0.0.1",
self.port1.getHost().port)
r2 = resource.Resource()
r2.putChild("here", self.sub)
f2 = MySite(r2)
self.port2 = reactor.listenTCP(0, f2)
d = client.getPage("http://127.0.0.1:%d/here/there" % \
self.port2.getHost().port)
d.addCallback(self.assertEqual, 'root')
return d
def _setupDistribServer(self, child):
"""
Set up a resource on a distrib site using L{ResourcePublisher}.
@param child: The resource to publish using distrib.
@return: A tuple consisting of the host and port on which to contact
the created site.
"""
distribRoot = resource.Resource()
distribRoot.putChild("child", child)
distribSite = server.Site(distribRoot)
self.f1 = distribFactory = PBServerFactory(
distrib.ResourcePublisher(distribSite))
distribPort = reactor.listenTCP(
0, distribFactory, interface="127.0.0.1")
self.addCleanup(distribPort.stopListening)
addr = distribPort.getHost()
self.sub = mainRoot = distrib.ResourceSubscription(
addr.host, addr.port)
mainSite = server.Site(mainRoot)
mainPort = reactor.listenTCP(0, mainSite, interface="127.0.0.1")
self.addCleanup(mainPort.stopListening)
mainAddr = mainPort.getHost()
return mainPort, mainAddr
def _requestTest(self, child, **kwargs):
"""
Set up a resource on a distrib site using L{ResourcePublisher} and
then retrieve it from a L{ResourceSubscription} via an HTTP client.
@param child: The resource to publish using distrib.
@param **kwargs: Extra keyword arguments to pass to L{getPage} when
requesting the resource.
@return: A L{Deferred} which fires with the result of the request.
"""
mainPort, mainAddr = self._setupDistribServer(child)
return client.getPage("http://%s:%s/child" % (
mainAddr.host, mainAddr.port), **kwargs)
def _requestAgentTest(self, child, **kwargs):
"""
Set up a resource on a distrib site using L{ResourcePublisher} and
then retrieve it from a L{ResourceSubscription} via an HTTP client.
@param child: The resource to publish using distrib.
@param **kwargs: Extra keyword arguments to pass to L{Agent.request} when
requesting the resource.
@return: A L{Deferred} which fires with a tuple consisting of a
L{twisted.test.proto_helpers.AccumulatingProtocol} containing the
body of the response and an L{IResponse} with the response itself.
"""
mainPort, mainAddr = self._setupDistribServer(child)
d = client.Agent(reactor).request("GET", "http://%s:%s/child" % (
mainAddr.host, mainAddr.port), **kwargs)
def cbCollectBody(response):
protocol = proto_helpers.AccumulatingProtocol()
response.deliverBody(protocol)
d = protocol.closedDeferred = defer.Deferred()
d.addCallback(lambda _: (protocol, response))
return d
d.addCallback(cbCollectBody)
return d
def test_requestHeaders(self):
"""
The request headers are available on the request object passed to a
distributed resource's C{render} method.
"""
requestHeaders = {}
class ReportRequestHeaders(resource.Resource):
def render(self, request):
requestHeaders.update(dict(
request.requestHeaders.getAllRawHeaders()))
return ""
request = self._requestTest(
ReportRequestHeaders(), headers={'foo': 'bar'})
def cbRequested(result):
self.assertEqual(requestHeaders['Foo'], ['bar'])
request.addCallback(cbRequested)
return request
def test_requestResponseCode(self):
"""
The response code can be set by the request object passed to a
distributed resource's C{render} method.
"""
class SetResponseCode(resource.Resource):
def render(self, request):
request.setResponseCode(200)
return ""
request = self._requestAgentTest(SetResponseCode())
def cbRequested(result):
self.assertEqual(result[0].data, "")
self.assertEqual(result[1].code, 200)
self.assertEqual(result[1].phrase, "OK")
request.addCallback(cbRequested)
return request
def test_requestResponseCodeMessage(self):
"""
The response code and message can be set by the request object passed to
a distributed resource's C{render} method.
"""
class SetResponseCode(resource.Resource):
def render(self, request):
request.setResponseCode(200, "some-message")
return ""
request = self._requestAgentTest(SetResponseCode())
def cbRequested(result):
self.assertEqual(result[0].data, "")
self.assertEqual(result[1].code, 200)
self.assertEqual(result[1].phrase, "some-message")
request.addCallback(cbRequested)
return request
def test_largeWrite(self):
"""
If a string longer than the Banana size limit is passed to the
L{distrib.Request} passed to the remote resource, it is broken into
smaller strings to be transported over the PB connection.
"""
class LargeWrite(resource.Resource):
def render(self, request):
request.write('x' * SIZE_LIMIT + 'y')
request.finish()
return server.NOT_DONE_YET
request = self._requestTest(LargeWrite())
request.addCallback(self.assertEqual, 'x' * SIZE_LIMIT + 'y')
return request
def test_largeReturn(self):
"""
Like L{test_largeWrite}, but for the case where C{render} returns a
long string rather than explicitly passing it to L{Request.write}.
"""
class LargeReturn(resource.Resource):
def render(self, request):
return 'x' * SIZE_LIMIT + 'y'
request = self._requestTest(LargeReturn())
request.addCallback(self.assertEqual, 'x' * SIZE_LIMIT + 'y')
return request
def test_connectionLost(self):
"""
If there is an error issuing the request to the remote publisher, an
error response is returned.
"""
# Using pb.Root as a publisher will cause request calls to fail with an
# error every time. Just what we want to test.
self.f1 = serverFactory = PBServerFactory(pb.Root())
self.port1 = serverPort = reactor.listenTCP(0, serverFactory)
self.sub = subscription = distrib.ResourceSubscription(
"127.0.0.1", serverPort.getHost().port)
request = DummyRequest([''])
d = _render(subscription, request)
def cbRendered(ignored):
self.assertEqual(request.responseCode, 500)
# This is the error we caused the request to fail with. It should
# have been logged.
self.assertEqual(len(self.flushLoggedErrors(pb.NoSuchMethod)), 1)
d.addCallback(cbRendered)
return d
class _PasswordDatabase:
def __init__(self, users):
self._users = users
def getpwall(self):
return iter(self._users)
def getpwnam(self, username):
for user in self._users:
if user[0] == username:
return user
raise KeyError()
class UserDirectoryTests(unittest.TestCase):
"""
Tests for L{UserDirectory}, a resource for listing all user resources
available on a system.
"""
def setUp(self):
self.alice = ('alice', 'x', 123, 456, 'Alice,,,', self.mktemp(), '/bin/sh')
self.bob = ('bob', 'x', 234, 567, 'Bob,,,', self.mktemp(), '/bin/sh')
self.database = _PasswordDatabase([self.alice, self.bob])
self.directory = distrib.UserDirectory(self.database)
def test_interface(self):
"""
L{UserDirectory} instances provide L{resource.IResource}.
"""
self.assertTrue(verifyObject(resource.IResource, self.directory))
def _404Test(self, name):
"""
Verify that requesting the C{name} child of C{self.directory} results
in a 404 response.
"""
request = DummyRequest([name])
result = self.directory.getChild(name, request)
d = _render(result, request)
def cbRendered(ignored):
self.assertEqual(request.responseCode, 404)
d.addCallback(cbRendered)
return d
def test_getInvalidUser(self):
"""
L{UserDirectory.getChild} returns a resource which renders a 404
response when passed a string which does not correspond to any known
user.
"""
return self._404Test('carol')
def test_getUserWithoutResource(self):
"""
L{UserDirectory.getChild} returns a resource which renders a 404
response when passed a string which corresponds to a known user who has
neither a user directory nor a user distrib socket.
"""
return self._404Test('alice')
def test_getPublicHTMLChild(self):
"""
L{UserDirectory.getChild} returns a L{static.File} instance when passed
the name of a user with a home directory containing a I{public_html}
directory.
"""
home = filepath.FilePath(self.bob[-2])
public_html = home.child('public_html')
public_html.makedirs()
request = DummyRequest(['bob'])
result = self.directory.getChild('bob', request)
self.assertIsInstance(result, static.File)
self.assertEqual(result.path, public_html.path)
def test_getDistribChild(self):
"""
L{UserDirectory.getChild} returns a L{ResourceSubscription} instance
when passed the name of a user suffixed with C{".twistd"} who has a
home directory containing a I{.twistd-web-pb} socket.
"""
home = filepath.FilePath(self.bob[-2])
home.makedirs()
web = home.child('.twistd-web-pb')
request = DummyRequest(['bob'])
result = self.directory.getChild('bob.twistd', request)
self.assertIsInstance(result, distrib.ResourceSubscription)
self.assertEqual(result.host, 'unix')
self.assertEqual(abspath(result.port), web.path)
def test_invalidMethod(self):
"""
L{UserDirectory.render} raises L{UnsupportedMethod} in response to a
non-I{GET} request.
"""
request = DummyRequest([''])
request.method = 'POST'
self.assertRaises(
server.UnsupportedMethod, self.directory.render, request)
def test_render(self):
"""
L{UserDirectory} renders a list of links to available user content
in response to a I{GET} request.
"""
public_html = filepath.FilePath(self.alice[-2]).child('public_html')
public_html.makedirs()
web = filepath.FilePath(self.bob[-2])
web.makedirs()
# This really only works if it's a unix socket, but the implementation
# doesn't currently check for that. It probably should someday, and
# then skip users with non-sockets.
web.child('.twistd-web-pb').setContent("")
request = DummyRequest([''])
result = _render(self.directory, request)
def cbRendered(ignored):
document = parseString(''.join(request.written))
# Each user should have an li with a link to their page.
[alice, bob] = document.getElementsByTagName('li')
self.assertEqual(alice.firstChild.tagName, 'a')
self.assertEqual(alice.firstChild.getAttribute('href'), 'alice/')
self.assertEqual(alice.firstChild.firstChild.data, 'Alice (file)')
self.assertEqual(bob.firstChild.tagName, 'a')
self.assertEqual(bob.firstChild.getAttribute('href'), 'bob.twistd/')
self.assertEqual(bob.firstChild.firstChild.data, 'Bob (twistd)')
result.addCallback(cbRendered)
return result
def test_passwordDatabase(self):
"""
If L{UserDirectory} is instantiated with no arguments, it uses the
L{pwd} module as its password database.
"""
directory = distrib.UserDirectory()
self.assertIdentical(directory._pwd, pwd)
if pwd is None:
test_passwordDatabase.skip = "pwd module required"

View file

@ -0,0 +1,306 @@
# -*- test-case-name: twisted.web.test.test_domhelpers -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Specific tests for (some of) the methods in L{twisted.web.domhelpers}.
"""
from xml.dom import minidom
from twisted.trial.unittest import TestCase
from twisted.web import microdom
from twisted.web import domhelpers
class DOMHelpersTestsMixin:
"""
A mixin for L{TestCase} subclasses which defines test methods for
domhelpers functionality based on a DOM creation function provided by a
subclass.
"""
dom = None
def test_getElementsByTagName(self):
doc1 = self.dom.parseString('<foo/>')
actual=domhelpers.getElementsByTagName(doc1, 'foo')[0].nodeName
expected='foo'
self.assertEqual(actual, expected)
el1=doc1.documentElement
actual=domhelpers.getElementsByTagName(el1, 'foo')[0].nodeName
self.assertEqual(actual, expected)
doc2_xml='<a><foo in="a"/><b><foo in="b"/></b><c><foo in="c"/></c><foo in="d"/><foo in="ef"/><g><foo in="g"/><h><foo in="h"/></h></g></a>'
doc2 = self.dom.parseString(doc2_xml)
tag_list=domhelpers.getElementsByTagName(doc2, 'foo')
actual=''.join([node.getAttribute('in') for node in tag_list])
expected='abcdefgh'
self.assertEqual(actual, expected)
el2=doc2.documentElement
tag_list=domhelpers.getElementsByTagName(el2, 'foo')
actual=''.join([node.getAttribute('in') for node in tag_list])
self.assertEqual(actual, expected)
doc3_xml='''
<a><foo in="a"/>
<b><foo in="b"/>
<d><foo in="d"/>
<g><foo in="g"/></g>
<h><foo in="h"/></h>
</d>
<e><foo in="e"/>
<i><foo in="i"/></i>
</e>
</b>
<c><foo in="c"/>
<f><foo in="f"/>
<j><foo in="j"/></j>
</f>
</c>
</a>'''
doc3 = self.dom.parseString(doc3_xml)
tag_list=domhelpers.getElementsByTagName(doc3, 'foo')
actual=''.join([node.getAttribute('in') for node in tag_list])
expected='abdgheicfj'
self.assertEqual(actual, expected)
el3=doc3.documentElement
tag_list=domhelpers.getElementsByTagName(el3, 'foo')
actual=''.join([node.getAttribute('in') for node in tag_list])
self.assertEqual(actual, expected)
doc4_xml='<foo><bar></bar><baz><foo/></baz></foo>'
doc4 = self.dom.parseString(doc4_xml)
actual=domhelpers.getElementsByTagName(doc4, 'foo')
root=doc4.documentElement
expected=[root, root.childNodes[-1].childNodes[0]]
self.assertEqual(actual, expected)
actual=domhelpers.getElementsByTagName(root, 'foo')
self.assertEqual(actual, expected)
def test_gatherTextNodes(self):
doc1 = self.dom.parseString('<a>foo</a>')
actual=domhelpers.gatherTextNodes(doc1)
expected='foo'
self.assertEqual(actual, expected)
actual=domhelpers.gatherTextNodes(doc1.documentElement)
self.assertEqual(actual, expected)
doc2_xml='<a>a<b>b</b><c>c</c>def<g>g<h>h</h></g></a>'
doc2 = self.dom.parseString(doc2_xml)
actual=domhelpers.gatherTextNodes(doc2)
expected='abcdefgh'
self.assertEqual(actual, expected)
actual=domhelpers.gatherTextNodes(doc2.documentElement)
self.assertEqual(actual, expected)
doc3_xml=('<a>a<b>b<d>d<g>g</g><h>h</h></d><e>e<i>i</i></e></b>' +
'<c>c<f>f<j>j</j></f></c></a>')
doc3 = self.dom.parseString(doc3_xml)
actual=domhelpers.gatherTextNodes(doc3)
expected='abdgheicfj'
self.assertEqual(actual, expected)
actual=domhelpers.gatherTextNodes(doc3.documentElement)
self.assertEqual(actual, expected)
def test_clearNode(self):
doc1 = self.dom.parseString('<a><b><c><d/></c></b></a>')
a_node=doc1.documentElement
domhelpers.clearNode(a_node)
self.assertEqual(
a_node.toxml(),
self.dom.Element('a').toxml())
doc2 = self.dom.parseString('<a><b><c><d/></c></b></a>')
b_node=doc2.documentElement.childNodes[0]
domhelpers.clearNode(b_node)
actual=doc2.documentElement.toxml()
expected = self.dom.Element('a')
expected.appendChild(self.dom.Element('b'))
self.assertEqual(actual, expected.toxml())
def test_get(self):
doc1 = self.dom.parseString('<a><b id="bar"/><c class="foo"/></a>')
node=domhelpers.get(doc1, "foo")
actual=node.toxml()
expected = self.dom.Element('c')
expected.setAttribute('class', 'foo')
self.assertEqual(actual, expected.toxml())
node=domhelpers.get(doc1, "bar")
actual=node.toxml()
expected = self.dom.Element('b')
expected.setAttribute('id', 'bar')
self.assertEqual(actual, expected.toxml())
self.assertRaises(domhelpers.NodeLookupError,
domhelpers.get,
doc1,
"pzork")
def test_getIfExists(self):
doc1 = self.dom.parseString('<a><b id="bar"/><c class="foo"/></a>')
node=domhelpers.getIfExists(doc1, "foo")
actual=node.toxml()
expected = self.dom.Element('c')
expected.setAttribute('class', 'foo')
self.assertEqual(actual, expected.toxml())
node=domhelpers.getIfExists(doc1, "pzork")
self.assertIdentical(node, None)
def test_getAndClear(self):
doc1 = self.dom.parseString('<a><b id="foo"><c></c></b></a>')
node=domhelpers.getAndClear(doc1, "foo")
actual=node.toxml()
expected = self.dom.Element('b')
expected.setAttribute('id', 'foo')
self.assertEqual(actual, expected.toxml())
def test_locateNodes(self):
doc1 = self.dom.parseString('<a><b foo="olive"><c foo="olive"/></b><d foo="poopy"/></a>')
node_list=domhelpers.locateNodes(
doc1.childNodes, 'foo', 'olive', noNesting=1)
actual=''.join([node.toxml() for node in node_list])
expected = self.dom.Element('b')
expected.setAttribute('foo', 'olive')
c = self.dom.Element('c')
c.setAttribute('foo', 'olive')
expected.appendChild(c)
self.assertEqual(actual, expected.toxml())
node_list=domhelpers.locateNodes(
doc1.childNodes, 'foo', 'olive', noNesting=0)
actual=''.join([node.toxml() for node in node_list])
self.assertEqual(actual, expected.toxml() + c.toxml())
def test_getParents(self):
doc1 = self.dom.parseString('<a><b><c><d/></c><e/></b><f/></a>')
node_list = domhelpers.getParents(
doc1.childNodes[0].childNodes[0].childNodes[0])
actual = ''.join([node.tagName for node in node_list
if hasattr(node, 'tagName')])
self.assertEqual(actual, 'cba')
def test_findElementsWithAttribute(self):
doc1 = self.dom.parseString('<a foo="1"><b foo="2"/><c foo="1"/><d/></a>')
node_list = domhelpers.findElementsWithAttribute(doc1, 'foo')
actual = ''.join([node.tagName for node in node_list])
self.assertEqual(actual, 'abc')
node_list = domhelpers.findElementsWithAttribute(doc1, 'foo', '1')
actual = ''.join([node.tagName for node in node_list])
self.assertEqual(actual, 'ac')
def test_findNodesNamed(self):
doc1 = self.dom.parseString('<doc><foo/><bar/><foo>a</foo></doc>')
node_list = domhelpers.findNodesNamed(doc1, 'foo')
actual = len(node_list)
self.assertEqual(actual, 2)
# NOT SURE WHAT THESE ARE SUPPOSED TO DO..
# def test_RawText FIXME
# def test_superSetAttribute FIXME
# def test_superPrependAttribute FIXME
# def test_superAppendAttribute FIXME
# def test_substitute FIXME
def test_escape(self):
j='this string " contains many & characters> xml< won\'t like'
expected='this string &quot; contains many &amp; characters&gt; xml&lt; won\'t like'
self.assertEqual(domhelpers.escape(j), expected)
def test_unescape(self):
j='this string &quot; has &&amp; entities &gt; &lt; and some characters xml won\'t like<'
expected='this string " has && entities > < and some characters xml won\'t like<'
self.assertEqual(domhelpers.unescape(j), expected)
def test_getNodeText(self):
"""
L{getNodeText} returns the concatenation of all the text data at or
beneath the node passed to it.
"""
node = self.dom.parseString('<foo><bar>baz</bar><bar>quux</bar></foo>')
self.assertEqual(domhelpers.getNodeText(node), "bazquux")
class MicroDOMHelpersTests(DOMHelpersTestsMixin, TestCase):
dom = microdom
def test_gatherTextNodesDropsWhitespace(self):
"""
Microdom discards whitespace-only text nodes, so L{gatherTextNodes}
returns only the text from nodes which had non-whitespace characters.
"""
doc4_xml='''<html>
<head>
</head>
<body>
stuff
</body>
</html>
'''
doc4 = self.dom.parseString(doc4_xml)
actual = domhelpers.gatherTextNodes(doc4)
expected = '\n stuff\n '
self.assertEqual(actual, expected)
actual = domhelpers.gatherTextNodes(doc4.documentElement)
self.assertEqual(actual, expected)
def test_textEntitiesNotDecoded(self):
"""
Microdom does not decode entities in text nodes.
"""
doc5_xml='<x>Souffl&amp;</x>'
doc5 = self.dom.parseString(doc5_xml)
actual=domhelpers.gatherTextNodes(doc5)
expected='Souffl&amp;'
self.assertEqual(actual, expected)
actual=domhelpers.gatherTextNodes(doc5.documentElement)
self.assertEqual(actual, expected)
class MiniDOMHelpersTests(DOMHelpersTestsMixin, TestCase):
dom = minidom
def test_textEntitiesDecoded(self):
"""
Minidom does decode entities in text nodes.
"""
doc5_xml='<x>Souffl&amp;</x>'
doc5 = self.dom.parseString(doc5_xml)
actual=domhelpers.gatherTextNodes(doc5)
expected='Souffl&'
self.assertEqual(actual, expected)
actual=domhelpers.gatherTextNodes(doc5.documentElement)
self.assertEqual(actual, expected)
def test_getNodeUnicodeText(self):
"""
L{domhelpers.getNodeText} returns a C{unicode} string when text
nodes are represented in the DOM with unicode, whether or not there
are non-ASCII characters present.
"""
node = self.dom.parseString("<foo>bar</foo>")
text = domhelpers.getNodeText(node)
self.assertEqual(text, u"bar")
self.assertIsInstance(text, unicode)
node = self.dom.parseString(u"<foo>\N{SNOWMAN}</foo>".encode('utf-8'))
text = domhelpers.getNodeText(node)
self.assertEqual(text, u"\N{SNOWMAN}")
self.assertIsInstance(text, unicode)

View file

@ -0,0 +1,151 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
HTTP errors.
"""
from twisted.trial import unittest
from twisted.web import error
class ErrorTestCase(unittest.TestCase):
"""
Tests for how L{Error} attributes are initialized.
"""
def test_noMessageValidStatus(self):
"""
If no C{message} argument is passed to the L{Error} constructor and the
C{code} argument is a valid HTTP status code, C{code} is mapped to a
descriptive string to which C{message} is assigned.
"""
e = error.Error("200")
self.assertEqual(e.message, "OK")
def test_noMessageInvalidStatus(self):
"""
If no C{message} argument is passed to the L{Error} constructor and
C{code} isn't a valid HTTP status code, C{message} stays C{None}.
"""
e = error.Error("InvalidCode")
self.assertEqual(e.message, None)
def test_messageExists(self):
"""
If a C{message} argument is passed to the L{Error} constructor, the
C{message} isn't affected by the value of C{status}.
"""
e = error.Error("200", "My own message")
self.assertEqual(e.message, "My own message")
class PageRedirectTestCase(unittest.TestCase):
"""
Tests for how L{PageRedirect} attributes are initialized.
"""
def test_noMessageValidStatus(self):
"""
If no C{message} argument is passed to the L{PageRedirect} constructor
and the C{code} argument is a valid HTTP status code, C{code} is mapped
to a descriptive string to which C{message} is assigned.
"""
e = error.PageRedirect("200", location="/foo")
self.assertEqual(e.message, "OK to /foo")
def test_noMessageValidStatusNoLocation(self):
"""
If no C{message} argument is passed to the L{PageRedirect} constructor
and C{location} is also empty and the C{code} argument is a valid HTTP
status code, C{code} is mapped to a descriptive string to which
C{message} is assigned without trying to include an empty location.
"""
e = error.PageRedirect("200")
self.assertEqual(e.message, "OK")
def test_noMessageInvalidStatusLocationExists(self):
"""
If no C{message} argument is passed to the L{PageRedirect} constructor
and C{code} isn't a valid HTTP status code, C{message} stays C{None}.
"""
e = error.PageRedirect("InvalidCode", location="/foo")
self.assertEqual(e.message, None)
def test_messageExistsLocationExists(self):
"""
If a C{message} argument is passed to the L{PageRedirect} constructor,
the C{message} isn't affected by the value of C{status}.
"""
e = error.PageRedirect("200", "My own message", location="/foo")
self.assertEqual(e.message, "My own message to /foo")
def test_messageExistsNoLocation(self):
"""
If a C{message} argument is passed to the L{PageRedirect} constructor
and no location is provided, C{message} doesn't try to include the empty
location.
"""
e = error.PageRedirect("200", "My own message")
self.assertEqual(e.message, "My own message")
class InfiniteRedirectionTestCase(unittest.TestCase):
"""
Tests for how L{InfiniteRedirection} attributes are initialized.
"""
def test_noMessageValidStatus(self):
"""
If no C{message} argument is passed to the L{InfiniteRedirection}
constructor and the C{code} argument is a valid HTTP status code,
C{code} is mapped to a descriptive string to which C{message} is
assigned.
"""
e = error.InfiniteRedirection("200", location="/foo")
self.assertEqual(e.message, "OK to /foo")
def test_noMessageValidStatusNoLocation(self):
"""
If no C{message} argument is passed to the L{InfiniteRedirection}
constructor and C{location} is also empty and the C{code} argument is a
valid HTTP status code, C{code} is mapped to a descriptive string to
which C{message} is assigned without trying to include an empty
location.
"""
e = error.InfiniteRedirection("200")
self.assertEqual(e.message, "OK")
def test_noMessageInvalidStatusLocationExists(self):
"""
If no C{message} argument is passed to the L{InfiniteRedirection}
constructor and C{code} isn't a valid HTTP status code, C{message} stays
C{None}.
"""
e = error.InfiniteRedirection("InvalidCode", location="/foo")
self.assertEqual(e.message, None)
def test_messageExistsLocationExists(self):
"""
If a C{message} argument is passed to the L{InfiniteRedirection}
constructor, the C{message} isn't affected by the value of C{status}.
"""
e = error.InfiniteRedirection("200", "My own message", location="/foo")
self.assertEqual(e.message, "My own message to /foo")
def test_messageExistsNoLocation(self):
"""
If a C{message} argument is passed to the L{InfiniteRedirection}
constructor and no location is provided, C{message} doesn't try to
include the empty location.
"""
e = error.InfiniteRedirection("200", "My own message")
self.assertEqual(e.message, "My own message")

View file

@ -0,0 +1,554 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for the flattening portion of L{twisted.web.template}, implemented in
L{twisted.web._flatten}.
"""
import sys
import traceback
from xml.etree.cElementTree import XML
from zope.interface import implements, implementer
from twisted.trial.unittest import TestCase
from twisted.test.testutils import XMLAssertionMixin
from twisted.internet.defer import passthru, succeed, gatherResults
from twisted.web.iweb import IRenderable
from twisted.web.error import UnfilledSlot, UnsupportedType, FlattenerError
from twisted.web.template import tags, Tag, Comment, CDATA, CharRef, slot
from twisted.web.template import Element, renderer, TagLoader, flattenString
from twisted.web.test._util import FlattenTestCase
class OrderedAttributes(object):
"""
An L{OrderedAttributes} is a stand-in for the L{Tag.attributes} dictionary
that orders things in a deterministic order. It doesn't do any sorting, so
whatever order the attributes are passed in, they will be returned.
@ivar attributes: The result of a L{dict}C{.items} call.
@type attributes: L{list} of 2-L{tuples}
"""
def __init__(self, attributes):
self.attributes = attributes
def iteritems(self):
"""
Like L{dict}C{.iteritems}.
@return: an iterator
@rtype: list iterator
"""
return iter(self.attributes)
class TestSerialization(FlattenTestCase, XMLAssertionMixin):
"""
Tests for flattening various things.
"""
def test_nestedTags(self):
"""
Test that nested tags flatten correctly.
"""
return self.assertFlattensTo(
tags.html(tags.body('42'), hi='there'),
'<html hi="there"><body>42</body></html>')
def test_serializeString(self):
"""
Test that strings will be flattened and escaped correctly.
"""
return gatherResults([
self.assertFlattensTo('one', 'one'),
self.assertFlattensTo('<abc&&>123', '&lt;abc&amp;&amp;&gt;123'),
])
def test_serializeSelfClosingTags(self):
"""
The serialized form of a self-closing tag is C{'<tagName />'}.
"""
return self.assertFlattensTo(tags.img(), '<img />')
def test_serializeAttribute(self):
"""
The serialized form of attribute I{a} with value I{b} is C{'a="b"'}.
"""
self.assertFlattensImmediately(tags.img(src='foo'),
'<img src="foo" />')
def test_serializedMultipleAttributes(self):
"""
Multiple attributes are separated by a single space in their serialized
form.
"""
tag = tags.img()
tag.attributes = OrderedAttributes([("src", "foo"), ("name", "bar")])
self.assertFlattensImmediately(tag, '<img src="foo" name="bar" />')
def checkAttributeSanitization(self, wrapData, wrapTag):
"""
Common implementation of L{test_serializedAttributeWithSanitization}
and L{test_serializedDeferredAttributeWithSanitization},
L{test_serializedAttributeWithTransparentTag}.
@param wrapData: A 1-argument callable that wraps around the
attribute's value so other tests can customize it.
@param wrapData: callable taking L{bytes} and returning something
flattenable
@param wrapTag: A 1-argument callable that wraps around the outer tag
so other tests can customize it.
@type wrapTag: callable taking L{Tag} and returning L{Tag}.
"""
self.assertFlattensImmediately(
wrapTag(tags.img(src=wrapData("<>&\""))),
'<img src="&lt;&gt;&amp;&quot;" />')
def test_serializedAttributeWithSanitization(self):
"""
Attribute values containing C{"<"}, C{">"}, C{"&"}, or C{'"'} have
C{"&lt;"}, C{"&gt;"}, C{"&amp;"}, or C{"&quot;"} substituted for those
bytes in the serialized output.
"""
self.checkAttributeSanitization(passthru, passthru)
def test_serializedDeferredAttributeWithSanitization(self):
"""
Like L{test_serializedAttributeWithSanitization}, but when the contents
of the attribute are in a L{Deferred
<twisted.internet.defer.Deferred>}.
"""
self.checkAttributeSanitization(succeed, passthru)
def test_serializedAttributeWithSlotWithSanitization(self):
"""
Like L{test_serializedAttributeWithSanitization} but with a slot.
"""
toss = []
self.checkAttributeSanitization(
lambda value: toss.append(value) or slot("stuff"),
lambda tag: tag.fillSlots(stuff=toss.pop())
)
def test_serializedAttributeWithTransparentTag(self):
"""
Attribute values which are supplied via the value of a C{t:transparent}
tag have the same subsitution rules to them as values supplied
directly.
"""
self.checkAttributeSanitization(tags.transparent, passthru)
def test_serializedAttributeWithTransparentTagWithRenderer(self):
"""
Like L{test_serializedAttributeWithTransparentTag}, but when the
attribute is rendered by a renderer on an element.
"""
class WithRenderer(Element):
def __init__(self, value, loader):
self.value = value
super(WithRenderer, self).__init__(loader)
@renderer
def stuff(self, request, tag):
return self.value
toss = []
self.checkAttributeSanitization(
lambda value: toss.append(value) or
tags.transparent(render="stuff"),
lambda tag: WithRenderer(toss.pop(), TagLoader(tag))
)
def test_serializedAttributeWithRenderable(self):
"""
Like L{test_serializedAttributeWithTransparentTag}, but when the
attribute is a provider of L{IRenderable} rather than a transparent
tag.
"""
@implementer(IRenderable)
class Arbitrary(object):
def __init__(self, value):
self.value = value
def render(self, request):
return self.value
self.checkAttributeSanitization(Arbitrary, passthru)
def checkTagAttributeSerialization(self, wrapTag):
"""
Common implementation of L{test_serializedAttributeWithTag} and
L{test_serializedAttributeWithDeferredTag}.
@param wrapTag: A 1-argument callable that wraps around the attribute's
value so other tests can customize it.
@param wrapTag: callable taking L{Tag} and returning something
flattenable
"""
innerTag = tags.a('<>&"')
outerTag = tags.img(src=wrapTag(innerTag))
outer = self.assertFlattensImmediately(
outerTag,
'<img src="&lt;a&gt;&amp;lt;&amp;gt;&amp;amp;&quot;&lt;/a&gt;" />')
inner = self.assertFlattensImmediately(
innerTag, '<a>&lt;&gt;&amp;"</a>')
# Since the above quoting is somewhat tricky, validate it by making sure
# that the main use-case for tag-within-attribute is supported here: if
# we serialize a tag, it is quoted *such that it can be parsed out again
# as a tag*.
self.assertXMLEqual(XML(outer).attrib['src'], inner)
def test_serializedAttributeWithTag(self):
"""
L{Tag} objects which are serialized within the context of an attribute
are serialized such that the text content of the attribute may be
parsed to retrieve the tag.
"""
self.checkTagAttributeSerialization(passthru)
def test_serializedAttributeWithDeferredTag(self):
"""
Like L{test_serializedAttributeWithTag}, but when the L{Tag} is in a
L{Deferred <twisted.internet.defer.Deferred>}.
"""
self.checkTagAttributeSerialization(succeed)
def test_serializedAttributeWithTagWithAttribute(self):
"""
Similar to L{test_serializedAttributeWithTag}, but for the additional
complexity where the tag which is the attribute value itself has an
attribute value which contains bytes which require substitution.
"""
flattened = self.assertFlattensImmediately(
tags.img(src=tags.a(href='<>&"')),
'<img src="&lt;a href='
'&quot;&amp;lt;&amp;gt;&amp;amp;&amp;quot;&quot;&gt;'
'&lt;/a&gt;" />')
# As in checkTagAttributeSerialization, belt-and-suspenders:
self.assertXMLEqual(XML(flattened).attrib['src'],
'<a href="&lt;&gt;&amp;&quot;"></a>')
def test_serializeComment(self):
"""
Test that comments are correctly flattened and escaped.
"""
return self.assertFlattensTo(Comment('foo bar'), '<!--foo bar-->'),
def test_commentEscaping(self):
"""
The data in a L{Comment} is escaped and mangled in the flattened output
so that the result is a legal SGML and XML comment.
SGML comment syntax is complicated and hard to use. This rule is more
restrictive, and more compatible:
Comments start with <!-- and end with --> and never contain -- or >.
Also by XML syntax, a comment may not end with '-'.
@see: U{http://www.w3.org/TR/REC-xml/#sec-comments}
"""
def verifyComment(c):
self.assertTrue(
c.startswith('<!--'),
"%r does not start with the comment prefix" % (c,))
self.assertTrue(
c.endswith('-->'),
"%r does not end with the comment suffix" % (c,))
# If it is shorter than 7, then the prefix and suffix overlap
# illegally.
self.assertTrue(
len(c) >= 7,
"%r is too short to be a legal comment" % (c,))
content = c[4:-3]
self.assertNotIn('--', content)
self.assertNotIn('>', content)
if content:
self.assertNotEqual(content[-1], '-')
results = []
for c in [
'',
'foo---bar',
'foo---bar-',
'foo>bar',
'foo-->bar',
'----------------',
]:
d = flattenString(None, Comment(c))
d.addCallback(verifyComment)
results.append(d)
return gatherResults(results)
def test_serializeCDATA(self):
"""
Test that CDATA is correctly flattened and escaped.
"""
return gatherResults([
self.assertFlattensTo(CDATA('foo bar'), '<![CDATA[foo bar]]>'),
self.assertFlattensTo(
CDATA('foo ]]> bar'),
'<![CDATA[foo ]]]]><![CDATA[> bar]]>'),
])
def test_serializeUnicode(self):
"""
Test that unicode is encoded correctly in the appropriate places, and
raises an error when it occurs in inappropriate place.
"""
snowman = u'\N{SNOWMAN}'
return gatherResults([
self.assertFlattensTo(snowman, '\xe2\x98\x83'),
self.assertFlattensTo(tags.p(snowman), '<p>\xe2\x98\x83</p>'),
self.assertFlattensTo(Comment(snowman), '<!--\xe2\x98\x83-->'),
self.assertFlattensTo(CDATA(snowman), '<![CDATA[\xe2\x98\x83]]>'),
self.assertFlatteningRaises(
Tag(snowman), UnicodeEncodeError),
self.assertFlatteningRaises(
Tag('p', attributes={snowman: ''}), UnicodeEncodeError),
])
def test_serializeCharRef(self):
"""
A character reference is flattened to a string using the I{&#NNNN;}
syntax.
"""
ref = CharRef(ord(u"\N{SNOWMAN}"))
return self.assertFlattensTo(ref, "&#9731;")
def test_serializeDeferred(self):
"""
Test that a deferred is substituted with the current value in the
callback chain when flattened.
"""
return self.assertFlattensTo(succeed('two'), 'two')
def test_serializeSameDeferredTwice(self):
"""
Test that the same deferred can be flattened twice.
"""
d = succeed('three')
return gatherResults([
self.assertFlattensTo(d, 'three'),
self.assertFlattensTo(d, 'three'),
])
def test_serializeIRenderable(self):
"""
Test that flattening respects all of the IRenderable interface.
"""
class FakeElement(object):
implements(IRenderable)
def render(ign,ored):
return tags.p(
'hello, ',
tags.transparent(render='test'), ' - ',
tags.transparent(render='test'))
def lookupRenderMethod(ign, name):
self.assertEqual(name, 'test')
return lambda ign, node: node('world')
return gatherResults([
self.assertFlattensTo(FakeElement(), '<p>hello, world - world</p>'),
])
def test_serializeSlots(self):
"""
Test that flattening a slot will use the slot value from the tag.
"""
t1 = tags.p(slot('test'))
t2 = t1.clone()
t2.fillSlots(test='hello, world')
return gatherResults([
self.assertFlatteningRaises(t1, UnfilledSlot),
self.assertFlattensTo(t2, '<p>hello, world</p>'),
])
def test_serializeDeferredSlots(self):
"""
Test that a slot with a deferred as its value will be flattened using
the value from the deferred.
"""
t = tags.p(slot('test'))
t.fillSlots(test=succeed(tags.em('four>')))
return self.assertFlattensTo(t, '<p><em>four&gt;</em></p>')
def test_unknownTypeRaises(self):
"""
Test that flattening an unknown type of thing raises an exception.
"""
return self.assertFlatteningRaises(None, UnsupportedType)
# Use the co_filename mechanism (instead of the __file__ mechanism) because
# it is the mechanism traceback formatting uses. The two do not necessarily
# agree with each other. This requires a code object compiled in this file.
# The easiest way to get a code object is with a new function. I'll use a
# lambda to avoid adding anything else to this namespace. The result will
# be a string which agrees with the one the traceback module will put into a
# traceback for frames associated with functions defined in this file.
HERE = (lambda: None).func_code.co_filename
class FlattenerErrorTests(TestCase):
"""
Tests for L{FlattenerError}.
"""
def test_string(self):
"""
If a L{FlattenerError} is created with a string root, up to around 40
bytes from that string are included in the string representation of the
exception.
"""
self.assertEqual(
str(FlattenerError(RuntimeError("reason"), ['abc123xyz'], [])),
"Exception while flattening:\n"
" 'abc123xyz'\n"
"RuntimeError: reason\n")
self.assertEqual(
str(FlattenerError(
RuntimeError("reason"), ['0123456789' * 10], [])),
"Exception while flattening:\n"
" '01234567890123456789<...>01234567890123456789'\n"
"RuntimeError: reason\n")
def test_unicode(self):
"""
If a L{FlattenerError} is created with a unicode root, up to around 40
characters from that string are included in the string representation
of the exception.
"""
self.assertEqual(
str(FlattenerError(
RuntimeError("reason"), [u'abc\N{SNOWMAN}xyz'], [])),
"Exception while flattening:\n"
" u'abc\\u2603xyz'\n" # Codepoint for SNOWMAN
"RuntimeError: reason\n")
self.assertEqual(
str(FlattenerError(
RuntimeError("reason"), [u'01234567\N{SNOWMAN}9' * 10],
[])),
"Exception while flattening:\n"
" u'01234567\\u2603901234567\\u26039<...>01234567\\u2603901234567"
"\\u26039'\n"
"RuntimeError: reason\n")
def test_renderable(self):
"""
If a L{FlattenerError} is created with an L{IRenderable} provider root,
the repr of that object is included in the string representation of the
exception.
"""
class Renderable(object):
implements(IRenderable)
def __repr__(self):
return "renderable repr"
self.assertEqual(
str(FlattenerError(
RuntimeError("reason"), [Renderable()], [])),
"Exception while flattening:\n"
" renderable repr\n"
"RuntimeError: reason\n")
def test_tag(self):
"""
If a L{FlattenerError} is created with a L{Tag} instance with source
location information, the source location is included in the string
representation of the exception.
"""
tag = Tag(
'div', filename='/foo/filename.xhtml', lineNumber=17, columnNumber=12)
self.assertEqual(
str(FlattenerError(RuntimeError("reason"), [tag], [])),
"Exception while flattening:\n"
" File \"/foo/filename.xhtml\", line 17, column 12, in \"div\"\n"
"RuntimeError: reason\n")
def test_tagWithoutLocation(self):
"""
If a L{FlattenerError} is created with a L{Tag} instance without source
location information, only the tagName is included in the string
representation of the exception.
"""
self.assertEqual(
str(FlattenerError(RuntimeError("reason"), [Tag('span')], [])),
"Exception while flattening:\n"
" Tag <span>\n"
"RuntimeError: reason\n")
def test_traceback(self):
"""
If a L{FlattenerError} is created with traceback frames, they are
included in the string representation of the exception.
"""
# Try to be realistic in creating the data passed in for the traceback
# frames.
def f():
g()
def g():
raise RuntimeError("reason")
try:
f()
except RuntimeError, exc:
# Get the traceback, minus the info for *this* frame
tbinfo = traceback.extract_tb(sys.exc_info()[2])[1:]
else:
self.fail("f() must raise RuntimeError")
self.assertEqual(
str(FlattenerError(exc, [], tbinfo)),
"Exception while flattening:\n"
" File \"%s\", line %d, in f\n"
" g()\n"
" File \"%s\", line %d, in g\n"
" raise RuntimeError(\"reason\")\n"
"RuntimeError: reason\n" % (
HERE, f.func_code.co_firstlineno + 1,
HERE, g.func_code.co_firstlineno + 1))

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,631 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web.http_headers}.
"""
from __future__ import division, absolute_import
import sys
from twisted.python.compat import _PY3
from twisted.trial.unittest import TestCase
from twisted.web.http_headers import _DictHeaders, Headers
class HeadersTests(TestCase):
"""
Tests for L{Headers}.
"""
def test_initializer(self):
"""
The header values passed to L{Headers.__init__} can be retrieved via
L{Headers.getRawHeaders}.
"""
h = Headers({b'Foo': [b'bar']})
self.assertEqual(h.getRawHeaders(b'foo'), [b'bar'])
def test_setRawHeaders(self):
"""
L{Headers.setRawHeaders} sets the header values for the given
header name to the sequence of byte string values.
"""
rawValue = [b"value1", b"value2"]
h = Headers()
h.setRawHeaders(b"test", rawValue)
self.assertTrue(h.hasHeader(b"test"))
self.assertTrue(h.hasHeader(b"Test"))
self.assertEqual(h.getRawHeaders(b"test"), rawValue)
def test_rawHeadersTypeChecking(self):
"""
L{Headers.setRawHeaders} requires values to be of type list.
"""
h = Headers()
self.assertRaises(TypeError, h.setRawHeaders, {b'Foo': b'bar'})
def test_addRawHeader(self):
"""
L{Headers.addRawHeader} adds a new value for a given header.
"""
h = Headers()
h.addRawHeader(b"test", b"lemur")
self.assertEqual(h.getRawHeaders(b"test"), [b"lemur"])
h.addRawHeader(b"test", b"panda")
self.assertEqual(h.getRawHeaders(b"test"), [b"lemur", b"panda"])
def test_getRawHeadersNoDefault(self):
"""
L{Headers.getRawHeaders} returns C{None} if the header is not found and
no default is specified.
"""
self.assertIdentical(Headers().getRawHeaders(b"test"), None)
def test_getRawHeadersDefaultValue(self):
"""
L{Headers.getRawHeaders} returns the specified default value when no
header is found.
"""
h = Headers()
default = object()
self.assertIdentical(h.getRawHeaders(b"test", default), default)
def test_getRawHeaders(self):
"""
L{Headers.getRawHeaders} returns the values which have been set for a
given header.
"""
h = Headers()
h.setRawHeaders(b"test", [b"lemur"])
self.assertEqual(h.getRawHeaders(b"test"), [b"lemur"])
self.assertEqual(h.getRawHeaders(b"Test"), [b"lemur"])
def test_hasHeaderTrue(self):
"""
Check that L{Headers.hasHeader} returns C{True} when the given header
is found.
"""
h = Headers()
h.setRawHeaders(b"test", [b"lemur"])
self.assertTrue(h.hasHeader(b"test"))
self.assertTrue(h.hasHeader(b"Test"))
def test_hasHeaderFalse(self):
"""
L{Headers.hasHeader} returns C{False} when the given header is not
found.
"""
self.assertFalse(Headers().hasHeader(b"test"))
def test_removeHeader(self):
"""
Check that L{Headers.removeHeader} removes the given header.
"""
h = Headers()
h.setRawHeaders(b"foo", [b"lemur"])
self.assertTrue(h.hasHeader(b"foo"))
h.removeHeader(b"foo")
self.assertFalse(h.hasHeader(b"foo"))
h.setRawHeaders(b"bar", [b"panda"])
self.assertTrue(h.hasHeader(b"bar"))
h.removeHeader(b"Bar")
self.assertFalse(h.hasHeader(b"bar"))
def test_removeHeaderDoesntExist(self):
"""
L{Headers.removeHeader} is a no-operation when the specified header is
not found.
"""
h = Headers()
h.removeHeader(b"test")
self.assertEqual(list(h.getAllRawHeaders()), [])
def test_canonicalNameCaps(self):
"""
L{Headers._canonicalNameCaps} returns the canonical capitalization for
the given header.
"""
h = Headers()
self.assertEqual(h._canonicalNameCaps(b"test"), b"Test")
self.assertEqual(h._canonicalNameCaps(b"test-stuff"), b"Test-Stuff")
self.assertEqual(h._canonicalNameCaps(b"content-md5"), b"Content-MD5")
self.assertEqual(h._canonicalNameCaps(b"dnt"), b"DNT")
self.assertEqual(h._canonicalNameCaps(b"etag"), b"ETag")
self.assertEqual(h._canonicalNameCaps(b"p3p"), b"P3P")
self.assertEqual(h._canonicalNameCaps(b"te"), b"TE")
self.assertEqual(h._canonicalNameCaps(b"www-authenticate"),
b"WWW-Authenticate")
self.assertEqual(h._canonicalNameCaps(b"x-xss-protection"),
b"X-XSS-Protection")
def test_getAllRawHeaders(self):
"""
L{Headers.getAllRawHeaders} returns an iterable of (k, v) pairs, where
C{k} is the canonicalized representation of the header name, and C{v}
is a sequence of values.
"""
h = Headers()
h.setRawHeaders(b"test", [b"lemurs"])
h.setRawHeaders(b"www-authenticate", [b"basic aksljdlk="])
allHeaders = set([(k, tuple(v)) for k, v in h.getAllRawHeaders()])
self.assertEqual(allHeaders,
set([(b"WWW-Authenticate", (b"basic aksljdlk=",)),
(b"Test", (b"lemurs",))]))
def test_headersComparison(self):
"""
A L{Headers} instance compares equal to itself and to another
L{Headers} instance with the same values.
"""
first = Headers()
first.setRawHeaders(b"foo", [b"panda"])
second = Headers()
second.setRawHeaders(b"foo", [b"panda"])
third = Headers()
third.setRawHeaders(b"foo", [b"lemur", b"panda"])
self.assertEqual(first, first)
self.assertEqual(first, second)
self.assertNotEqual(first, third)
def test_otherComparison(self):
"""
An instance of L{Headers} does not compare equal to other unrelated
objects.
"""
h = Headers()
self.assertNotEqual(h, ())
self.assertNotEqual(h, object())
self.assertNotEqual(h, b"foo")
def test_repr(self):
"""
The L{repr} of a L{Headers} instance shows the names and values of all
the headers it contains.
"""
foo = b"foo"
bar = b"bar"
baz = b"baz"
self.assertEqual(
repr(Headers({foo: [bar, baz]})),
"Headers({%r: [%r, %r]})" % (foo, bar, baz))
def test_subclassRepr(self):
"""
The L{repr} of an instance of a subclass of L{Headers} uses the name
of the subclass instead of the string C{"Headers"}.
"""
foo = b"foo"
bar = b"bar"
baz = b"baz"
class FunnyHeaders(Headers):
pass
self.assertEqual(
repr(FunnyHeaders({foo: [bar, baz]})),
"FunnyHeaders({%r: [%r, %r]})" % (foo, bar, baz))
def test_copy(self):
"""
L{Headers.copy} creates a new independant copy of an existing
L{Headers} instance, allowing future modifications without impacts
between the copies.
"""
h = Headers()
h.setRawHeaders(b'test', [b'foo'])
i = h.copy()
self.assertEqual(i.getRawHeaders(b'test'), [b'foo'])
h.addRawHeader(b'test', b'bar')
self.assertEqual(i.getRawHeaders(b'test'), [b'foo'])
i.addRawHeader(b'test', b'baz')
self.assertEqual(h.getRawHeaders(b'test'), [b'foo', b'bar'])
class HeaderDictTests(TestCase):
"""
Tests for the backwards compatible C{dict} interface for L{Headers}
provided by L{_DictHeaders}.
"""
def headers(self, **kw):
"""
Create a L{Headers} instance populated with the header name/values
specified by C{kw} and a L{_DictHeaders} wrapped around it and return
them both.
"""
h = Headers()
for k, v in kw.items():
h.setRawHeaders(k.encode('ascii'), v)
return h, _DictHeaders(h)
def test_getItem(self):
"""
L{_DictHeaders.__getitem__} returns a single header for the given name.
"""
headers, wrapper = self.headers(test=[b"lemur"])
self.assertEqual(wrapper[b"test"], b"lemur")
def test_getItemMultiple(self):
"""
L{_DictHeaders.__getitem__} returns only the last header value for a
given name.
"""
headers, wrapper = self.headers(test=[b"lemur", b"panda"])
self.assertEqual(wrapper[b"test"], b"panda")
def test_getItemMissing(self):
"""
L{_DictHeaders.__getitem__} raises L{KeyError} if called with a header
which is not present.
"""
headers, wrapper = self.headers()
exc = self.assertRaises(KeyError, wrapper.__getitem__, b"test")
self.assertEqual(exc.args, (b"test",))
def test_iteration(self):
"""
L{_DictHeaders.__iter__} returns an iterator the elements of which
are the lowercase name of each header present.
"""
headers, wrapper = self.headers(foo=[b"lemur", b"panda"], bar=[b"baz"])
self.assertEqual(set(list(wrapper)), set([b"foo", b"bar"]))
def test_length(self):
"""
L{_DictHeaders.__len__} returns the number of headers present.
"""
headers, wrapper = self.headers()
self.assertEqual(len(wrapper), 0)
headers.setRawHeaders(b"foo", [b"bar"])
self.assertEqual(len(wrapper), 1)
headers.setRawHeaders(b"test", [b"lemur", b"panda"])
self.assertEqual(len(wrapper), 2)
def test_setItem(self):
"""
L{_DictHeaders.__setitem__} sets a single header value for the given
name.
"""
headers, wrapper = self.headers()
wrapper[b"test"] = b"lemur"
self.assertEqual(headers.getRawHeaders(b"test"), [b"lemur"])
def test_setItemOverwrites(self):
"""
L{_DictHeaders.__setitem__} will replace any previous header values for
the given name.
"""
headers, wrapper = self.headers(test=[b"lemur", b"panda"])
wrapper[b"test"] = b"lemur"
self.assertEqual(headers.getRawHeaders(b"test"), [b"lemur"])
def test_delItem(self):
"""
L{_DictHeaders.__delitem__} will remove the header values for the given
name.
"""
headers, wrapper = self.headers(test=[b"lemur"])
del wrapper[b"test"]
self.assertFalse(headers.hasHeader(b"test"))
def test_delItemMissing(self):
"""
L{_DictHeaders.__delitem__} will raise L{KeyError} if the given name is
not present.
"""
headers, wrapper = self.headers()
exc = self.assertRaises(KeyError, wrapper.__delitem__, b"test")
self.assertEqual(exc.args, (b"test",))
def test_keys(self, _method='keys', _requireList=not _PY3):
"""
L{_DictHeaders.keys} will return a list of all present header names.
"""
headers, wrapper = self.headers(test=[b"lemur"], foo=[b"bar"])
keys = getattr(wrapper, _method)()
if _requireList:
self.assertIsInstance(keys, list)
self.assertEqual(set(keys), set([b"foo", b"test"]))
def test_iterkeys(self):
"""
L{_DictHeaders.iterkeys} will return all present header names.
"""
self.test_keys('iterkeys', False)
def test_values(self, _method='values', _requireList=not _PY3):
"""
L{_DictHeaders.values} will return a list of all present header values,
returning only the last value for headers with more than one.
"""
headers, wrapper = self.headers(
foo=[b"lemur"], bar=[b"marmot", b"panda"])
values = getattr(wrapper, _method)()
if _requireList:
self.assertIsInstance(values, list)
self.assertEqual(set(values), set([b"lemur", b"panda"]))
def test_itervalues(self):
"""
L{_DictHeaders.itervalues} will return all present header values,
returning only the last value for headers with more than one.
"""
self.test_values('itervalues', False)
def test_items(self, _method='items', _requireList=not _PY3):
"""
L{_DictHeaders.items} will return a list of all present header names
and values as tuples, returning only the last value for headers with
more than one.
"""
headers, wrapper = self.headers(
foo=[b"lemur"], bar=[b"marmot", b"panda"])
items = getattr(wrapper, _method)()
if _requireList:
self.assertIsInstance(items, list)
self.assertEqual(
set(items), set([(b"foo", b"lemur"), (b"bar", b"panda")]))
def test_iteritems(self):
"""
L{_DictHeaders.iteritems} will return all present header names and
values as tuples, returning only the last value for headers with more
than one.
"""
self.test_items('iteritems', False)
def test_clear(self):
"""
L{_DictHeaders.clear} will remove all headers.
"""
headers, wrapper = self.headers(foo=[b"lemur"], bar=[b"panda"])
wrapper.clear()
self.assertEqual(list(headers.getAllRawHeaders()), [])
def test_copy(self):
"""
L{_DictHeaders.copy} will return a C{dict} with all the same headers
and the last value for each.
"""
headers, wrapper = self.headers(
foo=[b"lemur", b"panda"], bar=[b"marmot"])
duplicate = wrapper.copy()
self.assertEqual(duplicate, {b"foo": b"panda", b"bar": b"marmot"})
def test_get(self):
"""
L{_DictHeaders.get} returns the last value for the given header name.
"""
headers, wrapper = self.headers(foo=[b"lemur", b"panda"])
self.assertEqual(wrapper.get(b"foo"), b"panda")
def test_getMissing(self):
"""
L{_DictHeaders.get} returns C{None} for a header which is not present.
"""
headers, wrapper = self.headers()
self.assertIdentical(wrapper.get(b"foo"), None)
def test_getDefault(self):
"""
L{_DictHeaders.get} returns the last value for the given header name
even when it is invoked with a default value.
"""
headers, wrapper = self.headers(foo=[b"lemur"])
self.assertEqual(wrapper.get(b"foo", b"bar"), b"lemur")
def test_getDefaultMissing(self):
"""
L{_DictHeaders.get} returns the default value specified if asked for a
header which is not present.
"""
headers, wrapper = self.headers()
self.assertEqual(wrapper.get(b"foo", b"bar"), b"bar")
def test_has_key(self):
"""
L{_DictHeaders.has_key} returns C{True} if the given header is present,
C{False} otherwise.
"""
headers, wrapper = self.headers(foo=[b"lemur"])
self.assertTrue(wrapper.has_key(b"foo"))
self.assertFalse(wrapper.has_key(b"bar"))
def test_contains(self):
"""
L{_DictHeaders.__contains__} returns C{True} if the given header is
present, C{False} otherwise.
"""
headers, wrapper = self.headers(foo=[b"lemur"])
self.assertIn(b"foo", wrapper)
self.assertNotIn(b"bar", wrapper)
def test_pop(self):
"""
L{_DictHeaders.pop} returns the last header value associated with the
given header name and removes the header.
"""
headers, wrapper = self.headers(foo=[b"lemur", b"panda"])
self.assertEqual(wrapper.pop(b"foo"), b"panda")
self.assertIdentical(headers.getRawHeaders(b"foo"), None)
def test_popMissing(self):
"""
L{_DictHeaders.pop} raises L{KeyError} if passed a header name which is
not present.
"""
headers, wrapper = self.headers()
self.assertRaises(KeyError, wrapper.pop, b"foo")
def test_popDefault(self):
"""
L{_DictHeaders.pop} returns the last header value associated with the
given header name and removes the header, even if it is supplied with a
default value.
"""
headers, wrapper = self.headers(foo=[b"lemur"])
self.assertEqual(wrapper.pop(b"foo", b"bar"), b"lemur")
self.assertIdentical(headers.getRawHeaders(b"foo"), None)
def test_popDefaultMissing(self):
"""
L{_DictHeaders.pop} returns the default value is asked for a header
name which is not present.
"""
headers, wrapper = self.headers(foo=[b"lemur"])
self.assertEqual(wrapper.pop(b"bar", b"baz"), b"baz")
self.assertEqual(headers.getRawHeaders(b"foo"), [b"lemur"])
def test_popitem(self):
"""
L{_DictHeaders.popitem} returns some header name/value pair.
"""
headers, wrapper = self.headers(foo=[b"lemur", b"panda"])
self.assertEqual(wrapper.popitem(), (b"foo", b"panda"))
self.assertIdentical(headers.getRawHeaders(b"foo"), None)
def test_popitemEmpty(self):
"""
L{_DictHeaders.popitem} raises L{KeyError} if there are no headers
present.
"""
headers, wrapper = self.headers()
self.assertRaises(KeyError, wrapper.popitem)
def test_update(self):
"""
L{_DictHeaders.update} adds the header/value pairs in the C{dict} it is
passed, overriding any existing values for those headers.
"""
headers, wrapper = self.headers(foo=[b"lemur"])
wrapper.update({b"foo": b"panda", b"bar": b"marmot"})
self.assertEqual(headers.getRawHeaders(b"foo"), [b"panda"])
self.assertEqual(headers.getRawHeaders(b"bar"), [b"marmot"])
def test_updateWithKeywords(self):
"""
L{_DictHeaders.update} adds header names given as keyword arguments
with the keyword values as the header value.
"""
headers, wrapper = self.headers(foo=[b"lemur"])
wrapper.update(foo=b"panda", bar=b"marmot")
self.assertEqual(headers.getRawHeaders(b"foo"), [b"panda"])
self.assertEqual(headers.getRawHeaders(b"bar"), [b"marmot"])
if _PY3:
test_updateWithKeywords.skip = "Not yet supported on Python 3; see #6082."
def test_setdefaultMissing(self):
"""
If passed the name of a header which is not present,
L{_DictHeaders.setdefault} sets the value of the given header to the
specified default value and returns it.
"""
headers, wrapper = self.headers(foo=[b"bar"])
self.assertEqual(wrapper.setdefault(b"baz", b"quux"), b"quux")
self.assertEqual(headers.getRawHeaders(b"foo"), [b"bar"])
self.assertEqual(headers.getRawHeaders(b"baz"), [b"quux"])
def test_setdefaultPresent(self):
"""
If passed the name of a header which is present,
L{_DictHeaders.setdefault} makes no changes to the headers and
returns the last value already associated with that header.
"""
headers, wrapper = self.headers(foo=[b"bar", b"baz"])
self.assertEqual(wrapper.setdefault(b"foo", b"quux"), b"baz")
self.assertEqual(headers.getRawHeaders(b"foo"), [b"bar", b"baz"])
def test_setdefaultDefault(self):
"""
If a value is not passed to L{_DictHeaders.setdefault}, C{None} is
used.
"""
# This results in an invalid state for the headers, but maybe some
# application is doing this an intermediate step towards some other
# state. Anyway, it was broken with the old implementation so it's
# broken with the new implementation. Compatibility, for the win.
# -exarkun
headers, wrapper = self.headers()
self.assertIdentical(wrapper.setdefault(b"foo"), None)
self.assertEqual(headers.getRawHeaders(b"foo"), [None])
def test_dictComparison(self):
"""
An instance of L{_DictHeaders} compares equal to a C{dict} which
contains the same header/value pairs. For header names with multiple
values, the last value only is considered.
"""
headers, wrapper = self.headers(foo=[b"lemur"], bar=[b"panda", b"marmot"])
self.assertNotEqual(wrapper, {b"foo": b"lemur", b"bar": b"panda"})
self.assertEqual(wrapper, {b"foo": b"lemur", b"bar": b"marmot"})
def test_otherComparison(self):
"""
An instance of L{_DictHeaders} does not compare equal to other
unrelated objects.
"""
headers, wrapper = self.headers()
self.assertNotEqual(wrapper, ())
self.assertNotEqual(wrapper, object())
self.assertNotEqual(wrapper, b"foo")
if _PY3:
# Python 3 lacks these APIs
del test_iterkeys, test_itervalues, test_iteritems, test_has_key

View file

@ -0,0 +1,634 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web._auth}.
"""
from zope.interface import implements
from zope.interface.verify import verifyObject
from twisted.trial import unittest
from twisted.python.failure import Failure
from twisted.internet.error import ConnectionDone
from twisted.internet.address import IPv4Address
from twisted.cred import error, portal
from twisted.cred.checkers import InMemoryUsernamePasswordDatabaseDontUse
from twisted.cred.checkers import ANONYMOUS, AllowAnonymousAccess
from twisted.cred.credentials import IUsernamePassword
from twisted.web.iweb import ICredentialFactory
from twisted.web.resource import IResource, Resource, getChildForRequest
from twisted.web._auth import basic, digest
from twisted.web._auth.wrapper import HTTPAuthSessionWrapper, UnauthorizedResource
from twisted.web._auth.basic import BasicCredentialFactory
from twisted.web.server import NOT_DONE_YET
from twisted.web.static import Data
from twisted.web.test.test_web import DummyRequest
def b64encode(s):
return s.encode('base64').strip()
class BasicAuthTestsMixin:
"""
L{TestCase} mixin class which defines a number of tests for
L{basic.BasicCredentialFactory}. Because this mixin defines C{setUp}, it
must be inherited before L{TestCase}.
"""
def setUp(self):
self.request = self.makeRequest()
self.realm = 'foo'
self.username = 'dreid'
self.password = 'S3CuR1Ty'
self.credentialFactory = basic.BasicCredentialFactory(self.realm)
def makeRequest(self, method='GET', clientAddress=None):
"""
Create a request object to be passed to
L{basic.BasicCredentialFactory.decode} along with a response value.
Override this in a subclass.
"""
raise NotImplementedError("%r did not implement makeRequest" % (
self.__class__,))
def test_interface(self):
"""
L{BasicCredentialFactory} implements L{ICredentialFactory}.
"""
self.assertTrue(
verifyObject(ICredentialFactory, self.credentialFactory))
def test_usernamePassword(self):
"""
L{basic.BasicCredentialFactory.decode} turns a base64-encoded response
into a L{UsernamePassword} object with a password which reflects the
one which was encoded in the response.
"""
response = b64encode('%s:%s' % (self.username, self.password))
creds = self.credentialFactory.decode(response, self.request)
self.assertTrue(IUsernamePassword.providedBy(creds))
self.assertTrue(creds.checkPassword(self.password))
self.assertFalse(creds.checkPassword(self.password + 'wrong'))
def test_incorrectPadding(self):
"""
L{basic.BasicCredentialFactory.decode} decodes a base64-encoded
response with incorrect padding.
"""
response = b64encode('%s:%s' % (self.username, self.password))
response = response.strip('=')
creds = self.credentialFactory.decode(response, self.request)
self.assertTrue(verifyObject(IUsernamePassword, creds))
self.assertTrue(creds.checkPassword(self.password))
def test_invalidEncoding(self):
"""
L{basic.BasicCredentialFactory.decode} raises L{LoginFailed} if passed
a response which is not base64-encoded.
"""
response = 'x' # one byte cannot be valid base64 text
self.assertRaises(
error.LoginFailed,
self.credentialFactory.decode, response, self.makeRequest())
def test_invalidCredentials(self):
"""
L{basic.BasicCredentialFactory.decode} raises L{LoginFailed} when
passed a response which is not valid base64-encoded text.
"""
response = b64encode('123abc+/')
self.assertRaises(
error.LoginFailed,
self.credentialFactory.decode,
response, self.makeRequest())
class RequestMixin:
def makeRequest(self, method='GET', clientAddress=None):
"""
Create a L{DummyRequest} (change me to create a
L{twisted.web.http.Request} instead).
"""
request = DummyRequest('/')
request.method = method
request.client = clientAddress
return request
class BasicAuthTestCase(RequestMixin, BasicAuthTestsMixin, unittest.TestCase):
"""
Basic authentication tests which use L{twisted.web.http.Request}.
"""
class DigestAuthTestCase(RequestMixin, unittest.TestCase):
"""
Digest authentication tests which use L{twisted.web.http.Request}.
"""
def setUp(self):
"""
Create a DigestCredentialFactory for testing
"""
self.realm = "test realm"
self.algorithm = "md5"
self.credentialFactory = digest.DigestCredentialFactory(
self.algorithm, self.realm)
self.request = self.makeRequest()
def test_decode(self):
"""
L{digest.DigestCredentialFactory.decode} calls the C{decode} method on
L{twisted.cred.digest.DigestCredentialFactory} with the HTTP method and
host of the request.
"""
host = '169.254.0.1'
method = 'GET'
done = [False]
response = object()
def check(_response, _method, _host):
self.assertEqual(response, _response)
self.assertEqual(method, _method)
self.assertEqual(host, _host)
done[0] = True
self.patch(self.credentialFactory.digest, 'decode', check)
req = self.makeRequest(method, IPv4Address('TCP', host, 81))
self.credentialFactory.decode(response, req)
self.assertTrue(done[0])
def test_interface(self):
"""
L{DigestCredentialFactory} implements L{ICredentialFactory}.
"""
self.assertTrue(
verifyObject(ICredentialFactory, self.credentialFactory))
def test_getChallenge(self):
"""
The challenge issued by L{DigestCredentialFactory.getChallenge} must
include C{'qop'}, C{'realm'}, C{'algorithm'}, C{'nonce'}, and
C{'opaque'} keys. The values for the C{'realm'} and C{'algorithm'}
keys must match the values supplied to the factory's initializer.
None of the values may have newlines in them.
"""
challenge = self.credentialFactory.getChallenge(self.request)
self.assertEqual(challenge['qop'], 'auth')
self.assertEqual(challenge['realm'], 'test realm')
self.assertEqual(challenge['algorithm'], 'md5')
self.assertIn('nonce', challenge)
self.assertIn('opaque', challenge)
for v in challenge.values():
self.assertNotIn('\n', v)
def test_getChallengeWithoutClientIP(self):
"""
L{DigestCredentialFactory.getChallenge} can issue a challenge even if
the L{Request} it is passed returns C{None} from C{getClientIP}.
"""
request = self.makeRequest('GET', None)
challenge = self.credentialFactory.getChallenge(request)
self.assertEqual(challenge['qop'], 'auth')
self.assertEqual(challenge['realm'], 'test realm')
self.assertEqual(challenge['algorithm'], 'md5')
self.assertIn('nonce', challenge)
self.assertIn('opaque', challenge)
class UnauthorizedResourceTests(unittest.TestCase):
"""
Tests for L{UnauthorizedResource}.
"""
def test_getChildWithDefault(self):
"""
An L{UnauthorizedResource} is every child of itself.
"""
resource = UnauthorizedResource([])
self.assertIdentical(
resource.getChildWithDefault("foo", None), resource)
self.assertIdentical(
resource.getChildWithDefault("bar", None), resource)
def _unauthorizedRenderTest(self, request):
"""
Render L{UnauthorizedResource} for the given request object and verify
that the response code is I{Unauthorized} and that a I{WWW-Authenticate}
header is set in the response containing a challenge.
"""
resource = UnauthorizedResource([
BasicCredentialFactory('example.com')])
request.render(resource)
self.assertEqual(request.responseCode, 401)
self.assertEqual(
request.responseHeaders.getRawHeaders('www-authenticate'),
['basic realm="example.com"'])
def test_render(self):
"""
L{UnauthorizedResource} renders with a 401 response code and a
I{WWW-Authenticate} header and puts a simple unauthorized message
into the response body.
"""
request = DummyRequest([''])
self._unauthorizedRenderTest(request)
self.assertEqual('Unauthorized', ''.join(request.written))
def test_renderHEAD(self):
"""
The rendering behavior of L{UnauthorizedResource} for a I{HEAD} request
is like its handling of a I{GET} request, but no response body is
written.
"""
request = DummyRequest([''])
request.method = 'HEAD'
self._unauthorizedRenderTest(request)
self.assertEqual('', ''.join(request.written))
def test_renderQuotesRealm(self):
"""
The realm value included in the I{WWW-Authenticate} header set in
the response when L{UnauthorizedResounrce} is rendered has quotes
and backslashes escaped.
"""
resource = UnauthorizedResource([
BasicCredentialFactory('example\\"foo')])
request = DummyRequest([''])
request.render(resource)
self.assertEqual(
request.responseHeaders.getRawHeaders('www-authenticate'),
['basic realm="example\\\\\\"foo"'])
class Realm(object):
"""
A simple L{IRealm} implementation which gives out L{WebAvatar} for any
avatarId.
@type loggedIn: C{int}
@ivar loggedIn: The number of times C{requestAvatar} has been invoked for
L{IResource}.
@type loggedOut: C{int}
@ivar loggedOut: The number of times the logout callback has been invoked.
"""
implements(portal.IRealm)
def __init__(self, avatarFactory):
self.loggedOut = 0
self.loggedIn = 0
self.avatarFactory = avatarFactory
def requestAvatar(self, avatarId, mind, *interfaces):
if IResource in interfaces:
self.loggedIn += 1
return IResource, self.avatarFactory(avatarId), self.logout
raise NotImplementedError()
def logout(self):
self.loggedOut += 1
class HTTPAuthHeaderTests(unittest.TestCase):
"""
Tests for L{HTTPAuthSessionWrapper}.
"""
makeRequest = DummyRequest
def setUp(self):
"""
Create a realm, portal, and L{HTTPAuthSessionWrapper} to use in the tests.
"""
self.username = 'foo bar'
self.password = 'bar baz'
self.avatarContent = "contents of the avatar resource itself"
self.childName = "foo-child"
self.childContent = "contents of the foo child of the avatar"
self.checker = InMemoryUsernamePasswordDatabaseDontUse()
self.checker.addUser(self.username, self.password)
self.avatar = Data(self.avatarContent, 'text/plain')
self.avatar.putChild(
self.childName, Data(self.childContent, 'text/plain'))
self.avatars = {self.username: self.avatar}
self.realm = Realm(self.avatars.get)
self.portal = portal.Portal(self.realm, [self.checker])
self.credentialFactories = []
self.wrapper = HTTPAuthSessionWrapper(
self.portal, self.credentialFactories)
def _authorizedBasicLogin(self, request):
"""
Add an I{basic authorization} header to the given request and then
dispatch it, starting from C{self.wrapper} and returning the resulting
L{IResource}.
"""
authorization = b64encode(self.username + ':' + self.password)
request.headers['authorization'] = 'Basic ' + authorization
return getChildForRequest(self.wrapper, request)
def test_getChildWithDefault(self):
"""
Resource traversal which encounters an L{HTTPAuthSessionWrapper}
results in an L{UnauthorizedResource} instance when the request does
not have the required I{Authorization} headers.
"""
request = self.makeRequest([self.childName])
child = getChildForRequest(self.wrapper, request)
d = request.notifyFinish()
def cbFinished(result):
self.assertEqual(request.responseCode, 401)
d.addCallback(cbFinished)
request.render(child)
return d
def _invalidAuthorizationTest(self, response):
"""
Create a request with the given value as the value of an
I{Authorization} header and perform resource traversal with it,
starting at C{self.wrapper}. Assert that the result is a 401 response
code. Return a L{Deferred} which fires when this is all done.
"""
self.credentialFactories.append(BasicCredentialFactory('example.com'))
request = self.makeRequest([self.childName])
request.headers['authorization'] = response
child = getChildForRequest(self.wrapper, request)
d = request.notifyFinish()
def cbFinished(result):
self.assertEqual(request.responseCode, 401)
d.addCallback(cbFinished)
request.render(child)
return d
def test_getChildWithDefaultUnauthorizedUser(self):
"""
Resource traversal which enouncters an L{HTTPAuthSessionWrapper}
results in an L{UnauthorizedResource} when the request has an
I{Authorization} header with a user which does not exist.
"""
return self._invalidAuthorizationTest('Basic ' + b64encode('foo:bar'))
def test_getChildWithDefaultUnauthorizedPassword(self):
"""
Resource traversal which enouncters an L{HTTPAuthSessionWrapper}
results in an L{UnauthorizedResource} when the request has an
I{Authorization} header with a user which exists and the wrong
password.
"""
return self._invalidAuthorizationTest(
'Basic ' + b64encode(self.username + ':bar'))
def test_getChildWithDefaultUnrecognizedScheme(self):
"""
Resource traversal which enouncters an L{HTTPAuthSessionWrapper}
results in an L{UnauthorizedResource} when the request has an
I{Authorization} header with an unrecognized scheme.
"""
return self._invalidAuthorizationTest('Quux foo bar baz')
def test_getChildWithDefaultAuthorized(self):
"""
Resource traversal which encounters an L{HTTPAuthSessionWrapper}
results in an L{IResource} which renders the L{IResource} avatar
retrieved from the portal when the request has a valid I{Authorization}
header.
"""
self.credentialFactories.append(BasicCredentialFactory('example.com'))
request = self.makeRequest([self.childName])
child = self._authorizedBasicLogin(request)
d = request.notifyFinish()
def cbFinished(ignored):
self.assertEqual(request.written, [self.childContent])
d.addCallback(cbFinished)
request.render(child)
return d
def test_renderAuthorized(self):
"""
Resource traversal which terminates at an L{HTTPAuthSessionWrapper}
and includes correct authentication headers results in the
L{IResource} avatar (not one of its children) retrieved from the
portal being rendered.
"""
self.credentialFactories.append(BasicCredentialFactory('example.com'))
# Request it exactly, not any of its children.
request = self.makeRequest([])
child = self._authorizedBasicLogin(request)
d = request.notifyFinish()
def cbFinished(ignored):
self.assertEqual(request.written, [self.avatarContent])
d.addCallback(cbFinished)
request.render(child)
return d
def test_getChallengeCalledWithRequest(self):
"""
When L{HTTPAuthSessionWrapper} finds an L{ICredentialFactory} to issue
a challenge, it calls the C{getChallenge} method with the request as an
argument.
"""
class DumbCredentialFactory(object):
implements(ICredentialFactory)
scheme = 'dumb'
def __init__(self):
self.requests = []
def getChallenge(self, request):
self.requests.append(request)
return {}
factory = DumbCredentialFactory()
self.credentialFactories.append(factory)
request = self.makeRequest([self.childName])
child = getChildForRequest(self.wrapper, request)
d = request.notifyFinish()
def cbFinished(ignored):
self.assertEqual(factory.requests, [request])
d.addCallback(cbFinished)
request.render(child)
return d
def _logoutTest(self):
"""
Issue a request for an authentication-protected resource using valid
credentials and then return the C{DummyRequest} instance which was
used.
This is a helper for tests about the behavior of the logout
callback.
"""
self.credentialFactories.append(BasicCredentialFactory('example.com'))
class SlowerResource(Resource):
def render(self, request):
return NOT_DONE_YET
self.avatar.putChild(self.childName, SlowerResource())
request = self.makeRequest([self.childName])
child = self._authorizedBasicLogin(request)
request.render(child)
self.assertEqual(self.realm.loggedOut, 0)
return request
def test_logout(self):
"""
The realm's logout callback is invoked after the resource is rendered.
"""
request = self._logoutTest()
request.finish()
self.assertEqual(self.realm.loggedOut, 1)
def test_logoutOnError(self):
"""
The realm's logout callback is also invoked if there is an error
generating the response (for example, if the client disconnects
early).
"""
request = self._logoutTest()
request.processingFailed(
Failure(ConnectionDone("Simulated disconnect")))
self.assertEqual(self.realm.loggedOut, 1)
def test_decodeRaises(self):
"""
Resource traversal which enouncters an L{HTTPAuthSessionWrapper}
results in an L{UnauthorizedResource} when the request has a I{Basic
Authorization} header which cannot be decoded using base64.
"""
self.credentialFactories.append(BasicCredentialFactory('example.com'))
request = self.makeRequest([self.childName])
request.headers['authorization'] = 'Basic decode should fail'
child = getChildForRequest(self.wrapper, request)
self.assertIsInstance(child, UnauthorizedResource)
def test_selectParseResponse(self):
"""
L{HTTPAuthSessionWrapper._selectParseHeader} returns a two-tuple giving
the L{ICredentialFactory} to use to parse the header and a string
containing the portion of the header which remains to be parsed.
"""
basicAuthorization = 'Basic abcdef123456'
self.assertEqual(
self.wrapper._selectParseHeader(basicAuthorization),
(None, None))
factory = BasicCredentialFactory('example.com')
self.credentialFactories.append(factory)
self.assertEqual(
self.wrapper._selectParseHeader(basicAuthorization),
(factory, 'abcdef123456'))
def test_unexpectedDecodeError(self):
"""
Any unexpected exception raised by the credential factory's C{decode}
method results in a 500 response code and causes the exception to be
logged.
"""
class UnexpectedException(Exception):
pass
class BadFactory(object):
scheme = 'bad'
def getChallenge(self, client):
return {}
def decode(self, response, request):
raise UnexpectedException()
self.credentialFactories.append(BadFactory())
request = self.makeRequest([self.childName])
request.headers['authorization'] = 'Bad abc'
child = getChildForRequest(self.wrapper, request)
request.render(child)
self.assertEqual(request.responseCode, 500)
self.assertEqual(len(self.flushLoggedErrors(UnexpectedException)), 1)
def test_unexpectedLoginError(self):
"""
Any unexpected failure from L{Portal.login} results in a 500 response
code and causes the failure to be logged.
"""
class UnexpectedException(Exception):
pass
class BrokenChecker(object):
credentialInterfaces = (IUsernamePassword,)
def requestAvatarId(self, credentials):
raise UnexpectedException()
self.portal.registerChecker(BrokenChecker())
self.credentialFactories.append(BasicCredentialFactory('example.com'))
request = self.makeRequest([self.childName])
child = self._authorizedBasicLogin(request)
request.render(child)
self.assertEqual(request.responseCode, 500)
self.assertEqual(len(self.flushLoggedErrors(UnexpectedException)), 1)
def test_anonymousAccess(self):
"""
Anonymous requests are allowed if a L{Portal} has an anonymous checker
registered.
"""
unprotectedContents = "contents of the unprotected child resource"
self.avatars[ANONYMOUS] = Resource()
self.avatars[ANONYMOUS].putChild(
self.childName, Data(unprotectedContents, 'text/plain'))
self.portal.registerChecker(AllowAnonymousAccess())
self.credentialFactories.append(BasicCredentialFactory('example.com'))
request = self.makeRequest([self.childName])
child = getChildForRequest(self.wrapper, request)
d = request.notifyFinish()
def cbFinished(ignored):
self.assertEqual(request.written, [unprotectedContents])
d.addCallback(cbFinished)
request.render(child)
return d

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,544 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Test for L{twisted.web.proxy}.
"""
from twisted.trial.unittest import TestCase
from twisted.test.proto_helpers import StringTransportWithDisconnection
from twisted.test.proto_helpers import MemoryReactor
from twisted.web.resource import Resource
from twisted.web.server import Site
from twisted.web.proxy import ReverseProxyResource, ProxyClientFactory
from twisted.web.proxy import ProxyClient, ProxyRequest, ReverseProxyRequest
from twisted.web.test.test_web import DummyRequest
class ReverseProxyResourceTestCase(TestCase):
"""
Tests for L{ReverseProxyResource}.
"""
def _testRender(self, uri, expectedURI):
"""
Check that a request pointing at C{uri} produce a new proxy connection,
with the path of this request pointing at C{expectedURI}.
"""
root = Resource()
reactor = MemoryReactor()
resource = ReverseProxyResource("127.0.0.1", 1234, "/path", reactor)
root.putChild('index', resource)
site = Site(root)
transport = StringTransportWithDisconnection()
channel = site.buildProtocol(None)
channel.makeConnection(transport)
# Clear the timeout if the tests failed
self.addCleanup(channel.connectionLost, None)
channel.dataReceived("GET %s HTTP/1.1\r\nAccept: text/html\r\n\r\n" %
(uri,))
# Check that one connection has been created, to the good host/port
self.assertEqual(len(reactor.tcpClients), 1)
self.assertEqual(reactor.tcpClients[0][0], "127.0.0.1")
self.assertEqual(reactor.tcpClients[0][1], 1234)
# Check the factory passed to the connect, and its given path
factory = reactor.tcpClients[0][2]
self.assertIsInstance(factory, ProxyClientFactory)
self.assertEqual(factory.rest, expectedURI)
self.assertEqual(factory.headers["host"], "127.0.0.1:1234")
def test_render(self):
"""
Test that L{ReverseProxyResource.render} initiates a connection to the
given server with a L{ProxyClientFactory} as parameter.
"""
return self._testRender("/index", "/path")
def test_renderWithQuery(self):
"""
Test that L{ReverseProxyResource.render} passes query parameters to the
created factory.
"""
return self._testRender("/index?foo=bar", "/path?foo=bar")
def test_getChild(self):
"""
The L{ReverseProxyResource.getChild} method should return a resource
instance with the same class as the originating resource, forward
port, host, and reactor values, and update the path value with the
value passed.
"""
reactor = MemoryReactor()
resource = ReverseProxyResource("127.0.0.1", 1234, "/path", reactor)
child = resource.getChild('foo', None)
# The child should keep the same class
self.assertIsInstance(child, ReverseProxyResource)
self.assertEqual(child.path, "/path/foo")
self.assertEqual(child.port, 1234)
self.assertEqual(child.host, "127.0.0.1")
self.assertIdentical(child.reactor, resource.reactor)
def test_getChildWithSpecial(self):
"""
The L{ReverseProxyResource} return by C{getChild} has a path which has
already been quoted.
"""
resource = ReverseProxyResource("127.0.0.1", 1234, "/path")
child = resource.getChild(' /%', None)
self.assertEqual(child.path, "/path/%20%2F%25")
class DummyChannel(object):
"""
A dummy HTTP channel, that does nothing but holds a transport and saves
connection lost.
@ivar transport: the transport used by the client.
@ivar lostReason: the reason saved at connection lost.
"""
def __init__(self, transport):
"""
Hold a reference to the transport.
"""
self.transport = transport
self.lostReason = None
def connectionLost(self, reason):
"""
Keep track of the connection lost reason.
"""
self.lostReason = reason
class ProxyClientTestCase(TestCase):
"""
Tests for L{ProxyClient}.
"""
def _parseOutHeaders(self, content):
"""
Parse the headers out of some web content.
@param content: Bytes received from a web server.
@return: A tuple of (requestLine, headers, body). C{headers} is a dict
of headers, C{requestLine} is the first line (e.g. "POST /foo ...")
and C{body} is whatever is left.
"""
headers, body = content.split('\r\n\r\n')
headers = headers.split('\r\n')
requestLine = headers.pop(0)
return (
requestLine, dict(header.split(': ') for header in headers), body)
def makeRequest(self, path):
"""
Make a dummy request object for the URL path.
@param path: A URL path, beginning with a slash.
@return: A L{DummyRequest}.
"""
return DummyRequest(path)
def makeProxyClient(self, request, method="GET", headers=None,
requestBody=""):
"""
Make a L{ProxyClient} object used for testing.
@param request: The request to use.
@param method: The HTTP method to use, GET by default.
@param headers: The HTTP headers to use expressed as a dict. If not
provided, defaults to {'accept': 'text/html'}.
@param requestBody: The body of the request. Defaults to the empty
string.
@return: A L{ProxyClient}
"""
if headers is None:
headers = {"accept": "text/html"}
path = '/' + request.postpath
return ProxyClient(
method, path, 'HTTP/1.0', headers, requestBody, request)
def connectProxy(self, proxyClient):
"""
Connect a proxy client to a L{StringTransportWithDisconnection}.
@param proxyClient: A L{ProxyClient}.
@return: The L{StringTransportWithDisconnection}.
"""
clientTransport = StringTransportWithDisconnection()
clientTransport.protocol = proxyClient
proxyClient.makeConnection(clientTransport)
return clientTransport
def assertForwardsHeaders(self, proxyClient, requestLine, headers):
"""
Assert that C{proxyClient} sends C{headers} when it connects.
@param proxyClient: A L{ProxyClient}.
@param requestLine: The request line we expect to be sent.
@param headers: A dict of headers we expect to be sent.
@return: If the assertion is successful, return the request body as
bytes.
"""
self.connectProxy(proxyClient)
requestContent = proxyClient.transport.value()
receivedLine, receivedHeaders, body = self._parseOutHeaders(
requestContent)
self.assertEqual(receivedLine, requestLine)
self.assertEqual(receivedHeaders, headers)
return body
def makeResponseBytes(self, code, message, headers, body):
lines = ["HTTP/1.0 %d %s" % (code, message)]
for header, values in headers:
for value in values:
lines.append("%s: %s" % (header, value))
lines.extend(['', body])
return '\r\n'.join(lines)
def assertForwardsResponse(self, request, code, message, headers, body):
"""
Assert that C{request} has forwarded a response from the server.
@param request: A L{DummyRequest}.
@param code: The expected HTTP response code.
@param message: The expected HTTP message.
@param headers: The expected HTTP headers.
@param body: The expected response body.
"""
self.assertEqual(request.responseCode, code)
self.assertEqual(request.responseMessage, message)
receivedHeaders = list(request.responseHeaders.getAllRawHeaders())
receivedHeaders.sort()
expectedHeaders = headers[:]
expectedHeaders.sort()
self.assertEqual(receivedHeaders, expectedHeaders)
self.assertEqual(''.join(request.written), body)
def _testDataForward(self, code, message, headers, body, method="GET",
requestBody="", loseConnection=True):
"""
Build a fake proxy connection, and send C{data} over it, checking that
it's forwarded to the originating request.
"""
request = self.makeRequest('foo')
client = self.makeProxyClient(
request, method, {'accept': 'text/html'}, requestBody)
receivedBody = self.assertForwardsHeaders(
client, '%s /foo HTTP/1.0' % (method,),
{'connection': 'close', 'accept': 'text/html'})
self.assertEqual(receivedBody, requestBody)
# Fake an answer
client.dataReceived(
self.makeResponseBytes(code, message, headers, body))
# Check that the response data has been forwarded back to the original
# requester.
self.assertForwardsResponse(request, code, message, headers, body)
# Check that when the response is done, the request is finished.
if loseConnection:
client.transport.loseConnection()
# Even if we didn't call loseConnection, the transport should be
# disconnected. This lets us not rely on the server to close our
# sockets for us.
self.assertFalse(client.transport.connected)
self.assertEqual(request.finished, 1)
def test_forward(self):
"""
When connected to the server, L{ProxyClient} should send the saved
request, with modifications of the headers, and then forward the result
to the parent request.
"""
return self._testDataForward(
200, "OK", [("Foo", ["bar", "baz"])], "Some data\r\n")
def test_postData(self):
"""
Try to post content in the request, and check that the proxy client
forward the body of the request.
"""
return self._testDataForward(
200, "OK", [("Foo", ["bar"])], "Some data\r\n", "POST", "Some content")
def test_statusWithMessage(self):
"""
If the response contains a status with a message, it should be
forwarded to the parent request with all the information.
"""
return self._testDataForward(
404, "Not Found", [], "")
def test_contentLength(self):
"""
If the response contains a I{Content-Length} header, the inbound
request object should still only have C{finish} called on it once.
"""
data = "foo bar baz"
return self._testDataForward(
200, "OK", [("Content-Length", [str(len(data))])], data)
def test_losesConnection(self):
"""
If the response contains a I{Content-Length} header, the outgoing
connection is closed when all response body data has been received.
"""
data = "foo bar baz"
return self._testDataForward(
200, "OK", [("Content-Length", [str(len(data))])], data,
loseConnection=False)
def test_headersCleanups(self):
"""
The headers given at initialization should be modified:
B{proxy-connection} should be removed if present, and B{connection}
should be added.
"""
client = ProxyClient('GET', '/foo', 'HTTP/1.0',
{"accept": "text/html", "proxy-connection": "foo"}, '', None)
self.assertEqual(client.headers,
{"accept": "text/html", "connection": "close"})
def test_keepaliveNotForwarded(self):
"""
The proxy doesn't really know what to do with keepalive things from
the remote server, so we stomp over any keepalive header we get from
the client.
"""
headers = {
"accept": "text/html",
'keep-alive': '300',
'connection': 'keep-alive',
}
expectedHeaders = headers.copy()
expectedHeaders['connection'] = 'close'
del expectedHeaders['keep-alive']
client = ProxyClient('GET', '/foo', 'HTTP/1.0', headers, '', None)
self.assertForwardsHeaders(
client, 'GET /foo HTTP/1.0', expectedHeaders)
def test_defaultHeadersOverridden(self):
"""
L{server.Request} within the proxy sets certain response headers by
default. When we get these headers back from the remote server, the
defaults are overridden rather than simply appended.
"""
request = self.makeRequest('foo')
request.responseHeaders.setRawHeaders('server', ['old-bar'])
request.responseHeaders.setRawHeaders('date', ['old-baz'])
request.responseHeaders.setRawHeaders('content-type', ["old/qux"])
client = self.makeProxyClient(request, headers={'accept': 'text/html'})
self.connectProxy(client)
headers = {
'Server': ['bar'],
'Date': ['2010-01-01'],
'Content-Type': ['application/x-baz'],
}
client.dataReceived(
self.makeResponseBytes(200, "OK", headers.items(), ''))
self.assertForwardsResponse(
request, 200, 'OK', headers.items(), '')
class ProxyClientFactoryTestCase(TestCase):
"""
Tests for L{ProxyClientFactory}.
"""
def test_connectionFailed(self):
"""
Check that L{ProxyClientFactory.clientConnectionFailed} produces
a B{501} response to the parent request.
"""
request = DummyRequest(['foo'])
factory = ProxyClientFactory('GET', '/foo', 'HTTP/1.0',
{"accept": "text/html"}, '', request)
factory.clientConnectionFailed(None, None)
self.assertEqual(request.responseCode, 501)
self.assertEqual(request.responseMessage, "Gateway error")
self.assertEqual(
list(request.responseHeaders.getAllRawHeaders()),
[("Content-Type", ["text/html"])])
self.assertEqual(
''.join(request.written),
"<H1>Could not connect</H1>")
self.assertEqual(request.finished, 1)
def test_buildProtocol(self):
"""
L{ProxyClientFactory.buildProtocol} should produce a L{ProxyClient}
with the same values of attributes (with updates on the headers).
"""
factory = ProxyClientFactory('GET', '/foo', 'HTTP/1.0',
{"accept": "text/html"}, 'Some data',
None)
proto = factory.buildProtocol(None)
self.assertIsInstance(proto, ProxyClient)
self.assertEqual(proto.command, 'GET')
self.assertEqual(proto.rest, '/foo')
self.assertEqual(proto.data, 'Some data')
self.assertEqual(proto.headers,
{"accept": "text/html", "connection": "close"})
class ProxyRequestTestCase(TestCase):
"""
Tests for L{ProxyRequest}.
"""
def _testProcess(self, uri, expectedURI, method="GET", data=""):
"""
Build a request pointing at C{uri}, and check that a proxied request
is created, pointing a C{expectedURI}.
"""
transport = StringTransportWithDisconnection()
channel = DummyChannel(transport)
reactor = MemoryReactor()
request = ProxyRequest(channel, False, reactor)
request.gotLength(len(data))
request.handleContentChunk(data)
request.requestReceived(method, 'http://example.com%s' % (uri,),
'HTTP/1.0')
self.assertEqual(len(reactor.tcpClients), 1)
self.assertEqual(reactor.tcpClients[0][0], "example.com")
self.assertEqual(reactor.tcpClients[0][1], 80)
factory = reactor.tcpClients[0][2]
self.assertIsInstance(factory, ProxyClientFactory)
self.assertEqual(factory.command, method)
self.assertEqual(factory.version, 'HTTP/1.0')
self.assertEqual(factory.headers, {'host': 'example.com'})
self.assertEqual(factory.data, data)
self.assertEqual(factory.rest, expectedURI)
self.assertEqual(factory.father, request)
def test_process(self):
"""
L{ProxyRequest.process} should create a connection to the given server,
with a L{ProxyClientFactory} as connection factory, with the correct
parameters:
- forward comment, version and data values
- update headers with the B{host} value
- remove the host from the URL
- pass the request as parent request
"""
return self._testProcess("/foo/bar", "/foo/bar")
def test_processWithoutTrailingSlash(self):
"""
If the incoming request doesn't contain a slash,
L{ProxyRequest.process} should add one when instantiating
L{ProxyClientFactory}.
"""
return self._testProcess("", "/")
def test_processWithData(self):
"""
L{ProxyRequest.process} should be able to retrieve request body and
to forward it.
"""
return self._testProcess(
"/foo/bar", "/foo/bar", "POST", "Some content")
def test_processWithPort(self):
"""
Check that L{ProxyRequest.process} correctly parse port in the incoming
URL, and create a outgoing connection with this port.
"""
transport = StringTransportWithDisconnection()
channel = DummyChannel(transport)
reactor = MemoryReactor()
request = ProxyRequest(channel, False, reactor)
request.gotLength(0)
request.requestReceived('GET', 'http://example.com:1234/foo/bar',
'HTTP/1.0')
# That should create one connection, with the port parsed from the URL
self.assertEqual(len(reactor.tcpClients), 1)
self.assertEqual(reactor.tcpClients[0][0], "example.com")
self.assertEqual(reactor.tcpClients[0][1], 1234)
class DummyFactory(object):
"""
A simple holder for C{host} and C{port} information.
"""
def __init__(self, host, port):
self.host = host
self.port = port
class ReverseProxyRequestTestCase(TestCase):
"""
Tests for L{ReverseProxyRequest}.
"""
def test_process(self):
"""
L{ReverseProxyRequest.process} should create a connection to its
factory host/port, using a L{ProxyClientFactory} instantiated with the
correct parameters, and particulary set the B{host} header to the
factory host.
"""
transport = StringTransportWithDisconnection()
channel = DummyChannel(transport)
reactor = MemoryReactor()
request = ReverseProxyRequest(channel, False, reactor)
request.factory = DummyFactory("example.com", 1234)
request.gotLength(0)
request.requestReceived('GET', '/foo/bar', 'HTTP/1.0')
# Check that one connection has been created, to the good host/port
self.assertEqual(len(reactor.tcpClients), 1)
self.assertEqual(reactor.tcpClients[0][0], "example.com")
self.assertEqual(reactor.tcpClients[0][1], 1234)
# Check the factory passed to the connect, and its headers
factory = reactor.tcpClients[0][2]
self.assertIsInstance(factory, ProxyClientFactory)
self.assertEqual(factory.headers, {'host': 'example.com'})

View file

@ -0,0 +1,261 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web.resource}.
"""
from twisted.trial.unittest import TestCase
from twisted.web.error import UnsupportedMethod
from twisted.web.resource import (
NOT_FOUND, FORBIDDEN, Resource, ErrorPage, NoResource, ForbiddenResource,
getChildForRequest)
from twisted.web.test.requesthelper import DummyRequest
class ErrorPageTests(TestCase):
"""
Tests for L{ErrorPage}, L{NoResource}, and L{ForbiddenResource}.
"""
errorPage = ErrorPage
noResource = NoResource
forbiddenResource = ForbiddenResource
def test_getChild(self):
"""
The C{getChild} method of L{ErrorPage} returns the L{ErrorPage} it is
called on.
"""
page = self.errorPage(321, "foo", "bar")
self.assertIdentical(page.getChild(b"name", object()), page)
def _pageRenderingTest(self, page, code, brief, detail):
request = DummyRequest([b''])
template = (
u"\n"
u"<html>\n"
u" <head><title>%s - %s</title></head>\n"
u" <body>\n"
u" <h1>%s</h1>\n"
u" <p>%s</p>\n"
u" </body>\n"
u"</html>\n")
expected = template % (code, brief, brief, detail)
self.assertEqual(
page.render(request), expected.encode('utf-8'))
self.assertEqual(request.responseCode, code)
self.assertEqual(
request.outgoingHeaders,
{b'content-type': b'text/html; charset=utf-8'})
def test_errorPageRendering(self):
"""
L{ErrorPage.render} returns a C{bytes} describing the error defined by
the response code and message passed to L{ErrorPage.__init__}. It also
uses that response code to set the response code on the L{Request}
passed in.
"""
code = 321
brief = "brief description text"
detail = "much longer text might go here"
page = self.errorPage(code, brief, detail)
self._pageRenderingTest(page, code, brief, detail)
def test_noResourceRendering(self):
"""
L{NoResource} sets the HTTP I{NOT FOUND} code.
"""
detail = "long message"
page = self.noResource(detail)
self._pageRenderingTest(page, NOT_FOUND, "No Such Resource", detail)
def test_forbiddenResourceRendering(self):
"""
L{ForbiddenResource} sets the HTTP I{FORBIDDEN} code.
"""
detail = "longer message"
page = self.forbiddenResource(detail)
self._pageRenderingTest(page, FORBIDDEN, "Forbidden Resource", detail)
class DynamicChild(Resource):
"""
A L{Resource} to be created on the fly by L{DynamicChildren}.
"""
def __init__(self, path, request):
Resource.__init__(self)
self.path = path
self.request = request
class DynamicChildren(Resource):
"""
A L{Resource} with dynamic children.
"""
def getChild(self, path, request):
return DynamicChild(path, request)
class BytesReturnedRenderable(Resource):
"""
A L{Resource} with minimal capabilities to render a response.
"""
def __init__(self, response):
"""
@param response: A C{bytes} object giving the value to return from
C{render_GET}.
"""
Resource.__init__(self)
self._response = response
def render_GET(self, request):
"""
Render a response to a I{GET} request by returning a short byte string
to be written by the server.
"""
return self._response
class ImplicitAllowedMethods(Resource):
"""
A L{Resource} which implicitly defines its allowed methods by defining
renderers to handle them.
"""
def render_GET(self, request):
pass
def render_PUT(self, request):
pass
class ResourceTests(TestCase):
"""
Tests for L{Resource}.
"""
def test_staticChildren(self):
"""
L{Resource.putChild} adds a I{static} child to the resource. That child
is returned from any call to L{Resource.getChildWithDefault} for the
child's path.
"""
resource = Resource()
child = Resource()
sibling = Resource()
resource.putChild(b"foo", child)
resource.putChild(b"bar", sibling)
self.assertIdentical(
child, resource.getChildWithDefault(b"foo", DummyRequest([])))
def test_dynamicChildren(self):
"""
L{Resource.getChildWithDefault} delegates to L{Resource.getChild} when
the requested path is not associated with any static child.
"""
path = b"foo"
request = DummyRequest([])
resource = DynamicChildren()
child = resource.getChildWithDefault(path, request)
self.assertIsInstance(child, DynamicChild)
self.assertEqual(child.path, path)
self.assertIdentical(child.request, request)
def test_defaultHEAD(self):
"""
When not otherwise overridden, L{Resource.render} treats a I{HEAD}
request as if it were a I{GET} request.
"""
expected = b"insert response here"
request = DummyRequest([])
request.method = b'HEAD'
resource = BytesReturnedRenderable(expected)
self.assertEqual(expected, resource.render(request))
def test_explicitAllowedMethods(self):
"""
The L{UnsupportedMethod} raised by L{Resource.render} for an unsupported
request method has a C{allowedMethods} attribute set to the value of the
C{allowedMethods} attribute of the L{Resource}, if it has one.
"""
expected = [b'GET', b'HEAD', b'PUT']
resource = Resource()
resource.allowedMethods = expected
request = DummyRequest([])
request.method = b'FICTIONAL'
exc = self.assertRaises(UnsupportedMethod, resource.render, request)
self.assertEqual(set(expected), set(exc.allowedMethods))
def test_implicitAllowedMethods(self):
"""
The L{UnsupportedMethod} raised by L{Resource.render} for an unsupported
request method has a C{allowedMethods} attribute set to a list of the
methods supported by the L{Resource}, as determined by the
I{render_}-prefixed methods which it defines, if C{allowedMethods} is
not explicitly defined by the L{Resource}.
"""
expected = set([b'GET', b'HEAD', b'PUT'])
resource = ImplicitAllowedMethods()
request = DummyRequest([])
request.method = b'FICTIONAL'
exc = self.assertRaises(UnsupportedMethod, resource.render, request)
self.assertEqual(expected, set(exc.allowedMethods))
class GetChildForRequestTests(TestCase):
"""
Tests for L{getChildForRequest}.
"""
def test_exhaustedPostPath(self):
"""
L{getChildForRequest} returns whatever resource has been reached by the
time the request's C{postpath} is empty.
"""
request = DummyRequest([])
resource = Resource()
result = getChildForRequest(resource, request)
self.assertIdentical(resource, result)
def test_leafResource(self):
"""
L{getChildForRequest} returns the first resource it encounters with a
C{isLeaf} attribute set to C{True}.
"""
request = DummyRequest([b"foo", b"bar"])
resource = Resource()
resource.isLeaf = True
result = getChildForRequest(resource, request)
self.assertIdentical(resource, result)
def test_postPathToPrePath(self):
"""
As path segments from the request are traversed, they are taken from
C{postpath} and put into C{prepath}.
"""
request = DummyRequest([b"foo", b"bar"])
root = Resource()
child = Resource()
child.isLeaf = True
root.putChild(b"foo", child)
self.assertIdentical(child, getChildForRequest(root, request))
self.assertEqual(request.prepath, [b"foo"])
self.assertEqual(request.postpath, [b"bar"])

View file

@ -0,0 +1,70 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web.script}.
"""
import os
from twisted.trial.unittest import TestCase
from twisted.web.http import NOT_FOUND
from twisted.web.script import ResourceScriptDirectory, PythonScript
from twisted.web.test._util import _render
from twisted.web.test.test_web import DummyRequest
class ResourceScriptDirectoryTests(TestCase):
"""
Tests for L{ResourceScriptDirectory}.
"""
def test_render(self):
"""
L{ResourceScriptDirectory.render} sets the HTTP response code to I{NOT
FOUND}.
"""
resource = ResourceScriptDirectory(self.mktemp())
request = DummyRequest([''])
d = _render(resource, request)
def cbRendered(ignored):
self.assertEqual(request.responseCode, NOT_FOUND)
d.addCallback(cbRendered)
return d
def test_notFoundChild(self):
"""
L{ResourceScriptDirectory.getChild} returns a resource which renders an
response with the HTTP I{NOT FOUND} status code if the indicated child
does not exist as an entry in the directory used to initialized the
L{ResourceScriptDirectory}.
"""
path = self.mktemp()
os.makedirs(path)
resource = ResourceScriptDirectory(path)
request = DummyRequest(['foo'])
child = resource.getChild("foo", request)
d = _render(child, request)
def cbRendered(ignored):
self.assertEqual(request.responseCode, NOT_FOUND)
d.addCallback(cbRendered)
return d
class PythonScriptTests(TestCase):
"""
Tests for L{PythonScript}.
"""
def test_notFoundRender(self):
"""
If the source file a L{PythonScript} is initialized with doesn't exist,
L{PythonScript.render} sets the HTTP response code to I{NOT FOUND}.
"""
resource = PythonScript(self.mktemp(), None)
request = DummyRequest([''])
d = _render(resource, request)
def cbRendered(ignored):
self.assertEqual(request.responseCode, NOT_FOUND)
d.addCallback(cbRendered)
return d

View file

@ -0,0 +1,114 @@
#
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
#
"""Test SOAP support."""
try:
import SOAPpy
except ImportError:
SOAPpy = None
class SOAPPublisher: pass
else:
from twisted.web import soap
SOAPPublisher = soap.SOAPPublisher
from twisted.trial import unittest
from twisted.web import server, error
from twisted.internet import reactor, defer
class Test(SOAPPublisher):
def soap_add(self, a, b):
return a + b
def soap_kwargs(self, a=1, b=2):
return a + b
soap_kwargs.useKeywords=True
def soap_triple(self, string, num):
return [string, num, None]
def soap_struct(self):
return SOAPpy.structType({"a": "c"})
def soap_defer(self, x):
return defer.succeed(x)
def soap_deferFail(self):
return defer.fail(ValueError())
def soap_fail(self):
raise RuntimeError
def soap_deferFault(self):
return defer.fail(ValueError())
def soap_complex(self):
return {"a": ["b", "c", 12, []], "D": "foo"}
def soap_dict(self, map, key):
return map[key]
class SOAPTestCase(unittest.TestCase):
def setUp(self):
self.publisher = Test()
self.p = reactor.listenTCP(0, server.Site(self.publisher),
interface="127.0.0.1")
self.port = self.p.getHost().port
def tearDown(self):
return self.p.stopListening()
def proxy(self):
return soap.Proxy("http://127.0.0.1:%d/" % self.port)
def testResults(self):
inputOutput = [
("add", (2, 3), 5),
("defer", ("a",), "a"),
("dict", ({"a": 1}, "a"), 1),
("triple", ("a", 1), ["a", 1, None])]
dl = []
for meth, args, outp in inputOutput:
d = self.proxy().callRemote(meth, *args)
d.addCallback(self.assertEqual, outp)
dl.append(d)
# SOAPpy kinda blows.
d = self.proxy().callRemote('complex')
d.addCallback(lambda result: result._asdict())
d.addCallback(self.assertEqual, {"a": ["b", "c", 12, []], "D": "foo"})
dl.append(d)
# We now return to our regularly scheduled program, already in progress.
return defer.DeferredList(dl, fireOnOneErrback=True)
def testMethodNotFound(self):
"""
Check that a non existing method return error 500.
"""
d = self.proxy().callRemote('doesntexist')
self.assertFailure(d, error.Error)
def cb(err):
self.assertEqual(int(err.status), 500)
d.addCallback(cb)
return d
def testLookupFunction(self):
"""
Test lookupFunction method on publisher, to see available remote
methods.
"""
self.assertTrue(self.publisher.lookupFunction("add"))
self.assertTrue(self.publisher.lookupFunction("fail"))
self.assertFalse(self.publisher.lookupFunction("foobar"))
if not SOAPpy:
SOAPTestCase.skip = "SOAPpy not installed"

View file

@ -0,0 +1,139 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web._stan} portion of the L{twisted.web.template}
implementation.
"""
from twisted.web.template import Comment, CDATA, CharRef, Tag
from twisted.trial.unittest import TestCase
def proto(*a, **kw):
"""
Produce a new tag for testing.
"""
return Tag('hello')(*a, **kw)
class TestTag(TestCase):
"""
Tests for L{Tag}.
"""
def test_fillSlots(self):
"""
L{Tag.fillSlots} returns self.
"""
tag = proto()
self.assertIdentical(tag, tag.fillSlots(test='test'))
def test_cloneShallow(self):
"""
L{Tag.clone} copies all attributes and children of a tag, including its
render attribute. If the shallow flag is C{False}, that's where it
stops.
"""
innerList = ["inner list"]
tag = proto("How are you", innerList,
hello="world", render="aSampleMethod")
tag.fillSlots(foo='bar')
tag.filename = "foo/bar"
tag.lineNumber = 6
tag.columnNumber = 12
clone = tag.clone(deep=False)
self.assertEqual(clone.attributes['hello'], 'world')
self.assertNotIdentical(clone.attributes, tag.attributes)
self.assertEqual(clone.children, ["How are you", innerList])
self.assertNotIdentical(clone.children, tag.children)
self.assertIdentical(clone.children[1], innerList)
self.assertEqual(tag.slotData, clone.slotData)
self.assertNotIdentical(tag.slotData, clone.slotData)
self.assertEqual(clone.filename, "foo/bar")
self.assertEqual(clone.lineNumber, 6)
self.assertEqual(clone.columnNumber, 12)
self.assertEqual(clone.render, "aSampleMethod")
def test_cloneDeep(self):
"""
L{Tag.clone} copies all attributes and children of a tag, including its
render attribute. In its normal operating mode (where the deep flag is
C{True}, as is the default), it will clone all sub-lists and sub-tags.
"""
innerTag = proto("inner")
innerList = ["inner list"]
tag = proto("How are you", innerTag, innerList,
hello="world", render="aSampleMethod")
tag.fillSlots(foo='bar')
tag.filename = "foo/bar"
tag.lineNumber = 6
tag.columnNumber = 12
clone = tag.clone()
self.assertEqual(clone.attributes['hello'], 'world')
self.assertNotIdentical(clone.attributes, tag.attributes)
self.assertNotIdentical(clone.children, tag.children)
# sanity check
self.assertIdentical(tag.children[1], innerTag)
# clone should have sub-clone
self.assertNotIdentical(clone.children[1], innerTag)
# sanity check
self.assertIdentical(tag.children[2], innerList)
# clone should have sub-clone
self.assertNotIdentical(clone.children[2], innerList)
self.assertEqual(tag.slotData, clone.slotData)
self.assertNotIdentical(tag.slotData, clone.slotData)
self.assertEqual(clone.filename, "foo/bar")
self.assertEqual(clone.lineNumber, 6)
self.assertEqual(clone.columnNumber, 12)
self.assertEqual(clone.render, "aSampleMethod")
def test_clear(self):
"""
L{Tag.clear} removes all children from a tag, but leaves its attributes
in place.
"""
tag = proto("these are", "children", "cool", andSoIs='this-attribute')
tag.clear()
self.assertEqual(tag.children, [])
self.assertEqual(tag.attributes, {'andSoIs': 'this-attribute'})
def test_suffix(self):
"""
L{Tag.__call__} accepts Python keywords with a suffixed underscore as
the DOM attribute of that literal suffix.
"""
proto = Tag('div')
tag = proto()
tag(class_='a')
self.assertEqual(tag.attributes, {'class': 'a'})
def test_commentRepr(self):
"""
L{Comment.__repr__} returns a value which makes it easy to see what's in
the comment.
"""
self.assertEqual(repr(Comment(u"hello there")),
"Comment(u'hello there')")
def test_cdataRepr(self):
"""
L{CDATA.__repr__} returns a value which makes it easy to see what's in
the comment.
"""
self.assertEqual(repr(CDATA(u"test data")),
"CDATA(u'test data')")
def test_charrefRepr(self):
"""
L{CharRef.__repr__} returns a value which makes it easy to see what
character is referred to.
"""
snowman = ord(u"\N{SNOWMAN}")
self.assertEqual(repr(CharRef(snowman)), "CharRef(9731)")

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,196 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web.tap}.
"""
import os, stat
from twisted.python.usage import UsageError
from twisted.python.filepath import FilePath
from twisted.internet.interfaces import IReactorUNIX
from twisted.internet import reactor
from twisted.python.threadpool import ThreadPool
from twisted.trial.unittest import TestCase
from twisted.application import strports
from twisted.web.server import Site
from twisted.web.static import Data, File
from twisted.web.distrib import ResourcePublisher, UserDirectory
from twisted.web.wsgi import WSGIResource
from twisted.web.tap import Options, makePersonalServerFactory, makeService
from twisted.web.twcgi import CGIScript
from twisted.web.script import PythonScript
from twisted.spread.pb import PBServerFactory
application = object()
class ServiceTests(TestCase):
"""
Tests for the service creation APIs in L{twisted.web.tap}.
"""
def _pathOption(self):
"""
Helper for the I{--path} tests which creates a directory and creates
an L{Options} object which uses that directory as its static
filesystem root.
@return: A two-tuple of a L{FilePath} referring to the directory and
the value associated with the C{'root'} key in the L{Options}
instance after parsing a I{--path} option.
"""
path = FilePath(self.mktemp())
path.makedirs()
options = Options()
options.parseOptions(['--path', path.path])
root = options['root']
return path, root
def test_path(self):
"""
The I{--path} option causes L{Options} to create a root resource
which serves responses from the specified path.
"""
path, root = self._pathOption()
self.assertIsInstance(root, File)
self.assertEqual(root.path, path.path)
def test_cgiProcessor(self):
"""
The I{--path} option creates a root resource which serves a
L{CGIScript} instance for any child with the C{".cgi"} extension.
"""
path, root = self._pathOption()
path.child("foo.cgi").setContent("")
self.assertIsInstance(root.getChild("foo.cgi", None), CGIScript)
def test_epyProcessor(self):
"""
The I{--path} option creates a root resource which serves a
L{PythonScript} instance for any child with the C{".epy"} extension.
"""
path, root = self._pathOption()
path.child("foo.epy").setContent("")
self.assertIsInstance(root.getChild("foo.epy", None), PythonScript)
def test_rpyProcessor(self):
"""
The I{--path} option creates a root resource which serves the
C{resource} global defined by the Python source in any child with
the C{".rpy"} extension.
"""
path, root = self._pathOption()
path.child("foo.rpy").setContent(
"from twisted.web.static import Data\n"
"resource = Data('content', 'major/minor')\n")
child = root.getChild("foo.rpy", None)
self.assertIsInstance(child, Data)
self.assertEqual(child.data, 'content')
self.assertEqual(child.type, 'major/minor')
def test_makePersonalServerFactory(self):
"""
L{makePersonalServerFactory} returns a PB server factory which has
as its root object a L{ResourcePublisher}.
"""
# The fact that this pile of objects can actually be used somehow is
# verified by twisted.web.test.test_distrib.
site = Site(Data("foo bar", "text/plain"))
serverFactory = makePersonalServerFactory(site)
self.assertIsInstance(serverFactory, PBServerFactory)
self.assertIsInstance(serverFactory.root, ResourcePublisher)
self.assertIdentical(serverFactory.root.site, site)
def test_personalServer(self):
"""
The I{--personal} option to L{makeService} causes it to return a
service which will listen on the server address given by the I{--port}
option.
"""
port = self.mktemp()
options = Options()
options.parseOptions(['--port', 'unix:' + port, '--personal'])
service = makeService(options)
service.startService()
self.addCleanup(service.stopService)
self.assertTrue(os.path.exists(port))
self.assertTrue(stat.S_ISSOCK(os.stat(port).st_mode))
if not IReactorUNIX.providedBy(reactor):
test_personalServer.skip = (
"The reactor does not support UNIX domain sockets")
def test_defaultPersonalPath(self):
"""
If the I{--port} option not specified but the I{--personal} option is,
L{Options} defaults the port to C{UserDirectory.userSocketName} in the
user's home directory.
"""
options = Options()
options.parseOptions(['--personal'])
path = os.path.expanduser(
os.path.join('~', UserDirectory.userSocketName))
self.assertEqual(
strports.parse(options['port'], None)[:2],
('UNIX', (path, None)))
if not IReactorUNIX.providedBy(reactor):
test_defaultPersonalPath.skip = (
"The reactor does not support UNIX domain sockets")
def test_defaultPort(self):
"""
If the I{--port} option is not specified, L{Options} defaults the port
to C{8080}.
"""
options = Options()
options.parseOptions([])
self.assertEqual(
strports.parse(options['port'], None)[:2],
('TCP', (8080, None)))
def test_wsgi(self):
"""
The I{--wsgi} option takes the fully-qualifed Python name of a WSGI
application object and creates a L{WSGIResource} at the root which
serves that application.
"""
options = Options()
options.parseOptions(['--wsgi', __name__ + '.application'])
root = options['root']
self.assertTrue(root, WSGIResource)
self.assertIdentical(root._reactor, reactor)
self.assertTrue(isinstance(root._threadpool, ThreadPool))
self.assertIdentical(root._application, application)
# The threadpool should start and stop with the reactor.
self.assertFalse(root._threadpool.started)
reactor.fireSystemEvent('startup')
self.assertTrue(root._threadpool.started)
self.assertFalse(root._threadpool.joined)
reactor.fireSystemEvent('shutdown')
self.assertTrue(root._threadpool.joined)
def test_invalidApplication(self):
"""
If I{--wsgi} is given an invalid name, L{Options.parseOptions}
raises L{UsageError}.
"""
options = Options()
for name in [__name__ + '.nosuchthing', 'foo.']:
exc = self.assertRaises(
UsageError, options.parseOptions, ['--wsgi', name])
self.assertEqual(str(exc), "No such WSGI application: %r" % (name,))

View file

@ -0,0 +1,820 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web.template}
"""
from cStringIO import StringIO
from zope.interface.verify import verifyObject
from twisted.internet.defer import succeed, gatherResults
from twisted.python.filepath import FilePath
from twisted.trial.unittest import TestCase
from twisted.trial.util import suppress as SUPPRESS
from twisted.web.template import (
Element, TagLoader, renderer, tags, XMLFile, XMLString)
from twisted.web.iweb import ITemplateLoader
from twisted.web.error import (FlattenerError, MissingTemplateLoader,
MissingRenderMethod)
from twisted.web.template import renderElement
from twisted.web._element import UnexposedMethodError
from twisted.web.test._util import FlattenTestCase
from twisted.web.test.test_web import DummyRequest
from twisted.web.server import NOT_DONE_YET
_xmlFileSuppress = SUPPRESS(category=DeprecationWarning,
message="Passing filenames or file objects to XMLFile is "
"deprecated since Twisted 12.1. Pass a FilePath instead.")
class TagFactoryTests(TestCase):
"""
Tests for L{_TagFactory} through the publicly-exposed L{tags} object.
"""
def test_lookupTag(self):
"""
HTML tags can be retrieved through C{tags}.
"""
tag = tags.a
self.assertEqual(tag.tagName, "a")
def test_lookupHTML5Tag(self):
"""
Twisted supports the latest and greatest HTML tags from the HTML5
specification.
"""
tag = tags.video
self.assertEqual(tag.tagName, "video")
def test_lookupTransparentTag(self):
"""
To support transparent inclusion in templates, there is a special tag,
the transparent tag, which has no name of its own but is accessed
through the "transparent" attribute.
"""
tag = tags.transparent
self.assertEqual(tag.tagName, "")
def test_lookupInvalidTag(self):
"""
Invalid tags which are not part of HTML cause AttributeErrors when
accessed through C{tags}.
"""
self.assertRaises(AttributeError, getattr, tags, "invalid")
def test_lookupXMP(self):
"""
As a special case, the <xmp> tag is simply not available through
C{tags} or any other part of the templating machinery.
"""
self.assertRaises(AttributeError, getattr, tags, "xmp")
class ElementTests(TestCase):
"""
Tests for the awesome new L{Element} class.
"""
def test_missingTemplateLoader(self):
"""
L{Element.render} raises L{MissingTemplateLoader} if the C{loader}
attribute is C{None}.
"""
element = Element()
err = self.assertRaises(MissingTemplateLoader, element.render, None)
self.assertIdentical(err.element, element)
def test_missingTemplateLoaderRepr(self):
"""
A L{MissingTemplateLoader} instance can be repr()'d without error.
"""
class PrettyReprElement(Element):
def __repr__(self):
return 'Pretty Repr Element'
self.assertIn('Pretty Repr Element',
repr(MissingTemplateLoader(PrettyReprElement())))
def test_missingRendererMethod(self):
"""
When called with the name which is not associated with a render method,
L{Element.lookupRenderMethod} raises L{MissingRenderMethod}.
"""
element = Element()
err = self.assertRaises(
MissingRenderMethod, element.lookupRenderMethod, "foo")
self.assertIdentical(err.element, element)
self.assertEqual(err.renderName, "foo")
def test_missingRenderMethodRepr(self):
"""
A L{MissingRenderMethod} instance can be repr()'d without error.
"""
class PrettyReprElement(Element):
def __repr__(self):
return 'Pretty Repr Element'
s = repr(MissingRenderMethod(PrettyReprElement(),
'expectedMethod'))
self.assertIn('Pretty Repr Element', s)
self.assertIn('expectedMethod', s)
def test_definedRenderer(self):
"""
When called with the name of a defined render method,
L{Element.lookupRenderMethod} returns that render method.
"""
class ElementWithRenderMethod(Element):
@renderer
def foo(self, request, tag):
return "bar"
foo = ElementWithRenderMethod().lookupRenderMethod("foo")
self.assertEqual(foo(None, None), "bar")
def test_render(self):
"""
L{Element.render} loads a document from the C{loader} attribute and
returns it.
"""
class TemplateLoader(object):
def load(self):
return "result"
class StubElement(Element):
loader = TemplateLoader()
element = StubElement()
self.assertEqual(element.render(None), "result")
def test_misuseRenderer(self):
"""
If the L{renderer} decorator is called without any arguments, it will
raise a comprehensible exception.
"""
te = self.assertRaises(TypeError, renderer)
self.assertEqual(str(te),
"expose() takes at least 1 argument (0 given)")
def test_renderGetDirectlyError(self):
"""
Called directly, without a default, L{renderer.get} raises
L{UnexposedMethodError} when it cannot find a renderer.
"""
self.assertRaises(UnexposedMethodError, renderer.get, None,
"notARenderer")
class XMLFileReprTests(TestCase):
"""
Tests for L{twisted.web.template.XMLFile}'s C{__repr__}.
"""
def test_filePath(self):
"""
An L{XMLFile} with a L{FilePath} returns a useful repr().
"""
path = FilePath("/tmp/fake.xml")
self.assertEqual('<XMLFile of %r>' % (path,), repr(XMLFile(path)))
def test_filename(self):
"""
An L{XMLFile} with a filename returns a useful repr().
"""
fname = "/tmp/fake.xml"
self.assertEqual('<XMLFile of %r>' % (fname,), repr(XMLFile(fname)))
test_filename.suppress = [_xmlFileSuppress]
def test_file(self):
"""
An L{XMLFile} with a file object returns a useful repr().
"""
fobj = StringIO("not xml")
self.assertEqual('<XMLFile of %r>' % (fobj,), repr(XMLFile(fobj)))
test_file.suppress = [_xmlFileSuppress]
class XMLLoaderTestsMixin(object):
"""
@ivar templateString: Simple template to use to excercise the loaders.
@ivar deprecatedUse: C{True} if this use of L{XMLFile} is deprecated and
should emit a C{DeprecationWarning}.
"""
loaderFactory = None
templateString = '<p>Hello, world.</p>'
def test_load(self):
"""
Verify that the loader returns a tag with the correct children.
"""
loader = self.loaderFactory()
tag, = loader.load()
warnings = self.flushWarnings(offendingFunctions=[self.loaderFactory])
if self.deprecatedUse:
self.assertEqual(len(warnings), 1)
self.assertEqual(warnings[0]['category'], DeprecationWarning)
self.assertEqual(
warnings[0]['message'],
"Passing filenames or file objects to XMLFile is "
"deprecated since Twisted 12.1. Pass a FilePath instead.")
else:
self.assertEqual(len(warnings), 0)
self.assertEqual(tag.tagName, 'p')
self.assertEqual(tag.children, [u'Hello, world.'])
def test_loadTwice(self):
"""
If {load()} can be called on a loader twice the result should be the
same.
"""
loader = self.loaderFactory()
tags1 = loader.load()
tags2 = loader.load()
self.assertEqual(tags1, tags2)
test_loadTwice.suppress = [_xmlFileSuppress]
class XMLStringLoaderTests(TestCase, XMLLoaderTestsMixin):
"""
Tests for L{twisted.web.template.XMLString}
"""
deprecatedUse = False
def loaderFactory(self):
"""
@return: an L{XMLString} constructed with C{self.templateString}.
"""
return XMLString(self.templateString)
class XMLFileWithFilePathTests(TestCase, XMLLoaderTestsMixin):
"""
Tests for L{twisted.web.template.XMLFile}'s L{FilePath} support.
"""
deprecatedUse = False
def loaderFactory(self):
"""
@return: an L{XMLString} constructed with a L{FilePath} pointing to a
file that contains C{self.templateString}.
"""
fp = FilePath(self.mktemp())
fp.setContent(self.templateString)
return XMLFile(fp)
class XMLFileWithFileTests(TestCase, XMLLoaderTestsMixin):
"""
Tests for L{twisted.web.template.XMLFile}'s deprecated file object support.
"""
deprecatedUse = True
def loaderFactory(self):
"""
@return: an L{XMLString} constructed with a file object that contains
C{self.templateString}.
"""
return XMLFile(StringIO(self.templateString))
class XMLFileWithFilenameTests(TestCase, XMLLoaderTestsMixin):
"""
Tests for L{twisted.web.template.XMLFile}'s deprecated filename support.
"""
deprecatedUse = True
def loaderFactory(self):
"""
@return: an L{XMLString} constructed with a filename that points to a
file containing C{self.templateString}.
"""
fp = FilePath(self.mktemp())
fp.setContent(self.templateString)
return XMLFile(fp.path)
class FlattenIntegrationTests(FlattenTestCase):
"""
Tests for integration between L{Element} and
L{twisted.web._flatten.flatten}.
"""
def test_roundTrip(self):
"""
Given a series of parsable XML strings, verify that
L{twisted.web._flatten.flatten} will flatten the L{Element} back to the
input when sent on a round trip.
"""
fragments = [
"<p>Hello, world.</p>",
"<p><!-- hello, world --></p>",
"<p><![CDATA[Hello, world.]]></p>",
'<test1 xmlns:test2="urn:test2">'
'<test2:test3></test2:test3></test1>',
'<test1 xmlns="urn:test2"><test3></test3></test1>',
'<p>\xe2\x98\x83</p>',
]
deferreds = [
self.assertFlattensTo(Element(loader=XMLString(xml)), xml)
for xml in fragments]
return gatherResults(deferreds)
def test_entityConversion(self):
"""
When flattening an HTML entity, it should flatten out to the utf-8
representation if possible.
"""
element = Element(loader=XMLString('<p>&#9731;</p>'))
return self.assertFlattensTo(element, '<p>\xe2\x98\x83</p>')
def test_missingTemplateLoader(self):
"""
Rendering a Element without a loader attribute raises the appropriate
exception.
"""
return self.assertFlatteningRaises(Element(), MissingTemplateLoader)
def test_missingRenderMethod(self):
"""
Flattening an L{Element} with a C{loader} which has a tag with a render
directive fails with L{FlattenerError} if there is no available render
method to satisfy that directive.
"""
element = Element(loader=XMLString("""
<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
t:render="unknownMethod" />
"""))
return self.assertFlatteningRaises(element, MissingRenderMethod)
def test_transparentRendering(self):
"""
A C{transparent} element should be eliminated from the DOM and rendered as
only its children.
"""
element = Element(loader=XMLString(
'<t:transparent '
'xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
'Hello, world.'
'</t:transparent>'
))
return self.assertFlattensTo(element, "Hello, world.")
def test_attrRendering(self):
"""
An Element with an attr tag renders the vaule of its attr tag as an
attribute of its containing tag.
"""
element = Element(loader=XMLString(
'<a xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
'<t:attr name="href">http://example.com</t:attr>'
'Hello, world.'
'</a>'
))
return self.assertFlattensTo(element,
'<a href="http://example.com">Hello, world.</a>')
def test_errorToplevelAttr(self):
"""
A template with a toplevel C{attr} tag will not load; it will raise
L{AssertionError} if you try.
"""
self.assertRaises(
AssertionError,
XMLString,
"""<t:attr
xmlns:t='http://twistedmatrix.com/ns/twisted.web.template/0.1'
name='something'
>hello</t:attr>
""")
def test_errorUnnamedAttr(self):
"""
A template with an C{attr} tag with no C{name} attribute will not load;
it will raise L{AssertionError} if you try.
"""
self.assertRaises(
AssertionError,
XMLString,
"""<html><t:attr
xmlns:t='http://twistedmatrix.com/ns/twisted.web.template/0.1'
>hello</t:attr></html>""")
def test_lenientPrefixBehavior(self):
"""
If the parser sees a prefix it doesn't recognize on an attribute, it
will pass it on through to serialization.
"""
theInput = (
'<hello:world hello:sample="testing" '
'xmlns:hello="http://made-up.example.com/ns/not-real">'
'This is a made-up tag.</hello:world>')
element = Element(loader=XMLString(theInput))
self.assertFlattensTo(element, theInput)
def test_deferredRendering(self):
"""
An Element with a render method which returns a Deferred will render
correctly.
"""
class RenderfulElement(Element):
@renderer
def renderMethod(self, request, tag):
return succeed("Hello, world.")
element = RenderfulElement(loader=XMLString("""
<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
t:render="renderMethod">
Goodbye, world.
</p>
"""))
return self.assertFlattensTo(element, "Hello, world.")
def test_loaderClassAttribute(self):
"""
If there is a non-None loader attribute on the class of an Element
instance but none on the instance itself, the class attribute is used.
"""
class SubElement(Element):
loader = XMLString("<p>Hello, world.</p>")
return self.assertFlattensTo(SubElement(), "<p>Hello, world.</p>")
def test_directiveRendering(self):
"""
An Element with a valid render directive has that directive invoked and
the result added to the output.
"""
renders = []
class RenderfulElement(Element):
@renderer
def renderMethod(self, request, tag):
renders.append((self, request))
return tag("Hello, world.")
element = RenderfulElement(loader=XMLString("""
<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
t:render="renderMethod" />
"""))
return self.assertFlattensTo(element, "<p>Hello, world.</p>")
def test_directiveRenderingOmittingTag(self):
"""
An Element with a render method which omits the containing tag
successfully removes that tag from the output.
"""
class RenderfulElement(Element):
@renderer
def renderMethod(self, request, tag):
return "Hello, world."
element = RenderfulElement(loader=XMLString("""
<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
t:render="renderMethod">
Goodbye, world.
</p>
"""))
return self.assertFlattensTo(element, "Hello, world.")
def test_elementContainingStaticElement(self):
"""
An Element which is returned by the render method of another Element is
rendered properly.
"""
class RenderfulElement(Element):
@renderer
def renderMethod(self, request, tag):
return tag(Element(
loader=XMLString("<em>Hello, world.</em>")))
element = RenderfulElement(loader=XMLString("""
<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
t:render="renderMethod" />
"""))
return self.assertFlattensTo(element, "<p><em>Hello, world.</em></p>")
def test_elementUsingSlots(self):
"""
An Element which is returned by the render method of another Element is
rendered properly.
"""
class RenderfulElement(Element):
@renderer
def renderMethod(self, request, tag):
return tag.fillSlots(test2='world.')
element = RenderfulElement(loader=XMLString(
'<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"'
' t:render="renderMethod">'
'<t:slot name="test1" default="Hello, " />'
'<t:slot name="test2" />'
'</p>'
))
return self.assertFlattensTo(element, "<p>Hello, world.</p>")
def test_elementContainingDynamicElement(self):
"""
Directives in the document factory of a Element returned from a render
method of another Element are satisfied from the correct object: the
"inner" Element.
"""
class OuterElement(Element):
@renderer
def outerMethod(self, request, tag):
return tag(InnerElement(loader=XMLString("""
<t:ignored
xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
t:render="innerMethod" />
""")))
class InnerElement(Element):
@renderer
def innerMethod(self, request, tag):
return "Hello, world."
element = OuterElement(loader=XMLString("""
<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1"
t:render="outerMethod" />
"""))
return self.assertFlattensTo(element, "<p>Hello, world.</p>")
def test_sameLoaderTwice(self):
"""
Rendering the output of a loader, or even the same element, should
return different output each time.
"""
sharedLoader = XMLString(
'<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
'<t:transparent t:render="classCounter" /> '
'<t:transparent t:render="instanceCounter" />'
'</p>')
class DestructiveElement(Element):
count = 0
instanceCount = 0
loader = sharedLoader
@renderer
def classCounter(self, request, tag):
DestructiveElement.count += 1
return tag(str(DestructiveElement.count))
@renderer
def instanceCounter(self, request, tag):
self.instanceCount += 1
return tag(str(self.instanceCount))
e1 = DestructiveElement()
e2 = DestructiveElement()
self.assertFlattensImmediately(e1, "<p>1 1</p>")
self.assertFlattensImmediately(e1, "<p>2 2</p>")
self.assertFlattensImmediately(e2, "<p>3 1</p>")
class TagLoaderTests(FlattenTestCase):
"""
Tests for L{TagLoader}.
"""
def setUp(self):
self.loader = TagLoader(tags.i('test'))
def test_interface(self):
"""
An instance of L{TagLoader} provides L{ITemplateLoader}.
"""
self.assertTrue(verifyObject(ITemplateLoader, self.loader))
def test_loadsList(self):
"""
L{TagLoader.load} returns a list, per L{ITemplateLoader}.
"""
self.assertIsInstance(self.loader.load(), list)
def test_flatten(self):
"""
L{TagLoader} can be used in an L{Element}, and flattens as the tag used
to construct the L{TagLoader} would flatten.
"""
e = Element(self.loader)
self.assertFlattensImmediately(e, '<i>test</i>')
class TestElement(Element):
"""
An L{Element} that can be rendered successfully.
"""
loader = XMLString(
'<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
'Hello, world.'
'</p>')
class TestFailureElement(Element):
"""
An L{Element} that can be used in place of L{FailureElement} to verify
that L{renderElement} can render failures properly.
"""
loader = XMLString(
'<p xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">'
'I failed.'
'</p>')
def __init__(self, failure, loader=None):
self.failure = failure
class FailingElement(Element):
"""
An element that raises an exception when rendered.
"""
def render(self, request):
a = 42
b = 0
return a // b
class FakeSite(object):
"""
A minimal L{Site} object that we can use to test displayTracebacks
"""
displayTracebacks = False
class TestRenderElement(TestCase):
"""
Test L{renderElement}
"""
def setUp(self):
"""
Set up a common L{DummyRequest} and L{FakeSite}.
"""
self.request = DummyRequest([""])
self.request.site = FakeSite()
def test_simpleRender(self):
"""
L{renderElement} returns NOT_DONE_YET and eventually
writes the rendered L{Element} to the request before finishing the
request.
"""
element = TestElement()
d = self.request.notifyFinish()
def check(_):
self.assertEqual(
"".join(self.request.written),
"<!DOCTYPE html>\n"
"<p>Hello, world.</p>")
self.assertTrue(self.request.finished)
d.addCallback(check)
self.assertIdentical(NOT_DONE_YET, renderElement(self.request, element))
return d
def test_simpleFailure(self):
"""
L{renderElement} handles failures by writing a minimal
error message to the request and finishing it.
"""
element = FailingElement()
d = self.request.notifyFinish()
def check(_):
flushed = self.flushLoggedErrors(FlattenerError)
self.assertEqual(len(flushed), 1)
self.assertEqual(
"".join(self.request.written),
('<!DOCTYPE html>\n'
'<div style="font-size:800%;'
'background-color:#FFF;'
'color:#F00'
'">An error occurred while rendering the response.</div>'))
self.assertTrue(self.request.finished)
d.addCallback(check)
self.assertIdentical(NOT_DONE_YET, renderElement(self.request, element))
return d
def test_simpleFailureWithTraceback(self):
"""
L{renderElement} will render a traceback when rendering of
the element fails and our site is configured to display tracebacks.
"""
self.request.site.displayTracebacks = True
element = FailingElement()
d = self.request.notifyFinish()
def check(_):
flushed = self.flushLoggedErrors(FlattenerError)
self.assertEqual(len(flushed), 1)
self.assertEqual(
"".join(self.request.written),
"<!DOCTYPE html>\n<p>I failed.</p>")
self.assertTrue(self.request.finished)
d.addCallback(check)
renderElement(self.request, element, _failElement=TestFailureElement)
return d
def test_nonDefaultDoctype(self):
"""
L{renderElement} will write the doctype string specified by the
doctype keyword argument.
"""
element = TestElement()
d = self.request.notifyFinish()
def check(_):
self.assertEqual(
"".join(self.request.written),
('<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">\n'
'<p>Hello, world.</p>'))
d.addCallback(check)
renderElement(
self.request,
element,
doctype=(
'<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"'
' "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">'))
return d
def test_noneDoctype(self):
"""
L{renderElement} will not write out a doctype if the doctype keyword
argument is C{None}.
"""
element = TestElement()
d = self.request.notifyFinish()
def check(_):
self.assertEqual(
"".join(self.request.written),
'<p>Hello, world.</p>')
d.addCallback(check)
renderElement(self.request, element, doctype=None)
return d

View file

@ -0,0 +1,472 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web.util}.
"""
from twisted.python.failure import Failure
from twisted.trial.unittest import TestCase
from twisted.internet import defer
from twisted.web import util
from twisted.web.error import FlattenerError
from twisted.web.util import (
redirectTo, _SourceLineElement,
_SourceFragmentElement, _FrameElement, _StackElement,
FailureElement, formatFailure, DeferredResource, htmlIndent)
from twisted.web.http import FOUND
from twisted.web.server import Request
from twisted.web.template import TagLoader, flattenString, tags
from twisted.web import resource
from twisted.web.test.requesthelper import DummyChannel, DummyRequest
class RedirectToTestCase(TestCase):
"""
Tests for L{redirectTo}.
"""
def test_headersAndCode(self):
"""
L{redirectTo} will set the C{Location} and C{Content-Type} headers on
its request, and set the response code to C{FOUND}, so the browser will
be redirected.
"""
request = Request(DummyChannel(), True)
request.method = 'GET'
targetURL = "http://target.example.com/4321"
redirectTo(targetURL, request)
self.assertEqual(request.code, FOUND)
self.assertEqual(
request.responseHeaders.getRawHeaders('location'), [targetURL])
self.assertEqual(
request.responseHeaders.getRawHeaders('content-type'),
['text/html; charset=utf-8'])
def test_redirectToUnicodeURL(self) :
"""
L{redirectTo} will raise TypeError if unicode object is passed in URL
"""
request = Request(DummyChannel(), True)
request.method = 'GET'
targetURL = u'http://target.example.com/4321'
self.assertRaises(TypeError, redirectTo, targetURL, request)
class FailureElementTests(TestCase):
"""
Tests for L{FailureElement} and related helpers which can render a
L{Failure} as an HTML string.
"""
def setUp(self):
"""
Create a L{Failure} which can be used by the rendering tests.
"""
def lineNumberProbeAlsoBroken():
message = "This is a problem"
raise Exception(message)
# Figure out the line number from which the exception will be raised.
self.base = lineNumberProbeAlsoBroken.func_code.co_firstlineno + 1
try:
lineNumberProbeAlsoBroken()
except:
self.failure = Failure(captureVars=True)
self.frame = self.failure.frames[-1]
def test_sourceLineElement(self):
"""
L{_SourceLineElement} renders a source line and line number.
"""
element = _SourceLineElement(
TagLoader(tags.div(
tags.span(render="lineNumber"),
tags.span(render="sourceLine"))),
50, " print 'hello'")
d = flattenString(None, element)
expected = (
u"<div><span>50</span><span>"
u" \N{NO-BREAK SPACE} \N{NO-BREAK SPACE}print 'hello'</span></div>")
d.addCallback(
self.assertEqual, expected.encode('utf-8'))
return d
def test_sourceFragmentElement(self):
"""
L{_SourceFragmentElement} renders source lines at and around the line
number indicated by a frame object.
"""
element = _SourceFragmentElement(
TagLoader(tags.div(
tags.span(render="lineNumber"),
tags.span(render="sourceLine"),
render="sourceLines")),
self.frame)
source = [
u' \N{NO-BREAK SPACE} \N{NO-BREAK SPACE}message = '
u'"This is a problem"',
u' \N{NO-BREAK SPACE} \N{NO-BREAK SPACE}raise Exception(message)',
u'# Figure out the line number from which the exception will be '
u'raised.',
]
d = flattenString(None, element)
d.addCallback(
self.assertEqual,
''.join([
'<div class="snippet%sLine"><span>%d</span><span>%s</span>'
'</div>' % (
["", "Highlight"][lineNumber == 1],
self.base + lineNumber,
(u" \N{NO-BREAK SPACE}" * 4 + sourceLine).encode(
'utf-8'))
for (lineNumber, sourceLine)
in enumerate(source)]))
return d
def test_frameElementFilename(self):
"""
The I{filename} renderer of L{_FrameElement} renders the filename
associated with the frame object used to initialize the
L{_FrameElement}.
"""
element = _FrameElement(
TagLoader(tags.span(render="filename")),
self.frame)
d = flattenString(None, element)
d.addCallback(
# __file__ differs depending on whether an up-to-date .pyc file
# already existed.
self.assertEqual, "<span>" + __file__.rstrip('c') + "</span>")
return d
def test_frameElementLineNumber(self):
"""
The I{lineNumber} renderer of L{_FrameElement} renders the line number
associated with the frame object used to initialize the
L{_FrameElement}.
"""
element = _FrameElement(
TagLoader(tags.span(render="lineNumber")),
self.frame)
d = flattenString(None, element)
d.addCallback(
self.assertEqual, "<span>" + str(self.base + 1) + "</span>")
return d
def test_frameElementFunction(self):
"""
The I{function} renderer of L{_FrameElement} renders the line number
associated with the frame object used to initialize the
L{_FrameElement}.
"""
element = _FrameElement(
TagLoader(tags.span(render="function")),
self.frame)
d = flattenString(None, element)
d.addCallback(
self.assertEqual, "<span>lineNumberProbeAlsoBroken</span>")
return d
def test_frameElementSource(self):
"""
The I{source} renderer of L{_FrameElement} renders the source code near
the source filename/line number associated with the frame object used to
initialize the L{_FrameElement}.
"""
element = _FrameElement(None, self.frame)
renderer = element.lookupRenderMethod("source")
tag = tags.div()
result = renderer(None, tag)
self.assertIsInstance(result, _SourceFragmentElement)
self.assertIdentical(result.frame, self.frame)
self.assertEqual([tag], result.loader.load())
def test_stackElement(self):
"""
The I{frames} renderer of L{_StackElement} renders each stack frame in
the list of frames used to initialize the L{_StackElement}.
"""
element = _StackElement(None, self.failure.frames[:2])
renderer = element.lookupRenderMethod("frames")
tag = tags.div()
result = renderer(None, tag)
self.assertIsInstance(result, list)
self.assertIsInstance(result[0], _FrameElement)
self.assertIdentical(result[0].frame, self.failure.frames[0])
self.assertIsInstance(result[1], _FrameElement)
self.assertIdentical(result[1].frame, self.failure.frames[1])
# They must not share the same tag object.
self.assertNotEqual(result[0].loader.load(), result[1].loader.load())
self.assertEqual(2, len(result))
def test_failureElementTraceback(self):
"""
The I{traceback} renderer of L{FailureElement} renders the failure's
stack frames using L{_StackElement}.
"""
element = FailureElement(self.failure)
renderer = element.lookupRenderMethod("traceback")
tag = tags.div()
result = renderer(None, tag)
self.assertIsInstance(result, _StackElement)
self.assertIdentical(result.stackFrames, self.failure.frames)
self.assertEqual([tag], result.loader.load())
def test_failureElementType(self):
"""
The I{type} renderer of L{FailureElement} renders the failure's
exception type.
"""
element = FailureElement(
self.failure, TagLoader(tags.span(render="type")))
d = flattenString(None, element)
d.addCallback(
self.assertEqual, "<span>exceptions.Exception</span>")
return d
def test_failureElementValue(self):
"""
The I{value} renderer of L{FailureElement} renders the value's exception
value.
"""
element = FailureElement(
self.failure, TagLoader(tags.span(render="value")))
d = flattenString(None, element)
d.addCallback(
self.assertEqual, '<span>This is a problem</span>')
return d
class FormatFailureTests(TestCase):
"""
Tests for L{twisted.web.util.formatFailure} which returns an HTML string
representing the L{Failure} instance passed to it.
"""
def test_flattenerError(self):
"""
If there is an error flattening the L{Failure} instance,
L{formatFailure} raises L{FlattenerError}.
"""
self.assertRaises(FlattenerError, formatFailure, object())
def test_returnsBytes(self):
"""
The return value of L{formatFailure} is a C{str} instance (not a
C{unicode} instance) with numeric character references for any non-ASCII
characters meant to appear in the output.
"""
try:
raise Exception("Fake bug")
except:
result = formatFailure(Failure())
self.assertIsInstance(result, str)
self.assertTrue(all(ord(ch) < 128 for ch in result))
# Indentation happens to rely on NO-BREAK SPACE
self.assertIn("&#160;", result)
class DeprecatedHTMLHelpers(TestCase):
"""
The various HTML generation helper APIs in L{twisted.web.util} are
deprecated.
"""
def _htmlHelperDeprecationTest(self, functionName):
"""
Helper method which asserts that using the name indicated by
C{functionName} from the L{twisted.web.util} module emits a deprecation
warning.
"""
getattr(util, functionName)
warnings = self.flushWarnings([self._htmlHelperDeprecationTest])
self.assertEqual(warnings[0]['category'], DeprecationWarning)
self.assertEqual(
warnings[0]['message'],
"twisted.web.util.%s was deprecated in Twisted 12.1.0: "
"See twisted.web.template." % (functionName,))
def test_htmlrepr(self):
"""
L{twisted.web.util.htmlrepr} is deprecated.
"""
self._htmlHelperDeprecationTest("htmlrepr")
def test_saferepr(self):
"""
L{twisted.web.util.saferepr} is deprecated.
"""
self._htmlHelperDeprecationTest("saferepr")
def test_htmlUnknown(self):
"""
L{twisted.web.util.htmlUnknown} is deprecated.
"""
self._htmlHelperDeprecationTest("htmlUnknown")
def test_htmlDict(self):
"""
L{twisted.web.util.htmlDict} is deprecated.
"""
self._htmlHelperDeprecationTest("htmlDict")
def test_htmlList(self):
"""
L{twisted.web.util.htmlList} is deprecated.
"""
self._htmlHelperDeprecationTest("htmlList")
def test_htmlInst(self):
"""
L{twisted.web.util.htmlInst} is deprecated.
"""
self._htmlHelperDeprecationTest("htmlInst")
def test_htmlString(self):
"""
L{twisted.web.util.htmlString} is deprecated.
"""
self._htmlHelperDeprecationTest("htmlString")
def test_htmlIndent(self):
"""
L{twisted.web.util.htmlIndent} is deprecated.
"""
self._htmlHelperDeprecationTest("htmlIndent")
def test_htmlFunc(self):
"""
L{twisted.web.util.htmlFunc} is deprecated.
"""
self._htmlHelperDeprecationTest("htmlFunc")
def test_htmlReprTypes(self):
"""
L{twisted.web.util.htmlReprTypes} is deprecated.
"""
self._htmlHelperDeprecationTest("htmlReprTypes")
def test_stylesheet(self):
"""
L{twisted.web.util.stylesheet} is deprecated.
"""
self._htmlHelperDeprecationTest("stylesheet")
class SDResource(resource.Resource):
def __init__(self,default):
self.default = default
def getChildWithDefault(self, name, request):
d = defer.succeed(self.default)
resource = util.DeferredResource(d)
return resource.getChildWithDefault(name, request)
class DeferredResourceTests(TestCase):
"""
Tests for L{DeferredResource}.
"""
def testDeferredResource(self):
r = resource.Resource()
r.isLeaf = 1
s = SDResource(r)
d = DummyRequest(['foo', 'bar', 'baz'])
resource.getChildForRequest(s, d)
self.assertEqual(d.postpath, ['bar', 'baz'])
def test_render(self):
"""
L{DeferredResource} uses the request object's C{render} method to
render the resource which is the result of the L{Deferred} being
handled.
"""
rendered = []
request = DummyRequest([])
request.render = rendered.append
result = resource.Resource()
deferredResource = DeferredResource(defer.succeed(result))
deferredResource.render(request)
self.assertEqual(rendered, [result])
class HtmlIndentTests(TestCase):
"""
Tests for L{htmlIndent}
"""
def test_simpleInput(self):
"""
L{htmlIndent} transparently processes input with no special cases
inside.
"""
line = "foo bar"
self.assertEqual(line, htmlIndent(line))
def test_escapeHtml(self):
"""
L{htmlIndent} escapes HTML from its input.
"""
line = "<br />"
self.assertEqual("&lt;br /&gt;", htmlIndent(line))
def test_stripTrailingWhitespace(self):
"""
L{htmlIndent} removes trailing whitespaces from its input.
"""
line = " foo bar "
self.assertEqual(" foo bar", htmlIndent(line))
def test_forceSpacingFromSpaceCharacters(self):
"""
If L{htmlIndent} detects consecutive space characters, it forces the
rendering by substituting unbreakable space.
"""
line = " foo bar"
self.assertEqual("&nbsp;foo&nbsp;bar", htmlIndent(line))
def test_indentFromTabCharacters(self):
"""
L{htmlIndent} replaces tab characters with unbreakable spaces.
"""
line = "\tfoo"
self.assertEqual("&nbsp; &nbsp; &nbsp; &nbsp; foo", htmlIndent(line))

View file

@ -0,0 +1,105 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for L{twisted.web.vhost}.
"""
from twisted.internet.defer import gatherResults
from twisted.trial.unittest import TestCase
from twisted.web.http import NOT_FOUND
from twisted.web.static import Data
from twisted.web.vhost import NameVirtualHost
from twisted.web.test.test_web import DummyRequest
from twisted.web.test._util import _render
class NameVirtualHostTests(TestCase):
"""
Tests for L{NameVirtualHost}.
"""
def test_renderWithoutHost(self):
"""
L{NameVirtualHost.render} returns the result of rendering the
instance's C{default} if it is not C{None} and there is no I{Host}
header in the request.
"""
virtualHostResource = NameVirtualHost()
virtualHostResource.default = Data("correct result", "")
request = DummyRequest([''])
self.assertEqual(
virtualHostResource.render(request), "correct result")
def test_renderWithoutHostNoDefault(self):
"""
L{NameVirtualHost.render} returns a response with a status of I{NOT
FOUND} if the instance's C{default} is C{None} and there is no I{Host}
header in the request.
"""
virtualHostResource = NameVirtualHost()
request = DummyRequest([''])
d = _render(virtualHostResource, request)
def cbRendered(ignored):
self.assertEqual(request.responseCode, NOT_FOUND)
d.addCallback(cbRendered)
return d
def test_renderWithHost(self):
"""
L{NameVirtualHost.render} returns the result of rendering the resource
which is the value in the instance's C{host} dictionary corresponding
to the key indicated by the value of the I{Host} header in the request.
"""
virtualHostResource = NameVirtualHost()
virtualHostResource.addHost('example.org', Data("winner", ""))
request = DummyRequest([''])
request.headers['host'] = 'example.org'
d = _render(virtualHostResource, request)
def cbRendered(ignored, request):
self.assertEqual(''.join(request.written), "winner")
d.addCallback(cbRendered, request)
# The port portion of the Host header should not be considered.
requestWithPort = DummyRequest([''])
requestWithPort.headers['host'] = 'example.org:8000'
dWithPort = _render(virtualHostResource, requestWithPort)
def cbRendered(ignored, requestWithPort):
self.assertEqual(''.join(requestWithPort.written), "winner")
dWithPort.addCallback(cbRendered, requestWithPort)
return gatherResults([d, dWithPort])
def test_renderWithUnknownHost(self):
"""
L{NameVirtualHost.render} returns the result of rendering the
instance's C{default} if it is not C{None} and there is no host
matching the value of the I{Host} header in the request.
"""
virtualHostResource = NameVirtualHost()
virtualHostResource.default = Data("correct data", "")
request = DummyRequest([''])
request.headers['host'] = 'example.com'
d = _render(virtualHostResource, request)
def cbRendered(ignored):
self.assertEqual(''.join(request.written), "correct data")
d.addCallback(cbRendered)
return d
def test_renderWithUnknownHostNoDefault(self):
"""
L{NameVirtualHost.render} returns a response with a status of I{NOT
FOUND} if the instance's C{default} is C{None} and there is no host
matching the value of the I{Host} header in the request.
"""
virtualHostResource = NameVirtualHost()
request = DummyRequest([''])
request.headers['host'] = 'example.com'
d = _render(virtualHostResource, request)
def cbRendered(ignored):
self.assertEqual(request.responseCode, NOT_FOUND)
d.addCallback(cbRendered)
return d

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,818 @@
# -*- test-case-name: twisted.web.test.test_xmlrpc -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
Tests for XML-RPC support in L{twisted.web.xmlrpc}.
"""
import datetime
import xmlrpclib
from StringIO import StringIO
from twisted.trial import unittest
from twisted.web import xmlrpc
from twisted.web.xmlrpc import (
XMLRPC, payloadTemplate, addIntrospection, _QueryFactory, withRequest)
from twisted.web import server, static, client, error, http
from twisted.internet import reactor, defer
from twisted.internet.error import ConnectionDone
from twisted.python import failure
from twisted.test.proto_helpers import MemoryReactor
from twisted.web.test.test_web import DummyRequest
try:
import twisted.internet.ssl
except ImportError:
sslSkip = "OpenSSL not present"
else:
sslSkip = None
class AsyncXMLRPCTests(unittest.TestCase):
"""
Tests for L{XMLRPC}'s support of Deferreds.
"""
def setUp(self):
self.request = DummyRequest([''])
self.request.method = 'POST'
self.request.content = StringIO(
payloadTemplate % ('async', xmlrpclib.dumps(())))
result = self.result = defer.Deferred()
class AsyncResource(XMLRPC):
def xmlrpc_async(self):
return result
self.resource = AsyncResource()
def test_deferredResponse(self):
"""
If an L{XMLRPC} C{xmlrpc_*} method returns a L{defer.Deferred}, the
response to the request is the result of that L{defer.Deferred}.
"""
self.resource.render(self.request)
self.assertEqual(self.request.written, [])
self.result.callback("result")
resp = xmlrpclib.loads("".join(self.request.written))
self.assertEqual(resp, (('result',), None))
self.assertEqual(self.request.finished, 1)
def test_interruptedDeferredResponse(self):
"""
While waiting for the L{Deferred} returned by an L{XMLRPC} C{xmlrpc_*}
method to fire, the connection the request was issued over may close.
If this happens, neither C{write} nor C{finish} is called on the
request.
"""
self.resource.render(self.request)
self.request.processingFailed(
failure.Failure(ConnectionDone("Simulated")))
self.result.callback("result")
self.assertEqual(self.request.written, [])
self.assertEqual(self.request.finished, 0)
class TestRuntimeError(RuntimeError):
pass
class TestValueError(ValueError):
pass
class Test(XMLRPC):
# If you add xmlrpc_ methods to this class, go change test_listMethods
# below.
FAILURE = 666
NOT_FOUND = 23
SESSION_EXPIRED = 42
def xmlrpc_echo(self, arg):
return arg
# the doc string is part of the test
def xmlrpc_add(self, a, b):
"""
This function add two numbers.
"""
return a + b
xmlrpc_add.signature = [['int', 'int', 'int'],
['double', 'double', 'double']]
# the doc string is part of the test
def xmlrpc_pair(self, string, num):
"""
This function puts the two arguments in an array.
"""
return [string, num]
xmlrpc_pair.signature = [['array', 'string', 'int']]
# the doc string is part of the test
def xmlrpc_defer(self, x):
"""Help for defer."""
return defer.succeed(x)
def xmlrpc_deferFail(self):
return defer.fail(TestValueError())
# don't add a doc string, it's part of the test
def xmlrpc_fail(self):
raise TestRuntimeError
def xmlrpc_fault(self):
return xmlrpc.Fault(12, "hello")
def xmlrpc_deferFault(self):
return defer.fail(xmlrpc.Fault(17, "hi"))
def xmlrpc_complex(self):
return {"a": ["b", "c", 12, []], "D": "foo"}
def xmlrpc_dict(self, map, key):
return map[key]
xmlrpc_dict.help = 'Help for dict.'
@withRequest
def xmlrpc_withRequest(self, request, other):
"""
A method decorated with L{withRequest} which can be called by
a test to verify that the request object really is passed as
an argument.
"""
return (
# as a proof that request is a request
request.method +
# plus proof other arguments are still passed along
' ' + other)
def lookupProcedure(self, procedurePath):
try:
return XMLRPC.lookupProcedure(self, procedurePath)
except xmlrpc.NoSuchFunction:
if procedurePath.startswith("SESSION"):
raise xmlrpc.Fault(self.SESSION_EXPIRED,
"Session non-existant/expired.")
else:
raise
class TestLookupProcedure(XMLRPC):
"""
This is a resource which customizes procedure lookup to be used by the tests
of support for this customization.
"""
def echo(self, x):
return x
def lookupProcedure(self, procedureName):
"""
Lookup a procedure from a fixed set of choices, either I{echo} or
I{system.listeMethods}.
"""
if procedureName == 'echo':
return self.echo
raise xmlrpc.NoSuchFunction(
self.NOT_FOUND, 'procedure %s not found' % (procedureName,))
class TestListProcedures(XMLRPC):
"""
This is a resource which customizes procedure enumeration to be used by the
tests of support for this customization.
"""
def listProcedures(self):
"""
Return a list of a single method this resource will claim to support.
"""
return ['foo']
class TestAuthHeader(Test):
"""
This is used to get the header info so that we can test
authentication.
"""
def __init__(self):
Test.__init__(self)
self.request = None
def render(self, request):
self.request = request
return Test.render(self, request)
def xmlrpc_authinfo(self):
return self.request.getUser(), self.request.getPassword()
class TestQueryProtocol(xmlrpc.QueryProtocol):
"""
QueryProtocol for tests that saves headers received inside the factory.
"""
def connectionMade(self):
self.factory.transport = self.transport
xmlrpc.QueryProtocol.connectionMade(self)
def handleHeader(self, key, val):
self.factory.headers[key.lower()] = val
class TestQueryFactory(xmlrpc._QueryFactory):
"""
QueryFactory using L{TestQueryProtocol} for saving headers.
"""
protocol = TestQueryProtocol
def __init__(self, *args, **kwargs):
self.headers = {}
xmlrpc._QueryFactory.__init__(self, *args, **kwargs)
class TestQueryFactoryCancel(xmlrpc._QueryFactory):
"""
QueryFactory that saves a reference to the
L{twisted.internet.interfaces.IConnector} to test connection lost.
"""
def startedConnecting(self, connector):
self.connector = connector
class XMLRPCTestCase(unittest.TestCase):
def setUp(self):
self.p = reactor.listenTCP(0, server.Site(Test()),
interface="127.0.0.1")
self.port = self.p.getHost().port
self.factories = []
def tearDown(self):
self.factories = []
return self.p.stopListening()
def queryFactory(self, *args, **kwargs):
"""
Specific queryFactory for proxy that uses our custom
L{TestQueryFactory}, and save factories.
"""
factory = TestQueryFactory(*args, **kwargs)
self.factories.append(factory)
return factory
def proxy(self, factory=None):
"""
Return a new xmlrpc.Proxy for the test site created in
setUp(), using the given factory as the queryFactory, or
self.queryFactory if no factory is provided.
"""
p = xmlrpc.Proxy("http://127.0.0.1:%d/" % self.port)
if factory is None:
p.queryFactory = self.queryFactory
else:
p.queryFactory = factory
return p
def test_results(self):
inputOutput = [
("add", (2, 3), 5),
("defer", ("a",), "a"),
("dict", ({"a": 1}, "a"), 1),
("pair", ("a", 1), ["a", 1]),
("complex", (), {"a": ["b", "c", 12, []], "D": "foo"})]
dl = []
for meth, args, outp in inputOutput:
d = self.proxy().callRemote(meth, *args)
d.addCallback(self.assertEqual, outp)
dl.append(d)
return defer.DeferredList(dl, fireOnOneErrback=True)
def test_errors(self):
"""
Verify that for each way a method exposed via XML-RPC can fail, the
correct 'Content-type' header is set in the response and that the
client-side Deferred is errbacked with an appropriate C{Fault}
instance.
"""
dl = []
for code, methodName in [(666, "fail"), (666, "deferFail"),
(12, "fault"), (23, "noSuchMethod"),
(17, "deferFault"), (42, "SESSION_TEST")]:
d = self.proxy().callRemote(methodName)
d = self.assertFailure(d, xmlrpc.Fault)
d.addCallback(lambda exc, code=code:
self.assertEqual(exc.faultCode, code))
dl.append(d)
d = defer.DeferredList(dl, fireOnOneErrback=True)
def cb(ign):
for factory in self.factories:
self.assertEqual(factory.headers['content-type'],
'text/xml')
self.flushLoggedErrors(TestRuntimeError, TestValueError)
d.addCallback(cb)
return d
def test_cancel(self):
"""
A deferred from the Proxy can be cancelled, disconnecting
the L{twisted.internet.interfaces.IConnector}.
"""
def factory(*args, **kw):
factory.f = TestQueryFactoryCancel(*args, **kw)
return factory.f
d = self.proxy(factory).callRemote('add', 2, 3)
self.assertNotEquals(factory.f.connector.state, "disconnected")
d.cancel()
self.assertEqual(factory.f.connector.state, "disconnected")
d = self.assertFailure(d, defer.CancelledError)
return d
def test_errorGet(self):
"""
A classic GET on the xml server should return a NOT_ALLOWED.
"""
d = client.getPage("http://127.0.0.1:%d/" % (self.port,))
d = self.assertFailure(d, error.Error)
d.addCallback(
lambda exc: self.assertEqual(int(exc.args[0]), http.NOT_ALLOWED))
return d
def test_errorXMLContent(self):
"""
Test that an invalid XML input returns an L{xmlrpc.Fault}.
"""
d = client.getPage("http://127.0.0.1:%d/" % (self.port,),
method="POST", postdata="foo")
def cb(result):
self.assertRaises(xmlrpc.Fault, xmlrpclib.loads, result)
d.addCallback(cb)
return d
def test_datetimeRoundtrip(self):
"""
If an L{xmlrpclib.DateTime} is passed as an argument to an XML-RPC
call and then returned by the server unmodified, the result should
be equal to the original object.
"""
when = xmlrpclib.DateTime()
d = self.proxy().callRemote("echo", when)
d.addCallback(self.assertEqual, when)
return d
def test_doubleEncodingError(self):
"""
If it is not possible to encode a response to the request (for example,
because L{xmlrpclib.dumps} raises an exception when encoding a
L{Fault}) the exception which prevents the response from being
generated is logged and the request object is finished anyway.
"""
d = self.proxy().callRemote("echo", "")
# *Now* break xmlrpclib.dumps. Hopefully the client already used it.
def fakeDumps(*args, **kwargs):
raise RuntimeError("Cannot encode anything at all!")
self.patch(xmlrpclib, 'dumps', fakeDumps)
# It doesn't matter how it fails, so long as it does. Also, it happens
# to fail with an implementation detail exception right now, not
# something suitable as part of a public interface.
d = self.assertFailure(d, Exception)
def cbFailed(ignored):
# The fakeDumps exception should have been logged.
self.assertEqual(len(self.flushLoggedErrors(RuntimeError)), 1)
d.addCallback(cbFailed)
return d
def test_closeConnectionAfterRequest(self):
"""
The connection to the web server is closed when the request is done.
"""
d = self.proxy().callRemote('echo', '')
def responseDone(ignored):
[factory] = self.factories
self.assertFalse(factory.transport.connected)
self.assertTrue(factory.transport.disconnected)
return d.addCallback(responseDone)
def test_tcpTimeout(self):
"""
For I{HTTP} URIs, L{xmlrpc.Proxy.callRemote} passes the value it
received for the C{connectTimeout} parameter as the C{timeout} argument
to the underlying connectTCP call.
"""
reactor = MemoryReactor()
proxy = xmlrpc.Proxy("http://127.0.0.1:69", connectTimeout=2.0,
reactor=reactor)
proxy.callRemote("someMethod")
self.assertEqual(reactor.tcpClients[0][3], 2.0)
def test_sslTimeout(self):
"""
For I{HTTPS} URIs, L{xmlrpc.Proxy.callRemote} passes the value it
received for the C{connectTimeout} parameter as the C{timeout} argument
to the underlying connectSSL call.
"""
reactor = MemoryReactor()
proxy = xmlrpc.Proxy("https://127.0.0.1:69", connectTimeout=3.0,
reactor=reactor)
proxy.callRemote("someMethod")
self.assertEqual(reactor.sslClients[0][4], 3.0)
test_sslTimeout.skip = sslSkip
class XMLRPCTestCase2(XMLRPCTestCase):
"""
Test with proxy that doesn't add a slash.
"""
def proxy(self, factory=None):
p = xmlrpc.Proxy("http://127.0.0.1:%d" % self.port)
if factory is None:
p.queryFactory = self.queryFactory
else:
p.queryFactory = factory
return p
class XMLRPCTestPublicLookupProcedure(unittest.TestCase):
"""
Tests for L{XMLRPC}'s support of subclasses which override
C{lookupProcedure} and C{listProcedures}.
"""
def createServer(self, resource):
self.p = reactor.listenTCP(
0, server.Site(resource), interface="127.0.0.1")
self.addCleanup(self.p.stopListening)
self.port = self.p.getHost().port
self.proxy = xmlrpc.Proxy('http://127.0.0.1:%d' % self.port)
def test_lookupProcedure(self):
"""
A subclass of L{XMLRPC} can override C{lookupProcedure} to find
procedures that are not defined using a C{xmlrpc_}-prefixed method name.
"""
self.createServer(TestLookupProcedure())
what = "hello"
d = self.proxy.callRemote("echo", what)
d.addCallback(self.assertEqual, what)
return d
def test_errors(self):
"""
A subclass of L{XMLRPC} can override C{lookupProcedure} to raise
L{NoSuchFunction} to indicate that a requested method is not available
to be called, signalling a fault to the XML-RPC client.
"""
self.createServer(TestLookupProcedure())
d = self.proxy.callRemote("xxxx", "hello")
d = self.assertFailure(d, xmlrpc.Fault)
return d
def test_listMethods(self):
"""
A subclass of L{XMLRPC} can override C{listProcedures} to define
Overriding listProcedures should prevent introspection from being
broken.
"""
resource = TestListProcedures()
addIntrospection(resource)
self.createServer(resource)
d = self.proxy.callRemote("system.listMethods")
def listed(procedures):
# The list will also include other introspection procedures added by
# addIntrospection. We just want to see "foo" from our customized
# listProcedures.
self.assertIn('foo', procedures)
d.addCallback(listed)
return d
class SerializationConfigMixin:
"""
Mixin which defines a couple tests which should pass when a particular flag
is passed to L{XMLRPC}.
These are not meant to be exhaustive serialization tests, since L{xmlrpclib}
does all of the actual serialization work. They are just meant to exercise
a few codepaths to make sure we are calling into xmlrpclib correctly.
@ivar flagName: A C{str} giving the name of the flag which must be passed to
L{XMLRPC} to allow the tests to pass. Subclasses should set this.
@ivar value: A value which the specified flag will allow the serialization
of. Subclasses should set this.
"""
def setUp(self):
"""
Create a new XML-RPC server with C{allowNone} set to C{True}.
"""
kwargs = {self.flagName: True}
self.p = reactor.listenTCP(
0, server.Site(Test(**kwargs)), interface="127.0.0.1")
self.addCleanup(self.p.stopListening)
self.port = self.p.getHost().port
self.proxy = xmlrpc.Proxy(
"http://127.0.0.1:%d/" % (self.port,), **kwargs)
def test_roundtripValue(self):
"""
C{self.value} can be round-tripped over an XMLRPC method call/response.
"""
d = self.proxy.callRemote('defer', self.value)
d.addCallback(self.assertEqual, self.value)
return d
def test_roundtripNestedValue(self):
"""
A C{dict} which contains C{self.value} can be round-tripped over an
XMLRPC method call/response.
"""
d = self.proxy.callRemote('defer', {'a': self.value})
d.addCallback(self.assertEqual, {'a': self.value})
return d
class XMLRPCAllowNoneTestCase(SerializationConfigMixin, unittest.TestCase):
"""
Tests for passing C{None} when the C{allowNone} flag is set.
"""
flagName = "allowNone"
value = None
class XMLRPCUseDateTimeTestCase(SerializationConfigMixin, unittest.TestCase):
"""
Tests for passing a C{datetime.datetime} instance when the C{useDateTime}
flag is set.
"""
flagName = "useDateTime"
value = datetime.datetime(2000, 12, 28, 3, 45, 59)
class XMLRPCTestAuthenticated(XMLRPCTestCase):
"""
Test with authenticated proxy. We run this with the same inout/ouput as
above.
"""
user = "username"
password = "asecret"
def setUp(self):
self.p = reactor.listenTCP(0, server.Site(TestAuthHeader()),
interface="127.0.0.1")
self.port = self.p.getHost().port
self.factories = []
def test_authInfoInURL(self):
p = xmlrpc.Proxy("http://%s:%s@127.0.0.1:%d/" % (
self.user, self.password, self.port))
d = p.callRemote("authinfo")
d.addCallback(self.assertEqual, [self.user, self.password])
return d
def test_explicitAuthInfo(self):
p = xmlrpc.Proxy("http://127.0.0.1:%d/" % (
self.port,), self.user, self.password)
d = p.callRemote("authinfo")
d.addCallback(self.assertEqual, [self.user, self.password])
return d
def test_longPassword(self):
"""
C{QueryProtocol} uses the C{base64.b64encode} function to encode user
name and password in the I{Authorization} header, so that it doesn't
embed new lines when using long inputs.
"""
longPassword = self.password * 40
p = xmlrpc.Proxy("http://127.0.0.1:%d/" % (
self.port,), self.user, longPassword)
d = p.callRemote("authinfo")
d.addCallback(self.assertEqual, [self.user, longPassword])
return d
def test_explicitAuthInfoOverride(self):
p = xmlrpc.Proxy("http://wrong:info@127.0.0.1:%d/" % (
self.port,), self.user, self.password)
d = p.callRemote("authinfo")
d.addCallback(self.assertEqual, [self.user, self.password])
return d
class XMLRPCTestIntrospection(XMLRPCTestCase):
def setUp(self):
xmlrpc = Test()
addIntrospection(xmlrpc)
self.p = reactor.listenTCP(0, server.Site(xmlrpc),interface="127.0.0.1")
self.port = self.p.getHost().port
self.factories = []
def test_listMethods(self):
def cbMethods(meths):
meths.sort()
self.assertEqual(
meths,
['add', 'complex', 'defer', 'deferFail',
'deferFault', 'dict', 'echo', 'fail', 'fault',
'pair', 'system.listMethods',
'system.methodHelp',
'system.methodSignature', 'withRequest'])
d = self.proxy().callRemote("system.listMethods")
d.addCallback(cbMethods)
return d
def test_methodHelp(self):
inputOutputs = [
("defer", "Help for defer."),
("fail", ""),
("dict", "Help for dict.")]
dl = []
for meth, expected in inputOutputs:
d = self.proxy().callRemote("system.methodHelp", meth)
d.addCallback(self.assertEqual, expected)
dl.append(d)
return defer.DeferredList(dl, fireOnOneErrback=True)
def test_methodSignature(self):
inputOutputs = [
("defer", ""),
("add", [['int', 'int', 'int'],
['double', 'double', 'double']]),
("pair", [['array', 'string', 'int']])]
dl = []
for meth, expected in inputOutputs:
d = self.proxy().callRemote("system.methodSignature", meth)
d.addCallback(self.assertEqual, expected)
dl.append(d)
return defer.DeferredList(dl, fireOnOneErrback=True)
class XMLRPCClientErrorHandling(unittest.TestCase):
"""
Test error handling on the xmlrpc client.
"""
def setUp(self):
self.resource = static.Data(
"This text is not a valid XML-RPC response.",
"text/plain")
self.resource.isLeaf = True
self.port = reactor.listenTCP(0, server.Site(self.resource),
interface='127.0.0.1')
def tearDown(self):
return self.port.stopListening()
def test_erroneousResponse(self):
"""
Test that calling the xmlrpc client on a static http server raises
an exception.
"""
proxy = xmlrpc.Proxy("http://127.0.0.1:%d/" %
(self.port.getHost().port,))
return self.assertFailure(proxy.callRemote("someMethod"), Exception)
class TestQueryFactoryParseResponse(unittest.TestCase):
"""
Test the behaviour of L{_QueryFactory.parseResponse}.
"""
def setUp(self):
# The _QueryFactory that we are testing. We don't care about any
# of the constructor parameters.
self.queryFactory = _QueryFactory(
path=None, host=None, method='POST', user=None, password=None,
allowNone=False, args=())
# An XML-RPC response that will parse without raising an error.
self.goodContents = xmlrpclib.dumps(('',))
# An 'XML-RPC response' that will raise a parsing error.
self.badContents = 'invalid xml'
# A dummy 'reason' to pass to clientConnectionLost. We don't care
# what it is.
self.reason = failure.Failure(ConnectionDone())
def test_parseResponseCallbackSafety(self):
"""
We can safely call L{_QueryFactory.clientConnectionLost} as a callback
of L{_QueryFactory.parseResponse}.
"""
d = self.queryFactory.deferred
# The failure mode is that this callback raises an AlreadyCalled
# error. We have to add it now so that it gets called synchronously
# and triggers the race condition.
d.addCallback(self.queryFactory.clientConnectionLost, self.reason)
self.queryFactory.parseResponse(self.goodContents)
return d
def test_parseResponseErrbackSafety(self):
"""
We can safely call L{_QueryFactory.clientConnectionLost} as an errback
of L{_QueryFactory.parseResponse}.
"""
d = self.queryFactory.deferred
# The failure mode is that this callback raises an AlreadyCalled
# error. We have to add it now so that it gets called synchronously
# and triggers the race condition.
d.addErrback(self.queryFactory.clientConnectionLost, self.reason)
self.queryFactory.parseResponse(self.badContents)
return d
def test_badStatusErrbackSafety(self):
"""
We can safely call L{_QueryFactory.clientConnectionLost} as an errback
of L{_QueryFactory.badStatus}.
"""
d = self.queryFactory.deferred
# The failure mode is that this callback raises an AlreadyCalled
# error. We have to add it now so that it gets called synchronously
# and triggers the race condition.
d.addErrback(self.queryFactory.clientConnectionLost, self.reason)
self.queryFactory.badStatus('status', 'message')
return d
def test_parseResponseWithoutData(self):
"""
Some server can send a response without any data:
L{_QueryFactory.parseResponse} should catch the error and call the
result errback.
"""
content = """
<methodResponse>
<params>
<param>
</param>
</params>
</methodResponse>"""
d = self.queryFactory.deferred
self.queryFactory.parseResponse(content)
return self.assertFailure(d, IndexError)
class XMLRPCTestWithRequest(unittest.TestCase):
def setUp(self):
self.resource = Test()
def test_withRequest(self):
"""
When an XML-RPC method is called and the implementation is
decorated with L{withRequest}, the request object is passed as
the first argument.
"""
request = DummyRequest('/RPC2')
request.method = "POST"
request.content = StringIO(xmlrpclib.dumps(("foo",), 'withRequest'))
def valid(n, request):
data = xmlrpclib.loads(request.written[0])
self.assertEqual(data, (('POST foo',), None))
d = request.notifyFinish().addCallback(valid, request)
self.resource.render_POST(request)
return d

View file

@ -0,0 +1,705 @@
Ticket numbers in this file can be looked up by visiting
http://twistedmatrix.com/trac/ticket/<number>
Twisted Web 14.0.0 (2014-05-08)
===============================
Features
--------
- twisted.web.http.proxiedLogFormatter can now be used with
twisted.web.http.HTTPFactory (and subclasses) to record X
-Forwarded-For values to the access log when the HTTP server is
deployed behind a reverse proxy. (#1468)
- twisted.web.client.Agent now uses
twisted.internet.ssl.CertificateOptions for SSL/TLS and benefits
from its continuous improvements. (#6893)
Bugfixes
--------
- twisted.web.client.Agent now correctly manage flow-control on
pooled connections, and therefore requests will no longer hang
sometimes when deliverBody is not called synchronously within the
callback on Request. (#6751)
- twisted.web.client.Agent now verifies that the provided server
certificate in a TLS connection is trusted by the platform. (#7042)
- When requesting an HTTPS URL with twisted.web.client.Agent, the
hostname of the presented certificate will be checked against the
requested hostname; mismatches will now result in an error rather
than a man-in-the-middle opportunity for attackers. This may break
existing code that incorrectly depended on insecure behavior, but
such code was erroneous and should be updated. (#4888)
Other
-----
- #5004, #6881, #6956
Twisted Web 13.2.0 (2013-10-29)
===============================
Features
--------
- IAgent has been added to twisted.web.iweb to explicitly define the
interface implemented by the various "Agent" classes in
twisted.web.client. (#6702)
Bugfixes
--------
- twisted.web.client.Response.deliverBody now calls connectionLost on
the body protocol for responses with no body (such as 204, 304, and
HEAD requests). (#5476)
- twisted.web.static.loadMimeTypes now uses all available system MIME
types. (#5717)
Deprecations and Removals
-------------------------
- Two attributes of twisted.web.iweb.IRequest, headers and
received_headers, are now deprecated. (#6704)
Other
-----
- #5387, #6119, #6121, #6695, #6701, #6734
Twisted Web 13.1.0 (2013-06-23)
===============================
Features
--------
- The deferred returned by twisted.web.client.Agent.request can now
be cancelled. (#4330)
- twisted.web.client.BrowserLikeRedirectAgent, a new redirect agent,
treats HTTP 301 and 302 like HTTP 303 on non-HEAD/GET requests,
changing the method to GET before proceeding. (#5434)
- The new attribute twisted.web.iweb.IResponse.request is a reference
to a provider of the new twisted.web.iweb.IClientRequest interface
which, among other things, provides a way to access the request's
absolute URI. It is now also possible to inspect redirect history
with twisted.web.iweb.IResponse.previousResponse. (#5435)
- twisted.web.client.RedirectAgent now supports relative URI
references in the Location HTTP header. (#5462)
- twisted.web.client now provides readBody to collect the body of a
response from Agent into a string. (#6251)
Bugfixes
--------
- twisted.web.xmlrpc.QueryProtocol now generates valid Authorization
headers for long user names and passwords. (#2980)
Other
-----
- #6122, #6153, #6342, #6381, #6391, #6503
Twisted Web 13.0.0 (2013-03-19)
===============================
Bugfixes
--------
- twisted.web.template now properly quotes attribute values,
including Tag instances serialized within attribute values. (#6275)
Other
-----
- #6167, #6297, #6326
Twisted Web 12.3.0 (2012-12-20)
===============================
Features
--------
- twisted.web.server.Site now supports an encoders argument to encode
request content, twisted.web.server.GzipEncoderFactory being the
first one provided. (#104)
Bugfixes
--------
- twisted.web.http.HTTPChannel.headerReceived now catches the error
if the Content-Length header is not an integer and return a 400 Bad
Request response. (#6029)
- twisted.web.http.HTTPChannel now drops the connection and issues a
400 error upon receipt of a chunk-encoding encoded request with a
bad chunk-length field. (#6030)
Improved Documentation
----------------------
- twisted.web.iweb.IRequest now documents its `content` attribute and
a new "web in 60 seconds" howto demonstrates its use. (#6181)
Other
-----
- #5882, #5883, #5887, #5920, #6031, #6077, #6078, #6079, #6080,
#6110, #6113, #6196, #6205
Twisted Web 12.2.0 (2012-08-26)
===============================
Deprecations and Removals
-------------------------
- twisted.web.static.FileTransfer, deprecated since 9.0, is removed
now. Use a subclass of StaticProducer instead. (#5651)
- ErrorPage, NoResource and ForbiddenResource in twisted.web.error
were deprecated since 9.0 and are removed now. (#5659)
- twisted.web.google, deprecated since Twisted 11.1, is removed now.
(#5768)
Other
-----
- #5665
Twisted Web 12.1.0 (2012-06-02)
===============================
Features
--------
- twisted.web.client.Agent and ProxyAgent now support persistent
connections. (#3420)
- Added twisted.web.template.renderElement, a function which renders
an Element to a response. (#5395)
- twisted.web.client.HTTPConnectionPool now ensures that failed
queries on persistent connections are retried, when possible.
(#5479)
- twisted.web.template.XMLFile now supports FilePath objects. (#5509)
- twisted.web.template.renderElement takes a doctype keyword
argument, which will be written as the first line of the response,
defaulting to the HTML5 doctype. (#5560)
Bugfixes
--------
- twisted.web.util.formatFailure now quotes all data in its output to
avoid it being mistakenly interpreted as markup. (#4896)
- twisted.web.distrib now lets distributed servers set the response
message. (#5525)
Deprecations and Removals
-------------------------
- PHP3Script and PHPScript were removed from twisted.web.twcgi,
deprecated since 10.1. Use twcgi.FilteredScript instead. (#5456)
- twisted.web.template.XMLFile's support for file objects and
filenames is now deprecated. Use the new support for FilePath
objects. (#5509)
- twisted.web.server.date_time_string and
twisted.web.server.string_date_time are now deprecated in favor of
twisted.web.http.datetimeToString and twisted.web.
http.stringToDatetime (#5535)
Other
-----
- #4966, #5460, #5490, #5591, #5602, #5609, #5612
Twisted Web 12.0.0 (2012-02-10)
===============================
Features
--------
- twisted.web.util.redirectTo now raises TypeError if the URL passed
to it is a unicode string instead of a byte string. (#5236)
- The new class twisted.web.template.CharRef provides support for
inserting numeric character references in output generated by
twisted.web.template. (#5408)
Improved Documentation
----------------------
- The Twisted Web howto now has a section on proxies and reverse
proxies. (#399)
- The web client howto now covers ContentDecoderAgent and links to an
example of its use. (#5415)
Other
-----
- #5404, #5438
Twisted Web 11.1.0 (2011-11-15)
===============================
Features
--------
- twisted.web.client.ProxyAgent is a new HTTP/1.1 web client which
adds proxy support. (#1774)
- twisted.web.client.Agent now takes optional connectTimeout and
bindAddress arguments which are forwarded to the subsequent
connectTCP/connectSSL call. (#3450)
- The new class twisted.web.client.FileBodyProducer makes it easy to
upload data in HTTP requests made using the Agent client APIs.
(#4017)
- twisted.web.xmlrpc.XMLRPC now allows its lookupProcedure method to
be overridden to change how XML-RPC procedures are dispatched.
(#4836)
- A new HTTP cookie-aware Twisted Web Agent wrapper is included in
twisted.web.client.CookieAgent (#4922)
- New class twisted.web.template.TagLoader provides an
ITemplateLoader implementation which loads already-created
twisted.web.iweb.IRenderable providers. (#5040)
- The new class twisted.web.client.RedirectAgent adds redirect
support to the HTTP 1.1 client stack. (#5157)
- twisted.web.template now supports HTML tags from the HTML5
standard, including <canvas> and <video>. (#5306)
Bugfixes
--------
- twisted.web.client.getPage and .downloadPage now only fire their
result Deferred after the underlying connection they use has been
closed. (#3796)
- twisted.web.server now omits the default Content-Type header from
NOT MODIFIED responses. (#4156)
- twisted.web.server now responds correctly to 'Expect: 100-continue'
headers, although this is not yet usefully exposed to user code.
(#4673)
- twisted.web.client.Agent no longer raises an exception if a server
responds and closes the connection before the request has been
fully transmitted. (#5013)
- twisted.web.http_headers.Headers now correctly capitalizes the
header names Content-MD5, DNT, ETag, P3P, TE, and X-XSS-Protection.
(#5054)
- twisted.web.template now escapes more inputs to comments which
require escaping in the output. (#5275)
Improved Documentation
----------------------
- The twisted.web.template howto now documents the common idiom of
yielding tag clones from a renderer. (#5286)
- CookieAgent is now documented in the twisted.web.client how-to.
(#5110)
Deprecations and Removals
-------------------------
- twisted.web.google is now deprecated. (#5209)
Other
-----
- #4951, #5057, #5175, #5288, #5316
Twisted Web 11.0.0 (2011-04-01)
===============================
Features
--------
- twisted.web._newclient.HTTPParser (and therefore Agent) now handles
HTTP headers delimited by bare LF newlines. (#3833)
- twisted.web.client.downloadPage now accepts the `afterFoundGet`
parameter, with the same meaning as the `getPage` parameter of the
same name. (#4364)
- twisted.web.xmlrpc.Proxy constructor now takes additional 'timeout'
and 'reactor' arguments. The 'timeout' argument defaults to 30
seconds. (#4741)
- Twisted Web now has a templating system, twisted.web.template,
which is a direct, simplified derivative of Divmod Nevow. (#4939)
Bugfixes
--------
- HTTPPageGetter now adds the port to the host header if it is not
the default for that scheme. (#3857)
- twisted.web.http.Request.write now raises an exception if it is
called after response generation has already finished. (#4317)
- twisted.web.client.HTTPPageGetter and twisted.web.client.getPage
now no longer make two requests when using afterFoundGet. (#4760)
- twisted.web.twcgi no longer adds an extra "content-type" header to
CGI responses. (#4786)
- twisted.web will now properly specify an encoding (UTF-8) on error,
redirect, and directory listing pages, so that IE7 and previous
will not improperly guess the 'utf7' encoding in these cases.
Please note that Twisted still sets a *default* content-type of
'text/html', and you shouldn't rely on that: you should set the
encoding appropriately in your application. (#4900)
- twisted.web.http.Request.setHost now sets the port in the host
header if it is not the default. (#4918)
- default NOT_IMPLEMENTED and NOT_ALLOWED pages now quote the request
method and URI respectively, to protect against browsers which
don't quote those values for us. (#4978)
Improved Documentation
----------------------
- The XML-RPC howto now includes an example demonstrating how to
access the HTTP request object in a server-side XML-RPC method.
(#4732)
- The Twisted Web client howto now uses the correct, public name for
twisted.web.client.Response. (#4769)
- Some broken links were fixed, descriptions were updated, and new
API links were added in the Resource Templating documentation
(resource-templates.xhtml) (#4968)
Other
-----
- #2271, #2386, #4162, #4733, #4855, #4911, #4973
Twisted Web 10.2.0 (2010-11-29)
===============================
Features
--------
- twisted.web.xmlrpc.XMLRPC.xmlrpc_* methods can now be decorated
using withRequest to cause them to be passed the HTTP request
object. (#3073)
Bugfixes
--------
- twisted.web.xmlrpc.QueryProtocol.handleResponse now disconnects
from the server, meaning that Twisted XML-RPC clients disconnect
from the server as soon as they receive a response, rather than
relying on the server to disconnect. (#2518)
- twisted.web.twcgi now generates responses containing all
occurrences of duplicate headers produced by CGI scripts, not just
the last value. (#4742)
Deprecations and Removals
-------------------------
- twisted.web.trp, which has been deprecated since Twisted 9.0, was
removed. (#4299)
Other
-----
- #4576, #4577, #4709, #4723
Twisted Web 10.1.0 (2010-06-27)
===============================
Features
--------
- twisted.web.xmlrpc.XMLRPC and twisted.web.xmlrpc.Proxy now expose
xmlrpclib's support of datetime.datetime objects if useDateTime is
set to True. (#3219)
- HTTP11ClientProtocol now has an abort() method for cancelling an
outstanding request by closing the connection before receiving the
entire response. (#3811)
- twisted.web.http_headers.Headers initializer now rejects
incorrectly structured dictionaries. (#4022)
- twisted.web.client.Agent now supports HTTPS URLs. (#4023)
- twisted.web.xmlrpc.Proxy.callRemote now returns a Deferred which
can be cancelled to abort the attempted XML-RPC call. (#4377)
Bugfixes
--------
- twisted.web.guard now logs out avatars even if a request completes
with an error. (#4411)
- twisted.web.xmlrpc.XMLRPC will now no longer trigger a RuntimeError
by trying to write responses to closed connections. (#4423)
Improved Documentation
----------------------
- Fix broken links to deliverBody and iweb.UNKNOWN_LENGTH in
doc/web/howto/client.xhtml. (#4507)
Deprecations and Removals
-------------------------
- twisted.web.twcgi.PHP3Script and twisted.web.twcgi.PHPScript are
now deprecated. (#516)
Other
-----
- #4403, #4452
Twisted Web 10.0.0 (2010-03-01)
===============================
Features
--------
- Twisted Web in 60 Seconds, a series of short tutorials with self-
contained examples on a range of common web topics, is now a part
of the Twisted Web howto documentation. (#4192)
Bugfixes
--------
- Data and File from twisted.web.static and
twisted.web.distrib.UserDirectory will now only generate a 200
response for GET or HEAD requests.
twisted.web.client.HTTPPageGetter will no longer ignore the case of
a request method when considering whether to apply special HEAD
processing to a response. (#446)
- twisted.web.http.HTTPClient now supports multi-line headers.
(#2062)
- Resources served via twisted.web.distrib will no longer encounter a
Banana error when writing more than 640kB at once to the request
object. (#3212)
- The Error, PageRedirect, and InfiniteRedirection exception in
twisted.web now initialize an empty message parameter by mapping
the HTTP status code parameter to a descriptive string. Previously
the lookup would always fail, leaving message empty. (#3806)
- The 'wsgi.input' WSGI environment object now supports -1 and None
as arguments to the read and readlines methods. (#4114)
- twisted.web.wsgi doesn't unquote QUERY_STRING anymore, thus
complying with the WSGI reference implementation. (#4143)
- The HTTP proxy will no longer pass on keep-alive request headers
from the client, preventing pages from loading then "hanging"
(leaving the connection open with no hope of termination). (#4179)
Deprecations and Removals
-------------------------
- Remove '--static' option from twistd web, that served as an alias
for the '--path' option. (#3907)
Other
-----
- #3784, #4216, #4242
Twisted Web 9.0.0 (2009-11-24)
==============================
Features
--------
- There is now an iweb.IRequest interface which specifies the interface that
request objects provide (#3416)
- downloadPage now supports the same cookie, redirect, and timeout features
that getPage supports (#2971)
- A chapter about WSGI has been added to the twisted.web documentation (#3510)
- The HTTP auth support in the web server now allows anonymous sessions by
logging in with ANONYMOUS credentials when no Authorization header is
provided in a request (#3924, #3936)
- HTTPClientFactory now accepts a parameter to enable a common deviation from
the HTTP 1.1 standard by responding to redirects in a POSTed request with a
GET instead of another POST (#3624)
- A new basic HTTP/1.1 client API is included in twisted.web.client.Agent
(#886, #3987)
Fixes
-----
- Requests for "insecure" children of a static.File (such as paths containing
encoded directory separators) will now result in a 404 instead of a 500
(#3549, #3469)
- When specifying a followRedirect argument to the getPage function, the state
of redirect-following for other getPage calls should now be unaffected. It
was previously overwriting a class attribute which would affect outstanding
getPage calls (#3192)
- Downloading an URL of the form "http://example.com:/" will now work,
ignoring the extraneous colon (#2402)
- microdom's appendChild method will no longer issue a spurious warning, and
microdom's methods in general should now issue more meaningful exceptions
when invalid parameters are passed (#3421)
- WSGI applications will no longer have spurious Content-Type headers added to
their responses by the twisted.web server. In addition, WSGI applications
will no longer be able to specify the server-restricted headers Server and
Date (#3569)
- http_headers.Headers now normalizes the case of raw headers passed directly
to it in the same way that it normalizes the headers passed to setRawHeaders
(#3557)
- The distrib module no longer relies on the deprecated woven package (#3559)
- twisted.web.domhelpers now works with both microdom and minidom (#3600)
- twisted.web servers will now ignore invalid If-Modified-Since headers instead
of returning a 500 error (#3601)
- Certain request-bound memory and file resources are cleaned up slightly
sooner by the request when the connection is lost (#1621, #3176)
- xmlrpclib.DateTime objects should now correctly round-trip over twisted.web's
XMLRPC support in all supported versions of Python, and errors during error
serialization will no longer hang a twisted.web XMLRPC response (#2446)
- request.content should now always be seeked to the beginning when
request.process is called, so application code should never need to seek
back manually (#3585)
- Fetching a child of static.File with a double-slash in the URL (such as
"example//foo.html") should now return a 404 instead of a traceback and
500 error (#3631)
- downloadPage will now fire a Failure on its returned Deferred instead of
indicating success when the connection is prematurely lost (#3645)
- static.File will now provide a 404 instead of a 500 error when it was
constructed with a non-existent file (#3634)
- microdom should now serialize namespaces correctly (#3672)
- The HTTP Auth support resource wrapper should no longer corrupt requests and
cause them to skip a segment in the request path (#3679)
- The twisted.web WSGI support should now include leading slashes in PATH_INFO,
and SCRIPT_NAME will be empty if the application is at the root of the
resource tree. This means that WSGI applications should no longer generate
URLs with double-slashes in them even if they naively concatenate the values
(#3721)
- WSGI applications should now receive the requesting client's IP in the
REMOTE_ADDR environment variable (#3730)
- The distrib module should work again. It was unfortunately broken with the
refactoring of twisted.web's header support (#3697)
- static.File now supports multiple ranges specified in the Range header
(#3574)
- static.File should now generate a correct Content-Length value when the
requested Range value doesn't fit entirely within the file's contents (#3814)
- Attempting to call request.finish() after the connection has been lost will
now immediately raise a RuntimeError (#4013)
- An HTTP-auth resource should now be able to directly render the wrapped
avatar, whereas before it would only allow retrieval of child resources
(#4014)
- twisted.web's wsgi support should no longer attempt to call request.finish
twice, which would cause errors in certain cases (#4025)
- WSGI applications should now be able to handle requests with large bodies
(#4029)
- Exceptions raised from WSGI applications should now more reliably be turned
into 500 errors on the HTTP level (#4019)
- DeferredResource now correctly passes through exceptions raised from the
wrapped resource, instead of turning them all into 500 errors (#3932)
- Agent.request now generates a Host header when no headers are passed at
(#4131)
Deprecations and Removals
-------------------------
- The unmaintained and untested twisted.web.monitor module was removed (#2763)
- The twisted.web.woven package has been removed (#1522)
- All of the error resources in twisted.web.error are now in
twisted.web.resource, and accessing them through twisted.web.error is now
deprecated (#3035)
- To facilitate a simplification of the timeout logic in server.Session,
various things have been deprecated (#3457)
- the loopFactory attribute is now ignored
- the checkExpired method now does nothing
- the lifetime parameter to startCheckingExpiration is now ignored
- The twisted.web.trp module is now deprecated (#2030)
Other
-----
- #2763, #3540, #3575, #3610, #3605, #1176, #3539, #3750, #3761, #3779, #2677,
#3782, #3904, #3919, #3418, #3990, #1404, #4050
Web 8.2.0 (2008-12-16)
======================
Features
--------
- The web server can now deal with multi-value headers in the new attributes of
Request, requestHeaders and responseHeaders (#165)
- There is now a resource-wrapper which implements HTTP Basic and Digest auth
in terms of twisted.cred (#696)
- It's now possible to limit the number of redirects that client.getPage will
follow (#2412)
- The directory-listing code no longer uses Woven (#3257)
- static.File now supports Range headers with a single range (#1493)
- twisted.web now has a rudimentary WSGI container (#2753)
- The web server now supports chunked encoding in requests (#3385)
Fixes
-----
- The xmlrpc client now raises an error when the server sends an empty
response (#3399)
- HTTPPageGetter no longer duplicates default headers when they're explicitly
overridden in the headers parameter (#1382)
- The server will no longer timeout clients which are still sending request
data (#1903)
- microdom's isEqualToNode now returns False when the nodes aren't equal
(#2542)
Deprecations and Removals
-------------------------
- Request.headers and Request.received_headers are not quite deprecated, but
they are discouraged in favor of requestHeaders and responseHeaders (#165)
Other
-----
- #909, #687, #2938, #1152, #2930, #2025, #2683, #3471
8.1.0 (2008-05-18)
==================
Fixes
-----
- Fixed an XMLRPC bug whereby sometimes a callRemote Deferred would
accidentally be fired twice when a connection was lost during the handling of
a response (#3152)
- Fixed a bug in the "Using Twisted Web" document which prevented an example
resource from being renderable (#3147)
- The deprecated mktap API is no longer used (#3127)
8.0.0 (2008-03-17)
==================
Features
--------
- Add support to twisted.web.client.getPage for the HTTP HEAD method. (#2750)
Fixes
-----
- Set content-type in xmlrpc responses to "text/xml" (#2430)
- Add more error checking in the xmlrpc.XMLRPC render method, and enforce
POST requests. (#2505)
- Reject unicode input to twisted.web.client._parse to reject invalid
unicode URLs early. (#2628)
- Correctly re-quote URL path segments when generating an URL string to
return from Request.prePathURL. (#2934)
- Make twisted.web.proxy.ProxyClientFactory close the connection when
reporting a 501 error. (#1089)
- Fix twisted.web.proxy.ReverseProxyResource to specify the port in the
host header if different from 80. (#1117)
- Change twisted.web.proxy.ReverseProxyResource so that it correctly encodes
the request URI it sends on to the server for which it is a proxy. (#3013)
- Make "twistd web --personal" use PBServerFactory (#2681)
Misc
----
- #1996, #2382, #2211, #2633, #2634, #2640, #2752, #238, #2905
0.7.0 (2007-01-02)
==================
Features
--------
- Python 2.5 is now supported (#1867)
- twisted.web.xmlrpc now supports the <nil/> xml-rpc extension type
in both the server and the client (#469)
Fixes
-----
- Microdom and SUX now manages certain malformed XML more resiliently
(#1984, #2225, #2298)
- twisted.web.client.getPage can now deal with an URL of the form
"http://example.com" (no trailing slash) (#1080)
- The HTTP server now allows (invalid) URLs with multiple question
marks (#1550)
- '=' can now be in the value of a cookie (#1051)
- Microdom now correctly handles xmlns="" (#2184)
Deprecations and Removals
-------------------------
- websetroot was removed, because it wasn't working anyway (#945)
- woven.guard no longer supports the old twisted.cred API (#1440)
Other
-----
The following changes are minor or closely related to other changes.
- #1636, #1637, #1638, #1936, #1883, #447
0.6.0 (2006-05-21)
==================
Features
--------
- Basic auth support for the XMLRPC client (#1474).
Fixes
-----
- More correct datetime parsing.
- Efficiency improvements (#974)
- Handle popular non-RFC compliant formats for If-Modified-Since
headers (#976).
- Improve support for certain buggy CGI scripts.
- CONTENT_LENGTH is now available to CGI scripts.
- Support for even worse HTML in microdom (#1358).
- Trying to view a user's home page when the user doesn't have a
~/public_html no longer displays a traceback (#551).
- Misc: #543, #1011, #1005, #1287, #1337, #1383, #1079, #1492, #1189,
#737, #872.
0.5.0
=====
- Client properly reports timeouts as error
- "Socially deprecate" woven
- Fix memory leak in _c_urlarg library
- Stop using _c_urlarg library
- Fix 'gzip' and 'bzip2' content-encodings
- Escape log entries so remote user cannot corrupt the log
- Commented out range support because it's broken
- Fix HEAD responses without content-length

View file

@ -0,0 +1,6 @@
Twisted Web 14.0.0
Twisted Web depends on Twisted Core. pyOpenSSL
(<http://launchpad.net/pyopenssl>) is also required for HTTPS. SOAPpy
(<http://pywebsvcs.sourceforge.net/>) is required for SOAP support. For Quixote
resource templates, Quixote (<http://www.quixote.ca/>) is required.

View file

@ -0,0 +1,307 @@
# -*- test-case-name: twisted.web.test.test_cgi -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
I hold resource classes and helper classes that deal with CGI scripts.
"""
# System Imports
import os
import urllib
# Twisted Imports
from twisted.web import http
from twisted.internet import protocol
from twisted.spread import pb
from twisted.python import log, filepath
from twisted.web import resource, server, static
class CGIDirectory(resource.Resource, filepath.FilePath):
def __init__(self, pathname):
resource.Resource.__init__(self)
filepath.FilePath.__init__(self, pathname)
def getChild(self, path, request):
fnp = self.child(path)
if not fnp.exists():
return static.File.childNotFound
elif fnp.isdir():
return CGIDirectory(fnp.path)
else:
return CGIScript(fnp.path)
return resource.NoResource()
def render(self, request):
notFound = resource.NoResource(
"CGI directories do not support directory listing.")
return notFound.render(request)
class CGIScript(resource.Resource):
"""
L{CGIScript} is a resource which runs child processes according to the CGI
specification.
The implementation is complex due to the fact that it requires asynchronous
IPC with an external process with an unpleasant protocol.
"""
isLeaf = 1
def __init__(self, filename, registry=None, reactor=None):
"""
Initialize, with the name of a CGI script file.
"""
self.filename = filename
if reactor is None:
# This installs a default reactor, if None was installed before.
# We do a late import here, so that importing the current module
# won't directly trigger installing a default reactor.
from twisted.internet import reactor
self._reactor = reactor
def render(self, request):
"""
Do various things to conform to the CGI specification.
I will set up the usual slew of environment variables, then spin off a
process.
@type request: L{twisted.web.http.Request}
@param request: An HTTP request.
"""
script_name = "/" + "/".join(request.prepath)
serverName = request.getRequestHostname().split(':')[0]
env = {"SERVER_SOFTWARE": server.version,
"SERVER_NAME": serverName,
"GATEWAY_INTERFACE": "CGI/1.1",
"SERVER_PROTOCOL": request.clientproto,
"SERVER_PORT": str(request.getHost().port),
"REQUEST_METHOD": request.method,
"SCRIPT_NAME": script_name, # XXX
"SCRIPT_FILENAME": self.filename,
"REQUEST_URI": request.uri,
}
client = request.getClient()
if client is not None:
env['REMOTE_HOST'] = client
ip = request.getClientIP()
if ip is not None:
env['REMOTE_ADDR'] = ip
pp = request.postpath
if pp:
env["PATH_INFO"] = "/" + "/".join(pp)
if hasattr(request, "content"):
# request.content is either a StringIO or a TemporaryFile, and
# the file pointer is sitting at the beginning (seek(0,0))
request.content.seek(0,2)
length = request.content.tell()
request.content.seek(0,0)
env['CONTENT_LENGTH'] = str(length)
try:
qindex = request.uri.index('?')
except ValueError:
env['QUERY_STRING'] = ''
qargs = []
else:
qs = env['QUERY_STRING'] = request.uri[qindex+1:]
if '=' in qs:
qargs = []
else:
qargs = [urllib.unquote(x) for x in qs.split('+')]
# Propogate HTTP headers
for title, header in request.getAllHeaders().items():
envname = title.replace('-', '_').upper()
if title not in ('content-type', 'content-length'):
envname = "HTTP_" + envname
env[envname] = header
# Propogate our environment
for key, value in os.environ.items():
if key not in env:
env[key] = value
# And they're off!
self.runProcess(env, request, qargs)
return server.NOT_DONE_YET
def runProcess(self, env, request, qargs=[]):
"""
Run the cgi script.
@type env: A C{dict} of C{str}, or C{None}
@param env: The environment variables to pass to the processs that will
get spawned. See
L{twisted.internet.interfaces.IReactorProcess.spawnProcess} for more
information about environments and process creation.
@type request: L{twisted.web.http.Request}
@param request: An HTTP request.
@type qargs: A C{list} of C{str}
@param qargs: The command line arguments to pass to the process that
will get spawned.
"""
p = CGIProcessProtocol(request)
self._reactor.spawnProcess(p, self.filename, [self.filename] + qargs,
env, os.path.dirname(self.filename))
class FilteredScript(CGIScript):
"""
I am a special version of a CGI script, that uses a specific executable.
This is useful for interfacing with other scripting languages that adhere to
the CGI standard. My C{filter} attribute specifies what executable to run,
and my C{filename} init parameter describes which script to pass to the
first argument of that script.
To customize me for a particular location of a CGI interpreter, override
C{filter}.
@type filter: C{str}
@ivar filter: The absolute path to the executable.
"""
filter = '/usr/bin/cat'
def runProcess(self, env, request, qargs=[]):
"""
Run a script through the C{filter} executable.
@type env: A C{dict} of C{str}, or C{None}
@param env: The environment variables to pass to the processs that will
get spawned. See
L{twisted.internet.interfaces.IReactorProcess.spawnProcess} for more
information about environments and process creation.
@type request: L{twisted.web.http.Request}
@param request: An HTTP request.
@type qargs: A C{list} of C{str}
@param qargs: The command line arguments to pass to the process that
will get spawned.
"""
p = CGIProcessProtocol(request)
self._reactor.spawnProcess(p, self.filter,
[self.filter, self.filename] + qargs, env,
os.path.dirname(self.filename))
class CGIProcessProtocol(protocol.ProcessProtocol, pb.Viewable):
handling_headers = 1
headers_written = 0
headertext = ''
errortext = ''
# Remotely relay producer interface.
def view_resumeProducing(self, issuer):
self.resumeProducing()
def view_pauseProducing(self, issuer):
self.pauseProducing()
def view_stopProducing(self, issuer):
self.stopProducing()
def resumeProducing(self):
self.transport.resumeProducing()
def pauseProducing(self):
self.transport.pauseProducing()
def stopProducing(self):
self.transport.loseConnection()
def __init__(self, request):
self.request = request
def connectionMade(self):
self.request.registerProducer(self, 1)
self.request.content.seek(0, 0)
content = self.request.content.read()
if content:
self.transport.write(content)
self.transport.closeStdin()
def errReceived(self, error):
self.errortext = self.errortext + error
def outReceived(self, output):
"""
Handle a chunk of input
"""
# First, make sure that the headers from the script are sorted
# out (we'll want to do some parsing on these later.)
if self.handling_headers:
text = self.headertext + output
headerEnds = []
for delimiter in '\n\n','\r\n\r\n','\r\r', '\n\r\n':
headerend = text.find(delimiter)
if headerend != -1:
headerEnds.append((headerend, delimiter))
if headerEnds:
# The script is entirely in control of response headers; disable the
# default Content-Type value normally provided by
# twisted.web.server.Request.
self.request.defaultContentType = None
headerEnds.sort()
headerend, delimiter = headerEnds[0]
self.headertext = text[:headerend]
# This is a final version of the header text.
linebreak = delimiter[:len(delimiter)//2]
headers = self.headertext.split(linebreak)
for header in headers:
br = header.find(': ')
if br == -1:
log.msg(
format='ignoring malformed CGI header: %(header)r',
header=header)
else:
headerName = header[:br].lower()
headerText = header[br+2:]
if headerName == 'location':
self.request.setResponseCode(http.FOUND)
if headerName == 'status':
try:
statusNum = int(headerText[:3]) #"XXX <description>" sometimes happens.
except:
log.msg( "malformed status header" )
else:
self.request.setResponseCode(statusNum)
else:
# Don't allow the application to control these required headers.
if headerName.lower() not in ('server', 'date'):
self.request.responseHeaders.addRawHeader(headerName, headerText)
output = text[headerend+len(delimiter):]
self.handling_headers = 0
if self.handling_headers:
self.headertext = text
if not self.handling_headers:
self.request.write(output)
def processEnded(self, reason):
if reason.value.exitCode != 0:
log.msg("CGI %s exited with exit code %s" %
(self.request.uri, reason.value.exitCode))
if self.errortext:
log.msg("Errors from CGI %s: %s" % (self.request.uri, self.errortext))
if self.handling_headers:
log.msg("Premature end of headers in %s: %s" % (self.request.uri, self.headertext))
self.request.write(
resource.ErrorPage(http.INTERNAL_SERVER_ERROR,
"CGI Script Error",
"Premature end of script headers.").render(self.request))
self.request.unregisterProducer()
self.request.finish()

View file

@ -0,0 +1,440 @@
# -*- test-case-name: twisted.web.test.test_util -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
An assortment of web server-related utilities.
"""
__all__ = [
"redirectTo", "Redirect", "ChildRedirector", "ParentRedirect",
"DeferredResource", "htmlIndent", "FailureElement", "formatFailure"]
from cStringIO import StringIO
import linecache
import types
from twisted.python.reflect import fullyQualifiedName
from twisted.python.deprecate import deprecatedModuleAttribute
from twisted.python.versions import Version
from twisted.python.modules import getModule
from twisted.web import html, resource
from twisted.web.template import (
TagLoader, XMLFile, Element, renderer, flattenString)
def redirectTo(URL, request):
"""
Generate a redirect to the given location.
@param URL: A C{str} giving the location to which to redirect.
@type URL: C{str}
@param request: The request object to use to generate the redirect.
@type request: L{IRequest<twisted.web.iweb.IRequest>} provider
@raise TypeError: If the type of C{URL} a C{unicode} instead of C{str}.
@return: A C{str} containing HTML which tries to convince the client agent
to visit the new location even if it doesn't respect the I{FOUND}
response code. This is intended to be returned from a render method,
eg::
def render_GET(self, request):
return redirectTo("http://example.com/", request)
"""
if isinstance(URL, unicode) :
raise TypeError("Unicode object not allowed as URL")
request.setHeader("content-type", "text/html; charset=utf-8")
request.redirect(URL)
return """
<html>
<head>
<meta http-equiv=\"refresh\" content=\"0;URL=%(url)s\">
</head>
<body bgcolor=\"#FFFFFF\" text=\"#000000\">
<a href=\"%(url)s\">click here</a>
</body>
</html>
""" % {'url': URL}
class Redirect(resource.Resource):
isLeaf = 1
def __init__(self, url):
resource.Resource.__init__(self)
self.url = url
def render(self, request):
return redirectTo(self.url, request)
def getChild(self, name, request):
return self
class ChildRedirector(Redirect):
isLeaf = 0
def __init__(self, url):
# XXX is this enough?
if ((url.find('://') == -1)
and (not url.startswith('..'))
and (not url.startswith('/'))):
raise ValueError("It seems you've given me a redirect (%s) that is a child of myself! That's not good, it'll cause an infinite redirect." % url)
Redirect.__init__(self, url)
def getChild(self, name, request):
newUrl = self.url
if not newUrl.endswith('/'):
newUrl += '/'
newUrl += name
return ChildRedirector(newUrl)
from twisted.python import urlpath
class ParentRedirect(resource.Resource):
"""
I redirect to URLPath.here().
"""
isLeaf = 1
def render(self, request):
return redirectTo(urlpath.URLPath.fromRequest(request).here(), request)
def getChild(self, request):
return self
class DeferredResource(resource.Resource):
"""
I wrap up a Deferred that will eventually result in a Resource
object.
"""
isLeaf = 1
def __init__(self, d):
resource.Resource.__init__(self)
self.d = d
def getChild(self, name, request):
return self
def render(self, request):
self.d.addCallback(self._cbChild, request).addErrback(
self._ebChild,request)
from twisted.web.server import NOT_DONE_YET
return NOT_DONE_YET
def _cbChild(self, child, request):
request.render(resource.getChildForRequest(child, request))
def _ebChild(self, reason, request):
request.processingFailed(reason)
return reason
stylesheet = ""
def htmlrepr(x):
return htmlReprTypes.get(type(x), htmlUnknown)(x)
def saferepr(x):
try:
rx = repr(x)
except:
rx = "<repr failed! %s instance at %s>" % (x.__class__, id(x))
return rx
def htmlUnknown(x):
return '<code>'+html.escape(saferepr(x))+'</code>'
def htmlDict(d):
io = StringIO()
w = io.write
w('<div class="dict"><span class="heading">Dictionary instance @ %s</span>' % hex(id(d)))
w('<table class="dict">')
for k, v in d.items():
if k == '__builtins__':
v = 'builtin dictionary'
w('<tr><td class="dictKey">%s</td><td class="dictValue">%s</td></tr>' % (htmlrepr(k), htmlrepr(v)))
w('</table></div>')
return io.getvalue()
def htmlList(l):
io = StringIO()
w = io.write
w('<div class="list"><span class="heading">List instance @ %s</span>' % hex(id(l)))
for i in l:
w('<div class="listItem">%s</div>' % htmlrepr(i))
w('</div>')
return io.getvalue()
def htmlInst(i):
if hasattr(i, "__html__"):
s = i.__html__()
else:
s = html.escape(saferepr(i))
return '''<div class="instance"><span class="instanceName">%s instance @ %s</span>
<span class="instanceRepr">%s</span></div>
''' % (i.__class__, hex(id(i)), s)
def htmlString(s):
return html.escape(saferepr(s))
def htmlFunc(f):
return ('<div class="function">' +
html.escape("function %s in file %s at line %s" %
(f.__name__, f.func_code.co_filename,
f.func_code.co_firstlineno))+
'</div>')
htmlReprTypes = {types.DictType: htmlDict,
types.ListType: htmlList,
types.InstanceType: htmlInst,
types.StringType: htmlString,
types.FunctionType: htmlFunc}
def htmlIndent(snippetLine):
"""
Strip trailing whitespace, escape HTML entitities and expand indentation
whitespace to HTML non-breaking space.
@param snippetLine: The line of input to indent.
@type snippetLine: L{bytes}
@return: The escaped and indented line.
"""
ret = (html.escape(snippetLine.rstrip())
.replace(' ', '&nbsp;')
.replace('\t', '&nbsp; &nbsp; &nbsp; &nbsp; '))
return ret
class _SourceLineElement(Element):
"""
L{_SourceLineElement} is an L{IRenderable} which can render a single line of
source code.
@ivar number: A C{int} giving the line number of the source code to be
rendered.
@ivar source: A C{str} giving the source code to be rendered.
"""
def __init__(self, loader, number, source):
Element.__init__(self, loader)
self.number = number
self.source = source
@renderer
def sourceLine(self, request, tag):
"""
Render the line of source as a child of C{tag}.
"""
return tag(self.source.replace(' ', u' \N{NO-BREAK SPACE}'))
@renderer
def lineNumber(self, request, tag):
"""
Render the line number as a child of C{tag}.
"""
return tag(str(self.number))
class _SourceFragmentElement(Element):
"""
L{_SourceFragmentElement} is an L{IRenderable} which can render several lines
of source code near the line number of a particular frame object.
@ivar frame: A L{Failure<twisted.python.failure.Failure>}-style frame object
for which to load a source line to render. This is really a tuple
holding some information from a frame object. See
L{Failure.frames<twisted.python.failure.Failure>} for specifics.
"""
def __init__(self, loader, frame):
Element.__init__(self, loader)
self.frame = frame
def _getSourceLines(self):
"""
Find the source line references by C{self.frame} and yield, in source
line order, it and the previous and following lines.
@return: A generator which yields two-tuples. Each tuple gives a source
line number and the contents of that source line.
"""
filename = self.frame[1]
lineNumber = self.frame[2]
for snipLineNumber in range(lineNumber - 1, lineNumber + 2):
yield (snipLineNumber,
linecache.getline(filename, snipLineNumber).rstrip())
@renderer
def sourceLines(self, request, tag):
"""
Render the source line indicated by C{self.frame} and several
surrounding lines. The active line will be given a I{class} of
C{"snippetHighlightLine"}. Other lines will be given a I{class} of
C{"snippetLine"}.
"""
for (lineNumber, sourceLine) in self._getSourceLines():
newTag = tag.clone()
if lineNumber == self.frame[2]:
cssClass = "snippetHighlightLine"
else:
cssClass = "snippetLine"
loader = TagLoader(newTag(**{"class": cssClass}))
yield _SourceLineElement(loader, lineNumber, sourceLine)
class _FrameElement(Element):
"""
L{_FrameElement} is an L{IRenderable} which can render details about one
frame from a L{Failure<twisted.python.failure.Failure>}.
@ivar frame: A L{Failure<twisted.python.failure.Failure>}-style frame object
for which to load a source line to render. This is really a tuple
holding some information from a frame object. See
L{Failure.frames<twisted.python.failure.Failure>} for specifics.
"""
def __init__(self, loader, frame):
Element.__init__(self, loader)
self.frame = frame
@renderer
def filename(self, request, tag):
"""
Render the name of the file this frame references as a child of C{tag}.
"""
return tag(self.frame[1])
@renderer
def lineNumber(self, request, tag):
"""
Render the source line number this frame references as a child of
C{tag}.
"""
return tag(str(self.frame[2]))
@renderer
def function(self, request, tag):
"""
Render the function name this frame references as a child of C{tag}.
"""
return tag(self.frame[0])
@renderer
def source(self, request, tag):
"""
Render the source code surrounding the line this frame references,
replacing C{tag}.
"""
return _SourceFragmentElement(TagLoader(tag), self.frame)
class _StackElement(Element):
"""
L{_StackElement} renders an L{IRenderable} which can render a list of frames.
"""
def __init__(self, loader, stackFrames):
Element.__init__(self, loader)
self.stackFrames = stackFrames
@renderer
def frames(self, request, tag):
"""
Render the list of frames in this L{_StackElement}, replacing C{tag}.
"""
return [
_FrameElement(TagLoader(tag.clone()), frame)
for frame
in self.stackFrames]
class FailureElement(Element):
"""
L{FailureElement} is an L{IRenderable} which can render detailed information
about a L{Failure<twisted.python.failure.Failure>}.
@ivar failure: The L{Failure<twisted.python.failure.Failure>} instance which
will be rendered.
@since: 12.1
"""
loader = XMLFile(getModule(__name__).filePath.sibling("failure.xhtml"))
def __init__(self, failure, loader=None):
Element.__init__(self, loader)
self.failure = failure
@renderer
def type(self, request, tag):
"""
Render the exception type as a child of C{tag}.
"""
return tag(fullyQualifiedName(self.failure.type))
@renderer
def value(self, request, tag):
"""
Render the exception value as a child of C{tag}.
"""
return tag(str(self.failure.value))
@renderer
def traceback(self, request, tag):
"""
Render all the frames in the wrapped
L{Failure<twisted.python.failure.Failure>}'s traceback stack, replacing
C{tag}.
"""
return _StackElement(TagLoader(tag), self.failure.frames)
def formatFailure(myFailure):
"""
Construct an HTML representation of the given failure.
Consider using L{FailureElement} instead.
@type myFailure: L{Failure<twisted.python.failure.Failure>}
@rtype: C{str}
@return: A string containing the HTML representation of the given failure.
"""
result = []
flattenString(None, FailureElement(myFailure)).addBoth(result.append)
if isinstance(result[0], str):
# Ensure the result string is all ASCII, for compatibility with the
# default encoding expected by browsers.
return result[0].decode('utf-8').encode('ascii', 'xmlcharrefreplace')
result[0].raiseException()
_twelveOne = Version("Twisted", 12, 1, 0)
for name in ["htmlrepr", "saferepr", "htmlUnknown", "htmlString", "htmlList",
"htmlDict", "htmlInst", "htmlFunc", "htmlIndent", "htmlReprTypes",
"stylesheet"]:
deprecatedModuleAttribute(
_twelveOne, "See twisted.web.template.", __name__, name)
del name

View file

@ -0,0 +1,135 @@
# -*- test-case-name: twisted.web.
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
I am a virtual hosts implementation.
"""
# Twisted Imports
from twisted.python import roots
from twisted.web import resource
class VirtualHostCollection(roots.Homogenous):
"""Wrapper for virtual hosts collection.
This exists for configuration purposes.
"""
entityType = resource.Resource
def __init__(self, nvh):
self.nvh = nvh
def listStaticEntities(self):
return self.nvh.hosts.items()
def getStaticEntity(self, name):
return self.nvh.hosts.get(self)
def reallyPutEntity(self, name, entity):
self.nvh.addHost(name, entity)
def delEntity(self, name):
self.nvh.removeHost(name)
class NameVirtualHost(resource.Resource):
"""I am a resource which represents named virtual hosts.
"""
default = None
def __init__(self):
"""Initialize.
"""
resource.Resource.__init__(self)
self.hosts = {}
def listStaticEntities(self):
return resource.Resource.listStaticEntities(self) + [("Virtual Hosts", VirtualHostCollection(self))]
def getStaticEntity(self, name):
if name == "Virtual Hosts":
return VirtualHostCollection(self)
else:
return resource.Resource.getStaticEntity(self, name)
def addHost(self, name, resrc):
"""Add a host to this virtual host.
This will take a host named `name', and map it to a resource
`resrc'. For example, a setup for our virtual hosts would be::
nvh.addHost('divunal.com', divunalDirectory)
nvh.addHost('www.divunal.com', divunalDirectory)
nvh.addHost('twistedmatrix.com', twistedMatrixDirectory)
nvh.addHost('www.twistedmatrix.com', twistedMatrixDirectory)
"""
self.hosts[name] = resrc
def removeHost(self, name):
"""Remove a host."""
del self.hosts[name]
def _getResourceForRequest(self, request):
"""(Internal) Get the appropriate resource for the given host.
"""
hostHeader = request.getHeader('host')
if hostHeader == None:
return self.default or resource.NoResource()
else:
host = hostHeader.lower().split(':', 1)[0]
return (self.hosts.get(host, self.default)
or resource.NoResource("host %s not in vhost map" % repr(host)))
def render(self, request):
"""Implementation of resource.Resource's render method.
"""
resrc = self._getResourceForRequest(request)
return resrc.render(request)
def getChild(self, path, request):
"""Implementation of resource.Resource's getChild method.
"""
resrc = self._getResourceForRequest(request)
if resrc.isLeaf:
request.postpath.insert(0,request.prepath.pop(-1))
return resrc
else:
return resrc.getChildWithDefault(path, request)
class _HostResource(resource.Resource):
def getChild(self, path, request):
if ':' in path:
host, port = path.split(':', 1)
port = int(port)
else:
host, port = path, 80
request.setHost(host, port)
prefixLen = 3+request.isSecure()+4+len(path)+len(request.prepath[-3])
request.path = '/'+'/'.join(request.postpath)
request.uri = request.uri[prefixLen:]
del request.prepath[:3]
return request.site.getResourceFor(request)
class VHostMonsterResource(resource.Resource):
"""
Use this to be able to record the hostname and method (http vs. https)
in the URL without disturbing your web site. If you put this resource
in a URL http://foo.com/bar then requests to
http://foo.com/bar/http/baz.com/something will be equivalent to
http://foo.com/something, except that the hostname the request will
appear to be accessing will be "baz.com". So if "baz.com" is redirecting
all requests for to foo.com, while foo.com is inaccessible from the outside,
then redirect and url generation will work correctly
"""
def getChild(self, path, request):
if path == 'http':
request.isSecure = lambda: 0
elif path == 'https':
request.isSecure = lambda: 1
return _HostResource()

View file

@ -0,0 +1,403 @@
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
An implementation of
U{Web Resource Gateway Interface<http://www.python.org/dev/peps/pep-0333/>}.
"""
__metaclass__ = type
from sys import exc_info
from zope.interface import implements
from twisted.python.log import msg, err
from twisted.python.failure import Failure
from twisted.web.resource import IResource
from twisted.web.server import NOT_DONE_YET
from twisted.web.http import INTERNAL_SERVER_ERROR
class _ErrorStream:
"""
File-like object instances of which are used as the value for the
C{'wsgi.errors'} key in the C{environ} dictionary passed to the application
object.
This simply passes writes on to L{logging<twisted.python.log>} system as
error events from the C{'wsgi'} system. In the future, it may be desirable
to expose more information in the events it logs, such as the application
object which generated the message.
"""
def write(self, bytes):
"""
Generate an event for the logging system with the given bytes as the
message.
This is called in a WSGI application thread, not the I/O thread.
"""
msg(bytes, system='wsgi', isError=True)
def writelines(self, iovec):
"""
Join the given lines and pass them to C{write} to be handled in the
usual way.
This is called in a WSGI application thread, not the I/O thread.
@param iovec: A C{list} of C{'\\n'}-terminated C{str} which will be
logged.
"""
self.write(''.join(iovec))
def flush(self):
"""
Nothing is buffered, so flushing does nothing. This method is required
to exist by PEP 333, though.
This is called in a WSGI application thread, not the I/O thread.
"""
class _InputStream:
"""
File-like object instances of which are used as the value for the
C{'wsgi.input'} key in the C{environ} dictionary passed to the application
object.
This only exists to make the handling of C{readline(-1)} consistent across
different possible underlying file-like object implementations. The other
supported methods pass through directly to the wrapped object.
"""
def __init__(self, input):
"""
Initialize the instance.
This is called in the I/O thread, not a WSGI application thread.
"""
self._wrapped = input
def read(self, size=None):
"""
Pass through to the underlying C{read}.
This is called in a WSGI application thread, not the I/O thread.
"""
# Avoid passing None because cStringIO and file don't like it.
if size is None:
return self._wrapped.read()
return self._wrapped.read(size)
def readline(self, size=None):
"""
Pass through to the underlying C{readline}, with a size of C{-1} replaced
with a size of C{None}.
This is called in a WSGI application thread, not the I/O thread.
"""
# Check for -1 because StringIO doesn't handle it correctly. Check for
# None because files and tempfiles don't accept that.
if size == -1 or size is None:
return self._wrapped.readline()
return self._wrapped.readline(size)
def readlines(self, size=None):
"""
Pass through to the underlying C{readlines}.
This is called in a WSGI application thread, not the I/O thread.
"""
# Avoid passing None because cStringIO and file don't like it.
if size is None:
return self._wrapped.readlines()
return self._wrapped.readlines(size)
def __iter__(self):
"""
Pass through to the underlying C{__iter__}.
This is called in a WSGI application thread, not the I/O thread.
"""
return iter(self._wrapped)
class _WSGIResponse:
"""
Helper for L{WSGIResource} which drives the WSGI application using a
threadpool and hooks it up to the L{Request}.
@ivar started: A C{bool} indicating whether or not the response status and
headers have been written to the request yet. This may only be read or
written in the WSGI application thread.
@ivar reactor: An L{IReactorThreads} provider which is used to call methods
on the request in the I/O thread.
@ivar threadpool: A L{ThreadPool} which is used to call the WSGI
application object in a non-I/O thread.
@ivar application: The WSGI application object.
@ivar request: The L{Request} upon which the WSGI environment is based and
to which the application's output will be sent.
@ivar environ: The WSGI environment C{dict}.
@ivar status: The HTTP response status C{str} supplied to the WSGI
I{start_response} callable by the application.
@ivar headers: A list of HTTP response headers supplied to the WSGI
I{start_response} callable by the application.
@ivar _requestFinished: A flag which indicates whether it is possible to
generate more response data or not. This is C{False} until
L{Request.notifyFinish} tells us the request is done, then C{True}.
"""
_requestFinished = False
def __init__(self, reactor, threadpool, application, request):
self.started = False
self.reactor = reactor
self.threadpool = threadpool
self.application = application
self.request = request
self.request.notifyFinish().addBoth(self._finished)
if request.prepath:
scriptName = '/' + '/'.join(request.prepath)
else:
scriptName = ''
if request.postpath:
pathInfo = '/' + '/'.join(request.postpath)
else:
pathInfo = ''
parts = request.uri.split('?', 1)
if len(parts) == 1:
queryString = ''
else:
queryString = parts[1]
self.environ = {
'REQUEST_METHOD': request.method,
'REMOTE_ADDR': request.getClientIP(),
'SCRIPT_NAME': scriptName,
'PATH_INFO': pathInfo,
'QUERY_STRING': queryString,
'CONTENT_TYPE': request.getHeader('content-type') or '',
'CONTENT_LENGTH': request.getHeader('content-length') or '',
'SERVER_NAME': request.getRequestHostname(),
'SERVER_PORT': str(request.getHost().port),
'SERVER_PROTOCOL': request.clientproto}
# The application object is entirely in control of response headers;
# disable the default Content-Type value normally provided by
# twisted.web.server.Request.
self.request.defaultContentType = None
for name, values in request.requestHeaders.getAllRawHeaders():
name = 'HTTP_' + name.upper().replace('-', '_')
# It might be preferable for http.HTTPChannel to clear out
# newlines.
self.environ[name] = ','.join([
v.replace('\n', ' ') for v in values])
self.environ.update({
'wsgi.version': (1, 0),
'wsgi.url_scheme': request.isSecure() and 'https' or 'http',
'wsgi.run_once': False,
'wsgi.multithread': True,
'wsgi.multiprocess': False,
'wsgi.errors': _ErrorStream(),
# Attend: request.content was owned by the I/O thread up until
# this point. By wrapping it and putting the result into the
# environment dictionary, it is effectively being given to
# another thread. This means that whatever it is, it has to be
# safe to access it from two different threads. The access
# *should* all be serialized (first the I/O thread writes to
# it, then the WSGI thread reads from it, then the I/O thread
# closes it). However, since the request is made available to
# arbitrary application code during resource traversal, it's
# possible that some other code might decide to use it in the
# I/O thread concurrently with its use in the WSGI thread.
# More likely than not, this will break. This seems like an
# unlikely possibility to me, but if it is to be allowed,
# something here needs to change. -exarkun
'wsgi.input': _InputStream(request.content)})
def _finished(self, ignored):
"""
Record the end of the response generation for the request being
serviced.
"""
self._requestFinished = True
def startResponse(self, status, headers, excInfo=None):
"""
The WSGI I{start_response} callable. The given values are saved until
they are needed to generate the response.
This will be called in a non-I/O thread.
"""
if self.started and excInfo is not None:
raise excInfo[0], excInfo[1], excInfo[2]
self.status = status
self.headers = headers
return self.write
def write(self, bytes):
"""
The WSGI I{write} callable returned by the I{start_response} callable.
The given bytes will be written to the response body, possibly flushing
the status and headers first.
This will be called in a non-I/O thread.
"""
def wsgiWrite(started):
if not started:
self._sendResponseHeaders()
self.request.write(bytes)
self.reactor.callFromThread(wsgiWrite, self.started)
self.started = True
def _sendResponseHeaders(self):
"""
Set the response code and response headers on the request object, but
do not flush them. The caller is responsible for doing a write in
order for anything to actually be written out in response to the
request.
This must be called in the I/O thread.
"""
code, message = self.status.split(None, 1)
code = int(code)
self.request.setResponseCode(code, message)
for name, value in self.headers:
# Don't allow the application to control these required headers.
if name.lower() not in ('server', 'date'):
self.request.responseHeaders.addRawHeader(name, value)
def start(self):
"""
Start the WSGI application in the threadpool.
This must be called in the I/O thread.
"""
self.threadpool.callInThread(self.run)
def run(self):
"""
Call the WSGI application object, iterate it, and handle its output.
This must be called in a non-I/O thread (ie, a WSGI application
thread).
"""
try:
appIterator = self.application(self.environ, self.startResponse)
for elem in appIterator:
if elem:
self.write(elem)
if self._requestFinished:
break
close = getattr(appIterator, 'close', None)
if close is not None:
close()
except:
def wsgiError(started, type, value, traceback):
err(Failure(value, type, traceback), "WSGI application error")
if started:
self.request.transport.loseConnection()
else:
self.request.setResponseCode(INTERNAL_SERVER_ERROR)
self.request.finish()
self.reactor.callFromThread(wsgiError, self.started, *exc_info())
else:
def wsgiFinish(started):
if not self._requestFinished:
if not started:
self._sendResponseHeaders()
self.request.finish()
self.reactor.callFromThread(wsgiFinish, self.started)
self.started = True
class WSGIResource:
"""
An L{IResource} implementation which delegates responsibility for all
resources hierarchically inferior to it to a WSGI application.
@ivar _reactor: An L{IReactorThreads} provider which will be passed on to
L{_WSGIResponse} to schedule calls in the I/O thread.
@ivar _threadpool: A L{ThreadPool} which will be passed on to
L{_WSGIResponse} to run the WSGI application object.
@ivar _application: The WSGI application object.
"""
implements(IResource)
# Further resource segments are left up to the WSGI application object to
# handle.
isLeaf = True
def __init__(self, reactor, threadpool, application):
self._reactor = reactor
self._threadpool = threadpool
self._application = application
def render(self, request):
"""
Turn the request into the appropriate C{environ} C{dict} suitable to be
passed to the WSGI application object and then pass it on.
The WSGI application object is given almost complete control of the
rendering process. C{NOT_DONE_YET} will always be returned in order
and response completion will be dictated by the application object, as
will the status, headers, and the response body.
"""
response = _WSGIResponse(
self._reactor, self._threadpool, self._application, request)
response.start()
return NOT_DONE_YET
def getChildWithDefault(self, name, request):
"""
Reject attempts to retrieve a child resource. All path segments beyond
the one which refers to this resource are handled by the WSGI
application object.
"""
raise RuntimeError("Cannot get IResource children from WSGIResource")
def putChild(self, path, child):
"""
Reject attempts to add a child resource to this resource. The WSGI
application object handles all path segments beneath this resource, so
L{IResource} children can never be found.
"""
raise RuntimeError("Cannot put IResource children under WSGIResource")
__all__ = ['WSGIResource']

View file

@ -0,0 +1,572 @@
# -*- test-case-name: twisted.web.test.test_xmlrpc -*-
# Copyright (c) Twisted Matrix Laboratories.
# See LICENSE for details.
"""
A generic resource for publishing objects via XML-RPC.
Maintainer: Itamar Shtull-Trauring
"""
# System Imports
import base64
import xmlrpclib
import urlparse
# Sibling Imports
from twisted.web import resource, server, http
from twisted.internet import defer, protocol, reactor
from twisted.python import log, reflect, failure
# These are deprecated, use the class level definitions
NOT_FOUND = 8001
FAILURE = 8002
# Useful so people don't need to import xmlrpclib directly
Fault = xmlrpclib.Fault
Binary = xmlrpclib.Binary
Boolean = xmlrpclib.Boolean
DateTime = xmlrpclib.DateTime
def withRequest(f):
"""
Decorator to cause the request to be passed as the first argument
to the method.
If an I{xmlrpc_} method is wrapped with C{withRequest}, the
request object is passed as the first argument to that method.
For example::
@withRequest
def xmlrpc_echo(self, request, s):
return s
@since: 10.2
"""
f.withRequest = True
return f
class NoSuchFunction(Fault):
"""
There is no function by the given name.
"""
class Handler:
"""
Handle a XML-RPC request and store the state for a request in progress.
Override the run() method and return result using self.result,
a Deferred.
We require this class since we're not using threads, so we can't
encapsulate state in a running function if we're going to have
to wait for results.
For example, lets say we want to authenticate against twisted.cred,
run a LDAP query and then pass its result to a database query, all
as a result of a single XML-RPC command. We'd use a Handler instance
to store the state of the running command.
"""
def __init__(self, resource, *args):
self.resource = resource # the XML-RPC resource we are connected to
self.result = defer.Deferred()
self.run(*args)
def run(self, *args):
# event driven equivalent of 'raise UnimplementedError'
self.result.errback(
NotImplementedError("Implement run() in subclasses"))
class XMLRPC(resource.Resource):
"""
A resource that implements XML-RPC.
You probably want to connect this to '/RPC2'.
Methods published can return XML-RPC serializable results, Faults,
Binary, Boolean, DateTime, Deferreds, or Handler instances.
By default methods beginning with 'xmlrpc_' are published.
Sub-handlers for prefixed methods (e.g., system.listMethods)
can be added with putSubHandler. By default, prefixes are
separated with a '.'. Override self.separator to change this.
@ivar allowNone: Permit XML translating of Python constant None.
@type allowNone: C{bool}
@ivar useDateTime: Present C{datetime} values as C{datetime.datetime}
objects?
@type useDateTime: C{bool}
"""
# Error codes for Twisted, if they conflict with yours then
# modify them at runtime.
NOT_FOUND = 8001
FAILURE = 8002
isLeaf = 1
separator = '.'
allowedMethods = ('POST',)
def __init__(self, allowNone=False, useDateTime=False):
resource.Resource.__init__(self)
self.subHandlers = {}
self.allowNone = allowNone
self.useDateTime = useDateTime
def __setattr__(self, name, value):
self.__dict__[name] = value
def putSubHandler(self, prefix, handler):
self.subHandlers[prefix] = handler
def getSubHandler(self, prefix):
return self.subHandlers.get(prefix, None)
def getSubHandlerPrefixes(self):
return self.subHandlers.keys()
def render_POST(self, request):
request.content.seek(0, 0)
request.setHeader("content-type", "text/xml")
try:
args, functionPath = xmlrpclib.loads(request.content.read(),
use_datetime=self.useDateTime)
except Exception, e:
f = Fault(self.FAILURE, "Can't deserialize input: %s" % (e,))
self._cbRender(f, request)
else:
try:
function = self.lookupProcedure(functionPath)
except Fault, f:
self._cbRender(f, request)
else:
# Use this list to track whether the response has failed or not.
# This will be used later on to decide if the result of the
# Deferred should be written out and Request.finish called.
responseFailed = []
request.notifyFinish().addErrback(responseFailed.append)
if getattr(function, 'withRequest', False):
d = defer.maybeDeferred(function, request, *args)
else:
d = defer.maybeDeferred(function, *args)
d.addErrback(self._ebRender)
d.addCallback(self._cbRender, request, responseFailed)
return server.NOT_DONE_YET
def _cbRender(self, result, request, responseFailed=None):
if responseFailed:
return
if isinstance(result, Handler):
result = result.result
if not isinstance(result, Fault):
result = (result,)
try:
try:
content = xmlrpclib.dumps(
result, methodresponse=True,
allow_none=self.allowNone)
except Exception, e:
f = Fault(self.FAILURE, "Can't serialize output: %s" % (e,))
content = xmlrpclib.dumps(f, methodresponse=True,
allow_none=self.allowNone)
request.setHeader("content-length", str(len(content)))
request.write(content)
except:
log.err()
request.finish()
def _ebRender(self, failure):
if isinstance(failure.value, Fault):
return failure.value
log.err(failure)
return Fault(self.FAILURE, "error")
def lookupProcedure(self, procedurePath):
"""
Given a string naming a procedure, return a callable object for that
procedure or raise NoSuchFunction.
The returned object will be called, and should return the result of the
procedure, a Deferred, or a Fault instance.
Override in subclasses if you want your own policy. The base
implementation that given C{'foo'}, C{self.xmlrpc_foo} will be returned.
If C{procedurePath} contains C{self.separator}, the sub-handler for the
initial prefix is used to search for the remaining path.
If you override C{lookupProcedure}, you may also want to override
C{listProcedures} to accurately report the procedures supported by your
resource, so that clients using the I{system.listMethods} procedure
receive accurate results.
@since: 11.1
"""
if procedurePath.find(self.separator) != -1:
prefix, procedurePath = procedurePath.split(self.separator, 1)
handler = self.getSubHandler(prefix)
if handler is None:
raise NoSuchFunction(self.NOT_FOUND,
"no such subHandler %s" % prefix)
return handler.lookupProcedure(procedurePath)
f = getattr(self, "xmlrpc_%s" % procedurePath, None)
if not f:
raise NoSuchFunction(self.NOT_FOUND,
"procedure %s not found" % procedurePath)
elif not callable(f):
raise NoSuchFunction(self.NOT_FOUND,
"procedure %s not callable" % procedurePath)
else:
return f
def listProcedures(self):
"""
Return a list of the names of all xmlrpc procedures.
@since: 11.1
"""
return reflect.prefixedMethodNames(self.__class__, 'xmlrpc_')
class XMLRPCIntrospection(XMLRPC):
"""
Implement the XML-RPC Introspection API.
By default, the methodHelp method returns the 'help' method attribute,
if it exists, otherwise the __doc__ method attribute, if it exists,
otherwise the empty string.
To enable the methodSignature method, add a 'signature' method attribute
containing a list of lists. See methodSignature's documentation for the
format. Note the type strings should be XML-RPC types, not Python types.
"""
def __init__(self, parent):
"""
Implement Introspection support for an XMLRPC server.
@param parent: the XMLRPC server to add Introspection support to.
@type parent: L{XMLRPC}
"""
XMLRPC.__init__(self)
self._xmlrpc_parent = parent
def xmlrpc_listMethods(self):
"""
Return a list of the method names implemented by this server.
"""
functions = []
todo = [(self._xmlrpc_parent, '')]
while todo:
obj, prefix = todo.pop(0)
functions.extend([prefix + name for name in obj.listProcedures()])
todo.extend([ (obj.getSubHandler(name),
prefix + name + obj.separator)
for name in obj.getSubHandlerPrefixes() ])
return functions
xmlrpc_listMethods.signature = [['array']]
def xmlrpc_methodHelp(self, method):
"""
Return a documentation string describing the use of the given method.
"""
method = self._xmlrpc_parent.lookupProcedure(method)
return (getattr(method, 'help', None)
or getattr(method, '__doc__', None) or '')
xmlrpc_methodHelp.signature = [['string', 'string']]
def xmlrpc_methodSignature(self, method):
"""
Return a list of type signatures.
Each type signature is a list of the form [rtype, type1, type2, ...]
where rtype is the return type and typeN is the type of the Nth
argument. If no signature information is available, the empty
string is returned.
"""
method = self._xmlrpc_parent.lookupProcedure(method)
return getattr(method, 'signature', None) or ''
xmlrpc_methodSignature.signature = [['array', 'string'],
['string', 'string']]
def addIntrospection(xmlrpc):
"""
Add Introspection support to an XMLRPC server.
@param parent: the XMLRPC server to add Introspection support to.
@type parent: L{XMLRPC}
"""
xmlrpc.putSubHandler('system', XMLRPCIntrospection(xmlrpc))
class QueryProtocol(http.HTTPClient):
def connectionMade(self):
self._response = None
self.sendCommand('POST', self.factory.path)
self.sendHeader('User-Agent', 'Twisted/XMLRPClib')
self.sendHeader('Host', self.factory.host)
self.sendHeader('Content-type', 'text/xml')
self.sendHeader('Content-length', str(len(self.factory.payload)))
if self.factory.user:
auth = '%s:%s' % (self.factory.user, self.factory.password)
auth = base64.b64encode(auth)
self.sendHeader('Authorization', 'Basic %s' % (auth,))
self.endHeaders()
self.transport.write(self.factory.payload)
def handleStatus(self, version, status, message):
if status != '200':
self.factory.badStatus(status, message)
def handleResponse(self, contents):
"""
Handle the XML-RPC response received from the server.
Specifically, disconnect from the server and store the XML-RPC
response so that it can be properly handled when the disconnect is
finished.
"""
self.transport.loseConnection()
self._response = contents
def connectionLost(self, reason):
"""
The connection to the server has been lost.
If we have a full response from the server, then parse it and fired a
Deferred with the return value or C{Fault} that the server gave us.
"""
http.HTTPClient.connectionLost(self, reason)
if self._response is not None:
response, self._response = self._response, None
self.factory.parseResponse(response)
payloadTemplate = """<?xml version="1.0"?>
<methodCall>
<methodName>%s</methodName>
%s
</methodCall>
"""
class _QueryFactory(protocol.ClientFactory):
"""
XML-RPC Client Factory
@ivar path: The path portion of the URL to which to post method calls.
@type path: C{str}
@ivar host: The value to use for the Host HTTP header.
@type host: C{str}
@ivar user: The username with which to authenticate with the server
when making calls.
@type user: C{str} or C{NoneType}
@ivar password: The password with which to authenticate with the server
when making calls.
@type password: C{str} or C{NoneType}
@ivar useDateTime: Accept datetime values as datetime.datetime objects.
also passed to the underlying xmlrpclib implementation. Defaults to
C{False}.
@type useDateTime: C{bool}
"""
deferred = None
protocol = QueryProtocol
def __init__(self, path, host, method, user=None, password=None,
allowNone=False, args=(), canceller=None, useDateTime=False):
"""
@param method: The name of the method to call.
@type method: C{str}
@param allowNone: allow the use of None values in parameters. It's
passed to the underlying xmlrpclib implementation. Defaults to
C{False}.
@type allowNone: C{bool} or C{NoneType}
@param args: the arguments to pass to the method.
@type args: C{tuple}
@param canceller: A 1-argument callable passed to the deferred as the
canceller callback.
@type canceller: callable or C{NoneType}
"""
self.path, self.host = path, host
self.user, self.password = user, password
self.payload = payloadTemplate % (method,
xmlrpclib.dumps(args, allow_none=allowNone))
self.deferred = defer.Deferred(canceller)
self.useDateTime = useDateTime
def parseResponse(self, contents):
if not self.deferred:
return
try:
response = xmlrpclib.loads(contents,
use_datetime=self.useDateTime)[0][0]
except:
deferred, self.deferred = self.deferred, None
deferred.errback(failure.Failure())
else:
deferred, self.deferred = self.deferred, None
deferred.callback(response)
def clientConnectionLost(self, _, reason):
if self.deferred is not None:
deferred, self.deferred = self.deferred, None
deferred.errback(reason)
clientConnectionFailed = clientConnectionLost
def badStatus(self, status, message):
deferred, self.deferred = self.deferred, None
deferred.errback(ValueError(status, message))
class Proxy:
"""
A Proxy for making remote XML-RPC calls.
Pass the URL of the remote XML-RPC server to the constructor.
Use C{proxy.callRemote('foobar', *args)} to call remote method
'foobar' with *args.
@ivar user: The username with which to authenticate with the server
when making calls. If specified, overrides any username information
embedded in C{url}. If not specified, a value may be taken from
C{url} if present.
@type user: C{str} or C{NoneType}
@ivar password: The password with which to authenticate with the server
when making calls. If specified, overrides any password information
embedded in C{url}. If not specified, a value may be taken from
C{url} if present.
@type password: C{str} or C{NoneType}
@ivar allowNone: allow the use of None values in parameters. It's
passed to the underlying L{xmlrpclib} implementation. Defaults to
C{False}.
@type allowNone: C{bool} or C{NoneType}
@ivar useDateTime: Accept datetime values as datetime.datetime objects.
also passed to the underlying L{xmlrpclib} implementation. Defaults to
C{False}.
@type useDateTime: C{bool}
@ivar connectTimeout: Number of seconds to wait before assuming the
connection has failed.
@type connectTimeout: C{float}
@ivar _reactor: The reactor used to create connections.
@type _reactor: Object providing L{twisted.internet.interfaces.IReactorTCP}
@ivar queryFactory: Object returning a factory for XML-RPC protocol. Mainly
useful for tests.
"""
queryFactory = _QueryFactory
def __init__(self, url, user=None, password=None, allowNone=False,
useDateTime=False, connectTimeout=30.0, reactor=reactor):
"""
@param url: The URL to which to post method calls. Calls will be made
over SSL if the scheme is HTTPS. If netloc contains username or
password information, these will be used to authenticate, as long as
the C{user} and C{password} arguments are not specified.
@type url: C{str}
"""
scheme, netloc, path, params, query, fragment = urlparse.urlparse(url)
netlocParts = netloc.split('@')
if len(netlocParts) == 2:
userpass = netlocParts.pop(0).split(':')
self.user = userpass.pop(0)
try:
self.password = userpass.pop(0)
except:
self.password = None
else:
self.user = self.password = None
hostport = netlocParts[0].split(':')
self.host = hostport.pop(0)
try:
self.port = int(hostport.pop(0))
except:
self.port = None
self.path = path
if self.path in ['', None]:
self.path = '/'
self.secure = (scheme == 'https')
if user is not None:
self.user = user
if password is not None:
self.password = password
self.allowNone = allowNone
self.useDateTime = useDateTime
self.connectTimeout = connectTimeout
self._reactor = reactor
def callRemote(self, method, *args):
"""
Call remote XML-RPC C{method} with given arguments.
@return: a L{defer.Deferred} that will fire with the method response,
or a failure if the method failed. Generally, the failure type will
be L{Fault}, but you can also have an C{IndexError} on some buggy
servers giving empty responses.
If the deferred is cancelled before the request completes, the
connection is closed and the deferred will fire with a
L{defer.CancelledError}.
"""
def cancel(d):
factory.deferred = None
connector.disconnect()
factory = self.queryFactory(
self.path, self.host, method, self.user,
self.password, self.allowNone, args, cancel, self.useDateTime)
if self.secure:
from twisted.internet import ssl
connector = self._reactor.connectSSL(
self.host, self.port or 443,
factory, ssl.ClientContextFactory(),
timeout=self.connectTimeout)
else:
connector = self._reactor.connectTCP(
self.host, self.port or 80, factory,
timeout=self.connectTimeout)
return factory.deferred
__all__ = [
"XMLRPC", "Handler", "NoSuchFunction", "Proxy",
"Fault", "Binary", "Boolean", "DateTime"]