aboutsummaryrefslogtreecommitdiffstats
path: root/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/static.py
diff options
context:
space:
mode:
Diffstat (limited to 'lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/static.py')
-rwxr-xr-xlib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/static.py1033
1 files changed, 0 insertions, 1033 deletions
diff --git a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/static.py b/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/static.py
deleted file mode 100755
index 05fd7d98..00000000
--- a/lib/python2.7/site-packages/Twisted-12.2.0-py2.7-linux-x86_64.egg/twisted/web/static.py
+++ /dev/null
@@ -1,1033 +0,0 @@
-# -*- test-case-name: twisted.web.test.test_static -*-
-# Copyright (c) Twisted Matrix Laboratories.
-# See LICENSE for details.
-
-"""
-Static resources for L{twisted.web}.
-"""
-from __future__ import division
-
-import os
-import warnings
-import urllib
-import itertools
-import cgi
-import time
-
-from zope.interface import implements
-
-from twisted.web import server
-from twisted.web import resource
-from twisted.web import http
-from twisted.web.util import redirectTo
-
-from twisted.python import components, filepath, log
-from twisted.internet import abstract, interfaces
-from twisted.persisted import styles
-from twisted.python.util import InsensitiveDict
-from twisted.python.runtime import platformType
-
-
-dangerousPathError = resource.NoResource("Invalid request URL.")
-
-def isDangerous(path):
- return path == '..' or '/' in path or os.sep in path
-
-
-class Data(resource.Resource):
- """
- This is a static, in-memory resource.
- """
-
- def __init__(self, data, type):
- resource.Resource.__init__(self)
- self.data = data
- self.type = type
-
-
- def render_GET(self, request):
- request.setHeader("content-type", self.type)
- request.setHeader("content-length", str(len(self.data)))
- if request.method == "HEAD":
- return ''
- return self.data
- render_HEAD = render_GET
-
-
-def addSlash(request):
- qs = ''
- qindex = request.uri.find('?')
- if qindex != -1:
- qs = request.uri[qindex:]
-
- return "http%s://%s%s/%s" % (
- request.isSecure() and 's' or '',
- request.getHeader("host"),
- (request.uri.split('?')[0]),
- qs)
-
-class Redirect(resource.Resource):
- def __init__(self, request):
- resource.Resource.__init__(self)
- self.url = addSlash(request)
-
- def render(self, request):
- return redirectTo(self.url, request)
-
-
-class Registry(components.Componentized, styles.Versioned):
- """
- I am a Componentized object that will be made available to internal Twisted
- file-based dynamic web content such as .rpy and .epy scripts.
- """
-
- def __init__(self):
- components.Componentized.__init__(self)
- self._pathCache = {}
-
- persistenceVersion = 1
-
- def upgradeToVersion1(self):
- self._pathCache = {}
-
- def cachePath(self, path, rsrc):
- self._pathCache[path] = rsrc
-
- def getCachedPath(self, path):
- return self._pathCache.get(path)
-
-
-def loadMimeTypes(mimetype_locations=['/etc/mime.types']):
- """
- Multiple file locations containing mime-types can be passed as a list.
- The files will be sourced in that order, overriding mime-types from the
- files sourced beforehand, but only if a new entry explicitly overrides
- the current entry.
- """
- import mimetypes
- # Grab Python's built-in mimetypes dictionary.
- contentTypes = mimetypes.types_map
- # Update Python's semi-erroneous dictionary with a few of the
- # usual suspects.
- contentTypes.update(
- {
- '.conf': 'text/plain',
- '.diff': 'text/plain',
- '.exe': 'application/x-executable',
- '.flac': 'audio/x-flac',
- '.java': 'text/plain',
- '.ogg': 'application/ogg',
- '.oz': 'text/x-oz',
- '.swf': 'application/x-shockwave-flash',
- '.tgz': 'application/x-gtar',
- '.wml': 'text/vnd.wap.wml',
- '.xul': 'application/vnd.mozilla.xul+xml',
- '.py': 'text/plain',
- '.patch': 'text/plain',
- }
- )
- # Users can override these mime-types by loading them out configuration
- # files (this defaults to ['/etc/mime.types']).
- for location in mimetype_locations:
- if os.path.exists(location):
- more = mimetypes.read_mime_types(location)
- if more is not None:
- contentTypes.update(more)
-
- return contentTypes
-
-def getTypeAndEncoding(filename, types, encodings, defaultType):
- p, ext = os.path.splitext(filename)
- ext = ext.lower()
- if ext in encodings:
- enc = encodings[ext]
- ext = os.path.splitext(p)[1].lower()
- else:
- enc = None
- type = types.get(ext, defaultType)
- return type, enc
-
-
-
-class File(resource.Resource, styles.Versioned, filepath.FilePath):
- """
- File is a resource that represents a plain non-interpreted file
- (although it can look for an extension like .rpy or .cgi and hand the
- file to a processor for interpretation if you wish). Its constructor
- takes a file path.
-
- Alternatively, you can give a directory path to the constructor. In this
- case the resource will represent that directory, and its children will
- be files underneath that directory. This provides access to an entire
- filesystem tree with a single Resource.
-
- If you map the URL 'http://server/FILE' to a resource created as
- File('/tmp'), then http://server/FILE/ will return an HTML-formatted
- listing of the /tmp/ directory, and http://server/FILE/foo/bar.html will
- return the contents of /tmp/foo/bar.html .
-
- @cvar childNotFound: L{Resource} used to render 404 Not Found error pages.
- """
-
- contentTypes = loadMimeTypes()
-
- contentEncodings = {
- ".gz" : "gzip",
- ".bz2": "bzip2"
- }
-
- processors = {}
-
- indexNames = ["index", "index.html", "index.htm", "index.rpy"]
-
- type = None
-
- ### Versioning
-
- persistenceVersion = 6
-
- def upgradeToVersion6(self):
- self.ignoredExts = []
- if self.allowExt:
- self.ignoreExt("*")
- del self.allowExt
-
-
- def upgradeToVersion5(self):
- if not isinstance(self.registry, Registry):
- self.registry = Registry()
-
-
- def upgradeToVersion4(self):
- if not hasattr(self, 'registry'):
- self.registry = {}
-
-
- def upgradeToVersion3(self):
- if not hasattr(self, 'allowExt'):
- self.allowExt = 0
-
-
- def upgradeToVersion2(self):
- self.defaultType = "text/html"
-
-
- def upgradeToVersion1(self):
- if hasattr(self, 'indexName'):
- self.indexNames = [self.indexName]
- del self.indexName
-
-
- def __init__(self, path, defaultType="text/html", ignoredExts=(), registry=None, allowExt=0):
- """
- Create a file with the given path.
-
- @param path: The filename of the file from which this L{File} will
- serve data.
- @type path: C{str}
-
- @param defaultType: A I{major/minor}-style MIME type specifier
- indicating the I{Content-Type} with which this L{File}'s data
- will be served if a MIME type cannot be determined based on
- C{path}'s extension.
- @type defaultType: C{str}
-
- @param ignoredExts: A sequence giving the extensions of paths in the
- filesystem which will be ignored for the purposes of child
- lookup. For example, if C{ignoredExts} is C{(".bar",)} and
- C{path} is a directory containing a file named C{"foo.bar"}, a
- request for the C{"foo"} child of this resource will succeed
- with a L{File} pointing to C{"foo.bar"}.
-
- @param registry: The registry object being used to handle this
- request. If C{None}, one will be created.
- @type registry: L{Registry}
-
- @param allowExt: Ignored parameter, only present for backwards
- compatibility. Do not pass a value for this parameter.
- """
- resource.Resource.__init__(self)
- filepath.FilePath.__init__(self, path)
- self.defaultType = defaultType
- if ignoredExts in (0, 1) or allowExt:
- warnings.warn("ignoredExts should receive a list, not a boolean")
- if ignoredExts or allowExt:
- self.ignoredExts = ['*']
- else:
- self.ignoredExts = []
- else:
- self.ignoredExts = list(ignoredExts)
- self.registry = registry or Registry()
-
-
- def ignoreExt(self, ext):
- """Ignore the given extension.
-
- Serve file.ext if file is requested
- """
- self.ignoredExts.append(ext)
-
- childNotFound = resource.NoResource("File not found.")
-
- def directoryListing(self):
- return DirectoryLister(self.path,
- self.listNames(),
- self.contentTypes,
- self.contentEncodings,
- self.defaultType)
-
-
- def getChild(self, path, request):
- """
- If this L{File}'s path refers to a directory, return a L{File}
- referring to the file named C{path} in that directory.
-
- If C{path} is the empty string, return a L{DirectoryLister} instead.
- """
- self.restat(reraise=False)
-
- if not self.isdir():
- return self.childNotFound
-
- if path:
- try:
- fpath = self.child(path)
- except filepath.InsecurePath:
- return self.childNotFound
- else:
- fpath = self.childSearchPreauth(*self.indexNames)
- if fpath is None:
- return self.directoryListing()
-
- if not fpath.exists():
- fpath = fpath.siblingExtensionSearch(*self.ignoredExts)
- if fpath is None:
- return self.childNotFound
-
- if platformType == "win32":
- # don't want .RPY to be different than .rpy, since that would allow
- # source disclosure.
- processor = InsensitiveDict(self.processors).get(fpath.splitext()[1])
- else:
- processor = self.processors.get(fpath.splitext()[1])
- if processor:
- return resource.IResource(processor(fpath.path, self.registry))
- return self.createSimilarFile(fpath.path)
-
-
- # methods to allow subclasses to e.g. decrypt files on the fly:
- def openForReading(self):
- """Open a file and return it."""
- return self.open()
-
-
- def getFileSize(self):
- """Return file size."""
- return self.getsize()
-
-
- def _parseRangeHeader(self, range):
- """
- Parse the value of a Range header into (start, stop) pairs.
-
- In a given pair, either of start or stop can be None, signifying that
- no value was provided, but not both.
-
- @return: A list C{[(start, stop)]} of pairs of length at least one.
-
- @raise ValueError: if the header is syntactically invalid or if the
- Bytes-Unit is anything other than 'bytes'.
- """
- try:
- kind, value = range.split('=', 1)
- except ValueError:
- raise ValueError("Missing '=' separator")
- kind = kind.strip()
- if kind != 'bytes':
- raise ValueError("Unsupported Bytes-Unit: %r" % (kind,))
- unparsedRanges = filter(None, map(str.strip, value.split(',')))
- parsedRanges = []
- for byteRange in unparsedRanges:
- try:
- start, end = byteRange.split('-', 1)
- except ValueError:
- raise ValueError("Invalid Byte-Range: %r" % (byteRange,))
- if start:
- try:
- start = int(start)
- except ValueError:
- raise ValueError("Invalid Byte-Range: %r" % (byteRange,))
- else:
- start = None
- if end:
- try:
- end = int(end)
- except ValueError:
- raise ValueError("Invalid Byte-Range: %r" % (byteRange,))
- else:
- end = None
- if start is not None:
- if end is not None and start > end:
- # Start must be less than or equal to end or it is invalid.
- raise ValueError("Invalid Byte-Range: %r" % (byteRange,))
- elif end is None:
- # One or both of start and end must be specified. Omitting
- # both is invalid.
- raise ValueError("Invalid Byte-Range: %r" % (byteRange,))
- parsedRanges.append((start, end))
- return parsedRanges
-
-
- def _rangeToOffsetAndSize(self, start, end):
- """
- Convert a start and end from a Range header to an offset and size.
-
- This method checks that the resulting range overlaps with the resource
- being served (and so has the value of C{getFileSize()} as an indirect
- input).
-
- Either but not both of start or end can be C{None}:
-
- - Omitted start means that the end value is actually a start value
- relative to the end of the resource.
-
- - Omitted end means the end of the resource should be the end of
- the range.
-
- End is interpreted as inclusive, as per RFC 2616.
-
- If this range doesn't overlap with any of this resource, C{(0, 0)} is
- returned, which is not otherwise a value return value.
-
- @param start: The start value from the header, or C{None} if one was
- not present.
- @param end: The end value from the header, or C{None} if one was not
- present.
- @return: C{(offset, size)} where offset is how far into this resource
- this resource the range begins and size is how long the range is,
- or C{(0, 0)} if the range does not overlap this resource.
- """
- size = self.getFileSize()
- if start is None:
- start = size - end
- end = size
- elif end is None:
- end = size
- elif end < size:
- end += 1
- elif end > size:
- end = size
- if start >= size:
- start = end = 0
- return start, (end - start)
-
-
- def _contentRange(self, offset, size):
- """
- Return a string suitable for the value of a Content-Range header for a
- range with the given offset and size.
-
- The offset and size are not sanity checked in any way.
-
- @param offset: How far into this resource the range begins.
- @param size: How long the range is.
- @return: The value as appropriate for the value of a Content-Range
- header.
- """
- return 'bytes %d-%d/%d' % (
- offset, offset + size - 1, self.getFileSize())
-
-
- def _doSingleRangeRequest(self, request, (start, end)):
- """
- Set up the response for Range headers that specify a single range.
-
- This method checks if the request is satisfiable and sets the response
- code and Content-Range header appropriately. The return value
- indicates which part of the resource to return.
-
- @param request: The Request object.
- @param start: The start of the byte range as specified by the header.
- @param end: The end of the byte range as specified by the header. At
- most one of C{start} and C{end} may be C{None}.
- @return: A 2-tuple of the offset and size of the range to return.
- offset == size == 0 indicates that the request is not satisfiable.
- """
- offset, size = self._rangeToOffsetAndSize(start, end)
- if offset == size == 0:
- # This range doesn't overlap with any of this resource, so the
- # request is unsatisfiable.
- request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
- request.setHeader(
- 'content-range', 'bytes */%d' % (self.getFileSize(),))
- else:
- request.setResponseCode(http.PARTIAL_CONTENT)
- request.setHeader(
- 'content-range', self._contentRange(offset, size))
- return offset, size
-
-
- def _doMultipleRangeRequest(self, request, byteRanges):
- """
- Set up the response for Range headers that specify a single range.
-
- This method checks if the request is satisfiable and sets the response
- code and Content-Type and Content-Length headers appropriately. The
- return value, which is a little complicated, indicates which parts of
- the resource to return and the boundaries that should separate the
- parts.
-
- In detail, the return value is a tuple rangeInfo C{rangeInfo} is a
- list of 3-tuples C{(partSeparator, partOffset, partSize)}. The
- response to this request should be, for each element of C{rangeInfo},
- C{partSeparator} followed by C{partSize} bytes of the resource
- starting at C{partOffset}. Each C{partSeparator} includes the
- MIME-style boundary and the part-specific Content-type and
- Content-range headers. It is convenient to return the separator as a
- concrete string from this method, becasue this method needs to compute
- the number of bytes that will make up the response to be able to set
- the Content-Length header of the response accurately.
-
- @param request: The Request object.
- @param byteRanges: A list of C{(start, end)} values as specified by
- the header. For each range, at most one of C{start} and C{end}
- may be C{None}.
- @return: See above.
- """
- matchingRangeFound = False
- rangeInfo = []
- contentLength = 0
- boundary = "%x%x" % (int(time.time()*1000000), os.getpid())
- if self.type:
- contentType = self.type
- else:
- contentType = 'bytes' # It's what Apache does...
- for start, end in byteRanges:
- partOffset, partSize = self._rangeToOffsetAndSize(start, end)
- if partOffset == partSize == 0:
- continue
- contentLength += partSize
- matchingRangeFound = True
- partContentRange = self._contentRange(partOffset, partSize)
- partSeparator = (
- "\r\n"
- "--%s\r\n"
- "Content-type: %s\r\n"
- "Content-range: %s\r\n"
- "\r\n") % (boundary, contentType, partContentRange)
- contentLength += len(partSeparator)
- rangeInfo.append((partSeparator, partOffset, partSize))
- if not matchingRangeFound:
- request.setResponseCode(http.REQUESTED_RANGE_NOT_SATISFIABLE)
- request.setHeader(
- 'content-length', '0')
- request.setHeader(
- 'content-range', 'bytes */%d' % (self.getFileSize(),))
- return [], ''
- finalBoundary = "\r\n--" + boundary + "--\r\n"
- rangeInfo.append((finalBoundary, 0, 0))
- request.setResponseCode(http.PARTIAL_CONTENT)
- request.setHeader(
- 'content-type', 'multipart/byteranges; boundary="%s"' % (boundary,))
- request.setHeader(
- 'content-length', contentLength + len(finalBoundary))
- return rangeInfo
-
-
- def _setContentHeaders(self, request, size=None):
- """
- Set the Content-length and Content-type headers for this request.
-
- This method is not appropriate for requests for multiple byte ranges;
- L{_doMultipleRangeRequest} will set these headers in that case.
-
- @param request: The L{Request} object.
- @param size: The size of the response. If not specified, default to
- C{self.getFileSize()}.
- """
- if size is None:
- size = self.getFileSize()
- request.setHeader('content-length', str(size))
- if self.type:
- request.setHeader('content-type', self.type)
- if self.encoding:
- request.setHeader('content-encoding', self.encoding)
-
-
- def makeProducer(self, request, fileForReading):
- """
- Make a L{StaticProducer} that will produce the body of this response.
-
- This method will also set the response code and Content-* headers.
-
- @param request: The L{Request} object.
- @param fileForReading: The file object containing the resource.
- @return: A L{StaticProducer}. Calling C{.start()} on this will begin
- producing the response.
- """
- byteRange = request.getHeader('range')
- if byteRange is None:
- self._setContentHeaders(request)
- request.setResponseCode(http.OK)
- return NoRangeStaticProducer(request, fileForReading)
- try:
- parsedRanges = self._parseRangeHeader(byteRange)
- except ValueError:
- log.msg("Ignoring malformed Range header %r" % (byteRange,))
- self._setContentHeaders(request)
- request.setResponseCode(http.OK)
- return NoRangeStaticProducer(request, fileForReading)
-
- if len(parsedRanges) == 1:
- offset, size = self._doSingleRangeRequest(
- request, parsedRanges[0])
- self._setContentHeaders(request, size)
- return SingleRangeStaticProducer(
- request, fileForReading, offset, size)
- else:
- rangeInfo = self._doMultipleRangeRequest(request, parsedRanges)
- return MultipleRangeStaticProducer(
- request, fileForReading, rangeInfo)
-
-
- def render_GET(self, request):
- """
- Begin sending the contents of this L{File} (or a subset of the
- contents, based on the 'range' header) to the given request.
- """
- self.restat(False)
-
- if self.type is None:
- self.type, self.encoding = getTypeAndEncoding(self.basename(),
- self.contentTypes,
- self.contentEncodings,
- self.defaultType)
-
- if not self.exists():
- return self.childNotFound.render(request)
-
- if self.isdir():
- return self.redirect(request)
-
- request.setHeader('accept-ranges', 'bytes')
-
- try:
- fileForReading = self.openForReading()
- except IOError, e:
- import errno
- if e[0] == errno.EACCES:
- return resource.ForbiddenResource().render(request)
- else:
- raise
-
- if request.setLastModified(self.getmtime()) is http.CACHED:
- return ''
-
-
- producer = self.makeProducer(request, fileForReading)
-
- if request.method == 'HEAD':
- return ''
-
- producer.start()
- # and make sure the connection doesn't get closed
- return server.NOT_DONE_YET
- render_HEAD = render_GET
-
-
- def redirect(self, request):
- return redirectTo(addSlash(request), request)
-
-
- def listNames(self):
- if not self.isdir():
- return []
- directory = self.listdir()
- directory.sort()
- return directory
-
- def listEntities(self):
- return map(lambda fileName, self=self: self.createSimilarFile(os.path.join(self.path, fileName)), self.listNames())
-
-
- def createSimilarFile(self, path):
- f = self.__class__(path, self.defaultType, self.ignoredExts, self.registry)
- # refactoring by steps, here - constructor should almost certainly take these
- f.processors = self.processors
- f.indexNames = self.indexNames[:]
- f.childNotFound = self.childNotFound
- return f
-
-
-
-class StaticProducer(object):
- """
- Superclass for classes that implement the business of producing.
-
- @ivar request: The L{IRequest} to write the contents of the file to.
- @ivar fileObject: The file the contents of which to write to the request.
- """
-
- implements(interfaces.IPullProducer)
-
- bufferSize = abstract.FileDescriptor.bufferSize
-
-
- def __init__(self, request, fileObject):
- """
- Initialize the instance.
- """
- self.request = request
- self.fileObject = fileObject
-
-
- def start(self):
- raise NotImplementedError(self.start)
-
-
- def resumeProducing(self):
- raise NotImplementedError(self.resumeProducing)
-
-
- def stopProducing(self):
- """
- Stop producing data.
-
- L{IPullProducer.stopProducing} is called when our consumer has died,
- and subclasses also call this method when they are done producing
- data.
- """
- self.fileObject.close()
- self.request = None
-
-
-
-class NoRangeStaticProducer(StaticProducer):
- """
- A L{StaticProducer} that writes the entire file to the request.
- """
-
- def start(self):
- self.request.registerProducer(self, False)
-
-
- def resumeProducing(self):
- if not self.request:
- return
- data = self.fileObject.read(self.bufferSize)
- if data:
- # this .write will spin the reactor, calling .doWrite and then
- # .resumeProducing again, so be prepared for a re-entrant call
- self.request.write(data)
- else:
- self.request.unregisterProducer()
- self.request.finish()
- self.stopProducing()
-
-
-
-class SingleRangeStaticProducer(StaticProducer):
- """
- A L{StaticProducer} that writes a single chunk of a file to the request.
- """
-
- def __init__(self, request, fileObject, offset, size):
- """
- Initialize the instance.
-
- @param request: See L{StaticProducer}.
- @param fileObject: See L{StaticProducer}.
- @param offset: The offset into the file of the chunk to be written.
- @param size: The size of the chunk to write.
- """
- StaticProducer.__init__(self, request, fileObject)
- self.offset = offset
- self.size = size
-
-
- def start(self):
- self.fileObject.seek(self.offset)
- self.bytesWritten = 0
- self.request.registerProducer(self, 0)
-
-
- def resumeProducing(self):
- if not self.request:
- return
- data = self.fileObject.read(
- min(self.bufferSize, self.size - self.bytesWritten))
- if data:
- self.bytesWritten += len(data)
- # this .write will spin the reactor, calling .doWrite and then
- # .resumeProducing again, so be prepared for a re-entrant call
- self.request.write(data)
- if self.request and self.bytesWritten == self.size:
- self.request.unregisterProducer()
- self.request.finish()
- self.stopProducing()
-
-
-
-class MultipleRangeStaticProducer(StaticProducer):
- """
- A L{StaticProducer} that writes several chunks of a file to the request.
- """
-
- def __init__(self, request, fileObject, rangeInfo):
- """
- Initialize the instance.
-
- @param request: See L{StaticProducer}.
- @param fileObject: See L{StaticProducer}.
- @param rangeInfo: A list of tuples C{[(boundary, offset, size)]}
- where:
- - C{boundary} will be written to the request first.
- - C{offset} the offset into the file of chunk to write.
- - C{size} the size of the chunk to write.
- """
- StaticProducer.__init__(self, request, fileObject)
- self.rangeInfo = rangeInfo
-
-
- def start(self):
- self.rangeIter = iter(self.rangeInfo)
- self._nextRange()
- self.request.registerProducer(self, 0)
-
-
- def _nextRange(self):
- self.partBoundary, partOffset, self._partSize = self.rangeIter.next()
- self._partBytesWritten = 0
- self.fileObject.seek(partOffset)
-
-
- def resumeProducing(self):
- if not self.request:
- return
- data = []
- dataLength = 0
- done = False
- while dataLength < self.bufferSize:
- if self.partBoundary:
- dataLength += len(self.partBoundary)
- data.append(self.partBoundary)
- self.partBoundary = None
- p = self.fileObject.read(
- min(self.bufferSize - dataLength,
- self._partSize - self._partBytesWritten))
- self._partBytesWritten += len(p)
- dataLength += len(p)
- data.append(p)
- if self.request and self._partBytesWritten == self._partSize:
- try:
- self._nextRange()
- except StopIteration:
- done = True
- break
- self.request.write(''.join(data))
- if done:
- self.request.unregisterProducer()
- self.request.finish()
- self.request = None
-
-
-
-class ASISProcessor(resource.Resource):
- """
- Serve files exactly as responses without generating a status-line or any
- headers. Inspired by Apache's mod_asis.
- """
-
- def __init__(self, path, registry=None):
- resource.Resource.__init__(self)
- self.path = path
- self.registry = registry or Registry()
-
-
- def render(self, request):
- request.startedWriting = 1
- res = File(self.path, registry=self.registry)
- return res.render(request)
-
-
-
-def formatFileSize(size):
- """
- Format the given file size in bytes to human readable format.
- """
- if size < 1024:
- return '%iB' % size
- elif size < (1024 ** 2):
- return '%iK' % (size / 1024)
- elif size < (1024 ** 3):
- return '%iM' % (size / (1024 ** 2))
- else:
- return '%iG' % (size / (1024 ** 3))
-
-
-
-class DirectoryLister(resource.Resource):
- """
- Print the content of a directory.
-
- @ivar template: page template used to render the content of the directory.
- It must contain the format keys B{header} and B{tableContent}.
- @type template: C{str}
-
- @ivar linePattern: template used to render one line in the listing table.
- It must contain the format keys B{class}, B{href}, B{text}, B{size},
- B{type} and B{encoding}.
- @type linePattern: C{str}
-
- @ivar contentEncodings: a mapping of extensions to encoding types.
- @type contentEncodings: C{dict}
-
- @ivar defaultType: default type used when no mimetype is detected.
- @type defaultType: C{str}
-
- @ivar dirs: filtered content of C{path}, if the whole content should not be
- displayed (default to C{None}, which means the actual content of
- C{path} is printed).
- @type dirs: C{NoneType} or C{list}
-
- @ivar path: directory which content should be listed.
- @type path: C{str}
- """
-
- template = """<html>
-<head>
-<title>%(header)s</title>
-<style>
-.even-dir { background-color: #efe0ef }
-.even { background-color: #eee }
-.odd-dir {background-color: #f0d0ef }
-.odd { background-color: #dedede }
-.icon { text-align: center }
-.listing {
- margin-left: auto;
- margin-right: auto;
- width: 50%%;
- padding: 0.1em;
- }
-
-body { border: 0; padding: 0; margin: 0; background-color: #efefef; }
-h1 {padding: 0.1em; background-color: #777; color: white; border-bottom: thin white dashed;}
-
-</style>
-</head>
-
-<body>
-<h1>%(header)s</h1>
-
-<table>
- <thead>
- <tr>
- <th>Filename</th>
- <th>Size</th>
- <th>Content type</th>
- <th>Content encoding</th>
- </tr>
- </thead>
- <tbody>
-%(tableContent)s
- </tbody>
-</table>
-
-</body>
-</html>
-"""
-
- linePattern = """<tr class="%(class)s">
- <td><a href="%(href)s">%(text)s</a></td>
- <td>%(size)s</td>
- <td>%(type)s</td>
- <td>%(encoding)s</td>
-</tr>
-"""
-
- def __init__(self, pathname, dirs=None,
- contentTypes=File.contentTypes,
- contentEncodings=File.contentEncodings,
- defaultType='text/html'):
- resource.Resource.__init__(self)
- self.contentTypes = contentTypes
- self.contentEncodings = contentEncodings
- self.defaultType = defaultType
- # dirs allows usage of the File to specify what gets listed
- self.dirs = dirs
- self.path = pathname
-
-
- def _getFilesAndDirectories(self, directory):
- """
- Helper returning files and directories in given directory listing, with
- attributes to be used to build a table content with
- C{self.linePattern}.
-
- @return: tuple of (directories, files)
- @rtype: C{tuple} of C{list}
- """
- files = []
- dirs = []
- for path in directory:
- url = urllib.quote(path, "/")
- escapedPath = cgi.escape(path)
- if os.path.isdir(os.path.join(self.path, path)):
- url = url + '/'
- dirs.append({'text': escapedPath + "/", 'href': url,
- 'size': '', 'type': '[Directory]',
- 'encoding': ''})
- else:
- mimetype, encoding = getTypeAndEncoding(path, self.contentTypes,
- self.contentEncodings,
- self.defaultType)
- try:
- size = os.stat(os.path.join(self.path, path)).st_size
- except OSError:
- continue
- files.append({
- 'text': escapedPath, "href": url,
- 'type': '[%s]' % mimetype,
- 'encoding': (encoding and '[%s]' % encoding or ''),
- 'size': formatFileSize(size)})
- return dirs, files
-
-
- def _buildTableContent(self, elements):
- """
- Build a table content using C{self.linePattern} and giving elements odd
- and even classes.
- """
- tableContent = []
- rowClasses = itertools.cycle(['odd', 'even'])
- for element, rowClass in zip(elements, rowClasses):
- element["class"] = rowClass
- tableContent.append(self.linePattern % element)
- return tableContent
-
-
- def render(self, request):
- """
- Render a listing of the content of C{self.path}.
- """
- request.setHeader("content-type", "text/html; charset=utf-8")
- if self.dirs is None:
- directory = os.listdir(self.path)
- directory.sort()
- else:
- directory = self.dirs
-
- dirs, files = self._getFilesAndDirectories(directory)
-
- tableContent = "".join(self._buildTableContent(dirs + files))
-
- header = "Directory listing for %s" % (
- cgi.escape(urllib.unquote(request.uri)),)
-
- return self.template % {"header": header, "tableContent": tableContent}
-
-
- def __repr__(self):
- return '<DirectoryLister of %r>' % self.path
-
- __str__ = __repr__