util.py 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443
  1. # -*- test-case-name: twisted.web.test.test_util -*-
  2. # Copyright (c) Twisted Matrix Laboratories.
  3. # See LICENSE for details.
  4. """
  5. An assortment of web server-related utilities.
  6. """
  7. from __future__ import division, absolute_import
  8. import linecache
  9. from twisted.python import urlpath
  10. from twisted.python.compat import _PY3, unicode, nativeString, escape
  11. from twisted.python.reflect import fullyQualifiedName
  12. from twisted.web import resource
  13. from twisted.web.template import TagLoader, XMLString, Element, renderer
  14. from twisted.web.template import flattenString
  15. def _PRE(text):
  16. """
  17. Wraps <pre> tags around some text and HTML-escape it.
  18. This is here since once twisted.web.html was deprecated it was hard to
  19. migrate the html.PRE from current code to twisted.web.template.
  20. For new code consider using twisted.web.template.
  21. @return: Escaped text wrapped in <pre> tags.
  22. @rtype: C{str}
  23. """
  24. return '<pre>%s</pre>' % (escape(text),)
  25. def redirectTo(URL, request):
  26. """
  27. Generate a redirect to the given location.
  28. @param URL: A L{bytes} giving the location to which to redirect.
  29. @type URL: L{bytes}
  30. @param request: The request object to use to generate the redirect.
  31. @type request: L{IRequest<twisted.web.iweb.IRequest>} provider
  32. @raise TypeError: If the type of C{URL} a L{unicode} instead of L{bytes}.
  33. @return: A C{bytes} containing HTML which tries to convince the client agent
  34. to visit the new location even if it doesn't respect the I{FOUND}
  35. response code. This is intended to be returned from a render method,
  36. eg::
  37. def render_GET(self, request):
  38. return redirectTo(b"http://example.com/", request)
  39. """
  40. if isinstance(URL, unicode) :
  41. raise TypeError("Unicode object not allowed as URL")
  42. request.setHeader(b"Content-Type", b"text/html; charset=utf-8")
  43. request.redirect(URL)
  44. content = """
  45. <html>
  46. <head>
  47. <meta http-equiv=\"refresh\" content=\"0;URL=%(url)s\">
  48. </head>
  49. <body bgcolor=\"#FFFFFF\" text=\"#000000\">
  50. <a href=\"%(url)s\">click here</a>
  51. </body>
  52. </html>
  53. """ % {'url': nativeString(URL)}
  54. if _PY3:
  55. content = content.encode("utf8")
  56. return content
  57. class Redirect(resource.Resource):
  58. isLeaf = True
  59. def __init__(self, url):
  60. resource.Resource.__init__(self)
  61. self.url = url
  62. def render(self, request):
  63. return redirectTo(self.url, request)
  64. def getChild(self, name, request):
  65. return self
  66. class ChildRedirector(Redirect):
  67. isLeaf = 0
  68. def __init__(self, url):
  69. # XXX is this enough?
  70. if ((url.find('://') == -1)
  71. and (not url.startswith('..'))
  72. and (not url.startswith('/'))):
  73. 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)
  74. Redirect.__init__(self, url)
  75. def getChild(self, name, request):
  76. newUrl = self.url
  77. if not newUrl.endswith('/'):
  78. newUrl += '/'
  79. newUrl += name
  80. return ChildRedirector(newUrl)
  81. class ParentRedirect(resource.Resource):
  82. """
  83. I redirect to URLPath.here().
  84. """
  85. isLeaf = 1
  86. def render(self, request):
  87. return redirectTo(urlpath.URLPath.fromRequest(request).here(), request)
  88. def getChild(self, request):
  89. return self
  90. class DeferredResource(resource.Resource):
  91. """
  92. I wrap up a Deferred that will eventually result in a Resource
  93. object.
  94. """
  95. isLeaf = 1
  96. def __init__(self, d):
  97. resource.Resource.__init__(self)
  98. self.d = d
  99. def getChild(self, name, request):
  100. return self
  101. def render(self, request):
  102. self.d.addCallback(self._cbChild, request).addErrback(
  103. self._ebChild,request)
  104. from twisted.web.server import NOT_DONE_YET
  105. return NOT_DONE_YET
  106. def _cbChild(self, child, request):
  107. request.render(resource.getChildForRequest(child, request))
  108. def _ebChild(self, reason, request):
  109. request.processingFailed(reason)
  110. class _SourceLineElement(Element):
  111. """
  112. L{_SourceLineElement} is an L{IRenderable} which can render a single line of
  113. source code.
  114. @ivar number: A C{int} giving the line number of the source code to be
  115. rendered.
  116. @ivar source: A C{str} giving the source code to be rendered.
  117. """
  118. def __init__(self, loader, number, source):
  119. Element.__init__(self, loader)
  120. self.number = number
  121. self.source = source
  122. @renderer
  123. def sourceLine(self, request, tag):
  124. """
  125. Render the line of source as a child of C{tag}.
  126. """
  127. return tag(self.source.replace(' ', u' \N{NO-BREAK SPACE}'))
  128. @renderer
  129. def lineNumber(self, request, tag):
  130. """
  131. Render the line number as a child of C{tag}.
  132. """
  133. return tag(str(self.number))
  134. class _SourceFragmentElement(Element):
  135. """
  136. L{_SourceFragmentElement} is an L{IRenderable} which can render several lines
  137. of source code near the line number of a particular frame object.
  138. @ivar frame: A L{Failure<twisted.python.failure.Failure>}-style frame object
  139. for which to load a source line to render. This is really a tuple
  140. holding some information from a frame object. See
  141. L{Failure.frames<twisted.python.failure.Failure>} for specifics.
  142. """
  143. def __init__(self, loader, frame):
  144. Element.__init__(self, loader)
  145. self.frame = frame
  146. def _getSourceLines(self):
  147. """
  148. Find the source line references by C{self.frame} and yield, in source
  149. line order, it and the previous and following lines.
  150. @return: A generator which yields two-tuples. Each tuple gives a source
  151. line number and the contents of that source line.
  152. """
  153. filename = self.frame[1]
  154. lineNumber = self.frame[2]
  155. for snipLineNumber in range(lineNumber - 1, lineNumber + 2):
  156. yield (snipLineNumber,
  157. linecache.getline(filename, snipLineNumber).rstrip())
  158. @renderer
  159. def sourceLines(self, request, tag):
  160. """
  161. Render the source line indicated by C{self.frame} and several
  162. surrounding lines. The active line will be given a I{class} of
  163. C{"snippetHighlightLine"}. Other lines will be given a I{class} of
  164. C{"snippetLine"}.
  165. """
  166. for (lineNumber, sourceLine) in self._getSourceLines():
  167. newTag = tag.clone()
  168. if lineNumber == self.frame[2]:
  169. cssClass = "snippetHighlightLine"
  170. else:
  171. cssClass = "snippetLine"
  172. loader = TagLoader(newTag(**{"class": cssClass}))
  173. yield _SourceLineElement(loader, lineNumber, sourceLine)
  174. class _FrameElement(Element):
  175. """
  176. L{_FrameElement} is an L{IRenderable} which can render details about one
  177. frame from a L{Failure<twisted.python.failure.Failure>}.
  178. @ivar frame: A L{Failure<twisted.python.failure.Failure>}-style frame object
  179. for which to load a source line to render. This is really a tuple
  180. holding some information from a frame object. See
  181. L{Failure.frames<twisted.python.failure.Failure>} for specifics.
  182. """
  183. def __init__(self, loader, frame):
  184. Element.__init__(self, loader)
  185. self.frame = frame
  186. @renderer
  187. def filename(self, request, tag):
  188. """
  189. Render the name of the file this frame references as a child of C{tag}.
  190. """
  191. return tag(self.frame[1])
  192. @renderer
  193. def lineNumber(self, request, tag):
  194. """
  195. Render the source line number this frame references as a child of
  196. C{tag}.
  197. """
  198. return tag(str(self.frame[2]))
  199. @renderer
  200. def function(self, request, tag):
  201. """
  202. Render the function name this frame references as a child of C{tag}.
  203. """
  204. return tag(self.frame[0])
  205. @renderer
  206. def source(self, request, tag):
  207. """
  208. Render the source code surrounding the line this frame references,
  209. replacing C{tag}.
  210. """
  211. return _SourceFragmentElement(TagLoader(tag), self.frame)
  212. class _StackElement(Element):
  213. """
  214. L{_StackElement} renders an L{IRenderable} which can render a list of frames.
  215. """
  216. def __init__(self, loader, stackFrames):
  217. Element.__init__(self, loader)
  218. self.stackFrames = stackFrames
  219. @renderer
  220. def frames(self, request, tag):
  221. """
  222. Render the list of frames in this L{_StackElement}, replacing C{tag}.
  223. """
  224. return [
  225. _FrameElement(TagLoader(tag.clone()), frame)
  226. for frame
  227. in self.stackFrames]
  228. class FailureElement(Element):
  229. """
  230. L{FailureElement} is an L{IRenderable} which can render detailed information
  231. about a L{Failure<twisted.python.failure.Failure>}.
  232. @ivar failure: The L{Failure<twisted.python.failure.Failure>} instance which
  233. will be rendered.
  234. @since: 12.1
  235. """
  236. loader = XMLString("""
  237. <div xmlns:t="http://twistedmatrix.com/ns/twisted.web.template/0.1">
  238. <style type="text/css">
  239. div.error {
  240. color: red;
  241. font-family: Verdana, Arial, helvetica, sans-serif;
  242. font-weight: bold;
  243. }
  244. div {
  245. font-family: Verdana, Arial, helvetica, sans-serif;
  246. }
  247. div.stackTrace {
  248. }
  249. div.frame {
  250. padding: 1em;
  251. background: white;
  252. border-bottom: thin black dashed;
  253. }
  254. div.frame:first-child {
  255. padding: 1em;
  256. background: white;
  257. border-top: thin black dashed;
  258. border-bottom: thin black dashed;
  259. }
  260. div.location {
  261. }
  262. span.function {
  263. font-weight: bold;
  264. font-family: "Courier New", courier, monospace;
  265. }
  266. div.snippet {
  267. margin-bottom: 0.5em;
  268. margin-left: 1em;
  269. background: #FFFFDD;
  270. }
  271. div.snippetHighlightLine {
  272. color: red;
  273. }
  274. span.code {
  275. font-family: "Courier New", courier, monospace;
  276. }
  277. </style>
  278. <div class="error">
  279. <span t:render="type" />: <span t:render="value" />
  280. </div>
  281. <div class="stackTrace" t:render="traceback">
  282. <div class="frame" t:render="frames">
  283. <div class="location">
  284. <span t:render="filename" />:<span t:render="lineNumber" /> in
  285. <span class="function" t:render="function" />
  286. </div>
  287. <div class="snippet" t:render="source">
  288. <div t:render="sourceLines">
  289. <span class="lineno" t:render="lineNumber" />
  290. <code class="code" t:render="sourceLine" />
  291. </div>
  292. </div>
  293. </div>
  294. </div>
  295. <div class="error">
  296. <span t:render="type" />: <span t:render="value" />
  297. </div>
  298. </div>
  299. """)
  300. def __init__(self, failure, loader=None):
  301. Element.__init__(self, loader)
  302. self.failure = failure
  303. @renderer
  304. def type(self, request, tag):
  305. """
  306. Render the exception type as a child of C{tag}.
  307. """
  308. return tag(fullyQualifiedName(self.failure.type))
  309. @renderer
  310. def value(self, request, tag):
  311. """
  312. Render the exception value as a child of C{tag}.
  313. """
  314. return tag(unicode(self.failure.value).encode('utf8'))
  315. @renderer
  316. def traceback(self, request, tag):
  317. """
  318. Render all the frames in the wrapped
  319. L{Failure<twisted.python.failure.Failure>}'s traceback stack, replacing
  320. C{tag}.
  321. """
  322. return _StackElement(TagLoader(tag), self.failure.frames)
  323. def formatFailure(myFailure):
  324. """
  325. Construct an HTML representation of the given failure.
  326. Consider using L{FailureElement} instead.
  327. @type myFailure: L{Failure<twisted.python.failure.Failure>}
  328. @rtype: C{bytes}
  329. @return: A string containing the HTML representation of the given failure.
  330. """
  331. result = []
  332. flattenString(None, FailureElement(myFailure)).addBoth(result.append)
  333. if isinstance(result[0], bytes):
  334. # Ensure the result string is all ASCII, for compatibility with the
  335. # default encoding expected by browsers.
  336. return result[0].decode('utf-8').encode('ascii', 'xmlcharrefreplace')
  337. result[0].raiseException()
  338. __all__ = [
  339. "redirectTo", "Redirect", "ChildRedirector", "ParentRedirect",
  340. "DeferredResource", "FailureElement", "formatFailure"]