from Common import *
from Request import Request
from Session import Session, SessionError
from WebKit.Cookie import CookieEngine
Cookie = CookieEngine.SimpleCookie
import os, cgi, sys, traceback
from types import ListType
from WebUtils.Funcs import requestURI
from WebUtils import FieldStorage
import HTTPResponse
debug=0
class HTTPRequest(Request):
"""A type of Message for HTTP requests.
FUTURE
* How about some documentation?
* The "Information" section is a bit screwed up.
Because the WebKit server adapter is a CGI script,
these values are oriented towards that rather than the servlet.
"""
def __init__(self, dict={}):
Request.__init__(self)
self._parents = []
if dict:
assert dict['format'] == 'CGI'
self._time = dict['time']
self._environ = dict['environ']
self._input = dict['input']
self._requestID = dict['requestID']
self._fields = FieldStorage.FieldStorage(self._input,
environ=self._environ, keep_blank_values=1, strict_parsing=0)
self._fields.parse_qs()
self._cookies = Cookie()
if self._environ.has_key('HTTP_COOKIE'):
try:
self._cookies.load(self._environ['HTTP_COOKIE'])
except:
traceback.print_exc(file=sys.stderr)
else:
import time
self._time = time.time()
self._environ = os.environ.copy()
self._input = None
self._fields = cgi.FieldStorage(keep_blank_values=1)
self._cookies = Cookie()
if debug:
f = open('env.text', 'a')
save = sys.stdout
sys.stdout = f
print '>> env for request:'
keys = self._environ.keys()
keys.sort()
for key in keys:
print '%s: %s' % (repr(key), repr(self._environ[key]))
print
sys.stdout = save
f.close()
if not self._environ.has_key('PATH_INFO'):
self._environ['PATH_INFO'] = ''
if not self._environ.has_key('REQUEST_URI'):
self._environ['REQUEST_URI'] = requestURI(self._environ)
self._adapterName = self._environ.get('SCRIPT_NAME', '')
try:
keys = self._fields.keys()
except TypeError:
keys = []
dict = {}
for key in keys:
value = self._fields[key]
if type(value) is not ListType:
if value.filename:
if debug: print "Uploaded File Found"
else:
value = value.value
else:
value = map(lambda miniFieldStorage: miniFieldStorage.value,
value)
dict[key] = value
self._fieldStorage = self._fields
self._fields = dict
self._pathInfo = None
dict = {}
for key in self._cookies.keys():
dict[key] = self._cookies[key].value
self._cookies = dict
self._cookieSession = self._cookies.get('_SID_', None)
self._transaction = None
self._serverRootPath = ""
self._extraURLPath = ""
self._pathSession = None
if self._environ['PATH_INFO'][1:6] == '_SID_':
self._pathSession = self._environ['PATH_INFO'][7:].split('/',1)[0]
self._cookies['_SID_'] = self._pathSession
sidstring = '_SID_=' + self._pathSession +'/'
self._environ['REQUEST_URI'] = self._environ[
'REQUEST_URI'].replace(sidstring,'')
self._environ['PATH_INFO'] = self._environ[
'PATH_INFO'].replace(sidstring,'')
if self._environ.has_key('PATH_TRANSLATED'):
self._environ['PATH_TRANSLATED'] = self._environ[
'PATH_TRANSLATED'].replace(sidstring,'')
assert(not self._environ.has_key('WK_URI'))
self._sessionExpired = 0
self._originalURLPath = self.urlPath()
if debug:
print "Done setting up request, found keys %r" % self._fields.keys()
def responseClass(self):
return HTTPResponse.HTTPResponse
def transaction(self):
return self._transaction
def setTransaction(self, trans):
"""This method should be invoked after the transaction is created for this request."""
self._transaction = trans
def value(self, name, default=NoDefault):
"""Return the value with the given name.
Values are fields or cookies.
Use this method when you're field/cookie agnostic.
"""
if self._fields.has_key(name):
return self._fields[name]
else:
return self.cookie(name, default)
def hasValue(self, name):
return self._fields.has_key(name) or self._cookies.has_key(name)
def extraURLPath(self):
return self._extraURLPath
def fieldStorage(self):
return self._fieldStorage
def field(self, name, default=NoDefault):
if default is NoDefault:
return self._fields[name]
else:
return self._fields.get(name, default)
def hasField(self, name):
return self._fields.has_key(name)
def fields(self):
return self._fields
def setField(self, name, value):
self._fields[name] = value
def delField(self, name):
del self._fields[name]
def cookie(self, name, default=NoDefault):
"""Return the value of the specified cookie."""
if default is NoDefault:
return self._cookies[name]
else:
return self._cookies.get(name, default)
def hasCookie(self, name):
return self._cookies.has_key(name)
def cookies(self):
"""Return a dictionary-style object of all Cookie objects the client sent with this request."""
return self._cookies
def serverDictionary(self):
"""Return a dictionary with the data the web server gave us.
This data includes HTTP_HOST and HTTP_USER_AGENT, for example.
"""
return self._environ
def session(self):
"""Return the session associated with this request.
The session is either as specified by sessionId() or newly created.
This is a convenience for transaction.session()
"""
return self._transaction.session()
def isSessionExpired(self):
"""Return whether or not this request originally contained an expired session ID.
Only works if the Application.config setting "IgnoreInvalidSession" is set to 1;
otherwise you get a canned error page on an invalid session,
so your servlet never gets processed.
"""
return self._sessionExpired
def setSessionExpired(self, sessionExpired):
self._sessionExpired = sessionExpired
def remoteUser(self):
"""Always returns None since authentication is not yet supported.
Take from CGI variable REMOTE_USER.
"""
return self._environ['REMOTE_USER']
def remoteAddress(self):
"""Return a string containing the IP address of the client that sent the request."""
return self._environ['REMOTE_ADDR']
def remoteName(self):
"""Return the fully qualified name of the client that sent the request.
Returns the IP address of the client if the name cannot be determined.
"""
env = self._environ
return env.get('REMOTE_NAME', env['REMOTE_ADDR'])
def urlPath(self):
"""Return the URL path of the servlet sans host, adapter and query string.
For example, http://host/WebKit.cgi/Context/Servlet?x=1 yields '/Context/Servlet'.
"""
self._absolutepath = 0
if self._environ.has_key('WK_ABSOLUTE'):
self._absolutepath = 1
return self.fsPath()
return self._environ['PATH_INFO']
def originalURLPath(self):
"""Return the URL path of the _original_ servlet before any forwarding."""
return self._originalURLPath
def urlPathDir(self):
"""Same as urlPath, but only gives the directory."""
path = self.urlPath()
if not path[:-1] == '/':
path = path[:path.rfind('/')+1]
return path
_evars = ('PATH_INFO', 'REQUEST_URI', 'SCRIPT_NAME')
_pvars = ('_absolutepath', '_serverSidePath', '_serverSideContextPath',
'_adapterName')
def getstate(self):
"""Debugging and testing code. This will likely be removed in the future."""
rv = []
env = self._environ
for key in self._evars:
rv.append(" * env['%s'] = %s"
% (key, env.get(key, "* no def *")))
for key in self._pvars + ('_contextName', '_extraURLPath'):
rv.append(" * req.%s = %s"
% (key, getattr(self, key, "* no def *")))
return '\n'.join(rv)
def setURLPath(self, path):
"""Set the URL path of the request."
There is rarely a need to do this. Proceed with caution.
The only known current use for this is Application.forwardRequest().
"""
if hasattr(self, '_serverSidePath'):
del self._serverSidePath
del self._serverSideContextPath
if hasattr(self, '_serverSideDir'):
del self._serverSideDir
self._environ['PATH_INFO'] = path
self._environ['REQUEST_URI'] = self.adapterName() + path
def serverSidePath(self, path=None):
"""Return the absolute server-side path of the request.
If the optional path is passed in, then it is joined with the
server side directory to form a path relative to the object.
"""
if path:
return os.path.normpath(os.path.join(os.path.dirname(
self._serverSidePath), path))
else:
return self._serverSidePath
def serverSideContextPath(self, path=None):
"""Return the absolute server-side path of the context of this request.
If the optional path is passed in, then it is joined with the server side
context directory to form a path relative to the object.
This directory could be different from the result of serverSidePath() if the request
is in a subdirectory of the main context directory.
"""
if path:
return os.path.normpath(os.path.join(
self._serverSideContextPath, path))
else:
return self._serverSideContextPath
def contextName(self):
"""Return the name of the context of this request.
This isn't necessarily the same as the name of the directory containing the context.
"""
return self._contextName
def servletURI(self):
"""Return the URI of the servlet, without any query strings or extra path info."""
pinfo = self.pathInfo()
if not self._extraURLPath:
if pinfo and pinfo[-1] == "/":
pinfo = pinfo[:-1]
return pinfo
pos = pinfo.rfind(self._extraURLPath)
if pos == -1:
URI = pinfo
else:
URI = pinfo[:pos]
if URI and URI[-1] == "/":
URI = URI[:-1]
return URI
def uriWebKitRoot(self):
if not self._serverRootPath:
self._serverRootPath = ''
loc = self.urlPath()
loc,curr = os.path.split(loc)
while 1:
loc,curr = os.path.split(loc)
if curr:
self._serverRootPath = self._serverRootPath + "../"
else: break
return self._serverRootPath
def fsPath(self):
"""The filesystem path of the request, using the webserver's docroot."""
docroot = self._environ['DOCUMENT_ROOT']
requri = self._environ['REQUEST_URI'][1:]
if self.queryString():
qslength = len(self.queryString())+1
requri = requri[:-qslength]
fspath = os.path.join(docroot,requri)
return fspath
def serverURL(self):
"""Return the full internet path to this request.
The path is returned without any extra path info or query strings,
i.e. www.my.own.host.com/WebKit/TestPage.py
"""
host = self._environ['HTTP_HOST']
adapter = self.adapterName()
path = self.urlPath()
return host+adapter+path
def serverURLDir(self):
"""Return the Directory of the URL in full internet form.
This is the same as serverURL,
but removes the actual page name if it was included.
"""
fullurl = self.serverURL()
if fullurl[-1] != '/':
fullurl = fullurl[:fullurl.rfind('/')+1]
return fullurl
def siteRoot(self):
"""Return the URL path components necessary to get back home from the current location.
Examples:
''
'../'
'../../'
You can use this as a prefix to a URL that you know is based off
the home location. Any time you are in a servlet that may have been
forwarded to from another servlet at a different level,
you should prefix your URL's with this. That is, if servlet "Foo/Bar"
forwards to "Qux", then the qux servlet should use siteRoot() to construct all
links to avoid broken links. This works properly because this method
computes the path based on the _original_ servlet, not the location of the
servlet that you have forwarded to.
"""
url = self.originalURLPath()[1:]
contextName = self.contextName() + '/'
if url.startswith(contextName):
url = url[len(contextName):]
numStepsBackward = len(url.split('/')) - 1
return '../' * numStepsBackward
def siteRootFromCurrentServlet(self):
"""Return the URL path componentes necessary to get back home from the current servlet.
Similar to siteRoot() but instead, it returns the site root
relative to the _current_ servlet, not the _original_ servlet.
"""
url = self.urlPath()[1:]
contextName = self.contextName() + '/'
if url.startswith(contextName):
url = url[len(contextName):]
numStepsBackward = len(url.split('/')) - 1
return '../' * numStepsBackward
def servletPathFromSiteRoot(self):
"""Returns the "servlet path" of this servlet relative to the siteRoot.
In other words, everything after the name of the context (if present).
If you append this to the result of self.siteRoot() you get back to the
current servlet. This is useful for saving the path to the current servlet
in a database, for example.
"""
urlPath = self.urlPath()
if urlPath[:1] == '/':
urlPath = urlPath[1:]
parts = urlPath.split('/')
newParts = []
for part in parts:
if part == '..':
if newParts:
newParts.pop()
else:
newParts.append(part)
if newParts[:1] == [self.contextName()]:
newParts[:1] = []
return '/'.join(newParts)
def adapterName(self):
"""Return the name of the adapter as it appears in the URL.
Example: '/WebKit.cgi'
This is useful in special cases when you are constructing URLs.
See Testing/Main.py for an example use.
"""
return self._adapterName
def rawRequest(self):
"""Return the raw request that was used to initialize this request object."""
return self._rawRequest
def environ(self):
return self._environ
def addParent(self, servlet):
self._parents.append(servlet)
def popParent(self):
if self._parents:
self._parents.pop()
def parent(self):
"""Get the servlet that passed this request to us, if any."""
if self._parents:
return self._parents[len(self._parents)-1]
def parents(self):
"""Return the parents list."""
return self._parents
def rawInput(self, rewind=0):
"""Get the raw input from the request.
This gives you a file-like object for the data that was
sent with the request (e.g., the body of a POST request,
or the documented uploaded in a PUT request).
The file might not be rewound to the beginning if there
was valid, form-encoded POST data. Pass rewind=1 if
you want to be sure you get the entire body of the request.
"""
fs = self.fieldStorage()
if rewind:
fs.file.seek(0)
return fs.file
def time(self):
"""Return the time that the request was received."""
return self._time
def requestID(self):
"""Return the request ID.
The request ID is a serial number unique to this request
(at least unique for one run of the AppServer).
"""
return self._requestID
def servletPath(self):
return self._environ['SCRIPT_NAME']
def contextPath(self):
"""Return the portion of the request URI that is the context of the request."""
raise NotImplementedError
def pathInfo(self):
"""Return any extra path information associated with the URL the client sent with this request.
Equivalent to CGI variable PATH_INFO.
"""
if self._pathInfo is None:
self._pathInfo = self._environ['PATH_INFO'][1:]
return self._pathInfo
def pathTranslated(self):
"""Return any extra path information after the servlet name but before the query string.
The path is translated to a file system path.
Equivalent to CGI variable PATH_TRANSLATED.
"""
return self._environ.get('PATH_TRANSLATED', None)
def queryString(self):
"""Return the query string portion of the URL for this request.
Taken from the CGI variable QUERY_STRING.
"""
return self._environ.get('QUERY_STRING', '')
def uri(self):
""" Returns the request URI, which is the entire URL except for the query string.
@@TR 2003-03-22: I think this docstring is wrong, as REQUEST_URI
does contain the query string with Apache.
"""
return self._environ['REQUEST_URI']
def method(self):
"""Return the HTTP request method (in all uppercase).
Typically from the set GET, POST, PUT, DELETE, OPTIONS and TRACE.
"""
return self._environ['REQUEST_METHOD'].upper()
def sessionId(self):
"""Return a string with the session id specified by the client, or None if there isn't one."""
sid = self.value('_SID_', None)
if self._transaction.application().setting('Debug')['Sessions']:
print '>> sessionId: returning sid =', sid
return sid
def setSessionId(self, sessionID, force=False):
"""Allows you to set the session id.
This needs to be called _before_ attempting to use the session.
This would be useful if the session ID is being passed in through
unusual means, for example via a field in an XML-RPC request.
Pass in force=True if you want to force this session ID to be used
even if the session doesn't exist. This would be useful in unusual
circumstances where the client is responsible for creating the unique
session ID rather than the server.
Be sure to use only legal filename characters in the session ID --
0-9, a-z, A-Z, _, -, and . are OK but everything else will be rejected,
as will identifiers longer than 80 characters.
(Without passing in force=True, a random session ID will be generated
if that session ID isn't already present in the session store.)
"""
self.setField('_SID_', sessionID)
if force:
self._transaction.application().createSessionWithID(self._transaction, sessionID)
def hasPathSession(self):
return self._pathSession is not None
def hasCookieSession(self):
return self._cookieSession is not None
def info(self):
"""Return request info.
Return a list of tuples where each tuple has a key/label (a string)
and a value (any kind of object).
Values are typically atomic values such as numbers and strings
or another list of tuples in the same fashion. This is for debugging only.
"""
info = [
('time', self._time),
('environ', self._environ),
('input', self._input),
('fields', self._fields),
('cookies', self._cookies)
]
for method in _infoMethods:
try:
info.append((method.__name__, apply(method, (self,))))
except:
info.append((method.__name__, None))
return info
def htmlInfo(self):
"""Return a single HTML string that represents info().
Useful for inspecting objects via web browsers.
"""
return htmlInfo(self.info())
info = self.info()
res = ['<table border="1">\n']
for pair in info:
value = pair[1]
if hasattr(value, 'items') and (type(value) is type({})
or hasattr(value, '__getitem__')):
value = _infoForDict(value)
res.append('<tr valign="top"><td>%s</td><td>%s </td></tr>\n'
% (pair[0], value))
res.append('</table>\n')
return ''.join(res)
exceptionReportAttrNames = Request.exceptionReportAttrNames + (
'uri servletPath serverSidePath pathInfo pathTranslated'
' queryString method sessionId parents fields cookies environ'.split())
def serverSideDir(self):
"""deprecated: HTTPRequest.serverSideDir() on 01/24/01 in 0.5.
use serverSidePath() instead.@
Return the directory of the Servlet (as given through __init__()'s path).
"""
self.deprecated(self.serverSideDir)
if not hasattr(self, '_serverSideDir'):
self._serverSideDir = os.path.dirname(self.serverSidePath())
return self._serverSideDir
def relativePath(self, joinPath):
"""deprecated: HTTPRequest.relativePath() on 01/24/01 in 0.5.
use serverSidePath() instead.@
Return a new path which includes the servlet's path appended by 'joinPath'.
Note that if 'joinPath' is an absolute path, then only 'joinPath' is returned.
"""
self.deprecated(self.relativePath)
return os.path.join(self.serverSideDir(), joinPath)
_infoMethods = (
HTTPRequest.servletPath,
HTTPRequest.contextPath,
HTTPRequest.pathInfo,
HTTPRequest.pathTranslated,
HTTPRequest.queryString,
HTTPRequest.uri,
HTTPRequest.method,
HTTPRequest.sessionId
)
def htmlInfo(info):
"""Return a single HTML string that represents the info structure.
Useful for inspecting objects via web browsers.
"""
res = ['<table border="1">\n']
for pair in info:
value = pair[1]
if hasattr(value, 'items') and (type(value) is type({})
or hasattr(value, '__getitem__')):
value = htmlInfo(_infoForDict(value))
res.append('<tr valign="top"><td>%s</td><td>%s </td></tr>\n'
% (pair[0], value))
res.append('</table>\n')
return ''.join(res)
def _infoForDict(dict):
"""Return an "info" structure for any dictionary-like object."""
items = dict.items()
items.sort(lambda a, b: cmp(a[0], b[0]))
return items