initial deployment and project layout commit
authorom
Mon, 02 Dec 2013 14:02:05 +0100
changeset 365432e6c6042
parent 1 446a7ba98309
child 4 0c8bc9c94951
initial deployment and project layout commit
OpenSecurity/Readme.md
OpenSecurity/client/about.py
OpenSecurity/client/credentials.py
OpenSecurity/client/cygwin.py
OpenSecurity/client/environment.py
OpenSecurity/client/launch.py
OpenSecurity/client/opensecurity_client_restful_server.py
OpenSecurity/client/opensecurity_dialog.py
OpenSecurity/client/opensecurity_server.py
OpenSecurity/client/opensecurity_tray.py
OpenSecurity/client/password.py
OpenSecurity/cygwin/Cygwin.vbs
OpenSecurity/cygwin/content.txt
OpenSecurity/cygwin/setup-x86.exe
OpenSecurity/cygwin64/Cygwin.vbs
OpenSecurity/cygwin64/content.txt
OpenSecurity/cygwin64/setup-x86_64.exe
OpenSecurity/gfx/ait_logo.jpg
OpenSecurity/gfx/ait_logo_no_claim.png
OpenSecurity/gfx/bmvit_logo.jpg
OpenSecurity/gfx/ffg_logo.jpg
OpenSecurity/gfx/ikarus_logo.jpg
OpenSecurity/gfx/kiras_logo.jpg
OpenSecurity/gfx/linz_logo.jpg
OpenSecurity/gfx/liqua_logo.jpg
OpenSecurity/gfx/opensecurity.ico
OpenSecurity/gfx/opensecurity_icon_64.png
OpenSecurity/gfx/opensecurity_logo.jpg
OpenSecurity/gfx/x-net_logo.jpg
OpenSecurity/install/OpenSecurity.reg
OpenSecurity/install/content.txt
OpenSecurity/install/web.py-0.37/PKG-INFO
OpenSecurity/install/web.py-0.37/build/lib/web/__init__.py
OpenSecurity/install/web.py-0.37/build/lib/web/application.py
OpenSecurity/install/web.py-0.37/build/lib/web/browser.py
OpenSecurity/install/web.py-0.37/build/lib/web/contrib/__init__.py
OpenSecurity/install/web.py-0.37/build/lib/web/contrib/template.py
OpenSecurity/install/web.py-0.37/build/lib/web/db.py
OpenSecurity/install/web.py-0.37/build/lib/web/debugerror.py
OpenSecurity/install/web.py-0.37/build/lib/web/form.py
OpenSecurity/install/web.py-0.37/build/lib/web/http.py
OpenSecurity/install/web.py-0.37/build/lib/web/httpserver.py
OpenSecurity/install/web.py-0.37/build/lib/web/net.py
OpenSecurity/install/web.py-0.37/build/lib/web/python23.py
OpenSecurity/install/web.py-0.37/build/lib/web/session.py
OpenSecurity/install/web.py-0.37/build/lib/web/template.py
OpenSecurity/install/web.py-0.37/build/lib/web/test.py
OpenSecurity/install/web.py-0.37/build/lib/web/utils.py
OpenSecurity/install/web.py-0.37/build/lib/web/webapi.py
OpenSecurity/install/web.py-0.37/build/lib/web/webopenid.py
OpenSecurity/install/web.py-0.37/build/lib/web/wsgi.py
OpenSecurity/install/web.py-0.37/build/lib/web/wsgiserver/__init__.py
OpenSecurity/install/web.py-0.37/build/lib/web/wsgiserver/ssl_builtin.py
OpenSecurity/install/web.py-0.37/build/lib/web/wsgiserver/ssl_pyopenssl.py
OpenSecurity/install/web.py-0.37/setup.py
OpenSecurity/install/web.py-0.37/web/__init__.py
OpenSecurity/install/web.py-0.37/web/__init__.pyc
OpenSecurity/install/web.py-0.37/web/application.py
OpenSecurity/install/web.py-0.37/web/application.pyc
OpenSecurity/install/web.py-0.37/web/browser.py
OpenSecurity/install/web.py-0.37/web/browser.pyc
OpenSecurity/install/web.py-0.37/web/contrib/__init__.py
OpenSecurity/install/web.py-0.37/web/contrib/template.py
OpenSecurity/install/web.py-0.37/web/db.py
OpenSecurity/install/web.py-0.37/web/db.pyc
OpenSecurity/install/web.py-0.37/web/debugerror.py
OpenSecurity/install/web.py-0.37/web/debugerror.pyc
OpenSecurity/install/web.py-0.37/web/form.py
OpenSecurity/install/web.py-0.37/web/form.pyc
OpenSecurity/install/web.py-0.37/web/http.py
OpenSecurity/install/web.py-0.37/web/http.pyc
OpenSecurity/install/web.py-0.37/web/httpserver.py
OpenSecurity/install/web.py-0.37/web/httpserver.pyc
OpenSecurity/install/web.py-0.37/web/net.py
OpenSecurity/install/web.py-0.37/web/net.pyc
OpenSecurity/install/web.py-0.37/web/python23.py
OpenSecurity/install/web.py-0.37/web/session.py
OpenSecurity/install/web.py-0.37/web/session.pyc
OpenSecurity/install/web.py-0.37/web/template.py
OpenSecurity/install/web.py-0.37/web/template.pyc
OpenSecurity/install/web.py-0.37/web/test.py
OpenSecurity/install/web.py-0.37/web/utils.py
OpenSecurity/install/web.py-0.37/web/utils.pyc
OpenSecurity/install/web.py-0.37/web/webapi.py
OpenSecurity/install/web.py-0.37/web/webapi.pyc
OpenSecurity/install/web.py-0.37/web/webopenid.py
OpenSecurity/install/web.py-0.37/web/webopenid.pyc
OpenSecurity/install/web.py-0.37/web/wsgi.py
OpenSecurity/install/web.py-0.37/web/wsgi.pyc
OpenSecurity/install/web.py-0.37/web/wsgiserver/__init__.py
OpenSecurity/install/web.py-0.37/web/wsgiserver/ssl_builtin.py
OpenSecurity/install/web.py-0.37/web/wsgiserver/ssl_pyopenssl.py
OpenSecurity/server/opensecurityd.py
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/OpenSecurity/Readme.md	Mon Dec 02 14:02:05 2013 +0100
     1.3 @@ -0,0 +1,151 @@
     1.4 +# How To OpenSecurity Demo
     1.5 +
     1.6 +## Installation
     1.7 +
     1.8 +1. Copy the OpenSecurity parent Folder as-is to "C:\Program Files"
     1.9 +
    1.10 +2. Inside this folder you find:
    1.11 +
    1.12 +    OpenSecurity/
    1.13 +    ├── client  ............... OpenSecurity client code
    1.14 +    ├── cygwin  ............... A cygwin subsystem used inside OpenSecurity
    1.15 +    ├── gfx  .................. OpenSecurity images and icons used
    1.16 +    ├── install  .............. Necessary 3rd party installment
    1.17 +    └── server  ............... OpenSecuirty server code
    1.18 +
    1.19 +3. Switch into the "install" folder. There you have:
    1.20 +
    1.21 +    OpenSecurity/
    1.22 +    └── install/
    1.23 +        ├── OpenSecurity.reg
    1.24 +        ├── PyQt4-4.10.3-gpl-Py2.7-Qt4.8.5-x32.exe
    1.25 +        ├── PyQt4-4.10.3-gpl-Py2.7-Qt4.8.5-x64.exe
    1.26 +        ├── python-2.7.6.amd64.msi
    1.27 +        ├── python-2.7.6.msi
    1.28 +        ├── VirtualBox-4.3.4-91027-Win.exe
    1.29 +        └── web.py-0.37
    1.30 +    
    1.31 +    Please install the software via double-click:
    1.32 +
    1.33 +    * python-2.7.6.msi on 32-Bit
    1.34 +        _or_ 
    1.35 +      python-2.7.6.amd64.msi on 64-Bit
    1.36 +      
    1.37 +    * PyQt4-4.10.3-gpl-Py2.7-Qt4.8.5-x32.exe on 32-Bit
    1.38 +        _or_
    1.39 +      PyQt4-4.10.3-gpl-Py2.7-Qt4.8.5-x64.exe on 64-Bit
    1.40 +    
    1.41 +    * VirtualBox-4.3.4-91027-Win.exe
    1.42 +    
    1.43 +    If you didn't change any settings you'll have a Win32 Python
    1.44 +    installation at C:\Python27 right now. This is important for 
    1.45 +    the next step.
    1.46 +    
    1.47 +    1. Open up a cmd box --> Start / Execute / "cmd"
    1.48 +    2. Switch into the OpenSecurity folder where the web.py resides:
    1.49 +    
    1.50 +        C:> cd C:\Program Files\OpenSecurity\install\web.py-0.37
    1.51 +        
    1.52 +    3. Install web.py by calling the setup.py with the "install" command
    1.53 +       from within a python shell:
    1.54 +       
    1.55 +        C:\Program Files\OpenSecurity\install\web.py-0.37> C:\Python27\python.exe setup.py install
    1.56 +        running install
    1.57 +        running build
    1.58 +        running build_py
    1.59 +        creating build
    1.60 +        creating build\lib
    1.61 +        creating build\lib\web
    1.62 +        copying web\application.py -> build\lib\web
    1.63 +        copying web\browser.py -> build\lib\web
    1.64 +        copying web\db.py -> build\lib\web
    1.65 +        copying web\debugerror.py -> build\lib\web
    1.66 +        copying web\form.py -> build\lib\web        
    1.67 +        ...
    1.68 +
    1.69 +4. Finally update your registry by double-clicking the OpenSecurity.reg file.
    1.70 +
    1.71 +5. To make changes in effect (automatically starting the OpenSecurity client and server daemons) you should restrat the machine.
    1.72 +
    1.73 +NOTE: 
    1.74 +    Windows will pop up an UAC dialog for X11, OpenSecurity Client Daemon and OpenSecuirty Server Daemon
    1.75 +
    1.76 +    
    1.77 +## VirtualBox VM Images
    1.78 +
    1.79 +For the current setup to work you need at least a single Virtual Machine:
    1.80 +
    1.81 +1. Create a Virtual Machine for Debian Linux
    1.82 +    --> The machine should be named 'Debian 7'
    1.83 +    --> There must be a user called 'user'
    1.84 +
    1.85 +2. Have a Debian 7 (or 7.2) network installation ready and install a fresh new Debian system, with a user called 'user'.
    1.86 +
    1.87 +3. Create 2 (!) Network Interfaces for your Virtual Machine
    1.88 +    a) The first will be set to "NAT" --> this will be eth0
    1.89 +    b) The second will be set to "Host-Only Adapter" --> this will be eth1
    1.90 +
    1.91 +4. Power up the Virtual Machine and set the network interface configuration (/etc/network/interfaces) to:
    1.92 +
    1.93 +    auto lo
    1.94 +    iface lo
    1.95 +    
    1.96 +    auto eth0
    1.97 +    allow-hotplug eth0
    1.98 +    iface eth0 inet dhcp
    1.99 +    
   1.100 +    auto eth1
   1.101 +    iface eth1 inet static
   1.102 +        address 192.168.56.101
   1.103 +        netmask 255.255.255.0
   1.104 +        gateway 192.168.56.1
   1.105 +        
   1.106 +5. Create a passwordless SSH connection from within Cygwin into the VM:
   1.107 +
   1.108 +    a) ensure the VM is started and you have a user login called 'user'.
   1.109 +    b) start a cygwin shell by double-clicking "C:\Program Files\OpenSecurity\cygwin\Cygwin.vbs"
   1.110 +    c) generate a ssh-key
   1.111 +    
   1.112 +        $ ssh-keygen
   1.113 +        
   1.114 +        --> do not set passphrases, leave all to default
   1.115 +    d) copy the public key to the virtual machine
   1.116 +    
   1.117 +        $ scp ~/.ssh/id_rsa.pub user@192.168.56.101:
   1.118 +        
   1.119 +    e) add the public key to the list of authorized keys:
   1.120 +    
   1.121 +        - login into the virtual machine
   1.122 +        - open up a terminal
   1.123 +        
   1.124 +        $ mkdir ~/.ssh &> /dev/null
   1.125 +        $ cat id_rsa.pub >> ~/.ssh/authorized_keys
   1.126 +        
   1.127 +    f) test the passwordless connection by open the cyginw command prompt on the Windows Host again:
   1.128 +    
   1.129 +        $ ssh user@192.168.56.101
   1.130 +        
   1.131 +       --> this should now give you a login shell on the virtual machine without a password request.
   1.132 +        
   1.133 +        (you can now safely delete the id_rsa.pub file in your virtual machine's home)
   1.134 +
   1.135 +
   1.136 +## Demonstration
   1.137 +
   1.138 +* Start the Virtual Machine
   1.139 +    --> You do not have to log in. Just start the machine. If the X11-Login Screen appears, all is done.
   1.140 +    
   1.141 +* Start the opensecurity-client by calling
   1.142 +
   1.143 +    NOTE: you may omit this step if you double-clicked the OpenSecuirty.reg file previously.
   1.144 +
   1.145 +    C:> C:\
   1.146 +    C:> cd "C:\Program Files\OpenSecurity\client"
   1.147 +    C:\Program Files\OpenSecurity\client> start "opensecurity_client_restful_server.py 8090"
   1.148 +    
   1.149 +    
   1.150 +* Open Up a browser and type:
   1.151 +
   1.152 +    "http://127.0.0.1:8090"
   1.153 +    
   1.154 +    HAVE FUN! =D
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/OpenSecurity/client/about.py	Mon Dec 02 14:02:05 2013 +0100
     2.3 @@ -0,0 +1,126 @@
     2.4 +#!/bin/env python
     2.5 +# -*- coding: utf-8 -*-
     2.6 +
     2.7 +# ------------------------------------------------------------
     2.8 +# about-dialog
     2.9 +# 
    2.10 +# tell the user about the project
    2.11 +#
    2.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    2.13 +#
    2.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
    2.15 +# AIT Austrian Institute of Technology GmbH
    2.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
    2.17 +# http://www.ait.ac.at
    2.18 +#
    2.19 +# This program is free software; you can redistribute it and/or
    2.20 +# modify it under the terms of the GNU General Public License
    2.21 +# as published by the Free Software Foundation version 2.
    2.22 +# 
    2.23 +# This program is distributed in the hope that it will be useful,
    2.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    2.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    2.26 +# GNU General Public License for more details.
    2.27 +# 
    2.28 +# You should have received a copy of the GNU General Public License
    2.29 +# along with this program; if not, write to the Free Software
    2.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    2.31 +# Boston, MA  02110-1301, USA.
    2.32 +# ------------------------------------------------------------
    2.33 +
    2.34 +
    2.35 +# ------------------------------------------------------------
    2.36 +# imports
    2.37 +
    2.38 +import os
    2.39 +
    2.40 +from PyQt4 import QtCore
    2.41 +from PyQt4 import QtGui
    2.42 +
    2.43 +# local
    2.44 +from environment import Environment
    2.45 +
    2.46 +# ------------------------------------------------------------
    2.47 +# vars
    2.48 +
    2.49 +
    2.50 +ABOUT_TEXT = """
    2.51 +<html>
    2.52 +<body bgcolor="#FFFFFF">
    2.53 +
    2.54 +<div align="center">
    2.55 +<p/>
    2.56 +<img src="image:ait_logo_no_claim.png"/>
    2.57 +<p/>
    2.58 +<h1>OpenSecurity</h1>
    2.59 +<p/>
    2.60 +</div>
    2.61 +<p/>
    2.62 +Blah ...<br/>
    2.63 +
    2.64 +<p>
    2.65 +Copyright (C) 2013, AIT Austrian Institute of Technology<br/>
    2.66 +AIT Austrian Institute of Technology GmbH<br/>
    2.67 +Donau-City-Strasse 1 | 1220 Vienna | Austria<br/>
    2.68 +<a href="http://www.ait.ac.at">http://www.ait.ac.at</a>
    2.69 +</p>
    2.70 +</div>
    2.71 +
    2.72 +</body>
    2.73 +</html>
    2.74 +""";
    2.75 +
    2.76 +
    2.77 +# ------------------------------------------------------------
    2.78 +# code
    2.79 +
    2.80 +
    2.81 +class About(QtGui.QDialog):
    2.82 +    
    2.83 +    """Show some about stuff."""
    2.84 +    
    2.85 +    def __init__(self, parent = None, flags = QtCore.Qt.WindowFlags(0)):
    2.86 +        
    2.87 +        # super call and widget init
    2.88 +        super(About, self).__init__(parent, flags)
    2.89 +        
    2.90 +        # setup image search path
    2.91 +        QtCore.QDir.setSearchPaths("image", QtCore.QStringList(os.path.join(Environment('opensecurity').data_path, '..', 'gfx')));
    2.92 +        
    2.93 +        self.setWindowTitle('About OpenSecuirty ...')
    2.94 +        self.setup_ui()
    2.95 +        
    2.96 +
    2.97 +    def setup_ui(self):
    2.98 +        
    2.99 +        """Create the widgets."""
   2.100 +        
   2.101 +        lyMain = QtGui.QVBoxLayout(self)
   2.102 +        lyMain.setContentsMargins(8, 8, 8, 8)
   2.103 +        
   2.104 +        lbAbout = QtGui.QLabel()
   2.105 +        lbAbout.setStyleSheet("QWidget { background: white; color: black; };")
   2.106 +        lbAbout.setText(ABOUT_TEXT)
   2.107 +        lbAbout.setContentsMargins(12, 12, 12, 12)
   2.108 +        
   2.109 +        scAbout = QtGui.QScrollArea()
   2.110 +        scAbout.setWidget(lbAbout)
   2.111 +        scAbout.viewport().setStyleSheet("QWidget { background: white; color: black; };")
   2.112 +        lyMain.addWidget(scAbout)
   2.113 +        
   2.114 +        # buttons
   2.115 +        lyButton = QtGui.QHBoxLayout()
   2.116 +        lyMain.addLayout(lyButton)
   2.117 +        
   2.118 +        lyButton.addStretch(1)
   2.119 +        btnOk = QtGui.QPushButton('&Ok', self)
   2.120 +        btnOk.setMinimumWidth(100)
   2.121 +        lyButton.addWidget(btnOk)
   2.122 +        
   2.123 +        # connectors
   2.124 +        btnOk.clicked.connect(self.accept)
   2.125 +        
   2.126 +        # reduce to the max
   2.127 +        self.setMinimumSize(400, 200)
   2.128 +        self.resize(lyMain.minimumSize())
   2.129 +
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/OpenSecurity/client/credentials.py	Mon Dec 02 14:02:05 2013 +0100
     3.3 @@ -0,0 +1,160 @@
     3.4 +#!/bin/env python
     3.5 +# -*- coding: utf-8 -*-
     3.6 +
     3.7 +# ------------------------------------------------------------
     3.8 +# credentials-dialog
     3.9 +# 
    3.10 +# ask the user credentials
    3.11 +#
    3.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    3.13 +#
    3.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
    3.15 +# AIT Austrian Institute of Technology GmbH
    3.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
    3.17 +# http://www.ait.ac.at
    3.18 +#
    3.19 +# This program is free software; you can redistribute it and/or
    3.20 +# modify it under the terms of the GNU General Public License
    3.21 +# as published by the Free Software Foundation version 2.
    3.22 +# 
    3.23 +# This program is distributed in the hope that it will be useful,
    3.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.26 +# GNU General Public License for more details.
    3.27 +# 
    3.28 +# You should have received a copy of the GNU General Public License
    3.29 +# along with this program; if not, write to the Free Software
    3.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    3.31 +# Boston, MA  02110-1301, USA.
    3.32 +# ------------------------------------------------------------
    3.33 +
    3.34 +
    3.35 +# ------------------------------------------------------------
    3.36 +# imports
    3.37 +
    3.38 +import sys
    3.39 +
    3.40 +from PyQt4 import QtCore
    3.41 +from PyQt4 import QtGui
    3.42 +
    3.43 +# local
    3.44 +from about import About
    3.45 +
    3.46 +# ------------------------------------------------------------
    3.47 +# code
    3.48 +
    3.49 +
    3.50 +class Credentials(QtGui.QDialog):
    3.51 +    
    3.52 +    """Ask the user for credentials."""
    3.53 +    
    3.54 +    def __init__(self, text, parent = None, flags = QtCore.Qt.WindowFlags(0)):
    3.55 +        
    3.56 +        super(Credentials, self).__init__(parent, flags)
    3.57 +        self.setWindowTitle('OpenSecuirty Credentials Request')
    3.58 +        self.setup_ui()
    3.59 +        
    3.60 +        # positionate ourself central
    3.61 +        screen = QtGui.QDesktopWidget().screenGeometry()
    3.62 +        self.resize(self.geometry().width() * 1.25, self.geometry().height())
    3.63 +        size = self.geometry()
    3.64 +        self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2)
    3.65 +        
    3.66 +        # fix up text
    3.67 +        self.lbText.setText(text)
    3.68 +        
    3.69 +
    3.70 +    def clicked_about(self):
    3.71 +        """clicked the about button"""
    3.72 +        dlgAbout = About()
    3.73 +        dlgAbout.exec_()
    3.74 +    
    3.75 +
    3.76 +    def clicked_cancel(self):
    3.77 +        """clicked the cancel button"""
    3.78 +        self.reject()
    3.79 +    
    3.80 +
    3.81 +    def clicked_ok(self):
    3.82 +        """clicked the ok button"""
    3.83 +        sys.stdout.write('{ ')
    3.84 +        sys.stdout.write('\'user\': \'')
    3.85 +        sys.stdout.write(self.edUser.text())
    3.86 +        sys.stdout.write('\', ')
    3.87 +        sys.stdout.write('\'password\': \'')
    3.88 +        sys.stdout.write(self.edPassword.text())
    3.89 +        sys.stdout.write('\' ')
    3.90 +        sys.stdout.write('}\n')
    3.91 +        self.accept()
    3.92 +    
    3.93 +
    3.94 +    def setup_ui(self):
    3.95 +        
    3.96 +        """Create the widgets."""
    3.97 +        
    3.98 +        lyMain = QtGui.QVBoxLayout(self)
    3.99 +        lyMain.setContentsMargins(8, 8, 8, 8)
   3.100 +        
   3.101 +        # content area: left pixmap, right text
   3.102 +        lyContent = QtGui.QHBoxLayout()
   3.103 +        lyMain.addLayout(lyContent)
   3.104 +        
   3.105 +        # pixmap
   3.106 +        lbPix = QtGui.QLabel()
   3.107 +        lbPix.setPixmap(QtGui.QPixmapCache.find('opensecurity_icon_64'))
   3.108 +        lyContent.addWidget(lbPix, 0, QtCore.Qt.Alignment(QtCore.Qt.AlignTop + QtCore.Qt.AlignHCenter))
   3.109 +        lyContent.addSpacing(16)
   3.110 +        
   3.111 +        # text ...
   3.112 +        lyText = QtGui.QGridLayout()
   3.113 +        lyContent.addLayout(lyText)
   3.114 +        self.lbText = QtGui.QLabel()
   3.115 +        lyText.addWidget(self.lbText, 0, 0, 1, 2)
   3.116 +        
   3.117 +        lbUser = QtGui.QLabel('&User:')
   3.118 +        lyText.addWidget(lbUser, 1, 0)
   3.119 +        self.edUser = QtGui.QLineEdit()
   3.120 +        lyText.addWidget(self.edUser, 1, 1)
   3.121 +        lbUser.setBuddy(self.edUser)
   3.122 +        
   3.123 +        lbPassword = QtGui.QLabel('&Password:')
   3.124 +        lyText.addWidget(lbPassword, 2, 0)
   3.125 +        self.edPassword = QtGui.QLineEdit()
   3.126 +        self.edPassword.setEchoMode(QtGui.QLineEdit.Password)
   3.127 +        lyText.addWidget(self.edPassword, 2, 1)
   3.128 +        lbPassword.setBuddy(self.edPassword)
   3.129 +        
   3.130 +        lyText.addWidget(QtGui.QWidget(), 3, 0, 1, 2)
   3.131 +        lyText.setColumnStretch(1, 1)
   3.132 +        lyText.setRowStretch(3, 1)
   3.133 +        
   3.134 +        lyMain.addStretch(1)
   3.135 +        
   3.136 +        # buttons
   3.137 +        lyButton = QtGui.QHBoxLayout()
   3.138 +        lyMain.addLayout(lyButton)
   3.139 +        
   3.140 +        lyButton.addStretch(1)
   3.141 +        btnOk = QtGui.QPushButton('&Ok', self)
   3.142 +        btnOk.setDefault(True)
   3.143 +        btnOk.setMinimumWidth(100)
   3.144 +        lyButton.addWidget(btnOk)
   3.145 +        btnCancel = QtGui.QPushButton('&Cancel', self)
   3.146 +        btnCancel.setMinimumWidth(100)
   3.147 +        lyButton.addWidget(btnCancel)
   3.148 +        btnAbout = QtGui.QPushButton('&About', self)
   3.149 +        btnAbout.setMinimumWidth(100)
   3.150 +        lyButton.addWidget(btnAbout)
   3.151 +        
   3.152 +        button_width = max(btnOk.width(), btnCancel.width(), btnAbout.width())
   3.153 +        btnOk.setMinimumWidth(button_width)
   3.154 +        btnCancel.setMinimumWidth(button_width)
   3.155 +        btnAbout.setMinimumWidth(button_width)
   3.156 +        
   3.157 +        # reduce to the max
   3.158 +        self.resize(lyMain.minimumSize())
   3.159 +        
   3.160 +        # connectors
   3.161 +        btnOk.clicked.connect(self.clicked_ok)
   3.162 +        btnCancel.clicked.connect(self.clicked_cancel)
   3.163 +        btnAbout.clicked.connect(self.clicked_about)
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/OpenSecurity/client/cygwin.py	Mon Dec 02 14:02:05 2013 +0100
     4.3 @@ -0,0 +1,105 @@
     4.4 +#!/bin/env python
     4.5 +# -*- coding: utf-8 -*-
     4.6 +
     4.7 +# ------------------------------------------------------------
     4.8 +# cygwin command
     4.9 +# 
    4.10 +# executes a cygwin command inside the opensecurity project
    4.11 +#
    4.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    4.13 +#
    4.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
    4.15 +# AIT Austrian Institute of Technology GmbH
    4.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
    4.17 +# http://www.ait.ac.at
    4.18 +#
    4.19 +# This program is free software; you can redistribute it and/or
    4.20 +# modify it under the terms of the GNU General Public License
    4.21 +# as published by the Free Software Foundation version 2.
    4.22 +# 
    4.23 +# This program is distributed in the hope that it will be useful,
    4.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.26 +# GNU General Public License for more details.
    4.27 +# 
    4.28 +# You should have received a copy of the GNU General Public License
    4.29 +# along with this program; if not, write to the Free Software
    4.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    4.31 +# Boston, MA  02110-1301, USA.
    4.32 +# ------------------------------------------------------------
    4.33 +
    4.34 +
    4.35 +# ------------------------------------------------------------
    4.36 +# imports
    4.37 +
    4.38 +import os
    4.39 +import subprocess
    4.40 +import sys
    4.41 +
    4.42 +# local
    4.43 +from environment import Environment
    4.44 +
    4.45 +
    4.46 +# ------------------------------------------------------------
    4.47 +# code
    4.48 +
    4.49 +
    4.50 +class Cygwin(object):
    4.51 +
    4.52 +    """Some nifty methods working with Cygwin"""
    4.53 +    
    4.54 +    def __call__(self, command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE):
    4.55 +        """make an instance of this object act as a function"""
    4.56 +        return self.execute(command, stdin, stdout, stderr)
    4.57 +
    4.58 +        
    4.59 +    @staticmethod
    4.60 +    def root():
    4.61 +        """get the path to our local cygwin installment"""
    4.62 +        return os.path.join(Environment('OpenSecurity').prefix_path, '..', 'cygwin')
    4.63 +
    4.64 +
    4.65 +    def execute(self, command, stdin = subprocess.PIPE, stdout = subprocess.PIPE, stderr = subprocess.PIPE):
    4.66 +        """execute a cygwin shell command
    4.67 +        
    4.68 +        command is list of arguments like ['/bin/ls', '-al', '-h']
    4.69 +        
    4.70 +        a Popen object is returned"""
    4.71 +        command_path = Cygwin.root() + os.sep.join(command[0].split('/'))
    4.72 +        command = [command_path] + command[1:]
    4.73 +        
    4.74 +        return subprocess.Popen(command, shell = False, stdin = stdin, stdout = stdout, stderr = stderr)
    4.75 +        
    4.76 +        
    4.77 +    @staticmethod
    4.78 +    def is_X11_running():
    4.79 +        """check if we can connect to a X11 running instance"""
    4.80 +        p = Cygwin()(['/bin/bash', '-c', 'DISPLAY=:0 /usr/bin/xset -q'])
    4.81 +        stdout, stderr = p.communicate()
    4.82 +        return p.returncode == 0
    4.83 +        
    4.84 +        
    4.85 +    @staticmethod
    4.86 +    def start_X11():
    4.87 +        """start X11 in the background (if not already running) on DISPLAY=:0"""
    4.88 +        
    4.89 +        # do not start if already running
    4.90 +        if Cygwin.is_X11_running():
    4.91 +            return
    4.92 +            
    4.93 +        # launch X11 (forget output and return immediately)
    4.94 +        p = Cygwin()(['/bin/bash', '--login', '-i', '-c', ' X :0 -multiwindow'], stdin = None, stdout = None, stderr = None)
    4.95 +        
    4.96 +    
    4.97 +# start
    4.98 +if __name__ == "__main__":
    4.99 +
   4.100 +    # execute what is given on the command line
   4.101 +    c = Cygwin()
   4.102 +    p = c(sys.argv[1:])
   4.103 +    
   4.104 +    # wait until the process finished and grab the output
   4.105 +    stdout, stderr = p.communicate()
   4.106 +    print('=== call result on stdout: ===\n' + stdout)
   4.107 +    print('=== call result on stderr: ===\n' + stderr)
   4.108 +    
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/OpenSecurity/client/environment.py	Mon Dec 02 14:02:05 2013 +0100
     5.3 @@ -0,0 +1,103 @@
     5.4 +#!/bin/env python
     5.5 +# -*- coding: utf-8 -*-
     5.6 +
     5.7 +# ------------------------------------------------------------
     5.8 +# environment.py
     5.9 +# 
    5.10 +# pick some current environment infos
    5.11 +#
    5.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    5.13 +#
    5.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
    5.15 +# AIT Austrian Institute of Technology GmbH
    5.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
    5.17 +# http://www.ait.ac.at
    5.18 +#
    5.19 +# This program is free software; you can redistribute it and/or
    5.20 +# modify it under the terms of the GNU General Public License
    5.21 +# as published by the Free Software Foundation version 2.
    5.22 +# 
    5.23 +# This program is distributed in the hope that it will be useful,
    5.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    5.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    5.26 +# GNU General Public License for more details.
    5.27 +# 
    5.28 +# You should have received a copy of the GNU General Public License
    5.29 +# along with this program; if not, write to the Free Software
    5.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    5.31 +# Boston, MA  02110-1301, USA.
    5.32 +# ------------------------------------------------------------
    5.33 +
    5.34 +
    5.35 +# ------------------------------------------------------------
    5.36 +# imports
    5.37 +
    5.38 +import os
    5.39 +import os.path
    5.40 +import sys
    5.41 +
    5.42 +
    5.43 +# ------------------------------------------------------------
    5.44 +# code
    5.45 +
    5.46 +
    5.47 +class Environment(object):
    5.48 +    
    5.49 +    """Hold some nifty environment stuff in a dedicated class."""
    5.50 +    
    5.51 +    def __init__(self, application = None):
    5.52 +        
    5.53 +        # if we ain't got a path to start from, all is valid/lost
    5.54 +        if len(sys.path[0]) == 0:
    5.55 +            self._prefix_path = ''
    5.56 +            self._data_path = ''
    5.57 +            return
    5.58 +        
    5.59 +        # the prefix path
    5.60 +        #
    5.61 +        # - on Linux: this is ../../ to the current executable
    5.62 +        #   e.g. "/usr/bin/myprogram" --> "/usr"
    5.63 +        #
    5.64 +        # - on Windows (inkl. Cygwin): this is the installation folder
    5.65 +        #   e.g. "C:/Program Files/MyProgram/myprogam" --> "C:/Program Files/MyProgram"
    5.66 +        #
    5.67 +        if sys.platform == 'linux2':
    5.68 +            self._prefix_path = os.path.split(sys.path[0])[0]
    5.69 +        elif sys.platform == 'win32' or sys.platform == 'cygwin':
    5.70 +            self._prefix_path = sys.path[0]
    5.71 +            
    5.72 +        # the data path where all data files are stored
    5.73 +        if sys.platform == 'linux2':
    5.74 +            if not application is None:
    5.75 +                self._data_path = os.path.join(self._prefix_path, os.path.join('share', application))
    5.76 +            else:
    5.77 +                self._data_path = os.path.join(self._prefix_path, 'share')
    5.78 +        elif sys.platform == 'win32' or sys.platform == 'cygwin':
    5.79 +            self._data_path = self._prefix_path
    5.80 +
    5.81 +            
    5.82 +    def data_path_get(self):
    5.83 +        """dat_path get"""
    5.84 +        return self._data_path
    5.85 +        
    5.86 +    data_path = property(data_path_get)
    5.87 +            
    5.88 +            
    5.89 +    def prefix_path_get(self):
    5.90 +        """prefix_path get"""
    5.91 +        return self._prefix_path
    5.92 +        
    5.93 +    prefix_path = property(prefix_path_get)
    5.94 +            
    5.95 +# test method			
    5.96 +def test():
    5.97 +
    5.98 +	"""Test: class Environment"""
    5.99 +	e = Environment('My Application')
   5.100 +	print('prefix_path: "{0}"'.format(e.prefix_path))
   5.101 +	print('  data_path: "{0}"'.format(e.data_path))
   5.102 +			
   5.103 +			
   5.104 +# test the module			
   5.105 +if __name__ == '__main__':
   5.106 +	test()
     6.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     6.2 +++ b/OpenSecurity/client/launch.py	Mon Dec 02 14:02:05 2013 +0100
     6.3 @@ -0,0 +1,287 @@
     6.4 +#!/bin/env python
     6.5 +# -*- coding: utf-8 -*-
     6.6 +
     6.7 +# ------------------------------------------------------------
     6.8 +# opensecurity-launcher
     6.9 +# 
    6.10 +# launches an application inside a VM
    6.11 +#
    6.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    6.13 +#
    6.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
    6.15 +# AIT Austrian Institute of Technology GmbH
    6.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
    6.17 +# http://www.ait.ac.at
    6.18 +#
    6.19 +# This program is free software; you can redistribute it and/or
    6.20 +# modify it under the terms of the GNU General Public License
    6.21 +# as published by the Free Software Foundation version 2.
    6.22 +# 
    6.23 +# This program is distributed in the hope that it will be useful,
    6.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    6.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    6.26 +# GNU General Public License for more details.
    6.27 +# 
    6.28 +# You should have received a copy of the GNU General Public License
    6.29 +# along with this program; if not, write to the Free Software
    6.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    6.31 +# Boston, MA  02110-1301, USA.
    6.32 +# ------------------------------------------------------------
    6.33 +
    6.34 +
    6.35 +# ------------------------------------------------------------
    6.36 +# imports
    6.37 +
    6.38 +import argparse
    6.39 +import os
    6.40 +import subprocess
    6.41 +import sys
    6.42 +
    6.43 +from PyQt4 import QtCore
    6.44 +from PyQt4 import QtGui
    6.45 +
    6.46 +# local
    6.47 +from about import About
    6.48 +from cygwin import Cygwin
    6.49 +from environment import Environment
    6.50 +import opensecurity_server
    6.51 +
    6.52 +
    6.53 +# ------------------------------------------------------------
    6.54 +# code
    6.55 +
    6.56 +
    6.57 +class Chooser(QtGui.QDialog, object):
    6.58 +    
    6.59 +    """Ask the user what to launch."""
    6.60 +    
    6.61 +    def __init__(self, parent = None, flags = QtCore.Qt.WindowFlags(0)):
    6.62 +    
    6.63 +        super(Chooser, self).__init__(parent, flags)
    6.64 +        self.setWindowTitle('OpenSecuirty Launch Application')
    6.65 +        self.setup_ui()
    6.66 +        
    6.67 +        # known vms and applications
    6.68 +        self._apps, self_vms = [], []
    6.69 +        
    6.70 +        # positionate ourself central
    6.71 +        screen = QtGui.QDesktopWidget().screenGeometry()
    6.72 +        self.resize(self.geometry().width() * 1.25, self.geometry().height())
    6.73 +        size = self.geometry()
    6.74 +        self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2)
    6.75 +        
    6.76 +        # refresh vm and command input
    6.77 +        self.refresh()
    6.78 +        
    6.79 +        
    6.80 +    def app_get(self):
    6.81 +        """The application of the user."""
    6.82 +        a = str(self._cbApplication.currentText())
    6.83 +        for app in self._apps:
    6.84 +            if a == app['name']:
    6.85 +                return app['command']
    6.86 +        return a
    6.87 +        
    6.88 +    app = property(app_get)
    6.89 +        
    6.90 +
    6.91 +    def clicked_about(self):
    6.92 +        """clicked the about button"""
    6.93 +        dlgAbout = About()
    6.94 +        dlgAbout.exec_()
    6.95 +
    6.96 +
    6.97 +    def clicked_cancel(self):
    6.98 +        """clicked the cancel button"""
    6.99 +        self.reject()
   6.100 +    
   6.101 +
   6.102 +    def clicked_ok(self):
   6.103 +        """clicked the ok button"""
   6.104 +        self.accept()
   6.105 +    
   6.106 +    
   6.107 +    def refresh(self):
   6.108 +        """load the known vms and commands and adjust input fields"""
   6.109 +        
   6.110 +        self._apps = opensecurity_server.query_apps()
   6.111 +        self._vms = opensecurity_server.query_vms()
   6.112 +        
   6.113 +        # add the VMs we know
   6.114 +        self._cbApplication.clear()
   6.115 +        for app in self._apps:
   6.116 +            self._cbApplication.addItem(app['name'])
   6.117 +        
   6.118 +        # add the commands we know
   6.119 +        self._cbVM.clear()
   6.120 +        for vm in self._vms:
   6.121 +            self._cbVM.addItem(vm['name'])
   6.122 +        
   6.123 +        
   6.124 +    def setup_ui(self):
   6.125 +        """Create the widgets."""
   6.126 +        
   6.127 +        lyMain = QtGui.QVBoxLayout(self)
   6.128 +        lyMain.setContentsMargins(8, 8, 8, 8)
   6.129 +        
   6.130 +        # content area: left pixmap, right text
   6.131 +        lyContent = QtGui.QHBoxLayout()
   6.132 +        lyMain.addLayout(lyContent)
   6.133 +        
   6.134 +        # pixmap
   6.135 +        lbPix = QtGui.QLabel()
   6.136 +        lbPix.setPixmap(QtGui.QPixmapCache.find('opensecurity_icon_64'))
   6.137 +        lyContent.addWidget(lbPix, 0, QtCore.Qt.Alignment(QtCore.Qt.AlignTop + QtCore.Qt.AlignHCenter))
   6.138 +        lyContent.addSpacing(16)
   6.139 +        
   6.140 +        # launch ...
   6.141 +        lyLaunch = QtGui.QGridLayout()
   6.142 +        lyContent.addLayout(lyLaunch)
   6.143 +        lbTitle = QtGui.QLabel('Specify details for application to launch.')
   6.144 +        lyLaunch.addWidget(lbTitle, 0, 0, 1, 2)
   6.145 +        
   6.146 +        lbVM = QtGui.QLabel('&VM-ID:')
   6.147 +        lyLaunch.addWidget(lbVM, 1, 0)
   6.148 +        self._cbVM = QtGui.QComboBox()
   6.149 +        self._cbVM.setEditable(True)
   6.150 +        self._cbVM.setInsertPolicy(QtGui.QComboBox.InsertAlphabetically)
   6.151 +        lyLaunch.addWidget(self._cbVM, 1, 1)
   6.152 +        lbVM.setBuddy(self._cbVM)
   6.153 +        
   6.154 +        lbApplication = QtGui.QLabel('&Application:')
   6.155 +        lyLaunch.addWidget(lbApplication, 2, 0)
   6.156 +        self._cbApplication = QtGui.QComboBox()
   6.157 +        self._cbApplication.setEditable(True)
   6.158 +        self._cbApplication.setInsertPolicy(QtGui.QComboBox.InsertAlphabetically)
   6.159 +        lyLaunch.addWidget(self._cbApplication, 2, 1)
   6.160 +        lbApplication.setBuddy(self._cbApplication)
   6.161 +        
   6.162 +        lyLaunch.addWidget(QtGui.QWidget(), 3, 0, 1, 2)
   6.163 +        lyLaunch.setColumnStretch(1, 1)
   6.164 +        lyLaunch.setRowStretch(3, 1)
   6.165 +        
   6.166 +        lyMain.addStretch(1)
   6.167 +        
   6.168 +        # buttons
   6.169 +        lyButton = QtGui.QHBoxLayout()
   6.170 +        lyMain.addLayout(lyButton)
   6.171 +        
   6.172 +        lyButton.addStretch(1)
   6.173 +        btnOk = QtGui.QPushButton('&Ok', self)
   6.174 +        btnOk.setDefault(True)
   6.175 +        btnOk.setMinimumWidth(100)
   6.176 +        lyButton.addWidget(btnOk)
   6.177 +        btnCancel = QtGui.QPushButton('&Cancel', self)
   6.178 +        btnCancel.setMinimumWidth(100)
   6.179 +        lyButton.addWidget(btnCancel)
   6.180 +        btnAbout = QtGui.QPushButton('&About', self)
   6.181 +        btnAbout.setMinimumWidth(100)
   6.182 +        lyButton.addWidget(btnAbout)
   6.183 +        
   6.184 +        button_width = max(btnOk.width(), btnCancel.width(), btnAbout.width())
   6.185 +        btnOk.setMinimumWidth(button_width)
   6.186 +        btnCancel.setMinimumWidth(button_width)
   6.187 +        btnAbout.setMinimumWidth(button_width)
   6.188 +        
   6.189 +        # reduce to the max
   6.190 +        self.resize(lyMain.minimumSize())
   6.191 +        
   6.192 +        # connectors
   6.193 +        btnOk.clicked.connect(self.clicked_ok)
   6.194 +        btnCancel.clicked.connect(self.clicked_cancel)
   6.195 +        btnAbout.clicked.connect(self.clicked_about)
   6.196 +
   6.197 +        
   6.198 +    def user_get(self):
   6.199 +        """The user of the vm of choice."""
   6.200 +        v = str(self._cbVM.currentText())
   6.201 +        for vm in self._vms:
   6.202 +            if v == vm['name']:
   6.203 +                return vm['user']
   6.204 +        return v
   6.205 +        
   6.206 +    user = property(user_get)
   6.207 +    
   6.208 +    
   6.209 +    def vm_get(self):
   6.210 +        """The vm of choice."""
   6.211 +        v = str(self._cbVM.currentText())
   6.212 +        for vm in self._vms:
   6.213 +            if v == vm['name']:
   6.214 +                return vm['ip']
   6.215 +        return v
   6.216 +        
   6.217 +    vm = property(vm_get)
   6.218 +        
   6.219 +        
   6.220 +def ask_user():
   6.221 +    """ask the user for VM and app to start"""
   6.222 +    
   6.223 +    # launch Qt
   6.224 +    app = QtGui.QApplication(sys.argv)
   6.225 +    
   6.226 +    # prebuild the pixmap cache: fetch all jpg, png and jpeg images and load them
   6.227 +    image_path = os.path.join(Environment("OpenSecurity").data_path, '..', 'gfx')
   6.228 +    for file in os.listdir(image_path):
   6.229 +        if file.lower().rpartition('.')[2] in ('jpg', 'png', 'jpeg'):
   6.230 +            QtGui.QPixmapCache.insert(file.lower().rpartition('.')[0], QtGui.QPixmap(os.path.join(image_path, file)))
   6.231 +            
   6.232 +    # we should have now our application icon
   6.233 +    app.setWindowIcon(QtGui.QIcon(QtGui.QPixmapCache.find('opensecurity_icon_64')))
   6.234 +    
   6.235 +    # pop up the dialog
   6.236 +    dlg = Chooser()
   6.237 +    dlg.show()
   6.238 +    app.exec_()
   6.239 +    
   6.240 +    if dlg.result() == QtGui.QDialog.Accepted:
   6.241 +        return dlg.user, dlg.vm, dlg.app
   6.242 +
   6.243 +    return '', '', ''
   6.244 +    
   6.245 +
   6.246 +def main():
   6.247 +    """entry point"""
   6.248 +    
   6.249 +    # parse command line
   6.250 +    parser = argparse.ArgumentParser(description = 'OpenSecurity Launcher: run application in VM')
   6.251 +    parser.add_argument('user', metavar='USER', help='USER on Virtual Machine', nargs='?', type=str, default='')
   6.252 +    parser.add_argument('ip', metavar='IP', help='IP of Virtual Machine', nargs='?', type=str, default='')
   6.253 +    parser.add_argument('command', metavar='COMMAND', help='Full path of command and arguments to start inside VM', nargs='?', type=str, default='')
   6.254 +    args = parser.parse_args()
   6.255 +    
   6.256 +    # we must have at least all or none set
   6.257 +    set_user = args.user != ''
   6.258 +    set_ip = args.ip != ''
   6.259 +    set_command = args.command != ''
   6.260 +    set_ALL = set_user and set_ip and set_command
   6.261 +    set_NONE = (not set_user) and (not set_ip) and (not set_command)
   6.262 +    if (not set_ALL) and (not set_NONE):
   6.263 +        sys.stderr.write("Please specify user, ip and command or none.\n")
   6.264 +        sys.stderr.write("Type '--help' for help.\n")
   6.265 +        sys.exit(1)
   6.266 +        
   6.267 +    # check if we need to ask the user
   6.268 +    if set_NONE:
   6.269 +        args.user, args.ip, args.command = ask_user()
   6.270 +        
   6.271 +    # still no IP? --> no chance, over and out!
   6.272 +    if args.ip == '':
   6.273 +        sys.exit(0)
   6.274 +        
   6.275 +    # ensure we have our X11 running
   6.276 +    Cygwin.start_X11()
   6.277 +    
   6.278 +    # the SSH command
   6.279 +    user_at_guest = args.user + '@' + args.ip
   6.280 +    ssh = 'DISPLAY=:0 /usr/bin/ssh -Y ' + user_at_guest + ' ' + args.command
   6.281 +    print(ssh)
   6.282 +    
   6.283 +    # off we go!
   6.284 +    Cygwin()(['/bin/bash', '--login', '-i', '-c', ssh], None, None, None)
   6.285 +
   6.286 +    
   6.287 +# start
   6.288 +if __name__ == "__main__":
   6.289 +    main()
   6.290 +
     7.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     7.2 +++ b/OpenSecurity/client/opensecurity_client_restful_server.py	Mon Dec 02 14:02:05 2013 +0100
     7.3 @@ -0,0 +1,215 @@
     7.4 +#!/bin/env python
     7.5 +# -*- coding: utf-8 -*-
     7.6 +
     7.7 +# ------------------------------------------------------------
     7.8 +# opensecurity_client_restful_server
     7.9 +# 
    7.10 +# the OpenSecurity client RESTful server
    7.11 +#
    7.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    7.13 +#
    7.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
    7.15 +# AIT Austrian Institute of Technology GmbH
    7.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
    7.17 +# http://www.ait.ac.at
    7.18 +#
    7.19 +# This program is free software; you can redistribute it and/or
    7.20 +# modify it under the terms of the GNU General Public License
    7.21 +# as published by the Free Software Foundation version 2.
    7.22 +# 
    7.23 +# This program is distributed in the hope that it will be useful,
    7.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    7.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    7.26 +# GNU General Public License for more details.
    7.27 +# 
    7.28 +# You should have received a copy of the GNU General Public License
    7.29 +# along with this program; if not, write to the Free Software
    7.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    7.31 +# Boston, MA  02110-1301, USA.
    7.32 +# ------------------------------------------------------------
    7.33 +
    7.34 +
    7.35 +# ------------------------------------------------------------
    7.36 +# imports
    7.37 +
    7.38 +import os
    7.39 +import os.path
    7.40 +import subprocess
    7.41 +import sys
    7.42 +import web
    7.43 +
    7.44 +# local
    7.45 +from environment import Environment
    7.46 +import opensecurity_server
    7.47 +
    7.48 +
    7.49 +# ------------------------------------------------------------
    7.50 +# const
    7.51 +
    7.52 +
    7.53 +__version__ = "0.1"
    7.54 +
    7.55 +
    7.56 +"""All the URLs we know mapping to class handler"""
    7.57 +opensecurity_urls = (
    7.58 +    '/application',             'os_application',
    7.59 +    '/credentials',             'os_credentials',
    7.60 +    '/password',                'os_password',
    7.61 +    '/',                        'os_root'
    7.62 +)
    7.63 +
    7.64 +
    7.65 +# ------------------------------------------------------------
    7.66 +# code
    7.67 +
    7.68 +
    7.69 +class os_application:
    7.70 +    """OpenSecurity '/application' handler.
    7.71 +    
    7.72 +    This is called on GET /application?vm=VM-ID&app=APP-ID
    7.73 +    This tries to access the vm identified with the label VM-ID
    7.74 +    and launched the application identified APP-ID
    7.75 +    """
    7.76 +    
    7.77 +    def GET(self):
    7.78 +        
    7.79 +        # pick the arguments
    7.80 +        args = web.input()
    7.81 +        
    7.82 +        # we _need_ a vm
    7.83 +        if not "vm" in args:
    7.84 +            raise web.badrequest()
    7.85 +        
    7.86 +        # we _need_ a app
    7.87 +        if not "app" in args:
    7.88 +            raise web.badrequest()
    7.89 +        
    7.90 +        apps = opensecurity_server.query_apps()
    7.91 +        vms = opensecurity_server.query_vms()
    7.92 +        
    7.93 +        # check if we do have valid vm
    7.94 +        v = [v for v in vms if v['name'] == args.vm]
    7.95 +        if len(v) == 0:
    7.96 +            raise web.notfound('vm not found')
    7.97 +        v = v[0]
    7.98 +        
    7.99 +        # check if we do have a valid app
   7.100 +        a = [a for a in apps if a['name'] == args.app]
   7.101 +        if len(a) == 0:
   7.102 +            raise web.notfound('app not found')
   7.103 +        a = a[0]
   7.104 +        
   7.105 +        # invoke launch with 
   7.106 +        res = "starting: launch " + v['user'] + " " + v['ip'] + " " + a['command']
   7.107 +
   7.108 +        launch_image = os.path.join(sys.path[0], 'launch.py')
   7.109 +        process_command = [sys.executable, launch_image, v['user'], v['ip'], a['command']]
   7.110 +        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
   7.111 +        result = process.communicate()[0]
   7.112 +        if process.returncode != 0:
   7.113 +            return 'Launch of application aborted.'
   7.114 +        
   7.115 +        return result
   7.116 +        
   7.117 +
   7.118 +class os_credentials:
   7.119 +    """OpenSecurity '/credentials' handler.
   7.120 +    
   7.121 +    This is called on GET /credentials?text=TEXT.
   7.122 +    Ideally this should pop up a user dialog to insert his
   7.123 +    credentials based the given TEXT.
   7.124 +    """
   7.125 +    
   7.126 +    def GET(self):
   7.127 +        
   7.128 +        # pick the arguments
   7.129 +        args = web.input()
   7.130 +        
   7.131 +        # we _need_ a device id
   7.132 +        if not "text" in args:
   7.133 +            raise web.badrequest()
   7.134 +        
   7.135 +        # invoke the user dialog as a subprocess
   7.136 +        dlg_credentials_image = os.path.join(sys.path[0], 'opensecurity_dialog.py')
   7.137 +        process_command = [sys.executable, dlg_credentials_image, 'credentials', args.text]
   7.138 +        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
   7.139 +        result = process.communicate()[0]
   7.140 +        if process.returncode != 0:
   7.141 +            return 'Credentials request has been aborted.'
   7.142 +        
   7.143 +        return result
   7.144 +
   7.145 +
   7.146 +class os_password:
   7.147 +    """OpenSecurity '/password' handler.
   7.148 +    
   7.149 +    This is called on GET /password?text=TEXT.
   7.150 +    Ideally this should pop up a user dialog to insert his
   7.151 +    password based device name.
   7.152 +    """
   7.153 +    
   7.154 +    def GET(self):
   7.155 +        
   7.156 +        # pick the arguments
   7.157 +        args = web.input()
   7.158 +        
   7.159 +        # we _need_ a device id
   7.160 +        if not "text" in args:
   7.161 +            raise web.badrequest()
   7.162 +            
   7.163 +        # invoke the user dialog as a subprocess
   7.164 +        dlg_credentials_image = os.path.join(sys.path[0], 'opensecurity_dialog.py')
   7.165 +        process_command = [sys.executable, dlg_credentials_image, 'password', args.text]
   7.166 +        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
   7.167 +        result = process.communicate()[0]
   7.168 +        if process.returncode != 0:
   7.169 +            return 'password request has been aborted.'
   7.170 +        
   7.171 +        return result
   7.172 +
   7.173 +
   7.174 +class os_root:
   7.175 +    """OpenSecurity '/' handler"""
   7.176 +    
   7.177 +    def GET(self):
   7.178 +    
   7.179 +        res = "OpenSecurity-Client RESTFul Server { \"version\": \"%s\" }" % __version__
   7.180 +        
   7.181 +        # add some sample links
   7.182 +        res = res + """
   7.183 +        
   7.184 +USAGE EXAMPLES:
   7.185 +        
   7.186 +Request a password: 
   7.187 +    (copy paste this into your browser's address field after the host:port)
   7.188 +    
   7.189 +    /password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0)
   7.190 +    
   7.191 +    (eg.: http://127.0.0.1:8090/password?text=Give+me+a+password+for+device+%22My+USB+Drive%22+(ID%3A+32090-AAA-X0))
   7.192 +    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   7.193 +    
   7.194 +    
   7.195 +Request a combination of user and password:
   7.196 +    (copy paste this into your browser's address field after the host:port)
   7.197 +    
   7.198 +    /credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.
   7.199 +    
   7.200 +    (eg.: http://127.0.0.1:8090/credentials?text=Tell+the+NSA+which+credentials+to+use+in+order+to+avoid+hacking+noise+on+wire.)
   7.201 +    NOTE: check yout taskbar, the dialog window may not pop up in front of your browser window.
   7.202 +    
   7.203 +
   7.204 +Start a Browser:
   7.205 +    (copy paste this into your browser's address field after the host:port)
   7.206 +
   7.207 +    /application?vm=Debian+7&app=Browser
   7.208 +
   7.209 +    (e.g. http://127.0.0.1:8090/application?vm=Debian+7&app=Browser)
   7.210 +        """
   7.211 +    
   7.212 +        return res
   7.213 +
   7.214 +
   7.215 +# start
   7.216 +if __name__ == "__main__":
   7.217 +    server = web.application(opensecurity_urls, globals())
   7.218 +    server.run()
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/OpenSecurity/client/opensecurity_dialog.py	Mon Dec 02 14:02:05 2013 +0100
     8.3 @@ -0,0 +1,93 @@
     8.4 +#!/bin/env python
     8.5 +# -*- coding: utf-8 -*-
     8.6 +
     8.7 +# ------------------------------------------------------------
     8.8 +# opensecurity-dialog
     8.9 +# 
    8.10 +# an opensecurity dialog
    8.11 +#
    8.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    8.13 +#
    8.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
    8.15 +# AIT Austrian Institute of Technology GmbH
    8.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
    8.17 +# http://www.ait.ac.at
    8.18 +#
    8.19 +# This program is free software; you can redistribute it and/or
    8.20 +# modify it under the terms of the GNU General Public License
    8.21 +# as published by the Free Software Foundation version 2.
    8.22 +# 
    8.23 +# This program is distributed in the hope that it will be useful,
    8.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    8.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    8.26 +# GNU General Public License for more details.
    8.27 +# 
    8.28 +# You should have received a copy of the GNU General Public License
    8.29 +# along with this program; if not, write to the Free Software
    8.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    8.31 +# Boston, MA  02110-1301, USA.
    8.32 +# ------------------------------------------------------------
    8.33 +
    8.34 +
    8.35 +# ------------------------------------------------------------
    8.36 +# imports
    8.37 +
    8.38 +import argparse
    8.39 +import os
    8.40 +import sys
    8.41 +
    8.42 +from PyQt4 import QtCore
    8.43 +from PyQt4 import QtGui
    8.44 +
    8.45 +# local
    8.46 +from credentials import Credentials
    8.47 +from environment import Environment
    8.48 +from password import Password
    8.49 +
    8.50 +
    8.51 +# ------------------------------------------------------------
    8.52 +# code
    8.53 +
    8.54 +
    8.55 +def main():
    8.56 +    
    8.57 +    # parse command line
    8.58 +    parser = argparse.ArgumentParser(description = 'OpenSecurity Dialog.')
    8.59 +    parser.add_argument('mode', metavar='MODE', help='dialog mode: \'password\' or \'credentials\'')
    8.60 +    parser.add_argument('text', metavar='TEXT', help='text to show')
    8.61 +    args = parser.parse_args()
    8.62 +    
    8.63 +    app = QtGui.QApplication(sys.argv)
    8.64 +    
    8.65 +    # prebuild the pixmap cache: fetch all jpg, png and jpeg images and load them
    8.66 +    data_path = Environment("OpenSecurity").data_path
    8.67 +    image_path = os.path.join(data_path, '..', 'gfx')
    8.68 +    for file in os.listdir(image_path):
    8.69 +        if file.lower().rpartition('.')[2] in ('jpg', 'png', 'jpeg'):
    8.70 +            QtGui.QPixmapCache.insert(file.lower().rpartition('.')[0], QtGui.QPixmap(os.path.join(image_path, file)))
    8.71 +            
    8.72 +    # we should have now our application icon
    8.73 +    app.setWindowIcon(QtGui.QIcon(QtGui.QPixmapCache.find('opensecurity_icon_64')))
    8.74 +    
    8.75 +    if args.mode == 'password':
    8.76 +        dlg = Password(args.text)
    8.77 +    
    8.78 +    if args.mode == 'credentials':
    8.79 +        dlg = Credentials(args.text)
    8.80 +    
    8.81 +    # pop up the dialog
    8.82 +    dlg.show()
    8.83 +    app.exec_()
    8.84 +    
    8.85 +    # give proper result code
    8.86 +    if dlg.result() == QtGui.QDialog.Accepted:
    8.87 +        res = 0
    8.88 +    else:
    8.89 +        res = 1
    8.90 +    sys.exit(res)
    8.91 +    
    8.92 +
    8.93 +# start
    8.94 +if __name__ == "__main__":
    8.95 +    main()
    8.96 +
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/OpenSecurity/client/opensecurity_server.py	Mon Dec 02 14:02:05 2013 +0100
     9.3 @@ -0,0 +1,71 @@
     9.4 +#!/bin/env python
     9.5 +# -*- coding: utf-8 -*-
     9.6 +
     9.7 +# ------------------------------------------------------------
     9.8 +# opensecurity-server
     9.9 +# 
    9.10 +# talk to the opensecurity server
    9.11 +#
    9.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
    9.13 +#
    9.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
    9.15 +# AIT Austrian Institute of Technology GmbH
    9.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
    9.17 +# http://www.ait.ac.at
    9.18 +#
    9.19 +# This program is free software; you can redistribute it and/or
    9.20 +# modify it under the terms of the GNU General Public License
    9.21 +# as published by the Free Software Foundation version 2.
    9.22 +# 
    9.23 +# This program is distributed in the hope that it will be useful,
    9.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
    9.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    9.26 +# GNU General Public License for more details.
    9.27 +# 
    9.28 +# You should have received a copy of the GNU General Public License
    9.29 +# along with this program; if not, write to the Free Software
    9.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
    9.31 +# Boston, MA  02110-1301, USA.
    9.32 +# ------------------------------------------------------------
    9.33 +
    9.34 +# ------------------------------------------------------------
    9.35 +# import
    9.36 +
    9.37 +from pprint import PrettyPrinter
    9.38 +
    9.39 +
    9.40 +# ------------------------------------------------------------
    9.41 +# code
    9.42 +
    9.43 +def query_apps():
    9.44 +    """get the list of known apps"""
    9.45 +    
    9.46 +    # TODO: REPLACE THIS HARDCODED STUFF WITH REAL CODE TO THE OS SERVER
    9.47 +    apps = [ 
    9.48 +        { 'vm': 'Debian 7', 'name': 'Browser', 'command': '/usr/bin/iceweasel'}, 
    9.49 +        { 'vm': 'Debian 7', 'name': 'VLC', 'command': '/usr/bin/vlc'}
    9.50 +    ]
    9.51 +    
    9.52 +    return apps
    9.53 +    
    9.54 +
    9.55 +def query_vms():
    9.56 +    """get the list of registered vms, their ip and the prefered user"""
    9.57 +    
    9.58 +    # TODO: REPLACE THIS HARDCODED STUFF WITH REAL CODE TO THE OS SERVER
    9.59 +    vms = [ 
    9.60 +        { 'user': 'user', 'name': 'Debian 7', 'ip': '192.168.56.101'}, 
    9.61 +        { 'user': 'user', 'name': 'Anit-Virus VM', 'ip': '192.168.56.101'}
    9.62 +    ]
    9.63 +    
    9.64 +    return vms
    9.65 +
    9.66 +    
    9.67 +# start
    9.68 +if __name__ == "__main__":
    9.69 +    print("known apps: ")
    9.70 +    PrettyPrinter().pprint(query_apps())
    9.71 +    print("known vms: ")
    9.72 +    PrettyPrinter().pprint(query_vms())
    9.73 +    
    9.74 +
    10.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    10.2 +++ b/OpenSecurity/client/opensecurity_tray.py	Mon Dec 02 14:02:05 2013 +0100
    10.3 @@ -0,0 +1,131 @@
    10.4 +#!/bin/env python
    10.5 +# -*- coding: utf-8 -*-
    10.6 +
    10.7 +# ------------------------------------------------------------
    10.8 +# opensecurity-dialog
    10.9 +# 
   10.10 +# an opensecurity dialog
   10.11 +#
   10.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
   10.13 +#
   10.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
   10.15 +# AIT Austrian Institute of Technology GmbH
   10.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
   10.17 +# http://www.ait.ac.at
   10.18 +#
   10.19 +# This program is free software; you can redistribute it and/or
   10.20 +# modify it under the terms of the GNU General Public License
   10.21 +# as published by the Free Software Foundation version 2.
   10.22 +# 
   10.23 +# This program is distributed in the hope that it will be useful,
   10.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
   10.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   10.26 +# GNU General Public License for more details.
   10.27 +# 
   10.28 +# You should have received a copy of the GNU General Public License
   10.29 +# along with this program; if not, write to the Free Software
   10.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
   10.31 +# Boston, MA  02110-1301, USA.
   10.32 +# ------------------------------------------------------------
   10.33 +
   10.34 +
   10.35 +# ------------------------------------------------------------
   10.36 +# imports
   10.37 +
   10.38 +import argparse
   10.39 +import os
   10.40 +import subprocess
   10.41 +import sys
   10.42 +
   10.43 +from PyQt4 import QtCore
   10.44 +from PyQt4 import QtGui
   10.45 +
   10.46 +# local
   10.47 +from about import About
   10.48 +from environment import Environment
   10.49 +
   10.50 +
   10.51 +# ------------------------------------------------------------
   10.52 +# code
   10.53 +
   10.54 +
   10.55 +class OpenSecurityTrayIcon(QtGui.QSystemTrayIcon):
   10.56 +    
   10.57 +    """This is the OpenSecuirty Tray Icon"""
   10.58 +
   10.59 +    def __init__(self, icon, parent=None):
   10.60 +        
   10.61 +        super(OpenSecurityTrayIcon, self).__init__(icon, parent)
   10.62 +        self.setup_ui()
   10.63 +        
   10.64 +        
   10.65 +    def clicked_about(self):
   10.66 +        """clicked about"""
   10.67 +        dlgAbout = About()
   10.68 +        dlgAbout.exec_()
   10.69 +    
   10.70 +
   10.71 +    def clicked_exit(self):
   10.72 +        """clicked exit"""
   10.73 +        sys.exit(0)
   10.74 +    
   10.75 +
   10.76 +    def clicked_launch_application(self):
   10.77 +        """clicked the launch an application"""
   10.78 +        dlg_launch_image = os.path.join(sys.path[0], 'launch.pyw')
   10.79 +        process_command = [sys.executable, dlg_launch_image]
   10.80 +        print(process_command)
   10.81 +        process = subprocess.Popen(process_command, shell = False)
   10.82 +        process.communicate()
   10.83 +            
   10.84 +            
   10.85 +    def clicked_refresh(self):
   10.86 +        """clicked refresh"""
   10.87 +        self.setup_ui()
   10.88 +
   10.89 +    
   10.90 +    def setup_ui(self):
   10.91 +        """create the user interface
   10.92 +        As for the system tray this is 'just' the context menu.
   10.93 +        """
   10.94 +    
   10.95 +        # define the tray icon menu
   10.96 +        menu = QtGui.QMenu(self.parent())
   10.97 +        self.setContextMenu(menu)
   10.98 +        
   10.99 +        # add known apps
  10.100 +        
  10.101 +        # add standard menu items
  10.102 +        cAcLaunch = menu.addAction(QtGui.QIcon(QtGui.QPixmapCache.find('opensecurity_icon_64')), 'Lauch Application')
  10.103 +        menu.addSeparator()
  10.104 +        cAcRefresh = menu.addAction('Refresh')
  10.105 +        cAcAbout = menu.addAction("About")
  10.106 +        cAcExit = menu.addAction("Exit")
  10.107 +        
  10.108 +        cAcLaunch.triggered.connect(self.clicked_launch_application)
  10.109 +        cAcRefresh.triggered.connect(self.clicked_refresh)
  10.110 +        cAcAbout.triggered.connect(self.clicked_about)
  10.111 +        cAcExit.triggered.connect(self.clicked_exit)
  10.112 +        
  10.113 +        
  10.114 +def main():
  10.115 +    
  10.116 +    app = QtGui.QApplication(sys.argv)
  10.117 +
  10.118 +    # prebuild the pixmap cache: fetch all jpg, png and jpeg images and load them
  10.119 +    image_path = os.path.join(Environment("OpenSecurity").data_path, '..', 'gfx')
  10.120 +    for file in os.listdir(image_path):
  10.121 +        if file.lower().rpartition('.')[2] in ('jpg', 'png', 'jpeg'):
  10.122 +            QtGui.QPixmapCache.insert(file.lower().rpartition('.')[0], QtGui.QPixmap(os.path.join(image_path, file)))
  10.123 +
  10.124 +    w = QtGui.QWidget()
  10.125 +    trayIcon = OpenSecurityTrayIcon(QtGui.QIcon(QtGui.QPixmapCache.find('opensecurity_icon_64')), w)
  10.126 +
  10.127 +    trayIcon.show()
  10.128 +    sys.exit(app.exec_())
  10.129 +   
  10.130 +
  10.131 +# start
  10.132 +if __name__ == "__main__":
  10.133 +    main()
  10.134 +
    11.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    11.2 +++ b/OpenSecurity/client/password.py	Mon Dec 02 14:02:05 2013 +0100
    11.3 @@ -0,0 +1,150 @@
    11.4 +#!/bin/env python
    11.5 +# -*- coding: utf-8 -*-
    11.6 +
    11.7 +# ------------------------------------------------------------
    11.8 +# password-dialog
    11.9 +# 
   11.10 +# ask the user a password
   11.11 +#
   11.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
   11.13 +#
   11.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
   11.15 +# AIT Austrian Institute of Technology GmbH
   11.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
   11.17 +# http://www.ait.ac.at
   11.18 +#
   11.19 +# This program is free software; you can redistribute it and/or
   11.20 +# modify it under the terms of the GNU General Public License
   11.21 +# as published by the Free Software Foundation version 2.
   11.22 +# 
   11.23 +# This program is distributed in the hope that it will be useful,
   11.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
   11.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   11.26 +# GNU General Public License for more details.
   11.27 +# 
   11.28 +# You should have received a copy of the GNU General Public License
   11.29 +# along with this program; if not, write to the Free Software
   11.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
   11.31 +# Boston, MA  02110-1301, USA.
   11.32 +# ------------------------------------------------------------
   11.33 +
   11.34 +
   11.35 +# ------------------------------------------------------------
   11.36 +# imports
   11.37 +
   11.38 +import sys
   11.39 +
   11.40 +from PyQt4 import QtCore
   11.41 +from PyQt4 import QtGui
   11.42 +
   11.43 +# local
   11.44 +from about import About
   11.45 +
   11.46 +# ------------------------------------------------------------
   11.47 +# code
   11.48 +
   11.49 +
   11.50 +class Password(QtGui.QDialog):
   11.51 +    
   11.52 +    """Ask the user for a password."""
   11.53 +    
   11.54 +    def __init__(self, text, parent = None, flags = QtCore.Qt.WindowFlags(0)):
   11.55 +        
   11.56 +        # super call and widget init
   11.57 +        super(Password, self).__init__(parent, flags)
   11.58 +        self.setWindowTitle('OpenSecuirty Password Request')
   11.59 +        self.setup_ui()
   11.60 +        
   11.61 +        # positionate ourself central
   11.62 +        screen = QtGui.QDesktopWidget().screenGeometry()
   11.63 +        self.resize(self.geometry().width() * 1.25, self.geometry().height())
   11.64 +        size = self.geometry()
   11.65 +        self.move((screen.width() - size.width()) / 2, (screen.height() - size.height()) / 2)
   11.66 +        
   11.67 +        # fix up text
   11.68 +        self.lbText.setText(text)
   11.69 +        
   11.70 +
   11.71 +    def clicked_about(self):
   11.72 +        """clicked the about button"""
   11.73 +        dlgAbout = About()
   11.74 +        dlgAbout.exec_()
   11.75 +    
   11.76 +
   11.77 +    def clicked_cancel(self):
   11.78 +        """clicked the cancel button"""
   11.79 +        self.reject()
   11.80 +    
   11.81 +
   11.82 +    def clicked_ok(self):
   11.83 +        """clicked the ok button"""
   11.84 +        sys.stdout.write('{ ')
   11.85 +        sys.stdout.write('\'password\': \'')
   11.86 +        sys.stdout.write(self.edPassword.text())
   11.87 +        sys.stdout.write('\' ')
   11.88 +        sys.stdout.write('}\n')
   11.89 +        self.accept()
   11.90 +    
   11.91 +
   11.92 +    def setup_ui(self):
   11.93 +        
   11.94 +        """Create the widgets."""
   11.95 +        
   11.96 +        lyMain = QtGui.QVBoxLayout(self)
   11.97 +        lyMain.setContentsMargins(8, 8, 8, 8)
   11.98 +        
   11.99 +        # content area: left pixmap, right text
  11.100 +        lyContent = QtGui.QHBoxLayout()
  11.101 +        lyMain.addLayout(lyContent)
  11.102 +        
  11.103 +        # pixmap
  11.104 +        lbPix = QtGui.QLabel()
  11.105 +        lbPix.setPixmap(QtGui.QPixmapCache.find('opensecurity_icon_64'))
  11.106 +        lyContent.addWidget(lbPix, 0, QtCore.Qt.Alignment(QtCore.Qt.AlignTop + QtCore.Qt.AlignHCenter))
  11.107 +        lyContent.addSpacing(16)
  11.108 +        
  11.109 +        # text ...
  11.110 +        lyText = QtGui.QVBoxLayout()
  11.111 +        lyContent.addLayout(lyText)
  11.112 +        self.lbText = QtGui.QLabel()
  11.113 +        lyText.addWidget(self.lbText)
  11.114 +        lyPassword = QtGui.QHBoxLayout()
  11.115 +        lyText.addLayout(lyPassword)
  11.116 +        lbPassword = QtGui.QLabel('&Password:')
  11.117 +        lyPassword.addWidget(lbPassword)
  11.118 +        self.edPassword = QtGui.QLineEdit()
  11.119 +        self.edPassword.setEchoMode(QtGui.QLineEdit.Password)
  11.120 +        lyPassword.addWidget(self.edPassword)
  11.121 +        lbPassword.setBuddy(self.edPassword)
  11.122 +        lyText.addStretch(1)
  11.123 +        
  11.124 +        lyMain.addStretch(1)
  11.125 +        
  11.126 +        # buttons
  11.127 +        lyButton = QtGui.QHBoxLayout()
  11.128 +        lyMain.addLayout(lyButton)
  11.129 +        
  11.130 +        lyButton.addStretch(1)
  11.131 +        btnOk = QtGui.QPushButton('&Ok', self)
  11.132 +        btnOk.setDefault(True)
  11.133 +        btnOk.setMinimumWidth(100)
  11.134 +        lyButton.addWidget(btnOk)
  11.135 +        btnCancel = QtGui.QPushButton('&Cancel', self)
  11.136 +        btnCancel.setMinimumWidth(100)
  11.137 +        lyButton.addWidget(btnCancel)
  11.138 +        btnAbout = QtGui.QPushButton('&About', self)
  11.139 +        btnAbout.setMinimumWidth(100)
  11.140 +        lyButton.addWidget(btnAbout)
  11.141 +        
  11.142 +        button_width = max(btnOk.width(), btnCancel.width(), btnAbout.width())
  11.143 +        btnOk.setMinimumWidth(button_width)
  11.144 +        btnCancel.setMinimumWidth(button_width)
  11.145 +        btnAbout.setMinimumWidth(button_width)
  11.146 +        
  11.147 +        # reduce to the max
  11.148 +        self.resize(lyMain.minimumSize())
  11.149 +        
  11.150 +        # connectors
  11.151 +        btnOk.clicked.connect(self.clicked_ok)
  11.152 +        btnCancel.clicked.connect(self.clicked_cancel)
  11.153 +        btnAbout.clicked.connect(self.clicked_about)
    12.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    12.2 +++ b/OpenSecurity/cygwin/Cygwin.vbs	Mon Dec 02 14:02:05 2013 +0100
    12.3 @@ -0,0 +1,50 @@
    12.4 +Option Explicit
    12.5 +
    12.6 +' ------------------------------------------------------------
    12.7 +' start a cygwin shell
    12.8 +'
    12.9 +' Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
   12.10 +'
   12.11 +' Copyright (C) 2013 AIT Austrian Institute of Technology
   12.12 +' AIT Austrian Institute of Technology GmbH
   12.13 +' Donau-City-Strasse 1 | 1220 Vienna | Austria
   12.14 +' http://www.ait.ac.at
   12.15 +' ------------------------------------------------------------
   12.16 +
   12.17 +' ------------------------------------------------------------
   12.18 +' This starts a cygwin shell within a relocatable cygwin
   12.19 +' folder but without a DOS shell window prior. The idea is
   12.20 +' to do it like in this BAT snippet below:
   12.21 +'
   12.22 +'		@echo off
   12.23 +' 		SET INSTALL_DRIVE=%~d0
   12.24 +' 		SET INSTALL_FOLDER=%~p0
   12.25 +' 		SET CYGWIN_BIN="%INSTALL_DRIVE%%INSTALL_FOLDER%bin"
   12.26 +' 		SET CYGWIN_BASH="%INSTALL_DRIVE%%INSTALL_FOLDER%bin\bash"
   12.27 +' 		%INSTALL_DRIVE%
   12.28 +' 		chdir "%CYGWIN_BIN%"
   12.29 +' 		start mintty.exe %CYGWIN_BASH% --login -i
   12.30 +'
   12.31 +' ------------------------------------------------------------
   12.32 +
   12.33 +' setup the basic objects
   12.34 +Dim cFSO
   12.35 +Dim cShell
   12.36 +Set cShell = CreateObject("WScript.Shell")
   12.37 +Set cFSO = CreateObject("Scripting.FileSystemObject")
   12.38 +
   12.39 +' parse script location and cd into folder
   12.40 +Dim sPath
   12.41 +sPath = Wscript.ScriptFullName
   12.42 +sPath = cFSO.GetAbsolutePathName(sPath)
   12.43 +sPath = cFSO.GetParentFolderName(sPath)
   12.44 +cShell.CurrentDirectory = sPath
   12.45 +
   12.46 +' locations of mintty and bash
   12.47 +Dim sMinTTYPath
   12.48 +Dim sBashPath
   12.49 +sMinTTYPath = """" & sPath & "\bin\mintty" & """"
   12.50 +sBashPath = """" & sPath & "\bin\bash" & """"
   12.51 +
   12.52 +' start a cygwin shell
   12.53 +cShell.Run sMinTTYPath & " " & sBashPath & " --login -i", 1, false
    13.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    13.2 +++ b/OpenSecurity/cygwin/content.txt	Mon Dec 02 14:02:05 2013 +0100
    13.3 @@ -0,0 +1,10 @@
    13.4 +Content of Opensecurity/cygwin
    13.5 +==============================
    13.6 +
    13.7 +This folder contains a complete Cygwin - 32 Bit with at least these packages installed:
    13.8 +
    13.9 +    - bash
   13.10 +    - coreutils
   13.11 +    - openssh
   13.12 +    - xinit
   13.13 +    - genisofs
    14.1 Binary file OpenSecurity/cygwin/setup-x86.exe has changed
    15.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    15.2 +++ b/OpenSecurity/cygwin64/Cygwin.vbs	Mon Dec 02 14:02:05 2013 +0100
    15.3 @@ -0,0 +1,50 @@
    15.4 +Option Explicit
    15.5 +
    15.6 +' ------------------------------------------------------------
    15.7 +' start a cygwin shell
    15.8 +'
    15.9 +' Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
   15.10 +'
   15.11 +' Copyright (C) 2013 AIT Austrian Institute of Technology
   15.12 +' AIT Austrian Institute of Technology GmbH
   15.13 +' Donau-City-Strasse 1 | 1220 Vienna | Austria
   15.14 +' http://www.ait.ac.at
   15.15 +' ------------------------------------------------------------
   15.16 +
   15.17 +' ------------------------------------------------------------
   15.18 +' This starts a cygwin shell within a relocatable cygwin
   15.19 +' folder but without a DOS shell window prior. The idea is
   15.20 +' to do it like in this BAT snippet below:
   15.21 +'
   15.22 +'		@echo off
   15.23 +' 		SET INSTALL_DRIVE=%~d0
   15.24 +' 		SET INSTALL_FOLDER=%~p0
   15.25 +' 		SET CYGWIN_BIN="%INSTALL_DRIVE%%INSTALL_FOLDER%bin"
   15.26 +' 		SET CYGWIN_BASH="%INSTALL_DRIVE%%INSTALL_FOLDER%bin\bash"
   15.27 +' 		%INSTALL_DRIVE%
   15.28 +' 		chdir "%CYGWIN_BIN%"
   15.29 +' 		start mintty.exe %CYGWIN_BASH% --login -i
   15.30 +'
   15.31 +' ------------------------------------------------------------
   15.32 +
   15.33 +' setup the basic objects
   15.34 +Dim cFSO
   15.35 +Dim cShell
   15.36 +Set cShell = CreateObject("WScript.Shell")
   15.37 +Set cFSO = CreateObject("Scripting.FileSystemObject")
   15.38 +
   15.39 +' parse script location and cd into folder
   15.40 +Dim sPath
   15.41 +sPath = Wscript.ScriptFullName
   15.42 +sPath = cFSO.GetAbsolutePathName(sPath)
   15.43 +sPath = cFSO.GetParentFolderName(sPath)
   15.44 +cShell.CurrentDirectory = sPath
   15.45 +
   15.46 +' locations of mintty and bash
   15.47 +Dim sMinTTYPath
   15.48 +Dim sBashPath
   15.49 +sMinTTYPath = """" & sPath & "\bin\mintty" & """"
   15.50 +sBashPath = """" & sPath & "\bin\bash" & """"
   15.51 +
   15.52 +' start a cygwin shell
   15.53 +cShell.Run sMinTTYPath & " " & sBashPath & " --login -i", 1, false
    16.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    16.2 +++ b/OpenSecurity/cygwin64/content.txt	Mon Dec 02 14:02:05 2013 +0100
    16.3 @@ -0,0 +1,10 @@
    16.4 +Content of Opensecurity/cygwin
    16.5 +==============================
    16.6 +
    16.7 +This folder contains a complete Cygwin - 64 Bit with at least these packages installed:
    16.8 +
    16.9 +    - bash
   16.10 +    - coreutils
   16.11 +    - openssh
   16.12 +    - xinit
   16.13 +    - genisofs
    17.1 Binary file OpenSecurity/cygwin64/setup-x86_64.exe has changed
    18.1 Binary file OpenSecurity/gfx/ait_logo.jpg has changed
    19.1 Binary file OpenSecurity/gfx/ait_logo_no_claim.png has changed
    20.1 Binary file OpenSecurity/gfx/bmvit_logo.jpg has changed
    21.1 Binary file OpenSecurity/gfx/ffg_logo.jpg has changed
    22.1 Binary file OpenSecurity/gfx/ikarus_logo.jpg has changed
    23.1 Binary file OpenSecurity/gfx/kiras_logo.jpg has changed
    24.1 Binary file OpenSecurity/gfx/linz_logo.jpg has changed
    25.1 Binary file OpenSecurity/gfx/liqua_logo.jpg has changed
    26.1 Binary file OpenSecurity/gfx/opensecurity.ico has changed
    27.1 Binary file OpenSecurity/gfx/opensecurity_icon_64.png has changed
    28.1 Binary file OpenSecurity/gfx/opensecurity_logo.jpg has changed
    29.1 Binary file OpenSecurity/gfx/x-net_logo.jpg has changed
    30.1 Binary file OpenSecurity/install/OpenSecurity.reg has changed
    31.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    31.2 +++ b/OpenSecurity/install/content.txt	Mon Dec 02 14:02:05 2013 +0100
    31.3 @@ -0,0 +1,15 @@
    31.4 +Content of Opensecurity/install
    31.5 +==============================
    31.6 +
    31.7 +Additonally to the files already present in this folder, we need:
    31.8 +
    31.9 +- PyQt4-4.10.3-gpl-Py2.7-Qt4.8.5-x32.exe
   31.10 +- PyQt4-4.10.3-gpl-Py2.7-Qt4.8.5-x64.exe
   31.11 +    (--> http://www.riverbankcomputing.com/software/pyqt/download)
   31.12 +
   31.13 +- python-2.7.6.amd64.msi
   31.14 +- python-2.7.6.msi
   31.15 +    (--> http://www.python.org/download)
   31.16 +
   31.17 +- VirtualBox-4.3.4-91027-Win.exe
   31.18 +    (--> https://virtualbox.org/wiki/Downloads)
    32.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    32.2 +++ b/OpenSecurity/install/web.py-0.37/PKG-INFO	Mon Dec 02 14:02:05 2013 +0100
    32.3 @@ -0,0 +1,10 @@
    32.4 +Metadata-Version: 1.0
    32.5 +Name: web.py
    32.6 +Version: 0.37
    32.7 +Summary: web.py: makes web apps
    32.8 +Home-page:  http://webpy.org/
    32.9 +Author: Anand Chitipothu
   32.10 +Author-email: anandology@gmail.com
   32.11 +License: Public domain
   32.12 +Description: Think about the ideal way to write a web app. Write the code to make it happen.
   32.13 +Platform: any
    33.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    33.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/__init__.py	Mon Dec 02 14:02:05 2013 +0100
    33.3 @@ -0,0 +1,33 @@
    33.4 +#!/usr/bin/env python
    33.5 +"""web.py: makes web apps (http://webpy.org)"""
    33.6 +
    33.7 +from __future__ import generators
    33.8 +
    33.9 +__version__ = "0.37"
   33.10 +__author__ = [
   33.11 +    "Aaron Swartz <me@aaronsw.com>",
   33.12 +    "Anand Chitipothu <anandology@gmail.com>"
   33.13 +]
   33.14 +__license__ = "public domain"
   33.15 +__contributors__ = "see http://webpy.org/changes"
   33.16 +
   33.17 +import utils, db, net, wsgi, http, webapi, httpserver, debugerror
   33.18 +import template, form
   33.19 +
   33.20 +import session
   33.21 +
   33.22 +from utils import *
   33.23 +from db import *
   33.24 +from net import *
   33.25 +from wsgi import *
   33.26 +from http import *
   33.27 +from webapi import *
   33.28 +from httpserver import *
   33.29 +from debugerror import *
   33.30 +from application import *
   33.31 +from browser import *
   33.32 +try:
   33.33 +    import webopenid as openid
   33.34 +except ImportError:
   33.35 +    pass # requires openid module
   33.36 +
    34.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    34.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/application.py	Mon Dec 02 14:02:05 2013 +0100
    34.3 @@ -0,0 +1,687 @@
    34.4 +"""
    34.5 +Web application
    34.6 +(from web.py)
    34.7 +"""
    34.8 +import webapi as web
    34.9 +import webapi, wsgi, utils
   34.10 +import debugerror
   34.11 +import httpserver
   34.12 +
   34.13 +from utils import lstrips, safeunicode
   34.14 +import sys
   34.15 +
   34.16 +import urllib
   34.17 +import traceback
   34.18 +import itertools
   34.19 +import os
   34.20 +import types
   34.21 +from exceptions import SystemExit
   34.22 +
   34.23 +try:
   34.24 +    import wsgiref.handlers
   34.25 +except ImportError:
   34.26 +    pass # don't break people with old Pythons
   34.27 +
   34.28 +__all__ = [
   34.29 +    "application", "auto_application",
   34.30 +    "subdir_application", "subdomain_application", 
   34.31 +    "loadhook", "unloadhook",
   34.32 +    "autodelegate"
   34.33 +]
   34.34 +
   34.35 +class application:
   34.36 +    """
   34.37 +    Application to delegate requests based on path.
   34.38 +    
   34.39 +        >>> urls = ("/hello", "hello")
   34.40 +        >>> app = application(urls, globals())
   34.41 +        >>> class hello:
   34.42 +        ...     def GET(self): return "hello"
   34.43 +        >>>
   34.44 +        >>> app.request("/hello").data
   34.45 +        'hello'
   34.46 +    """
   34.47 +    def __init__(self, mapping=(), fvars={}, autoreload=None):
   34.48 +        if autoreload is None:
   34.49 +            autoreload = web.config.get('debug', False)
   34.50 +        self.init_mapping(mapping)
   34.51 +        self.fvars = fvars
   34.52 +        self.processors = []
   34.53 +        
   34.54 +        self.add_processor(loadhook(self._load))
   34.55 +        self.add_processor(unloadhook(self._unload))
   34.56 +        
   34.57 +        if autoreload:
   34.58 +            def main_module_name():
   34.59 +                mod = sys.modules['__main__']
   34.60 +                file = getattr(mod, '__file__', None) # make sure this works even from python interpreter
   34.61 +                return file and os.path.splitext(os.path.basename(file))[0]
   34.62 +
   34.63 +            def modname(fvars):
   34.64 +                """find name of the module name from fvars."""
   34.65 +                file, name = fvars.get('__file__'), fvars.get('__name__')
   34.66 +                if file is None or name is None:
   34.67 +                    return None
   34.68 +
   34.69 +                if name == '__main__':
   34.70 +                    # Since the __main__ module can't be reloaded, the module has 
   34.71 +                    # to be imported using its file name.                    
   34.72 +                    name = main_module_name()
   34.73 +                return name
   34.74 +                
   34.75 +            mapping_name = utils.dictfind(fvars, mapping)
   34.76 +            module_name = modname(fvars)
   34.77 +            
   34.78 +            def reload_mapping():
   34.79 +                """loadhook to reload mapping and fvars."""
   34.80 +                mod = __import__(module_name, None, None, [''])
   34.81 +                mapping = getattr(mod, mapping_name, None)
   34.82 +                if mapping:
   34.83 +                    self.fvars = mod.__dict__
   34.84 +                    self.init_mapping(mapping)
   34.85 +
   34.86 +            self.add_processor(loadhook(Reloader()))
   34.87 +            if mapping_name and module_name:
   34.88 +                self.add_processor(loadhook(reload_mapping))
   34.89 +
   34.90 +            # load __main__ module usings its filename, so that it can be reloaded.
   34.91 +            if main_module_name() and '__main__' in sys.argv:
   34.92 +                try:
   34.93 +                    __import__(main_module_name())
   34.94 +                except ImportError:
   34.95 +                    pass
   34.96 +                    
   34.97 +    def _load(self):
   34.98 +        web.ctx.app_stack.append(self)
   34.99 +        
  34.100 +    def _unload(self):
  34.101 +        web.ctx.app_stack = web.ctx.app_stack[:-1]
  34.102 +        
  34.103 +        if web.ctx.app_stack:
  34.104 +            # this is a sub-application, revert ctx to earlier state.
  34.105 +            oldctx = web.ctx.get('_oldctx')
  34.106 +            if oldctx:
  34.107 +                web.ctx.home = oldctx.home
  34.108 +                web.ctx.homepath = oldctx.homepath
  34.109 +                web.ctx.path = oldctx.path
  34.110 +                web.ctx.fullpath = oldctx.fullpath
  34.111 +                
  34.112 +    def _cleanup(self):
  34.113 +        # Threads can be recycled by WSGI servers.
  34.114 +        # Clearing up all thread-local state to avoid interefereing with subsequent requests.
  34.115 +        utils.ThreadedDict.clear_all()
  34.116 +
  34.117 +    def init_mapping(self, mapping):
  34.118 +        self.mapping = list(utils.group(mapping, 2))
  34.119 +
  34.120 +    def add_mapping(self, pattern, classname):
  34.121 +        self.mapping.append((pattern, classname))
  34.122 +
  34.123 +    def add_processor(self, processor):
  34.124 +        """
  34.125 +        Adds a processor to the application. 
  34.126 +        
  34.127 +            >>> urls = ("/(.*)", "echo")
  34.128 +            >>> app = application(urls, globals())
  34.129 +            >>> class echo:
  34.130 +            ...     def GET(self, name): return name
  34.131 +            ...
  34.132 +            >>>
  34.133 +            >>> def hello(handler): return "hello, " +  handler()
  34.134 +            ...
  34.135 +            >>> app.add_processor(hello)
  34.136 +            >>> app.request("/web.py").data
  34.137 +            'hello, web.py'
  34.138 +        """
  34.139 +        self.processors.append(processor)
  34.140 +
  34.141 +    def request(self, localpart='/', method='GET', data=None,
  34.142 +                host="0.0.0.0:8080", headers=None, https=False, **kw):
  34.143 +        """Makes request to this application for the specified path and method.
  34.144 +        Response will be a storage object with data, status and headers.
  34.145 +
  34.146 +            >>> urls = ("/hello", "hello")
  34.147 +            >>> app = application(urls, globals())
  34.148 +            >>> class hello:
  34.149 +            ...     def GET(self): 
  34.150 +            ...         web.header('Content-Type', 'text/plain')
  34.151 +            ...         return "hello"
  34.152 +            ...
  34.153 +            >>> response = app.request("/hello")
  34.154 +            >>> response.data
  34.155 +            'hello'
  34.156 +            >>> response.status
  34.157 +            '200 OK'
  34.158 +            >>> response.headers['Content-Type']
  34.159 +            'text/plain'
  34.160 +
  34.161 +        To use https, use https=True.
  34.162 +
  34.163 +            >>> urls = ("/redirect", "redirect")
  34.164 +            >>> app = application(urls, globals())
  34.165 +            >>> class redirect:
  34.166 +            ...     def GET(self): raise web.seeother("/foo")
  34.167 +            ...
  34.168 +            >>> response = app.request("/redirect")
  34.169 +            >>> response.headers['Location']
  34.170 +            'http://0.0.0.0:8080/foo'
  34.171 +            >>> response = app.request("/redirect", https=True)
  34.172 +            >>> response.headers['Location']
  34.173 +            'https://0.0.0.0:8080/foo'
  34.174 +
  34.175 +        The headers argument specifies HTTP headers as a mapping object
  34.176 +        such as a dict.
  34.177 +
  34.178 +            >>> urls = ('/ua', 'uaprinter')
  34.179 +            >>> class uaprinter:
  34.180 +            ...     def GET(self):
  34.181 +            ...         return 'your user-agent is ' + web.ctx.env['HTTP_USER_AGENT']
  34.182 +            ... 
  34.183 +            >>> app = application(urls, globals())
  34.184 +            >>> app.request('/ua', headers = {
  34.185 +            ...      'User-Agent': 'a small jumping bean/1.0 (compatible)'
  34.186 +            ... }).data
  34.187 +            'your user-agent is a small jumping bean/1.0 (compatible)'
  34.188 +
  34.189 +        """
  34.190 +        path, maybe_query = urllib.splitquery(localpart)
  34.191 +        query = maybe_query or ""
  34.192 +        
  34.193 +        if 'env' in kw:
  34.194 +            env = kw['env']
  34.195 +        else:
  34.196 +            env = {}
  34.197 +        env = dict(env, HTTP_HOST=host, REQUEST_METHOD=method, PATH_INFO=path, QUERY_STRING=query, HTTPS=str(https))
  34.198 +        headers = headers or {}
  34.199 +
  34.200 +        for k, v in headers.items():
  34.201 +            env['HTTP_' + k.upper().replace('-', '_')] = v
  34.202 +
  34.203 +        if 'HTTP_CONTENT_LENGTH' in env:
  34.204 +            env['CONTENT_LENGTH'] = env.pop('HTTP_CONTENT_LENGTH')
  34.205 +
  34.206 +        if 'HTTP_CONTENT_TYPE' in env:
  34.207 +            env['CONTENT_TYPE'] = env.pop('HTTP_CONTENT_TYPE')
  34.208 +
  34.209 +        if method not in ["HEAD", "GET"]:
  34.210 +            data = data or ''
  34.211 +            import StringIO
  34.212 +            if isinstance(data, dict):
  34.213 +                q = urllib.urlencode(data)
  34.214 +            else:
  34.215 +                q = data
  34.216 +            env['wsgi.input'] = StringIO.StringIO(q)
  34.217 +            if not env.get('CONTENT_TYPE', '').lower().startswith('multipart/') and 'CONTENT_LENGTH' not in env:
  34.218 +                env['CONTENT_LENGTH'] = len(q)
  34.219 +        response = web.storage()
  34.220 +        def start_response(status, headers):
  34.221 +            response.status = status
  34.222 +            response.headers = dict(headers)
  34.223 +            response.header_items = headers
  34.224 +        response.data = "".join(self.wsgifunc()(env, start_response))
  34.225 +        return response
  34.226 +
  34.227 +    def browser(self):
  34.228 +        import browser
  34.229 +        return browser.AppBrowser(self)
  34.230 +
  34.231 +    def handle(self):
  34.232 +        fn, args = self._match(self.mapping, web.ctx.path)
  34.233 +        return self._delegate(fn, self.fvars, args)
  34.234 +        
  34.235 +    def handle_with_processors(self):
  34.236 +        def process(processors):
  34.237 +            try:
  34.238 +                if processors:
  34.239 +                    p, processors = processors[0], processors[1:]
  34.240 +                    return p(lambda: process(processors))
  34.241 +                else:
  34.242 +                    return self.handle()
  34.243 +            except web.HTTPError:
  34.244 +                raise
  34.245 +            except (KeyboardInterrupt, SystemExit):
  34.246 +                raise
  34.247 +            except:
  34.248 +                print >> web.debug, traceback.format_exc()
  34.249 +                raise self.internalerror()
  34.250 +        
  34.251 +        # processors must be applied in the resvere order. (??)
  34.252 +        return process(self.processors)
  34.253 +                        
  34.254 +    def wsgifunc(self, *middleware):
  34.255 +        """Returns a WSGI-compatible function for this application."""
  34.256 +        def peep(iterator):
  34.257 +            """Peeps into an iterator by doing an iteration
  34.258 +            and returns an equivalent iterator.
  34.259 +            """
  34.260 +            # wsgi requires the headers first
  34.261 +            # so we need to do an iteration
  34.262 +            # and save the result for later
  34.263 +            try:
  34.264 +                firstchunk = iterator.next()
  34.265 +            except StopIteration:
  34.266 +                firstchunk = ''
  34.267 +
  34.268 +            return itertools.chain([firstchunk], iterator)    
  34.269 +                                
  34.270 +        def is_generator(x): return x and hasattr(x, 'next')
  34.271 +        
  34.272 +        def wsgi(env, start_resp):
  34.273 +            # clear threadlocal to avoid inteference of previous requests
  34.274 +            self._cleanup()
  34.275 +
  34.276 +            self.load(env)
  34.277 +            try:
  34.278 +                # allow uppercase methods only
  34.279 +                if web.ctx.method.upper() != web.ctx.method:
  34.280 +                    raise web.nomethod()
  34.281 +
  34.282 +                result = self.handle_with_processors()
  34.283 +                if is_generator(result):
  34.284 +                    result = peep(result)
  34.285 +                else:
  34.286 +                    result = [result]
  34.287 +            except web.HTTPError, e:
  34.288 +                result = [e.data]
  34.289 +
  34.290 +            result = web.safestr(iter(result))
  34.291 +
  34.292 +            status, headers = web.ctx.status, web.ctx.headers
  34.293 +            start_resp(status, headers)
  34.294 +            
  34.295 +            def cleanup():
  34.296 +                self._cleanup()
  34.297 +                yield '' # force this function to be a generator
  34.298 +                            
  34.299 +            return itertools.chain(result, cleanup())
  34.300 +
  34.301 +        for m in middleware: 
  34.302 +            wsgi = m(wsgi)
  34.303 +
  34.304 +        return wsgi
  34.305 +
  34.306 +    def run(self, *middleware):
  34.307 +        """
  34.308 +        Starts handling requests. If called in a CGI or FastCGI context, it will follow
  34.309 +        that protocol. If called from the command line, it will start an HTTP
  34.310 +        server on the port named in the first command line argument, or, if there
  34.311 +        is no argument, on port 8080.
  34.312 +        
  34.313 +        `middleware` is a list of WSGI middleware which is applied to the resulting WSGI
  34.314 +        function.
  34.315 +        """
  34.316 +        return wsgi.runwsgi(self.wsgifunc(*middleware))
  34.317 +
  34.318 +    def stop(self):
  34.319 +        """Stops the http server started by run.
  34.320 +        """
  34.321 +        if httpserver.server:
  34.322 +            httpserver.server.stop()
  34.323 +            httpserver.server = None
  34.324 +    
  34.325 +    def cgirun(self, *middleware):
  34.326 +        """
  34.327 +        Return a CGI handler. This is mostly useful with Google App Engine.
  34.328 +        There you can just do:
  34.329 +        
  34.330 +            main = app.cgirun()
  34.331 +        """
  34.332 +        wsgiapp = self.wsgifunc(*middleware)
  34.333 +
  34.334 +        try:
  34.335 +            from google.appengine.ext.webapp.util import run_wsgi_app
  34.336 +            return run_wsgi_app(wsgiapp)
  34.337 +        except ImportError:
  34.338 +            # we're not running from within Google App Engine
  34.339 +            return wsgiref.handlers.CGIHandler().run(wsgiapp)
  34.340 +    
  34.341 +    def load(self, env):
  34.342 +        """Initializes ctx using env."""
  34.343 +        ctx = web.ctx
  34.344 +        ctx.clear()
  34.345 +        ctx.status = '200 OK'
  34.346 +        ctx.headers = []
  34.347 +        ctx.output = ''
  34.348 +        ctx.environ = ctx.env = env
  34.349 +        ctx.host = env.get('HTTP_HOST')
  34.350 +
  34.351 +        if env.get('wsgi.url_scheme') in ['http', 'https']:
  34.352 +            ctx.protocol = env['wsgi.url_scheme']
  34.353 +        elif env.get('HTTPS', '').lower() in ['on', 'true', '1']:
  34.354 +            ctx.protocol = 'https'
  34.355 +        else:
  34.356 +            ctx.protocol = 'http'
  34.357 +        ctx.homedomain = ctx.protocol + '://' + env.get('HTTP_HOST', '[unknown]')
  34.358 +        ctx.homepath = os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', ''))
  34.359 +        ctx.home = ctx.homedomain + ctx.homepath
  34.360 +        #@@ home is changed when the request is handled to a sub-application.
  34.361 +        #@@ but the real home is required for doing absolute redirects.
  34.362 +        ctx.realhome = ctx.home
  34.363 +        ctx.ip = env.get('REMOTE_ADDR')
  34.364 +        ctx.method = env.get('REQUEST_METHOD')
  34.365 +        ctx.path = env.get('PATH_INFO')
  34.366 +        # http://trac.lighttpd.net/trac/ticket/406 requires:
  34.367 +        if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'):
  34.368 +            ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0], ctx.homepath)
  34.369 +            # Apache and CherryPy webservers unquote the url but lighttpd doesn't. 
  34.370 +            # unquote explicitly for lighttpd to make ctx.path uniform across all servers.
  34.371 +            ctx.path = urllib.unquote(ctx.path)
  34.372 +
  34.373 +        if env.get('QUERY_STRING'):
  34.374 +            ctx.query = '?' + env.get('QUERY_STRING', '')
  34.375 +        else:
  34.376 +            ctx.query = ''
  34.377 +
  34.378 +        ctx.fullpath = ctx.path + ctx.query
  34.379 +        
  34.380 +        for k, v in ctx.iteritems():
  34.381 +            # convert all string values to unicode values and replace 
  34.382 +            # malformed data with a suitable replacement marker.
  34.383 +            if isinstance(v, str):
  34.384 +                ctx[k] = v.decode('utf-8', 'replace') 
  34.385 +
  34.386 +        # status must always be str
  34.387 +        ctx.status = '200 OK'
  34.388 +        
  34.389 +        ctx.app_stack = []
  34.390 +
  34.391 +    def _delegate(self, f, fvars, args=[]):
  34.392 +        def handle_class(cls):
  34.393 +            meth = web.ctx.method
  34.394 +            if meth == 'HEAD' and not hasattr(cls, meth):
  34.395 +                meth = 'GET'
  34.396 +            if not hasattr(cls, meth):
  34.397 +                raise web.nomethod(cls)
  34.398 +            tocall = getattr(cls(), meth)
  34.399 +            return tocall(*args)
  34.400 +            
  34.401 +        def is_class(o): return isinstance(o, (types.ClassType, type))
  34.402 +            
  34.403 +        if f is None:
  34.404 +            raise web.notfound()
  34.405 +        elif isinstance(f, application):
  34.406 +            return f.handle_with_processors()
  34.407 +        elif is_class(f):
  34.408 +            return handle_class(f)
  34.409 +        elif isinstance(f, basestring):
  34.410 +            if f.startswith('redirect '):
  34.411 +                url = f.split(' ', 1)[1]
  34.412 +                if web.ctx.method == "GET":
  34.413 +                    x = web.ctx.env.get('QUERY_STRING', '')
  34.414 +                    if x:
  34.415 +                        url += '?' + x
  34.416 +                raise web.redirect(url)
  34.417 +            elif '.' in f:
  34.418 +                mod, cls = f.rsplit('.', 1)
  34.419 +                mod = __import__(mod, None, None, [''])
  34.420 +                cls = getattr(mod, cls)
  34.421 +            else:
  34.422 +                cls = fvars[f]
  34.423 +            return handle_class(cls)
  34.424 +        elif hasattr(f, '__call__'):
  34.425 +            return f()
  34.426 +        else:
  34.427 +            return web.notfound()
  34.428 +
  34.429 +    def _match(self, mapping, value):
  34.430 +        for pat, what in mapping:
  34.431 +            if isinstance(what, application):
  34.432 +                if value.startswith(pat):
  34.433 +                    f = lambda: self._delegate_sub_application(pat, what)
  34.434 +                    return f, None
  34.435 +                else:
  34.436 +                    continue
  34.437 +            elif isinstance(what, basestring):
  34.438 +                what, result = utils.re_subm('^' + pat + '$', what, value)
  34.439 +            else:
  34.440 +                result = utils.re_compile('^' + pat + '$').match(value)
  34.441 +                
  34.442 +            if result: # it's a match
  34.443 +                return what, [x for x in result.groups()]
  34.444 +        return None, None
  34.445 +        
  34.446 +    def _delegate_sub_application(self, dir, app):
  34.447 +        """Deletes request to sub application `app` rooted at the directory `dir`.
  34.448 +        The home, homepath, path and fullpath values in web.ctx are updated to mimic request
  34.449 +        to the subapp and are restored after it is handled. 
  34.450 +        
  34.451 +        @@Any issues with when used with yield?
  34.452 +        """
  34.453 +        web.ctx._oldctx = web.storage(web.ctx)
  34.454 +        web.ctx.home += dir
  34.455 +        web.ctx.homepath += dir
  34.456 +        web.ctx.path = web.ctx.path[len(dir):]
  34.457 +        web.ctx.fullpath = web.ctx.fullpath[len(dir):]
  34.458 +        return app.handle_with_processors()
  34.459 +            
  34.460 +    def get_parent_app(self):
  34.461 +        if self in web.ctx.app_stack:
  34.462 +            index = web.ctx.app_stack.index(self)
  34.463 +            if index > 0:
  34.464 +                return web.ctx.app_stack[index-1]
  34.465 +        
  34.466 +    def notfound(self):
  34.467 +        """Returns HTTPError with '404 not found' message"""
  34.468 +        parent = self.get_parent_app()
  34.469 +        if parent:
  34.470 +            return parent.notfound()
  34.471 +        else:
  34.472 +            return web._NotFound()
  34.473 +            
  34.474 +    def internalerror(self):
  34.475 +        """Returns HTTPError with '500 internal error' message"""
  34.476 +        parent = self.get_parent_app()
  34.477 +        if parent:
  34.478 +            return parent.internalerror()
  34.479 +        elif web.config.get('debug'):
  34.480 +            import debugerror
  34.481 +            return debugerror.debugerror()
  34.482 +        else:
  34.483 +            return web._InternalError()
  34.484 +
  34.485 +class auto_application(application):
  34.486 +    """Application similar to `application` but urls are constructed 
  34.487 +    automatiacally using metaclass.
  34.488 +
  34.489 +        >>> app = auto_application()
  34.490 +        >>> class hello(app.page):
  34.491 +        ...     def GET(self): return "hello, world"
  34.492 +        ...
  34.493 +        >>> class foo(app.page):
  34.494 +        ...     path = '/foo/.*'
  34.495 +        ...     def GET(self): return "foo"
  34.496 +        >>> app.request("/hello").data
  34.497 +        'hello, world'
  34.498 +        >>> app.request('/foo/bar').data
  34.499 +        'foo'
  34.500 +    """
  34.501 +    def __init__(self):
  34.502 +        application.__init__(self)
  34.503 +
  34.504 +        class metapage(type):
  34.505 +            def __init__(klass, name, bases, attrs):
  34.506 +                type.__init__(klass, name, bases, attrs)
  34.507 +                path = attrs.get('path', '/' + name)
  34.508 +
  34.509 +                # path can be specified as None to ignore that class
  34.510 +                # typically required to create a abstract base class.
  34.511 +                if path is not None:
  34.512 +                    self.add_mapping(path, klass)
  34.513 +
  34.514 +        class page:
  34.515 +            path = None
  34.516 +            __metaclass__ = metapage
  34.517 +
  34.518 +        self.page = page
  34.519 +
  34.520 +# The application class already has the required functionality of subdir_application
  34.521 +subdir_application = application
  34.522 +                
  34.523 +class subdomain_application(application):
  34.524 +    """
  34.525 +    Application to delegate requests based on the host.
  34.526 +
  34.527 +        >>> urls = ("/hello", "hello")
  34.528 +        >>> app = application(urls, globals())
  34.529 +        >>> class hello:
  34.530 +        ...     def GET(self): return "hello"
  34.531 +        >>>
  34.532 +        >>> mapping = (r"hello\.example\.com", app)
  34.533 +        >>> app2 = subdomain_application(mapping)
  34.534 +        >>> app2.request("/hello", host="hello.example.com").data
  34.535 +        'hello'
  34.536 +        >>> response = app2.request("/hello", host="something.example.com")
  34.537 +        >>> response.status
  34.538 +        '404 Not Found'
  34.539 +        >>> response.data
  34.540 +        'not found'
  34.541 +    """
  34.542 +    def handle(self):
  34.543 +        host = web.ctx.host.split(':')[0] #strip port
  34.544 +        fn, args = self._match(self.mapping, host)
  34.545 +        return self._delegate(fn, self.fvars, args)
  34.546 +        
  34.547 +    def _match(self, mapping, value):
  34.548 +        for pat, what in mapping:
  34.549 +            if isinstance(what, basestring):
  34.550 +                what, result = utils.re_subm('^' + pat + '$', what, value)
  34.551 +            else:
  34.552 +                result = utils.re_compile('^' + pat + '$').match(value)
  34.553 +
  34.554 +            if result: # it's a match
  34.555 +                return what, [x for x in result.groups()]
  34.556 +        return None, None
  34.557 +        
  34.558 +def loadhook(h):
  34.559 +    """
  34.560 +    Converts a load hook into an application processor.
  34.561 +    
  34.562 +        >>> app = auto_application()
  34.563 +        >>> def f(): "something done before handling request"
  34.564 +        ...
  34.565 +        >>> app.add_processor(loadhook(f))
  34.566 +    """
  34.567 +    def processor(handler):
  34.568 +        h()
  34.569 +        return handler()
  34.570 +        
  34.571 +    return processor
  34.572 +    
  34.573 +def unloadhook(h):
  34.574 +    """
  34.575 +    Converts an unload hook into an application processor.
  34.576 +    
  34.577 +        >>> app = auto_application()
  34.578 +        >>> def f(): "something done after handling request"
  34.579 +        ...
  34.580 +        >>> app.add_processor(unloadhook(f))    
  34.581 +    """
  34.582 +    def processor(handler):
  34.583 +        try:
  34.584 +            result = handler()
  34.585 +            is_generator = result and hasattr(result, 'next')
  34.586 +        except:
  34.587 +            # run the hook even when handler raises some exception
  34.588 +            h()
  34.589 +            raise
  34.590 +
  34.591 +        if is_generator:
  34.592 +            return wrap(result)
  34.593 +        else:
  34.594 +            h()
  34.595 +            return result
  34.596 +            
  34.597 +    def wrap(result):
  34.598 +        def next():
  34.599 +            try:
  34.600 +                return result.next()
  34.601 +            except:
  34.602 +                # call the hook at the and of iterator
  34.603 +                h()
  34.604 +                raise
  34.605 +
  34.606 +        result = iter(result)
  34.607 +        while True:
  34.608 +            yield next()
  34.609 +            
  34.610 +    return processor
  34.611 +
  34.612 +def autodelegate(prefix=''):
  34.613 +    """
  34.614 +    Returns a method that takes one argument and calls the method named prefix+arg,
  34.615 +    calling `notfound()` if there isn't one. Example:
  34.616 +
  34.617 +        urls = ('/prefs/(.*)', 'prefs')
  34.618 +
  34.619 +        class prefs:
  34.620 +            GET = autodelegate('GET_')
  34.621 +            def GET_password(self): pass
  34.622 +            def GET_privacy(self): pass
  34.623 +
  34.624 +    `GET_password` would get called for `/prefs/password` while `GET_privacy` for 
  34.625 +    `GET_privacy` gets called for `/prefs/privacy`.
  34.626 +    
  34.627 +    If a user visits `/prefs/password/change` then `GET_password(self, '/change')`
  34.628 +    is called.
  34.629 +    """
  34.630 +    def internal(self, arg):
  34.631 +        if '/' in arg:
  34.632 +            first, rest = arg.split('/', 1)
  34.633 +            func = prefix + first
  34.634 +            args = ['/' + rest]
  34.635 +        else:
  34.636 +            func = prefix + arg
  34.637 +            args = []
  34.638 +        
  34.639 +        if hasattr(self, func):
  34.640 +            try:
  34.641 +                return getattr(self, func)(*args)
  34.642 +            except TypeError:
  34.643 +                raise web.notfound()
  34.644 +        else:
  34.645 +            raise web.notfound()
  34.646 +    return internal
  34.647 +
  34.648 +class Reloader:
  34.649 +    """Checks to see if any loaded modules have changed on disk and, 
  34.650 +    if so, reloads them.
  34.651 +    """
  34.652 +
  34.653 +    """File suffix of compiled modules."""
  34.654 +    if sys.platform.startswith('java'):
  34.655 +        SUFFIX = '$py.class'
  34.656 +    else:
  34.657 +        SUFFIX = '.pyc'
  34.658 +    
  34.659 +    def __init__(self):
  34.660 +        self.mtimes = {}
  34.661 +
  34.662 +    def __call__(self):
  34.663 +        for mod in sys.modules.values():
  34.664 +            self.check(mod)
  34.665 +
  34.666 +    def check(self, mod):
  34.667 +        # jython registers java packages as modules but they either
  34.668 +        # don't have a __file__ attribute or its value is None
  34.669 +        if not (mod and hasattr(mod, '__file__') and mod.__file__):
  34.670 +            return
  34.671 +
  34.672 +        try: 
  34.673 +            mtime = os.stat(mod.__file__).st_mtime
  34.674 +        except (OSError, IOError):
  34.675 +            return
  34.676 +        if mod.__file__.endswith(self.__class__.SUFFIX) and os.path.exists(mod.__file__[:-1]):
  34.677 +            mtime = max(os.stat(mod.__file__[:-1]).st_mtime, mtime)
  34.678 +            
  34.679 +        if mod not in self.mtimes:
  34.680 +            self.mtimes[mod] = mtime
  34.681 +        elif self.mtimes[mod] < mtime:
  34.682 +            try: 
  34.683 +                reload(mod)
  34.684 +                self.mtimes[mod] = mtime
  34.685 +            except ImportError: 
  34.686 +                pass
  34.687 +                
  34.688 +if __name__ == "__main__":
  34.689 +    import doctest
  34.690 +    doctest.testmod()
    35.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    35.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/browser.py	Mon Dec 02 14:02:05 2013 +0100
    35.3 @@ -0,0 +1,236 @@
    35.4 +"""Browser to test web applications.
    35.5 +(from web.py)
    35.6 +"""
    35.7 +from utils import re_compile
    35.8 +from net import htmlunquote
    35.9 +
   35.10 +import httplib, urllib, urllib2
   35.11 +import copy
   35.12 +from StringIO import StringIO
   35.13 +
   35.14 +DEBUG = False
   35.15 +
   35.16 +__all__ = [
   35.17 +    "BrowserError",
   35.18 +    "Browser", "AppBrowser",
   35.19 +    "AppHandler"
   35.20 +]
   35.21 +
   35.22 +class BrowserError(Exception):
   35.23 +    pass
   35.24 +
   35.25 +class Browser:
   35.26 +    def __init__(self):
   35.27 +        import cookielib
   35.28 +        self.cookiejar = cookielib.CookieJar()
   35.29 +        self._cookie_processor = urllib2.HTTPCookieProcessor(self.cookiejar)
   35.30 +        self.form = None
   35.31 +
   35.32 +        self.url = "http://0.0.0.0:8080/"
   35.33 +        self.path = "/"
   35.34 +        
   35.35 +        self.status = None
   35.36 +        self.data = None
   35.37 +        self._response = None
   35.38 +        self._forms = None
   35.39 +
   35.40 +    def reset(self):
   35.41 +        """Clears all cookies and history."""
   35.42 +        self.cookiejar.clear()
   35.43 +
   35.44 +    def build_opener(self):
   35.45 +        """Builds the opener using urllib2.build_opener. 
   35.46 +        Subclasses can override this function to prodive custom openers.
   35.47 +        """
   35.48 +        return urllib2.build_opener()
   35.49 +
   35.50 +    def do_request(self, req):
   35.51 +        if DEBUG:
   35.52 +            print 'requesting', req.get_method(), req.get_full_url()
   35.53 +        opener = self.build_opener()
   35.54 +        opener.add_handler(self._cookie_processor)
   35.55 +        try:
   35.56 +            self._response = opener.open(req)
   35.57 +        except urllib2.HTTPError, e:
   35.58 +            self._response = e
   35.59 +
   35.60 +        self.url = self._response.geturl()
   35.61 +        self.path = urllib2.Request(self.url).get_selector()
   35.62 +        self.data = self._response.read()
   35.63 +        self.status = self._response.code
   35.64 +        self._forms = None
   35.65 +        self.form = None
   35.66 +        return self.get_response()
   35.67 +
   35.68 +    def open(self, url, data=None, headers={}):
   35.69 +        """Opens the specified url."""
   35.70 +        url = urllib.basejoin(self.url, url)
   35.71 +        req = urllib2.Request(url, data, headers)
   35.72 +        return self.do_request(req)
   35.73 +
   35.74 +    def show(self):
   35.75 +        """Opens the current page in real web browser."""
   35.76 +        f = open('page.html', 'w')
   35.77 +        f.write(self.data)
   35.78 +        f.close()
   35.79 +
   35.80 +        import webbrowser, os
   35.81 +        url = 'file://' + os.path.abspath('page.html')
   35.82 +        webbrowser.open(url)
   35.83 +
   35.84 +    def get_response(self):
   35.85 +        """Returns a copy of the current response."""
   35.86 +        return urllib.addinfourl(StringIO(self.data), self._response.info(), self._response.geturl())
   35.87 +
   35.88 +    def get_soup(self):
   35.89 +        """Returns beautiful soup of the current document."""
   35.90 +        import BeautifulSoup
   35.91 +        return BeautifulSoup.BeautifulSoup(self.data)
   35.92 +
   35.93 +    def get_text(self, e=None):
   35.94 +        """Returns content of e or the current document as plain text."""
   35.95 +        e = e or self.get_soup()
   35.96 +        return ''.join([htmlunquote(c) for c in e.recursiveChildGenerator() if isinstance(c, unicode)])
   35.97 +
   35.98 +    def _get_links(self):
   35.99 +        soup = self.get_soup()
  35.100 +        return [a for a in soup.findAll(name='a')]
  35.101 +        
  35.102 +    def get_links(self, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
  35.103 +        """Returns all links in the document."""
  35.104 +        return self._filter_links(self._get_links(),
  35.105 +            text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
  35.106 +
  35.107 +    def follow_link(self, link=None, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
  35.108 +        if link is None:
  35.109 +            links = self._filter_links(self.get_links(),
  35.110 +                text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
  35.111 +            link = links and links[0]
  35.112 +            
  35.113 +        if link:
  35.114 +            return self.open(link['href'])
  35.115 +        else:
  35.116 +            raise BrowserError("No link found")
  35.117 +            
  35.118 +    def find_link(self, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
  35.119 +        links = self._filter_links(self.get_links(), 
  35.120 +            text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
  35.121 +        return links and links[0] or None
  35.122 +            
  35.123 +    def _filter_links(self, links, 
  35.124 +            text=None, text_regex=None,
  35.125 +            url=None, url_regex=None,
  35.126 +            predicate=None):
  35.127 +        predicates = []
  35.128 +        if text is not None:
  35.129 +            predicates.append(lambda link: link.string == text)
  35.130 +        if text_regex is not None:
  35.131 +            predicates.append(lambda link: re_compile(text_regex).search(link.string or ''))
  35.132 +        if url is not None:
  35.133 +            predicates.append(lambda link: link.get('href') == url)
  35.134 +        if url_regex is not None:
  35.135 +            predicates.append(lambda link: re_compile(url_regex).search(link.get('href', '')))
  35.136 +        if predicate:
  35.137 +            predicate.append(predicate)
  35.138 +
  35.139 +        def f(link):
  35.140 +            for p in predicates:
  35.141 +                if not p(link):
  35.142 +                    return False
  35.143 +            return True
  35.144 +
  35.145 +        return [link for link in links if f(link)]
  35.146 +
  35.147 +    def get_forms(self):
  35.148 +        """Returns all forms in the current document.
  35.149 +        The returned form objects implement the ClientForm.HTMLForm interface.
  35.150 +        """
  35.151 +        if self._forms is None:
  35.152 +            import ClientForm
  35.153 +            self._forms = ClientForm.ParseResponse(self.get_response(), backwards_compat=False)
  35.154 +        return self._forms
  35.155 +
  35.156 +    def select_form(self, name=None, predicate=None, index=0):
  35.157 +        """Selects the specified form."""
  35.158 +        forms = self.get_forms()
  35.159 +
  35.160 +        if name is not None:
  35.161 +            forms = [f for f in forms if f.name == name]
  35.162 +        if predicate:
  35.163 +            forms = [f for f in forms if predicate(f)]
  35.164 +            
  35.165 +        if forms:
  35.166 +            self.form = forms[index]
  35.167 +            return self.form
  35.168 +        else:
  35.169 +            raise BrowserError("No form selected.")
  35.170 +        
  35.171 +    def submit(self, **kw):
  35.172 +        """submits the currently selected form."""
  35.173 +        if self.form is None:
  35.174 +            raise BrowserError("No form selected.")
  35.175 +        req = self.form.click(**kw)
  35.176 +        return self.do_request(req)
  35.177 +
  35.178 +    def __getitem__(self, key):
  35.179 +        return self.form[key]
  35.180 +
  35.181 +    def __setitem__(self, key, value):
  35.182 +        self.form[key] = value
  35.183 +
  35.184 +class AppBrowser(Browser):
  35.185 +    """Browser interface to test web.py apps.
  35.186 +    
  35.187 +        b = AppBrowser(app)
  35.188 +        b.open('/')
  35.189 +        b.follow_link(text='Login')
  35.190 +        
  35.191 +        b.select_form(name='login')
  35.192 +        b['username'] = 'joe'
  35.193 +        b['password'] = 'secret'
  35.194 +        b.submit()
  35.195 +
  35.196 +        assert b.path == '/'
  35.197 +        assert 'Welcome joe' in b.get_text()
  35.198 +    """
  35.199 +    def __init__(self, app):
  35.200 +        Browser.__init__(self)
  35.201 +        self.app = app
  35.202 +
  35.203 +    def build_opener(self):
  35.204 +        return urllib2.build_opener(AppHandler(self.app))
  35.205 +
  35.206 +class AppHandler(urllib2.HTTPHandler):
  35.207 +    """urllib2 handler to handle requests using web.py application."""
  35.208 +    handler_order = 100
  35.209 +
  35.210 +    def __init__(self, app):
  35.211 +        self.app = app
  35.212 +
  35.213 +    def http_open(self, req):
  35.214 +        result = self.app.request(
  35.215 +            localpart=req.get_selector(),
  35.216 +            method=req.get_method(),
  35.217 +            host=req.get_host(),
  35.218 +            data=req.get_data(),
  35.219 +            headers=dict(req.header_items()),
  35.220 +            https=req.get_type() == "https"
  35.221 +        )
  35.222 +        return self._make_response(result, req.get_full_url())
  35.223 +
  35.224 +    def https_open(self, req):
  35.225 +        return self.http_open(req)
  35.226 +    
  35.227 +    try:
  35.228 +        https_request = urllib2.HTTPHandler.do_request_
  35.229 +    except AttributeError:
  35.230 +        # for python 2.3
  35.231 +        pass
  35.232 +
  35.233 +    def _make_response(self, result, url):
  35.234 +        data = "\r\n".join(["%s: %s" % (k, v) for k, v in result.header_items])
  35.235 +        headers = httplib.HTTPMessage(StringIO(data))
  35.236 +        response = urllib.addinfourl(StringIO(result.data), headers, url)
  35.237 +        code, msg = result.status.split(None, 1)
  35.238 +        response.code, response.msg = int(code), msg
  35.239 +        return response
    36.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    36.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/contrib/template.py	Mon Dec 02 14:02:05 2013 +0100
    36.3 @@ -0,0 +1,131 @@
    36.4 +"""
    36.5 +Interface to various templating engines.
    36.6 +"""
    36.7 +import os.path
    36.8 +
    36.9 +__all__ = [
   36.10 +    "render_cheetah", "render_genshi", "render_mako",
   36.11 +    "cache", 
   36.12 +]
   36.13 +
   36.14 +class render_cheetah:
   36.15 +    """Rendering interface to Cheetah Templates.
   36.16 +
   36.17 +    Example:
   36.18 +
   36.19 +        render = render_cheetah('templates')
   36.20 +        render.hello(name="cheetah")
   36.21 +    """
   36.22 +    def __init__(self, path):
   36.23 +        # give error if Chetah is not installed
   36.24 +        from Cheetah.Template import Template
   36.25 +        self.path = path
   36.26 +
   36.27 +    def __getattr__(self, name):
   36.28 +        from Cheetah.Template import Template
   36.29 +        path = os.path.join(self.path, name + ".html")
   36.30 +        
   36.31 +        def template(**kw):
   36.32 +            t = Template(file=path, searchList=[kw])
   36.33 +            return t.respond()
   36.34 +
   36.35 +        return template
   36.36 +    
   36.37 +class render_genshi:
   36.38 +    """Rendering interface genshi templates.
   36.39 +    Example:
   36.40 +
   36.41 +    for xml/html templates.
   36.42 +
   36.43 +        render = render_genshi(['templates/'])
   36.44 +        render.hello(name='genshi')
   36.45 +
   36.46 +    For text templates:
   36.47 +
   36.48 +        render = render_genshi(['templates/'], type='text')
   36.49 +        render.hello(name='genshi')
   36.50 +    """
   36.51 +
   36.52 +    def __init__(self, *a, **kwargs):
   36.53 +        from genshi.template import TemplateLoader
   36.54 +
   36.55 +        self._type = kwargs.pop('type', None)
   36.56 +        self._loader = TemplateLoader(*a, **kwargs)
   36.57 +
   36.58 +    def __getattr__(self, name):
   36.59 +        # Assuming all templates are html
   36.60 +        path = name + ".html"
   36.61 +
   36.62 +        if self._type == "text":
   36.63 +            from genshi.template import TextTemplate
   36.64 +            cls = TextTemplate
   36.65 +            type = "text"
   36.66 +        else:
   36.67 +            cls = None
   36.68 +            type = None
   36.69 +
   36.70 +        t = self._loader.load(path, cls=cls)
   36.71 +        def template(**kw):
   36.72 +            stream = t.generate(**kw)
   36.73 +            if type:
   36.74 +                return stream.render(type)
   36.75 +            else:
   36.76 +                return stream.render()
   36.77 +        return template
   36.78 +
   36.79 +class render_jinja:
   36.80 +    """Rendering interface to Jinja2 Templates
   36.81 +    
   36.82 +    Example:
   36.83 +
   36.84 +        render= render_jinja('templates')
   36.85 +        render.hello(name='jinja2')
   36.86 +    """
   36.87 +    def __init__(self, *a, **kwargs):
   36.88 +        extensions = kwargs.pop('extensions', [])
   36.89 +        globals = kwargs.pop('globals', {})
   36.90 +
   36.91 +        from jinja2 import Environment,FileSystemLoader
   36.92 +        self._lookup = Environment(loader=FileSystemLoader(*a, **kwargs), extensions=extensions)
   36.93 +        self._lookup.globals.update(globals)
   36.94 +        
   36.95 +    def __getattr__(self, name):
   36.96 +        # Assuming all templates end with .html
   36.97 +        path = name + '.html'
   36.98 +        t = self._lookup.get_template(path)
   36.99 +        return t.render
  36.100 +        
  36.101 +class render_mako:
  36.102 +    """Rendering interface to Mako Templates.
  36.103 +
  36.104 +    Example:
  36.105 +
  36.106 +        render = render_mako(directories=['templates'])
  36.107 +        render.hello(name="mako")
  36.108 +    """
  36.109 +    def __init__(self, *a, **kwargs):
  36.110 +        from mako.lookup import TemplateLookup
  36.111 +        self._lookup = TemplateLookup(*a, **kwargs)
  36.112 +
  36.113 +    def __getattr__(self, name):
  36.114 +        # Assuming all templates are html
  36.115 +        path = name + ".html"
  36.116 +        t = self._lookup.get_template(path)
  36.117 +        return t.render
  36.118 +
  36.119 +class cache:
  36.120 +    """Cache for any rendering interface.
  36.121 +    
  36.122 +    Example:
  36.123 +
  36.124 +        render = cache(render_cheetah("templates/"))
  36.125 +        render.hello(name='cache')
  36.126 +    """
  36.127 +    def __init__(self, render):
  36.128 +        self._render = render
  36.129 +        self._cache = {}
  36.130 +
  36.131 +    def __getattr__(self, name):
  36.132 +        if name not in self._cache:
  36.133 +            self._cache[name] = getattr(self._render, name)
  36.134 +        return self._cache[name]
    37.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    37.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/db.py	Mon Dec 02 14:02:05 2013 +0100
    37.3 @@ -0,0 +1,1237 @@
    37.4 +"""
    37.5 +Database API
    37.6 +(part of web.py)
    37.7 +"""
    37.8 +
    37.9 +__all__ = [
   37.10 +  "UnknownParamstyle", "UnknownDB", "TransactionError", 
   37.11 +  "sqllist", "sqlors", "reparam", "sqlquote",
   37.12 +  "SQLQuery", "SQLParam", "sqlparam",
   37.13 +  "SQLLiteral", "sqlliteral",
   37.14 +  "database", 'DB',
   37.15 +]
   37.16 +
   37.17 +import time
   37.18 +try:
   37.19 +    import datetime
   37.20 +except ImportError:
   37.21 +    datetime = None
   37.22 +
   37.23 +try: set
   37.24 +except NameError:
   37.25 +    from sets import Set as set
   37.26 +    
   37.27 +from utils import threadeddict, storage, iters, iterbetter, safestr, safeunicode
   37.28 +
   37.29 +try:
   37.30 +    # db module can work independent of web.py
   37.31 +    from webapi import debug, config
   37.32 +except:
   37.33 +    import sys
   37.34 +    debug = sys.stderr
   37.35 +    config = storage()
   37.36 +
   37.37 +class UnknownDB(Exception):
   37.38 +    """raised for unsupported dbms"""
   37.39 +    pass
   37.40 +
   37.41 +class _ItplError(ValueError): 
   37.42 +    def __init__(self, text, pos):
   37.43 +        ValueError.__init__(self)
   37.44 +        self.text = text
   37.45 +        self.pos = pos
   37.46 +    def __str__(self):
   37.47 +        return "unfinished expression in %s at char %d" % (
   37.48 +            repr(self.text), self.pos)
   37.49 +
   37.50 +class TransactionError(Exception): pass
   37.51 +
   37.52 +class UnknownParamstyle(Exception): 
   37.53 +    """
   37.54 +    raised for unsupported db paramstyles
   37.55 +
   37.56 +    (currently supported: qmark, numeric, format, pyformat)
   37.57 +    """
   37.58 +    pass
   37.59 +    
   37.60 +class SQLParam(object):
   37.61 +    """
   37.62 +    Parameter in SQLQuery.
   37.63 +    
   37.64 +        >>> q = SQLQuery(["SELECT * FROM test WHERE name=", SQLParam("joe")])
   37.65 +        >>> q
   37.66 +        <sql: "SELECT * FROM test WHERE name='joe'">
   37.67 +        >>> q.query()
   37.68 +        'SELECT * FROM test WHERE name=%s'
   37.69 +        >>> q.values()
   37.70 +        ['joe']
   37.71 +    """
   37.72 +    __slots__ = ["value"]
   37.73 +
   37.74 +    def __init__(self, value):
   37.75 +        self.value = value
   37.76 +        
   37.77 +    def get_marker(self, paramstyle='pyformat'):
   37.78 +        if paramstyle == 'qmark':
   37.79 +            return '?'
   37.80 +        elif paramstyle == 'numeric':
   37.81 +            return ':1'
   37.82 +        elif paramstyle is None or paramstyle in ['format', 'pyformat']:
   37.83 +            return '%s'
   37.84 +        raise UnknownParamstyle, paramstyle
   37.85 +        
   37.86 +    def sqlquery(self): 
   37.87 +        return SQLQuery([self])
   37.88 +        
   37.89 +    def __add__(self, other):
   37.90 +        return self.sqlquery() + other
   37.91 +        
   37.92 +    def __radd__(self, other):
   37.93 +        return other + self.sqlquery() 
   37.94 +            
   37.95 +    def __str__(self): 
   37.96 +        return str(self.value)
   37.97 +    
   37.98 +    def __repr__(self):
   37.99 +        return '<param: %s>' % repr(self.value)
  37.100 +
  37.101 +sqlparam =  SQLParam
  37.102 +
  37.103 +class SQLQuery(object):
  37.104 +    """
  37.105 +    You can pass this sort of thing as a clause in any db function.
  37.106 +    Otherwise, you can pass a dictionary to the keyword argument `vars`
  37.107 +    and the function will call reparam for you.
  37.108 +
  37.109 +    Internally, consists of `items`, which is a list of strings and
  37.110 +    SQLParams, which get concatenated to produce the actual query.
  37.111 +    """
  37.112 +    __slots__ = ["items"]
  37.113 +
  37.114 +    # tested in sqlquote's docstring
  37.115 +    def __init__(self, items=None):
  37.116 +        r"""Creates a new SQLQuery.
  37.117 +        
  37.118 +            >>> SQLQuery("x")
  37.119 +            <sql: 'x'>
  37.120 +            >>> q = SQLQuery(['SELECT * FROM ', 'test', ' WHERE x=', SQLParam(1)])
  37.121 +            >>> q
  37.122 +            <sql: 'SELECT * FROM test WHERE x=1'>
  37.123 +            >>> q.query(), q.values()
  37.124 +            ('SELECT * FROM test WHERE x=%s', [1])
  37.125 +            >>> SQLQuery(SQLParam(1))
  37.126 +            <sql: '1'>
  37.127 +        """
  37.128 +        if items is None:
  37.129 +            self.items = []
  37.130 +        elif isinstance(items, list):
  37.131 +            self.items = items
  37.132 +        elif isinstance(items, SQLParam):
  37.133 +            self.items = [items]
  37.134 +        elif isinstance(items, SQLQuery):
  37.135 +            self.items = list(items.items)
  37.136 +        else:
  37.137 +            self.items = [items]
  37.138 +            
  37.139 +        # Take care of SQLLiterals
  37.140 +        for i, item in enumerate(self.items):
  37.141 +            if isinstance(item, SQLParam) and isinstance(item.value, SQLLiteral):
  37.142 +                self.items[i] = item.value.v
  37.143 +
  37.144 +    def append(self, value):
  37.145 +        self.items.append(value)
  37.146 +
  37.147 +    def __add__(self, other):
  37.148 +        if isinstance(other, basestring):
  37.149 +            items = [other]
  37.150 +        elif isinstance(other, SQLQuery):
  37.151 +            items = other.items
  37.152 +        else:
  37.153 +            return NotImplemented
  37.154 +        return SQLQuery(self.items + items)
  37.155 +
  37.156 +    def __radd__(self, other):
  37.157 +        if isinstance(other, basestring):
  37.158 +            items = [other]
  37.159 +        else:
  37.160 +            return NotImplemented
  37.161 +            
  37.162 +        return SQLQuery(items + self.items)
  37.163 +
  37.164 +    def __iadd__(self, other):
  37.165 +        if isinstance(other, (basestring, SQLParam)):
  37.166 +            self.items.append(other)
  37.167 +        elif isinstance(other, SQLQuery):
  37.168 +            self.items.extend(other.items)
  37.169 +        else:
  37.170 +            return NotImplemented
  37.171 +        return self
  37.172 +
  37.173 +    def __len__(self):
  37.174 +        return len(self.query())
  37.175 +        
  37.176 +    def query(self, paramstyle=None):
  37.177 +        """
  37.178 +        Returns the query part of the sql query.
  37.179 +            >>> q = SQLQuery(["SELECT * FROM test WHERE name=", SQLParam('joe')])
  37.180 +            >>> q.query()
  37.181 +            'SELECT * FROM test WHERE name=%s'
  37.182 +            >>> q.query(paramstyle='qmark')
  37.183 +            'SELECT * FROM test WHERE name=?'
  37.184 +        """
  37.185 +        s = []
  37.186 +        for x in self.items:
  37.187 +            if isinstance(x, SQLParam):
  37.188 +                x = x.get_marker(paramstyle)
  37.189 +                s.append(safestr(x))
  37.190 +            else:
  37.191 +                x = safestr(x)
  37.192 +                # automatically escape % characters in the query
  37.193 +                # For backward compatability, ignore escaping when the query looks already escaped
  37.194 +                if paramstyle in ['format', 'pyformat']:
  37.195 +                    if '%' in x and '%%' not in x:
  37.196 +                        x = x.replace('%', '%%')
  37.197 +                s.append(x)
  37.198 +        return "".join(s)
  37.199 +    
  37.200 +    def values(self):
  37.201 +        """
  37.202 +        Returns the values of the parameters used in the sql query.
  37.203 +            >>> q = SQLQuery(["SELECT * FROM test WHERE name=", SQLParam('joe')])
  37.204 +            >>> q.values()
  37.205 +            ['joe']
  37.206 +        """
  37.207 +        return [i.value for i in self.items if isinstance(i, SQLParam)]
  37.208 +        
  37.209 +    def join(items, sep=' ', prefix=None, suffix=None, target=None):
  37.210 +        """
  37.211 +        Joins multiple queries.
  37.212 +        
  37.213 +        >>> SQLQuery.join(['a', 'b'], ', ')
  37.214 +        <sql: 'a, b'>
  37.215 +
  37.216 +        Optinally, prefix and suffix arguments can be provided.
  37.217 +
  37.218 +        >>> SQLQuery.join(['a', 'b'], ', ', prefix='(', suffix=')')
  37.219 +        <sql: '(a, b)'>
  37.220 +
  37.221 +        If target argument is provided, the items are appended to target instead of creating a new SQLQuery.
  37.222 +        """
  37.223 +        if target is None:
  37.224 +            target = SQLQuery()
  37.225 +
  37.226 +        target_items = target.items
  37.227 +
  37.228 +        if prefix:
  37.229 +            target_items.append(prefix)
  37.230 +
  37.231 +        for i, item in enumerate(items):
  37.232 +            if i != 0:
  37.233 +                target_items.append(sep)
  37.234 +            if isinstance(item, SQLQuery):
  37.235 +                target_items.extend(item.items)
  37.236 +            else:
  37.237 +                target_items.append(item)
  37.238 +
  37.239 +        if suffix:
  37.240 +            target_items.append(suffix)
  37.241 +        return target
  37.242 +    
  37.243 +    join = staticmethod(join)
  37.244 +    
  37.245 +    def _str(self):
  37.246 +        try:
  37.247 +            return self.query() % tuple([sqlify(x) for x in self.values()])            
  37.248 +        except (ValueError, TypeError):
  37.249 +            return self.query()
  37.250 +        
  37.251 +    def __str__(self):
  37.252 +        return safestr(self._str())
  37.253 +        
  37.254 +    def __unicode__(self):
  37.255 +        return safeunicode(self._str())
  37.256 +
  37.257 +    def __repr__(self):
  37.258 +        return '<sql: %s>' % repr(str(self))
  37.259 +
  37.260 +class SQLLiteral: 
  37.261 +    """
  37.262 +    Protects a string from `sqlquote`.
  37.263 +
  37.264 +        >>> sqlquote('NOW()')
  37.265 +        <sql: "'NOW()'">
  37.266 +        >>> sqlquote(SQLLiteral('NOW()'))
  37.267 +        <sql: 'NOW()'>
  37.268 +    """
  37.269 +    def __init__(self, v): 
  37.270 +        self.v = v
  37.271 +
  37.272 +    def __repr__(self): 
  37.273 +        return self.v
  37.274 +
  37.275 +sqlliteral = SQLLiteral
  37.276 +
  37.277 +def _sqllist(values):
  37.278 +    """
  37.279 +        >>> _sqllist([1, 2, 3])
  37.280 +        <sql: '(1, 2, 3)'>
  37.281 +    """
  37.282 +    items = []
  37.283 +    items.append('(')
  37.284 +    for i, v in enumerate(values):
  37.285 +        if i != 0:
  37.286 +            items.append(', ')
  37.287 +        items.append(sqlparam(v))
  37.288 +    items.append(')')
  37.289 +    return SQLQuery(items)
  37.290 +
  37.291 +def reparam(string_, dictionary): 
  37.292 +    """
  37.293 +    Takes a string and a dictionary and interpolates the string
  37.294 +    using values from the dictionary. Returns an `SQLQuery` for the result.
  37.295 +
  37.296 +        >>> reparam("s = $s", dict(s=True))
  37.297 +        <sql: "s = 't'">
  37.298 +        >>> reparam("s IN $s", dict(s=[1, 2]))
  37.299 +        <sql: 's IN (1, 2)'>
  37.300 +    """
  37.301 +    dictionary = dictionary.copy() # eval mucks with it
  37.302 +    vals = []
  37.303 +    result = []
  37.304 +    for live, chunk in _interpolate(string_):
  37.305 +        if live:
  37.306 +            v = eval(chunk, dictionary)
  37.307 +            result.append(sqlquote(v))
  37.308 +        else: 
  37.309 +            result.append(chunk)
  37.310 +    return SQLQuery.join(result, '')
  37.311 +
  37.312 +def sqlify(obj): 
  37.313 +    """
  37.314 +    converts `obj` to its proper SQL version
  37.315 +
  37.316 +        >>> sqlify(None)
  37.317 +        'NULL'
  37.318 +        >>> sqlify(True)
  37.319 +        "'t'"
  37.320 +        >>> sqlify(3)
  37.321 +        '3'
  37.322 +    """
  37.323 +    # because `1 == True and hash(1) == hash(True)`
  37.324 +    # we have to do this the hard way...
  37.325 +
  37.326 +    if obj is None:
  37.327 +        return 'NULL'
  37.328 +    elif obj is True:
  37.329 +        return "'t'"
  37.330 +    elif obj is False:
  37.331 +        return "'f'"
  37.332 +    elif datetime and isinstance(obj, datetime.datetime):
  37.333 +        return repr(obj.isoformat())
  37.334 +    else:
  37.335 +        if isinstance(obj, unicode): obj = obj.encode('utf8')
  37.336 +        return repr(obj)
  37.337 +
  37.338 +def sqllist(lst): 
  37.339 +    """
  37.340 +    Converts the arguments for use in something like a WHERE clause.
  37.341 +    
  37.342 +        >>> sqllist(['a', 'b'])
  37.343 +        'a, b'
  37.344 +        >>> sqllist('a')
  37.345 +        'a'
  37.346 +        >>> sqllist(u'abc')
  37.347 +        u'abc'
  37.348 +    """
  37.349 +    if isinstance(lst, basestring): 
  37.350 +        return lst
  37.351 +    else:
  37.352 +        return ', '.join(lst)
  37.353 +
  37.354 +def sqlors(left, lst):
  37.355 +    """
  37.356 +    `left is a SQL clause like `tablename.arg = ` 
  37.357 +    and `lst` is a list of values. Returns a reparam-style
  37.358 +    pair featuring the SQL that ORs together the clause
  37.359 +    for each item in the lst.
  37.360 +
  37.361 +        >>> sqlors('foo = ', [])
  37.362 +        <sql: '1=2'>
  37.363 +        >>> sqlors('foo = ', [1])
  37.364 +        <sql: 'foo = 1'>
  37.365 +        >>> sqlors('foo = ', 1)
  37.366 +        <sql: 'foo = 1'>
  37.367 +        >>> sqlors('foo = ', [1,2,3])
  37.368 +        <sql: '(foo = 1 OR foo = 2 OR foo = 3 OR 1=2)'>
  37.369 +    """
  37.370 +    if isinstance(lst, iters):
  37.371 +        lst = list(lst)
  37.372 +        ln = len(lst)
  37.373 +        if ln == 0:
  37.374 +            return SQLQuery("1=2")
  37.375 +        if ln == 1:
  37.376 +            lst = lst[0]
  37.377 +
  37.378 +    if isinstance(lst, iters):
  37.379 +        return SQLQuery(['('] + 
  37.380 +          sum([[left, sqlparam(x), ' OR '] for x in lst], []) +
  37.381 +          ['1=2)']
  37.382 +        )
  37.383 +    else:
  37.384 +        return left + sqlparam(lst)
  37.385 +        
  37.386 +def sqlwhere(dictionary, grouping=' AND '): 
  37.387 +    """
  37.388 +    Converts a `dictionary` to an SQL WHERE clause `SQLQuery`.
  37.389 +    
  37.390 +        >>> sqlwhere({'cust_id': 2, 'order_id':3})
  37.391 +        <sql: 'order_id = 3 AND cust_id = 2'>
  37.392 +        >>> sqlwhere({'cust_id': 2, 'order_id':3}, grouping=', ')
  37.393 +        <sql: 'order_id = 3, cust_id = 2'>
  37.394 +        >>> sqlwhere({'a': 'a', 'b': 'b'}).query()
  37.395 +        'a = %s AND b = %s'
  37.396 +    """
  37.397 +    return SQLQuery.join([k + ' = ' + sqlparam(v) for k, v in dictionary.items()], grouping)
  37.398 +
  37.399 +def sqlquote(a): 
  37.400 +    """
  37.401 +    Ensures `a` is quoted properly for use in a SQL query.
  37.402 +
  37.403 +        >>> 'WHERE x = ' + sqlquote(True) + ' AND y = ' + sqlquote(3)
  37.404 +        <sql: "WHERE x = 't' AND y = 3">
  37.405 +        >>> 'WHERE x = ' + sqlquote(True) + ' AND y IN ' + sqlquote([2, 3])
  37.406 +        <sql: "WHERE x = 't' AND y IN (2, 3)">
  37.407 +    """
  37.408 +    if isinstance(a, list):
  37.409 +        return _sqllist(a)
  37.410 +    else:
  37.411 +        return sqlparam(a).sqlquery()
  37.412 +
  37.413 +class Transaction:
  37.414 +    """Database transaction."""
  37.415 +    def __init__(self, ctx):
  37.416 +        self.ctx = ctx
  37.417 +        self.transaction_count = transaction_count = len(ctx.transactions)
  37.418 +
  37.419 +        class transaction_engine:
  37.420 +            """Transaction Engine used in top level transactions."""
  37.421 +            def do_transact(self):
  37.422 +                ctx.commit(unload=False)
  37.423 +
  37.424 +            def do_commit(self):
  37.425 +                ctx.commit()
  37.426 +
  37.427 +            def do_rollback(self):
  37.428 +                ctx.rollback()
  37.429 +
  37.430 +        class subtransaction_engine:
  37.431 +            """Transaction Engine used in sub transactions."""
  37.432 +            def query(self, q):
  37.433 +                db_cursor = ctx.db.cursor()
  37.434 +                ctx.db_execute(db_cursor, SQLQuery(q % transaction_count))
  37.435 +
  37.436 +            def do_transact(self):
  37.437 +                self.query('SAVEPOINT webpy_sp_%s')
  37.438 +
  37.439 +            def do_commit(self):
  37.440 +                self.query('RELEASE SAVEPOINT webpy_sp_%s')
  37.441 +
  37.442 +            def do_rollback(self):
  37.443 +                self.query('ROLLBACK TO SAVEPOINT webpy_sp_%s')
  37.444 +
  37.445 +        class dummy_engine:
  37.446 +            """Transaction Engine used instead of subtransaction_engine 
  37.447 +            when sub transactions are not supported."""
  37.448 +            do_transact = do_commit = do_rollback = lambda self: None
  37.449 +
  37.450 +        if self.transaction_count:
  37.451 +            # nested transactions are not supported in some databases
  37.452 +            if self.ctx.get('ignore_nested_transactions'):
  37.453 +                self.engine = dummy_engine()
  37.454 +            else:
  37.455 +                self.engine = subtransaction_engine()
  37.456 +        else:
  37.457 +            self.engine = transaction_engine()
  37.458 +
  37.459 +        self.engine.do_transact()
  37.460 +        self.ctx.transactions.append(self)
  37.461 +
  37.462 +    def __enter__(self):
  37.463 +        return self
  37.464 +
  37.465 +    def __exit__(self, exctype, excvalue, traceback):
  37.466 +        if exctype is not None:
  37.467 +            self.rollback()
  37.468 +        else:
  37.469 +            self.commit()
  37.470 +
  37.471 +    def commit(self):
  37.472 +        if len(self.ctx.transactions) > self.transaction_count:
  37.473 +            self.engine.do_commit()
  37.474 +            self.ctx.transactions = self.ctx.transactions[:self.transaction_count]
  37.475 +
  37.476 +    def rollback(self):
  37.477 +        if len(self.ctx.transactions) > self.transaction_count:
  37.478 +            self.engine.do_rollback()
  37.479 +            self.ctx.transactions = self.ctx.transactions[:self.transaction_count]
  37.480 +
  37.481 +class DB: 
  37.482 +    """Database"""
  37.483 +    def __init__(self, db_module, keywords):
  37.484 +        """Creates a database.
  37.485 +        """
  37.486 +        # some DB implementaions take optional paramater `driver` to use a specific driver modue
  37.487 +        # but it should not be passed to connect
  37.488 +        keywords.pop('driver', None)
  37.489 +
  37.490 +        self.db_module = db_module
  37.491 +        self.keywords = keywords
  37.492 +
  37.493 +        self._ctx = threadeddict()
  37.494 +        # flag to enable/disable printing queries
  37.495 +        self.printing = config.get('debug_sql', config.get('debug', False))
  37.496 +        self.supports_multiple_insert = False
  37.497 +        
  37.498 +        try:
  37.499 +            import DBUtils
  37.500 +            # enable pooling if DBUtils module is available.
  37.501 +            self.has_pooling = True
  37.502 +        except ImportError:
  37.503 +            self.has_pooling = False
  37.504 +            
  37.505 +        # Pooling can be disabled by passing pooling=False in the keywords.
  37.506 +        self.has_pooling = self.keywords.pop('pooling', True) and self.has_pooling
  37.507 +            
  37.508 +    def _getctx(self): 
  37.509 +        if not self._ctx.get('db'):
  37.510 +            self._load_context(self._ctx)
  37.511 +        return self._ctx
  37.512 +    ctx = property(_getctx)
  37.513 +    
  37.514 +    def _load_context(self, ctx):
  37.515 +        ctx.dbq_count = 0
  37.516 +        ctx.transactions = [] # stack of transactions
  37.517 +        
  37.518 +        if self.has_pooling:
  37.519 +            ctx.db = self._connect_with_pooling(self.keywords)
  37.520 +        else:
  37.521 +            ctx.db = self._connect(self.keywords)
  37.522 +        ctx.db_execute = self._db_execute
  37.523 +        
  37.524 +        if not hasattr(ctx.db, 'commit'):
  37.525 +            ctx.db.commit = lambda: None
  37.526 +
  37.527 +        if not hasattr(ctx.db, 'rollback'):
  37.528 +            ctx.db.rollback = lambda: None
  37.529 +            
  37.530 +        def commit(unload=True):
  37.531 +            # do db commit and release the connection if pooling is enabled.            
  37.532 +            ctx.db.commit()
  37.533 +            if unload and self.has_pooling:
  37.534 +                self._unload_context(self._ctx)
  37.535 +                
  37.536 +        def rollback():
  37.537 +            # do db rollback and release the connection if pooling is enabled.
  37.538 +            ctx.db.rollback()
  37.539 +            if self.has_pooling:
  37.540 +                self._unload_context(self._ctx)
  37.541 +                
  37.542 +        ctx.commit = commit
  37.543 +        ctx.rollback = rollback
  37.544 +            
  37.545 +    def _unload_context(self, ctx):
  37.546 +        del ctx.db
  37.547 +            
  37.548 +    def _connect(self, keywords):
  37.549 +        return self.db_module.connect(**keywords)
  37.550 +        
  37.551 +    def _connect_with_pooling(self, keywords):
  37.552 +        def get_pooled_db():
  37.553 +            from DBUtils import PooledDB
  37.554 +
  37.555 +            # In DBUtils 0.9.3, `dbapi` argument is renamed as `creator`
  37.556 +            # see Bug#122112
  37.557 +            
  37.558 +            if PooledDB.__version__.split('.') < '0.9.3'.split('.'):
  37.559 +                return PooledDB.PooledDB(dbapi=self.db_module, **keywords)
  37.560 +            else:
  37.561 +                return PooledDB.PooledDB(creator=self.db_module, **keywords)
  37.562 +        
  37.563 +        if getattr(self, '_pooleddb', None) is None:
  37.564 +            self._pooleddb = get_pooled_db()
  37.565 +        
  37.566 +        return self._pooleddb.connection()
  37.567 +        
  37.568 +    def _db_cursor(self):
  37.569 +        return self.ctx.db.cursor()
  37.570 +
  37.571 +    def _param_marker(self):
  37.572 +        """Returns parameter marker based on paramstyle attribute if this database."""
  37.573 +        style = getattr(self, 'paramstyle', 'pyformat')
  37.574 +
  37.575 +        if style == 'qmark':
  37.576 +            return '?'
  37.577 +        elif style == 'numeric':
  37.578 +            return ':1'
  37.579 +        elif style in ['format', 'pyformat']:
  37.580 +            return '%s'
  37.581 +        raise UnknownParamstyle, style
  37.582 +
  37.583 +    def _db_execute(self, cur, sql_query): 
  37.584 +        """executes an sql query"""
  37.585 +        self.ctx.dbq_count += 1
  37.586 +        
  37.587 +        try:
  37.588 +            a = time.time()
  37.589 +            query, params = self._process_query(sql_query)
  37.590 +            out = cur.execute(query, params)
  37.591 +            b = time.time()
  37.592 +        except:
  37.593 +            if self.printing:
  37.594 +                print >> debug, 'ERR:', str(sql_query)
  37.595 +            if self.ctx.transactions:
  37.596 +                self.ctx.transactions[-1].rollback()
  37.597 +            else:
  37.598 +                self.ctx.rollback()
  37.599 +            raise
  37.600 +
  37.601 +        if self.printing:
  37.602 +            print >> debug, '%s (%s): %s' % (round(b-a, 2), self.ctx.dbq_count, str(sql_query))
  37.603 +        return out
  37.604 +
  37.605 +    def _process_query(self, sql_query):
  37.606 +        """Takes the SQLQuery object and returns query string and parameters.
  37.607 +        """
  37.608 +        paramstyle = getattr(self, 'paramstyle', 'pyformat')
  37.609 +        query = sql_query.query(paramstyle)
  37.610 +        params = sql_query.values()
  37.611 +        return query, params
  37.612 +    
  37.613 +    def _where(self, where, vars): 
  37.614 +        if isinstance(where, (int, long)):
  37.615 +            where = "id = " + sqlparam(where)
  37.616 +        #@@@ for backward-compatibility
  37.617 +        elif isinstance(where, (list, tuple)) and len(where) == 2:
  37.618 +            where = SQLQuery(where[0], where[1])
  37.619 +        elif isinstance(where, SQLQuery):
  37.620 +            pass
  37.621 +        else:
  37.622 +            where = reparam(where, vars)        
  37.623 +        return where
  37.624 +    
  37.625 +    def query(self, sql_query, vars=None, processed=False, _test=False): 
  37.626 +        """
  37.627 +        Execute SQL query `sql_query` using dictionary `vars` to interpolate it.
  37.628 +        If `processed=True`, `vars` is a `reparam`-style list to use 
  37.629 +        instead of interpolating.
  37.630 +        
  37.631 +            >>> db = DB(None, {})
  37.632 +            >>> db.query("SELECT * FROM foo", _test=True)
  37.633 +            <sql: 'SELECT * FROM foo'>
  37.634 +            >>> db.query("SELECT * FROM foo WHERE x = $x", vars=dict(x='f'), _test=True)
  37.635 +            <sql: "SELECT * FROM foo WHERE x = 'f'">
  37.636 +            >>> db.query("SELECT * FROM foo WHERE x = " + sqlquote('f'), _test=True)
  37.637 +            <sql: "SELECT * FROM foo WHERE x = 'f'">
  37.638 +        """
  37.639 +        if vars is None: vars = {}
  37.640 +        
  37.641 +        if not processed and not isinstance(sql_query, SQLQuery):
  37.642 +            sql_query = reparam(sql_query, vars)
  37.643 +        
  37.644 +        if _test: return sql_query
  37.645 +        
  37.646 +        db_cursor = self._db_cursor()
  37.647 +        self._db_execute(db_cursor, sql_query)
  37.648 +        
  37.649 +        if db_cursor.description:
  37.650 +            names = [x[0] for x in db_cursor.description]
  37.651 +            def iterwrapper():
  37.652 +                row = db_cursor.fetchone()
  37.653 +                while row:
  37.654 +                    yield storage(dict(zip(names, row)))
  37.655 +                    row = db_cursor.fetchone()
  37.656 +            out = iterbetter(iterwrapper())
  37.657 +            out.__len__ = lambda: int(db_cursor.rowcount)
  37.658 +            out.list = lambda: [storage(dict(zip(names, x))) \
  37.659 +                               for x in db_cursor.fetchall()]
  37.660 +        else:
  37.661 +            out = db_cursor.rowcount
  37.662 +        
  37.663 +        if not self.ctx.transactions: 
  37.664 +            self.ctx.commit()
  37.665 +        return out
  37.666 +    
  37.667 +    def select(self, tables, vars=None, what='*', where=None, order=None, group=None, 
  37.668 +               limit=None, offset=None, _test=False): 
  37.669 +        """
  37.670 +        Selects `what` from `tables` with clauses `where`, `order`, 
  37.671 +        `group`, `limit`, and `offset`. Uses vars to interpolate. 
  37.672 +        Otherwise, each clause can be a SQLQuery.
  37.673 +        
  37.674 +            >>> db = DB(None, {})
  37.675 +            >>> db.select('foo', _test=True)
  37.676 +            <sql: 'SELECT * FROM foo'>
  37.677 +            >>> db.select(['foo', 'bar'], where="foo.bar_id = bar.id", limit=5, _test=True)
  37.678 +            <sql: 'SELECT * FROM foo, bar WHERE foo.bar_id = bar.id LIMIT 5'>
  37.679 +        """
  37.680 +        if vars is None: vars = {}
  37.681 +        sql_clauses = self.sql_clauses(what, tables, where, group, order, limit, offset)
  37.682 +        clauses = [self.gen_clause(sql, val, vars) for sql, val in sql_clauses if val is not None]
  37.683 +        qout = SQLQuery.join(clauses)
  37.684 +        if _test: return qout
  37.685 +        return self.query(qout, processed=True)
  37.686 +    
  37.687 +    def where(self, table, what='*', order=None, group=None, limit=None, 
  37.688 +              offset=None, _test=False, **kwargs):
  37.689 +        """
  37.690 +        Selects from `table` where keys are equal to values in `kwargs`.
  37.691 +        
  37.692 +            >>> db = DB(None, {})
  37.693 +            >>> db.where('foo', bar_id=3, _test=True)
  37.694 +            <sql: 'SELECT * FROM foo WHERE bar_id = 3'>
  37.695 +            >>> db.where('foo', source=2, crust='dewey', _test=True)
  37.696 +            <sql: "SELECT * FROM foo WHERE source = 2 AND crust = 'dewey'">
  37.697 +            >>> db.where('foo', _test=True)
  37.698 +            <sql: 'SELECT * FROM foo'>
  37.699 +        """
  37.700 +        where_clauses = []
  37.701 +        for k, v in kwargs.iteritems():
  37.702 +            where_clauses.append(k + ' = ' + sqlquote(v))
  37.703 +            
  37.704 +        if where_clauses:
  37.705 +            where = SQLQuery.join(where_clauses, " AND ")
  37.706 +        else:
  37.707 +            where = None
  37.708 +            
  37.709 +        return self.select(table, what=what, order=order, 
  37.710 +               group=group, limit=limit, offset=offset, _test=_test, 
  37.711 +               where=where)
  37.712 +    
  37.713 +    def sql_clauses(self, what, tables, where, group, order, limit, offset): 
  37.714 +        return (
  37.715 +            ('SELECT', what),
  37.716 +            ('FROM', sqllist(tables)),
  37.717 +            ('WHERE', where),
  37.718 +            ('GROUP BY', group),
  37.719 +            ('ORDER BY', order),
  37.720 +            ('LIMIT', limit),
  37.721 +            ('OFFSET', offset))
  37.722 +    
  37.723 +    def gen_clause(self, sql, val, vars): 
  37.724 +        if isinstance(val, (int, long)):
  37.725 +            if sql == 'WHERE':
  37.726 +                nout = 'id = ' + sqlquote(val)
  37.727 +            else:
  37.728 +                nout = SQLQuery(val)
  37.729 +        #@@@
  37.730 +        elif isinstance(val, (list, tuple)) and len(val) == 2:
  37.731 +            nout = SQLQuery(val[0], val[1]) # backwards-compatibility
  37.732 +        elif isinstance(val, SQLQuery):
  37.733 +            nout = val
  37.734 +        else:
  37.735 +            nout = reparam(val, vars)
  37.736 +
  37.737 +        def xjoin(a, b):
  37.738 +            if a and b: return a + ' ' + b
  37.739 +            else: return a or b
  37.740 +
  37.741 +        return xjoin(sql, nout)
  37.742 +
  37.743 +    def insert(self, tablename, seqname=None, _test=False, **values): 
  37.744 +        """
  37.745 +        Inserts `values` into `tablename`. Returns current sequence ID.
  37.746 +        Set `seqname` to the ID if it's not the default, or to `False`
  37.747 +        if there isn't one.
  37.748 +        
  37.749 +            >>> db = DB(None, {})
  37.750 +            >>> q = db.insert('foo', name='bob', age=2, created=SQLLiteral('NOW()'), _test=True)
  37.751 +            >>> q
  37.752 +            <sql: "INSERT INTO foo (age, name, created) VALUES (2, 'bob', NOW())">
  37.753 +            >>> q.query()
  37.754 +            'INSERT INTO foo (age, name, created) VALUES (%s, %s, NOW())'
  37.755 +            >>> q.values()
  37.756 +            [2, 'bob']
  37.757 +        """
  37.758 +        def q(x): return "(" + x + ")"
  37.759 +        
  37.760 +        if values:
  37.761 +            _keys = SQLQuery.join(values.keys(), ', ')
  37.762 +            _values = SQLQuery.join([sqlparam(v) for v in values.values()], ', ')
  37.763 +            sql_query = "INSERT INTO %s " % tablename + q(_keys) + ' VALUES ' + q(_values)
  37.764 +        else:
  37.765 +            sql_query = SQLQuery(self._get_insert_default_values_query(tablename))
  37.766 +
  37.767 +        if _test: return sql_query
  37.768 +        
  37.769 +        db_cursor = self._db_cursor()
  37.770 +        if seqname is not False: 
  37.771 +            sql_query = self._process_insert_query(sql_query, tablename, seqname)
  37.772 +
  37.773 +        if isinstance(sql_query, tuple):
  37.774 +            # for some databases, a separate query has to be made to find 
  37.775 +            # the id of the inserted row.
  37.776 +            q1, q2 = sql_query
  37.777 +            self._db_execute(db_cursor, q1)
  37.778 +            self._db_execute(db_cursor, q2)
  37.779 +        else:
  37.780 +            self._db_execute(db_cursor, sql_query)
  37.781 +
  37.782 +        try: 
  37.783 +            out = db_cursor.fetchone()[0]
  37.784 +        except Exception: 
  37.785 +            out = None
  37.786 +        
  37.787 +        if not self.ctx.transactions: 
  37.788 +            self.ctx.commit()
  37.789 +        return out
  37.790 +        
  37.791 +    def _get_insert_default_values_query(self, table):
  37.792 +        return "INSERT INTO %s DEFAULT VALUES" % table
  37.793 +
  37.794 +    def multiple_insert(self, tablename, values, seqname=None, _test=False):
  37.795 +        """
  37.796 +        Inserts multiple rows into `tablename`. The `values` must be a list of dictioanries, 
  37.797 +        one for each row to be inserted, each with the same set of keys.
  37.798 +        Returns the list of ids of the inserted rows.        
  37.799 +        Set `seqname` to the ID if it's not the default, or to `False`
  37.800 +        if there isn't one.
  37.801 +        
  37.802 +            >>> db = DB(None, {})
  37.803 +            >>> db.supports_multiple_insert = True
  37.804 +            >>> values = [{"name": "foo", "email": "foo@example.com"}, {"name": "bar", "email": "bar@example.com"}]
  37.805 +            >>> db.multiple_insert('person', values=values, _test=True)
  37.806 +            <sql: "INSERT INTO person (name, email) VALUES ('foo', 'foo@example.com'), ('bar', 'bar@example.com')">
  37.807 +        """        
  37.808 +        if not values:
  37.809 +            return []
  37.810 +            
  37.811 +        if not self.supports_multiple_insert:
  37.812 +            out = [self.insert(tablename, seqname=seqname, _test=_test, **v) for v in values]
  37.813 +            if seqname is False:
  37.814 +                return None
  37.815 +            else:
  37.816 +                return out
  37.817 +                
  37.818 +        keys = values[0].keys()
  37.819 +        #@@ make sure all keys are valid
  37.820 +
  37.821 +        # make sure all rows have same keys.
  37.822 +        for v in values:
  37.823 +            if v.keys() != keys:
  37.824 +                raise ValueError, 'Bad data'
  37.825 +
  37.826 +        sql_query = SQLQuery('INSERT INTO %s (%s) VALUES ' % (tablename, ', '.join(keys)))
  37.827 +
  37.828 +        for i, row in enumerate(values):
  37.829 +            if i != 0:
  37.830 +                sql_query.append(", ")
  37.831 +            SQLQuery.join([SQLParam(row[k]) for k in keys], sep=", ", target=sql_query, prefix="(", suffix=")")
  37.832 +        
  37.833 +        if _test: return sql_query
  37.834 +
  37.835 +        db_cursor = self._db_cursor()
  37.836 +        if seqname is not False: 
  37.837 +            sql_query = self._process_insert_query(sql_query, tablename, seqname)
  37.838 +
  37.839 +        if isinstance(sql_query, tuple):
  37.840 +            # for some databases, a separate query has to be made to find 
  37.841 +            # the id of the inserted row.
  37.842 +            q1, q2 = sql_query
  37.843 +            self._db_execute(db_cursor, q1)
  37.844 +            self._db_execute(db_cursor, q2)
  37.845 +        else:
  37.846 +            self._db_execute(db_cursor, sql_query)
  37.847 +
  37.848 +        try: 
  37.849 +            out = db_cursor.fetchone()[0]
  37.850 +            out = range(out-len(values)+1, out+1)        
  37.851 +        except Exception: 
  37.852 +            out = None
  37.853 +
  37.854 +        if not self.ctx.transactions: 
  37.855 +            self.ctx.commit()
  37.856 +        return out
  37.857 +
  37.858 +    
  37.859 +    def update(self, tables, where, vars=None, _test=False, **values): 
  37.860 +        """
  37.861 +        Update `tables` with clause `where` (interpolated using `vars`)
  37.862 +        and setting `values`.
  37.863 +
  37.864 +            >>> db = DB(None, {})
  37.865 +            >>> name = 'Joseph'
  37.866 +            >>> q = db.update('foo', where='name = $name', name='bob', age=2,
  37.867 +            ...     created=SQLLiteral('NOW()'), vars=locals(), _test=True)
  37.868 +            >>> q
  37.869 +            <sql: "UPDATE foo SET age = 2, name = 'bob', created = NOW() WHERE name = 'Joseph'">
  37.870 +            >>> q.query()
  37.871 +            'UPDATE foo SET age = %s, name = %s, created = NOW() WHERE name = %s'
  37.872 +            >>> q.values()
  37.873 +            [2, 'bob', 'Joseph']
  37.874 +        """
  37.875 +        if vars is None: vars = {}
  37.876 +        where = self._where(where, vars)
  37.877 +
  37.878 +        query = (
  37.879 +          "UPDATE " + sqllist(tables) + 
  37.880 +          " SET " + sqlwhere(values, ', ') + 
  37.881 +          " WHERE " + where)
  37.882 +
  37.883 +        if _test: return query
  37.884 +        
  37.885 +        db_cursor = self._db_cursor()
  37.886 +        self._db_execute(db_cursor, query)
  37.887 +        if not self.ctx.transactions: 
  37.888 +            self.ctx.commit()
  37.889 +        return db_cursor.rowcount
  37.890 +    
  37.891 +    def delete(self, table, where, using=None, vars=None, _test=False): 
  37.892 +        """
  37.893 +        Deletes from `table` with clauses `where` and `using`.
  37.894 +
  37.895 +            >>> db = DB(None, {})
  37.896 +            >>> name = 'Joe'
  37.897 +            >>> db.delete('foo', where='name = $name', vars=locals(), _test=True)
  37.898 +            <sql: "DELETE FROM foo WHERE name = 'Joe'">
  37.899 +        """
  37.900 +        if vars is None: vars = {}
  37.901 +        where = self._where(where, vars)
  37.902 +
  37.903 +        q = 'DELETE FROM ' + table
  37.904 +        if using: q += ' USING ' + sqllist(using)
  37.905 +        if where: q += ' WHERE ' + where
  37.906 +
  37.907 +        if _test: return q
  37.908 +
  37.909 +        db_cursor = self._db_cursor()
  37.910 +        self._db_execute(db_cursor, q)
  37.911 +        if not self.ctx.transactions: 
  37.912 +            self.ctx.commit()
  37.913 +        return db_cursor.rowcount
  37.914 +
  37.915 +    def _process_insert_query(self, query, tablename, seqname):
  37.916 +        return query
  37.917 +
  37.918 +    def transaction(self): 
  37.919 +        """Start a transaction."""
  37.920 +        return Transaction(self.ctx)
  37.921 +    
  37.922 +class PostgresDB(DB): 
  37.923 +    """Postgres driver."""
  37.924 +    def __init__(self, **keywords):
  37.925 +        if 'pw' in keywords:
  37.926 +            keywords['password'] = keywords.pop('pw')
  37.927 +            
  37.928 +        db_module = import_driver(["psycopg2", "psycopg", "pgdb"], preferred=keywords.pop('driver', None))
  37.929 +        if db_module.__name__ == "psycopg2":
  37.930 +            import psycopg2.extensions
  37.931 +            psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
  37.932 +
  37.933 +        # if db is not provided postgres driver will take it from PGDATABASE environment variable
  37.934 +        if 'db' in keywords:
  37.935 +            keywords['database'] = keywords.pop('db')
  37.936 +        
  37.937 +        self.dbname = "postgres"
  37.938 +        self.paramstyle = db_module.paramstyle
  37.939 +        DB.__init__(self, db_module, keywords)
  37.940 +        self.supports_multiple_insert = True
  37.941 +        self._sequences = None
  37.942 +        
  37.943 +    def _process_insert_query(self, query, tablename, seqname):
  37.944 +        if seqname is None:
  37.945 +            # when seqname is not provided guess the seqname and make sure it exists
  37.946 +            seqname = tablename + "_id_seq"
  37.947 +            if seqname not in self._get_all_sequences():
  37.948 +                seqname = None
  37.949 +        
  37.950 +        if seqname:
  37.951 +            query += "; SELECT currval('%s')" % seqname
  37.952 +            
  37.953 +        return query
  37.954 +    
  37.955 +    def _get_all_sequences(self):
  37.956 +        """Query postgres to find names of all sequences used in this database."""
  37.957 +        if self._sequences is None:
  37.958 +            q = "SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'"
  37.959 +            self._sequences = set([c.relname for c in self.query(q)])
  37.960 +        return self._sequences
  37.961 +
  37.962 +    def _connect(self, keywords):
  37.963 +        conn = DB._connect(self, keywords)
  37.964 +        try:
  37.965 +            conn.set_client_encoding('UTF8')
  37.966 +        except AttributeError:
  37.967 +            # fallback for pgdb driver
  37.968 +            conn.cursor().execute("set client_encoding to 'UTF-8'")
  37.969 +        return conn
  37.970 +        
  37.971 +    def _connect_with_pooling(self, keywords):
  37.972 +        conn = DB._connect_with_pooling(self, keywords)
  37.973 +        conn._con._con.set_client_encoding('UTF8')
  37.974 +        return conn
  37.975 +
  37.976 +class MySQLDB(DB): 
  37.977 +    def __init__(self, **keywords):
  37.978 +        import MySQLdb as db
  37.979 +        if 'pw' in keywords:
  37.980 +            keywords['passwd'] = keywords['pw']
  37.981 +            del keywords['pw']
  37.982 +
  37.983 +        if 'charset' not in keywords:
  37.984 +            keywords['charset'] = 'utf8'
  37.985 +        elif keywords['charset'] is None:
  37.986 +            del keywords['charset']
  37.987 +
  37.988 +        self.paramstyle = db.paramstyle = 'pyformat' # it's both, like psycopg
  37.989 +        self.dbname = "mysql"
  37.990 +        DB.__init__(self, db, keywords)
  37.991 +        self.supports_multiple_insert = True
  37.992 +        
  37.993 +    def _process_insert_query(self, query, tablename, seqname):
  37.994 +        return query, SQLQuery('SELECT last_insert_id();')
  37.995 +        
  37.996 +    def _get_insert_default_values_query(self, table):
  37.997 +        return "INSERT INTO %s () VALUES()" % table
  37.998 +
  37.999 +def import_driver(drivers, preferred=None):
 37.1000 +    """Import the first available driver or preferred driver.
 37.1001 +    """
 37.1002 +    if preferred:
 37.1003 +        drivers = [preferred]
 37.1004 +
 37.1005 +    for d in drivers:
 37.1006 +        try:
 37.1007 +            return __import__(d, None, None, ['x'])
 37.1008 +        except ImportError:
 37.1009 +            pass
 37.1010 +    raise ImportError("Unable to import " + " or ".join(drivers))
 37.1011 +
 37.1012 +class SqliteDB(DB): 
 37.1013 +    def __init__(self, **keywords):
 37.1014 +        db = import_driver(["sqlite3", "pysqlite2.dbapi2", "sqlite"], preferred=keywords.pop('driver', None))
 37.1015 +
 37.1016 +        if db.__name__ in ["sqlite3", "pysqlite2.dbapi2"]:
 37.1017 +            db.paramstyle = 'qmark'
 37.1018 +            
 37.1019 +        # sqlite driver doesn't create datatime objects for timestamp columns unless `detect_types` option is passed.
 37.1020 +        # It seems to be supported in sqlite3 and pysqlite2 drivers, not surte about sqlite.
 37.1021 +        keywords.setdefault('detect_types', db.PARSE_DECLTYPES)
 37.1022 +
 37.1023 +        self.paramstyle = db.paramstyle
 37.1024 +        keywords['database'] = keywords.pop('db')
 37.1025 +        keywords['pooling'] = False # sqlite don't allows connections to be shared by threads
 37.1026 +        self.dbname = "sqlite"        
 37.1027 +        DB.__init__(self, db, keywords)
 37.1028 +
 37.1029 +    def _process_insert_query(self, query, tablename, seqname):
 37.1030 +        return query, SQLQuery('SELECT last_insert_rowid();')
 37.1031 +    
 37.1032 +    def query(self, *a, **kw):
 37.1033 +        out = DB.query(self, *a, **kw)
 37.1034 +        if isinstance(out, iterbetter):
 37.1035 +            del out.__len__
 37.1036 +        return out
 37.1037 +
 37.1038 +class FirebirdDB(DB):
 37.1039 +    """Firebird Database.
 37.1040 +    """
 37.1041 +    def __init__(self, **keywords):
 37.1042 +        try:
 37.1043 +            import kinterbasdb as db
 37.1044 +        except Exception:
 37.1045 +            db = None
 37.1046 +            pass
 37.1047 +        if 'pw' in keywords:
 37.1048 +            keywords['passwd'] = keywords['pw']
 37.1049 +            del keywords['pw']
 37.1050 +        keywords['database'] = keywords['db']
 37.1051 +        del keywords['db']
 37.1052 +        DB.__init__(self, db, keywords)
 37.1053 +        
 37.1054 +    def delete(self, table, where=None, using=None, vars=None, _test=False):
 37.1055 +        # firebird doesn't support using clause
 37.1056 +        using=None
 37.1057 +        return DB.delete(self, table, where, using, vars, _test)
 37.1058 +
 37.1059 +    def sql_clauses(self, what, tables, where, group, order, limit, offset):
 37.1060 +        return (
 37.1061 +            ('SELECT', ''),
 37.1062 +            ('FIRST', limit),
 37.1063 +            ('SKIP', offset),
 37.1064 +            ('', what),
 37.1065 +            ('FROM', sqllist(tables)),
 37.1066 +            ('WHERE', where),
 37.1067 +            ('GROUP BY', group),
 37.1068 +            ('ORDER BY', order)
 37.1069 +        )
 37.1070 +
 37.1071 +class MSSQLDB(DB):
 37.1072 +    def __init__(self, **keywords):
 37.1073 +        import pymssql as db    
 37.1074 +        if 'pw' in keywords:
 37.1075 +            keywords['password'] = keywords.pop('pw')
 37.1076 +        keywords['database'] = keywords.pop('db')
 37.1077 +        self.dbname = "mssql"
 37.1078 +        DB.__init__(self, db, keywords)
 37.1079 +
 37.1080 +    def _process_query(self, sql_query):
 37.1081 +        """Takes the SQLQuery object and returns query string and parameters.
 37.1082 +        """
 37.1083 +        # MSSQLDB expects params to be a tuple. 
 37.1084 +        # Overwriting the default implementation to convert params to tuple.
 37.1085 +        paramstyle = getattr(self, 'paramstyle', 'pyformat')
 37.1086 +        query = sql_query.query(paramstyle)
 37.1087 +        params = sql_query.values()
 37.1088 +        return query, tuple(params)
 37.1089 +
 37.1090 +    def sql_clauses(self, what, tables, where, group, order, limit, offset): 
 37.1091 +        return (
 37.1092 +            ('SELECT', what),
 37.1093 +            ('TOP', limit),
 37.1094 +            ('FROM', sqllist(tables)),
 37.1095 +            ('WHERE', where),
 37.1096 +            ('GROUP BY', group),
 37.1097 +            ('ORDER BY', order),
 37.1098 +            ('OFFSET', offset))
 37.1099 +            
 37.1100 +    def _test(self):
 37.1101 +        """Test LIMIT.
 37.1102 +
 37.1103 +            Fake presence of pymssql module for running tests.
 37.1104 +            >>> import sys
 37.1105 +            >>> sys.modules['pymssql'] = sys.modules['sys']
 37.1106 +            
 37.1107 +            MSSQL has TOP clause instead of LIMIT clause.
 37.1108 +            >>> db = MSSQLDB(db='test', user='joe', pw='secret')
 37.1109 +            >>> db.select('foo', limit=4, _test=True)
 37.1110 +            <sql: 'SELECT * TOP 4 FROM foo'>
 37.1111 +        """
 37.1112 +        pass
 37.1113 +
 37.1114 +class OracleDB(DB): 
 37.1115 +    def __init__(self, **keywords): 
 37.1116 +        import cx_Oracle as db 
 37.1117 +        if 'pw' in keywords: 
 37.1118 +            keywords['password'] = keywords.pop('pw') 
 37.1119 +
 37.1120 +        #@@ TODO: use db.makedsn if host, port is specified 
 37.1121 +        keywords['dsn'] = keywords.pop('db') 
 37.1122 +        self.dbname = 'oracle' 
 37.1123 +        db.paramstyle = 'numeric' 
 37.1124 +        self.paramstyle = db.paramstyle
 37.1125 +
 37.1126 +        # oracle doesn't support pooling 
 37.1127 +        keywords.pop('pooling', None) 
 37.1128 +        DB.__init__(self, db, keywords) 
 37.1129 +
 37.1130 +    def _process_insert_query(self, query, tablename, seqname): 
 37.1131 +        if seqname is None: 
 37.1132 +            # It is not possible to get seq name from table name in Oracle
 37.1133 +            return query
 37.1134 +        else:
 37.1135 +            return query + "; SELECT %s.currval FROM dual" % seqname 
 37.1136 +
 37.1137 +_databases = {}
 37.1138 +def database(dburl=None, **params):
 37.1139 +    """Creates appropriate database using params.
 37.1140 +    
 37.1141 +    Pooling will be enabled if DBUtils module is available. 
 37.1142 +    Pooling can be disabled by passing pooling=False in params.
 37.1143 +    """
 37.1144 +    dbn = params.pop('dbn')
 37.1145 +    if dbn in _databases:
 37.1146 +        return _databases[dbn](**params)
 37.1147 +    else:
 37.1148 +        raise UnknownDB, dbn
 37.1149 +
 37.1150 +def register_database(name, clazz):
 37.1151 +    """
 37.1152 +    Register a database.
 37.1153 +
 37.1154 +        >>> class LegacyDB(DB): 
 37.1155 +        ...     def __init__(self, **params): 
 37.1156 +        ...        pass 
 37.1157 +        ...
 37.1158 +        >>> register_database('legacy', LegacyDB)
 37.1159 +        >>> db = database(dbn='legacy', db='test', user='joe', passwd='secret') 
 37.1160 +    """
 37.1161 +    _databases[name] = clazz
 37.1162 +
 37.1163 +register_database('mysql', MySQLDB)
 37.1164 +register_database('postgres', PostgresDB)
 37.1165 +register_database('sqlite', SqliteDB)
 37.1166 +register_database('firebird', FirebirdDB)
 37.1167 +register_database('mssql', MSSQLDB)
 37.1168 +register_database('oracle', OracleDB)
 37.1169 +
 37.1170 +def _interpolate(format): 
 37.1171 +    """
 37.1172 +    Takes a format string and returns a list of 2-tuples of the form
 37.1173 +    (boolean, string) where boolean says whether string should be evaled
 37.1174 +    or not.
 37.1175 +
 37.1176 +    from <http://lfw.org/python/Itpl.py> (public domain, Ka-Ping Yee)
 37.1177 +    """
 37.1178 +    from tokenize import tokenprog
 37.1179 +
 37.1180 +    def matchorfail(text, pos):
 37.1181 +        match = tokenprog.match(text, pos)
 37.1182 +        if match is None:
 37.1183 +            raise _ItplError(text, pos)
 37.1184 +        return match, match.end()
 37.1185 +
 37.1186 +    namechars = "abcdefghijklmnopqrstuvwxyz" \
 37.1187 +        "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
 37.1188 +    chunks = []
 37.1189 +    pos = 0
 37.1190 +
 37.1191 +    while 1:
 37.1192 +        dollar = format.find("$", pos)
 37.1193 +        if dollar < 0: 
 37.1194 +            break
 37.1195 +        nextchar = format[dollar + 1]
 37.1196 +
 37.1197 +        if nextchar == "{":
 37.1198 +            chunks.append((0, format[pos:dollar]))
 37.1199 +            pos, level = dollar + 2, 1
 37.1200 +            while level:
 37.1201 +                match, pos = matchorfail(format, pos)
 37.1202 +                tstart, tend = match.regs[3]
 37.1203 +                token = format[tstart:tend]
 37.1204 +                if token == "{": 
 37.1205 +                    level = level + 1
 37.1206 +                elif token == "}":  
 37.1207 +                    level = level - 1
 37.1208 +            chunks.append((1, format[dollar + 2:pos - 1]))
 37.1209 +
 37.1210 +        elif nextchar in namechars:
 37.1211 +            chunks.append((0, format[pos:dollar]))
 37.1212 +            match, pos = matchorfail(format, dollar + 1)
 37.1213 +            while pos < len(format):
 37.1214 +                if format[pos] == "." and \
 37.1215 +                    pos + 1 < len(format) and format[pos + 1] in namechars:
 37.1216 +                    match, pos = matchorfail(format, pos + 1)
 37.1217 +                elif format[pos] in "([":
 37.1218 +                    pos, level = pos + 1, 1
 37.1219 +                    while level:
 37.1220 +                        match, pos = matchorfail(format, pos)
 37.1221 +                        tstart, tend = match.regs[3]
 37.1222 +                        token = format[tstart:tend]
 37.1223 +                        if token[0] in "([": 
 37.1224 +                            level = level + 1
 37.1225 +                        elif token[0] in ")]":  
 37.1226 +                            level = level - 1
 37.1227 +                else: 
 37.1228 +                    break
 37.1229 +            chunks.append((1, format[dollar + 1:pos]))
 37.1230 +        else:
 37.1231 +            chunks.append((0, format[pos:dollar + 1]))
 37.1232 +            pos = dollar + 1 + (nextchar == "$")
 37.1233 +
 37.1234 +    if pos < len(format): 
 37.1235 +        chunks.append((0, format[pos:]))
 37.1236 +    return chunks
 37.1237 +
 37.1238 +if __name__ == "__main__":
 37.1239 +    import doctest
 37.1240 +    doctest.testmod()
    38.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    38.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/debugerror.py	Mon Dec 02 14:02:05 2013 +0100
    38.3 @@ -0,0 +1,354 @@
    38.4 +"""
    38.5 +pretty debug errors
    38.6 +(part of web.py)
    38.7 +
    38.8 +portions adapted from Django <djangoproject.com> 
    38.9 +Copyright (c) 2005, the Lawrence Journal-World
   38.10 +Used under the modified BSD license:
   38.11 +http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
   38.12 +"""
   38.13 +
   38.14 +__all__ = ["debugerror", "djangoerror", "emailerrors"]
   38.15 +
   38.16 +import sys, urlparse, pprint, traceback
   38.17 +from template import Template
   38.18 +from net import websafe
   38.19 +from utils import sendmail, safestr
   38.20 +import webapi as web
   38.21 +
   38.22 +import os, os.path
   38.23 +whereami = os.path.join(os.getcwd(), __file__)
   38.24 +whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1])
   38.25 +djangoerror_t = """\
   38.26 +$def with (exception_type, exception_value, frames)
   38.27 +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
   38.28 +<html lang="en">
   38.29 +<head>
   38.30 +  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
   38.31 +  <meta name="robots" content="NONE,NOARCHIVE" />
   38.32 +  <title>$exception_type at $ctx.path</title>
   38.33 +  <style type="text/css">
   38.34 +    html * { padding:0; margin:0; }
   38.35 +    body * { padding:10px 20px; }
   38.36 +    body * * { padding:0; }
   38.37 +    body { font:small sans-serif; }
   38.38 +    body>div { border-bottom:1px solid #ddd; }
   38.39 +    h1 { font-weight:normal; }
   38.40 +    h2 { margin-bottom:.8em; }
   38.41 +    h2 span { font-size:80%; color:#666; font-weight:normal; }
   38.42 +    h3 { margin:1em 0 .5em 0; }
   38.43 +    h4 { margin:0 0 .5em 0; font-weight: normal; }
   38.44 +    table { 
   38.45 +        border:1px solid #ccc; border-collapse: collapse; background:white; }
   38.46 +    tbody td, tbody th { vertical-align:top; padding:2px 3px; }
   38.47 +    thead th { 
   38.48 +        padding:1px 6px 1px 3px; background:#fefefe; text-align:left; 
   38.49 +        font-weight:normal; font-size:11px; border:1px solid #ddd; }
   38.50 +    tbody th { text-align:right; color:#666; padding-right:.5em; }
   38.51 +    table.vars { margin:5px 0 2px 40px; }
   38.52 +    table.vars td, table.req td { font-family:monospace; }
   38.53 +    table td.code { width:100%;}
   38.54 +    table td.code div { overflow:hidden; }
   38.55 +    table.source th { color:#666; }
   38.56 +    table.source td { 
   38.57 +        font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
   38.58 +    ul.traceback { list-style-type:none; }
   38.59 +    ul.traceback li.frame { margin-bottom:1em; }
   38.60 +    div.context { margin: 10px 0; }
   38.61 +    div.context ol { 
   38.62 +        padding-left:30px; margin:0 10px; list-style-position: inside; }
   38.63 +    div.context ol li { 
   38.64 +        font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
   38.65 +    div.context ol.context-line li { color:black; background-color:#ccc; }
   38.66 +    div.context ol.context-line li span { float: right; }
   38.67 +    div.commands { margin-left: 40px; }
   38.68 +    div.commands a { color:black; text-decoration:none; }
   38.69 +    #summary { background: #ffc; }
   38.70 +    #summary h2 { font-weight: normal; color: #666; }
   38.71 +    #explanation { background:#eee; }
   38.72 +    #template, #template-not-exist { background:#f6f6f6; }
   38.73 +    #template-not-exist ul { margin: 0 0 0 20px; }
   38.74 +    #traceback { background:#eee; }
   38.75 +    #requestinfo { background:#f6f6f6; padding-left:120px; }
   38.76 +    #summary table { border:none; background:transparent; }
   38.77 +    #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
   38.78 +    #requestinfo h3 { margin-bottom:-1em; }
   38.79 +    .error { background: #ffc; }
   38.80 +    .specific { color:#cc3300; font-weight:bold; }
   38.81 +  </style>
   38.82 +  <script type="text/javascript">
   38.83 +  //<!--
   38.84 +    function getElementsByClassName(oElm, strTagName, strClassName){
   38.85 +        // Written by Jonathan Snook, http://www.snook.ca/jon; 
   38.86 +        // Add-ons by Robert Nyman, http://www.robertnyman.com
   38.87 +        var arrElements = (strTagName == "*" && document.all)? document.all :
   38.88 +        oElm.getElementsByTagName(strTagName);
   38.89 +        var arrReturnElements = new Array();
   38.90 +        strClassName = strClassName.replace(/\-/g, "\\-");
   38.91 +        var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
   38.92 +        var oElement;
   38.93 +        for(var i=0; i<arrElements.length; i++){
   38.94 +            oElement = arrElements[i];
   38.95 +            if(oRegExp.test(oElement.className)){
   38.96 +                arrReturnElements.push(oElement);
   38.97 +            }
   38.98 +        }
   38.99 +        return (arrReturnElements)
  38.100 +    }
  38.101 +    function hideAll(elems) {
  38.102 +      for (var e = 0; e < elems.length; e++) {
  38.103 +        elems[e].style.display = 'none';
  38.104 +      }
  38.105 +    }
  38.106 +    window.onload = function() {
  38.107 +      hideAll(getElementsByClassName(document, 'table', 'vars'));
  38.108 +      hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
  38.109 +      hideAll(getElementsByClassName(document, 'ol', 'post-context'));
  38.110 +    }
  38.111 +    function toggle() {
  38.112 +      for (var i = 0; i < arguments.length; i++) {
  38.113 +        var e = document.getElementById(arguments[i]);
  38.114 +        if (e) {
  38.115 +          e.style.display = e.style.display == 'none' ? 'block' : 'none';
  38.116 +        }
  38.117 +      }
  38.118 +      return false;
  38.119 +    }
  38.120 +    function varToggle(link, id) {
  38.121 +      toggle('v' + id);
  38.122 +      var s = link.getElementsByTagName('span')[0];
  38.123 +      var uarr = String.fromCharCode(0x25b6);
  38.124 +      var darr = String.fromCharCode(0x25bc);
  38.125 +      s.innerHTML = s.innerHTML == uarr ? darr : uarr;
  38.126 +      return false;
  38.127 +    }
  38.128 +    //-->
  38.129 +  </script>
  38.130 +</head>
  38.131 +<body>
  38.132 +
  38.133 +$def dicttable (d, kls='req', id=None):
  38.134 +    $ items = d and d.items() or []
  38.135 +    $items.sort()
  38.136 +    $:dicttable_items(items, kls, id)
  38.137 +        
  38.138 +$def dicttable_items(items, kls='req', id=None):
  38.139 +    $if items:
  38.140 +        <table class="$kls"
  38.141 +        $if id: id="$id"
  38.142 +        ><thead><tr><th>Variable</th><th>Value</th></tr></thead>
  38.143 +        <tbody>
  38.144 +        $for k, v in items:
  38.145 +            <tr><td>$k</td><td class="code"><div>$prettify(v)</div></td></tr>
  38.146 +        </tbody>
  38.147 +        </table>
  38.148 +    $else:
  38.149 +        <p>No data.</p>
  38.150 +
  38.151 +<div id="summary">
  38.152 +  <h1>$exception_type at $ctx.path</h1>
  38.153 +  <h2>$exception_value</h2>
  38.154 +  <table><tr>
  38.155 +    <th>Python</th>
  38.156 +    <td>$frames[0].filename in $frames[0].function, line $frames[0].lineno</td>
  38.157 +  </tr><tr>
  38.158 +    <th>Web</th>
  38.159 +    <td>$ctx.method $ctx.home$ctx.path</td>
  38.160 +  </tr></table>
  38.161 +</div>
  38.162 +<div id="traceback">
  38.163 +<h2>Traceback <span>(innermost first)</span></h2>
  38.164 +<ul class="traceback">
  38.165 +$for frame in frames:
  38.166 +    <li class="frame">
  38.167 +    <code>$frame.filename</code> in <code>$frame.function</code>
  38.168 +    $if frame.context_line is not None:
  38.169 +        <div class="context" id="c$frame.id">
  38.170 +        $if frame.pre_context:
  38.171 +            <ol start="$frame.pre_context_lineno" class="pre-context" id="pre$frame.id">
  38.172 +            $for line in frame.pre_context:
  38.173 +                <li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
  38.174 +            </ol>
  38.175 +            <ol start="$frame.lineno" class="context-line"><li onclick="toggle('pre$frame.id', 'post$frame.id')">$frame.context_line <span>...</span></li></ol>
  38.176 +        $if frame.post_context:
  38.177 +            <ol start='${frame.lineno + 1}' class="post-context" id="post$frame.id">
  38.178 +            $for line in frame.post_context:
  38.179 +                <li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
  38.180 +            </ol>
  38.181 +      </div>
  38.182 +    
  38.183 +    $if frame.vars:
  38.184 +        <div class="commands">
  38.185 +        <a href='#' onclick="return varToggle(this, '$frame.id')"><span>&#x25b6;</span> Local vars</a>
  38.186 +        $# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame))
  38.187 +        </div>
  38.188 +        $:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id)))
  38.189 +      </li>
  38.190 +  </ul>
  38.191 +</div>
  38.192 +
  38.193 +<div id="requestinfo">
  38.194 +$if ctx.output or ctx.headers:
  38.195 +    <h2>Response so far</h2>
  38.196 +    <h3>HEADERS</h3>
  38.197 +    $:dicttable_items(ctx.headers)
  38.198 +
  38.199 +    <h3>BODY</h3>
  38.200 +    <p class="req" style="padding-bottom: 2em"><code>
  38.201 +    $ctx.output
  38.202 +    </code></p>
  38.203 +  
  38.204 +<h2>Request information</h2>
  38.205 +
  38.206 +<h3>INPUT</h3>
  38.207 +$:dicttable(web.input(_unicode=False))
  38.208 +
  38.209 +<h3 id="cookie-info">COOKIES</h3>
  38.210 +$:dicttable(web.cookies())
  38.211 +
  38.212 +<h3 id="meta-info">META</h3>
  38.213 +$ newctx = [(k, v) for (k, v) in ctx.iteritems() if not k.startswith('_') and not isinstance(v, dict)]
  38.214 +$:dicttable(dict(newctx))
  38.215 +
  38.216 +<h3 id="meta-info">ENVIRONMENT</h3>
  38.217 +$:dicttable(ctx.env)
  38.218 +</div>
  38.219 +
  38.220 +<div id="explanation">
  38.221 +  <p>
  38.222 +    You're seeing this error because you have <code>web.config.debug</code>
  38.223 +    set to <code>True</code>. Set that to <code>False</code> if you don't want to see this.
  38.224 +  </p>
  38.225 +</div>
  38.226 +
  38.227 +</body>
  38.228 +</html>
  38.229 +"""
  38.230 +
  38.231 +djangoerror_r = None
  38.232 +
  38.233 +def djangoerror():
  38.234 +    def _get_lines_from_file(filename, lineno, context_lines):
  38.235 +        """
  38.236 +        Returns context_lines before and after lineno from file.
  38.237 +        Returns (pre_context_lineno, pre_context, context_line, post_context).
  38.238 +        """
  38.239 +        try:
  38.240 +            source = open(filename).readlines()
  38.241 +            lower_bound = max(0, lineno - context_lines)
  38.242 +            upper_bound = lineno + context_lines
  38.243 +
  38.244 +            pre_context = \
  38.245 +                [line.strip('\n') for line in source[lower_bound:lineno]]
  38.246 +            context_line = source[lineno].strip('\n')
  38.247 +            post_context = \
  38.248 +                [line.strip('\n') for line in source[lineno + 1:upper_bound]]
  38.249 +
  38.250 +            return lower_bound, pre_context, context_line, post_context
  38.251 +        except (OSError, IOError, IndexError):
  38.252 +            return None, [], None, []    
  38.253 +    
  38.254 +    exception_type, exception_value, tback = sys.exc_info()
  38.255 +    frames = []
  38.256 +    while tback is not None:
  38.257 +        filename = tback.tb_frame.f_code.co_filename
  38.258 +        function = tback.tb_frame.f_code.co_name
  38.259 +        lineno = tback.tb_lineno - 1
  38.260 +
  38.261 +        # hack to get correct line number for templates
  38.262 +        lineno += tback.tb_frame.f_locals.get("__lineoffset__", 0)
  38.263 +        
  38.264 +        pre_context_lineno, pre_context, context_line, post_context = \
  38.265 +            _get_lines_from_file(filename, lineno, 7)
  38.266 +
  38.267 +        if '__hidetraceback__' not in tback.tb_frame.f_locals:
  38.268 +            frames.append(web.storage({
  38.269 +                'tback': tback,
  38.270 +                'filename': filename,
  38.271 +                'function': function,
  38.272 +                'lineno': lineno,
  38.273 +                'vars': tback.tb_frame.f_locals,
  38.274 +                'id': id(tback),
  38.275 +                'pre_context': pre_context,
  38.276 +                'context_line': context_line,
  38.277 +                'post_context': post_context,
  38.278 +                'pre_context_lineno': pre_context_lineno,
  38.279 +            }))
  38.280 +        tback = tback.tb_next
  38.281 +    frames.reverse()
  38.282 +    urljoin = urlparse.urljoin
  38.283 +    def prettify(x):
  38.284 +        try: 
  38.285 +            out = pprint.pformat(x)
  38.286 +        except Exception, e: 
  38.287 +            out = '[could not display: <' + e.__class__.__name__ + \
  38.288 +                  ': '+str(e)+'>]'
  38.289 +        return out
  38.290 +        
  38.291 +    global djangoerror_r
  38.292 +    if djangoerror_r is None:
  38.293 +        djangoerror_r = Template(djangoerror_t, filename=__file__, filter=websafe)
  38.294 +        
  38.295 +    t = djangoerror_r
  38.296 +    globals = {'ctx': web.ctx, 'web':web, 'dict':dict, 'str':str, 'prettify': prettify}
  38.297 +    t.t.func_globals.update(globals)
  38.298 +    return t(exception_type, exception_value, frames)
  38.299 +
  38.300 +def debugerror():
  38.301 +    """
  38.302 +    A replacement for `internalerror` that presents a nice page with lots
  38.303 +    of debug information for the programmer.
  38.304 +
  38.305 +    (Based on the beautiful 500 page from [Django](http://djangoproject.com/), 
  38.306 +    designed by [Wilson Miner](http://wilsonminer.com/).)
  38.307 +    """
  38.308 +    return web._InternalError(djangoerror())
  38.309 +
  38.310 +def emailerrors(to_address, olderror, from_address=None):
  38.311 +    """
  38.312 +    Wraps the old `internalerror` handler (pass as `olderror`) to 
  38.313 +    additionally email all errors to `to_address`, to aid in
  38.314 +    debugging production websites.
  38.315 +    
  38.316 +    Emails contain a normal text traceback as well as an
  38.317 +    attachment containing the nice `debugerror` page.
  38.318 +    """
  38.319 +    from_address = from_address or to_address
  38.320 +
  38.321 +    def emailerrors_internal():
  38.322 +        error = olderror()
  38.323 +        tb = sys.exc_info()
  38.324 +        error_name = tb[0]
  38.325 +        error_value = tb[1]
  38.326 +        tb_txt = ''.join(traceback.format_exception(*tb))
  38.327 +        path = web.ctx.path
  38.328 +        request = web.ctx.method + ' ' + web.ctx.home + web.ctx.fullpath
  38.329 +        
  38.330 +        message = "\n%s\n\n%s\n\n" % (request, tb_txt)
  38.331 +        
  38.332 +        sendmail(
  38.333 +            "your buggy site <%s>" % from_address,
  38.334 +            "the bugfixer <%s>" % to_address,
  38.335 +            "bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(),
  38.336 +            message,
  38.337 +            attachments=[
  38.338 +                dict(filename="bug.html", content=safestr(djangoerror()))
  38.339 +            ],
  38.340 +        )
  38.341 +        return error
  38.342 +    
  38.343 +    return emailerrors_internal
  38.344 +
  38.345 +if __name__ == "__main__":
  38.346 +    urls = (
  38.347 +        '/', 'index'
  38.348 +    )
  38.349 +    from application import application
  38.350 +    app = application(urls, globals())
  38.351 +    app.internalerror = debugerror
  38.352 +    
  38.353 +    class index:
  38.354 +        def GET(self):
  38.355 +            thisdoesnotexist
  38.356 +
  38.357 +    app.run()
    39.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    39.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/form.py	Mon Dec 02 14:02:05 2013 +0100
    39.3 @@ -0,0 +1,410 @@
    39.4 +"""
    39.5 +HTML forms
    39.6 +(part of web.py)
    39.7 +"""
    39.8 +
    39.9 +import copy, re
   39.10 +import webapi as web
   39.11 +import utils, net
   39.12 +
   39.13 +def attrget(obj, attr, value=None):
   39.14 +    try:
   39.15 +        if hasattr(obj, 'has_key') and obj.has_key(attr): 
   39.16 +            return obj[attr]
   39.17 +    except TypeError:
   39.18 +        # Handle the case where has_key takes different number of arguments.
   39.19 +        # This is the case with Model objects on appengine. See #134
   39.20 +        pass
   39.21 +    if hasattr(obj, attr):
   39.22 +        return getattr(obj, attr)
   39.23 +    return value
   39.24 +
   39.25 +class Form(object):
   39.26 +    r"""
   39.27 +    HTML form.
   39.28 +    
   39.29 +        >>> f = Form(Textbox("x"))
   39.30 +        >>> f.render()
   39.31 +        u'<table>\n    <tr><th><label for="x">x</label></th><td><input type="text" id="x" name="x"/></td></tr>\n</table>'
   39.32 +    """
   39.33 +    def __init__(self, *inputs, **kw):
   39.34 +        self.inputs = inputs
   39.35 +        self.valid = True
   39.36 +        self.note = None
   39.37 +        self.validators = kw.pop('validators', [])
   39.38 +
   39.39 +    def __call__(self, x=None):
   39.40 +        o = copy.deepcopy(self)
   39.41 +        if x: o.validates(x)
   39.42 +        return o
   39.43 +    
   39.44 +    def render(self):
   39.45 +        out = ''
   39.46 +        out += self.rendernote(self.note)
   39.47 +        out += '<table>\n'
   39.48 +        
   39.49 +        for i in self.inputs:
   39.50 +            html = utils.safeunicode(i.pre) + i.render() + self.rendernote(i.note) + utils.safeunicode(i.post)
   39.51 +            if i.is_hidden():
   39.52 +                out += '    <tr style="display: none;"><th></th><td>%s</td></tr>\n' % (html)
   39.53 +            else:
   39.54 +                out += '    <tr><th><label for="%s">%s</label></th><td>%s</td></tr>\n' % (i.id, net.websafe(i.description), html)
   39.55 +        out += "</table>"
   39.56 +        return out
   39.57 +        
   39.58 +    def render_css(self): 
   39.59 +        out = [] 
   39.60 +        out.append(self.rendernote(self.note)) 
   39.61 +        for i in self.inputs:
   39.62 +            if not i.is_hidden():
   39.63 +                out.append('<label for="%s">%s</label>' % (i.id, net.websafe(i.description))) 
   39.64 +            out.append(i.pre)
   39.65 +            out.append(i.render()) 
   39.66 +            out.append(self.rendernote(i.note))
   39.67 +            out.append(i.post) 
   39.68 +            out.append('\n')
   39.69 +        return ''.join(out) 
   39.70 +        
   39.71 +    def rendernote(self, note):
   39.72 +        if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
   39.73 +        else: return ""
   39.74 +    
   39.75 +    def validates(self, source=None, _validate=True, **kw):
   39.76 +        source = source or kw or web.input()
   39.77 +        out = True
   39.78 +        for i in self.inputs:
   39.79 +            v = attrget(source, i.name)
   39.80 +            if _validate:
   39.81 +                out = i.validate(v) and out
   39.82 +            else:
   39.83 +                i.set_value(v)
   39.84 +        if _validate:
   39.85 +            out = out and self._validate(source)
   39.86 +            self.valid = out
   39.87 +        return out
   39.88 +
   39.89 +    def _validate(self, value):
   39.90 +        self.value = value
   39.91 +        for v in self.validators:
   39.92 +            if not v.valid(value):
   39.93 +                self.note = v.msg
   39.94 +                return False
   39.95 +        return True
   39.96 +
   39.97 +    def fill(self, source=None, **kw):
   39.98 +        return self.validates(source, _validate=False, **kw)
   39.99 +    
  39.100 +    def __getitem__(self, i):
  39.101 +        for x in self.inputs:
  39.102 +            if x.name == i: return x
  39.103 +        raise KeyError, i
  39.104 +
  39.105 +    def __getattr__(self, name):
  39.106 +        # don't interfere with deepcopy
  39.107 +        inputs = self.__dict__.get('inputs') or []
  39.108 +        for x in inputs:
  39.109 +            if x.name == name: return x
  39.110 +        raise AttributeError, name
  39.111 +    
  39.112 +    def get(self, i, default=None):
  39.113 +        try:
  39.114 +            return self[i]
  39.115 +        except KeyError:
  39.116 +            return default
  39.117 +            
  39.118 +    def _get_d(self): #@@ should really be form.attr, no?
  39.119 +        return utils.storage([(i.name, i.get_value()) for i in self.inputs])
  39.120 +    d = property(_get_d)
  39.121 +
  39.122 +class Input(object):
  39.123 +    def __init__(self, name, *validators, **attrs):
  39.124 +        self.name = name
  39.125 +        self.validators = validators
  39.126 +        self.attrs = attrs = AttributeList(attrs)
  39.127 +        
  39.128 +        self.description = attrs.pop('description', name)
  39.129 +        self.value = attrs.pop('value', None)
  39.130 +        self.pre = attrs.pop('pre', "")
  39.131 +        self.post = attrs.pop('post', "")
  39.132 +        self.note = None
  39.133 +        
  39.134 +        self.id = attrs.setdefault('id', self.get_default_id())
  39.135 +        
  39.136 +        if 'class_' in attrs:
  39.137 +            attrs['class'] = attrs['class_']
  39.138 +            del attrs['class_']
  39.139 +        
  39.140 +    def is_hidden(self):
  39.141 +        return False
  39.142 +        
  39.143 +    def get_type(self):
  39.144 +        raise NotImplementedError
  39.145 +        
  39.146 +    def get_default_id(self):
  39.147 +        return self.name
  39.148 +
  39.149 +    def validate(self, value):
  39.150 +        self.set_value(value)
  39.151 +
  39.152 +        for v in self.validators:
  39.153 +            if not v.valid(value):
  39.154 +                self.note = v.msg
  39.155 +                return False
  39.156 +        return True
  39.157 +
  39.158 +    def set_value(self, value):
  39.159 +        self.value = value
  39.160 +
  39.161 +    def get_value(self):
  39.162 +        return self.value
  39.163 +
  39.164 +    def render(self):
  39.165 +        attrs = self.attrs.copy()
  39.166 +        attrs['type'] = self.get_type()
  39.167 +        if self.value is not None:
  39.168 +            attrs['value'] = self.value
  39.169 +        attrs['name'] = self.name
  39.170 +        return '<input %s/>' % attrs
  39.171 +
  39.172 +    def rendernote(self, note):
  39.173 +        if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
  39.174 +        else: return ""
  39.175 +        
  39.176 +    def addatts(self):
  39.177 +        # add leading space for backward-compatibility
  39.178 +        return " " + str(self.attrs)
  39.179 +
  39.180 +class AttributeList(dict):
  39.181 +    """List of atributes of input.
  39.182 +    
  39.183 +    >>> a = AttributeList(type='text', name='x', value=20)
  39.184 +    >>> a
  39.185 +    <attrs: 'type="text" name="x" value="20"'>
  39.186 +    """
  39.187 +    def copy(self):
  39.188 +        return AttributeList(self)
  39.189 +        
  39.190 +    def __str__(self):
  39.191 +        return " ".join(['%s="%s"' % (k, net.websafe(v)) for k, v in self.items()])
  39.192 +        
  39.193 +    def __repr__(self):
  39.194 +        return '<attrs: %s>' % repr(str(self))
  39.195 +
  39.196 +class Textbox(Input):
  39.197 +    """Textbox input.
  39.198 +    
  39.199 +        >>> Textbox(name='foo', value='bar').render()
  39.200 +        u'<input type="text" id="foo" value="bar" name="foo"/>'
  39.201 +        >>> Textbox(name='foo', value=0).render()
  39.202 +        u'<input type="text" id="foo" value="0" name="foo"/>'
  39.203 +    """        
  39.204 +    def get_type(self):
  39.205 +        return 'text'
  39.206 +
  39.207 +class Password(Input):
  39.208 +    """Password input.
  39.209 +
  39.210 +        >>> Password(name='password', value='secret').render()
  39.211 +        u'<input type="password" id="password" value="secret" name="password"/>'
  39.212 +    """
  39.213 +    
  39.214 +    def get_type(self):
  39.215 +        return 'password'
  39.216 +
  39.217 +class Textarea(Input):
  39.218 +    """Textarea input.
  39.219 +    
  39.220 +        >>> Textarea(name='foo', value='bar').render()
  39.221 +        u'<textarea id="foo" name="foo">bar</textarea>'
  39.222 +    """
  39.223 +    def render(self):
  39.224 +        attrs = self.attrs.copy()
  39.225 +        attrs['name'] = self.name
  39.226 +        value = net.websafe(self.value or '')
  39.227 +        return '<textarea %s>%s</textarea>' % (attrs, value)
  39.228 +
  39.229 +class Dropdown(Input):
  39.230 +    r"""Dropdown/select input.
  39.231 +    
  39.232 +        >>> Dropdown(name='foo', args=['a', 'b', 'c'], value='b').render()
  39.233 +        u'<select id="foo" name="foo">\n  <option value="a">a</option>\n  <option selected="selected" value="b">b</option>\n  <option value="c">c</option>\n</select>\n'
  39.234 +        >>> Dropdown(name='foo', args=[('a', 'aa'), ('b', 'bb'), ('c', 'cc')], value='b').render()
  39.235 +        u'<select id="foo" name="foo">\n  <option value="a">aa</option>\n  <option selected="selected" value="b">bb</option>\n  <option value="c">cc</option>\n</select>\n'
  39.236 +    """
  39.237 +    def __init__(self, name, args, *validators, **attrs):
  39.238 +        self.args = args
  39.239 +        super(Dropdown, self).__init__(name, *validators, **attrs)
  39.240 +
  39.241 +    def render(self):
  39.242 +        attrs = self.attrs.copy()
  39.243 +        attrs['name'] = self.name
  39.244 +        
  39.245 +        x = '<select %s>\n' % attrs
  39.246 +        
  39.247 +        for arg in self.args:
  39.248 +            x += self._render_option(arg)
  39.249 +
  39.250 +        x += '</select>\n'
  39.251 +        return x
  39.252 +
  39.253 +    def _render_option(self, arg, indent='  '):
  39.254 +        if isinstance(arg, (tuple, list)):
  39.255 +            value, desc= arg
  39.256 +        else:
  39.257 +            value, desc = arg, arg 
  39.258 +
  39.259 +        if self.value == value or (isinstance(self.value, list) and value in self.value):
  39.260 +            select_p = ' selected="selected"'
  39.261 +        else:
  39.262 +            select_p = ''
  39.263 +        return indent + '<option%s value="%s">%s</option>\n' % (select_p, net.websafe(value), net.websafe(desc))
  39.264 +        
  39.265 +
  39.266 +class GroupedDropdown(Dropdown):
  39.267 +    r"""Grouped Dropdown/select input.
  39.268 +    
  39.269 +        >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', ('Volvo', 'Saab')), ('German Cars', ('Mercedes', 'Audi'))), value='Audi').render()
  39.270 +        u'<select id="car_type" name="car_type">\n  <optgroup label="Swedish Cars">\n    <option value="Volvo">Volvo</option>\n    <option value="Saab">Saab</option>\n  </optgroup>\n  <optgroup label="German Cars">\n    <option value="Mercedes">Mercedes</option>\n    <option selected="selected" value="Audi">Audi</option>\n  </optgroup>\n</select>\n'
  39.271 +        >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', (('v', 'Volvo'), ('s', 'Saab'))), ('German Cars', (('m', 'Mercedes'), ('a', 'Audi')))), value='a').render()
  39.272 +        u'<select id="car_type" name="car_type">\n  <optgroup label="Swedish Cars">\n    <option value="v">Volvo</option>\n    <option value="s">Saab</option>\n  </optgroup>\n  <optgroup label="German Cars">\n    <option value="m">Mercedes</option>\n    <option selected="selected" value="a">Audi</option>\n  </optgroup>\n</select>\n'
  39.273 +
  39.274 +    """
  39.275 +    def __init__(self, name, args, *validators, **attrs):
  39.276 +        self.args = args
  39.277 +        super(Dropdown, self).__init__(name, *validators, **attrs)
  39.278 +
  39.279 +    def render(self):
  39.280 +        attrs = self.attrs.copy()
  39.281 +        attrs['name'] = self.name
  39.282 +        
  39.283 +        x = '<select %s>\n' % attrs
  39.284 +        
  39.285 +        for label, options in self.args:
  39.286 +            x += '  <optgroup label="%s">\n' % net.websafe(label)
  39.287 +            for arg in options:
  39.288 +                x += self._render_option(arg, indent = '    ')
  39.289 +            x +=  '  </optgroup>\n'
  39.290 +            
  39.291 +        x += '</select>\n'
  39.292 +        return x
  39.293 +
  39.294 +class Radio(Input):
  39.295 +    def __init__(self, name, args, *validators, **attrs):
  39.296 +        self.args = args
  39.297 +        super(Radio, self).__init__(name, *validators, **attrs)
  39.298 +
  39.299 +    def render(self):
  39.300 +        x = '<span>'
  39.301 +        for arg in self.args:
  39.302 +            if isinstance(arg, (tuple, list)):
  39.303 +                value, desc= arg
  39.304 +            else:
  39.305 +                value, desc = arg, arg 
  39.306 +            attrs = self.attrs.copy()
  39.307 +            attrs['name'] = self.name
  39.308 +            attrs['type'] = 'radio'
  39.309 +            attrs['value'] = value
  39.310 +            if self.value == value:
  39.311 +                attrs['checked'] = 'checked'
  39.312 +            x += '<input %s/> %s' % (attrs, net.websafe(desc))
  39.313 +        x += '</span>'
  39.314 +        return x
  39.315 +
  39.316 +class Checkbox(Input):
  39.317 +    """Checkbox input.
  39.318 +
  39.319 +    >>> Checkbox('foo', value='bar', checked=True).render()
  39.320 +    u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>'
  39.321 +    >>> Checkbox('foo', value='bar').render()
  39.322 +    u'<input type="checkbox" id="foo_bar" value="bar" name="foo"/>'
  39.323 +    >>> c = Checkbox('foo', value='bar')
  39.324 +    >>> c.validate('on')
  39.325 +    True
  39.326 +    >>> c.render()
  39.327 +    u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>'
  39.328 +    """
  39.329 +    def __init__(self, name, *validators, **attrs):
  39.330 +        self.checked = attrs.pop('checked', False)
  39.331 +        Input.__init__(self, name, *validators, **attrs)
  39.332 +        
  39.333 +    def get_default_id(self):
  39.334 +        value = utils.safestr(self.value or "")
  39.335 +        return self.name + '_' + value.replace(' ', '_')
  39.336 +
  39.337 +    def render(self):
  39.338 +        attrs = self.attrs.copy()
  39.339 +        attrs['type'] = 'checkbox'
  39.340 +        attrs['name'] = self.name
  39.341 +        attrs['value'] = self.value
  39.342 +
  39.343 +        if self.checked:
  39.344 +            attrs['checked'] = 'checked'            
  39.345 +        return '<input %s/>' % attrs
  39.346 +
  39.347 +    def set_value(self, value):
  39.348 +        self.checked = bool(value)
  39.349 +
  39.350 +    def get_value(self):
  39.351 +        return self.checked
  39.352 +
  39.353 +class Button(Input):
  39.354 +    """HTML Button.
  39.355 +    
  39.356 +    >>> Button("save").render()
  39.357 +    u'<button id="save" name="save">save</button>'
  39.358 +    >>> Button("action", value="save", html="<b>Save Changes</b>").render()
  39.359 +    u'<button id="action" value="save" name="action"><b>Save Changes</b></button>'
  39.360 +    """
  39.361 +    def __init__(self, name, *validators, **attrs):
  39.362 +        super(Button, self).__init__(name, *validators, **attrs)
  39.363 +        self.description = ""
  39.364 +
  39.365 +    def render(self):
  39.366 +        attrs = self.attrs.copy()
  39.367 +        attrs['name'] = self.name
  39.368 +        if self.value is not None:
  39.369 +            attrs['value'] = self.value
  39.370 +        html = attrs.pop('html', None) or net.websafe(self.name)
  39.371 +        return '<button %s>%s</button>' % (attrs, html)
  39.372 +
  39.373 +class Hidden(Input):
  39.374 +    """Hidden Input.
  39.375 +    
  39.376 +        >>> Hidden(name='foo', value='bar').render()
  39.377 +        u'<input type="hidden" id="foo" value="bar" name="foo"/>'
  39.378 +    """
  39.379 +    def is_hidden(self):
  39.380 +        return True
  39.381 +        
  39.382 +    def get_type(self):
  39.383 +        return 'hidden'
  39.384 +
  39.385 +class File(Input):
  39.386 +    """File input.
  39.387 +    
  39.388 +        >>> File(name='f').render()
  39.389 +        u'<input type="file" id="f" name="f"/>'
  39.390 +    """
  39.391 +    def get_type(self):
  39.392 +        return 'file'
  39.393 +    
  39.394 +class Validator:
  39.395 +    def __deepcopy__(self, memo): return copy.copy(self)
  39.396 +    def __init__(self, msg, test, jstest=None): utils.autoassign(self, locals())
  39.397 +    def valid(self, value): 
  39.398 +        try: return self.test(value)
  39.399 +        except: return False
  39.400 +
  39.401 +notnull = Validator("Required", bool)
  39.402 +
  39.403 +class regexp(Validator):
  39.404 +    def __init__(self, rexp, msg):
  39.405 +        self.rexp = re.compile(rexp)
  39.406 +        self.msg = msg
  39.407 +    
  39.408 +    def valid(self, value):
  39.409 +        return bool(self.rexp.match(value))
  39.410 +
  39.411 +if __name__ == "__main__":
  39.412 +    import doctest
  39.413 +    doctest.testmod()
    40.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    40.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/http.py	Mon Dec 02 14:02:05 2013 +0100
    40.3 @@ -0,0 +1,150 @@
    40.4 +"""
    40.5 +HTTP Utilities
    40.6 +(from web.py)
    40.7 +"""
    40.8 +
    40.9 +__all__ = [
   40.10 +  "expires", "lastmodified", 
   40.11 +  "prefixurl", "modified", 
   40.12 +  "changequery", "url",
   40.13 +  "profiler",
   40.14 +]
   40.15 +
   40.16 +import sys, os, threading, urllib, urlparse
   40.17 +try: import datetime
   40.18 +except ImportError: pass
   40.19 +import net, utils, webapi as web
   40.20 +
   40.21 +def prefixurl(base=''):
   40.22 +    """
   40.23 +    Sorry, this function is really difficult to explain.
   40.24 +    Maybe some other time.
   40.25 +    """
   40.26 +    url = web.ctx.path.lstrip('/')
   40.27 +    for i in xrange(url.count('/')): 
   40.28 +        base += '../'
   40.29 +    if not base: 
   40.30 +        base = './'
   40.31 +    return base
   40.32 +
   40.33 +def expires(delta):
   40.34 +    """
   40.35 +    Outputs an `Expires` header for `delta` from now. 
   40.36 +    `delta` is a `timedelta` object or a number of seconds.
   40.37 +    """
   40.38 +    if isinstance(delta, (int, long)):
   40.39 +        delta = datetime.timedelta(seconds=delta)
   40.40 +    date_obj = datetime.datetime.utcnow() + delta
   40.41 +    web.header('Expires', net.httpdate(date_obj))
   40.42 +
   40.43 +def lastmodified(date_obj):
   40.44 +    """Outputs a `Last-Modified` header for `datetime`."""
   40.45 +    web.header('Last-Modified', net.httpdate(date_obj))
   40.46 +
   40.47 +def modified(date=None, etag=None):
   40.48 +    """
   40.49 +    Checks to see if the page has been modified since the version in the
   40.50 +    requester's cache.
   40.51 +    
   40.52 +    When you publish pages, you can include `Last-Modified` and `ETag`
   40.53 +    with the date the page was last modified and an opaque token for
   40.54 +    the particular version, respectively. When readers reload the page, 
   40.55 +    the browser sends along the modification date and etag value for
   40.56 +    the version it has in its cache. If the page hasn't changed, 
   40.57 +    the server can just return `304 Not Modified` and not have to 
   40.58 +    send the whole page again.
   40.59 +    
   40.60 +    This function takes the last-modified date `date` and the ETag `etag`
   40.61 +    and checks the headers to see if they match. If they do, it returns 
   40.62 +    `True`, or otherwise it raises NotModified error. It also sets 
   40.63 +    `Last-Modified` and `ETag` output headers.
   40.64 +    """
   40.65 +    try:
   40.66 +        from __builtin__ import set
   40.67 +    except ImportError:
   40.68 +        # for python 2.3
   40.69 +        from sets import Set as set
   40.70 +
   40.71 +    n = set([x.strip('" ') for x in web.ctx.env.get('HTTP_IF_NONE_MATCH', '').split(',')])
   40.72 +    m = net.parsehttpdate(web.ctx.env.get('HTTP_IF_MODIFIED_SINCE', '').split(';')[0])
   40.73 +    validate = False
   40.74 +    if etag:
   40.75 +        if '*' in n or etag in n:
   40.76 +            validate = True
   40.77 +    if date and m:
   40.78 +        # we subtract a second because 
   40.79 +        # HTTP dates don't have sub-second precision
   40.80 +        if date-datetime.timedelta(seconds=1) <= m:
   40.81 +            validate = True
   40.82 +    
   40.83 +    if date: lastmodified(date)
   40.84 +    if etag: web.header('ETag', '"' + etag + '"')
   40.85 +    if validate:
   40.86 +        raise web.notmodified()
   40.87 +    else:
   40.88 +        return True
   40.89 +
   40.90 +def urlencode(query, doseq=0):
   40.91 +    """
   40.92 +    Same as urllib.urlencode, but supports unicode strings.
   40.93 +    
   40.94 +        >>> urlencode({'text':'foo bar'})
   40.95 +        'text=foo+bar'
   40.96 +        >>> urlencode({'x': [1, 2]}, doseq=True)
   40.97 +        'x=1&x=2'
   40.98 +    """
   40.99 +    def convert(value, doseq=False):
  40.100 +        if doseq and isinstance(value, list):
  40.101 +            return [convert(v) for v in value]
  40.102 +        else:
  40.103 +            return utils.safestr(value)
  40.104 +        
  40.105 +    query = dict([(k, convert(v, doseq)) for k, v in query.items()])
  40.106 +    return urllib.urlencode(query, doseq=doseq)
  40.107 +
  40.108 +def changequery(query=None, **kw):
  40.109 +    """
  40.110 +    Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return
  40.111 +    `/foo?a=3&b=2` -- the same URL but with the arguments you requested
  40.112 +    changed.
  40.113 +    """
  40.114 +    if query is None:
  40.115 +        query = web.rawinput(method='get')
  40.116 +    for k, v in kw.iteritems():
  40.117 +        if v is None:
  40.118 +            query.pop(k, None)
  40.119 +        else:
  40.120 +            query[k] = v
  40.121 +    out = web.ctx.path
  40.122 +    if query:
  40.123 +        out += '?' + urlencode(query, doseq=True)
  40.124 +    return out
  40.125 +
  40.126 +def url(path=None, doseq=False, **kw):
  40.127 +    """
  40.128 +    Makes url by concatenating web.ctx.homepath and path and the 
  40.129 +    query string created using the arguments.
  40.130 +    """
  40.131 +    if path is None:
  40.132 +        path = web.ctx.path
  40.133 +    if path.startswith("/"):
  40.134 +        out = web.ctx.homepath + path
  40.135 +    else:
  40.136 +        out = path
  40.137 +
  40.138 +    if kw:
  40.139 +        out += '?' + urlencode(kw, doseq=doseq)
  40.140 +    
  40.141 +    return out
  40.142 +
  40.143 +def profiler(app):
  40.144 +    """Outputs basic profiling information at the bottom of each response."""
  40.145 +    from utils import profile
  40.146 +    def profile_internal(e, o):
  40.147 +        out, result = profile(app)(e, o)
  40.148 +        return list(out) + ['<pre>' + net.websafe(result) + '</pre>']
  40.149 +    return profile_internal
  40.150 +
  40.151 +if __name__ == "__main__":
  40.152 +    import doctest
  40.153 +    doctest.testmod()
    41.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    41.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/httpserver.py	Mon Dec 02 14:02:05 2013 +0100
    41.3 @@ -0,0 +1,319 @@
    41.4 +__all__ = ["runsimple"]
    41.5 +
    41.6 +import sys, os
    41.7 +from SimpleHTTPServer import SimpleHTTPRequestHandler
    41.8 +import urllib
    41.9 +import posixpath
   41.10 +
   41.11 +import webapi as web
   41.12 +import net
   41.13 +import utils
   41.14 +
   41.15 +def runbasic(func, server_address=("0.0.0.0", 8080)):
   41.16 +    """
   41.17 +    Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` 
   41.18 +    is hosted statically.
   41.19 +
   41.20 +    Based on [WsgiServer][ws] from [Colin Stewart][cs].
   41.21 +    
   41.22 +  [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
   41.23 +  [cs]: http://www.owlfish.com/
   41.24 +    """
   41.25 +    # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
   41.26 +    # Modified somewhat for simplicity
   41.27 +    # Used under the modified BSD license:
   41.28 +    # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
   41.29 +
   41.30 +    import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
   41.31 +    import socket, errno
   41.32 +    import traceback
   41.33 +
   41.34 +    class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
   41.35 +        def run_wsgi_app(self):
   41.36 +            protocol, host, path, parameters, query, fragment = \
   41.37 +                urlparse.urlparse('http://dummyhost%s' % self.path)
   41.38 +
   41.39 +            # we only use path, query
   41.40 +            env = {'wsgi.version': (1, 0)
   41.41 +                   ,'wsgi.url_scheme': 'http'
   41.42 +                   ,'wsgi.input': self.rfile
   41.43 +                   ,'wsgi.errors': sys.stderr
   41.44 +                   ,'wsgi.multithread': 1
   41.45 +                   ,'wsgi.multiprocess': 0
   41.46 +                   ,'wsgi.run_once': 0
   41.47 +                   ,'REQUEST_METHOD': self.command
   41.48 +                   ,'REQUEST_URI': self.path
   41.49 +                   ,'PATH_INFO': path
   41.50 +                   ,'QUERY_STRING': query
   41.51 +                   ,'CONTENT_TYPE': self.headers.get('Content-Type', '')
   41.52 +                   ,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
   41.53 +                   ,'REMOTE_ADDR': self.client_address[0]
   41.54 +                   ,'SERVER_NAME': self.server.server_address[0]
   41.55 +                   ,'SERVER_PORT': str(self.server.server_address[1])
   41.56 +                   ,'SERVER_PROTOCOL': self.request_version
   41.57 +                   }
   41.58 +
   41.59 +            for http_header, http_value in self.headers.items():
   41.60 +                env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
   41.61 +                    http_value
   41.62 +
   41.63 +            # Setup the state
   41.64 +            self.wsgi_sent_headers = 0
   41.65 +            self.wsgi_headers = []
   41.66 +
   41.67 +            try:
   41.68 +                # We have there environment, now invoke the application
   41.69 +                result = self.server.app(env, self.wsgi_start_response)
   41.70 +                try:
   41.71 +                    try:
   41.72 +                        for data in result:
   41.73 +                            if data: 
   41.74 +                                self.wsgi_write_data(data)
   41.75 +                    finally:
   41.76 +                        if hasattr(result, 'close'): 
   41.77 +                            result.close()
   41.78 +                except socket.error, socket_err:
   41.79 +                    # Catch common network errors and suppress them
   41.80 +                    if (socket_err.args[0] in \
   41.81 +                       (errno.ECONNABORTED, errno.EPIPE)): 
   41.82 +                        return
   41.83 +                except socket.timeout, socket_timeout: 
   41.84 +                    return
   41.85 +            except:
   41.86 +                print >> web.debug, traceback.format_exc(),
   41.87 +
   41.88 +            if (not self.wsgi_sent_headers):
   41.89 +                # We must write out something!
   41.90 +                self.wsgi_write_data(" ")
   41.91 +            return
   41.92 +
   41.93 +        do_POST = run_wsgi_app
   41.94 +        do_PUT = run_wsgi_app
   41.95 +        do_DELETE = run_wsgi_app
   41.96 +
   41.97 +        def do_GET(self):
   41.98 +            if self.path.startswith('/static/'):
   41.99 +                SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
  41.100 +            else:
  41.101 +                self.run_wsgi_app()
  41.102 +
  41.103 +        def wsgi_start_response(self, response_status, response_headers, 
  41.104 +                              exc_info=None):
  41.105 +            if (self.wsgi_sent_headers):
  41.106 +                raise Exception \
  41.107 +                      ("Headers already sent and start_response called again!")
  41.108 +            # Should really take a copy to avoid changes in the application....
  41.109 +            self.wsgi_headers = (response_status, response_headers)
  41.110 +            return self.wsgi_write_data
  41.111 +
  41.112 +        def wsgi_write_data(self, data):
  41.113 +            if (not self.wsgi_sent_headers):
  41.114 +                status, headers = self.wsgi_headers
  41.115 +                # Need to send header prior to data
  41.116 +                status_code = status[:status.find(' ')]
  41.117 +                status_msg = status[status.find(' ') + 1:]
  41.118 +                self.send_response(int(status_code), status_msg)
  41.119 +                for header, value in headers:
  41.120 +                    self.send_header(header, value)
  41.121 +                self.end_headers()
  41.122 +                self.wsgi_sent_headers = 1
  41.123 +            # Send the data
  41.124 +            self.wfile.write(data)
  41.125 +
  41.126 +    class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
  41.127 +        def __init__(self, func, server_address):
  41.128 +            BaseHTTPServer.HTTPServer.__init__(self, 
  41.129 +                                               server_address, 
  41.130 +                                               WSGIHandler)
  41.131 +            self.app = func
  41.132 +            self.serverShuttingDown = 0
  41.133 +
  41.134 +    print "http://%s:%d/" % server_address
  41.135 +    WSGIServer(func, server_address).serve_forever()
  41.136 +
  41.137 +# The WSGIServer instance. 
  41.138 +# Made global so that it can be stopped in embedded mode.
  41.139 +server = None
  41.140 +
  41.141 +def runsimple(func, server_address=("0.0.0.0", 8080)):
  41.142 +    """
  41.143 +    Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. 
  41.144 +    The directory `static/` is hosted statically.
  41.145 +
  41.146 +    [cp]: http://www.cherrypy.org
  41.147 +    """
  41.148 +    global server
  41.149 +    func = StaticMiddleware(func)
  41.150 +    func = LogMiddleware(func)
  41.151 +    
  41.152 +    server = WSGIServer(server_address, func)
  41.153 +
  41.154 +    if server.ssl_adapter:
  41.155 +        print "https://%s:%d/" % server_address
  41.156 +    else:
  41.157 +        print "http://%s:%d/" % server_address
  41.158 +
  41.159 +    try:
  41.160 +        server.start()
  41.161 +    except (KeyboardInterrupt, SystemExit):
  41.162 +        server.stop()
  41.163 +        server = None
  41.164 +
  41.165 +def WSGIServer(server_address, wsgi_app):
  41.166 +    """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`.
  41.167 +    This function can be overwritten to customize the webserver or use a different webserver.
  41.168 +    """
  41.169 +    import wsgiserver
  41.170 +    
  41.171 +    # Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver
  41.172 +    # prefix. Overwriting it make it work with web.wsgiserver.
  41.173 +    wsgiserver.ssl_adapters = {
  41.174 +        'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
  41.175 +        'pyopenssl': 'web.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
  41.176 +    }
  41.177 +    
  41.178 +    server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
  41.179 +        
  41.180 +    def create_ssl_adapter(cert, key):
  41.181 +        # wsgiserver tries to import submodules as cherrypy.wsgiserver.foo.
  41.182 +        # That doesn't work as not it is web.wsgiserver. 
  41.183 +        # Patching sys.modules temporarily to make it work.
  41.184 +        import types
  41.185 +        cherrypy = types.ModuleType('cherrypy')
  41.186 +        cherrypy.wsgiserver = wsgiserver
  41.187 +        sys.modules['cherrypy'] = cherrypy
  41.188 +        sys.modules['cherrypy.wsgiserver'] = wsgiserver
  41.189 +        
  41.190 +        from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
  41.191 +        adapter = pyOpenSSLAdapter(cert, key)
  41.192 +        
  41.193 +        # We are done with our work. Cleanup the patches.
  41.194 +        del sys.modules['cherrypy']
  41.195 +        del sys.modules['cherrypy.wsgiserver']
  41.196 +
  41.197 +        return adapter
  41.198 +
  41.199 +    # SSL backward compatibility
  41.200 +    if (server.ssl_adapter is None and
  41.201 +        getattr(server, 'ssl_certificate', None) and
  41.202 +        getattr(server, 'ssl_private_key', None)):
  41.203 +        server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, server.ssl_private_key)
  41.204 +
  41.205 +    server.nodelay = not sys.platform.startswith('java') # TCP_NODELAY isn't supported on the JVM
  41.206 +    return server
  41.207 +
  41.208 +class StaticApp(SimpleHTTPRequestHandler):
  41.209 +    """WSGI application for serving static files."""
  41.210 +    def __init__(self, environ, start_response):
  41.211 +        self.headers = []
  41.212 +        self.environ = environ
  41.213 +        self.start_response = start_response
  41.214 +
  41.215 +    def send_response(self, status, msg=""):
  41.216 +        self.status = str(status) + " " + msg
  41.217 +
  41.218 +    def send_header(self, name, value):
  41.219 +        self.headers.append((name, value))
  41.220 +
  41.221 +    def end_headers(self):
  41.222 +        pass
  41.223 +
  41.224 +    def log_message(*a): pass
  41.225 +
  41.226 +    def __iter__(self):
  41.227 +        environ = self.environ
  41.228 +
  41.229 +        self.path = environ.get('PATH_INFO', '')
  41.230 +        self.client_address = environ.get('REMOTE_ADDR','-'), \
  41.231 +                              environ.get('REMOTE_PORT','-')
  41.232 +        self.command = environ.get('REQUEST_METHOD', '-')
  41.233 +
  41.234 +        from cStringIO import StringIO
  41.235 +        self.wfile = StringIO() # for capturing error
  41.236 +
  41.237 +        try:
  41.238 +            path = self.translate_path(self.path)
  41.239 +            etag = '"%s"' % os.path.getmtime(path)
  41.240 +            client_etag = environ.get('HTTP_IF_NONE_MATCH')
  41.241 +            self.send_header('ETag', etag)
  41.242 +            if etag == client_etag:
  41.243 +                self.send_response(304, "Not Modified")
  41.244 +                self.start_response(self.status, self.headers)
  41.245 +                raise StopIteration
  41.246 +        except OSError:
  41.247 +            pass # Probably a 404
  41.248 +
  41.249 +        f = self.send_head()
  41.250 +        self.start_response(self.status, self.headers)
  41.251 +
  41.252 +        if f:
  41.253 +            block_size = 16 * 1024
  41.254 +            while True:
  41.255 +                buf = f.read(block_size)
  41.256 +                if not buf:
  41.257 +                    break
  41.258 +                yield buf
  41.259 +            f.close()
  41.260 +        else:
  41.261 +            value = self.wfile.getvalue()
  41.262 +            yield value
  41.263 +
  41.264 +class StaticMiddleware:
  41.265 +    """WSGI middleware for serving static files."""
  41.266 +    def __init__(self, app, prefix='/static/'):
  41.267 +        self.app = app
  41.268 +        self.prefix = prefix
  41.269 +        
  41.270 +    def __call__(self, environ, start_response):
  41.271 +        path = environ.get('PATH_INFO', '')
  41.272 +        path = self.normpath(path)
  41.273 +
  41.274 +        if path.startswith(self.prefix):
  41.275 +            return StaticApp(environ, start_response)
  41.276 +        else:
  41.277 +            return self.app(environ, start_response)
  41.278 +
  41.279 +    def normpath(self, path):
  41.280 +        path2 = posixpath.normpath(urllib.unquote(path))
  41.281 +        if path.endswith("/"):
  41.282 +            path2 += "/"
  41.283 +        return path2
  41.284 +
  41.285 +    
  41.286 +class LogMiddleware:
  41.287 +    """WSGI middleware for logging the status."""
  41.288 +    def __init__(self, app):
  41.289 +        self.app = app
  41.290 +        self.format = '%s - - [%s] "%s %s %s" - %s'
  41.291 +    
  41.292 +        from BaseHTTPServer import BaseHTTPRequestHandler
  41.293 +        import StringIO
  41.294 +        f = StringIO.StringIO()
  41.295 +        
  41.296 +        class FakeSocket:
  41.297 +            def makefile(self, *a):
  41.298 +                return f
  41.299 +        
  41.300 +        # take log_date_time_string method from BaseHTTPRequestHandler
  41.301 +        self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string
  41.302 +        
  41.303 +    def __call__(self, environ, start_response):
  41.304 +        def xstart_response(status, response_headers, *args):
  41.305 +            out = start_response(status, response_headers, *args)
  41.306 +            self.log(status, environ)
  41.307 +            return out
  41.308 +
  41.309 +        return self.app(environ, xstart_response)
  41.310 +             
  41.311 +    def log(self, status, environ):
  41.312 +        outfile = environ.get('wsgi.errors', web.debug)
  41.313 +        req = environ.get('PATH_INFO', '_')
  41.314 +        protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
  41.315 +        method = environ.get('REQUEST_METHOD', '-')
  41.316 +        host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), 
  41.317 +                          environ.get('REMOTE_PORT','-'))
  41.318 +
  41.319 +        time = self.log_date_time_string()
  41.320 +
  41.321 +        msg = self.format % (host, time, protocol, method, req, status)
  41.322 +        print >> outfile, utils.safestr(msg)
    42.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    42.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/net.py	Mon Dec 02 14:02:05 2013 +0100
    42.3 @@ -0,0 +1,193 @@
    42.4 +"""
    42.5 +Network Utilities
    42.6 +(from web.py)
    42.7 +"""
    42.8 +
    42.9 +__all__ = [
   42.10 +  "validipaddr", "validipport", "validip", "validaddr", 
   42.11 +  "urlquote",
   42.12 +  "httpdate", "parsehttpdate", 
   42.13 +  "htmlquote", "htmlunquote", "websafe",
   42.14 +]
   42.15 +
   42.16 +import urllib, time
   42.17 +try: import datetime
   42.18 +except ImportError: pass
   42.19 +
   42.20 +def validipaddr(address):
   42.21 +    """
   42.22 +    Returns True if `address` is a valid IPv4 address.
   42.23 +    
   42.24 +        >>> validipaddr('192.168.1.1')
   42.25 +        True
   42.26 +        >>> validipaddr('192.168.1.800')
   42.27 +        False
   42.28 +        >>> validipaddr('192.168.1')
   42.29 +        False
   42.30 +    """
   42.31 +    try:
   42.32 +        octets = address.split('.')
   42.33 +        if len(octets) != 4:
   42.34 +            return False
   42.35 +        for x in octets:
   42.36 +            if not (0 <= int(x) <= 255):
   42.37 +                return False
   42.38 +    except ValueError:
   42.39 +        return False
   42.40 +    return True
   42.41 +
   42.42 +def validipport(port):
   42.43 +    """
   42.44 +    Returns True if `port` is a valid IPv4 port.
   42.45 +    
   42.46 +        >>> validipport('9000')
   42.47 +        True
   42.48 +        >>> validipport('foo')
   42.49 +        False
   42.50 +        >>> validipport('1000000')
   42.51 +        False
   42.52 +    """
   42.53 +    try:
   42.54 +        if not (0 <= int(port) <= 65535):
   42.55 +            return False
   42.56 +    except ValueError:
   42.57 +        return False
   42.58 +    return True
   42.59 +
   42.60 +def validip(ip, defaultaddr="0.0.0.0", defaultport=8080):
   42.61 +    """Returns `(ip_address, port)` from string `ip_addr_port`"""
   42.62 +    addr = defaultaddr
   42.63 +    port = defaultport
   42.64 +    
   42.65 +    ip = ip.split(":", 1)
   42.66 +    if len(ip) == 1:
   42.67 +        if not ip[0]:
   42.68 +            pass
   42.69 +        elif validipaddr(ip[0]):
   42.70 +            addr = ip[0]
   42.71 +        elif validipport(ip[0]):
   42.72 +            port = int(ip[0])
   42.73 +        else:
   42.74 +            raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
   42.75 +    elif len(ip) == 2:
   42.76 +        addr, port = ip
   42.77 +        if not validipaddr(addr) and validipport(port):
   42.78 +            raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
   42.79 +        port = int(port)
   42.80 +    else:
   42.81 +        raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
   42.82 +    return (addr, port)
   42.83 +
   42.84 +def validaddr(string_):
   42.85 +    """
   42.86 +    Returns either (ip_address, port) or "/path/to/socket" from string_
   42.87 +    
   42.88 +        >>> validaddr('/path/to/socket')
   42.89 +        '/path/to/socket'
   42.90 +        >>> validaddr('8000')
   42.91 +        ('0.0.0.0', 8000)
   42.92 +        >>> validaddr('127.0.0.1')
   42.93 +        ('127.0.0.1', 8080)
   42.94 +        >>> validaddr('127.0.0.1:8000')
   42.95 +        ('127.0.0.1', 8000)
   42.96 +        >>> validaddr('fff')
   42.97 +        Traceback (most recent call last):
   42.98 +            ...
   42.99 +        ValueError: fff is not a valid IP address/port
  42.100 +    """
  42.101 +    if '/' in string_:
  42.102 +        return string_
  42.103 +    else:
  42.104 +        return validip(string_)
  42.105 +
  42.106 +def urlquote(val):
  42.107 +    """
  42.108 +    Quotes a string for use in a URL.
  42.109 +    
  42.110 +        >>> urlquote('://?f=1&j=1')
  42.111 +        '%3A//%3Ff%3D1%26j%3D1'
  42.112 +        >>> urlquote(None)
  42.113 +        ''
  42.114 +        >>> urlquote(u'\u203d')
  42.115 +        '%E2%80%BD'
  42.116 +    """
  42.117 +    if val is None: return ''
  42.118 +    if not isinstance(val, unicode): val = str(val)
  42.119 +    else: val = val.encode('utf-8')
  42.120 +    return urllib.quote(val)
  42.121 +
  42.122 +def httpdate(date_obj):
  42.123 +    """
  42.124 +    Formats a datetime object for use in HTTP headers.
  42.125 +    
  42.126 +        >>> import datetime
  42.127 +        >>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1))
  42.128 +        'Thu, 01 Jan 1970 01:01:01 GMT'
  42.129 +    """
  42.130 +    return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT")
  42.131 +
  42.132 +def parsehttpdate(string_):
  42.133 +    """
  42.134 +    Parses an HTTP date into a datetime object.
  42.135 +
  42.136 +        >>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT')
  42.137 +        datetime.datetime(1970, 1, 1, 1, 1, 1)
  42.138 +    """
  42.139 +    try:
  42.140 +        t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z")
  42.141 +    except ValueError:
  42.142 +        return None
  42.143 +    return datetime.datetime(*t[:6])
  42.144 +
  42.145 +def htmlquote(text):
  42.146 +    r"""
  42.147 +    Encodes `text` for raw use in HTML.
  42.148 +    
  42.149 +        >>> htmlquote(u"<'&\">")
  42.150 +        u'&lt;&#39;&amp;&quot;&gt;'
  42.151 +    """
  42.152 +    text = text.replace(u"&", u"&amp;") # Must be done first!
  42.153 +    text = text.replace(u"<", u"&lt;")
  42.154 +    text = text.replace(u">", u"&gt;")
  42.155 +    text = text.replace(u"'", u"&#39;")
  42.156 +    text = text.replace(u'"', u"&quot;")
  42.157 +    return text
  42.158 +
  42.159 +def htmlunquote(text):
  42.160 +    r"""
  42.161 +    Decodes `text` that's HTML quoted.
  42.162 +
  42.163 +        >>> htmlunquote(u'&lt;&#39;&amp;&quot;&gt;')
  42.164 +        u'<\'&">'
  42.165 +    """
  42.166 +    text = text.replace(u"&quot;", u'"')
  42.167 +    text = text.replace(u"&#39;", u"'")
  42.168 +    text = text.replace(u"&gt;", u">")
  42.169 +    text = text.replace(u"&lt;", u"<")
  42.170 +    text = text.replace(u"&amp;", u"&") # Must be done last!
  42.171 +    return text
  42.172 +    
  42.173 +def websafe(val):
  42.174 +    r"""Converts `val` so that it is safe for use in Unicode HTML.
  42.175 +
  42.176 +        >>> websafe("<'&\">")
  42.177 +        u'&lt;&#39;&amp;&quot;&gt;'
  42.178 +        >>> websafe(None)
  42.179 +        u''
  42.180 +        >>> websafe(u'\u203d')
  42.181 +        u'\u203d'
  42.182 +        >>> websafe('\xe2\x80\xbd')
  42.183 +        u'\u203d'
  42.184 +    """
  42.185 +    if val is None:
  42.186 +        return u''
  42.187 +    elif isinstance(val, str):
  42.188 +        val = val.decode('utf-8')
  42.189 +    elif not isinstance(val, unicode):
  42.190 +        val = unicode(val)
  42.191 +        
  42.192 +    return htmlquote(val)
  42.193 +
  42.194 +if __name__ == "__main__":
  42.195 +    import doctest
  42.196 +    doctest.testmod()
    43.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    43.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/python23.py	Mon Dec 02 14:02:05 2013 +0100
    43.3 @@ -0,0 +1,46 @@
    43.4 +"""Python 2.3 compatabilty"""
    43.5 +import threading
    43.6 +
    43.7 +class threadlocal(object):
    43.8 +    """Implementation of threading.local for python2.3.
    43.9 +    """
   43.10 +    def __getattribute__(self, name):
   43.11 +        if name == "__dict__":
   43.12 +            return threadlocal._getd(self)
   43.13 +        else:
   43.14 +            try:
   43.15 +                return object.__getattribute__(self, name)
   43.16 +            except AttributeError:
   43.17 +                try:
   43.18 +                    return self.__dict__[name]
   43.19 +                except KeyError:
   43.20 +                    raise AttributeError, name
   43.21 +            
   43.22 +    def __setattr__(self, name, value):
   43.23 +        self.__dict__[name] = value
   43.24 +        
   43.25 +    def __delattr__(self, name):
   43.26 +        try:
   43.27 +            del self.__dict__[name]
   43.28 +        except KeyError:
   43.29 +            raise AttributeError, name
   43.30 +    
   43.31 +    def _getd(self):
   43.32 +        t = threading.currentThread()
   43.33 +        if not hasattr(t, '_d'):
   43.34 +            # using __dict__ of thread as thread local storage
   43.35 +            t._d = {}
   43.36 +        
   43.37 +        _id = id(self)
   43.38 +        # there could be multiple instances of threadlocal.
   43.39 +        # use id(self) as key
   43.40 +        if _id not in t._d:
   43.41 +            t._d[_id] = {}
   43.42 +        return t._d[_id]
   43.43 +        
   43.44 +if __name__ == '__main__':
   43.45 +     d = threadlocal()
   43.46 +     d.x = 1
   43.47 +     print d.__dict__
   43.48 +     print d.x
   43.49 +     
   43.50 \ No newline at end of file
    44.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    44.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/session.py	Mon Dec 02 14:02:05 2013 +0100
    44.3 @@ -0,0 +1,358 @@
    44.4 +"""
    44.5 +Session Management
    44.6 +(from web.py)
    44.7 +"""
    44.8 +
    44.9 +import os, time, datetime, random, base64
   44.10 +import os.path
   44.11 +from copy import deepcopy
   44.12 +try:
   44.13 +    import cPickle as pickle
   44.14 +except ImportError:
   44.15 +    import pickle
   44.16 +try:
   44.17 +    import hashlib
   44.18 +    sha1 = hashlib.sha1
   44.19 +except ImportError:
   44.20 +    import sha
   44.21 +    sha1 = sha.new
   44.22 +
   44.23 +import utils
   44.24 +import webapi as web
   44.25 +
   44.26 +__all__ = [
   44.27 +    'Session', 'SessionExpired',
   44.28 +    'Store', 'DiskStore', 'DBStore',
   44.29 +]
   44.30 +
   44.31 +web.config.session_parameters = utils.storage({
   44.32 +    'cookie_name': 'webpy_session_id',
   44.33 +    'cookie_domain': None,
   44.34 +    'cookie_path' : None,
   44.35 +    'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds
   44.36 +    'ignore_expiry': True,
   44.37 +    'ignore_change_ip': True,
   44.38 +    'secret_key': 'fLjUfxqXtfNoIldA0A0J',
   44.39 +    'expired_message': 'Session expired',
   44.40 +    'httponly': True,
   44.41 +    'secure': False
   44.42 +})
   44.43 +
   44.44 +class SessionExpired(web.HTTPError): 
   44.45 +    def __init__(self, message):
   44.46 +        web.HTTPError.__init__(self, '200 OK', {}, data=message)
   44.47 +
   44.48 +class Session(object):
   44.49 +    """Session management for web.py
   44.50 +    """
   44.51 +    __slots__ = [
   44.52 +        "store", "_initializer", "_last_cleanup_time", "_config", "_data", 
   44.53 +        "__getitem__", "__setitem__", "__delitem__"
   44.54 +    ]
   44.55 +
   44.56 +    def __init__(self, app, store, initializer=None):
   44.57 +        self.store = store
   44.58 +        self._initializer = initializer
   44.59 +        self._last_cleanup_time = 0
   44.60 +        self._config = utils.storage(web.config.session_parameters)
   44.61 +        self._data = utils.threadeddict()
   44.62 +        
   44.63 +        self.__getitem__ = self._data.__getitem__
   44.64 +        self.__setitem__ = self._data.__setitem__
   44.65 +        self.__delitem__ = self._data.__delitem__
   44.66 +
   44.67 +        if app:
   44.68 +            app.add_processor(self._processor)
   44.69 +
   44.70 +    def __contains__(self, name):
   44.71 +        return name in self._data
   44.72 +
   44.73 +    def __getattr__(self, name):
   44.74 +        return getattr(self._data, name)
   44.75 +    
   44.76 +    def __setattr__(self, name, value):
   44.77 +        if name in self.__slots__:
   44.78 +            object.__setattr__(self, name, value)
   44.79 +        else:
   44.80 +            setattr(self._data, name, value)
   44.81 +        
   44.82 +    def __delattr__(self, name):
   44.83 +        delattr(self._data, name)
   44.84 +
   44.85 +    def _processor(self, handler):
   44.86 +        """Application processor to setup session for every request"""
   44.87 +        self._cleanup()
   44.88 +        self._load()
   44.89 +
   44.90 +        try:
   44.91 +            return handler()
   44.92 +        finally:
   44.93 +            self._save()
   44.94 +
   44.95 +    def _load(self):
   44.96 +        """Load the session from the store, by the id from cookie"""
   44.97 +        cookie_name = self._config.cookie_name
   44.98 +        cookie_domain = self._config.cookie_domain
   44.99 +        cookie_path = self._config.cookie_path
  44.100 +        httponly = self._config.httponly
  44.101 +        self.session_id = web.cookies().get(cookie_name)
  44.102 +
  44.103 +        # protection against session_id tampering
  44.104 +        if self.session_id and not self._valid_session_id(self.session_id):
  44.105 +            self.session_id = None
  44.106 +
  44.107 +        self._check_expiry()
  44.108 +        if self.session_id:
  44.109 +            d = self.store[self.session_id]
  44.110 +            self.update(d)
  44.111 +            self._validate_ip()
  44.112 +        
  44.113 +        if not self.session_id:
  44.114 +            self.session_id = self._generate_session_id()
  44.115 +
  44.116 +            if self._initializer:
  44.117 +                if isinstance(self._initializer, dict):
  44.118 +                    self.update(deepcopy(self._initializer))
  44.119 +                elif hasattr(self._initializer, '__call__'):
  44.120 +                    self._initializer()
  44.121 + 
  44.122 +        self.ip = web.ctx.ip
  44.123 +
  44.124 +    def _check_expiry(self):
  44.125 +        # check for expiry
  44.126 +        if self.session_id and self.session_id not in self.store:
  44.127 +            if self._config.ignore_expiry:
  44.128 +                self.session_id = None
  44.129 +            else:
  44.130 +                return self.expired()
  44.131 +
  44.132 +    def _validate_ip(self):
  44.133 +        # check for change of IP
  44.134 +        if self.session_id and self.get('ip', None) != web.ctx.ip:
  44.135 +            if not self._config.ignore_change_ip:
  44.136 +               return self.expired() 
  44.137 +    
  44.138 +    def _save(self):
  44.139 +        if not self.get('_killed'):
  44.140 +            self._setcookie(self.session_id)
  44.141 +            self.store[self.session_id] = dict(self._data)
  44.142 +        else:
  44.143 +            self._setcookie(self.session_id, expires=-1)
  44.144 +            
  44.145 +    def _setcookie(self, session_id, expires='', **kw):
  44.146 +        cookie_name = self._config.cookie_name
  44.147 +        cookie_domain = self._config.cookie_domain
  44.148 +        cookie_path = self._config.cookie_path
  44.149 +        httponly = self._config.httponly
  44.150 +        secure = self._config.secure
  44.151 +        web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path)
  44.152 +    
  44.153 +    def _generate_session_id(self):
  44.154 +        """Generate a random id for session"""
  44.155 +
  44.156 +        while True:
  44.157 +            rand = os.urandom(16)
  44.158 +            now = time.time()
  44.159 +            secret_key = self._config.secret_key
  44.160 +            session_id = sha1("%s%s%s%s" %(rand, now, utils.safestr(web.ctx.ip), secret_key))
  44.161 +            session_id = session_id.hexdigest()
  44.162 +            if session_id not in self.store:
  44.163 +                break
  44.164 +        return session_id
  44.165 +
  44.166 +    def _valid_session_id(self, session_id):
  44.167 +        rx = utils.re_compile('^[0-9a-fA-F]+$')
  44.168 +        return rx.match(session_id)
  44.169 +        
  44.170 +    def _cleanup(self):
  44.171 +        """Cleanup the stored sessions"""
  44.172 +        current_time = time.time()
  44.173 +        timeout = self._config.timeout
  44.174 +        if current_time - self._last_cleanup_time > timeout:
  44.175 +            self.store.cleanup(timeout)
  44.176 +            self._last_cleanup_time = current_time
  44.177 +
  44.178 +    def expired(self):
  44.179 +        """Called when an expired session is atime"""
  44.180 +        self._killed = True
  44.181 +        self._save()
  44.182 +        raise SessionExpired(self._config.expired_message)
  44.183 + 
  44.184 +    def kill(self):
  44.185 +        """Kill the session, make it no longer available"""
  44.186 +        del self.store[self.session_id]
  44.187 +        self._killed = True
  44.188 +
  44.189 +class Store:
  44.190 +    """Base class for session stores"""
  44.191 +
  44.192 +    def __contains__(self, key):
  44.193 +        raise NotImplementedError
  44.194 +
  44.195 +    def __getitem__(self, key):
  44.196 +        raise NotImplementedError
  44.197 +
  44.198 +    def __setitem__(self, key, value):
  44.199 +        raise NotImplementedError
  44.200 +
  44.201 +    def cleanup(self, timeout):
  44.202 +        """removes all the expired sessions"""
  44.203 +        raise NotImplementedError
  44.204 +
  44.205 +    def encode(self, session_dict):
  44.206 +        """encodes session dict as a string"""
  44.207 +        pickled = pickle.dumps(session_dict)
  44.208 +        return base64.encodestring(pickled)
  44.209 +
  44.210 +    def decode(self, session_data):
  44.211 +        """decodes the data to get back the session dict """
  44.212 +        pickled = base64.decodestring(session_data)
  44.213 +        return pickle.loads(pickled)
  44.214 +
  44.215 +class DiskStore(Store):
  44.216 +    """
  44.217 +    Store for saving a session on disk.
  44.218 +
  44.219 +        >>> import tempfile
  44.220 +        >>> root = tempfile.mkdtemp()
  44.221 +        >>> s = DiskStore(root)
  44.222 +        >>> s['a'] = 'foo'
  44.223 +        >>> s['a']
  44.224 +        'foo'
  44.225 +        >>> time.sleep(0.01)
  44.226 +        >>> s.cleanup(0.01)
  44.227 +        >>> s['a']
  44.228 +        Traceback (most recent call last):
  44.229 +            ...
  44.230 +        KeyError: 'a'
  44.231 +    """
  44.232 +    def __init__(self, root):
  44.233 +        # if the storage root doesn't exists, create it.
  44.234 +        if not os.path.exists(root):
  44.235 +            os.makedirs(
  44.236 +                    os.path.abspath(root)
  44.237 +                    )
  44.238 +        self.root = root
  44.239 +
  44.240 +    def _get_path(self, key):
  44.241 +        if os.path.sep in key: 
  44.242 +            raise ValueError, "Bad key: %s" % repr(key)
  44.243 +        return os.path.join(self.root, key)
  44.244 +    
  44.245 +    def __contains__(self, key):
  44.246 +        path = self._get_path(key)
  44.247 +        return os.path.exists(path)
  44.248 +
  44.249 +    def __getitem__(self, key):
  44.250 +        path = self._get_path(key)
  44.251 +        if os.path.exists(path): 
  44.252 +            pickled = open(path).read()
  44.253 +            return self.decode(pickled)
  44.254 +        else:
  44.255 +            raise KeyError, key
  44.256 +
  44.257 +    def __setitem__(self, key, value):
  44.258 +        path = self._get_path(key)
  44.259 +        pickled = self.encode(value)    
  44.260 +        try:
  44.261 +            f = open(path, 'w')
  44.262 +            try:
  44.263 +                f.write(pickled)
  44.264 +            finally: 
  44.265 +                f.close()
  44.266 +        except IOError:
  44.267 +            pass
  44.268 +
  44.269 +    def __delitem__(self, key):
  44.270 +        path = self._get_path(key)
  44.271 +        if os.path.exists(path):
  44.272 +            os.remove(path)
  44.273 +    
  44.274 +    def cleanup(self, timeout):
  44.275 +        now = time.time()
  44.276 +        for f in os.listdir(self.root):
  44.277 +            path = self._get_path(f)
  44.278 +            atime = os.stat(path).st_atime
  44.279 +            if now - atime > timeout :
  44.280 +                os.remove(path)
  44.281 +
  44.282 +class DBStore(Store):
  44.283 +    """Store for saving a session in database
  44.284 +    Needs a table with the following columns:
  44.285 +
  44.286 +        session_id CHAR(128) UNIQUE NOT NULL,
  44.287 +        atime DATETIME NOT NULL default current_timestamp,
  44.288 +        data TEXT
  44.289 +    """
  44.290 +    def __init__(self, db, table_name):
  44.291 +        self.db = db
  44.292 +        self.table = table_name
  44.293 +    
  44.294 +    def __contains__(self, key):
  44.295 +        data = self.db.select(self.table, where="session_id=$key", vars=locals())
  44.296 +        return bool(list(data)) 
  44.297 +
  44.298 +    def __getitem__(self, key):
  44.299 +        now = datetime.datetime.now()
  44.300 +        try:
  44.301 +            s = self.db.select(self.table, where="session_id=$key", vars=locals())[0]
  44.302 +            self.db.update(self.table, where="session_id=$key", atime=now, vars=locals())
  44.303 +        except IndexError:
  44.304 +            raise KeyError
  44.305 +        else:
  44.306 +            return self.decode(s.data)
  44.307 +
  44.308 +    def __setitem__(self, key, value):
  44.309 +        pickled = self.encode(value)
  44.310 +        now = datetime.datetime.now()
  44.311 +        if key in self:
  44.312 +            self.db.update(self.table, where="session_id=$key", data=pickled, vars=locals())
  44.313 +        else:
  44.314 +            self.db.insert(self.table, False, session_id=key, data=pickled )
  44.315 +                
  44.316 +    def __delitem__(self, key):
  44.317 +        self.db.delete(self.table, where="session_id=$key", vars=locals())
  44.318 +
  44.319 +    def cleanup(self, timeout):
  44.320 +        timeout = datetime.timedelta(timeout/(24.0*60*60)) #timedelta takes numdays as arg
  44.321 +        last_allowed_time = datetime.datetime.now() - timeout
  44.322 +        self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals())
  44.323 +
  44.324 +class ShelfStore:
  44.325 +    """Store for saving session using `shelve` module.
  44.326 +
  44.327 +        import shelve
  44.328 +        store = ShelfStore(shelve.open('session.shelf'))
  44.329 +
  44.330 +    XXX: is shelve thread-safe?
  44.331 +    """
  44.332 +    def __init__(self, shelf):
  44.333 +        self.shelf = shelf
  44.334 +
  44.335 +    def __contains__(self, key):
  44.336 +        return key in self.shelf
  44.337 +
  44.338 +    def __getitem__(self, key):
  44.339 +        atime, v = self.shelf[key]
  44.340 +        self[key] = v # update atime
  44.341 +        return v
  44.342 +
  44.343 +    def __setitem__(self, key, value):
  44.344 +        self.shelf[key] = time.time(), value
  44.345 +        
  44.346 +    def __delitem__(self, key):
  44.347 +        try:
  44.348 +            del self.shelf[key]
  44.349 +        except KeyError:
  44.350 +            pass
  44.351 +
  44.352 +    def cleanup(self, timeout):
  44.353 +        now = time.time()
  44.354 +        for k in self.shelf.keys():
  44.355 +            atime, v = self.shelf[k]
  44.356 +            if now - atime > timeout :
  44.357 +                del self[k]
  44.358 +
  44.359 +if __name__ == '__main__' :
  44.360 +    import doctest
  44.361 +    doctest.testmod()
    45.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    45.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/template.py	Mon Dec 02 14:02:05 2013 +0100
    45.3 @@ -0,0 +1,1515 @@
    45.4 +"""
    45.5 +simple, elegant templating
    45.6 +(part of web.py)
    45.7 +
    45.8 +Template design:
    45.9 +
   45.10 +Template string is split into tokens and the tokens are combined into nodes. 
   45.11 +Parse tree is a nodelist. TextNode and ExpressionNode are simple nodes and 
   45.12 +for-loop, if-loop etc are block nodes, which contain multiple child nodes. 
   45.13 +
   45.14 +Each node can emit some python string. python string emitted by the 
   45.15 +root node is validated for safeeval and executed using python in the given environment.
   45.16 +
   45.17 +Enough care is taken to make sure the generated code and the template has line to line match, 
   45.18 +so that the error messages can point to exact line number in template. (It doesn't work in some cases still.)
   45.19 +
   45.20 +Grammar:
   45.21 +
   45.22 +    template -> defwith sections 
   45.23 +    defwith -> '$def with (' arguments ')' | ''
   45.24 +    sections -> section*
   45.25 +    section -> block | assignment | line
   45.26 +
   45.27 +    assignment -> '$ ' <assignment expression>
   45.28 +    line -> (text|expr)*
   45.29 +    text -> <any characters other than $>
   45.30 +    expr -> '$' pyexpr | '$(' pyexpr ')' | '${' pyexpr '}'
   45.31 +    pyexpr -> <python expression>
   45.32 +"""
   45.33 +
   45.34 +__all__ = [
   45.35 +    "Template",
   45.36 +    "Render", "render", "frender",
   45.37 +    "ParseError", "SecurityError",
   45.38 +    "test"
   45.39 +]
   45.40 +
   45.41 +import tokenize
   45.42 +import os
   45.43 +import sys
   45.44 +import glob
   45.45 +import re
   45.46 +from UserDict import DictMixin
   45.47 +import warnings
   45.48 +
   45.49 +from utils import storage, safeunicode, safestr, re_compile
   45.50 +from webapi import config
   45.51 +from net import websafe
   45.52 +
   45.53 +def splitline(text):
   45.54 +    r"""
   45.55 +    Splits the given text at newline.
   45.56 +    
   45.57 +        >>> splitline('foo\nbar')
   45.58 +        ('foo\n', 'bar')
   45.59 +        >>> splitline('foo')
   45.60 +        ('foo', '')
   45.61 +        >>> splitline('')
   45.62 +        ('', '')
   45.63 +    """
   45.64 +    index = text.find('\n') + 1
   45.65 +    if index:
   45.66 +        return text[:index], text[index:]
   45.67 +    else:
   45.68 +        return text, ''
   45.69 +
   45.70 +class Parser:
   45.71 +    """Parser Base.
   45.72 +    """
   45.73 +    def __init__(self):
   45.74 +        self.statement_nodes = STATEMENT_NODES
   45.75 +        self.keywords = KEYWORDS
   45.76 +
   45.77 +    def parse(self, text, name="<template>"):
   45.78 +        self.text = text
   45.79 +        self.name = name
   45.80 +        
   45.81 +        defwith, text = self.read_defwith(text)
   45.82 +        suite = self.read_suite(text)
   45.83 +        return DefwithNode(defwith, suite)
   45.84 +
   45.85 +    def read_defwith(self, text):
   45.86 +        if text.startswith('$def with'):
   45.87 +            defwith, text = splitline(text)
   45.88 +            defwith = defwith[1:].strip() # strip $ and spaces
   45.89 +            return defwith, text
   45.90 +        else:
   45.91 +            return '', text
   45.92 +    
   45.93 +    def read_section(self, text):
   45.94 +        r"""Reads one section from the given text.
   45.95 +        
   45.96 +        section -> block | assignment | line
   45.97 +        
   45.98 +            >>> read_section = Parser().read_section
   45.99 +            >>> read_section('foo\nbar\n')
  45.100 +            (<line: [t'foo\n']>, 'bar\n')
  45.101 +            >>> read_section('$ a = b + 1\nfoo\n')
  45.102 +            (<assignment: 'a = b + 1'>, 'foo\n')
  45.103 +            
  45.104 +        read_section('$for in range(10):\n    hello $i\nfoo)
  45.105 +        """
  45.106 +        if text.lstrip(' ').startswith('$'):
  45.107 +            index = text.index('$')
  45.108 +            begin_indent, text2 = text[:index], text[index+1:]
  45.109 +            ahead = self.python_lookahead(text2)
  45.110 +            
  45.111 +            if ahead == 'var':
  45.112 +                return self.read_var(text2)
  45.113 +            elif ahead in self.statement_nodes:
  45.114 +                return self.read_block_section(text2, begin_indent)
  45.115 +            elif ahead in self.keywords:
  45.116 +                return self.read_keyword(text2)
  45.117 +            elif ahead.strip() == '':
  45.118 +                # assignments starts with a space after $
  45.119 +                # ex: $ a = b + 2
  45.120 +                return self.read_assignment(text2)
  45.121 +        return self.readline(text)
  45.122 +        
  45.123 +    def read_var(self, text):
  45.124 +        r"""Reads a var statement.
  45.125 +        
  45.126 +            >>> read_var = Parser().read_var
  45.127 +            >>> read_var('var x=10\nfoo')
  45.128 +            (<var: x = 10>, 'foo')
  45.129 +            >>> read_var('var x: hello $name\nfoo')
  45.130 +            (<var: x = join_(u'hello ', escape_(name, True))>, 'foo')
  45.131 +        """
  45.132 +        line, text = splitline(text)
  45.133 +        tokens = self.python_tokens(line)
  45.134 +        if len(tokens) < 4:
  45.135 +            raise SyntaxError('Invalid var statement')
  45.136 +            
  45.137 +        name = tokens[1]
  45.138 +        sep = tokens[2]
  45.139 +        value = line.split(sep, 1)[1].strip()
  45.140 +        
  45.141 +        if sep == '=':
  45.142 +            pass # no need to process value
  45.143 +        elif sep == ':': 
  45.144 +            #@@ Hack for backward-compatability
  45.145 +            if tokens[3] == '\n': # multi-line var statement
  45.146 +                block, text = self.read_indented_block(text, '    ')
  45.147 +                lines = [self.readline(x)[0] for x in block.splitlines()]
  45.148 +                nodes = []
  45.149 +                for x in lines:
  45.150 +                    nodes.extend(x.nodes)
  45.151 +                    nodes.append(TextNode('\n'))         
  45.152 +            else: # single-line var statement
  45.153 +                linenode, _ = self.readline(value)
  45.154 +                nodes = linenode.nodes                
  45.155 +            parts = [node.emit('') for node in nodes]
  45.156 +            value = "join_(%s)" % ", ".join(parts)
  45.157 +        else:
  45.158 +            raise SyntaxError('Invalid var statement')
  45.159 +        return VarNode(name, value), text
  45.160 +                    
  45.161 +    def read_suite(self, text):
  45.162 +        r"""Reads section by section till end of text.
  45.163 +        
  45.164 +            >>> read_suite = Parser().read_suite
  45.165 +            >>> read_suite('hello $name\nfoo\n')
  45.166 +            [<line: [t'hello ', $name, t'\n']>, <line: [t'foo\n']>]
  45.167 +        """
  45.168 +        sections = []
  45.169 +        while text:
  45.170 +            section, text = self.read_section(text)
  45.171 +            sections.append(section)
  45.172 +        return SuiteNode(sections)
  45.173 +    
  45.174 +    def readline(self, text):
  45.175 +        r"""Reads one line from the text. Newline is supressed if the line ends with \.
  45.176 +        
  45.177 +            >>> readline = Parser().readline
  45.178 +            >>> readline('hello $name!\nbye!')
  45.179 +            (<line: [t'hello ', $name, t'!\n']>, 'bye!')
  45.180 +            >>> readline('hello $name!\\\nbye!')
  45.181 +            (<line: [t'hello ', $name, t'!']>, 'bye!')
  45.182 +            >>> readline('$f()\n\n')
  45.183 +            (<line: [$f(), t'\n']>, '\n')
  45.184 +        """
  45.185 +        line, text = splitline(text)
  45.186 +
  45.187 +        # supress new line if line ends with \
  45.188 +        if line.endswith('\\\n'):
  45.189 +            line = line[:-2]
  45.190 +                
  45.191 +        nodes = []
  45.192 +        while line:
  45.193 +            node, line = self.read_node(line)
  45.194 +            nodes.append(node)
  45.195 +            
  45.196 +        return LineNode(nodes), text
  45.197 +
  45.198 +    def read_node(self, text):
  45.199 +        r"""Reads a node from the given text and returns the node and remaining text.
  45.200 +
  45.201 +            >>> read_node = Parser().read_node
  45.202 +            >>> read_node('hello $name')
  45.203 +            (t'hello ', '$name')
  45.204 +            >>> read_node('$name')
  45.205 +            ($name, '')
  45.206 +        """
  45.207 +        if text.startswith('$$'):
  45.208 +            return TextNode('$'), text[2:]
  45.209 +        elif text.startswith('$#'): # comment
  45.210 +            line, text = splitline(text)
  45.211 +            return TextNode('\n'), text
  45.212 +        elif text.startswith('$'):
  45.213 +            text = text[1:] # strip $
  45.214 +            if text.startswith(':'):
  45.215 +                escape = False
  45.216 +                text = text[1:] # strip :
  45.217 +            else:
  45.218 +                escape = True
  45.219 +            return self.read_expr(text, escape=escape)
  45.220 +        else:
  45.221 +            return self.read_text(text)
  45.222 +    
  45.223 +    def read_text(self, text):
  45.224 +        r"""Reads a text node from the given text.
  45.225 +        
  45.226 +            >>> read_text = Parser().read_text
  45.227 +            >>> read_text('hello $name')
  45.228 +            (t'hello ', '$name')
  45.229 +        """
  45.230 +        index = text.find('$')
  45.231 +        if index < 0:
  45.232 +            return TextNode(text), ''
  45.233 +        else:
  45.234 +            return TextNode(text[:index]), text[index:]
  45.235 +            
  45.236 +    def read_keyword(self, text):
  45.237 +        line, text = splitline(text)
  45.238 +        return StatementNode(line.strip() + "\n"), text
  45.239 +
  45.240 +    def read_expr(self, text, escape=True):
  45.241 +        """Reads a python expression from the text and returns the expression and remaining text.
  45.242 +
  45.243 +        expr -> simple_expr | paren_expr
  45.244 +        simple_expr -> id extended_expr
  45.245 +        extended_expr -> attr_access | paren_expr extended_expr | ''
  45.246 +        attr_access -> dot id extended_expr
  45.247 +        paren_expr -> [ tokens ] | ( tokens ) | { tokens }
  45.248 +     
  45.249 +            >>> read_expr = Parser().read_expr
  45.250 +            >>> read_expr("name")
  45.251 +            ($name, '')
  45.252 +            >>> read_expr("a.b and c")
  45.253 +            ($a.b, ' and c')
  45.254 +            >>> read_expr("a. b")
  45.255 +            ($a, '. b')
  45.256 +            >>> read_expr("name</h1>")
  45.257 +            ($name, '</h1>')
  45.258 +            >>> read_expr("(limit)ing")
  45.259 +            ($(limit), 'ing')
  45.260 +            >>> read_expr('a[1, 2][:3].f(1+2, "weird string[).", 3 + 4) done.')
  45.261 +            ($a[1, 2][:3].f(1+2, "weird string[).", 3 + 4), ' done.')
  45.262 +        """
  45.263 +        def simple_expr():
  45.264 +            identifier()
  45.265 +            extended_expr()
  45.266 +        
  45.267 +        def identifier():
  45.268 +            tokens.next()
  45.269 +        
  45.270 +        def extended_expr():
  45.271 +            lookahead = tokens.lookahead()
  45.272 +            if lookahead is None:
  45.273 +                return
  45.274 +            elif lookahead.value == '.':
  45.275 +                attr_access()
  45.276 +            elif lookahead.value in parens:
  45.277 +                paren_expr()
  45.278 +                extended_expr()
  45.279 +            else:
  45.280 +                return
  45.281 +        
  45.282 +        def attr_access():
  45.283 +            from token import NAME # python token constants
  45.284 +            dot = tokens.lookahead()
  45.285 +            if tokens.lookahead2().type == NAME:
  45.286 +                tokens.next() # consume dot
  45.287 +                identifier()
  45.288 +                extended_expr()
  45.289 +        
  45.290 +        def paren_expr():
  45.291 +            begin = tokens.next().value
  45.292 +            end = parens[begin]
  45.293 +            while True:
  45.294 +                if tokens.lookahead().value in parens:
  45.295 +                    paren_expr()
  45.296 +                else:
  45.297 +                    t = tokens.next()
  45.298 +                    if t.value == end:
  45.299 +                        break
  45.300 +            return
  45.301 +
  45.302 +        parens = {
  45.303 +            "(": ")",
  45.304 +            "[": "]",
  45.305 +            "{": "}"
  45.306 +        }
  45.307 +        
  45.308 +        def get_tokens(text):
  45.309 +            """tokenize text using python tokenizer.
  45.310 +            Python tokenizer ignores spaces, but they might be important in some cases. 
  45.311 +            This function introduces dummy space tokens when it identifies any ignored space.
  45.312 +            Each token is a storage object containing type, value, begin and end.
  45.313 +            """
  45.314 +            readline = iter([text]).next
  45.315 +            end = None
  45.316 +            for t in tokenize.generate_tokens(readline):
  45.317 +                t = storage(type=t[0], value=t[1], begin=t[2], end=t[3])
  45.318 +                if end is not None and end != t.begin:
  45.319 +                    _, x1 = end
  45.320 +                    _, x2 = t.begin
  45.321 +                    yield storage(type=-1, value=text[x1:x2], begin=end, end=t.begin)
  45.322 +                end = t.end
  45.323 +                yield t
  45.324 +                
  45.325 +        class BetterIter:
  45.326 +            """Iterator like object with 2 support for 2 look aheads."""
  45.327 +            def __init__(self, items):
  45.328 +                self.iteritems = iter(items)
  45.329 +                self.items = []
  45.330 +                self.position = 0
  45.331 +                self.current_item = None
  45.332 +            
  45.333 +            def lookahead(self):
  45.334 +                if len(self.items) <= self.position:
  45.335 +                    self.items.append(self._next())
  45.336 +                return self.items[self.position]
  45.337 +
  45.338 +            def _next(self):
  45.339 +                try:
  45.340 +                    return self.iteritems.next()
  45.341 +                except StopIteration:
  45.342 +                    return None
  45.343 +                
  45.344 +            def lookahead2(self):
  45.345 +                if len(self.items) <= self.position+1:
  45.346 +                    self.items.append(self._next())
  45.347 +                return self.items[self.position+1]
  45.348 +                    
  45.349 +            def next(self):
  45.350 +                self.current_item = self.lookahead()
  45.351 +                self.position += 1
  45.352 +                return self.current_item
  45.353 +
  45.354 +        tokens = BetterIter(get_tokens(text))
  45.355 +                
  45.356 +        if tokens.lookahead().value in parens:
  45.357 +            paren_expr()
  45.358 +        else:
  45.359 +            simple_expr()
  45.360 +        row, col = tokens.current_item.end
  45.361 +        return ExpressionNode(text[:col], escape=escape), text[col:]    
  45.362 +
  45.363 +    def read_assignment(self, text):
  45.364 +        r"""Reads assignment statement from text.
  45.365 +    
  45.366 +            >>> read_assignment = Parser().read_assignment
  45.367 +            >>> read_assignment('a = b + 1\nfoo')
  45.368 +            (<assignment: 'a = b + 1'>, 'foo')
  45.369 +        """
  45.370 +        line, text = splitline(text)
  45.371 +        return AssignmentNode(line.strip()), text
  45.372 +    
  45.373 +    def python_lookahead(self, text):
  45.374 +        """Returns the first python token from the given text.
  45.375 +        
  45.376 +            >>> python_lookahead = Parser().python_lookahead
  45.377 +            >>> python_lookahead('for i in range(10):')
  45.378 +            'for'
  45.379 +            >>> python_lookahead('else:')
  45.380 +            'else'
  45.381 +            >>> python_lookahead(' x = 1')
  45.382 +            ' '
  45.383 +        """
  45.384 +        readline = iter([text]).next
  45.385 +        tokens = tokenize.generate_tokens(readline)
  45.386 +        return tokens.next()[1]
  45.387 +        
  45.388 +    def python_tokens(self, text):
  45.389 +        readline = iter([text]).next
  45.390 +        tokens = tokenize.generate_tokens(readline)
  45.391 +        return [t[1] for t in tokens]
  45.392 +        
  45.393 +    def read_indented_block(self, text, indent):
  45.394 +        r"""Read a block of text. A block is what typically follows a for or it statement.
  45.395 +        It can be in the same line as that of the statement or an indented block.
  45.396 +
  45.397 +            >>> read_indented_block = Parser().read_indented_block
  45.398 +            >>> read_indented_block('  a\n  b\nc', '  ')
  45.399 +            ('a\nb\n', 'c')
  45.400 +            >>> read_indented_block('  a\n    b\n  c\nd', '  ')
  45.401 +            ('a\n  b\nc\n', 'd')
  45.402 +            >>> read_indented_block('  a\n\n    b\nc', '  ')
  45.403 +            ('a\n\n  b\n', 'c')
  45.404 +        """
  45.405 +        if indent == '':
  45.406 +            return '', text
  45.407 +            
  45.408 +        block = ""
  45.409 +        while text:
  45.410 +            line, text2 = splitline(text)
  45.411 +            if line.strip() == "":
  45.412 +                block += '\n'
  45.413 +            elif line.startswith(indent):
  45.414 +                block += line[len(indent):]
  45.415 +            else:
  45.416 +                break
  45.417 +            text = text2
  45.418 +        return block, text
  45.419 +
  45.420 +    def read_statement(self, text):
  45.421 +        r"""Reads a python statement.
  45.422 +        
  45.423 +            >>> read_statement = Parser().read_statement
  45.424 +            >>> read_statement('for i in range(10): hello $name')
  45.425 +            ('for i in range(10):', ' hello $name')
  45.426 +        """
  45.427 +        tok = PythonTokenizer(text)
  45.428 +        tok.consume_till(':')
  45.429 +        return text[:tok.index], text[tok.index:]
  45.430 +        
  45.431 +    def read_block_section(self, text, begin_indent=''):
  45.432 +        r"""
  45.433 +            >>> read_block_section = Parser().read_block_section
  45.434 +            >>> read_block_section('for i in range(10): hello $i\nfoo')
  45.435 +            (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, 'foo')
  45.436 +            >>> read_block_section('for i in range(10):\n        hello $i\n    foo', begin_indent='    ')
  45.437 +            (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, '    foo')
  45.438 +            >>> read_block_section('for i in range(10):\n  hello $i\nfoo')
  45.439 +            (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, 'foo')
  45.440 +        """
  45.441 +        line, text = splitline(text)
  45.442 +        stmt, line = self.read_statement(line)
  45.443 +        keyword = self.python_lookahead(stmt)
  45.444 +        
  45.445 +        # if there is some thing left in the line
  45.446 +        if line.strip():
  45.447 +            block = line.lstrip()
  45.448 +        else:
  45.449 +            def find_indent(text):
  45.450 +                rx = re_compile('  +')
  45.451 +                match = rx.match(text)    
  45.452 +                first_indent = match and match.group(0)
  45.453 +                return first_indent or ""
  45.454 +
  45.455 +            # find the indentation of the block by looking at the first line
  45.456 +            first_indent = find_indent(text)[len(begin_indent):]
  45.457 +
  45.458 +            #TODO: fix this special case
  45.459 +            if keyword == "code":
  45.460 +                indent = begin_indent + first_indent
  45.461 +            else:
  45.462 +                indent = begin_indent + min(first_indent, INDENT)
  45.463 +            
  45.464 +            block, text = self.read_indented_block(text, indent)
  45.465 +            
  45.466 +        return self.create_block_node(keyword, stmt, block, begin_indent), text
  45.467 +        
  45.468 +    def create_block_node(self, keyword, stmt, block, begin_indent):
  45.469 +        if keyword in self.statement_nodes:
  45.470 +            return self.statement_nodes[keyword](stmt, block, begin_indent)
  45.471 +        else:
  45.472 +            raise ParseError, 'Unknown statement: %s' % repr(keyword)
  45.473 +        
  45.474 +class PythonTokenizer:
  45.475 +    """Utility wrapper over python tokenizer."""
  45.476 +    def __init__(self, text):
  45.477 +        self.text = text
  45.478 +        readline = iter([text]).next
  45.479 +        self.tokens = tokenize.generate_tokens(readline)
  45.480 +        self.index = 0
  45.481 +        
  45.482 +    def consume_till(self, delim):        
  45.483 +        """Consumes tokens till colon.
  45.484 +        
  45.485 +            >>> tok = PythonTokenizer('for i in range(10): hello $i')
  45.486 +            >>> tok.consume_till(':')
  45.487 +            >>> tok.text[:tok.index]
  45.488 +            'for i in range(10):'
  45.489 +            >>> tok.text[tok.index:]
  45.490 +            ' hello $i'
  45.491 +        """
  45.492 +        try:
  45.493 +            while True:
  45.494 +                t = self.next()
  45.495 +                if t.value == delim:
  45.496 +                    break
  45.497 +                elif t.value == '(':
  45.498 +                    self.consume_till(')')
  45.499 +                elif t.value == '[':
  45.500 +                    self.consume_till(']')
  45.501 +                elif t.value == '{':
  45.502 +                    self.consume_till('}')
  45.503 +
  45.504 +                # if end of line is found, it is an exception.
  45.505 +                # Since there is no easy way to report the line number,
  45.506 +                # leave the error reporting to the python parser later  
  45.507 +                #@@ This should be fixed.
  45.508 +                if t.value == '\n':
  45.509 +                    break
  45.510 +        except:
  45.511 +            #raise ParseError, "Expected %s, found end of line." % repr(delim)
  45.512 +
  45.513 +            # raising ParseError doesn't show the line number. 
  45.514 +            # if this error is ignored, then it will be caught when compiling the python code.
  45.515 +            return
  45.516 +    
  45.517 +    def next(self):
  45.518 +        type, t, begin, end, line = self.tokens.next()
  45.519 +        row, col = end
  45.520 +        self.index = col
  45.521 +        return storage(type=type, value=t, begin=begin, end=end)
  45.522 +        
  45.523 +class DefwithNode:
  45.524 +    def __init__(self, defwith, suite):
  45.525 +        if defwith:
  45.526 +            self.defwith = defwith.replace('with', '__template__') + ':'
  45.527 +            # offset 4 lines. for encoding, __lineoffset__, loop and self.
  45.528 +            self.defwith += "\n    __lineoffset__ = -4"
  45.529 +        else:
  45.530 +            self.defwith = 'def __template__():'
  45.531 +            # offset 4 lines for encoding, __template__, __lineoffset__, loop and self.
  45.532 +            self.defwith += "\n    __lineoffset__ = -5"
  45.533 +
  45.534 +        self.defwith += "\n    loop = ForLoop()"
  45.535 +        self.defwith += "\n    self = TemplateResult(); extend_ = self.extend"
  45.536 +        self.suite = suite
  45.537 +        self.end = "\n    return self"
  45.538 +
  45.539 +    def emit(self, indent):
  45.540 +        encoding = "# coding: utf-8\n"
  45.541 +        return encoding + self.defwith + self.suite.emit(indent + INDENT) + self.end
  45.542 +
  45.543 +    def __repr__(self):
  45.544 +        return "<defwith: %s, %s>" % (self.defwith, self.suite)
  45.545 +
  45.546 +class TextNode:
  45.547 +    def __init__(self, value):
  45.548 +        self.value = value
  45.549 +
  45.550 +    def emit(self, indent, begin_indent=''):
  45.551 +        return repr(safeunicode(self.value))
  45.552 +        
  45.553 +    def __repr__(self):
  45.554 +        return 't' + repr(self.value)
  45.555 +
  45.556 +class ExpressionNode:
  45.557 +    def __init__(self, value, escape=True):
  45.558 +        self.value = value.strip()
  45.559 +        
  45.560 +        # convert ${...} to $(...)
  45.561 +        if value.startswith('{') and value.endswith('}'):
  45.562 +            self.value = '(' + self.value[1:-1] + ')'
  45.563 +            
  45.564 +        self.escape = escape
  45.565 +
  45.566 +    def emit(self, indent, begin_indent=''):
  45.567 +        return 'escape_(%s, %s)' % (self.value, bool(self.escape))
  45.568 +        
  45.569 +    def __repr__(self):
  45.570 +        if self.escape:
  45.571 +            escape = ''
  45.572 +        else:
  45.573 +            escape = ':'
  45.574 +        return "$%s%s" % (escape, self.value)
  45.575 +        
  45.576 +class AssignmentNode:
  45.577 +    def __init__(self, code):
  45.578 +        self.code = code
  45.579 +        
  45.580 +    def emit(self, indent, begin_indent=''):
  45.581 +        return indent + self.code + "\n"
  45.582 +        
  45.583 +    def __repr__(self):
  45.584 +        return "<assignment: %s>" % repr(self.code)
  45.585 +        
  45.586 +class LineNode:
  45.587 +    def __init__(self, nodes):
  45.588 +        self.nodes = nodes
  45.589 +        
  45.590 +    def emit(self, indent, text_indent='', name=''):
  45.591 +        text = [node.emit('') for node in self.nodes]
  45.592 +        if text_indent:
  45.593 +            text = [repr(text_indent)] + text
  45.594 +
  45.595 +        return indent + "extend_([%s])\n" % ", ".join(text)        
  45.596 +    
  45.597 +    def __repr__(self):
  45.598 +        return "<line: %s>" % repr(self.nodes)
  45.599 +
  45.600 +INDENT = '    ' # 4 spaces
  45.601 +        
  45.602 +class BlockNode:
  45.603 +    def __init__(self, stmt, block, begin_indent=''):
  45.604 +        self.stmt = stmt
  45.605 +        self.suite = Parser().read_suite(block)
  45.606 +        self.begin_indent = begin_indent
  45.607 +
  45.608 +    def emit(self, indent, text_indent=''):
  45.609 +        text_indent = self.begin_indent + text_indent
  45.610 +        out = indent + self.stmt + self.suite.emit(indent + INDENT, text_indent)
  45.611 +        return out
  45.612 +        
  45.613 +    def __repr__(self):
  45.614 +        return "<block: %s, %s>" % (repr(self.stmt), repr(self.suite))
  45.615 +
  45.616 +class ForNode(BlockNode):
  45.617 +    def __init__(self, stmt, block, begin_indent=''):
  45.618 +        self.original_stmt = stmt
  45.619 +        tok = PythonTokenizer(stmt)
  45.620 +        tok.consume_till('in')
  45.621 +        a = stmt[:tok.index] # for i in
  45.622 +        b = stmt[tok.index:-1] # rest of for stmt excluding :
  45.623 +        stmt = a + ' loop.setup(' + b.strip() + '):'
  45.624 +        BlockNode.__init__(self, stmt, block, begin_indent)
  45.625 +        
  45.626 +    def __repr__(self):
  45.627 +        return "<block: %s, %s>" % (repr(self.original_stmt), repr(self.suite))
  45.628 +
  45.629 +class CodeNode:
  45.630 +    def __init__(self, stmt, block, begin_indent=''):
  45.631 +        # compensate one line for $code:
  45.632 +        self.code = "\n" + block
  45.633 +        
  45.634 +    def emit(self, indent, text_indent=''):
  45.635 +        import re
  45.636 +        rx = re.compile('^', re.M)
  45.637 +        return rx.sub(indent, self.code).rstrip(' ')
  45.638 +        
  45.639 +    def __repr__(self):
  45.640 +        return "<code: %s>" % repr(self.code)
  45.641 +        
  45.642 +class StatementNode:
  45.643 +    def __init__(self, stmt):
  45.644 +        self.stmt = stmt
  45.645 +        
  45.646 +    def emit(self, indent, begin_indent=''):
  45.647 +        return indent + self.stmt
  45.648 +        
  45.649 +    def __repr__(self):
  45.650 +        return "<stmt: %s>" % repr(self.stmt)
  45.651 +        
  45.652 +class IfNode(BlockNode):
  45.653 +    pass
  45.654 +
  45.655 +class ElseNode(BlockNode):
  45.656 +    pass
  45.657 +
  45.658 +class ElifNode(BlockNode):
  45.659 +    pass
  45.660 +
  45.661 +class DefNode(BlockNode):
  45.662 +    def __init__(self, *a, **kw):
  45.663 +        BlockNode.__init__(self, *a, **kw)
  45.664 +
  45.665 +        code = CodeNode("", "")
  45.666 +        code.code = "self = TemplateResult(); extend_ = self.extend\n"
  45.667 +        self.suite.sections.insert(0, code)
  45.668 +
  45.669 +        code = CodeNode("", "")
  45.670 +        code.code = "return self\n"
  45.671 +        self.suite.sections.append(code)
  45.672 +        
  45.673 +    def emit(self, indent, text_indent=''):
  45.674 +        text_indent = self.begin_indent + text_indent
  45.675 +        out = indent + self.stmt + self.suite.emit(indent + INDENT, text_indent)
  45.676 +        return indent + "__lineoffset__ -= 3\n" + out
  45.677 +
  45.678 +class VarNode:
  45.679 +    def __init__(self, name, value):
  45.680 +        self.name = name
  45.681 +        self.value = value
  45.682 +        
  45.683 +    def emit(self, indent, text_indent):
  45.684 +        return indent + "self[%s] = %s\n" % (repr(self.name), self.value)
  45.685 +        
  45.686 +    def __repr__(self):
  45.687 +        return "<var: %s = %s>" % (self.name, self.value)
  45.688 +
  45.689 +class SuiteNode:
  45.690 +    """Suite is a list of sections."""
  45.691 +    def __init__(self, sections):
  45.692 +        self.sections = sections
  45.693 +        
  45.694 +    def emit(self, indent, text_indent=''):
  45.695 +        return "\n" + "".join([s.emit(indent, text_indent) for s in self.sections])
  45.696 +        
  45.697 +    def __repr__(self):
  45.698 +        return repr(self.sections)
  45.699 +
  45.700 +STATEMENT_NODES = {
  45.701 +    'for': ForNode,
  45.702 +    'while': BlockNode,
  45.703 +    'if': IfNode,
  45.704 +    'elif': ElifNode,
  45.705 +    'else': ElseNode,
  45.706 +    'def': DefNode,
  45.707 +    'code': CodeNode
  45.708 +}
  45.709 +
  45.710 +KEYWORDS = [
  45.711 +    "pass",
  45.712 +    "break",
  45.713 +    "continue",
  45.714 +    "return"
  45.715 +]
  45.716 +
  45.717 +TEMPLATE_BUILTIN_NAMES = [
  45.718 +    "dict", "enumerate", "float", "int", "bool", "list", "long", "reversed", 
  45.719 +    "set", "slice", "tuple", "xrange",
  45.720 +    "abs", "all", "any", "callable", "chr", "cmp", "divmod", "filter", "hex", 
  45.721 +    "id", "isinstance", "iter", "len", "max", "min", "oct", "ord", "pow", "range",
  45.722 +    "True", "False",
  45.723 +    "None",
  45.724 +    "__import__", # some c-libraries like datetime requires __import__ to present in the namespace
  45.725 +]
  45.726 +
  45.727 +import __builtin__
  45.728 +TEMPLATE_BUILTINS = dict([(name, getattr(__builtin__, name)) for name in TEMPLATE_BUILTIN_NAMES if name in __builtin__.__dict__])
  45.729 +
  45.730 +class ForLoop:
  45.731 +    """
  45.732 +    Wrapper for expression in for stament to support loop.xxx helpers.
  45.733 +    
  45.734 +        >>> loop = ForLoop()
  45.735 +        >>> for x in loop.setup(['a', 'b', 'c']):
  45.736 +        ...     print loop.index, loop.revindex, loop.parity, x
  45.737 +        ...
  45.738 +        1 3 odd a
  45.739 +        2 2 even b
  45.740 +        3 1 odd c
  45.741 +        >>> loop.index
  45.742 +        Traceback (most recent call last):
  45.743 +            ...
  45.744 +        AttributeError: index
  45.745 +    """
  45.746 +    def __init__(self):
  45.747 +        self._ctx = None
  45.748 +        
  45.749 +    def __getattr__(self, name):
  45.750 +        if self._ctx is None:
  45.751 +            raise AttributeError, name
  45.752 +        else:
  45.753 +            return getattr(self._ctx, name)
  45.754 +        
  45.755 +    def setup(self, seq):        
  45.756 +        self._push()
  45.757 +        return self._ctx.setup(seq)
  45.758 +        
  45.759 +    def _push(self):
  45.760 +        self._ctx = ForLoopContext(self, self._ctx)
  45.761 +        
  45.762 +    def _pop(self):
  45.763 +        self._ctx = self._ctx.parent
  45.764 +                
  45.765 +class ForLoopContext:
  45.766 +    """Stackable context for ForLoop to support nested for loops.
  45.767 +    """
  45.768 +    def __init__(self, forloop, parent):
  45.769 +        self._forloop = forloop
  45.770 +        self.parent = parent
  45.771 +        
  45.772 +    def setup(self, seq):
  45.773 +        try:
  45.774 +            self.length = len(seq)
  45.775 +        except:
  45.776 +            self.length = 0
  45.777 +
  45.778 +        self.index = 0
  45.779 +        for a in seq:
  45.780 +            self.index += 1
  45.781 +            yield a
  45.782 +        self._forloop._pop()
  45.783 +            
  45.784 +    index0 = property(lambda self: self.index-1)
  45.785 +    first = property(lambda self: self.index == 1)
  45.786 +    last = property(lambda self: self.index == self.length)
  45.787 +    odd = property(lambda self: self.index % 2 == 1)
  45.788 +    even = property(lambda self: self.index % 2 == 0)
  45.789 +    parity = property(lambda self: ['odd', 'even'][self.even])
  45.790 +    revindex0 = property(lambda self: self.length - self.index)
  45.791 +    revindex = property(lambda self: self.length - self.index + 1)
  45.792 +        
  45.793 +class BaseTemplate:
  45.794 +    def __init__(self, code, filename, filter, globals, builtins):
  45.795 +        self.filename = filename
  45.796 +        self.filter = filter
  45.797 +        self._globals = globals
  45.798 +        self._builtins = builtins
  45.799 +        if code:
  45.800 +            self.t = self._compile(code)
  45.801 +        else:
  45.802 +            self.t = lambda: ''
  45.803 +        
  45.804 +    def _compile(self, code):
  45.805 +        env = self.make_env(self._globals or {}, self._builtins)
  45.806 +        exec(code, env)
  45.807 +        return env['__template__']
  45.808 +
  45.809 +    def __call__(self, *a, **kw):
  45.810 +        __hidetraceback__ = True
  45.811 +        return self.t(*a, **kw)
  45.812 +
  45.813 +    def make_env(self, globals, builtins):
  45.814 +        return dict(globals,
  45.815 +            __builtins__=builtins, 
  45.816 +            ForLoop=ForLoop,
  45.817 +            TemplateResult=TemplateResult,
  45.818 +            escape_=self._escape,
  45.819 +            join_=self._join
  45.820 +        )
  45.821 +    def _join(self, *items):
  45.822 +        return u"".join(items)
  45.823 +            
  45.824 +    def _escape(self, value, escape=False):
  45.825 +        if value is None: 
  45.826 +            value = ''
  45.827 +            
  45.828 +        value = safeunicode(value)
  45.829 +        if escape and self.filter:
  45.830 +            value = self.filter(value)
  45.831 +        return value
  45.832 +
  45.833 +class Template(BaseTemplate):
  45.834 +    CONTENT_TYPES = {
  45.835 +        '.html' : 'text/html; charset=utf-8',
  45.836 +        '.xhtml' : 'application/xhtml+xml; charset=utf-8',
  45.837 +        '.txt' : 'text/plain',
  45.838 +    }
  45.839 +    FILTERS = {
  45.840 +        '.html': websafe,
  45.841 +        '.xhtml': websafe,
  45.842 +        '.xml': websafe
  45.843 +    }
  45.844 +    globals = {}
  45.845 +    
  45.846 +    def __init__(self, text, filename='<template>', filter=None, globals=None, builtins=None, extensions=None):
  45.847 +        self.extensions = extensions or []
  45.848 +        text = Template.normalize_text(text)
  45.849 +        code = self.compile_template(text, filename)
  45.850 +                
  45.851 +        _, ext = os.path.splitext(filename)
  45.852 +        filter = filter or self.FILTERS.get(ext, None)
  45.853 +        self.content_type = self.CONTENT_TYPES.get(ext, None)
  45.854 +
  45.855 +        if globals is None:
  45.856 +            globals = self.globals
  45.857 +        if builtins is None:
  45.858 +            builtins = TEMPLATE_BUILTINS
  45.859 +                
  45.860 +        BaseTemplate.__init__(self, code=code, filename=filename, filter=filter, globals=globals, builtins=builtins)
  45.861 +        
  45.862 +    def normalize_text(text):
  45.863 +        """Normalizes template text by correcting \r\n, tabs and BOM chars."""
  45.864 +        text = text.replace('\r\n', '\n').replace('\r', '\n').expandtabs()
  45.865 +        if not text.endswith('\n'):
  45.866 +            text += '\n'
  45.867 +
  45.868 +        # ignore BOM chars at the begining of template
  45.869 +        BOM = '\xef\xbb\xbf'
  45.870 +        if isinstance(text, str) and text.startswith(BOM):
  45.871 +            text = text[len(BOM):]
  45.872 +        
  45.873 +        # support fort \$ for backward-compatibility 
  45.874 +        text = text.replace(r'\$', '$$')
  45.875 +        return text
  45.876 +    normalize_text = staticmethod(normalize_text)
  45.877 +                
  45.878 +    def __call__(self, *a, **kw):
  45.879 +        __hidetraceback__ = True
  45.880 +        import webapi as web
  45.881 +        if 'headers' in web.ctx and self.content_type:
  45.882 +            web.header('Content-Type', self.content_type, unique=True)
  45.883 +            
  45.884 +        return BaseTemplate.__call__(self, *a, **kw)
  45.885 +        
  45.886 +    def generate_code(text, filename, parser=None):
  45.887 +        # parse the text
  45.888 +        parser = parser or Parser()
  45.889 +        rootnode = parser.parse(text, filename)
  45.890 +                
  45.891 +        # generate python code from the parse tree
  45.892 +        code = rootnode.emit(indent="").strip()
  45.893 +        return safestr(code)
  45.894 +        
  45.895 +    generate_code = staticmethod(generate_code)
  45.896 +    
  45.897 +    def create_parser(self):
  45.898 +        p = Parser()
  45.899 +        for ext in self.extensions:
  45.900 +            p = ext(p)
  45.901 +        return p
  45.902 +                
  45.903 +    def compile_template(self, template_string, filename):
  45.904 +        code = Template.generate_code(template_string, filename, parser=self.create_parser())
  45.905 +
  45.906 +        def get_source_line(filename, lineno):
  45.907 +            try:
  45.908 +                lines = open(filename).read().splitlines()
  45.909 +                return lines[lineno]
  45.910 +            except:
  45.911 +                return None
  45.912 +        
  45.913 +        try:
  45.914 +            # compile the code first to report the errors, if any, with the filename
  45.915 +            compiled_code = compile(code, filename, 'exec')
  45.916 +        except SyntaxError, e:
  45.917 +            # display template line that caused the error along with the traceback.
  45.918 +            try:
  45.919 +                e.msg += '\n\nTemplate traceback:\n    File %s, line %s\n        %s' % \
  45.920 +                    (repr(e.filename), e.lineno, get_source_line(e.filename, e.lineno-1))
  45.921 +            except: 
  45.922 +                pass
  45.923 +            raise
  45.924 +        
  45.925 +        # make sure code is safe - but not with jython, it doesn't have a working compiler module
  45.926 +        if not sys.platform.startswith('java'):
  45.927 +            try:
  45.928 +                import compiler
  45.929 +                ast = compiler.parse(code)
  45.930 +                SafeVisitor().walk(ast, filename)
  45.931 +            except ImportError:
  45.932 +                warnings.warn("Unabled to import compiler module. Unable to check templates for safety.")
  45.933 +        else:
  45.934 +            warnings.warn("SECURITY ISSUE: You are using Jython, which does not support checking templates for safety. Your templates can execute arbitrary code.")
  45.935 +
  45.936 +        return compiled_code
  45.937 +        
  45.938 +class CompiledTemplate(Template):
  45.939 +    def __init__(self, f, filename):
  45.940 +        Template.__init__(self, '', filename)
  45.941 +        self.t = f
  45.942 +        
  45.943 +    def compile_template(self, *a):
  45.944 +        return None
  45.945 +    
  45.946 +    def _compile(self, *a):
  45.947 +        return None
  45.948 +                
  45.949 +class Render:
  45.950 +    """The most preferred way of using templates.
  45.951 +    
  45.952 +        render = web.template.render('templates')
  45.953 +        print render.foo()
  45.954 +        
  45.955 +    Optional parameter can be `base` can be used to pass output of 
  45.956 +    every template through the base template.
  45.957 +    
  45.958 +        render = web.template.render('templates', base='layout')
  45.959 +    """
  45.960 +    def __init__(self, loc='templates', cache=None, base=None, **keywords):
  45.961 +        self._loc = loc
  45.962 +        self._keywords = keywords
  45.963 +
  45.964 +        if cache is None:
  45.965 +            cache = not config.get('debug', False)
  45.966 +        
  45.967 +        if cache:
  45.968 +            self._cache = {}
  45.969 +        else:
  45.970 +            self._cache = None
  45.971 +        
  45.972 +        if base and not hasattr(base, '__call__'):
  45.973 +            # make base a function, so that it can be passed to sub-renders
  45.974 +            self._base = lambda page: self._template(base)(page)
  45.975 +        else:
  45.976 +            self._base = base
  45.977 +    
  45.978 +    def _add_global(self, obj, name=None):
  45.979 +        """Add a global to this rendering instance."""
  45.980 +        if 'globals' not in self._keywords: self._keywords['globals'] = {}
  45.981 +        if not name:
  45.982 +            name = obj.__name__
  45.983 +        self._keywords['globals'][name] = obj
  45.984 +    
  45.985 +    def _lookup(self, name):
  45.986 +        path = os.path.join(self._loc, name)
  45.987 +        if os.path.isdir(path):
  45.988 +            return 'dir', path
  45.989 +        else:
  45.990 +            path = self._findfile(path)
  45.991 +            if path:
  45.992 +                return 'file', path
  45.993 +            else:
  45.994 +                return 'none', None
  45.995 +        
  45.996 +    def _load_template(self, name):
  45.997 +        kind, path = self._lookup(name)
  45.998 +        
  45.999 +        if kind == 'dir':
 45.1000 +            return Render(path, cache=self._cache is not None, base=self._base, **self._keywords)
 45.1001 +        elif kind == 'file':
 45.1002 +            return Template(open(path).read(), filename=path, **self._keywords)
 45.1003 +        else:
 45.1004 +            raise AttributeError, "No template named " + name            
 45.1005 +
 45.1006 +    def _findfile(self, path_prefix): 
 45.1007 +        p = [f for f in glob.glob(path_prefix + '.*') if not f.endswith('~')] # skip backup files
 45.1008 +        p.sort() # sort the matches for deterministic order
 45.1009 +        return p and p[0]
 45.1010 +            
 45.1011 +    def _template(self, name):
 45.1012 +        if self._cache is not None:
 45.1013 +            if name not in self._cache:
 45.1014 +                self._cache[name] = self._load_template(name)
 45.1015 +            return self._cache[name]
 45.1016 +        else:
 45.1017 +            return self._load_template(name)
 45.1018 +        
 45.1019 +    def __getattr__(self, name):
 45.1020 +        t = self._template(name)
 45.1021 +        if self._base and isinstance(t, Template):
 45.1022 +            def template(*a, **kw):
 45.1023 +                return self._base(t(*a, **kw))
 45.1024 +            return template
 45.1025 +        else:
 45.1026 +            return self._template(name)
 45.1027 +
 45.1028 +class GAE_Render(Render):
 45.1029 +    # Render gets over-written. make a copy here.
 45.1030 +    super = Render
 45.1031 +    def __init__(self, loc, *a, **kw):
 45.1032 +        GAE_Render.super.__init__(self, loc, *a, **kw)
 45.1033 +        
 45.1034 +        import types
 45.1035 +        if isinstance(loc, types.ModuleType):
 45.1036 +            self.mod = loc
 45.1037 +        else:
 45.1038 +            name = loc.rstrip('/').replace('/', '.')
 45.1039 +            self.mod = __import__(name, None, None, ['x'])
 45.1040 +
 45.1041 +        self.mod.__dict__.update(kw.get('builtins', TEMPLATE_BUILTINS))
 45.1042 +        self.mod.__dict__.update(Template.globals)
 45.1043 +        self.mod.__dict__.update(kw.get('globals', {}))
 45.1044 +
 45.1045 +    def _load_template(self, name):
 45.1046 +        t = getattr(self.mod, name)
 45.1047 +        import types
 45.1048 +        if isinstance(t, types.ModuleType):
 45.1049 +            return GAE_Render(t, cache=self._cache is not None, base=self._base, **self._keywords)
 45.1050 +        else:
 45.1051 +            return t
 45.1052 +
 45.1053 +render = Render
 45.1054 +# setup render for Google App Engine.
 45.1055 +try:
 45.1056 +    from google import appengine
 45.1057 +    render = Render = GAE_Render
 45.1058 +except ImportError:
 45.1059 +    pass
 45.1060 +        
 45.1061 +def frender(path, **keywords):
 45.1062 +    """Creates a template from the given file path.
 45.1063 +    """
 45.1064 +    return Template(open(path).read(), filename=path, **keywords)
 45.1065 +    
 45.1066 +def compile_templates(root):
 45.1067 +    """Compiles templates to python code."""
 45.1068 +    re_start = re_compile('^', re.M)
 45.1069 +    
 45.1070 +    for dirpath, dirnames, filenames in os.walk(root):
 45.1071 +        filenames = [f for f in filenames if not f.startswith('.') and not f.endswith('~') and not f.startswith('__init__.py')]
 45.1072 +
 45.1073 +        for d in dirnames[:]:
 45.1074 +            if d.startswith('.'):
 45.1075 +                dirnames.remove(d) # don't visit this dir
 45.1076 +
 45.1077 +        out = open(os.path.join(dirpath, '__init__.py'), 'w')
 45.1078 +        out.write('from web.template import CompiledTemplate, ForLoop, TemplateResult\n\n')
 45.1079 +        if dirnames:
 45.1080 +            out.write("import " + ", ".join(dirnames))
 45.1081 +        out.write("\n")
 45.1082 +
 45.1083 +        for f in filenames:
 45.1084 +            path = os.path.join(dirpath, f)
 45.1085 +
 45.1086 +            if '.' in f:
 45.1087 +                name, _ = f.split('.', 1)
 45.1088 +            else:
 45.1089 +                name = f
 45.1090 +                
 45.1091 +            text = open(path).read()
 45.1092 +            text = Template.normalize_text(text)
 45.1093 +            code = Template.generate_code(text, path)
 45.1094 +
 45.1095 +            code = code.replace("__template__", name, 1)
 45.1096 +            
 45.1097 +            out.write(code)
 45.1098 +
 45.1099 +            out.write('\n\n')
 45.1100 +            out.write('%s = CompiledTemplate(%s, %s)\n' % (name, name, repr(path)))
 45.1101 +            out.write("join_ = %s._join; escape_ = %s._escape\n\n" % (name, name))
 45.1102 +
 45.1103 +            # create template to make sure it compiles
 45.1104 +            t = Template(open(path).read(), path)
 45.1105 +        out.close()
 45.1106 +                
 45.1107 +class ParseError(Exception):
 45.1108 +    pass
 45.1109 +    
 45.1110 +class SecurityError(Exception):
 45.1111 +    """The template seems to be trying to do something naughty."""
 45.1112 +    pass
 45.1113 +
 45.1114 +# Enumerate all the allowed AST nodes
 45.1115 +ALLOWED_AST_NODES = [
 45.1116 +    "Add", "And",
 45.1117 +#   "AssAttr",
 45.1118 +    "AssList", "AssName", "AssTuple",
 45.1119 +#   "Assert",
 45.1120 +    "Assign", "AugAssign",
 45.1121 +#   "Backquote",
 45.1122 +    "Bitand", "Bitor", "Bitxor", "Break",
 45.1123 +    "CallFunc","Class", "Compare", "Const", "Continue",
 45.1124 +    "Decorators", "Dict", "Discard", "Div",
 45.1125 +    "Ellipsis", "EmptyNode",
 45.1126 +#   "Exec",
 45.1127 +    "Expression", "FloorDiv", "For",
 45.1128 +#   "From",
 45.1129 +    "Function", 
 45.1130 +    "GenExpr", "GenExprFor", "GenExprIf", "GenExprInner",
 45.1131 +    "Getattr", 
 45.1132 +#   "Global", 
 45.1133 +    "If", "IfExp",
 45.1134 +#   "Import",
 45.1135 +    "Invert", "Keyword", "Lambda", "LeftShift",
 45.1136 +    "List", "ListComp", "ListCompFor", "ListCompIf", "Mod",
 45.1137 +    "Module",
 45.1138 +    "Mul", "Name", "Not", "Or", "Pass", "Power",
 45.1139 +#   "Print", "Printnl", "Raise",
 45.1140 +    "Return", "RightShift", "Slice", "Sliceobj",
 45.1141 +    "Stmt", "Sub", "Subscript",
 45.1142 +#   "TryExcept", "TryFinally",
 45.1143 +    "Tuple", "UnaryAdd", "UnarySub",
 45.1144 +    "While", "With", "Yield",
 45.1145 +]
 45.1146 +
 45.1147 +class SafeVisitor(object):
 45.1148 +    """
 45.1149 +    Make sure code is safe by walking through the AST.
 45.1150 +    
 45.1151 +    Code considered unsafe if:
 45.1152 +        * it has restricted AST nodes
 45.1153 +        * it is trying to access resricted attributes   
 45.1154 +        
 45.1155 +    Adopted from http://www.zafar.se/bkz/uploads/safe.txt (public domain, Babar K. Zafar)
 45.1156 +    """
 45.1157 +    def __init__(self):
 45.1158 +        "Initialize visitor by generating callbacks for all AST node types."
 45.1159 +        self.errors = []
 45.1160 +
 45.1161 +    def walk(self, ast, filename):
 45.1162 +        "Validate each node in AST and raise SecurityError if the code is not safe."
 45.1163 +        self.filename = filename
 45.1164 +        self.visit(ast)
 45.1165 +        
 45.1166 +        if self.errors:        
 45.1167 +            raise SecurityError, '\n'.join([str(err) for err in self.errors])
 45.1168 +        
 45.1169 +    def visit(self, node, *args):
 45.1170 +        "Recursively validate node and all of its children."
 45.1171 +        def classname(obj):
 45.1172 +            return obj.__class__.__name__
 45.1173 +        nodename = classname(node)
 45.1174 +        fn = getattr(self, 'visit' + nodename, None)
 45.1175 +        
 45.1176 +        if fn:
 45.1177 +            fn(node, *args)
 45.1178 +        else:
 45.1179 +            if nodename not in ALLOWED_AST_NODES:
 45.1180 +                self.fail(node, *args)
 45.1181 +            
 45.1182 +        for child in node.getChildNodes():
 45.1183 +            self.visit(child, *args)
 45.1184 +
 45.1185 +    def visitName(self, node, *args):
 45.1186 +        "Disallow any attempts to access a restricted attr."
 45.1187 +        #self.assert_attr(node.getChildren()[0], node)
 45.1188 +        pass
 45.1189 +        
 45.1190 +    def visitGetattr(self, node, *args):
 45.1191 +        "Disallow any attempts to access a restricted attribute."
 45.1192 +        self.assert_attr(node.attrname, node)
 45.1193 +            
 45.1194 +    def assert_attr(self, attrname, node):
 45.1195 +        if self.is_unallowed_attr(attrname):
 45.1196 +            lineno = self.get_node_lineno(node)
 45.1197 +            e = SecurityError("%s:%d - access to attribute '%s' is denied" % (self.filename, lineno, attrname))
 45.1198 +            self.errors.append(e)
 45.1199 +
 45.1200 +    def is_unallowed_attr(self, name):
 45.1201 +        return name.startswith('_') \
 45.1202 +            or name.startswith('func_') \
 45.1203 +            or name.startswith('im_')
 45.1204 +            
 45.1205 +    def get_node_lineno(self, node):
 45.1206 +        return (node.lineno) and node.lineno or 0
 45.1207 +        
 45.1208 +    def fail(self, node, *args):
 45.1209 +        "Default callback for unallowed AST nodes."
 45.1210 +        lineno = self.get_node_lineno(node)
 45.1211 +        nodename = node.__class__.__name__
 45.1212 +        e = SecurityError("%s:%d - execution of '%s' statements is denied" % (self.filename, lineno, nodename))
 45.1213 +        self.errors.append(e)
 45.1214 +
 45.1215 +class TemplateResult(object, DictMixin):
 45.1216 +    """Dictionary like object for storing template output.
 45.1217 +    
 45.1218 +    The result of a template execution is usally a string, but sometimes it
 45.1219 +    contains attributes set using $var. This class provides a simple
 45.1220 +    dictionary like interface for storing the output of the template and the
 45.1221 +    attributes. The output is stored with a special key __body__. Convering
 45.1222 +    the the TemplateResult to string or unicode returns the value of __body__.
 45.1223 +    
 45.1224 +    When the template is in execution, the output is generated part by part
 45.1225 +    and those parts are combined at the end. Parts are added to the
 45.1226 +    TemplateResult by calling the `extend` method and the parts are combined
 45.1227 +    seemlessly when __body__ is accessed.
 45.1228 +    
 45.1229 +        >>> d = TemplateResult(__body__='hello, world', x='foo')
 45.1230 +        >>> d
 45.1231 +        <TemplateResult: {'__body__': 'hello, world', 'x': 'foo'}>
 45.1232 +        >>> print d
 45.1233 +        hello, world
 45.1234 +        >>> d.x
 45.1235 +        'foo'
 45.1236 +        >>> d = TemplateResult()
 45.1237 +        >>> d.extend([u'hello', u'world'])
 45.1238 +        >>> d
 45.1239 +        <TemplateResult: {'__body__': u'helloworld'}>
 45.1240 +    """
 45.1241 +    def __init__(self, *a, **kw):
 45.1242 +        self.__dict__["_d"] = dict(*a, **kw)
 45.1243 +        self._d.setdefault("__body__", u'')
 45.1244 +        
 45.1245 +        self.__dict__['_parts'] = []
 45.1246 +        self.__dict__["extend"] = self._parts.extend
 45.1247 +        
 45.1248 +        self._d.setdefault("__body__", None)
 45.1249 +    
 45.1250 +    def keys(self):
 45.1251 +        return self._d.keys()
 45.1252 +        
 45.1253 +    def _prepare_body(self):
 45.1254 +        """Prepare value of __body__ by joining parts.
 45.1255 +        """
 45.1256 +        if self._parts:
 45.1257 +            value = u"".join(self._parts)
 45.1258 +            self._parts[:] = []
 45.1259 +            body = self._d.get('__body__')
 45.1260 +            if body:
 45.1261 +                self._d['__body__'] = body + value
 45.1262 +            else:
 45.1263 +                self._d['__body__'] = value
 45.1264 +                
 45.1265 +    def __getitem__(self, name):
 45.1266 +        if name == "__body__":
 45.1267 +            self._prepare_body()
 45.1268 +        return self._d[name]
 45.1269 +        
 45.1270 +    def __setitem__(self, name, value):
 45.1271 +        if name == "__body__":
 45.1272 +            self._prepare_body()
 45.1273 +        return self._d.__setitem__(name, value)
 45.1274 +        
 45.1275 +    def __delitem__(self, name):
 45.1276 +        if name == "__body__":
 45.1277 +            self._prepare_body()
 45.1278 +        return self._d.__delitem__(name)
 45.1279 +
 45.1280 +    def __getattr__(self, key): 
 45.1281 +        try:
 45.1282 +            return self[key]
 45.1283 +        except KeyError, k:
 45.1284 +            raise AttributeError, k
 45.1285 +
 45.1286 +    def __setattr__(self, key, value): 
 45.1287 +        self[key] = value
 45.1288 +
 45.1289 +    def __delattr__(self, key):
 45.1290 +        try:
 45.1291 +            del self[key]
 45.1292 +        except KeyError, k:
 45.1293 +            raise AttributeError, k
 45.1294 +        
 45.1295 +    def __unicode__(self):
 45.1296 +        self._prepare_body()
 45.1297 +        return self["__body__"]
 45.1298 +    
 45.1299 +    def __str__(self):
 45.1300 +        self._prepare_body()
 45.1301 +        return self["__body__"].encode('utf-8')
 45.1302 +        
 45.1303 +    def __repr__(self):
 45.1304 +        self._prepare_body()
 45.1305 +        return "<TemplateResult: %s>" % self._d
 45.1306 +
 45.1307 +def test():
 45.1308 +    r"""Doctest for testing template module.
 45.1309 +
 45.1310 +    Define a utility function to run template test.
 45.1311 +    
 45.1312 +        >>> class TestResult:
 45.1313 +        ...     def __init__(self, t): self.t = t
 45.1314 +        ...     def __getattr__(self, name): return getattr(self.t, name)
 45.1315 +        ...     def __repr__(self): return repr(unicode(self))
 45.1316 +        ...
 45.1317 +        >>> def t(code, **keywords):
 45.1318 +        ...     tmpl = Template(code, **keywords)
 45.1319 +        ...     return lambda *a, **kw: TestResult(tmpl(*a, **kw))
 45.1320 +        ...
 45.1321 +    
 45.1322 +    Simple tests.
 45.1323 +    
 45.1324 +        >>> t('1')()
 45.1325 +        u'1\n'
 45.1326 +        >>> t('$def with ()\n1')()
 45.1327 +        u'1\n'
 45.1328 +        >>> t('$def with (a)\n$a')(1)
 45.1329 +        u'1\n'
 45.1330 +        >>> t('$def with (a=0)\n$a')(1)
 45.1331 +        u'1\n'
 45.1332 +        >>> t('$def with (a=0)\n$a')(a=1)
 45.1333 +        u'1\n'
 45.1334 +    
 45.1335 +    Test complicated expressions.
 45.1336 +        
 45.1337 +        >>> t('$def with (x)\n$x.upper()')('hello')
 45.1338 +        u'HELLO\n'
 45.1339 +        >>> t('$(2 * 3 + 4 * 5)')()
 45.1340 +        u'26\n'
 45.1341 +        >>> t('${2 * 3 + 4 * 5}')()
 45.1342 +        u'26\n'
 45.1343 +        >>> t('$def with (limit)\nkeep $(limit)ing.')('go')
 45.1344 +        u'keep going.\n'
 45.1345 +        >>> t('$def with (a)\n$a.b[0]')(storage(b=[1]))
 45.1346 +        u'1\n'
 45.1347 +        
 45.1348 +    Test html escaping.
 45.1349 +    
 45.1350 +        >>> t('$def with (x)\n$x', filename='a.html')('<html>')
 45.1351 +        u'&lt;html&gt;\n'
 45.1352 +        >>> t('$def with (x)\n$x', filename='a.txt')('<html>')
 45.1353 +        u'<html>\n'
 45.1354 +                
 45.1355 +    Test if, for and while.
 45.1356 +    
 45.1357 +        >>> t('$if 1: 1')()
 45.1358 +        u'1\n'
 45.1359 +        >>> t('$if 1:\n    1')()
 45.1360 +        u'1\n'
 45.1361 +        >>> t('$if 1:\n    1\\')()
 45.1362 +        u'1'
 45.1363 +        >>> t('$if 0: 0\n$elif 1: 1')()
 45.1364 +        u'1\n'
 45.1365 +        >>> t('$if 0: 0\n$elif None: 0\n$else: 1')()
 45.1366 +        u'1\n'
 45.1367 +        >>> t('$if 0 < 1 and 1 < 2: 1')()
 45.1368 +        u'1\n'
 45.1369 +        >>> t('$for x in [1, 2, 3]: $x')()
 45.1370 +        u'1\n2\n3\n'
 45.1371 +        >>> t('$def with (d)\n$for k, v in d.iteritems(): $k')({1: 1})
 45.1372 +        u'1\n'
 45.1373 +        >>> t('$for x in [1, 2, 3]:\n\t$x')()
 45.1374 +        u'    1\n    2\n    3\n'
 45.1375 +        >>> t('$def with (a)\n$while a and a.pop():1')([1, 2, 3])
 45.1376 +        u'1\n1\n1\n'
 45.1377 +
 45.1378 +    The space after : must be ignored.
 45.1379 +    
 45.1380 +        >>> t('$if True: foo')()
 45.1381 +        u'foo\n'
 45.1382 +    
 45.1383 +    Test loop.xxx.
 45.1384 +
 45.1385 +        >>> t("$for i in range(5):$loop.index, $loop.parity")()
 45.1386 +        u'1, odd\n2, even\n3, odd\n4, even\n5, odd\n'
 45.1387 +        >>> t("$for i in range(2):\n    $for j in range(2):$loop.parent.parity $loop.parity")()
 45.1388 +        u'odd odd\nodd even\neven odd\neven even\n'
 45.1389 +        
 45.1390 +    Test assignment.
 45.1391 +    
 45.1392 +        >>> t('$ a = 1\n$a')()
 45.1393 +        u'1\n'
 45.1394 +        >>> t('$ a = [1]\n$a[0]')()
 45.1395 +        u'1\n'
 45.1396 +        >>> t('$ a = {1: 1}\n$a.keys()[0]')()
 45.1397 +        u'1\n'
 45.1398 +        >>> t('$ a = []\n$if not a: 1')()
 45.1399 +        u'1\n'
 45.1400 +        >>> t('$ a = {}\n$if not a: 1')()
 45.1401 +        u'1\n'
 45.1402 +        >>> t('$ a = -1\n$a')()
 45.1403 +        u'-1\n'
 45.1404 +        >>> t('$ a = "1"\n$a')()
 45.1405 +        u'1\n'
 45.1406 +
 45.1407 +    Test comments.
 45.1408 +    
 45.1409 +        >>> t('$# 0')()
 45.1410 +        u'\n'
 45.1411 +        >>> t('hello$#comment1\nhello$#comment2')()
 45.1412 +        u'hello\nhello\n'
 45.1413 +        >>> t('$#comment0\nhello$#comment1\nhello$#comment2')()
 45.1414 +        u'\nhello\nhello\n'
 45.1415 +        
 45.1416 +    Test unicode.
 45.1417 +    
 45.1418 +        >>> t('$def with (a)\n$a')(u'\u203d')
 45.1419 +        u'\u203d\n'
 45.1420 +        >>> t('$def with (a)\n$a')(u'\u203d'.encode('utf-8'))
 45.1421 +        u'\u203d\n'
 45.1422 +        >>> t(u'$def with (a)\n$a $:a')(u'\u203d')
 45.1423 +        u'\u203d \u203d\n'
 45.1424 +        >>> t(u'$def with ()\nfoo')()
 45.1425 +        u'foo\n'
 45.1426 +        >>> def f(x): return x
 45.1427 +        ...
 45.1428 +        >>> t(u'$def with (f)\n$:f("x")')(f)
 45.1429 +        u'x\n'
 45.1430 +        >>> t('$def with (f)\n$:f("x")')(f)
 45.1431 +        u'x\n'
 45.1432 +    
 45.1433 +    Test dollar escaping.
 45.1434 +    
 45.1435 +        >>> t("Stop, $$money isn't evaluated.")()
 45.1436 +        u"Stop, $money isn't evaluated.\n"
 45.1437 +        >>> t("Stop, \$money isn't evaluated.")()
 45.1438 +        u"Stop, $money isn't evaluated.\n"
 45.1439 +        
 45.1440 +    Test space sensitivity.
 45.1441 +    
 45.1442 +        >>> t('$def with (x)\n$x')(1)
 45.1443 +        u'1\n'
 45.1444 +        >>> t('$def with(x ,y)\n$x')(1, 1)
 45.1445 +        u'1\n'
 45.1446 +        >>> t('$(1 + 2*3 + 4)')()
 45.1447 +        u'11\n'
 45.1448 +        
 45.1449 +    Make sure globals are working.
 45.1450 +            
 45.1451 +        >>> t('$x')()
 45.1452 +        Traceback (most recent call last):
 45.1453 +            ...
 45.1454 +        NameError: global name 'x' is not defined
 45.1455 +        >>> t('$x', globals={'x': 1})()
 45.1456 +        u'1\n'
 45.1457 +        
 45.1458 +    Can't change globals.
 45.1459 +    
 45.1460 +        >>> t('$ x = 2\n$x', globals={'x': 1})()
 45.1461 +        u'2\n'
 45.1462 +        >>> t('$ x = x + 1\n$x', globals={'x': 1})()
 45.1463 +        Traceback (most recent call last):
 45.1464 +            ...
 45.1465 +        UnboundLocalError: local variable 'x' referenced before assignment
 45.1466 +    
 45.1467 +    Make sure builtins are customizable.
 45.1468 +    
 45.1469 +        >>> t('$min(1, 2)')()
 45.1470 +        u'1\n'
 45.1471 +        >>> t('$min(1, 2)', builtins={})()
 45.1472 +        Traceback (most recent call last):
 45.1473 +            ...
 45.1474 +        NameError: global name 'min' is not defined
 45.1475 +        
 45.1476 +    Test vars.
 45.1477 +    
 45.1478 +        >>> x = t('$var x: 1')()
 45.1479 +        >>> x.x
 45.1480 +        u'1'
 45.1481 +        >>> x = t('$var x = 1')()
 45.1482 +        >>> x.x
 45.1483 +        1
 45.1484 +        >>> x = t('$var x:  \n    foo\n    bar')()
 45.1485 +        >>> x.x
 45.1486 +        u'foo\nbar\n'
 45.1487 +
 45.1488 +    Test BOM chars.
 45.1489 +
 45.1490 +        >>> t('\xef\xbb\xbf$def with(x)\n$x')('foo')
 45.1491 +        u'foo\n'
 45.1492 +
 45.1493 +    Test for with weird cases.
 45.1494 +
 45.1495 +        >>> t('$for i in range(10)[1:5]:\n    $i')()
 45.1496 +        u'1\n2\n3\n4\n'
 45.1497 +        >>> t("$for k, v in {'a': 1, 'b': 2}.items():\n    $k $v")()
 45.1498 +        u'a 1\nb 2\n'
 45.1499 +        >>> t("$for k, v in ({'a': 1, 'b': 2}.items():\n    $k $v")()
 45.1500 +        Traceback (most recent call last):
 45.1501 +            ...
 45.1502 +        SyntaxError: invalid syntax
 45.1503 +
 45.1504 +    Test datetime.
 45.1505 +
 45.1506 +        >>> import datetime
 45.1507 +        >>> t("$def with (date)\n$date.strftime('%m %Y')")(datetime.datetime(2009, 1, 1))
 45.1508 +        u'01 2009\n'
 45.1509 +    """
 45.1510 +    pass
 45.1511 +            
 45.1512 +if __name__ == "__main__":
 45.1513 +    import sys
 45.1514 +    if '--compile' in sys.argv:
 45.1515 +        compile_templates(sys.argv[2])
 45.1516 +    else:
 45.1517 +        import doctest
 45.1518 +        doctest.testmod()
    46.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    46.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/test.py	Mon Dec 02 14:02:05 2013 +0100
    46.3 @@ -0,0 +1,51 @@
    46.4 +"""test utilities
    46.5 +(part of web.py)
    46.6 +"""
    46.7 +import unittest
    46.8 +import sys, os
    46.9 +import web
   46.10 +
   46.11 +TestCase = unittest.TestCase
   46.12 +TestSuite = unittest.TestSuite
   46.13 +
   46.14 +def load_modules(names):
   46.15 +    return [__import__(name, None, None, "x") for name in names]
   46.16 +
   46.17 +def module_suite(module, classnames=None):
   46.18 +    """Makes a suite from a module."""
   46.19 +    if classnames:
   46.20 +        return unittest.TestLoader().loadTestsFromNames(classnames, module)
   46.21 +    elif hasattr(module, 'suite'):
   46.22 +        return module.suite()
   46.23 +    else:
   46.24 +        return unittest.TestLoader().loadTestsFromModule(module)
   46.25 +
   46.26 +def doctest_suite(module_names):
   46.27 +    """Makes a test suite from doctests."""
   46.28 +    import doctest
   46.29 +    suite = TestSuite()
   46.30 +    for mod in load_modules(module_names):
   46.31 +        suite.addTest(doctest.DocTestSuite(mod))
   46.32 +    return suite
   46.33 +    
   46.34 +def suite(module_names):
   46.35 +    """Creates a suite from multiple modules."""
   46.36 +    suite = TestSuite()
   46.37 +    for mod in load_modules(module_names):
   46.38 +        suite.addTest(module_suite(mod))
   46.39 +    return suite
   46.40 +
   46.41 +def runTests(suite):
   46.42 +    runner = unittest.TextTestRunner()
   46.43 +    return runner.run(suite)
   46.44 +
   46.45 +def main(suite=None):
   46.46 +    if not suite:
   46.47 +        main_module = __import__('__main__')
   46.48 +        # allow command line switches
   46.49 +        args = [a for a in sys.argv[1:] if not a.startswith('-')]
   46.50 +        suite = module_suite(main_module, args or None)
   46.51 +
   46.52 +    result = runTests(suite)
   46.53 +    sys.exit(not result.wasSuccessful())
   46.54 +
    47.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    47.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/utils.py	Mon Dec 02 14:02:05 2013 +0100
    47.3 @@ -0,0 +1,1526 @@
    47.4 +#!/usr/bin/env python
    47.5 +"""
    47.6 +General Utilities
    47.7 +(part of web.py)
    47.8 +"""
    47.9 +
   47.10 +__all__ = [
   47.11 +  "Storage", "storage", "storify", 
   47.12 +  "Counter", "counter",
   47.13 +  "iters", 
   47.14 +  "rstrips", "lstrips", "strips", 
   47.15 +  "safeunicode", "safestr", "utf8",
   47.16 +  "TimeoutError", "timelimit",
   47.17 +  "Memoize", "memoize",
   47.18 +  "re_compile", "re_subm",
   47.19 +  "group", "uniq", "iterview",
   47.20 +  "IterBetter", "iterbetter",
   47.21 +  "safeiter", "safewrite",
   47.22 +  "dictreverse", "dictfind", "dictfindall", "dictincr", "dictadd",
   47.23 +  "requeue", "restack",
   47.24 +  "listget", "intget", "datestr",
   47.25 +  "numify", "denumify", "commify", "dateify",
   47.26 +  "nthstr", "cond",
   47.27 +  "CaptureStdout", "capturestdout", "Profile", "profile",
   47.28 +  "tryall",
   47.29 +  "ThreadedDict", "threadeddict",
   47.30 +  "autoassign",
   47.31 +  "to36",
   47.32 +  "safemarkdown",
   47.33 +  "sendmail"
   47.34 +]
   47.35 +
   47.36 +import re, sys, time, threading, itertools, traceback, os
   47.37 +
   47.38 +try:
   47.39 +    import subprocess
   47.40 +except ImportError: 
   47.41 +    subprocess = None
   47.42 +
   47.43 +try: import datetime
   47.44 +except ImportError: pass
   47.45 +
   47.46 +try: set
   47.47 +except NameError:
   47.48 +    from sets import Set as set
   47.49 +    
   47.50 +try:
   47.51 +    from threading import local as threadlocal
   47.52 +except ImportError:
   47.53 +    from python23 import threadlocal
   47.54 +
   47.55 +class Storage(dict):
   47.56 +    """
   47.57 +    A Storage object is like a dictionary except `obj.foo` can be used
   47.58 +    in addition to `obj['foo']`.
   47.59 +    
   47.60 +        >>> o = storage(a=1)
   47.61 +        >>> o.a
   47.62 +        1
   47.63 +        >>> o['a']
   47.64 +        1
   47.65 +        >>> o.a = 2
   47.66 +        >>> o['a']
   47.67 +        2
   47.68 +        >>> del o.a
   47.69 +        >>> o.a
   47.70 +        Traceback (most recent call last):
   47.71 +            ...
   47.72 +        AttributeError: 'a'
   47.73 +    
   47.74 +    """
   47.75 +    def __getattr__(self, key): 
   47.76 +        try:
   47.77 +            return self[key]
   47.78 +        except KeyError, k:
   47.79 +            raise AttributeError, k
   47.80 +    
   47.81 +    def __setattr__(self, key, value): 
   47.82 +        self[key] = value
   47.83 +    
   47.84 +    def __delattr__(self, key):
   47.85 +        try:
   47.86 +            del self[key]
   47.87 +        except KeyError, k:
   47.88 +            raise AttributeError, k
   47.89 +    
   47.90 +    def __repr__(self):     
   47.91 +        return '<Storage ' + dict.__repr__(self) + '>'
   47.92 +
   47.93 +storage = Storage
   47.94 +
   47.95 +def storify(mapping, *requireds, **defaults):
   47.96 +    """
   47.97 +    Creates a `storage` object from dictionary `mapping`, raising `KeyError` if
   47.98 +    d doesn't have all of the keys in `requireds` and using the default 
   47.99 +    values for keys found in `defaults`.
  47.100 +
  47.101 +    For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of
  47.102 +    `storage({'a':1, 'b':2, 'c':3})`.
  47.103 +    
  47.104 +    If a `storify` value is a list (e.g. multiple values in a form submission), 
  47.105 +    `storify` returns the last element of the list, unless the key appears in 
  47.106 +    `defaults` as a list. Thus:
  47.107 +    
  47.108 +        >>> storify({'a':[1, 2]}).a
  47.109 +        2
  47.110 +        >>> storify({'a':[1, 2]}, a=[]).a
  47.111 +        [1, 2]
  47.112 +        >>> storify({'a':1}, a=[]).a
  47.113 +        [1]
  47.114 +        >>> storify({}, a=[]).a
  47.115 +        []
  47.116 +    
  47.117 +    Similarly, if the value has a `value` attribute, `storify will return _its_
  47.118 +    value, unless the key appears in `defaults` as a dictionary.
  47.119 +    
  47.120 +        >>> storify({'a':storage(value=1)}).a
  47.121 +        1
  47.122 +        >>> storify({'a':storage(value=1)}, a={}).a
  47.123 +        <Storage {'value': 1}>
  47.124 +        >>> storify({}, a={}).a
  47.125 +        {}
  47.126 +        
  47.127 +    Optionally, keyword parameter `_unicode` can be passed to convert all values to unicode.
  47.128 +    
  47.129 +        >>> storify({'x': 'a'}, _unicode=True)
  47.130 +        <Storage {'x': u'a'}>
  47.131 +        >>> storify({'x': storage(value='a')}, x={}, _unicode=True)
  47.132 +        <Storage {'x': <Storage {'value': 'a'}>}>
  47.133 +        >>> storify({'x': storage(value='a')}, _unicode=True)
  47.134 +        <Storage {'x': u'a'}>
  47.135 +    """
  47.136 +    _unicode = defaults.pop('_unicode', False)
  47.137 +
  47.138 +    # if _unicode is callable object, use it convert a string to unicode.
  47.139 +    to_unicode = safeunicode
  47.140 +    if _unicode is not False and hasattr(_unicode, "__call__"):
  47.141 +        to_unicode = _unicode
  47.142 +    
  47.143 +    def unicodify(s):
  47.144 +        if _unicode and isinstance(s, str): return to_unicode(s)
  47.145 +        else: return s
  47.146 +        
  47.147 +    def getvalue(x):
  47.148 +        if hasattr(x, 'file') and hasattr(x, 'value'):
  47.149 +            return x.value
  47.150 +        elif hasattr(x, 'value'):
  47.151 +            return unicodify(x.value)
  47.152 +        else:
  47.153 +            return unicodify(x)
  47.154 +    
  47.155 +    stor = Storage()
  47.156 +    for key in requireds + tuple(mapping.keys()):
  47.157 +        value = mapping[key]
  47.158 +        if isinstance(value, list):
  47.159 +            if isinstance(defaults.get(key), list):
  47.160 +                value = [getvalue(x) for x in value]
  47.161 +            else:
  47.162 +                value = value[-1]
  47.163 +        if not isinstance(defaults.get(key), dict):
  47.164 +            value = getvalue(value)
  47.165 +        if isinstance(defaults.get(key), list) and not isinstance(value, list):
  47.166 +            value = [value]
  47.167 +        setattr(stor, key, value)
  47.168 +
  47.169 +    for (key, value) in defaults.iteritems():
  47.170 +        result = value
  47.171 +        if hasattr(stor, key): 
  47.172 +            result = stor[key]
  47.173 +        if value == () and not isinstance(result, tuple): 
  47.174 +            result = (result,)
  47.175 +        setattr(stor, key, result)
  47.176 +    
  47.177 +    return stor
  47.178 +
  47.179 +class Counter(storage):
  47.180 +    """Keeps count of how many times something is added.
  47.181 +        
  47.182 +        >>> c = counter()
  47.183 +        >>> c.add('x')
  47.184 +        >>> c.add('x')
  47.185 +        >>> c.add('x')
  47.186 +        >>> c.add('x')
  47.187 +        >>> c.add('x')
  47.188 +        >>> c.add('y')
  47.189 +        >>> c
  47.190 +        <Counter {'y': 1, 'x': 5}>
  47.191 +        >>> c.most()
  47.192 +        ['x']
  47.193 +    """
  47.194 +    def add(self, n):
  47.195 +        self.setdefault(n, 0)
  47.196 +        self[n] += 1
  47.197 +    
  47.198 +    def most(self):
  47.199 +        """Returns the keys with maximum count."""
  47.200 +        m = max(self.itervalues())
  47.201 +        return [k for k, v in self.iteritems() if v == m]
  47.202 +        
  47.203 +    def least(self):
  47.204 +        """Returns the keys with mininum count."""
  47.205 +        m = min(self.itervalues())
  47.206 +        return [k for k, v in self.iteritems() if v == m]
  47.207 +
  47.208 +    def percent(self, key):
  47.209 +       """Returns what percentage a certain key is of all entries.
  47.210 +
  47.211 +           >>> c = counter()
  47.212 +           >>> c.add('x')
  47.213 +           >>> c.add('x')
  47.214 +           >>> c.add('x')
  47.215 +           >>> c.add('y')
  47.216 +           >>> c.percent('x')
  47.217 +           0.75
  47.218 +           >>> c.percent('y')
  47.219 +           0.25
  47.220 +       """
  47.221 +       return float(self[key])/sum(self.values())
  47.222 +             
  47.223 +    def sorted_keys(self):
  47.224 +        """Returns keys sorted by value.
  47.225 +             
  47.226 +             >>> c = counter()
  47.227 +             >>> c.add('x')
  47.228 +             >>> c.add('x')
  47.229 +             >>> c.add('y')
  47.230 +             >>> c.sorted_keys()
  47.231 +             ['x', 'y']
  47.232 +        """
  47.233 +        return sorted(self.keys(), key=lambda k: self[k], reverse=True)
  47.234 +    
  47.235 +    def sorted_values(self):
  47.236 +        """Returns values sorted by value.
  47.237 +            
  47.238 +            >>> c = counter()
  47.239 +            >>> c.add('x')
  47.240 +            >>> c.add('x')
  47.241 +            >>> c.add('y')
  47.242 +            >>> c.sorted_values()
  47.243 +            [2, 1]
  47.244 +        """
  47.245 +        return [self[k] for k in self.sorted_keys()]
  47.246 +    
  47.247 +    def sorted_items(self):
  47.248 +        """Returns items sorted by value.
  47.249 +            
  47.250 +            >>> c = counter()
  47.251 +            >>> c.add('x')
  47.252 +            >>> c.add('x')
  47.253 +            >>> c.add('y')
  47.254 +            >>> c.sorted_items()
  47.255 +            [('x', 2), ('y', 1)]
  47.256 +        """
  47.257 +        return [(k, self[k]) for k in self.sorted_keys()]
  47.258 +    
  47.259 +    def __repr__(self):
  47.260 +        return '<Counter ' + dict.__repr__(self) + '>'
  47.261 +       
  47.262 +counter = Counter
  47.263 +
  47.264 +iters = [list, tuple]
  47.265 +import __builtin__
  47.266 +if hasattr(__builtin__, 'set'):
  47.267 +    iters.append(set)
  47.268 +if hasattr(__builtin__, 'frozenset'):
  47.269 +    iters.append(set)
  47.270 +if sys.version_info < (2,6): # sets module deprecated in 2.6
  47.271 +    try:
  47.272 +        from sets import Set
  47.273 +        iters.append(Set)
  47.274 +    except ImportError: 
  47.275 +        pass
  47.276 +    
  47.277 +class _hack(tuple): pass
  47.278 +iters = _hack(iters)
  47.279 +iters.__doc__ = """
  47.280 +A list of iterable items (like lists, but not strings). Includes whichever
  47.281 +of lists, tuples, sets, and Sets are available in this version of Python.
  47.282 +"""
  47.283 +
  47.284 +def _strips(direction, text, remove):
  47.285 +    if isinstance(remove, iters):
  47.286 +        for subr in remove:
  47.287 +            text = _strips(direction, text, subr)
  47.288 +        return text
  47.289 +    
  47.290 +    if direction == 'l': 
  47.291 +        if text.startswith(remove): 
  47.292 +            return text[len(remove):]
  47.293 +    elif direction == 'r':
  47.294 +        if text.endswith(remove):   
  47.295 +            return text[:-len(remove)]
  47.296 +    else: 
  47.297 +        raise ValueError, "Direction needs to be r or l."
  47.298 +    return text
  47.299 +
  47.300 +def rstrips(text, remove):
  47.301 +    """
  47.302 +    removes the string `remove` from the right of `text`
  47.303 +
  47.304 +        >>> rstrips("foobar", "bar")
  47.305 +        'foo'
  47.306 +    
  47.307 +    """
  47.308 +    return _strips('r', text, remove)
  47.309 +
  47.310 +def lstrips(text, remove):
  47.311 +    """
  47.312 +    removes the string `remove` from the left of `text`
  47.313 +    
  47.314 +        >>> lstrips("foobar", "foo")
  47.315 +        'bar'
  47.316 +        >>> lstrips('http://foo.org/', ['http://', 'https://'])
  47.317 +        'foo.org/'
  47.318 +        >>> lstrips('FOOBARBAZ', ['FOO', 'BAR'])
  47.319 +        'BAZ'
  47.320 +        >>> lstrips('FOOBARBAZ', ['BAR', 'FOO'])
  47.321 +        'BARBAZ'
  47.322 +    
  47.323 +    """
  47.324 +    return _strips('l', text, remove)
  47.325 +
  47.326 +def strips(text, remove):
  47.327 +    """
  47.328 +    removes the string `remove` from the both sides of `text`
  47.329 +
  47.330 +        >>> strips("foobarfoo", "foo")
  47.331 +        'bar'
  47.332 +    
  47.333 +    """
  47.334 +    return rstrips(lstrips(text, remove), remove)
  47.335 +
  47.336 +def safeunicode(obj, encoding='utf-8'):
  47.337 +    r"""
  47.338 +    Converts any given object to unicode string.
  47.339 +    
  47.340 +        >>> safeunicode('hello')
  47.341 +        u'hello'
  47.342 +        >>> safeunicode(2)
  47.343 +        u'2'
  47.344 +        >>> safeunicode('\xe1\x88\xb4')
  47.345 +        u'\u1234'
  47.346 +    """
  47.347 +    t = type(obj)
  47.348 +    if t is unicode:
  47.349 +        return obj
  47.350 +    elif t is str:
  47.351 +        return obj.decode(encoding)
  47.352 +    elif t in [int, float, bool]:
  47.353 +        return unicode(obj)
  47.354 +    elif hasattr(obj, '__unicode__') or isinstance(obj, unicode):
  47.355 +        return unicode(obj)
  47.356 +    else:
  47.357 +        return str(obj).decode(encoding)
  47.358 +    
  47.359 +def safestr(obj, encoding='utf-8'):
  47.360 +    r"""
  47.361 +    Converts any given object to utf-8 encoded string. 
  47.362 +    
  47.363 +        >>> safestr('hello')
  47.364 +        'hello'
  47.365 +        >>> safestr(u'\u1234')
  47.366 +        '\xe1\x88\xb4'
  47.367 +        >>> safestr(2)
  47.368 +        '2'
  47.369 +    """
  47.370 +    if isinstance(obj, unicode):
  47.371 +        return obj.encode(encoding)
  47.372 +    elif isinstance(obj, str):
  47.373 +        return obj
  47.374 +    elif hasattr(obj, 'next'): # iterator
  47.375 +        return itertools.imap(safestr, obj)
  47.376 +    else:
  47.377 +        return str(obj)
  47.378 +
  47.379 +# for backward-compatibility
  47.380 +utf8 = safestr
  47.381 +    
  47.382 +class TimeoutError(Exception): pass
  47.383 +def timelimit(timeout):
  47.384 +    """
  47.385 +    A decorator to limit a function to `timeout` seconds, raising `TimeoutError`
  47.386 +    if it takes longer.
  47.387 +    
  47.388 +        >>> import time
  47.389 +        >>> def meaningoflife():
  47.390 +        ...     time.sleep(.2)
  47.391 +        ...     return 42
  47.392 +        >>> 
  47.393 +        >>> timelimit(.1)(meaningoflife)()
  47.394 +        Traceback (most recent call last):
  47.395 +            ...
  47.396 +        TimeoutError: took too long
  47.397 +        >>> timelimit(1)(meaningoflife)()
  47.398 +        42
  47.399 +
  47.400 +    _Caveat:_ The function isn't stopped after `timeout` seconds but continues 
  47.401 +    executing in a separate thread. (There seems to be no way to kill a thread.)
  47.402 +
  47.403 +    inspired by <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473878>
  47.404 +    """
  47.405 +    def _1(function):
  47.406 +        def _2(*args, **kw):
  47.407 +            class Dispatch(threading.Thread):
  47.408 +                def __init__(self):
  47.409 +                    threading.Thread.__init__(self)
  47.410 +                    self.result = None
  47.411 +                    self.error = None
  47.412 +
  47.413 +                    self.setDaemon(True)
  47.414 +                    self.start()
  47.415 +
  47.416 +                def run(self):
  47.417 +                    try:
  47.418 +                        self.result = function(*args, **kw)
  47.419 +                    except:
  47.420 +                        self.error = sys.exc_info()
  47.421 +
  47.422 +            c = Dispatch()
  47.423 +            c.join(timeout)
  47.424 +            if c.isAlive():
  47.425 +                raise TimeoutError, 'took too long'
  47.426 +            if c.error:
  47.427 +                raise c.error[0], c.error[1]
  47.428 +            return c.result
  47.429 +        return _2
  47.430 +    return _1
  47.431 +
  47.432 +class Memoize:
  47.433 +    """
  47.434 +    'Memoizes' a function, caching its return values for each input.
  47.435 +    If `expires` is specified, values are recalculated after `expires` seconds.
  47.436 +    If `background` is specified, values are recalculated in a separate thread.
  47.437 +    
  47.438 +        >>> calls = 0
  47.439 +        >>> def howmanytimeshaveibeencalled():
  47.440 +        ...     global calls
  47.441 +        ...     calls += 1
  47.442 +        ...     return calls
  47.443 +        >>> fastcalls = memoize(howmanytimeshaveibeencalled)
  47.444 +        >>> howmanytimeshaveibeencalled()
  47.445 +        1
  47.446 +        >>> howmanytimeshaveibeencalled()
  47.447 +        2
  47.448 +        >>> fastcalls()
  47.449 +        3
  47.450 +        >>> fastcalls()
  47.451 +        3
  47.452 +        >>> import time
  47.453 +        >>> fastcalls = memoize(howmanytimeshaveibeencalled, .1, background=False)
  47.454 +        >>> fastcalls()
  47.455 +        4
  47.456 +        >>> fastcalls()
  47.457 +        4
  47.458 +        >>> time.sleep(.2)
  47.459 +        >>> fastcalls()
  47.460 +        5
  47.461 +        >>> def slowfunc():
  47.462 +        ...     time.sleep(.1)
  47.463 +        ...     return howmanytimeshaveibeencalled()
  47.464 +        >>> fastcalls = memoize(slowfunc, .2, background=True)
  47.465 +        >>> fastcalls()
  47.466 +        6
  47.467 +        >>> timelimit(.05)(fastcalls)()
  47.468 +        6
  47.469 +        >>> time.sleep(.2)
  47.470 +        >>> timelimit(.05)(fastcalls)()
  47.471 +        6
  47.472 +        >>> timelimit(.05)(fastcalls)()
  47.473 +        6
  47.474 +        >>> time.sleep(.2)
  47.475 +        >>> timelimit(.05)(fastcalls)()
  47.476 +        7
  47.477 +        >>> fastcalls = memoize(slowfunc, None, background=True)
  47.478 +        >>> threading.Thread(target=fastcalls).start()
  47.479 +        >>> time.sleep(.01)
  47.480 +        >>> fastcalls()
  47.481 +        9
  47.482 +    """
  47.483 +    def __init__(self, func, expires=None, background=True): 
  47.484 +        self.func = func
  47.485 +        self.cache = {}
  47.486 +        self.expires = expires
  47.487 +        self.background = background
  47.488 +        self.running = {}
  47.489 +    
  47.490 +    def __call__(self, *args, **keywords):
  47.491 +        key = (args, tuple(keywords.items()))
  47.492 +        if not self.running.get(key):
  47.493 +            self.running[key] = threading.Lock()
  47.494 +        def update(block=False):
  47.495 +            if self.running[key].acquire(block):
  47.496 +                try:
  47.497 +                    self.cache[key] = (self.func(*args, **keywords), time.time())
  47.498 +                finally:
  47.499 +                    self.running[key].release()
  47.500 +        
  47.501 +        if key not in self.cache: 
  47.502 +            update(block=True)
  47.503 +        elif self.expires and (time.time() - self.cache[key][1]) > self.expires:
  47.504 +            if self.background:
  47.505 +                threading.Thread(target=update).start()
  47.506 +            else:
  47.507 +                update()
  47.508 +        return self.cache[key][0]
  47.509 +
  47.510 +memoize = Memoize
  47.511 +
  47.512 +re_compile = memoize(re.compile) #@@ threadsafe?
  47.513 +re_compile.__doc__ = """
  47.514 +A memoized version of re.compile.
  47.515 +"""
  47.516 +
  47.517 +class _re_subm_proxy:
  47.518 +    def __init__(self): 
  47.519 +        self.match = None
  47.520 +    def __call__(self, match): 
  47.521 +        self.match = match
  47.522 +        return ''
  47.523 +
  47.524 +def re_subm(pat, repl, string):
  47.525 +    """
  47.526 +    Like re.sub, but returns the replacement _and_ the match object.
  47.527 +    
  47.528 +        >>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball')
  47.529 +        >>> t
  47.530 +        'foooooolish'
  47.531 +        >>> m.groups()
  47.532 +        ('oooooo',)
  47.533 +    """
  47.534 +    compiled_pat = re_compile(pat)
  47.535 +    proxy = _re_subm_proxy()
  47.536 +    compiled_pat.sub(proxy.__call__, string)
  47.537 +    return compiled_pat.sub(repl, string), proxy.match
  47.538 +
  47.539 +def group(seq, size): 
  47.540 +    """
  47.541 +    Returns an iterator over a series of lists of length size from iterable.
  47.542 +
  47.543 +        >>> list(group([1,2,3,4], 2))
  47.544 +        [[1, 2], [3, 4]]
  47.545 +        >>> list(group([1,2,3,4,5], 2))
  47.546 +        [[1, 2], [3, 4], [5]]
  47.547 +    """
  47.548 +    def take(seq, n):
  47.549 +        for i in xrange(n):
  47.550 +            yield seq.next()
  47.551 +
  47.552 +    if not hasattr(seq, 'next'):  
  47.553 +        seq = iter(seq)
  47.554 +    while True: 
  47.555 +        x = list(take(seq, size))
  47.556 +        if x:
  47.557 +            yield x
  47.558 +        else:
  47.559 +            break
  47.560 +
  47.561 +def uniq(seq, key=None):
  47.562 +    """
  47.563 +    Removes duplicate elements from a list while preserving the order of the rest.
  47.564 +
  47.565 +        >>> uniq([9,0,2,1,0])
  47.566 +        [9, 0, 2, 1]
  47.567 +
  47.568 +    The value of the optional `key` parameter should be a function that
  47.569 +    takes a single argument and returns a key to test the uniqueness.
  47.570 +
  47.571 +        >>> uniq(["Foo", "foo", "bar"], key=lambda s: s.lower())
  47.572 +        ['Foo', 'bar']
  47.573 +    """
  47.574 +    key = key or (lambda x: x)
  47.575 +    seen = set()
  47.576 +    result = []
  47.577 +    for v in seq:
  47.578 +        k = key(v)
  47.579 +        if k in seen:
  47.580 +            continue
  47.581 +        seen.add(k)
  47.582 +        result.append(v)
  47.583 +    return result
  47.584 +
  47.585 +def iterview(x):
  47.586 +   """
  47.587 +   Takes an iterable `x` and returns an iterator over it
  47.588 +   which prints its progress to stderr as it iterates through.
  47.589 +   """
  47.590 +   WIDTH = 70
  47.591 +
  47.592 +   def plainformat(n, lenx):
  47.593 +       return '%5.1f%% (%*d/%d)' % ((float(n)/lenx)*100, len(str(lenx)), n, lenx)
  47.594 +
  47.595 +   def bars(size, n, lenx):
  47.596 +       val = int((float(n)*size)/lenx + 0.5)
  47.597 +       if size - val:
  47.598 +           spacing = ">" + (" "*(size-val))[1:]
  47.599 +       else:
  47.600 +           spacing = ""
  47.601 +       return "[%s%s]" % ("="*val, spacing)
  47.602 +
  47.603 +   def eta(elapsed, n, lenx):
  47.604 +       if n == 0:
  47.605 +           return '--:--:--'
  47.606 +       if n == lenx:
  47.607 +           secs = int(elapsed)
  47.608 +       else:
  47.609 +           secs = int((elapsed/n) * (lenx-n))
  47.610 +       mins, secs = divmod(secs, 60)
  47.611 +       hrs, mins = divmod(mins, 60)
  47.612 +
  47.613 +       return '%02d:%02d:%02d' % (hrs, mins, secs)
  47.614 +
  47.615 +   def format(starttime, n, lenx):
  47.616 +       out = plainformat(n, lenx) + ' '
  47.617 +       if n == lenx:
  47.618 +           end = '     '
  47.619 +       else:
  47.620 +           end = ' ETA '
  47.621 +       end += eta(time.time() - starttime, n, lenx)
  47.622 +       out += bars(WIDTH - len(out) - len(end), n, lenx)
  47.623 +       out += end
  47.624 +       return out
  47.625 +
  47.626 +   starttime = time.time()
  47.627 +   lenx = len(x)
  47.628 +   for n, y in enumerate(x):
  47.629 +       sys.stderr.write('\r' + format(starttime, n, lenx))
  47.630 +       yield y
  47.631 +   sys.stderr.write('\r' + format(starttime, n+1, lenx) + '\n')
  47.632 +
  47.633 +class IterBetter:
  47.634 +    """
  47.635 +    Returns an object that can be used as an iterator 
  47.636 +    but can also be used via __getitem__ (although it 
  47.637 +    cannot go backwards -- that is, you cannot request 
  47.638 +    `iterbetter[0]` after requesting `iterbetter[1]`).
  47.639 +    
  47.640 +        >>> import itertools
  47.641 +        >>> c = iterbetter(itertools.count())
  47.642 +        >>> c[1]
  47.643 +        1
  47.644 +        >>> c[5]
  47.645 +        5
  47.646 +        >>> c[3]
  47.647 +        Traceback (most recent call last):
  47.648 +            ...
  47.649 +        IndexError: already passed 3
  47.650 +
  47.651 +    For boolean test, IterBetter peeps at first value in the itertor without effecting the iteration.
  47.652 +
  47.653 +        >>> c = iterbetter(iter(range(5)))
  47.654 +        >>> bool(c)
  47.655 +        True
  47.656 +        >>> list(c)
  47.657 +        [0, 1, 2, 3, 4]
  47.658 +        >>> c = iterbetter(iter([]))
  47.659 +        >>> bool(c)
  47.660 +        False
  47.661 +        >>> list(c)
  47.662 +        []
  47.663 +    """
  47.664 +    def __init__(self, iterator): 
  47.665 +        self.i, self.c = iterator, 0
  47.666 +
  47.667 +    def __iter__(self): 
  47.668 +        if hasattr(self, "_head"):
  47.669 +            yield self._head
  47.670 +
  47.671 +        while 1:    
  47.672 +            yield self.i.next()
  47.673 +            self.c += 1
  47.674 +
  47.675 +    def __getitem__(self, i):
  47.676 +        #todo: slices
  47.677 +        if i < self.c: 
  47.678 +            raise IndexError, "already passed "+str(i)
  47.679 +        try:
  47.680 +            while i > self.c: 
  47.681 +                self.i.next()
  47.682 +                self.c += 1
  47.683 +            # now self.c == i
  47.684 +            self.c += 1
  47.685 +            return self.i.next()
  47.686 +        except StopIteration: 
  47.687 +            raise IndexError, str(i)
  47.688 +            
  47.689 +    def __nonzero__(self):
  47.690 +        if hasattr(self, "__len__"):
  47.691 +            return len(self) != 0
  47.692 +        elif hasattr(self, "_head"):
  47.693 +            return True
  47.694 +        else:
  47.695 +            try:
  47.696 +                self._head = self.i.next()
  47.697 +            except StopIteration:
  47.698 +                return False
  47.699 +            else:
  47.700 +                return True
  47.701 +
  47.702 +iterbetter = IterBetter
  47.703 +
  47.704 +def safeiter(it, cleanup=None, ignore_errors=True):
  47.705 +    """Makes an iterator safe by ignoring the exceptions occured during the iteration.
  47.706 +    """
  47.707 +    def next():
  47.708 +        while True:
  47.709 +            try:
  47.710 +                return it.next()
  47.711 +            except StopIteration:
  47.712 +                raise
  47.713 +            except:
  47.714 +                traceback.print_exc()
  47.715 +
  47.716 +    it = iter(it)
  47.717 +    while True:
  47.718 +        yield next()
  47.719 +
  47.720 +def safewrite(filename, content):
  47.721 +    """Writes the content to a temp file and then moves the temp file to 
  47.722 +    given filename to avoid overwriting the existing file in case of errors.
  47.723 +    """
  47.724 +    f = file(filename + '.tmp', 'w')
  47.725 +    f.write(content)
  47.726 +    f.close()
  47.727 +    os.rename(f.name, filename)
  47.728 +
  47.729 +def dictreverse(mapping):
  47.730 +    """
  47.731 +    Returns a new dictionary with keys and values swapped.
  47.732 +    
  47.733 +        >>> dictreverse({1: 2, 3: 4})
  47.734 +        {2: 1, 4: 3}
  47.735 +    """
  47.736 +    return dict([(value, key) for (key, value) in mapping.iteritems()])
  47.737 +
  47.738 +def dictfind(dictionary, element):
  47.739 +    """
  47.740 +    Returns a key whose value in `dictionary` is `element` 
  47.741 +    or, if none exists, None.
  47.742 +    
  47.743 +        >>> d = {1:2, 3:4}
  47.744 +        >>> dictfind(d, 4)
  47.745 +        3
  47.746 +        >>> dictfind(d, 5)
  47.747 +    """
  47.748 +    for (key, value) in dictionary.iteritems():
  47.749 +        if element is value: 
  47.750 +            return key
  47.751 +
  47.752 +def dictfindall(dictionary, element):
  47.753 +    """
  47.754 +    Returns the keys whose values in `dictionary` are `element`
  47.755 +    or, if none exists, [].
  47.756 +    
  47.757 +        >>> d = {1:4, 3:4}
  47.758 +        >>> dictfindall(d, 4)
  47.759 +        [1, 3]
  47.760 +        >>> dictfindall(d, 5)
  47.761 +        []
  47.762 +    """
  47.763 +    res = []
  47.764 +    for (key, value) in dictionary.iteritems():
  47.765 +        if element is value:
  47.766 +            res.append(key)
  47.767 +    return res
  47.768 +
  47.769 +def dictincr(dictionary, element):
  47.770 +    """
  47.771 +    Increments `element` in `dictionary`, 
  47.772 +    setting it to one if it doesn't exist.
  47.773 +    
  47.774 +        >>> d = {1:2, 3:4}
  47.775 +        >>> dictincr(d, 1)
  47.776 +        3
  47.777 +        >>> d[1]
  47.778 +        3
  47.779 +        >>> dictincr(d, 5)
  47.780 +        1
  47.781 +        >>> d[5]
  47.782 +        1
  47.783 +    """
  47.784 +    dictionary.setdefault(element, 0)
  47.785 +    dictionary[element] += 1
  47.786 +    return dictionary[element]
  47.787 +
  47.788 +def dictadd(*dicts):
  47.789 +    """
  47.790 +    Returns a dictionary consisting of the keys in the argument dictionaries.
  47.791 +    If they share a key, the value from the last argument is used.
  47.792 +    
  47.793 +        >>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1})
  47.794 +        {1: 0, 2: 1, 3: 1}
  47.795 +    """
  47.796 +    result = {}
  47.797 +    for dct in dicts:
  47.798 +        result.update(dct)
  47.799 +    return result
  47.800 +
  47.801 +def requeue(queue, index=-1):
  47.802 +    """Returns the element at index after moving it to the beginning of the queue.
  47.803 +
  47.804 +        >>> x = [1, 2, 3, 4]
  47.805 +        >>> requeue(x)
  47.806 +        4
  47.807 +        >>> x
  47.808 +        [4, 1, 2, 3]
  47.809 +    """
  47.810 +    x = queue.pop(index)
  47.811 +    queue.insert(0, x)
  47.812 +    return x
  47.813 +
  47.814 +def restack(stack, index=0):
  47.815 +    """Returns the element at index after moving it to the top of stack.
  47.816 +
  47.817 +           >>> x = [1, 2, 3, 4]
  47.818 +           >>> restack(x)
  47.819 +           1
  47.820 +           >>> x
  47.821 +           [2, 3, 4, 1]
  47.822 +    """
  47.823 +    x = stack.pop(index)
  47.824 +    stack.append(x)
  47.825 +    return x
  47.826 +
  47.827 +def listget(lst, ind, default=None):
  47.828 +    """
  47.829 +    Returns `lst[ind]` if it exists, `default` otherwise.
  47.830 +    
  47.831 +        >>> listget(['a'], 0)
  47.832 +        'a'
  47.833 +        >>> listget(['a'], 1)
  47.834 +        >>> listget(['a'], 1, 'b')
  47.835 +        'b'
  47.836 +    """
  47.837 +    if len(lst)-1 < ind: 
  47.838 +        return default
  47.839 +    return lst[ind]
  47.840 +
  47.841 +def intget(integer, default=None):
  47.842 +    """
  47.843 +    Returns `integer` as an int or `default` if it can't.
  47.844 +    
  47.845 +        >>> intget('3')
  47.846 +        3
  47.847 +        >>> intget('3a')
  47.848 +        >>> intget('3a', 0)
  47.849 +        0
  47.850 +    """
  47.851 +    try:
  47.852 +        return int(integer)
  47.853 +    except (TypeError, ValueError):
  47.854 +        return default
  47.855 +
  47.856 +def datestr(then, now=None):
  47.857 +    """
  47.858 +    Converts a (UTC) datetime object to a nice string representation.
  47.859 +    
  47.860 +        >>> from datetime import datetime, timedelta
  47.861 +        >>> d = datetime(1970, 5, 1)
  47.862 +        >>> datestr(d, now=d)
  47.863 +        '0 microseconds ago'
  47.864 +        >>> for t, v in {
  47.865 +        ...   timedelta(microseconds=1): '1 microsecond ago',
  47.866 +        ...   timedelta(microseconds=2): '2 microseconds ago',
  47.867 +        ...   -timedelta(microseconds=1): '1 microsecond from now',
  47.868 +        ...   -timedelta(microseconds=2): '2 microseconds from now',
  47.869 +        ...   timedelta(microseconds=2000): '2 milliseconds ago',
  47.870 +        ...   timedelta(seconds=2): '2 seconds ago',
  47.871 +        ...   timedelta(seconds=2*60): '2 minutes ago',
  47.872 +        ...   timedelta(seconds=2*60*60): '2 hours ago',
  47.873 +        ...   timedelta(days=2): '2 days ago',
  47.874 +        ... }.iteritems():
  47.875 +        ...     assert datestr(d, now=d+t) == v
  47.876 +        >>> datestr(datetime(1970, 1, 1), now=d)
  47.877 +        'January  1'
  47.878 +        >>> datestr(datetime(1969, 1, 1), now=d)
  47.879 +        'January  1, 1969'
  47.880 +        >>> datestr(datetime(1970, 6, 1), now=d)
  47.881 +        'June  1, 1970'
  47.882 +        >>> datestr(None)
  47.883 +        ''
  47.884 +    """
  47.885 +    def agohence(n, what, divisor=None):
  47.886 +        if divisor: n = n // divisor
  47.887 +
  47.888 +        out = str(abs(n)) + ' ' + what       # '2 day'
  47.889 +        if abs(n) != 1: out += 's'           # '2 days'
  47.890 +        out += ' '                           # '2 days '
  47.891 +        if n < 0:
  47.892 +            out += 'from now'
  47.893 +        else:
  47.894 +            out += 'ago'
  47.895 +        return out                           # '2 days ago'
  47.896 +
  47.897 +    oneday = 24 * 60 * 60
  47.898 +
  47.899 +    if not then: return ""
  47.900 +    if not now: now = datetime.datetime.utcnow()
  47.901 +    if type(now).__name__ == "DateTime":
  47.902 +        now = datetime.datetime.fromtimestamp(now)
  47.903 +    if type(then).__name__ == "DateTime":
  47.904 +        then = datetime.datetime.fromtimestamp(then)
  47.905 +    elif type(then).__name__ == "date":
  47.906 +        then = datetime.datetime(then.year, then.month, then.day)
  47.907 +
  47.908 +    delta = now - then
  47.909 +    deltaseconds = int(delta.days * oneday + delta.seconds + delta.microseconds * 1e-06)
  47.910 +    deltadays = abs(deltaseconds) // oneday
  47.911 +    if deltaseconds < 0: deltadays *= -1 # fix for oddity of floor
  47.912 +
  47.913 +    if deltadays:
  47.914 +        if abs(deltadays) < 4:
  47.915 +            return agohence(deltadays, 'day')
  47.916 +
  47.917 +        try:
  47.918 +            out = then.strftime('%B %e') # e.g. 'June  3'
  47.919 +        except ValueError:
  47.920 +            # %e doesn't work on Windows.
  47.921 +            out = then.strftime('%B %d') # e.g. 'June 03'
  47.922 +
  47.923 +        if then.year != now.year or deltadays < 0:
  47.924 +            out += ', %s' % then.year
  47.925 +        return out
  47.926 +
  47.927 +    if int(deltaseconds):
  47.928 +        if abs(deltaseconds) > (60 * 60):
  47.929 +            return agohence(deltaseconds, 'hour', 60 * 60)
  47.930 +        elif abs(deltaseconds) > 60:
  47.931 +            return agohence(deltaseconds, 'minute', 60)
  47.932 +        else:
  47.933 +            return agohence(deltaseconds, 'second')
  47.934 +
  47.935 +    deltamicroseconds = delta.microseconds
  47.936 +    if delta.days: deltamicroseconds = int(delta.microseconds - 1e6) # datetime oddity
  47.937 +    if abs(deltamicroseconds) > 1000:
  47.938 +        return agohence(deltamicroseconds, 'millisecond', 1000)
  47.939 +
  47.940 +    return agohence(deltamicroseconds, 'microsecond')
  47.941 +
  47.942 +def numify(string):
  47.943 +    """
  47.944 +    Removes all non-digit characters from `string`.
  47.945 +    
  47.946 +        >>> numify('800-555-1212')
  47.947 +        '8005551212'
  47.948 +        >>> numify('800.555.1212')
  47.949 +        '8005551212'
  47.950 +    
  47.951 +    """
  47.952 +    return ''.join([c for c in str(string) if c.isdigit()])
  47.953 +
  47.954 +def denumify(string, pattern):
  47.955 +    """
  47.956 +    Formats `string` according to `pattern`, where the letter X gets replaced
  47.957 +    by characters from `string`.
  47.958 +    
  47.959 +        >>> denumify("8005551212", "(XXX) XXX-XXXX")
  47.960 +        '(800) 555-1212'
  47.961 +    
  47.962 +    """
  47.963 +    out = []
  47.964 +    for c in pattern:
  47.965 +        if c == "X":
  47.966 +            out.append(string[0])
  47.967 +            string = string[1:]
  47.968 +        else:
  47.969 +            out.append(c)
  47.970 +    return ''.join(out)
  47.971 +
  47.972 +def commify(n):
  47.973 +    """
  47.974 +    Add commas to an integer `n`.
  47.975 +
  47.976 +        >>> commify(1)
  47.977 +        '1'
  47.978 +        >>> commify(123)
  47.979 +        '123'
  47.980 +        >>> commify(1234)
  47.981 +        '1,234'
  47.982 +        >>> commify(1234567890)
  47.983 +        '1,234,567,890'
  47.984 +        >>> commify(123.0)
  47.985 +        '123.0'
  47.986 +        >>> commify(1234.5)
  47.987 +        '1,234.5'
  47.988 +        >>> commify(1234.56789)
  47.989 +        '1,234.56789'
  47.990 +        >>> commify('%.2f' % 1234.5)
  47.991 +        '1,234.50'
  47.992 +        >>> commify(None)
  47.993 +        >>>
  47.994 +
  47.995 +    """
  47.996 +    if n is None: return None
  47.997 +    n = str(n)
  47.998 +    if '.' in n:
  47.999 +        dollars, cents = n.split('.')
 47.1000 +    else:
 47.1001 +        dollars, cents = n, None
 47.1002 +
 47.1003 +    r = []
 47.1004 +    for i, c in enumerate(str(dollars)[::-1]):
 47.1005 +        if i and (not (i % 3)):
 47.1006 +            r.insert(0, ',')
 47.1007 +        r.insert(0, c)
 47.1008 +    out = ''.join(r)
 47.1009 +    if cents:
 47.1010 +        out += '.' + cents
 47.1011 +    return out
 47.1012 +
 47.1013 +def dateify(datestring):
 47.1014 +    """
 47.1015 +    Formats a numified `datestring` properly.
 47.1016 +    """
 47.1017 +    return denumify(datestring, "XXXX-XX-XX XX:XX:XX")
 47.1018 +
 47.1019 +
 47.1020 +def nthstr(n):
 47.1021 +    """
 47.1022 +    Formats an ordinal.
 47.1023 +    Doesn't handle negative numbers.
 47.1024 +
 47.1025 +        >>> nthstr(1)
 47.1026 +        '1st'
 47.1027 +        >>> nthstr(0)
 47.1028 +        '0th'
 47.1029 +        >>> [nthstr(x) for x in [2, 3, 4, 5, 10, 11, 12, 13, 14, 15]]
 47.1030 +        ['2nd', '3rd', '4th', '5th', '10th', '11th', '12th', '13th', '14th', '15th']
 47.1031 +        >>> [nthstr(x) for x in [91, 92, 93, 94, 99, 100, 101, 102]]
 47.1032 +        ['91st', '92nd', '93rd', '94th', '99th', '100th', '101st', '102nd']
 47.1033 +        >>> [nthstr(x) for x in [111, 112, 113, 114, 115]]
 47.1034 +        ['111th', '112th', '113th', '114th', '115th']
 47.1035 +
 47.1036 +    """
 47.1037 +    
 47.1038 +    assert n >= 0
 47.1039 +    if n % 100 in [11, 12, 13]: return '%sth' % n
 47.1040 +    return {1: '%sst', 2: '%snd', 3: '%srd'}.get(n % 10, '%sth') % n
 47.1041 +
 47.1042 +def cond(predicate, consequence, alternative=None):
 47.1043 +    """
 47.1044 +    Function replacement for if-else to use in expressions.
 47.1045 +        
 47.1046 +        >>> x = 2
 47.1047 +        >>> cond(x % 2 == 0, "even", "odd")
 47.1048 +        'even'
 47.1049 +        >>> cond(x % 2 == 0, "even", "odd") + '_row'
 47.1050 +        'even_row'
 47.1051 +    """
 47.1052 +    if predicate:
 47.1053 +        return consequence
 47.1054 +    else:
 47.1055 +        return alternative
 47.1056 +
 47.1057 +class CaptureStdout:
 47.1058 +    """
 47.1059 +    Captures everything `func` prints to stdout and returns it instead.
 47.1060 +    
 47.1061 +        >>> def idiot():
 47.1062 +        ...     print "foo"
 47.1063 +        >>> capturestdout(idiot)()
 47.1064 +        'foo\\n'
 47.1065 +    
 47.1066 +    **WARNING:** Not threadsafe!
 47.1067 +    """
 47.1068 +    def __init__(self, func): 
 47.1069 +        self.func = func
 47.1070 +    def __call__(self, *args, **keywords):
 47.1071 +        from cStringIO import StringIO
 47.1072 +        # Not threadsafe!
 47.1073 +        out = StringIO()
 47.1074 +        oldstdout = sys.stdout
 47.1075 +        sys.stdout = out
 47.1076 +        try: 
 47.1077 +            self.func(*args, **keywords)
 47.1078 +        finally: 
 47.1079 +            sys.stdout = oldstdout
 47.1080 +        return out.getvalue()
 47.1081 +
 47.1082 +capturestdout = CaptureStdout
 47.1083 +
 47.1084 +class Profile:
 47.1085 +    """
 47.1086 +    Profiles `func` and returns a tuple containing its output
 47.1087 +    and a string with human-readable profiling information.
 47.1088 +        
 47.1089 +        >>> import time
 47.1090 +        >>> out, inf = profile(time.sleep)(.001)
 47.1091 +        >>> out
 47.1092 +        >>> inf[:10].strip()
 47.1093 +        'took 0.0'
 47.1094 +    """
 47.1095 +    def __init__(self, func): 
 47.1096 +        self.func = func
 47.1097 +    def __call__(self, *args): ##, **kw):   kw unused
 47.1098 +        import hotshot, hotshot.stats, os, tempfile ##, time already imported
 47.1099 +        f, filename = tempfile.mkstemp()
 47.1100 +        os.close(f)
 47.1101 +        
 47.1102 +        prof = hotshot.Profile(filename)
 47.1103 +
 47.1104 +        stime = time.time()
 47.1105 +        result = prof.runcall(self.func, *args)
 47.1106 +        stime = time.time() - stime
 47.1107 +        prof.close()
 47.1108 +
 47.1109 +        import cStringIO
 47.1110 +        out = cStringIO.StringIO()
 47.1111 +        stats = hotshot.stats.load(filename)
 47.1112 +        stats.stream = out
 47.1113 +        stats.strip_dirs()
 47.1114 +        stats.sort_stats('time', 'calls')
 47.1115 +        stats.print_stats(40)
 47.1116 +        stats.print_callers()
 47.1117 +
 47.1118 +        x =  '\n\ntook '+ str(stime) + ' seconds\n'
 47.1119 +        x += out.getvalue()
 47.1120 +
 47.1121 +        # remove the tempfile
 47.1122 +        try:
 47.1123 +            os.remove(filename)
 47.1124 +        except IOError:
 47.1125 +            pass
 47.1126 +            
 47.1127 +        return result, x
 47.1128 +
 47.1129 +profile = Profile
 47.1130 +
 47.1131 +
 47.1132 +import traceback
 47.1133 +# hack for compatibility with Python 2.3:
 47.1134 +if not hasattr(traceback, 'format_exc'):
 47.1135 +    from cStringIO import StringIO
 47.1136 +    def format_exc(limit=None):
 47.1137 +        strbuf = StringIO()
 47.1138 +        traceback.print_exc(limit, strbuf)
 47.1139 +        return strbuf.getvalue()
 47.1140 +    traceback.format_exc = format_exc
 47.1141 +
 47.1142 +def tryall(context, prefix=None):
 47.1143 +    """
 47.1144 +    Tries a series of functions and prints their results. 
 47.1145 +    `context` is a dictionary mapping names to values; 
 47.1146 +    the value will only be tried if it's callable.
 47.1147 +    
 47.1148 +        >>> tryall(dict(j=lambda: True))
 47.1149 +        j: True
 47.1150 +        ----------------------------------------
 47.1151 +        results:
 47.1152 +           True: 1
 47.1153 +
 47.1154 +    For example, you might have a file `test/stuff.py` 
 47.1155 +    with a series of functions testing various things in it. 
 47.1156 +    At the bottom, have a line:
 47.1157 +
 47.1158 +        if __name__ == "__main__": tryall(globals())
 47.1159 +
 47.1160 +    Then you can run `python test/stuff.py` and get the results of 
 47.1161 +    all the tests.
 47.1162 +    """
 47.1163 +    context = context.copy() # vars() would update
 47.1164 +    results = {}
 47.1165 +    for (key, value) in context.iteritems():
 47.1166 +        if not hasattr(value, '__call__'): 
 47.1167 +            continue
 47.1168 +        if prefix and not key.startswith(prefix): 
 47.1169 +            continue
 47.1170 +        print key + ':',
 47.1171 +        try:
 47.1172 +            r = value()
 47.1173 +            dictincr(results, r)
 47.1174 +            print r
 47.1175 +        except:
 47.1176 +            print 'ERROR'
 47.1177 +            dictincr(results, 'ERROR')
 47.1178 +            print '   ' + '\n   '.join(traceback.format_exc().split('\n'))
 47.1179 +        
 47.1180 +    print '-'*40
 47.1181 +    print 'results:'
 47.1182 +    for (key, value) in results.iteritems():
 47.1183 +        print ' '*2, str(key)+':', value
 47.1184 +        
 47.1185 +class ThreadedDict(threadlocal):
 47.1186 +    """
 47.1187 +    Thread local storage.
 47.1188 +    
 47.1189 +        >>> d = ThreadedDict()
 47.1190 +        >>> d.x = 1
 47.1191 +        >>> d.x
 47.1192 +        1
 47.1193 +        >>> import threading
 47.1194 +        >>> def f(): d.x = 2
 47.1195 +        ...
 47.1196 +        >>> t = threading.Thread(target=f)
 47.1197 +        >>> t.start()
 47.1198 +        >>> t.join()
 47.1199 +        >>> d.x
 47.1200 +        1
 47.1201 +    """
 47.1202 +    _instances = set()
 47.1203 +    
 47.1204 +    def __init__(self):
 47.1205 +        ThreadedDict._instances.add(self)
 47.1206 +        
 47.1207 +    def __del__(self):
 47.1208 +        ThreadedDict._instances.remove(self)
 47.1209 +        
 47.1210 +    def __hash__(self):
 47.1211 +        return id(self)
 47.1212 +    
 47.1213 +    def clear_all():
 47.1214 +        """Clears all ThreadedDict instances.
 47.1215 +        """
 47.1216 +        for t in list(ThreadedDict._instances):
 47.1217 +            t.clear()
 47.1218 +    clear_all = staticmethod(clear_all)
 47.1219 +    
 47.1220 +    # Define all these methods to more or less fully emulate dict -- attribute access
 47.1221 +    # is built into threading.local.
 47.1222 +
 47.1223 +    def __getitem__(self, key):
 47.1224 +        return self.__dict__[key]
 47.1225 +
 47.1226 +    def __setitem__(self, key, value):
 47.1227 +        self.__dict__[key] = value
 47.1228 +
 47.1229 +    def __delitem__(self, key):
 47.1230 +        del self.__dict__[key]
 47.1231 +
 47.1232 +    def __contains__(self, key):
 47.1233 +        return key in self.__dict__
 47.1234 +
 47.1235 +    has_key = __contains__
 47.1236 +        
 47.1237 +    def clear(self):
 47.1238 +        self.__dict__.clear()
 47.1239 +
 47.1240 +    def copy(self):
 47.1241 +        return self.__dict__.copy()
 47.1242 +
 47.1243 +    def get(self, key, default=None):
 47.1244 +        return self.__dict__.get(key, default)
 47.1245 +
 47.1246 +    def items(self):
 47.1247 +        return self.__dict__.items()
 47.1248 +
 47.1249 +    def iteritems(self):
 47.1250 +        return self.__dict__.iteritems()
 47.1251 +
 47.1252 +    def keys(self):
 47.1253 +        return self.__dict__.keys()
 47.1254 +
 47.1255 +    def iterkeys(self):
 47.1256 +        return self.__dict__.iterkeys()
 47.1257 +
 47.1258 +    iter = iterkeys
 47.1259 +
 47.1260 +    def values(self):
 47.1261 +        return self.__dict__.values()
 47.1262 +
 47.1263 +    def itervalues(self):
 47.1264 +        return self.__dict__.itervalues()
 47.1265 +
 47.1266 +    def pop(self, key, *args):
 47.1267 +        return self.__dict__.pop(key, *args)
 47.1268 +
 47.1269 +    def popitem(self):
 47.1270 +        return self.__dict__.popitem()
 47.1271 +
 47.1272 +    def setdefault(self, key, default=None):
 47.1273 +        return self.__dict__.setdefault(key, default)
 47.1274 +
 47.1275 +    def update(self, *args, **kwargs):
 47.1276 +        self.__dict__.update(*args, **kwargs)
 47.1277 +
 47.1278 +    def __repr__(self):
 47.1279 +        return '<ThreadedDict %r>' % self.__dict__
 47.1280 +
 47.1281 +    __str__ = __repr__
 47.1282 +    
 47.1283 +threadeddict = ThreadedDict
 47.1284 +
 47.1285 +def autoassign(self, locals):
 47.1286 +    """
 47.1287 +    Automatically assigns local variables to `self`.
 47.1288 +    
 47.1289 +        >>> self = storage()
 47.1290 +        >>> autoassign(self, dict(a=1, b=2))
 47.1291 +        >>> self
 47.1292 +        <Storage {'a': 1, 'b': 2}>
 47.1293 +    
 47.1294 +    Generally used in `__init__` methods, as in:
 47.1295 +
 47.1296 +        def __init__(self, foo, bar, baz=1): autoassign(self, locals())
 47.1297 +    """
 47.1298 +    for (key, value) in locals.iteritems():
 47.1299 +        if key == 'self': 
 47.1300 +            continue
 47.1301 +        setattr(self, key, value)
 47.1302 +
 47.1303 +def to36(q):
 47.1304 +    """
 47.1305 +    Converts an integer to base 36 (a useful scheme for human-sayable IDs).
 47.1306 +    
 47.1307 +        >>> to36(35)
 47.1308 +        'z'
 47.1309 +        >>> to36(119292)
 47.1310 +        '2k1o'
 47.1311 +        >>> int(to36(939387374), 36)
 47.1312 +        939387374
 47.1313 +        >>> to36(0)
 47.1314 +        '0'
 47.1315 +        >>> to36(-393)
 47.1316 +        Traceback (most recent call last):
 47.1317 +            ... 
 47.1318 +        ValueError: must supply a positive integer
 47.1319 +    
 47.1320 +    """
 47.1321 +    if q < 0: raise ValueError, "must supply a positive integer"
 47.1322 +    letters = "0123456789abcdefghijklmnopqrstuvwxyz"
 47.1323 +    converted = []
 47.1324 +    while q != 0:
 47.1325 +        q, r = divmod(q, 36)
 47.1326 +        converted.insert(0, letters[r])
 47.1327 +    return "".join(converted) or '0'
 47.1328 +
 47.1329 +
 47.1330 +r_url = re_compile('(?<!\()(http://(\S+))')
 47.1331 +def safemarkdown(text):
 47.1332 +    """
 47.1333 +    Converts text to HTML following the rules of Markdown, but blocking any
 47.1334 +    outside HTML input, so that only the things supported by Markdown
 47.1335 +    can be used. Also converts raw URLs to links.
 47.1336 +
 47.1337 +    (requires [markdown.py](http://webpy.org/markdown.py))
 47.1338 +    """
 47.1339 +    from markdown import markdown
 47.1340 +    if text:
 47.1341 +        text = text.replace('<', '&lt;')
 47.1342 +        # TODO: automatically get page title?
 47.1343 +        text = r_url.sub(r'<\1>', text)
 47.1344 +        text = markdown(text)
 47.1345 +        return text
 47.1346 +
 47.1347 +def sendmail(from_address, to_address, subject, message, headers=None, **kw):
 47.1348 +    """
 47.1349 +    Sends the email message `message` with mail and envelope headers
 47.1350 +    for from `from_address_` to `to_address` with `subject`. 
 47.1351 +    Additional email headers can be specified with the dictionary 
 47.1352 +    `headers.
 47.1353 +    
 47.1354 +    Optionally cc, bcc and attachments can be specified as keyword arguments.
 47.1355 +    Attachments must be an iterable and each attachment can be either a 
 47.1356 +    filename or a file object or a dictionary with filename, content and 
 47.1357 +    optionally content_type keys.
 47.1358 +
 47.1359 +    If `web.config.smtp_server` is set, it will send the message
 47.1360 +    to that SMTP server. Otherwise it will look for 
 47.1361 +    `/usr/sbin/sendmail`, the typical location for the sendmail-style
 47.1362 +    binary. To use sendmail from a different path, set `web.config.sendmail_path`.
 47.1363 +    """
 47.1364 +    attachments = kw.pop("attachments", [])
 47.1365 +    mail = _EmailMessage(from_address, to_address, subject, message, headers, **kw)
 47.1366 +
 47.1367 +    for a in attachments:
 47.1368 +        if isinstance(a, dict):
 47.1369 +            mail.attach(a['filename'], a['content'], a.get('content_type'))
 47.1370 +        elif hasattr(a, 'read'): # file
 47.1371 +            filename = os.path.basename(getattr(a, "name", ""))
 47.1372 +            content_type = getattr(a, 'content_type', None)
 47.1373 +            mail.attach(filename, a.read(), content_type)
 47.1374 +        elif isinstance(a, basestring):
 47.1375 +            f = open(a, 'rb')
 47.1376 +            content = f.read()
 47.1377 +            f.close()
 47.1378 +            filename = os.path.basename(a)
 47.1379 +            mail.attach(filename, content, None)
 47.1380 +        else:
 47.1381 +            raise ValueError, "Invalid attachment: %s" % repr(a)
 47.1382 +            
 47.1383 +    mail.send()
 47.1384 +
 47.1385 +class _EmailMessage:
 47.1386 +    def __init__(self, from_address, to_address, subject, message, headers=None, **kw):
 47.1387 +        def listify(x):
 47.1388 +            if not isinstance(x, list):
 47.1389 +                return [safestr(x)]
 47.1390 +            else:
 47.1391 +                return [safestr(a) for a in x]
 47.1392 +    
 47.1393 +        subject = safestr(subject)
 47.1394 +        message = safestr(message)
 47.1395 +
 47.1396 +        from_address = safestr(from_address)
 47.1397 +        to_address = listify(to_address)    
 47.1398 +        cc = listify(kw.get('cc', []))
 47.1399 +        bcc = listify(kw.get('bcc', []))
 47.1400 +        recipients = to_address + cc + bcc
 47.1401 +
 47.1402 +        import email.Utils
 47.1403 +        self.from_address = email.Utils.parseaddr(from_address)[1]
 47.1404 +        self.recipients = [email.Utils.parseaddr(r)[1] for r in recipients]        
 47.1405 +    
 47.1406 +        self.headers = dictadd({
 47.1407 +          'From': from_address,
 47.1408 +          'To': ", ".join(to_address),
 47.1409 +          'Subject': subject
 47.1410 +        }, headers or {})
 47.1411 +
 47.1412 +        if cc:
 47.1413 +            self.headers['Cc'] = ", ".join(cc)
 47.1414 +    
 47.1415 +        self.message = self.new_message()
 47.1416 +        self.message.add_header("Content-Transfer-Encoding", "7bit")
 47.1417 +        self.message.add_header("Content-Disposition", "inline")
 47.1418 +        self.message.add_header("MIME-Version", "1.0")
 47.1419 +        self.message.set_payload(message, 'utf-8')
 47.1420 +        self.multipart = False
 47.1421 +        
 47.1422 +    def new_message(self):
 47.1423 +        from email.Message import Message
 47.1424 +        return Message()
 47.1425 +        
 47.1426 +    def attach(self, filename, content, content_type=None):
 47.1427 +        if not self.multipart:
 47.1428 +            msg = self.new_message()
 47.1429 +            msg.add_header("Content-Type", "multipart/mixed")
 47.1430 +            msg.attach(self.message)
 47.1431 +            self.message = msg
 47.1432 +            self.multipart = True
 47.1433 +                        
 47.1434 +        import mimetypes
 47.1435 +        try:
 47.1436 +            from email import encoders
 47.1437 +        except:
 47.1438 +            from email import Encoders as encoders
 47.1439 +            
 47.1440 +        content_type = content_type or mimetypes.guess_type(filename)[0] or "applcation/octet-stream"
 47.1441 +        
 47.1442 +        msg = self.new_message()
 47.1443 +        msg.set_payload(content)
 47.1444 +        msg.add_header('Content-Type', content_type)
 47.1445 +        msg.add_header('Content-Disposition', 'attachment', filename=filename)
 47.1446 +        
 47.1447 +        if not content_type.startswith("text/"):
 47.1448 +            encoders.encode_base64(msg)
 47.1449 +            
 47.1450 +        self.message.attach(msg)
 47.1451 +
 47.1452 +    def prepare_message(self):
 47.1453 +        for k, v in self.headers.iteritems():
 47.1454 +            if k.lower() == "content-type":
 47.1455 +                self.message.set_type(v)
 47.1456 +            else:
 47.1457 +                self.message.add_header(k, v)
 47.1458 +
 47.1459 +        self.headers = {}
 47.1460 +
 47.1461 +    def send(self):
 47.1462 +        try:
 47.1463 +            import webapi
 47.1464 +        except ImportError:
 47.1465 +            webapi = Storage(config=Storage())
 47.1466 +
 47.1467 +        self.prepare_message()
 47.1468 +        message_text = self.message.as_string()
 47.1469 +    
 47.1470 +        if webapi.config.get('smtp_server'):
 47.1471 +            server = webapi.config.get('smtp_server')
 47.1472 +            port = webapi.config.get('smtp_port', 0)
 47.1473 +            username = webapi.config.get('smtp_username') 
 47.1474 +            password = webapi.config.get('smtp_password')
 47.1475 +            debug_level = webapi.config.get('smtp_debuglevel', None)
 47.1476 +            starttls = webapi.config.get('smtp_starttls', False)
 47.1477 +
 47.1478 +            import smtplib
 47.1479 +            smtpserver = smtplib.SMTP(server, port)
 47.1480 +
 47.1481 +            if debug_level:
 47.1482 +                smtpserver.set_debuglevel(debug_level)
 47.1483 +
 47.1484 +            if starttls:
 47.1485 +                smtpserver.ehlo()
 47.1486 +                smtpserver.starttls()
 47.1487 +                smtpserver.ehlo()
 47.1488 +
 47.1489 +            if username and password:
 47.1490 +                smtpserver.login(username, password)
 47.1491 +
 47.1492 +            smtpserver.sendmail(self.from_address, self.recipients, message_text)
 47.1493 +            smtpserver.quit()
 47.1494 +        elif webapi.config.get('email_engine') == 'aws':
 47.1495 +            import boto.ses
 47.1496 +            c = boto.ses.SESConnection(
 47.1497 +              aws_access_key_id=webapi.config.get('aws_access_key_id'),
 47.1498 +              aws_secret_access_key=web.api.config.get('aws_secret_access_key'))
 47.1499 +            c.send_raw_email(self.from_address, message_text, self.from_recipients)
 47.1500 +        else:
 47.1501 +            sendmail = webapi.config.get('sendmail_path', '/usr/sbin/sendmail')
 47.1502 +        
 47.1503 +            assert not self.from_address.startswith('-'), 'security'
 47.1504 +            for r in self.recipients:
 47.1505 +                assert not r.startswith('-'), 'security'
 47.1506 +                
 47.1507 +            cmd = [sendmail, '-f', self.from_address] + self.recipients
 47.1508 +
 47.1509 +            if subprocess:
 47.1510 +                p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
 47.1511 +                p.stdin.write(message_text)
 47.1512 +                p.stdin.close()
 47.1513 +                p.wait()
 47.1514 +            else:
 47.1515 +                i, o = os.popen2(cmd)
 47.1516 +                i.write(message)
 47.1517 +                i.close()
 47.1518 +                o.close()
 47.1519 +                del i, o
 47.1520 +                
 47.1521 +    def __repr__(self):
 47.1522 +        return "<EmailMessage>"
 47.1523 +    
 47.1524 +    def __str__(self):
 47.1525 +        return self.message.as_string()
 47.1526 +
 47.1527 +if __name__ == "__main__":
 47.1528 +    import doctest
 47.1529 +    doctest.testmod()
    48.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    48.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/webapi.py	Mon Dec 02 14:02:05 2013 +0100
    48.3 @@ -0,0 +1,525 @@
    48.4 +"""
    48.5 +Web API (wrapper around WSGI)
    48.6 +(from web.py)
    48.7 +"""
    48.8 +
    48.9 +__all__ = [
   48.10 +    "config",
   48.11 +    "header", "debug",
   48.12 +    "input", "data",
   48.13 +    "setcookie", "cookies",
   48.14 +    "ctx", 
   48.15 +    "HTTPError", 
   48.16 +
   48.17 +    # 200, 201, 202
   48.18 +    "OK", "Created", "Accepted",    
   48.19 +    "ok", "created", "accepted",
   48.20 +    
   48.21 +    # 301, 302, 303, 304, 307
   48.22 +    "Redirect", "Found", "SeeOther", "NotModified", "TempRedirect", 
   48.23 +    "redirect", "found", "seeother", "notmodified", "tempredirect",
   48.24 +
   48.25 +    # 400, 401, 403, 404, 405, 406, 409, 410, 412, 415
   48.26 +    "BadRequest", "Unauthorized", "Forbidden", "NotFound", "NoMethod", "NotAcceptable", "Conflict", "Gone", "PreconditionFailed", "UnsupportedMediaType",
   48.27 +    "badrequest", "unauthorized", "forbidden", "notfound", "nomethod", "notacceptable", "conflict", "gone", "preconditionfailed", "unsupportedmediatype",
   48.28 +
   48.29 +    # 500
   48.30 +    "InternalError", 
   48.31 +    "internalerror",
   48.32 +]
   48.33 +
   48.34 +import sys, cgi, Cookie, pprint, urlparse, urllib
   48.35 +from utils import storage, storify, threadeddict, dictadd, intget, safestr
   48.36 +
   48.37 +config = storage()
   48.38 +config.__doc__ = """
   48.39 +A configuration object for various aspects of web.py.
   48.40 +
   48.41 +`debug`
   48.42 +   : when True, enables reloading, disabled template caching and sets internalerror to debugerror.
   48.43 +"""
   48.44 +
   48.45 +class HTTPError(Exception):
   48.46 +    def __init__(self, status, headers={}, data=""):
   48.47 +        ctx.status = status
   48.48 +        for k, v in headers.items():
   48.49 +            header(k, v)
   48.50 +        self.data = data
   48.51 +        Exception.__init__(self, status)
   48.52 +        
   48.53 +def _status_code(status, data=None, classname=None, docstring=None):
   48.54 +    if data is None:
   48.55 +        data = status.split(" ", 1)[1]
   48.56 +    classname = status.split(" ", 1)[1].replace(' ', '') # 304 Not Modified -> NotModified    
   48.57 +    docstring = docstring or '`%s` status' % status
   48.58 +
   48.59 +    def __init__(self, data=data, headers={}):
   48.60 +        HTTPError.__init__(self, status, headers, data)
   48.61 +        
   48.62 +    # trick to create class dynamically with dynamic docstring.
   48.63 +    return type(classname, (HTTPError, object), {
   48.64 +        '__doc__': docstring,
   48.65 +        '__init__': __init__
   48.66 +    })
   48.67 +
   48.68 +ok = OK = _status_code("200 OK", data="")
   48.69 +created = Created = _status_code("201 Created")
   48.70 +accepted = Accepted = _status_code("202 Accepted")
   48.71 +
   48.72 +class Redirect(HTTPError):
   48.73 +    """A `301 Moved Permanently` redirect."""
   48.74 +    def __init__(self, url, status='301 Moved Permanently', absolute=False):
   48.75 +        """
   48.76 +        Returns a `status` redirect to the new URL. 
   48.77 +        `url` is joined with the base URL so that things like 
   48.78 +        `redirect("about") will work properly.
   48.79 +        """
   48.80 +        newloc = urlparse.urljoin(ctx.path, url)
   48.81 +
   48.82 +        if newloc.startswith('/'):
   48.83 +            if absolute:
   48.84 +                home = ctx.realhome
   48.85 +            else:
   48.86 +                home = ctx.home
   48.87 +            newloc = home + newloc
   48.88 +
   48.89 +        headers = {
   48.90 +            'Content-Type': 'text/html',
   48.91 +            'Location': newloc
   48.92 +        }
   48.93 +        HTTPError.__init__(self, status, headers, "")
   48.94 +
   48.95 +redirect = Redirect
   48.96 +
   48.97 +class Found(Redirect):
   48.98 +    """A `302 Found` redirect."""
   48.99 +    def __init__(self, url, absolute=False):
  48.100 +        Redirect.__init__(self, url, '302 Found', absolute=absolute)
  48.101 +
  48.102 +found = Found
  48.103 +
  48.104 +class SeeOther(Redirect):
  48.105 +    """A `303 See Other` redirect."""
  48.106 +    def __init__(self, url, absolute=False):
  48.107 +        Redirect.__init__(self, url, '303 See Other', absolute=absolute)
  48.108 +    
  48.109 +seeother = SeeOther
  48.110 +
  48.111 +class NotModified(HTTPError):
  48.112 +    """A `304 Not Modified` status."""
  48.113 +    def __init__(self):
  48.114 +        HTTPError.__init__(self, "304 Not Modified")
  48.115 +
  48.116 +notmodified = NotModified
  48.117 +
  48.118 +class TempRedirect(Redirect):
  48.119 +    """A `307 Temporary Redirect` redirect."""
  48.120 +    def __init__(self, url, absolute=False):
  48.121 +        Redirect.__init__(self, url, '307 Temporary Redirect', absolute=absolute)
  48.122 +
  48.123 +tempredirect = TempRedirect
  48.124 +
  48.125 +class BadRequest(HTTPError):
  48.126 +    """`400 Bad Request` error."""
  48.127 +    message = "bad request"
  48.128 +    def __init__(self, message=None):
  48.129 +        status = "400 Bad Request"
  48.130 +        headers = {'Content-Type': 'text/html'}
  48.131 +        HTTPError.__init__(self, status, headers, message or self.message)
  48.132 +
  48.133 +badrequest = BadRequest
  48.134 +
  48.135 +class Unauthorized(HTTPError):
  48.136 +    """`401 Unauthorized` error."""
  48.137 +    message = "unauthorized"
  48.138 +    def __init__(self):
  48.139 +        status = "401 Unauthorized"
  48.140 +        headers = {'Content-Type': 'text/html'}
  48.141 +        HTTPError.__init__(self, status, headers, self.message)
  48.142 +
  48.143 +unauthorized = Unauthorized
  48.144 +
  48.145 +class Forbidden(HTTPError):
  48.146 +    """`403 Forbidden` error."""
  48.147 +    message = "forbidden"
  48.148 +    def __init__(self):
  48.149 +        status = "403 Forbidden"
  48.150 +        headers = {'Content-Type': 'text/html'}
  48.151 +        HTTPError.__init__(self, status, headers, self.message)
  48.152 +
  48.153 +forbidden = Forbidden
  48.154 +
  48.155 +class _NotFound(HTTPError):
  48.156 +    """`404 Not Found` error."""
  48.157 +    message = "not found"
  48.158 +    def __init__(self, message=None):
  48.159 +        status = '404 Not Found'
  48.160 +        headers = {'Content-Type': 'text/html'}
  48.161 +        HTTPError.__init__(self, status, headers, message or self.message)
  48.162 +
  48.163 +def NotFound(message=None):
  48.164 +    """Returns HTTPError with '404 Not Found' error from the active application.
  48.165 +    """
  48.166 +    if message:
  48.167 +        return _NotFound(message)
  48.168 +    elif ctx.get('app_stack'):
  48.169 +        return ctx.app_stack[-1].notfound()
  48.170 +    else:
  48.171 +        return _NotFound()
  48.172 +
  48.173 +notfound = NotFound
  48.174 +
  48.175 +class NoMethod(HTTPError):
  48.176 +    """A `405 Method Not Allowed` error."""
  48.177 +    def __init__(self, cls=None):
  48.178 +        status = '405 Method Not Allowed'
  48.179 +        headers = {}
  48.180 +        headers['Content-Type'] = 'text/html'
  48.181 +        
  48.182 +        methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']
  48.183 +        if cls:
  48.184 +            methods = [method for method in methods if hasattr(cls, method)]
  48.185 +
  48.186 +        headers['Allow'] = ', '.join(methods)
  48.187 +        data = None
  48.188 +        HTTPError.__init__(self, status, headers, data)
  48.189 +        
  48.190 +nomethod = NoMethod
  48.191 +
  48.192 +class NotAcceptable(HTTPError):
  48.193 +    """`406 Not Acceptable` error."""
  48.194 +    message = "not acceptable"
  48.195 +    def __init__(self):
  48.196 +        status = "406 Not Acceptable"
  48.197 +        headers = {'Content-Type': 'text/html'}
  48.198 +        HTTPError.__init__(self, status, headers, self.message)
  48.199 +
  48.200 +notacceptable = NotAcceptable
  48.201 +
  48.202 +class Conflict(HTTPError):
  48.203 +    """`409 Conflict` error."""
  48.204 +    message = "conflict"
  48.205 +    def __init__(self):
  48.206 +        status = "409 Conflict"
  48.207 +        headers = {'Content-Type': 'text/html'}
  48.208 +        HTTPError.__init__(self, status, headers, self.message)
  48.209 +
  48.210 +conflict = Conflict
  48.211 +
  48.212 +class Gone(HTTPError):
  48.213 +    """`410 Gone` error."""
  48.214 +    message = "gone"
  48.215 +    def __init__(self):
  48.216 +        status = '410 Gone'
  48.217 +        headers = {'Content-Type': 'text/html'}
  48.218 +        HTTPError.__init__(self, status, headers, self.message)
  48.219 +
  48.220 +gone = Gone
  48.221 +
  48.222 +class PreconditionFailed(HTTPError):
  48.223 +    """`412 Precondition Failed` error."""
  48.224 +    message = "precondition failed"
  48.225 +    def __init__(self):
  48.226 +        status = "412 Precondition Failed"
  48.227 +        headers = {'Content-Type': 'text/html'}
  48.228 +        HTTPError.__init__(self, status, headers, self.message)
  48.229 +
  48.230 +preconditionfailed = PreconditionFailed
  48.231 +
  48.232 +class UnsupportedMediaType(HTTPError):
  48.233 +    """`415 Unsupported Media Type` error."""
  48.234 +    message = "unsupported media type"
  48.235 +    def __init__(self):
  48.236 +        status = "415 Unsupported Media Type"
  48.237 +        headers = {'Content-Type': 'text/html'}
  48.238 +        HTTPError.__init__(self, status, headers, self.message)
  48.239 +
  48.240 +unsupportedmediatype = UnsupportedMediaType
  48.241 +
  48.242 +class _InternalError(HTTPError):
  48.243 +    """500 Internal Server Error`."""
  48.244 +    message = "internal server error"
  48.245 +    
  48.246 +    def __init__(self, message=None):
  48.247 +        status = '500 Internal Server Error'
  48.248 +        headers = {'Content-Type': 'text/html'}
  48.249 +        HTTPError.__init__(self, status, headers, message or self.message)
  48.250 +
  48.251 +def InternalError(message=None):
  48.252 +    """Returns HTTPError with '500 internal error' error from the active application.
  48.253 +    """
  48.254 +    if message:
  48.255 +        return _InternalError(message)
  48.256 +    elif ctx.get('app_stack'):
  48.257 +        return ctx.app_stack[-1].internalerror()
  48.258 +    else:
  48.259 +        return _InternalError()
  48.260 +
  48.261 +internalerror = InternalError
  48.262 +
  48.263 +def header(hdr, value, unique=False):
  48.264 +    """
  48.265 +    Adds the header `hdr: value` with the response.
  48.266 +    
  48.267 +    If `unique` is True and a header with that name already exists,
  48.268 +    it doesn't add a new one. 
  48.269 +    """
  48.270 +    hdr, value = safestr(hdr), safestr(value)
  48.271 +    # protection against HTTP response splitting attack
  48.272 +    if '\n' in hdr or '\r' in hdr or '\n' in value or '\r' in value:
  48.273 +        raise ValueError, 'invalid characters in header'
  48.274 +        
  48.275 +    if unique is True:
  48.276 +        for h, v in ctx.headers:
  48.277 +            if h.lower() == hdr.lower(): return
  48.278 +    
  48.279 +    ctx.headers.append((hdr, value))
  48.280 +    
  48.281 +def rawinput(method=None):
  48.282 +    """Returns storage object with GET or POST arguments.
  48.283 +    """
  48.284 +    method = method or "both"
  48.285 +    from cStringIO import StringIO
  48.286 +
  48.287 +    def dictify(fs): 
  48.288 +        # hack to make web.input work with enctype='text/plain.
  48.289 +        if fs.list is None:
  48.290 +            fs.list = [] 
  48.291 +
  48.292 +        return dict([(k, fs[k]) for k in fs.keys()])
  48.293 +    
  48.294 +    e = ctx.env.copy()
  48.295 +    a = b = {}
  48.296 +    
  48.297 +    if method.lower() in ['both', 'post', 'put']:
  48.298 +        if e['REQUEST_METHOD'] in ['POST', 'PUT']:
  48.299 +            if e.get('CONTENT_TYPE', '').lower().startswith('multipart/'):
  48.300 +                # since wsgi.input is directly passed to cgi.FieldStorage, 
  48.301 +                # it can not be called multiple times. Saving the FieldStorage
  48.302 +                # object in ctx to allow calling web.input multiple times.
  48.303 +                a = ctx.get('_fieldstorage')
  48.304 +                if not a:
  48.305 +                    fp = e['wsgi.input']
  48.306 +                    a = cgi.FieldStorage(fp=fp, environ=e, keep_blank_values=1)
  48.307 +                    ctx._fieldstorage = a
  48.308 +            else:
  48.309 +                fp = StringIO(data())
  48.310 +                a = cgi.FieldStorage(fp=fp, environ=e, keep_blank_values=1)
  48.311 +            a = dictify(a)
  48.312 +
  48.313 +    if method.lower() in ['both', 'get']:
  48.314 +        e['REQUEST_METHOD'] = 'GET'
  48.315 +        b = dictify(cgi.FieldStorage(environ=e, keep_blank_values=1))
  48.316 +
  48.317 +    def process_fieldstorage(fs):
  48.318 +        if isinstance(fs, list):
  48.319 +            return [process_fieldstorage(x) for x in fs]
  48.320 +        elif fs.filename is None:
  48.321 +            return fs.value
  48.322 +        else:
  48.323 +            return fs
  48.324 +
  48.325 +    return storage([(k, process_fieldstorage(v)) for k, v in dictadd(b, a).items()])
  48.326 +
  48.327 +def input(*requireds, **defaults):
  48.328 +    """
  48.329 +    Returns a `storage` object with the GET and POST arguments. 
  48.330 +    See `storify` for how `requireds` and `defaults` work.
  48.331 +    """
  48.332 +    _method = defaults.pop('_method', 'both')
  48.333 +    out = rawinput(_method)
  48.334 +    try:
  48.335 +        defaults.setdefault('_unicode', True) # force unicode conversion by default.
  48.336 +        return storify(out, *requireds, **defaults)
  48.337 +    except KeyError:
  48.338 +        raise badrequest()
  48.339 +
  48.340 +def data():
  48.341 +    """Returns the data sent with the request."""
  48.342 +    if 'data' not in ctx:
  48.343 +        cl = intget(ctx.env.get('CONTENT_LENGTH'), 0)
  48.344 +        ctx.data = ctx.env['wsgi.input'].read(cl)
  48.345 +    return ctx.data
  48.346 +
  48.347 +def setcookie(name, value, expires='', domain=None,
  48.348 +              secure=False, httponly=False, path=None):
  48.349 +    """Sets a cookie."""
  48.350 +    morsel = Cookie.Morsel()
  48.351 +    name, value = safestr(name), safestr(value)
  48.352 +    morsel.set(name, value, urllib.quote(value))
  48.353 +    if expires < 0:
  48.354 +        expires = -1000000000
  48.355 +    morsel['expires'] = expires
  48.356 +    morsel['path'] = path or ctx.homepath+'/'
  48.357 +    if domain:
  48.358 +        morsel['domain'] = domain
  48.359 +    if secure:
  48.360 +        morsel['secure'] = secure
  48.361 +    value = morsel.OutputString()
  48.362 +    if httponly:
  48.363 +        value += '; httponly'
  48.364 +    header('Set-Cookie', value)
  48.365 +        
  48.366 +def decode_cookie(value):
  48.367 +    r"""Safely decodes a cookie value to unicode. 
  48.368 +    
  48.369 +    Tries us-ascii, utf-8 and io8859 encodings, in that order.
  48.370 +
  48.371 +    >>> decode_cookie('')
  48.372 +    u''
  48.373 +    >>> decode_cookie('asdf')
  48.374 +    u'asdf'
  48.375 +    >>> decode_cookie('foo \xC3\xA9 bar')
  48.376 +    u'foo \xe9 bar'
  48.377 +    >>> decode_cookie('foo \xE9 bar')
  48.378 +    u'foo \xe9 bar'
  48.379 +    """
  48.380 +    try:
  48.381 +        # First try plain ASCII encoding
  48.382 +        return unicode(value, 'us-ascii')
  48.383 +    except UnicodeError:
  48.384 +        # Then try UTF-8, and if that fails, ISO8859
  48.385 +        try:
  48.386 +            return unicode(value, 'utf-8')
  48.387 +        except UnicodeError:
  48.388 +            return unicode(value, 'iso8859', 'ignore')
  48.389 +
  48.390 +def parse_cookies(http_cookie):
  48.391 +    r"""Parse a HTTP_COOKIE header and return dict of cookie names and decoded values.
  48.392 +        
  48.393 +    >>> sorted(parse_cookies('').items())
  48.394 +    []
  48.395 +    >>> sorted(parse_cookies('a=1').items())
  48.396 +    [('a', '1')]
  48.397 +    >>> sorted(parse_cookies('a=1%202').items())
  48.398 +    [('a', '1 2')]
  48.399 +    >>> sorted(parse_cookies('a=Z%C3%A9Z').items())
  48.400 +    [('a', 'Z\xc3\xa9Z')]
  48.401 +    >>> sorted(parse_cookies('a=1; b=2; c=3').items())
  48.402 +    [('a', '1'), ('b', '2'), ('c', '3')]
  48.403 +    >>> sorted(parse_cookies('a=1; b=w("x")|y=z; c=3').items())
  48.404 +    [('a', '1'), ('b', 'w('), ('c', '3')]
  48.405 +    >>> sorted(parse_cookies('a=1; b=w(%22x%22)|y=z; c=3').items())
  48.406 +    [('a', '1'), ('b', 'w("x")|y=z'), ('c', '3')]
  48.407 +
  48.408 +    >>> sorted(parse_cookies('keebler=E=mc2').items())
  48.409 +    [('keebler', 'E=mc2')]
  48.410 +    >>> sorted(parse_cookies(r'keebler="E=mc2; L=\"Loves\"; fudge=\012;"').items())
  48.411 +    [('keebler', 'E=mc2; L="Loves"; fudge=\n;')]
  48.412 +    """
  48.413 +    #print "parse_cookies"
  48.414 +    if '"' in http_cookie:
  48.415 +        # HTTP_COOKIE has quotes in it, use slow but correct cookie parsing
  48.416 +        cookie = Cookie.SimpleCookie()
  48.417 +        try:
  48.418 +            cookie.load(http_cookie)
  48.419 +        except Cookie.CookieError:
  48.420 +            # If HTTP_COOKIE header is malformed, try at least to load the cookies we can by
  48.421 +            # first splitting on ';' and loading each attr=value pair separately
  48.422 +            cookie = Cookie.SimpleCookie()
  48.423 +            for attr_value in http_cookie.split(';'):
  48.424 +                try:
  48.425 +                    cookie.load(attr_value)
  48.426 +                except Cookie.CookieError:
  48.427 +                    pass
  48.428 +        cookies = dict((k, urllib.unquote(v.value)) for k, v in cookie.iteritems())
  48.429 +    else:
  48.430 +        # HTTP_COOKIE doesn't have quotes, use fast cookie parsing
  48.431 +        cookies = {}
  48.432 +        for key_value in http_cookie.split(';'):
  48.433 +            key_value = key_value.split('=', 1)
  48.434 +            if len(key_value) == 2:
  48.435 +                key, value = key_value
  48.436 +                cookies[key.strip()] = urllib.unquote(value.strip())
  48.437 +    return cookies
  48.438 +
  48.439 +def cookies(*requireds, **defaults):
  48.440 +    r"""Returns a `storage` object with all the request cookies in it.
  48.441 +    
  48.442 +    See `storify` for how `requireds` and `defaults` work.
  48.443 +
  48.444 +    This is forgiving on bad HTTP_COOKIE input, it tries to parse at least
  48.445 +    the cookies it can.
  48.446 +    
  48.447 +    The values are converted to unicode if _unicode=True is passed.
  48.448 +    """
  48.449 +    # If _unicode=True is specified, use decode_cookie to convert cookie value to unicode 
  48.450 +    if defaults.get("_unicode") is True:
  48.451 +        defaults['_unicode'] = decode_cookie
  48.452 +        
  48.453 +    # parse cookie string and cache the result for next time.
  48.454 +    if '_parsed_cookies' not in ctx:
  48.455 +        http_cookie = ctx.env.get("HTTP_COOKIE", "")
  48.456 +        ctx._parsed_cookies = parse_cookies(http_cookie)
  48.457 +
  48.458 +    try:
  48.459 +        return storify(ctx._parsed_cookies, *requireds, **defaults)
  48.460 +    except KeyError:
  48.461 +        badrequest()
  48.462 +        raise StopIteration
  48.463 +
  48.464 +def debug(*args):
  48.465 +    """
  48.466 +    Prints a prettyprinted version of `args` to stderr.
  48.467 +    """
  48.468 +    try: 
  48.469 +        out = ctx.environ['wsgi.errors']
  48.470 +    except: 
  48.471 +        out = sys.stderr
  48.472 +    for arg in args:
  48.473 +        print >> out, pprint.pformat(arg)
  48.474 +    return ''
  48.475 +
  48.476 +def _debugwrite(x):
  48.477 +    try: 
  48.478 +        out = ctx.environ['wsgi.errors']
  48.479 +    except: 
  48.480 +        out = sys.stderr
  48.481 +    out.write(x)
  48.482 +debug.write = _debugwrite
  48.483 +
  48.484 +ctx = context = threadeddict()
  48.485 +
  48.486 +ctx.__doc__ = """
  48.487 +A `storage` object containing various information about the request:
  48.488 +  
  48.489 +`environ` (aka `env`)
  48.490 +   : A dictionary containing the standard WSGI environment variables.
  48.491 +
  48.492 +`host`
  48.493 +   : The domain (`Host` header) requested by the user.
  48.494 +
  48.495 +`home`
  48.496 +   : The base path for the application.
  48.497 +
  48.498 +`ip`
  48.499 +   : The IP address of the requester.
  48.500 +
  48.501 +`method`
  48.502 +   : The HTTP method used.
  48.503 +
  48.504 +`path`
  48.505 +   : The path request.
  48.506 +   
  48.507 +`query`
  48.508 +   : If there are no query arguments, the empty string. Otherwise, a `?` followed
  48.509 +     by the query string.
  48.510 +
  48.511 +`fullpath`
  48.512 +   : The full path requested, including query arguments (`== path + query`).
  48.513 +
  48.514 +### Response Data
  48.515 +
  48.516 +`status` (default: "200 OK")
  48.517 +   : The status code to be used in the response.
  48.518 +
  48.519 +`headers`
  48.520 +   : A list of 2-tuples to be used in the response.
  48.521 +
  48.522 +`output`
  48.523 +   : A string to be used as the response.
  48.524 +"""
  48.525 +
  48.526 +if __name__ == "__main__":
  48.527 +    import doctest
  48.528 +    doctest.testmod()
  48.529 \ No newline at end of file
    49.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    49.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/webopenid.py	Mon Dec 02 14:02:05 2013 +0100
    49.3 @@ -0,0 +1,115 @@
    49.4 +"""openid.py: an openid library for web.py
    49.5 +
    49.6 +Notes:
    49.7 +
    49.8 + - This will create a file called .openid_secret_key in the 
    49.9 +   current directory with your secret key in it. If someone 
   49.10 +   has access to this file they can log in as any user. And 
   49.11 +   if the app can't find this file for any reason (e.g. you 
   49.12 +   moved the app somewhere else) then each currently logged 
   49.13 +   in user will get logged out.
   49.14 +
   49.15 + - State must be maintained through the entire auth process 
   49.16 +   -- this means that if you have multiple web.py processes 
   49.17 +   serving one set of URLs or if you restart your app often 
   49.18 +   then log ins will fail. You have to replace sessions and 
   49.19 +   store for things to work.
   49.20 +
   49.21 + - We set cookies starting with "openid_".
   49.22 +
   49.23 +"""
   49.24 +
   49.25 +import os
   49.26 +import random
   49.27 +import hmac
   49.28 +import __init__ as web
   49.29 +import openid.consumer.consumer
   49.30 +import openid.store.memstore
   49.31 +
   49.32 +sessions = {}
   49.33 +store = openid.store.memstore.MemoryStore()
   49.34 +
   49.35 +def _secret():
   49.36 +    try:
   49.37 +        secret = file('.openid_secret_key').read()
   49.38 +    except IOError:
   49.39 +        # file doesn't exist
   49.40 +        secret = os.urandom(20)
   49.41 +        file('.openid_secret_key', 'w').write(secret)
   49.42 +    return secret
   49.43 +
   49.44 +def _hmac(identity_url):
   49.45 +    return hmac.new(_secret(), identity_url).hexdigest()
   49.46 +
   49.47 +def _random_session():
   49.48 +    n = random.random()
   49.49 +    while n in sessions:
   49.50 +        n = random.random()
   49.51 +    n = str(n)
   49.52 +    return n
   49.53 +
   49.54 +def status():
   49.55 +    oid_hash = web.cookies().get('openid_identity_hash', '').split(',', 1)
   49.56 +    if len(oid_hash) > 1:
   49.57 +        oid_hash, identity_url = oid_hash
   49.58 +        if oid_hash == _hmac(identity_url):
   49.59 +            return identity_url
   49.60 +    return None
   49.61 +
   49.62 +def form(openid_loc):
   49.63 +    oid = status()
   49.64 +    if oid:
   49.65 +        return '''
   49.66 +        <form method="post" action="%s">
   49.67 +          <img src="http://openid.net/login-bg.gif" alt="OpenID" />
   49.68 +          <strong>%s</strong>
   49.69 +          <input type="hidden" name="action" value="logout" />
   49.70 +          <input type="hidden" name="return_to" value="%s" />
   49.71 +          <button type="submit">log out</button>
   49.72 +        </form>''' % (openid_loc, oid, web.ctx.fullpath)
   49.73 +    else:
   49.74 +        return '''
   49.75 +        <form method="post" action="%s">
   49.76 +          <input type="text" name="openid" value="" 
   49.77 +            style="background: url(http://openid.net/login-bg.gif) no-repeat; padding-left: 18px; background-position: 0 50%%;" />
   49.78 +          <input type="hidden" name="return_to" value="%s" />
   49.79 +          <button type="submit">log in</button>
   49.80 +        </form>''' % (openid_loc, web.ctx.fullpath)
   49.81 +
   49.82 +def logout():
   49.83 +    web.setcookie('openid_identity_hash', '', expires=-1)
   49.84 +
   49.85 +class host:
   49.86 +    def POST(self):
   49.87 +        # unlike the usual scheme of things, the POST is actually called
   49.88 +        # first here
   49.89 +        i = web.input(return_to='/')
   49.90 +        if i.get('action') == 'logout':
   49.91 +            logout()
   49.92 +            return web.redirect(i.return_to)
   49.93 +
   49.94 +        i = web.input('openid', return_to='/')
   49.95 +
   49.96 +        n = _random_session()
   49.97 +        sessions[n] = {'webpy_return_to': i.return_to}
   49.98 +        
   49.99 +        c = openid.consumer.consumer.Consumer(sessions[n], store)
  49.100 +        a = c.begin(i.openid)
  49.101 +        f = a.redirectURL(web.ctx.home, web.ctx.home + web.ctx.fullpath)
  49.102 +
  49.103 +        web.setcookie('openid_session_id', n)
  49.104 +        return web.redirect(f)
  49.105 +
  49.106 +    def GET(self):
  49.107 +        n = web.cookies('openid_session_id').openid_session_id
  49.108 +        web.setcookie('openid_session_id', '', expires=-1)
  49.109 +        return_to = sessions[n]['webpy_return_to']
  49.110 +
  49.111 +        c = openid.consumer.consumer.Consumer(sessions[n], store)
  49.112 +        a = c.complete(web.input(), web.ctx.home + web.ctx.fullpath)
  49.113 +
  49.114 +        if a.status.lower() == 'success':
  49.115 +            web.setcookie('openid_identity_hash', _hmac(a.identity_url) + ',' + a.identity_url)
  49.116 +
  49.117 +        del sessions[n]
  49.118 +        return web.redirect(return_to)
    50.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    50.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/wsgi.py	Mon Dec 02 14:02:05 2013 +0100
    50.3 @@ -0,0 +1,70 @@
    50.4 +"""
    50.5 +WSGI Utilities
    50.6 +(from web.py)
    50.7 +"""
    50.8 +
    50.9 +import os, sys
   50.10 +
   50.11 +import http
   50.12 +import webapi as web
   50.13 +from utils import listget
   50.14 +from net import validaddr, validip
   50.15 +import httpserver
   50.16 +    
   50.17 +def runfcgi(func, addr=('localhost', 8000)):
   50.18 +    """Runs a WSGI function as a FastCGI server."""
   50.19 +    import flup.server.fcgi as flups
   50.20 +    return flups.WSGIServer(func, multiplexed=True, bindAddress=addr, debug=False).run()
   50.21 +
   50.22 +def runscgi(func, addr=('localhost', 4000)):
   50.23 +    """Runs a WSGI function as an SCGI server."""
   50.24 +    import flup.server.scgi as flups
   50.25 +    return flups.WSGIServer(func, bindAddress=addr, debug=False).run()
   50.26 +
   50.27 +def runwsgi(func):
   50.28 +    """
   50.29 +    Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server,
   50.30 +    as appropriate based on context and `sys.argv`.
   50.31 +    """
   50.32 +    
   50.33 +    if os.environ.has_key('SERVER_SOFTWARE'): # cgi
   50.34 +        os.environ['FCGI_FORCE_CGI'] = 'Y'
   50.35 +
   50.36 +    if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi
   50.37 +      or os.environ.has_key('SERVER_SOFTWARE')):
   50.38 +        return runfcgi(func, None)
   50.39 +    
   50.40 +    if 'fcgi' in sys.argv or 'fastcgi' in sys.argv:
   50.41 +        args = sys.argv[1:]
   50.42 +        if 'fastcgi' in args: args.remove('fastcgi')
   50.43 +        elif 'fcgi' in args: args.remove('fcgi')
   50.44 +        if args:
   50.45 +            return runfcgi(func, validaddr(args[0]))
   50.46 +        else:
   50.47 +            return runfcgi(func, None)
   50.48 +    
   50.49 +    if 'scgi' in sys.argv:
   50.50 +        args = sys.argv[1:]
   50.51 +        args.remove('scgi')
   50.52 +        if args:
   50.53 +            return runscgi(func, validaddr(args[0]))
   50.54 +        else:
   50.55 +            return runscgi(func)
   50.56 +    
   50.57 +    return httpserver.runsimple(func, validip(listget(sys.argv, 1, '')))
   50.58 +    
   50.59 +def _is_dev_mode():
   50.60 +    # Some embedded python interpreters won't have sys.arv
   50.61 +    # For details, see https://github.com/webpy/webpy/issues/87
   50.62 +    argv = getattr(sys, "argv", [])
   50.63 +
   50.64 +    # quick hack to check if the program is running in dev mode.
   50.65 +    if os.environ.has_key('SERVER_SOFTWARE') \
   50.66 +        or os.environ.has_key('PHP_FCGI_CHILDREN') \
   50.67 +        or 'fcgi' in argv or 'fastcgi' in argv \
   50.68 +        or 'mod_wsgi' in argv:
   50.69 +            return False
   50.70 +    return True
   50.71 +
   50.72 +# When running the builtin-server, enable debug mode if not already set.
   50.73 +web.config.setdefault('debug', _is_dev_mode())
    51.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    51.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/wsgiserver/__init__.py	Mon Dec 02 14:02:05 2013 +0100
    51.3 @@ -0,0 +1,2219 @@
    51.4 +"""A high-speed, production ready, thread pooled, generic HTTP server.
    51.5 +
    51.6 +Simplest example on how to use this module directly
    51.7 +(without using CherryPy's application machinery)::
    51.8 +
    51.9 +    from cherrypy import wsgiserver
   51.10 +    
   51.11 +    def my_crazy_app(environ, start_response):
   51.12 +        status = '200 OK'
   51.13 +        response_headers = [('Content-type','text/plain')]
   51.14 +        start_response(status, response_headers)
   51.15 +        return ['Hello world!']
   51.16 +    
   51.17 +    server = wsgiserver.CherryPyWSGIServer(
   51.18 +                ('0.0.0.0', 8070), my_crazy_app,
   51.19 +                server_name='www.cherrypy.example')
   51.20 +    server.start()
   51.21 +    
   51.22 +The CherryPy WSGI server can serve as many WSGI applications 
   51.23 +as you want in one instance by using a WSGIPathInfoDispatcher::
   51.24 +    
   51.25 +    d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app})
   51.26 +    server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d)
   51.27 +    
   51.28 +Want SSL support? Just set server.ssl_adapter to an SSLAdapter instance.
   51.29 +
   51.30 +This won't call the CherryPy engine (application side) at all, only the
   51.31 +HTTP server, which is independent from the rest of CherryPy. Don't
   51.32 +let the name "CherryPyWSGIServer" throw you; the name merely reflects
   51.33 +its origin, not its coupling.
   51.34 +
   51.35 +For those of you wanting to understand internals of this module, here's the
   51.36 +basic call flow. The server's listening thread runs a very tight loop,
   51.37 +sticking incoming connections onto a Queue::
   51.38 +
   51.39 +    server = CherryPyWSGIServer(...)
   51.40 +    server.start()
   51.41 +    while True:
   51.42 +        tick()
   51.43 +        # This blocks until a request comes in:
   51.44 +        child = socket.accept()
   51.45 +        conn = HTTPConnection(child, ...)
   51.46 +        server.requests.put(conn)
   51.47 +
   51.48 +Worker threads are kept in a pool and poll the Queue, popping off and then
   51.49 +handling each connection in turn. Each connection can consist of an arbitrary
   51.50 +number of requests and their responses, so we run a nested loop::
   51.51 +
   51.52 +    while True:
   51.53 +        conn = server.requests.get()
   51.54 +        conn.communicate()
   51.55 +        ->  while True:
   51.56 +                req = HTTPRequest(...)
   51.57 +                req.parse_request()
   51.58 +                ->  # Read the Request-Line, e.g. "GET /page HTTP/1.1"
   51.59 +                    req.rfile.readline()
   51.60 +                    read_headers(req.rfile, req.inheaders)
   51.61 +                req.respond()
   51.62 +                ->  response = app(...)
   51.63 +                    try:
   51.64 +                        for chunk in response:
   51.65 +                            if chunk:
   51.66 +                                req.write(chunk)
   51.67 +                    finally:
   51.68 +                        if hasattr(response, "close"):
   51.69 +                            response.close()
   51.70 +                if req.close_connection:
   51.71 +                    return
   51.72 +"""
   51.73 +
   51.74 +CRLF = '\r\n'
   51.75 +import os
   51.76 +import Queue
   51.77 +import re
   51.78 +quoted_slash = re.compile("(?i)%2F")
   51.79 +import rfc822
   51.80 +import socket
   51.81 +import sys
   51.82 +if 'win' in sys.platform and not hasattr(socket, 'IPPROTO_IPV6'):
   51.83 +    socket.IPPROTO_IPV6 = 41
   51.84 +try:
   51.85 +    import cStringIO as StringIO
   51.86 +except ImportError:
   51.87 +    import StringIO
   51.88 +DEFAULT_BUFFER_SIZE = -1
   51.89 +
   51.90 +_fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring)
   51.91 +
   51.92 +import threading
   51.93 +import time
   51.94 +import traceback
   51.95 +def format_exc(limit=None):
   51.96 +    """Like print_exc() but return a string. Backport for Python 2.3."""
   51.97 +    try:
   51.98 +        etype, value, tb = sys.exc_info()
   51.99 +        return ''.join(traceback.format_exception(etype, value, tb, limit))
  51.100 +    finally:
  51.101 +        etype = value = tb = None
  51.102 +
  51.103 +
  51.104 +from urllib import unquote
  51.105 +from urlparse import urlparse
  51.106 +import warnings
  51.107 +
  51.108 +import errno
  51.109 +
  51.110 +def plat_specific_errors(*errnames):
  51.111 +    """Return error numbers for all errors in errnames on this platform.
  51.112 +    
  51.113 +    The 'errno' module contains different global constants depending on
  51.114 +    the specific platform (OS). This function will return the list of
  51.115 +    numeric values for a given list of potential names.
  51.116 +    """
  51.117 +    errno_names = dir(errno)
  51.118 +    nums = [getattr(errno, k) for k in errnames if k in errno_names]
  51.119 +    # de-dupe the list
  51.120 +    return dict.fromkeys(nums).keys()
  51.121 +
  51.122 +socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
  51.123 +
  51.124 +socket_errors_to_ignore = plat_specific_errors(
  51.125 +    "EPIPE",
  51.126 +    "EBADF", "WSAEBADF",
  51.127 +    "ENOTSOCK", "WSAENOTSOCK",
  51.128 +    "ETIMEDOUT", "WSAETIMEDOUT",
  51.129 +    "ECONNREFUSED", "WSAECONNREFUSED",
  51.130 +    "ECONNRESET", "WSAECONNRESET",
  51.131 +    "ECONNABORTED", "WSAECONNABORTED",
  51.132 +    "ENETRESET", "WSAENETRESET",
  51.133 +    "EHOSTDOWN", "EHOSTUNREACH",
  51.134 +    )
  51.135 +socket_errors_to_ignore.append("timed out")
  51.136 +socket_errors_to_ignore.append("The read operation timed out")
  51.137 +
  51.138 +socket_errors_nonblocking = plat_specific_errors(
  51.139 +    'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
  51.140 +
  51.141 +comma_separated_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding',
  51.142 +    'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control',
  51.143 +    'Connection', 'Content-Encoding', 'Content-Language', 'Expect',
  51.144 +    'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE',
  51.145 +    'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning',
  51.146 +    'WWW-Authenticate']
  51.147 +
  51.148 +
  51.149 +import logging
  51.150 +if not hasattr(logging, 'statistics'): logging.statistics = {}
  51.151 +
  51.152 +
  51.153 +def read_headers(rfile, hdict=None):
  51.154 +    """Read headers from the given stream into the given header dict.
  51.155 +    
  51.156 +    If hdict is None, a new header dict is created. Returns the populated
  51.157 +    header dict.
  51.158 +    
  51.159 +    Headers which are repeated are folded together using a comma if their
  51.160 +    specification so dictates.
  51.161 +    
  51.162 +    This function raises ValueError when the read bytes violate the HTTP spec.
  51.163 +    You should probably return "400 Bad Request" if this happens.
  51.164 +    """
  51.165 +    if hdict is None:
  51.166 +        hdict = {}
  51.167 +    
  51.168 +    while True:
  51.169 +        line = rfile.readline()
  51.170 +        if not line:
  51.171 +            # No more data--illegal end of headers
  51.172 +            raise ValueError("Illegal end of headers.")
  51.173 +        
  51.174 +        if line == CRLF:
  51.175 +            # Normal end of headers
  51.176 +            break
  51.177 +        if not line.endswith(CRLF):
  51.178 +            raise ValueError("HTTP requires CRLF terminators")
  51.179 +        
  51.180 +        if line[0] in ' \t':
  51.181 +            # It's a continuation line.
  51.182 +            v = line.strip()
  51.183 +        else:
  51.184 +            try:
  51.185 +                k, v = line.split(":", 1)
  51.186 +            except ValueError:
  51.187 +                raise ValueError("Illegal header line.")
  51.188 +            # TODO: what about TE and WWW-Authenticate?
  51.189 +            k = k.strip().title()
  51.190 +            v = v.strip()
  51.191 +            hname = k
  51.192 +        
  51.193 +        if k in comma_separated_headers:
  51.194 +            existing = hdict.get(hname)
  51.195 +            if existing:
  51.196 +                v = ", ".join((existing, v))
  51.197 +        hdict[hname] = v
  51.198 +    
  51.199 +    return hdict
  51.200 +
  51.201 +
  51.202 +class MaxSizeExceeded(Exception):
  51.203 +    pass
  51.204 +
  51.205 +class SizeCheckWrapper(object):
  51.206 +    """Wraps a file-like object, raising MaxSizeExceeded if too large."""
  51.207 +    
  51.208 +    def __init__(self, rfile, maxlen):
  51.209 +        self.rfile = rfile
  51.210 +        self.maxlen = maxlen
  51.211 +        self.bytes_read = 0
  51.212 +    
  51.213 +    def _check_length(self):
  51.214 +        if self.maxlen and self.bytes_read > self.maxlen:
  51.215 +            raise MaxSizeExceeded()
  51.216 +    
  51.217 +    def read(self, size=None):
  51.218 +        data = self.rfile.read(size)
  51.219 +        self.bytes_read += len(data)
  51.220 +        self._check_length()
  51.221 +        return data
  51.222 +    
  51.223 +    def readline(self, size=None):
  51.224 +        if size is not None:
  51.225 +            data = self.rfile.readline(size)
  51.226 +            self.bytes_read += len(data)
  51.227 +            self._check_length()
  51.228 +            return data
  51.229 +        
  51.230 +        # User didn't specify a size ...
  51.231 +        # We read the line in chunks to make sure it's not a 100MB line !
  51.232 +        res = []
  51.233 +        while True:
  51.234 +            data = self.rfile.readline(256)
  51.235 +            self.bytes_read += len(data)
  51.236 +            self._check_length()
  51.237 +            res.append(data)
  51.238 +            # See http://www.cherrypy.org/ticket/421
  51.239 +            if len(data) < 256 or data[-1:] == "\n":
  51.240 +                return ''.join(res)
  51.241 +    
  51.242 +    def readlines(self, sizehint=0):
  51.243 +        # Shamelessly stolen from StringIO
  51.244 +        total = 0
  51.245 +        lines = []
  51.246 +        line = self.readline()
  51.247 +        while line:
  51.248 +            lines.append(line)
  51.249 +            total += len(line)
  51.250 +            if 0 < sizehint <= total:
  51.251 +                break
  51.252 +            line = self.readline()
  51.253 +        return lines
  51.254 +    
  51.255 +    def close(self):
  51.256 +        self.rfile.close()
  51.257 +    
  51.258 +    def __iter__(self):
  51.259 +        return self
  51.260 +    
  51.261 +    def next(self):
  51.262 +        data = self.rfile.next()
  51.263 +        self.bytes_read += len(data)
  51.264 +        self._check_length()
  51.265 +        return data
  51.266 +
  51.267 +
  51.268 +class KnownLengthRFile(object):
  51.269 +    """Wraps a file-like object, returning an empty string when exhausted."""
  51.270 +    
  51.271 +    def __init__(self, rfile, content_length):
  51.272 +        self.rfile = rfile
  51.273 +        self.remaining = content_length
  51.274 +    
  51.275 +    def read(self, size=None):
  51.276 +        if self.remaining == 0:
  51.277 +            return ''
  51.278 +        if size is None:
  51.279 +            size = self.remaining
  51.280 +        else:
  51.281 +            size = min(size, self.remaining)
  51.282 +        
  51.283 +        data = self.rfile.read(size)
  51.284 +        self.remaining -= len(data)
  51.285 +        return data
  51.286 +    
  51.287 +    def readline(self, size=None):
  51.288 +        if self.remaining == 0:
  51.289 +            return ''
  51.290 +        if size is None:
  51.291 +            size = self.remaining
  51.292 +        else:
  51.293 +            size = min(size, self.remaining)
  51.294 +        
  51.295 +        data = self.rfile.readline(size)
  51.296 +        self.remaining -= len(data)
  51.297 +        return data
  51.298 +    
  51.299 +    def readlines(self, sizehint=0):
  51.300 +        # Shamelessly stolen from StringIO
  51.301 +        total = 0
  51.302 +        lines = []
  51.303 +        line = self.readline(sizehint)
  51.304 +        while line:
  51.305 +            lines.append(line)
  51.306 +            total += len(line)
  51.307 +            if 0 < sizehint <= total:
  51.308 +                break
  51.309 +            line = self.readline(sizehint)
  51.310 +        return lines
  51.311 +    
  51.312 +    def close(self):
  51.313 +        self.rfile.close()
  51.314 +    
  51.315 +    def __iter__(self):
  51.316 +        return self
  51.317 +    
  51.318 +    def __next__(self):
  51.319 +        data = next(self.rfile)
  51.320 +        self.remaining -= len(data)
  51.321 +        return data
  51.322 +
  51.323 +
  51.324 +class ChunkedRFile(object):
  51.325 +    """Wraps a file-like object, returning an empty string when exhausted.
  51.326 +    
  51.327 +    This class is intended to provide a conforming wsgi.input value for
  51.328 +    request entities that have been encoded with the 'chunked' transfer
  51.329 +    encoding.
  51.330 +    """
  51.331 +    
  51.332 +    def __init__(self, rfile, maxlen, bufsize=8192):
  51.333 +        self.rfile = rfile
  51.334 +        self.maxlen = maxlen
  51.335 +        self.bytes_read = 0
  51.336 +        self.buffer = ''
  51.337 +        self.bufsize = bufsize
  51.338 +        self.closed = False
  51.339 +    
  51.340 +    def _fetch(self):
  51.341 +        if self.closed:
  51.342 +            return
  51.343 +        
  51.344 +        line = self.rfile.readline()
  51.345 +        self.bytes_read += len(line)
  51.346 +        
  51.347 +        if self.maxlen and self.bytes_read > self.maxlen:
  51.348 +            raise MaxSizeExceeded("Request Entity Too Large", self.maxlen)
  51.349 +        
  51.350 +        line = line.strip().split(";", 1)
  51.351 +        
  51.352 +        try:
  51.353 +            chunk_size = line.pop(0)
  51.354 +            chunk_size = int(chunk_size, 16)
  51.355 +        except ValueError:
  51.356 +            raise ValueError("Bad chunked transfer size: " + repr(chunk_size))
  51.357 +        
  51.358 +        if chunk_size <= 0:
  51.359 +            self.closed = True
  51.360 +            return
  51.361 +        
  51.362 +##            if line: chunk_extension = line[0]
  51.363 +        
  51.364 +        if self.maxlen and self.bytes_read + chunk_size > self.maxlen:
  51.365 +            raise IOError("Request Entity Too Large")
  51.366 +        
  51.367 +        chunk = self.rfile.read(chunk_size)
  51.368 +        self.bytes_read += len(chunk)
  51.369 +        self.buffer += chunk
  51.370 +        
  51.371 +        crlf = self.rfile.read(2)
  51.372 +        if crlf != CRLF:
  51.373 +            raise ValueError(
  51.374 +                 "Bad chunked transfer coding (expected '\\r\\n', "
  51.375 +                 "got " + repr(crlf) + ")")
  51.376 +    
  51.377 +    def read(self, size=None):
  51.378 +        data = ''
  51.379 +        while True:
  51.380 +            if size and len(data) >= size:
  51.381 +                return data
  51.382 +            
  51.383 +            if not self.buffer:
  51.384 +                self._fetch()
  51.385 +                if not self.buffer:
  51.386 +                    # EOF
  51.387 +                    return data
  51.388 +            
  51.389 +            if size:
  51.390 +                remaining = size - len(data)
  51.391 +                data += self.buffer[:remaining]
  51.392 +                self.buffer = self.buffer[remaining:]
  51.393 +            else:
  51.394 +                data += self.buffer
  51.395 +    
  51.396 +    def readline(self, size=None):
  51.397 +        data = ''
  51.398 +        while True:
  51.399 +            if size and len(data) >= size:
  51.400 +                return data
  51.401 +            
  51.402 +            if not self.buffer:
  51.403 +                self._fetch()
  51.404 +                if not self.buffer:
  51.405 +                    # EOF
  51.406 +                    return data
  51.407 +            
  51.408 +            newline_pos = self.buffer.find('\n')
  51.409 +            if size:
  51.410 +                if newline_pos == -1:
  51.411 +                    remaining = size - len(data)
  51.412 +                    data += self.buffer[:remaining]
  51.413 +                    self.buffer = self.buffer[remaining:]
  51.414 +                else:
  51.415 +                    remaining = min(size - len(data), newline_pos)
  51.416 +                    data += self.buffer[:remaining]
  51.417 +                    self.buffer = self.buffer[remaining:]
  51.418 +            else:
  51.419 +                if newline_pos == -1:
  51.420 +                    data += self.buffer
  51.421 +                else:
  51.422 +                    data += self.buffer[:newline_pos]
  51.423 +                    self.buffer = self.buffer[newline_pos:]
  51.424 +    
  51.425 +    def readlines(self, sizehint=0):
  51.426 +        # Shamelessly stolen from StringIO
  51.427 +        total = 0
  51.428 +        lines = []
  51.429 +        line = self.readline(sizehint)
  51.430 +        while line:
  51.431 +            lines.append(line)
  51.432 +            total += len(line)
  51.433 +            if 0 < sizehint <= total:
  51.434 +                break
  51.435 +            line = self.readline(sizehint)
  51.436 +        return lines
  51.437 +    
  51.438 +    def read_trailer_lines(self):
  51.439 +        if not self.closed:
  51.440 +            raise ValueError(
  51.441 +                "Cannot read trailers until the request body has been read.")
  51.442 +        
  51.443 +        while True:
  51.444 +            line = self.rfile.readline()
  51.445 +            if not line:
  51.446 +                # No more data--illegal end of headers
  51.447 +                raise ValueError("Illegal end of headers.")
  51.448 +            
  51.449 +            self.bytes_read += len(line)
  51.450 +            if self.maxlen and self.bytes_read > self.maxlen:
  51.451 +                raise IOError("Request Entity Too Large")
  51.452 +            
  51.453 +            if line == CRLF:
  51.454 +                # Normal end of headers
  51.455 +                break
  51.456 +            if not line.endswith(CRLF):
  51.457 +                raise ValueError("HTTP requires CRLF terminators")
  51.458 +            
  51.459 +            yield line
  51.460 +    
  51.461 +    def close(self):
  51.462 +        self.rfile.close()
  51.463 +    
  51.464 +    def __iter__(self):
  51.465 +        # Shamelessly stolen from StringIO
  51.466 +        total = 0
  51.467 +        line = self.readline(sizehint)
  51.468 +        while line:
  51.469 +            yield line
  51.470 +            total += len(line)
  51.471 +            if 0 < sizehint <= total:
  51.472 +                break
  51.473 +            line = self.readline(sizehint)
  51.474 +
  51.475 +
  51.476 +class HTTPRequest(object):
  51.477 +    """An HTTP Request (and response).
  51.478 +    
  51.479 +    A single HTTP connection may consist of multiple request/response pairs.
  51.480 +    """
  51.481 +    
  51.482 +    server = None
  51.483 +    """The HTTPServer object which is receiving this request."""
  51.484 +    
  51.485 +    conn = None
  51.486 +    """The HTTPConnection object on which this request connected."""
  51.487 +    
  51.488 +    inheaders = {}
  51.489 +    """A dict of request headers."""
  51.490 +    
  51.491 +    outheaders = []
  51.492 +    """A list of header tuples to write in the response."""
  51.493 +    
  51.494 +    ready = False
  51.495 +    """When True, the request has been parsed and is ready to begin generating
  51.496 +    the response. When False, signals the calling Connection that the response
  51.497 +    should not be generated and the connection should close."""
  51.498 +    
  51.499 +    close_connection = False
  51.500 +    """Signals the calling Connection that the request should close. This does
  51.501 +    not imply an error! The client and/or server may each request that the
  51.502 +    connection be closed."""
  51.503 +    
  51.504 +    chunked_write = False
  51.505 +    """If True, output will be encoded with the "chunked" transfer-coding.
  51.506 +    
  51.507 +    This value is set automatically inside send_headers."""
  51.508 +    
  51.509 +    def __init__(self, server, conn):
  51.510 +        self.server= server
  51.511 +        self.conn = conn
  51.512 +        
  51.513 +        self.ready = False
  51.514 +        self.started_request = False
  51.515 +        self.scheme = "http"
  51.516 +        if self.server.ssl_adapter is not None:
  51.517 +            self.scheme = "https"
  51.518 +        # Use the lowest-common protocol in case read_request_line errors.
  51.519 +        self.response_protocol = 'HTTP/1.0'
  51.520 +        self.inheaders = {}
  51.521 +        
  51.522 +        self.status = ""
  51.523 +        self.outheaders = []
  51.524 +        self.sent_headers = False
  51.525 +        self.close_connection = self.__class__.close_connection
  51.526 +        self.chunked_read = False
  51.527 +        self.chunked_write = self.__class__.chunked_write
  51.528 +    
  51.529 +    def parse_request(self):
  51.530 +        """Parse the next HTTP request start-line and message-headers."""
  51.531 +        self.rfile = SizeCheckWrapper(self.conn.rfile,
  51.532 +                                      self.server.max_request_header_size)
  51.533 +        try:
  51.534 +            self.read_request_line()
  51.535 +        except MaxSizeExceeded:
  51.536 +            self.simple_response("414 Request-URI Too Long",
  51.537 +                "The Request-URI sent with the request exceeds the maximum "
  51.538 +                "allowed bytes.")
  51.539 +            return
  51.540 +        
  51.541 +        try:
  51.542 +            success = self.read_request_headers()
  51.543 +        except MaxSizeExceeded:
  51.544 +            self.simple_response("413 Request Entity Too Large",
  51.545 +                "The headers sent with the request exceed the maximum "
  51.546 +                "allowed bytes.")
  51.547 +            return
  51.548 +        else:
  51.549 +            if not success:
  51.550 +                return
  51.551 +        
  51.552 +        self.ready = True
  51.553 +    
  51.554 +    def read_request_line(self):
  51.555 +        # HTTP/1.1 connections are persistent by default. If a client
  51.556 +        # requests a page, then idles (leaves the connection open),
  51.557 +        # then rfile.readline() will raise socket.error("timed out").
  51.558 +        # Note that it does this based on the value given to settimeout(),
  51.559 +        # and doesn't need the client to request or acknowledge the close
  51.560 +        # (although your TCP stack might suffer for it: cf Apache's history
  51.561 +        # with FIN_WAIT_2).
  51.562 +        request_line = self.rfile.readline()
  51.563 +        
  51.564 +        # Set started_request to True so communicate() knows to send 408
  51.565 +        # from here on out.
  51.566 +        self.started_request = True
  51.567 +        if not request_line:
  51.568 +            # Force self.ready = False so the connection will close.
  51.569 +            self.ready = False
  51.570 +            return
  51.571 +        
  51.572 +        if request_line == CRLF:
  51.573 +            # RFC 2616 sec 4.1: "...if the server is reading the protocol
  51.574 +            # stream at the beginning of a message and receives a CRLF
  51.575 +            # first, it should ignore the CRLF."
  51.576 +            # But only ignore one leading line! else we enable a DoS.
  51.577 +            request_line = self.rfile.readline()
  51.578 +            if not request_line:
  51.579 +                self.ready = False
  51.580 +                return
  51.581 +        
  51.582 +        if not request_line.endswith(CRLF):
  51.583 +            self.simple_response("400 Bad Request", "HTTP requires CRLF terminators")
  51.584 +            return
  51.585 +        
  51.586 +        try:
  51.587 +            method, uri, req_protocol = request_line.strip().split(" ", 2)
  51.588 +            rp = int(req_protocol[5]), int(req_protocol[7])
  51.589 +        except (ValueError, IndexError):
  51.590 +            self.simple_response("400 Bad Request", "Malformed Request-Line")
  51.591 +            return
  51.592 +        
  51.593 +        self.uri = uri
  51.594 +        self.method = method
  51.595 +        
  51.596 +        # uri may be an abs_path (including "http://host.domain.tld");
  51.597 +        scheme, authority, path = self.parse_request_uri(uri)
  51.598 +        if '#' in path:
  51.599 +            self.simple_response("400 Bad Request",
  51.600 +                                 "Illegal #fragment in Request-URI.")
  51.601 +            return
  51.602 +        
  51.603 +        if scheme:
  51.604 +            self.scheme = scheme
  51.605 +        
  51.606 +        qs = ''
  51.607 +        if '?' in path:
  51.608 +            path, qs = path.split('?', 1)
  51.609 +        
  51.610 +        # Unquote the path+params (e.g. "/this%20path" -> "/this path").
  51.611 +        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
  51.612 +        #
  51.613 +        # But note that "...a URI must be separated into its components
  51.614 +        # before the escaped characters within those components can be
  51.615 +        # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
  51.616 +        # Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
  51.617 +        try:
  51.618 +            atoms = [unquote(x) for x in quoted_slash.split(path)]
  51.619 +        except ValueError, ex:
  51.620 +            self.simple_response("400 Bad Request", ex.args[0])
  51.621 +            return
  51.622 +        path = "%2F".join(atoms)
  51.623 +        self.path = path
  51.624 +        
  51.625 +        # Note that, like wsgiref and most other HTTP servers,
  51.626 +        # we "% HEX HEX"-unquote the path but not the query string.
  51.627 +        self.qs = qs
  51.628 +        
  51.629 +        # Compare request and server HTTP protocol versions, in case our
  51.630 +        # server does not support the requested protocol. Limit our output
  51.631 +        # to min(req, server). We want the following output:
  51.632 +        #     request    server     actual written   supported response
  51.633 +        #     protocol   protocol  response protocol    feature set
  51.634 +        # a     1.0        1.0           1.0                1.0
  51.635 +        # b     1.0        1.1           1.1                1.0
  51.636 +        # c     1.1        1.0           1.0                1.0
  51.637 +        # d     1.1        1.1           1.1                1.1
  51.638 +        # Notice that, in (b), the response will be "HTTP/1.1" even though
  51.639 +        # the client only understands 1.0. RFC 2616 10.5.6 says we should
  51.640 +        # only return 505 if the _major_ version is different.
  51.641 +        sp = int(self.server.protocol[5]), int(self.server.protocol[7])
  51.642 +        
  51.643 +        if sp[0] != rp[0]:
  51.644 +            self.simple_response("505 HTTP Version Not Supported")
  51.645 +            return
  51.646 +        self.request_protocol = req_protocol
  51.647 +        self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
  51.648 +    
  51.649 +    def read_request_headers(self):
  51.650 +        """Read self.rfile into self.inheaders. Return success."""
  51.651 +        
  51.652 +        # then all the http headers
  51.653 +        try:
  51.654 +            read_headers(self.rfile, self.inheaders)
  51.655 +        except ValueError, ex:
  51.656 +            self.simple_response("400 Bad Request", ex.args[0])
  51.657 +            return False
  51.658 +        
  51.659 +        mrbs = self.server.max_request_body_size
  51.660 +        if mrbs and int(self.inheaders.get("Content-Length", 0)) > mrbs:
  51.661 +            self.simple_response("413 Request Entity Too Large",
  51.662 +                "The entity sent with the request exceeds the maximum "
  51.663 +                "allowed bytes.")
  51.664 +            return False
  51.665 +        
  51.666 +        # Persistent connection support
  51.667 +        if self.response_protocol == "HTTP/1.1":
  51.668 +            # Both server and client are HTTP/1.1
  51.669 +            if self.inheaders.get("Connection", "") == "close":
  51.670 +                self.close_connection = True
  51.671 +        else:
  51.672 +            # Either the server or client (or both) are HTTP/1.0
  51.673 +            if self.inheaders.get("Connection", "") != "Keep-Alive":
  51.674 +                self.close_connection = True
  51.675 +        
  51.676 +        # Transfer-Encoding support
  51.677 +        te = None
  51.678 +        if self.response_protocol == "HTTP/1.1":
  51.679 +            te = self.inheaders.get("Transfer-Encoding")
  51.680 +            if te:
  51.681 +                te = [x.strip().lower() for x in te.split(",") if x.strip()]
  51.682 +        
  51.683 +        self.chunked_read = False
  51.684 +        
  51.685 +        if te:
  51.686 +            for enc in te:
  51.687 +                if enc == "chunked":
  51.688 +                    self.chunked_read = True
  51.689 +                else:
  51.690 +                    # Note that, even if we see "chunked", we must reject
  51.691 +                    # if there is an extension we don't recognize.
  51.692 +                    self.simple_response("501 Unimplemented")
  51.693 +                    self.close_connection = True
  51.694 +                    return False
  51.695 +        
  51.696 +        # From PEP 333:
  51.697 +        # "Servers and gateways that implement HTTP 1.1 must provide
  51.698 +        # transparent support for HTTP 1.1's "expect/continue" mechanism.
  51.699 +        # This may be done in any of several ways:
  51.700 +        #   1. Respond to requests containing an Expect: 100-continue request
  51.701 +        #      with an immediate "100 Continue" response, and proceed normally.
  51.702 +        #   2. Proceed with the request normally, but provide the application
  51.703 +        #      with a wsgi.input stream that will send the "100 Continue"
  51.704 +        #      response if/when the application first attempts to read from
  51.705 +        #      the input stream. The read request must then remain blocked
  51.706 +        #      until the client responds.
  51.707 +        #   3. Wait until the client decides that the server does not support
  51.708 +        #      expect/continue, and sends the request body on its own.
  51.709 +        #      (This is suboptimal, and is not recommended.)
  51.710 +        #
  51.711 +        # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
  51.712 +        # but it seems like it would be a big slowdown for such a rare case.
  51.713 +        if self.inheaders.get("Expect", "") == "100-continue":
  51.714 +            # Don't use simple_response here, because it emits headers
  51.715 +            # we don't want. See http://www.cherrypy.org/ticket/951
  51.716 +            msg = self.server.protocol + " 100 Continue\r\n\r\n"
  51.717 +            try:
  51.718 +                self.conn.wfile.sendall(msg)
  51.719 +            except socket.error, x:
  51.720 +                if x.args[0] not in socket_errors_to_ignore:
  51.721 +                    raise
  51.722 +        return True
  51.723 +    
  51.724 +    def parse_request_uri(self, uri):
  51.725 +        """Parse a Request-URI into (scheme, authority, path).
  51.726 +        
  51.727 +        Note that Request-URI's must be one of::
  51.728 +            
  51.729 +            Request-URI    = "*" | absoluteURI | abs_path | authority
  51.730 +        
  51.731 +        Therefore, a Request-URI which starts with a double forward-slash
  51.732 +        cannot be a "net_path"::
  51.733 +        
  51.734 +            net_path      = "//" authority [ abs_path ]
  51.735 +        
  51.736 +        Instead, it must be interpreted as an "abs_path" with an empty first
  51.737 +        path segment::
  51.738 +        
  51.739 +            abs_path      = "/"  path_segments
  51.740 +            path_segments = segment *( "/" segment )
  51.741 +            segment       = *pchar *( ";" param )
  51.742 +            param         = *pchar
  51.743 +        """
  51.744 +        if uri == "*":
  51.745 +            return None, None, uri
  51.746 +        
  51.747 +        i = uri.find('://')
  51.748 +        if i > 0 and '?' not in uri[:i]:
  51.749 +            # An absoluteURI.
  51.750 +            # If there's a scheme (and it must be http or https), then:
  51.751 +            # http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
  51.752 +            scheme, remainder = uri[:i].lower(), uri[i + 3:]
  51.753 +            authority, path = remainder.split("/", 1)
  51.754 +            return scheme, authority, path
  51.755 +        
  51.756 +        if uri.startswith('/'):
  51.757 +            # An abs_path.
  51.758 +            return None, None, uri
  51.759 +        else:
  51.760 +            # An authority.
  51.761 +            return None, uri, None
  51.762 +    
  51.763 +    def respond(self):
  51.764 +        """Call the gateway and write its iterable output."""
  51.765 +        mrbs = self.server.max_request_body_size
  51.766 +        if self.chunked_read:
  51.767 +            self.rfile = ChunkedRFile(self.conn.rfile, mrbs)
  51.768 +        else:
  51.769 +            cl = int(self.inheaders.get("Content-Length", 0))
  51.770 +            if mrbs and mrbs < cl:
  51.771 +                if not self.sent_headers:
  51.772 +                    self.simple_response("413 Request Entity Too Large",
  51.773 +                        "The entity sent with the request exceeds the maximum "
  51.774 +                        "allowed bytes.")
  51.775 +                return
  51.776 +            self.rfile = KnownLengthRFile(self.conn.rfile, cl)
  51.777 +        
  51.778 +        self.server.gateway(self).respond()
  51.779 +        
  51.780 +        if (self.ready and not self.sent_headers):
  51.781 +            self.sent_headers = True
  51.782 +            self.send_headers()
  51.783 +        if self.chunked_write:
  51.784 +            self.conn.wfile.sendall("0\r\n\r\n")
  51.785 +    
  51.786 +    def simple_response(self, status, msg=""):
  51.787 +        """Write a simple response back to the client."""
  51.788 +        status = str(status)
  51.789 +        buf = [self.server.protocol + " " +
  51.790 +               status + CRLF,
  51.791 +               "Content-Length: %s\r\n" % len(msg),
  51.792 +               "Content-Type: text/plain\r\n"]
  51.793 +        
  51.794 +        if status[:3] in ("413", "414"):
  51.795 +            # Request Entity Too Large / Request-URI Too Long
  51.796 +            self.close_connection = True
  51.797 +            if self.response_protocol == 'HTTP/1.1':
  51.798 +                # This will not be true for 414, since read_request_line
  51.799 +                # usually raises 414 before reading the whole line, and we
  51.800 +                # therefore cannot know the proper response_protocol.
  51.801 +                buf.append("Connection: close\r\n")
  51.802 +            else:
  51.803 +                # HTTP/1.0 had no 413/414 status nor Connection header.
  51.804 +                # Emit 400 instead and trust the message body is enough.
  51.805 +                status = "400 Bad Request"
  51.806 +        
  51.807 +        buf.append(CRLF)
  51.808 +        if msg:
  51.809 +            if isinstance(msg, unicode):
  51.810 +                msg = msg.encode("ISO-8859-1")
  51.811 +            buf.append(msg)
  51.812 +        
  51.813 +        try:
  51.814 +            self.conn.wfile.sendall("".join(buf))
  51.815 +        except socket.error, x:
  51.816 +            if x.args[0] not in socket_errors_to_ignore:
  51.817 +                raise
  51.818 +    
  51.819 +    def write(self, chunk):
  51.820 +        """Write unbuffered data to the client."""
  51.821 +        if self.chunked_write and chunk:
  51.822 +            buf = [hex(len(chunk))[2:], CRLF, chunk, CRLF]
  51.823 +            self.conn.wfile.sendall("".join(buf))
  51.824 +        else:
  51.825 +            self.conn.wfile.sendall(chunk)
  51.826 +    
  51.827 +    def send_headers(self):
  51.828 +        """Assert, process, and send the HTTP response message-headers.
  51.829 +        
  51.830 +        You must set self.status, and self.outheaders before calling this.
  51.831 +        """
  51.832 +        hkeys = [key.lower() for key, value in self.outheaders]
  51.833 +        status = int(self.status[:3])
  51.834 +        
  51.835 +        if status == 413:
  51.836 +            # Request Entity Too Large. Close conn to avoid garbage.
  51.837 +            self.close_connection = True
  51.838 +        elif "content-length" not in hkeys:
  51.839 +            # "All 1xx (informational), 204 (no content),
  51.840 +            # and 304 (not modified) responses MUST NOT
  51.841 +            # include a message-body." So no point chunking.
  51.842 +            if status < 200 or status in (204, 205, 304):
  51.843 +                pass
  51.844 +            else:
  51.845 +                if (self.response_protocol == 'HTTP/1.1'
  51.846 +                    and self.method != 'HEAD'):
  51.847 +                    # Use the chunked transfer-coding
  51.848 +                    self.chunked_write = True
  51.849 +                    self.outheaders.append(("Transfer-Encoding", "chunked"))
  51.850 +                else:
  51.851 +                    # Closing the conn is the only way to determine len.
  51.852 +                    self.close_connection = True
  51.853 +        
  51.854 +        if "connection" not in hkeys:
  51.855 +            if self.response_protocol == 'HTTP/1.1':
  51.856 +                # Both server and client are HTTP/1.1 or better
  51.857 +                if self.close_connection:
  51.858 +                    self.outheaders.append(("Connection", "close"))
  51.859 +            else:
  51.860 +                # Server and/or client are HTTP/1.0
  51.861 +                if not self.close_connection:
  51.862 +                    self.outheaders.append(("Connection", "Keep-Alive"))
  51.863 +        
  51.864 +        if (not self.close_connection) and (not self.chunked_read):
  51.865 +            # Read any remaining request body data on the socket.
  51.866 +            # "If an origin server receives a request that does not include an
  51.867 +            # Expect request-header field with the "100-continue" expectation,
  51.868 +            # the request includes a request body, and the server responds
  51.869 +            # with a final status code before reading the entire request body
  51.870 +            # from the transport connection, then the server SHOULD NOT close
  51.871 +            # the transport connection until it has read the entire request,
  51.872 +            # or until the client closes the connection. Otherwise, the client
  51.873 +            # might not reliably receive the response message. However, this
  51.874 +            # requirement is not be construed as preventing a server from
  51.875 +            # defending itself against denial-of-service attacks, or from
  51.876 +            # badly broken client implementations."
  51.877 +            remaining = getattr(self.rfile, 'remaining', 0)
  51.878 +            if remaining > 0:
  51.879 +                self.rfile.read(remaining)
  51.880 +        
  51.881 +        if "date" not in hkeys:
  51.882 +            self.outheaders.append(("Date", rfc822.formatdate()))
  51.883 +        
  51.884 +        if "server" not in hkeys:
  51.885 +            self.outheaders.append(("Server", self.server.server_name))
  51.886 +        
  51.887 +        buf = [self.server.protocol + " " + self.status + CRLF]
  51.888 +        for k, v in self.outheaders:
  51.889 +            buf.append(k + ": " + v + CRLF)
  51.890 +        buf.append(CRLF)
  51.891 +        self.conn.wfile.sendall("".join(buf))
  51.892 +
  51.893 +
  51.894 +class NoSSLError(Exception):
  51.895 +    """Exception raised when a client speaks HTTP to an HTTPS socket."""
  51.896 +    pass
  51.897 +
  51.898 +
  51.899 +class FatalSSLAlert(Exception):
  51.900 +    """Exception raised when the SSL implementation signals a fatal alert."""
  51.901 +    pass
  51.902 +
  51.903 +
  51.904 +class CP_fileobject(socket._fileobject):
  51.905 +    """Faux file object attached to a socket object."""
  51.906 +
  51.907 +    def __init__(self, *args, **kwargs):
  51.908 +        self.bytes_read = 0
  51.909 +        self.bytes_written = 0
  51.910 +        socket._fileobject.__init__(self, *args, **kwargs)
  51.911 +    
  51.912 +    def sendall(self, data):
  51.913 +        """Sendall for non-blocking sockets."""
  51.914 +        while data:
  51.915 +            try:
  51.916 +                bytes_sent = self.send(data)
  51.917 +                data = data[bytes_sent:]
  51.918 +            except socket.error, e:
  51.919 +                if e.args[0] not in socket_errors_nonblocking:
  51.920 +                    raise
  51.921 +
  51.922 +    def send(self, data):
  51.923 +        bytes_sent = self._sock.send(data)
  51.924 +        self.bytes_written += bytes_sent
  51.925 +        return bytes_sent
  51.926 +
  51.927 +    def flush(self):
  51.928 +        if self._wbuf:
  51.929 +            buffer = "".join(self._wbuf)
  51.930 +            self._wbuf = []
  51.931 +            self.sendall(buffer)
  51.932 +
  51.933 +    def recv(self, size):
  51.934 +        while True:
  51.935 +            try:
  51.936 +                data = self._sock.recv(size)
  51.937 +                self.bytes_read += len(data)
  51.938 +                return data
  51.939 +            except socket.error, e:
  51.940 +                if (e.args[0] not in socket_errors_nonblocking
  51.941 +                    and e.args[0] not in socket_error_eintr):
  51.942 +                    raise
  51.943 +
  51.944 +    if not _fileobject_uses_str_type:
  51.945 +        def read(self, size=-1):
  51.946 +            # Use max, disallow tiny reads in a loop as they are very inefficient.
  51.947 +            # We never leave read() with any leftover data from a new recv() call
  51.948 +            # in our internal buffer.
  51.949 +            rbufsize = max(self._rbufsize, self.default_bufsize)
  51.950 +            # Our use of StringIO rather than lists of string objects returned by
  51.951 +            # recv() minimizes memory usage and fragmentation that occurs when
  51.952 +            # rbufsize is large compared to the typical return value of recv().
  51.953 +            buf = self._rbuf
  51.954 +            buf.seek(0, 2)  # seek end
  51.955 +            if size < 0:
  51.956 +                # Read until EOF
  51.957 +                self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
  51.958 +                while True:
  51.959 +                    data = self.recv(rbufsize)
  51.960 +                    if not data:
  51.961 +                        break
  51.962 +                    buf.write(data)
  51.963 +                return buf.getvalue()
  51.964 +            else:
  51.965 +                # Read until size bytes or EOF seen, whichever comes first
  51.966 +                buf_len = buf.tell()
  51.967 +                if buf_len >= size:
  51.968 +                    # Already have size bytes in our buffer?  Extract and return.
  51.969 +                    buf.seek(0)
  51.970 +                    rv = buf.read(size)
  51.971 +                    self._rbuf = StringIO.StringIO()
  51.972 +                    self._rbuf.write(buf.read())
  51.973 +                    return rv
  51.974 +
  51.975 +                self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
  51.976 +                while True:
  51.977 +                    left = size - buf_len
  51.978 +                    # recv() will malloc the amount of memory given as its
  51.979 +                    # parameter even though it often returns much less data
  51.980 +                    # than that.  The returned data string is short lived
  51.981 +                    # as we copy it into a StringIO and free it.  This avoids
  51.982 +                    # fragmentation issues on many platforms.
  51.983 +                    data = self.recv(left)
  51.984 +                    if not data:
  51.985 +                        break
  51.986 +                    n = len(data)
  51.987 +                    if n == size and not buf_len:
  51.988 +                        # Shortcut.  Avoid buffer data copies when:
  51.989 +                        # - We have no data in our buffer.
  51.990 +                        # AND
  51.991 +                        # - Our call to recv returned exactly the
  51.992 +                        #   number of bytes we were asked to read.
  51.993 +                        return data
  51.994 +                    if n == left:
  51.995 +                        buf.write(data)
  51.996 +                        del data  # explicit free
  51.997 +                        break
  51.998 +                    assert n <= left, "recv(%d) returned %d bytes" % (left, n)
  51.999 +                    buf.write(data)
 51.1000 +                    buf_len += n
 51.1001 +                    del data  # explicit free
 51.1002 +                    #assert buf_len == buf.tell()
 51.1003 +                return buf.getvalue()
 51.1004 +
 51.1005 +        def readline(self, size=-1):
 51.1006 +            buf = self._rbuf
 51.1007 +            buf.seek(0, 2)  # seek end
 51.1008 +            if buf.tell() > 0:
 51.1009 +                # check if we already have it in our buffer
 51.1010 +                buf.seek(0)
 51.1011 +                bline = buf.readline(size)
 51.1012 +                if bline.endswith('\n') or len(bline) == size:
 51.1013 +                    self._rbuf = StringIO.StringIO()
 51.1014 +                    self._rbuf.write(buf.read())
 51.1015 +                    return bline
 51.1016 +                del bline
 51.1017 +            if size < 0:
 51.1018 +                # Read until \n or EOF, whichever comes first
 51.1019 +                if self._rbufsize <= 1:
 51.1020 +                    # Speed up unbuffered case
 51.1021 +                    buf.seek(0)
 51.1022 +                    buffers = [buf.read()]
 51.1023 +                    self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
 51.1024 +                    data = None
 51.1025 +                    recv = self.recv
 51.1026 +                    while data != "\n":
 51.1027 +                        data = recv(1)
 51.1028 +                        if not data:
 51.1029 +                            break
 51.1030 +                        buffers.append(data)
 51.1031 +                    return "".join(buffers)
 51.1032 +
 51.1033 +                buf.seek(0, 2)  # seek end
 51.1034 +                self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
 51.1035 +                while True:
 51.1036 +                    data = self.recv(self._rbufsize)
 51.1037 +                    if not data:
 51.1038 +                        break
 51.1039 +                    nl = data.find('\n')
 51.1040 +                    if nl >= 0:
 51.1041 +                        nl += 1
 51.1042 +                        buf.write(data[:nl])
 51.1043 +                        self._rbuf.write(data[nl:])
 51.1044 +                        del data
 51.1045 +                        break
 51.1046 +                    buf.write(data)
 51.1047 +                return buf.getvalue()
 51.1048 +            else:
 51.1049 +                # Read until size bytes or \n or EOF seen, whichever comes first
 51.1050 +                buf.seek(0, 2)  # seek end
 51.1051 +                buf_len = buf.tell()
 51.1052 +                if buf_len >= size:
 51.1053 +                    buf.seek(0)
 51.1054 +                    rv = buf.read(size)
 51.1055 +                    self._rbuf = StringIO.StringIO()
 51.1056 +                    self._rbuf.write(buf.read())
 51.1057 +                    return rv
 51.1058 +                self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
 51.1059 +                while True:
 51.1060 +                    data = self.recv(self._rbufsize)
 51.1061 +                    if not data:
 51.1062 +                        break
 51.1063 +                    left = size - buf_len
 51.1064 +                    # did we just receive a newline?
 51.1065 +                    nl = data.find('\n', 0, left)
 51.1066 +                    if nl >= 0:
 51.1067 +                        nl += 1
 51.1068 +                        # save the excess data to _rbuf
 51.1069 +                        self._rbuf.write(data[nl:])
 51.1070 +                        if buf_len:
 51.1071 +                            buf.write(data[:nl])
 51.1072 +                            break
 51.1073 +                        else:
 51.1074 +                            # Shortcut.  Avoid data copy through buf when returning
 51.1075 +                            # a substring of our first recv().
 51.1076 +                            return data[:nl]
 51.1077 +                    n = len(data)
 51.1078 +                    if n == size and not buf_len:
 51.1079 +                        # Shortcut.  Avoid data copy through buf when
 51.1080 +                        # returning exactly all of our first recv().
 51.1081 +                        return data
 51.1082 +                    if n >= left:
 51.1083 +                        buf.write(data[:left])
 51.1084 +                        self._rbuf.write(data[left:])
 51.1085 +                        break
 51.1086 +                    buf.write(data)
 51.1087 +                    buf_len += n
 51.1088 +                    #assert buf_len == buf.tell()
 51.1089 +                return buf.getvalue()
 51.1090 +    else:
 51.1091 +        def read(self, size=-1):
 51.1092 +            if size < 0:
 51.1093 +                # Read until EOF
 51.1094 +                buffers = [self._rbuf]
 51.1095 +                self._rbuf = ""
 51.1096 +                if self._rbufsize <= 1:
 51.1097 +                    recv_size = self.default_bufsize
 51.1098 +                else:
 51.1099 +                    recv_size = self._rbufsize
 51.1100 +
 51.1101 +                while True:
 51.1102 +                    data = self.recv(recv_size)
 51.1103 +                    if not data:
 51.1104 +                        break
 51.1105 +                    buffers.append(data)
 51.1106 +                return "".join(buffers)
 51.1107 +            else:
 51.1108 +                # Read until size bytes or EOF seen, whichever comes first
 51.1109 +                data = self._rbuf
 51.1110 +                buf_len = len(data)
 51.1111 +                if buf_len >= size:
 51.1112 +                    self._rbuf = data[size:]
 51.1113 +                    return data[:size]
 51.1114 +                buffers = []
 51.1115 +                if data:
 51.1116 +                    buffers.append(data)
 51.1117 +                self._rbuf = ""
 51.1118 +                while True:
 51.1119 +                    left = size - buf_len
 51.1120 +                    recv_size = max(self._rbufsize, left)
 51.1121 +                    data = self.recv(recv_size)
 51.1122 +                    if not data:
 51.1123 +                        break
 51.1124 +                    buffers.append(data)
 51.1125 +                    n = len(data)
 51.1126 +                    if n >= left:
 51.1127 +                        self._rbuf = data[left:]
 51.1128 +                        buffers[-1] = data[:left]
 51.1129 +                        break
 51.1130 +                    buf_len += n
 51.1131 +                return "".join(buffers)
 51.1132 +
 51.1133 +        def readline(self, size=-1):
 51.1134 +            data = self._rbuf
 51.1135 +            if size < 0:
 51.1136 +                # Read until \n or EOF, whichever comes first
 51.1137 +                if self._rbufsize <= 1:
 51.1138 +                    # Speed up unbuffered case
 51.1139 +                    assert data == ""
 51.1140 +                    buffers = []
 51.1141 +                    while data != "\n":
 51.1142 +                        data = self.recv(1)
 51.1143 +                        if not data:
 51.1144 +                            break
 51.1145 +                        buffers.append(data)
 51.1146 +                    return "".join(buffers)
 51.1147 +                nl = data.find('\n')
 51.1148 +                if nl >= 0:
 51.1149 +                    nl += 1
 51.1150 +                    self._rbuf = data[nl:]
 51.1151 +                    return data[:nl]
 51.1152 +                buffers = []
 51.1153 +                if data:
 51.1154 +                    buffers.append(data)
 51.1155 +                self._rbuf = ""
 51.1156 +                while True:
 51.1157 +                    data = self.recv(self._rbufsize)
 51.1158 +                    if not data:
 51.1159 +                        break
 51.1160 +                    buffers.append(data)
 51.1161 +                    nl = data.find('\n')
 51.1162 +                    if nl >= 0:
 51.1163 +                        nl += 1
 51.1164 +                        self._rbuf = data[nl:]
 51.1165 +                        buffers[-1] = data[:nl]
 51.1166 +                        break
 51.1167 +                return "".join(buffers)
 51.1168 +            else:
 51.1169 +                # Read until size bytes or \n or EOF seen, whichever comes first
 51.1170 +                nl = data.find('\n', 0, size)
 51.1171 +                if nl >= 0:
 51.1172 +                    nl += 1
 51.1173 +                    self._rbuf = data[nl:]
 51.1174 +                    return data[:nl]
 51.1175 +                buf_len = len(data)
 51.1176 +                if buf_len >= size:
 51.1177 +                    self._rbuf = data[size:]
 51.1178 +                    return data[:size]
 51.1179 +                buffers = []
 51.1180 +                if data:
 51.1181 +                    buffers.append(data)
 51.1182 +                self._rbuf = ""
 51.1183 +                while True:
 51.1184 +                    data = self.recv(self._rbufsize)
 51.1185 +                    if not data:
 51.1186 +                        break
 51.1187 +                    buffers.append(data)
 51.1188 +                    left = size - buf_len
 51.1189 +                    nl = data.find('\n', 0, left)
 51.1190 +                    if nl >= 0:
 51.1191 +                        nl += 1
 51.1192 +                        self._rbuf = data[nl:]
 51.1193 +                        buffers[-1] = data[:nl]
 51.1194 +                        break
 51.1195 +                    n = len(data)
 51.1196 +                    if n >= left:
 51.1197 +                        self._rbuf = data[left:]
 51.1198 +                        buffers[-1] = data[:left]
 51.1199 +                        break
 51.1200 +                    buf_len += n
 51.1201 +                return "".join(buffers)
 51.1202 +
 51.1203 +
 51.1204 +class HTTPConnection(object):
 51.1205 +    """An HTTP connection (active socket).
 51.1206 +    
 51.1207 +    server: the Server object which received this connection.
 51.1208 +    socket: the raw socket object (usually TCP) for this connection.
 51.1209 +    makefile: a fileobject class for reading from the socket.
 51.1210 +    """
 51.1211 +    
 51.1212 +    remote_addr = None
 51.1213 +    remote_port = None
 51.1214 +    ssl_env = None
 51.1215 +    rbufsize = DEFAULT_BUFFER_SIZE
 51.1216 +    wbufsize = DEFAULT_BUFFER_SIZE
 51.1217 +    RequestHandlerClass = HTTPRequest
 51.1218 +    
 51.1219 +    def __init__(self, server, sock, makefile=CP_fileobject):
 51.1220 +        self.server = server
 51.1221 +        self.socket = sock
 51.1222 +        self.rfile = makefile(sock, "rb", self.rbufsize)
 51.1223 +        self.wfile = makefile(sock, "wb", self.wbufsize)
 51.1224 +        self.requests_seen = 0
 51.1225 +    
 51.1226 +    def communicate(self):
 51.1227 +        """Read each request and respond appropriately."""
 51.1228 +        request_seen = False
 51.1229 +        try:
 51.1230 +            while True:
 51.1231 +                # (re)set req to None so that if something goes wrong in
 51.1232 +                # the RequestHandlerClass constructor, the error doesn't
 51.1233 +                # get written to the previous request.
 51.1234 +                req = None
 51.1235 +                req = self.RequestHandlerClass(self.server, self)
 51.1236 +                
 51.1237 +                # This order of operations should guarantee correct pipelining.
 51.1238 +                req.parse_request()
 51.1239 +                if self.server.stats['Enabled']:
 51.1240 +                    self.requests_seen += 1
 51.1241 +                if not req.ready:
 51.1242 +                    # Something went wrong in the parsing (and the server has
 51.1243 +                    # probably already made a simple_response). Return and
 51.1244 +                    # let the conn close.
 51.1245 +                    return
 51.1246 +                
 51.1247 +                request_seen = True
 51.1248 +                req.respond()
 51.1249 +                if req.close_connection:
 51.1250 +                    return
 51.1251 +        except socket.error, e:
 51.1252 +            errnum = e.args[0]
 51.1253 +            # sadly SSL sockets return a different (longer) time out string
 51.1254 +            if errnum == 'timed out' or errnum == 'The read operation timed out':
 51.1255 +                # Don't error if we're between requests; only error
 51.1256 +                # if 1) no request has been started at all, or 2) we're
 51.1257 +                # in the middle of a request.
 51.1258 +                # See http://www.cherrypy.org/ticket/853
 51.1259 +                if (not request_seen) or (req and req.started_request):
 51.1260 +                    # Don't bother writing the 408 if the response
 51.1261 +                    # has already started being written.
 51.1262 +                    if req and not req.sent_headers:
 51.1263 +                        try:
 51.1264 +                            req.simple_response("408 Request Timeout")
 51.1265 +                        except FatalSSLAlert:
 51.1266 +                            # Close the connection.
 51.1267 +                            return
 51.1268 +            elif errnum not in socket_errors_to_ignore:
 51.1269 +                if req and not req.sent_headers:
 51.1270 +                    try:
 51.1271 +                        req.simple_response("500 Internal Server Error",
 51.1272 +                                            format_exc())
 51.1273 +                    except FatalSSLAlert:
 51.1274 +                        # Close the connection.
 51.1275 +                        return
 51.1276 +            return
 51.1277 +        except (KeyboardInterrupt, SystemExit):
 51.1278 +            raise
 51.1279 +        except FatalSSLAlert:
 51.1280 +            # Close the connection.
 51.1281 +            return
 51.1282 +        except NoSSLError:
 51.1283 +            if req and not req.sent_headers:
 51.1284 +                # Unwrap our wfile
 51.1285 +                self.wfile = CP_fileobject(self.socket._sock, "wb", self.wbufsize)
 51.1286 +                req.simple_response("400 Bad Request",
 51.1287 +                    "The client sent a plain HTTP request, but "
 51.1288 +                    "this server only speaks HTTPS on this port.")
 51.1289 +                self.linger = True
 51.1290 +        except Exception:
 51.1291 +            if req and not req.sent_headers:
 51.1292 +                try:
 51.1293 +                    req.simple_response("500 Internal Server Error", format_exc())
 51.1294 +                except FatalSSLAlert:
 51.1295 +                    # Close the connection.
 51.1296 +                    return
 51.1297 +    
 51.1298 +    linger = False
 51.1299 +    
 51.1300 +    def close(self):
 51.1301 +        """Close the socket underlying this connection."""
 51.1302 +        self.rfile.close()
 51.1303 +        
 51.1304 +        if not self.linger:
 51.1305 +            # Python's socket module does NOT call close on the kernel socket
 51.1306 +            # when you call socket.close(). We do so manually here because we
 51.1307 +            # want this server to send a FIN TCP segment immediately. Note this
 51.1308 +            # must be called *before* calling socket.close(), because the latter
 51.1309 +            # drops its reference to the kernel socket.
 51.1310 +            if hasattr(self.socket, '_sock'):
 51.1311 +                self.socket._sock.close()
 51.1312 +            self.socket.close()
 51.1313 +        else:
 51.1314 +            # On the other hand, sometimes we want to hang around for a bit
 51.1315 +            # to make sure the client has a chance to read our entire
 51.1316 +            # response. Skipping the close() calls here delays the FIN
 51.1317 +            # packet until the socket object is garbage-collected later.
 51.1318 +            # Someday, perhaps, we'll do the full lingering_close that
 51.1319 +            # Apache does, but not today.
 51.1320 +            pass
 51.1321 +
 51.1322 +
 51.1323 +_SHUTDOWNREQUEST = None
 51.1324 +
 51.1325 +class WorkerThread(threading.Thread):
 51.1326 +    """Thread which continuously polls a Queue for Connection objects.
 51.1327 +    
 51.1328 +    Due to the timing issues of polling a Queue, a WorkerThread does not
 51.1329 +    check its own 'ready' flag after it has started. To stop the thread,
 51.1330 +    it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
 51.1331 +    (one for each running WorkerThread).
 51.1332 +    """
 51.1333 +    
 51.1334 +    conn = None
 51.1335 +    """The current connection pulled off the Queue, or None."""
 51.1336 +    
 51.1337 +    server = None
 51.1338 +    """The HTTP Server which spawned this thread, and which owns the
 51.1339 +    Queue and is placing active connections into it."""
 51.1340 +    
 51.1341 +    ready = False
 51.1342 +    """A simple flag for the calling server to know when this thread
 51.1343 +    has begun polling the Queue."""
 51.1344 +    
 51.1345 +    
 51.1346 +    def __init__(self, server):
 51.1347 +        self.ready = False
 51.1348 +        self.server = server
 51.1349 +        
 51.1350 +        self.requests_seen = 0
 51.1351 +        self.bytes_read = 0
 51.1352 +        self.bytes_written = 0
 51.1353 +        self.start_time = None
 51.1354 +        self.work_time = 0
 51.1355 +        self.stats = {
 51.1356 +            'Requests': lambda s: self.requests_seen + ((self.start_time is None) and 0 or self.conn.requests_seen),
 51.1357 +            'Bytes Read': lambda s: self.bytes_read + ((self.start_time is None) and 0 or self.conn.rfile.bytes_read),
 51.1358 +            'Bytes Written': lambda s: self.bytes_written + ((self.start_time is None) and 0 or self.conn.wfile.bytes_written),
 51.1359 +            'Work Time': lambda s: self.work_time + ((self.start_time is None) and 0 or time.time() - self.start_time),
 51.1360 +            'Read Throughput': lambda s: s['Bytes Read'](s) / (s['Work Time'](s) or 1e-6),
 51.1361 +            'Write Throughput': lambda s: s['Bytes Written'](s) / (s['Work Time'](s) or 1e-6),
 51.1362 +        }
 51.1363 +        threading.Thread.__init__(self)
 51.1364 +    
 51.1365 +    def run(self):
 51.1366 +        self.server.stats['Worker Threads'][self.getName()] = self.stats
 51.1367 +        try:
 51.1368 +            self.ready = True
 51.1369 +            while True:
 51.1370 +                conn = self.server.requests.get()
 51.1371 +                if conn is _SHUTDOWNREQUEST:
 51.1372 +                    return
 51.1373 +                
 51.1374 +                self.conn = conn
 51.1375 +                if self.server.stats['Enabled']:
 51.1376 +                    self.start_time = time.time()
 51.1377 +                try:
 51.1378 +                    conn.communicate()
 51.1379 +                finally:
 51.1380 +                    conn.close()
 51.1381 +                    if self.server.stats['Enabled']:
 51.1382 +                        self.requests_seen += self.conn.requests_seen
 51.1383 +                        self.bytes_read += self.conn.rfile.bytes_read
 51.1384 +                        self.bytes_written += self.conn.wfile.bytes_written
 51.1385 +                        self.work_time += time.time() - self.start_time
 51.1386 +                        self.start_time = None
 51.1387 +                    self.conn = None
 51.1388 +        except (KeyboardInterrupt, SystemExit), exc:
 51.1389 +            self.server.interrupt = exc
 51.1390 +
 51.1391 +
 51.1392 +class ThreadPool(object):
 51.1393 +    """A Request Queue for the CherryPyWSGIServer which pools threads.
 51.1394 +    
 51.1395 +    ThreadPool objects must provide min, get(), put(obj), start()
 51.1396 +    and stop(timeout) attributes.
 51.1397 +    """
 51.1398 +    
 51.1399 +    def __init__(self, server, min=10, max=-1):
 51.1400 +        self.server = server
 51.1401 +        self.min = min
 51.1402 +        self.max = max
 51.1403 +        self._threads = []
 51.1404 +        self._queue = Queue.Queue()
 51.1405 +        self.get = self._queue.get
 51.1406 +    
 51.1407 +    def start(self):
 51.1408 +        """Start the pool of threads."""
 51.1409 +        for i in range(self.min):
 51.1410 +            self._threads.append(WorkerThread(self.server))
 51.1411 +        for worker in self._threads:
 51.1412 +            worker.setName("CP Server " + worker.getName())
 51.1413 +            worker.start()
 51.1414 +        for worker in self._threads:
 51.1415 +            while not worker.ready:
 51.1416 +                time.sleep(.1)
 51.1417 +    
 51.1418 +    def _get_idle(self):
 51.1419 +        """Number of worker threads which are idle. Read-only."""
 51.1420 +        return len([t for t in self._threads if t.conn is None])
 51.1421 +    idle = property(_get_idle, doc=_get_idle.__doc__)
 51.1422 +    
 51.1423 +    def put(self, obj):
 51.1424 +        self._queue.put(obj)
 51.1425 +        if obj is _SHUTDOWNREQUEST:
 51.1426 +            return
 51.1427 +    
 51.1428 +    def grow(self, amount):
 51.1429 +        """Spawn new worker threads (not above self.max)."""
 51.1430 +        for i in range(amount):
 51.1431 +            if self.max > 0 and len(self._threads) >= self.max:
 51.1432 +                break
 51.1433 +            worker = WorkerThread(self.server)
 51.1434 +            worker.setName("CP Server " + worker.getName())
 51.1435 +            self._threads.append(worker)
 51.1436 +            worker.start()
 51.1437 +    
 51.1438 +    def shrink(self, amount):
 51.1439 +        """Kill off worker threads (not below self.min)."""
 51.1440 +        # Grow/shrink the pool if necessary.
 51.1441 +        # Remove any dead threads from our list
 51.1442 +        for t in self._threads:
 51.1443 +            if not t.isAlive():
 51.1444 +                self._threads.remove(t)
 51.1445 +                amount -= 1
 51.1446 +        
 51.1447 +        if amount > 0:
 51.1448 +            for i in range(min(amount, len(self._threads) - self.min)):
 51.1449 +                # Put a number of shutdown requests on the queue equal
 51.1450 +                # to 'amount'. Once each of those is processed by a worker,
 51.1451 +                # that worker will terminate and be culled from our list
 51.1452 +                # in self.put.
 51.1453 +                self._queue.put(_SHUTDOWNREQUEST)
 51.1454 +    
 51.1455 +    def stop(self, timeout=5):
 51.1456 +        # Must shut down threads here so the code that calls
 51.1457 +        # this method can know when all threads are stopped.
 51.1458 +        for worker in self._threads:
 51.1459 +            self._queue.put(_SHUTDOWNREQUEST)
 51.1460 +        
 51.1461 +        # Don't join currentThread (when stop is called inside a request).
 51.1462 +        current = threading.currentThread()
 51.1463 +        if timeout and timeout >= 0:
 51.1464 +            endtime = time.time() + timeout
 51.1465 +        while self._threads:
 51.1466 +            worker = self._threads.pop()
 51.1467 +            if worker is not current and worker.isAlive():
 51.1468 +                try:
 51.1469 +                    if timeout is None or timeout < 0:
 51.1470 +                        worker.join()
 51.1471 +                    else:
 51.1472 +                        remaining_time = endtime - time.time()
 51.1473 +                        if remaining_time > 0:
 51.1474 +                            worker.join(remaining_time)
 51.1475 +                        if worker.isAlive():
 51.1476 +                            # We exhausted the timeout.
 51.1477 +                            # Forcibly shut down the socket.
 51.1478 +                            c = worker.conn
 51.1479 +                            if c and not c.rfile.closed:
 51.1480 +                                try:
 51.1481 +                                    c.socket.shutdown(socket.SHUT_RD)
 51.1482 +                                except TypeError:
 51.1483 +                                    # pyOpenSSL sockets don't take an arg
 51.1484 +                                    c.socket.shutdown()
 51.1485 +                            worker.join()
 51.1486 +                except (AssertionError,
 51.1487 +                        # Ignore repeated Ctrl-C.
 51.1488 +                        # See http://www.cherrypy.org/ticket/691.
 51.1489 +                        KeyboardInterrupt), exc1:
 51.1490 +                    pass
 51.1491 +    
 51.1492 +    def _get_qsize(self):
 51.1493 +        return self._queue.qsize()
 51.1494 +    qsize = property(_get_qsize)
 51.1495 +
 51.1496 +
 51.1497 +
 51.1498 +try:
 51.1499 +    import fcntl
 51.1500 +except ImportError:
 51.1501 +    try:
 51.1502 +        from ctypes import windll, WinError
 51.1503 +    except ImportError:
 51.1504 +        def prevent_socket_inheritance(sock):
 51.1505 +            """Dummy function, since neither fcntl nor ctypes are available."""
 51.1506 +            pass
 51.1507 +    else:
 51.1508 +        def prevent_socket_inheritance(sock):
 51.1509 +            """Mark the given socket fd as non-inheritable (Windows)."""
 51.1510 +            if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
 51.1511 +                raise WinError()
 51.1512 +else:
 51.1513 +    def prevent_socket_inheritance(sock):
 51.1514 +        """Mark the given socket fd as non-inheritable (POSIX)."""
 51.1515 +        fd = sock.fileno()
 51.1516 +        old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
 51.1517 +        fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
 51.1518 +
 51.1519 +
 51.1520 +class SSLAdapter(object):
 51.1521 +    """Base class for SSL driver library adapters.
 51.1522 +    
 51.1523 +    Required methods:
 51.1524 +    
 51.1525 +        * ``wrap(sock) -> (wrapped socket, ssl environ dict)``
 51.1526 +        * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> socket file object``
 51.1527 +    """
 51.1528 +    
 51.1529 +    def __init__(self, certificate, private_key, certificate_chain=None):
 51.1530 +        self.certificate = certificate
 51.1531 +        self.private_key = private_key
 51.1532 +        self.certificate_chain = certificate_chain
 51.1533 +    
 51.1534 +    def wrap(self, sock):
 51.1535 +        raise NotImplemented
 51.1536 +    
 51.1537 +    def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
 51.1538 +        raise NotImplemented
 51.1539 +
 51.1540 +
 51.1541 +class HTTPServer(object):
 51.1542 +    """An HTTP server."""
 51.1543 +    
 51.1544 +    _bind_addr = "127.0.0.1"
 51.1545 +    _interrupt = None
 51.1546 +    
 51.1547 +    gateway = None
 51.1548 +    """A Gateway instance."""
 51.1549 +    
 51.1550 +    minthreads = None
 51.1551 +    """The minimum number of worker threads to create (default 10)."""
 51.1552 +    
 51.1553 +    maxthreads = None
 51.1554 +    """The maximum number of worker threads to create (default -1 = no limit)."""
 51.1555 +    
 51.1556 +    server_name = None
 51.1557 +    """The name of the server; defaults to socket.gethostname()."""
 51.1558 +    
 51.1559 +    protocol = "HTTP/1.1"
 51.1560 +    """The version string to write in the Status-Line of all HTTP responses.
 51.1561 +    
 51.1562 +    For example, "HTTP/1.1" is the default. This also limits the supported
 51.1563 +    features used in the response."""
 51.1564 +    
 51.1565 +    request_queue_size = 5
 51.1566 +    """The 'backlog' arg to socket.listen(); max queued connections (default 5)."""
 51.1567 +    
 51.1568 +    shutdown_timeout = 5
 51.1569 +    """The total time, in seconds, to wait for worker threads to cleanly exit."""
 51.1570 +    
 51.1571 +    timeout = 10
 51.1572 +    """The timeout in seconds for accepted connections (default 10)."""
 51.1573 +    
 51.1574 +    version = "CherryPy/3.2.0"
 51.1575 +    """A version string for the HTTPServer."""
 51.1576 +    
 51.1577 +    software = None
 51.1578 +    """The value to set for the SERVER_SOFTWARE entry in the WSGI environ.
 51.1579 +    
 51.1580 +    If None, this defaults to ``'%s Server' % self.version``."""
 51.1581 +    
 51.1582 +    ready = False
 51.1583 +    """An internal flag which marks whether the socket is accepting connections."""
 51.1584 +    
 51.1585 +    max_request_header_size = 0
 51.1586 +    """The maximum size, in bytes, for request headers, or 0 for no limit."""
 51.1587 +    
 51.1588 +    max_request_body_size = 0
 51.1589 +    """The maximum size, in bytes, for request bodies, or 0 for no limit."""
 51.1590 +    
 51.1591 +    nodelay = True
 51.1592 +    """If True (the default since 3.1), sets the TCP_NODELAY socket option."""
 51.1593 +    
 51.1594 +    ConnectionClass = HTTPConnection
 51.1595 +    """The class to use for handling HTTP connections."""
 51.1596 +    
 51.1597 +    ssl_adapter = None
 51.1598 +    """An instance of SSLAdapter (or a subclass).
 51.1599 +    
 51.1600 +    You must have the corresponding SSL driver library installed."""
 51.1601 +    
 51.1602 +    def __init__(self, bind_addr, gateway, minthreads=10, maxthreads=-1,
 51.1603 +                 server_name=None):
 51.1604 +        self.bind_addr = bind_addr
 51.1605 +        self.gateway = gateway
 51.1606 +        
 51.1607 +        self.requests = ThreadPool(self, min=minthreads or 1, max=maxthreads)
 51.1608 +        
 51.1609 +        if not server_name:
 51.1610 +            server_name = socket.gethostname()
 51.1611 +        self.server_name = server_name
 51.1612 +        self.clear_stats()
 51.1613 +    
 51.1614 +    def clear_stats(self):
 51.1615 +        self._start_time = None
 51.1616 +        self._run_time = 0
 51.1617 +        self.stats = {
 51.1618 +            'Enabled': False,
 51.1619 +            'Bind Address': lambda s: repr(self.bind_addr),
 51.1620 +            'Run time': lambda s: (not s['Enabled']) and 0 or self.runtime(),
 51.1621 +            'Accepts': 0,
 51.1622 +            'Accepts/sec': lambda s: s['Accepts'] / self.runtime(),
 51.1623 +            'Queue': lambda s: getattr(self.requests, "qsize", None),
 51.1624 +            'Threads': lambda s: len(getattr(self.requests, "_threads", [])),
 51.1625 +            'Threads Idle': lambda s: getattr(self.requests, "idle", None),
 51.1626 +            'Socket Errors': 0,
 51.1627 +            'Requests': lambda s: (not s['Enabled']) and 0 or sum([w['Requests'](w) for w
 51.1628 +                                       in s['Worker Threads'].values()], 0),
 51.1629 +            'Bytes Read': lambda s: (not s['Enabled']) and 0 or sum([w['Bytes Read'](w) for w
 51.1630 +                                         in s['Worker Threads'].values()], 0),
 51.1631 +            'Bytes Written': lambda s: (not s['Enabled']) and 0 or sum([w['Bytes Written'](w) for w
 51.1632 +                                            in s['Worker Threads'].values()], 0),
 51.1633 +            'Work Time': lambda s: (not s['Enabled']) and 0 or sum([w['Work Time'](w) for w
 51.1634 +                                         in s['Worker Threads'].values()], 0),
 51.1635 +            'Read Throughput': lambda s: (not s['Enabled']) and 0 or sum(
 51.1636 +                [w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6)
 51.1637 +                 for w in s['Worker Threads'].values()], 0),
 51.1638 +            'Write Throughput': lambda s: (not s['Enabled']) and 0 or sum(
 51.1639 +                [w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
 51.1640 +                 for w in s['Worker Threads'].values()], 0),
 51.1641 +            'Worker Threads': {},
 51.1642 +            }
 51.1643 +        logging.statistics["CherryPy HTTPServer %d" % id(self)] = self.stats
 51.1644 +    
 51.1645 +    def runtime(self):
 51.1646 +        if self._start_time is None:
 51.1647 +            return self._run_time
 51.1648 +        else:
 51.1649 +            return self._run_time + (time.time() - self._start_time)
 51.1650 +    
 51.1651 +    def __str__(self):
 51.1652 +        return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
 51.1653 +                              self.bind_addr)
 51.1654 +    
 51.1655 +    def _get_bind_addr(self):
 51.1656 +        return self._bind_addr
 51.1657 +    def _set_bind_addr(self, value):
 51.1658 +        if isinstance(value, tuple) and value[0] in ('', None):
 51.1659 +            # Despite the socket module docs, using '' does not
 51.1660 +            # allow AI_PASSIVE to work. Passing None instead
 51.1661 +            # returns '0.0.0.0' like we want. In other words:
 51.1662 +            #     host    AI_PASSIVE     result
 51.1663 +            #      ''         Y         192.168.x.y
 51.1664 +            #      ''         N         192.168.x.y
 51.1665 +            #     None        Y         0.0.0.0
 51.1666 +            #     None        N         127.0.0.1
 51.1667 +            # But since you can get the same effect with an explicit
 51.1668 +            # '0.0.0.0', we deny both the empty string and None as values.
 51.1669 +            raise ValueError("Host values of '' or None are not allowed. "
 51.1670 +                             "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
 51.1671 +                             "to listen on all active interfaces.")
 51.1672 +        self._bind_addr = value
 51.1673 +    bind_addr = property(_get_bind_addr, _set_bind_addr,
 51.1674 +        doc="""The interface on which to listen for connections.
 51.1675 +        
 51.1676 +        For TCP sockets, a (host, port) tuple. Host values may be any IPv4
 51.1677 +        or IPv6 address, or any valid hostname. The string 'localhost' is a
 51.1678 +        synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
 51.1679 +        The string '0.0.0.0' is a special IPv4 entry meaning "any active
 51.1680 +        interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
 51.1681 +        IPv6. The empty string or None are not allowed.
 51.1682 +        
 51.1683 +        For UNIX sockets, supply the filename as a string.""")
 51.1684 +    
 51.1685 +    def start(self):
 51.1686 +        """Run the server forever."""
 51.1687 +        # We don't have to trap KeyboardInterrupt or SystemExit here,
 51.1688 +        # because cherrpy.server already does so, calling self.stop() for us.
 51.1689 +        # If you're using this server with another framework, you should
 51.1690 +        # trap those exceptions in whatever code block calls start().
 51.1691 +        self._interrupt = None
 51.1692 +        
 51.1693 +        if self.software is None:
 51.1694 +            self.software = "%s Server" % self.version
 51.1695 +        
 51.1696 +        # SSL backward compatibility
 51.1697 +        if (self.ssl_adapter is None and
 51.1698 +            getattr(self, 'ssl_certificate', None) and
 51.1699 +            getattr(self, 'ssl_private_key', None)):
 51.1700 +            warnings.warn(
 51.1701 +                    "SSL attributes are deprecated in CherryPy 3.2, and will "
 51.1702 +                    "be removed in CherryPy 3.3. Use an ssl_adapter attribute "
 51.1703 +                    "instead.",
 51.1704 +                    DeprecationWarning
 51.1705 +                )
 51.1706 +            try:
 51.1707 +                from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
 51.1708 +            except ImportError:
 51.1709 +                pass
 51.1710 +            else:
 51.1711 +                self.ssl_adapter = pyOpenSSLAdapter(
 51.1712 +                    self.ssl_certificate, self.ssl_private_key,
 51.1713 +                    getattr(self, 'ssl_certificate_chain', None))
 51.1714 +        
 51.1715 +        # Select the appropriate socket
 51.1716 +        if isinstance(self.bind_addr, basestring):
 51.1717 +            # AF_UNIX socket
 51.1718 +            
 51.1719 +            # So we can reuse the socket...
 51.1720 +            try: os.unlink(self.bind_addr)
 51.1721 +            except: pass
 51.1722 +            
 51.1723 +            # So everyone can access the socket...
 51.1724 +            try: os.chmod(self.bind_addr, 0777)
 51.1725 +            except: pass
 51.1726 +            
 51.1727 +            info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
 51.1728 +        else:
 51.1729 +            # AF_INET or AF_INET6 socket
 51.1730 +            # Get the correct address family for our host (allows IPv6 addresses)
 51.1731 +            host, port = self.bind_addr
 51.1732 +            try:
 51.1733 +                info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
 51.1734 +                                          socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
 51.1735 +            except socket.gaierror:
 51.1736 +                if ':' in self.bind_addr[0]:
 51.1737 +                    info = [(socket.AF_INET6, socket.SOCK_STREAM,
 51.1738 +                             0, "", self.bind_addr + (0, 0))]
 51.1739 +                else:
 51.1740 +                    info = [(socket.AF_INET, socket.SOCK_STREAM,
 51.1741 +                             0, "", self.bind_addr)]
 51.1742 +        
 51.1743 +        self.socket = None
 51.1744 +        msg = "No socket could be created"
 51.1745 +        for res in info:
 51.1746 +            af, socktype, proto, canonname, sa = res
 51.1747 +            try:
 51.1748 +                self.bind(af, socktype, proto)
 51.1749 +            except socket.error:
 51.1750 +                if self.socket:
 51.1751 +                    self.socket.close()
 51.1752 +                self.socket = None
 51.1753 +                continue
 51.1754 +            break
 51.1755 +        if not self.socket:
 51.1756 +            raise socket.error(msg)
 51.1757 +        
 51.1758 +        # Timeout so KeyboardInterrupt can be caught on Win32
 51.1759 +        self.socket.settimeout(1)
 51.1760 +        self.socket.listen(self.request_queue_size)
 51.1761 +        
 51.1762 +        # Create worker threads
 51.1763 +        self.requests.start()
 51.1764 +        
 51.1765 +        self.ready = True
 51.1766 +        self._start_time = time.time()
 51.1767 +        while self.ready:
 51.1768 +            self.tick()
 51.1769 +            if self.interrupt:
 51.1770 +                while self.interrupt is True:
 51.1771 +                    # Wait for self.stop() to complete. See _set_interrupt.
 51.1772 +                    time.sleep(0.1)
 51.1773 +                if self.interrupt:
 51.1774 +                    raise self.interrupt
 51.1775 +    
 51.1776 +    def bind(self, family, type, proto=0):
 51.1777 +        """Create (or recreate) the actual socket object."""
 51.1778 +        self.socket = socket.socket(family, type, proto)
 51.1779 +        prevent_socket_inheritance(self.socket)
 51.1780 +        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 51.1781 +        if self.nodelay and not isinstance(self.bind_addr, str):
 51.1782 +            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
 51.1783 +        
 51.1784 +        if self.ssl_adapter is not None:
 51.1785 +            self.socket = self.ssl_adapter.bind(self.socket)
 51.1786 +        
 51.1787 +        # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
 51.1788 +        # activate dual-stack. See http://www.cherrypy.org/ticket/871.
 51.1789 +        if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
 51.1790 +            and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
 51.1791 +            try:
 51.1792 +                self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
 51.1793 +            except (AttributeError, socket.error):
 51.1794 +                # Apparently, the socket option is not available in
 51.1795 +                # this machine's TCP stack
 51.1796 +                pass
 51.1797 +        
 51.1798 +        self.socket.bind(self.bind_addr)
 51.1799 +    
 51.1800 +    def tick(self):
 51.1801 +        """Accept a new connection and put it on the Queue."""
 51.1802 +        try:
 51.1803 +            s, addr = self.socket.accept()
 51.1804 +            if self.stats['Enabled']:
 51.1805 +                self.stats['Accepts'] += 1
 51.1806 +            if not self.ready:
 51.1807 +                return
 51.1808 +            
 51.1809 +            prevent_socket_inheritance(s)
 51.1810 +            if hasattr(s, 'settimeout'):
 51.1811 +                s.settimeout(self.timeout)
 51.1812 +            
 51.1813 +            makefile = CP_fileobject
 51.1814 +            ssl_env = {}
 51.1815 +            # if ssl cert and key are set, we try to be a secure HTTP server
 51.1816 +            if self.ssl_adapter is not None:
 51.1817 +                try:
 51.1818 +                    s, ssl_env = self.ssl_adapter.wrap(s)
 51.1819 +                except NoSSLError:
 51.1820 +                    msg = ("The client sent a plain HTTP request, but "
 51.1821 +                           "this server only speaks HTTPS on this port.")
 51.1822 +                    buf = ["%s 400 Bad Request\r\n" % self.protocol,
 51.1823 +                           "Content-Length: %s\r\n" % len(msg),
 51.1824 +                           "Content-Type: text/plain\r\n\r\n",
 51.1825 +                           msg]
 51.1826 +                    
 51.1827 +                    wfile = CP_fileobject(s, "wb", DEFAULT_BUFFER_SIZE)
 51.1828 +                    try:
 51.1829 +                        wfile.sendall("".join(buf))
 51.1830 +                    except socket.error, x:
 51.1831 +                        if x.args[0] not in socket_errors_to_ignore:
 51.1832 +                            raise
 51.1833 +                    return
 51.1834 +                if not s:
 51.1835 +                    return
 51.1836 +                makefile = self.ssl_adapter.makefile
 51.1837 +                # Re-apply our timeout since we may have a new socket object
 51.1838 +                if hasattr(s, 'settimeout'):
 51.1839 +                    s.settimeout(self.timeout)
 51.1840 +            
 51.1841 +            conn = self.ConnectionClass(self, s, makefile)
 51.1842 +            
 51.1843 +            if not isinstance(self.bind_addr, basestring):
 51.1844 +                # optional values
 51.1845 +                # Until we do DNS lookups, omit REMOTE_HOST
 51.1846 +                if addr is None: # sometimes this can happen
 51.1847 +                    # figure out if AF_INET or AF_INET6.
 51.1848 +                    if len(s.getsockname()) == 2:
 51.1849 +                        # AF_INET
 51.1850 +                        addr = ('0.0.0.0', 0)
 51.1851 +                    else:
 51.1852 +                        # AF_INET6
 51.1853 +                        addr = ('::', 0)
 51.1854 +                conn.remote_addr = addr[0]
 51.1855 +                conn.remote_port = addr[1]
 51.1856 +            
 51.1857 +            conn.ssl_env = ssl_env
 51.1858 +            
 51.1859 +            self.requests.put(conn)
 51.1860 +        except socket.timeout:
 51.1861 +            # The only reason for the timeout in start() is so we can
 51.1862 +            # notice keyboard interrupts on Win32, which don't interrupt
 51.1863 +            # accept() by default
 51.1864 +            return
 51.1865 +        except socket.error, x:
 51.1866 +            if self.stats['Enabled']:
 51.1867 +                self.stats['Socket Errors'] += 1
 51.1868 +            if x.args[0] in socket_error_eintr:
 51.1869 +                # I *think* this is right. EINTR should occur when a signal
 51.1870 +                # is received during the accept() call; all docs say retry
 51.1871 +                # the call, and I *think* I'm reading it right that Python
 51.1872 +                # will then go ahead and poll for and handle the signal
 51.1873 +                # elsewhere. See http://www.cherrypy.org/ticket/707.
 51.1874 +                return
 51.1875 +            if x.args[0] in socket_errors_nonblocking:
 51.1876 +                # Just try again. See http://www.cherrypy.org/ticket/479.
 51.1877 +                return
 51.1878 +            if x.args[0] in socket_errors_to_ignore:
 51.1879 +                # Our socket was closed.
 51.1880 +                # See http://www.cherrypy.org/ticket/686.
 51.1881 +                return
 51.1882 +            raise
 51.1883 +    
 51.1884 +    def _get_interrupt(self):
 51.1885 +        return self._interrupt
 51.1886 +    def _set_interrupt(self, interrupt):
 51.1887 +        self._interrupt = True
 51.1888 +        self.stop()
 51.1889 +        self._interrupt = interrupt
 51.1890 +    interrupt = property(_get_interrupt, _set_interrupt,
 51.1891 +                         doc="Set this to an Exception instance to "
 51.1892 +                             "interrupt the server.")
 51.1893 +    
 51.1894 +    def stop(self):
 51.1895 +        """Gracefully shutdown a server that is serving forever."""
 51.1896 +        self.ready = False
 51.1897 +        if self._start_time is not None:
 51.1898 +            self._run_time += (time.time() - self._start_time)
 51.1899 +        self._start_time = None
 51.1900 +        
 51.1901 +        sock = getattr(self, "socket", None)
 51.1902 +        if sock:
 51.1903 +            if not isinstance(self.bind_addr, basestring):
 51.1904 +                # Touch our own socket to make accept() return immediately.
 51.1905 +                try:
 51.1906 +                    host, port = sock.getsockname()[:2]
 51.1907 +                except socket.error, x:
 51.1908 +                    if x.args[0] not in socket_errors_to_ignore:
 51.1909 +                        # Changed to use error code and not message
 51.1910 +                        # See http://www.cherrypy.org/ticket/860.
 51.1911 +                        raise
 51.1912 +                else:
 51.1913 +                    # Note that we're explicitly NOT using AI_PASSIVE,
 51.1914 +                    # here, because we want an actual IP to touch.
 51.1915 +                    # localhost won't work if we've bound to a public IP,
 51.1916 +                    # but it will if we bound to '0.0.0.0' (INADDR_ANY).
 51.1917 +                    for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
 51.1918 +                                                  socket.SOCK_STREAM):
 51.1919 +                        af, socktype, proto, canonname, sa = res
 51.1920 +                        s = None
 51.1921 +                        try:
 51.1922 +                            s = socket.socket(af, socktype, proto)
 51.1923 +                            # See http://groups.google.com/group/cherrypy-users/
 51.1924 +                            #        browse_frm/thread/bbfe5eb39c904fe0
 51.1925 +                            s.settimeout(1.0)
 51.1926 +                            s.connect((host, port))
 51.1927 +                            s.close()
 51.1928 +                        except socket.error:
 51.1929 +                            if s:
 51.1930 +                                s.close()
 51.1931 +            if hasattr(sock, "close"):
 51.1932 +                sock.close()
 51.1933 +            self.socket = None
 51.1934 +        
 51.1935 +        self.requests.stop(self.shutdown_timeout)
 51.1936 +
 51.1937 +
 51.1938 +class Gateway(object):
 51.1939 +    
 51.1940 +    def __init__(self, req):
 51.1941 +        self.req = req
 51.1942 +    
 51.1943 +    def respond(self):
 51.1944 +        raise NotImplemented
 51.1945 +
 51.1946 +
 51.1947 +# These may either be wsgiserver.SSLAdapter subclasses or the string names
 51.1948 +# of such classes (in which case they will be lazily loaded).
 51.1949 +ssl_adapters = {
 51.1950 +    'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
 51.1951 +    'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
 51.1952 +    }
 51.1953 +
 51.1954 +def get_ssl_adapter_class(name='pyopenssl'):
 51.1955 +    adapter = ssl_adapters[name.lower()]
 51.1956 +    if isinstance(adapter, basestring):
 51.1957 +        last_dot = adapter.rfind(".")
 51.1958 +        attr_name = adapter[last_dot + 1:]
 51.1959 +        mod_path = adapter[:last_dot]
 51.1960 +        
 51.1961 +        try:
 51.1962 +            mod = sys.modules[mod_path]
 51.1963 +            if mod is None:
 51.1964 +                raise KeyError()
 51.1965 +        except KeyError:
 51.1966 +            # The last [''] is important.
 51.1967 +            mod = __import__(mod_path, globals(), locals(), [''])
 51.1968 +        
 51.1969 +        # Let an AttributeError propagate outward.
 51.1970 +        try:
 51.1971 +            adapter = getattr(mod, attr_name)
 51.1972 +        except AttributeError:
 51.1973 +            raise AttributeError("'%s' object has no attribute '%s'"
 51.1974 +                                 % (mod_path, attr_name))
 51.1975 +    
 51.1976 +    return adapter
 51.1977 +
 51.1978 +# -------------------------------- WSGI Stuff -------------------------------- #
 51.1979 +
 51.1980 +
 51.1981 +class CherryPyWSGIServer(HTTPServer):
 51.1982 +    
 51.1983 +    wsgi_version = (1, 0)
 51.1984 +    
 51.1985 +    def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
 51.1986 +                 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
 51.1987 +        self.requests = ThreadPool(self, min=numthreads or 1, max=max)
 51.1988 +        self.wsgi_app = wsgi_app
 51.1989 +        self.gateway = wsgi_gateways[self.wsgi_version]
 51.1990 +        
 51.1991 +        self.bind_addr = bind_addr
 51.1992 +        if not server_name:
 51.1993 +            server_name = socket.gethostname()
 51.1994 +        self.server_name = server_name
 51.1995 +        self.request_queue_size = request_queue_size
 51.1996 +        
 51.1997 +        self.timeout = timeout
 51.1998 +        self.shutdown_timeout = shutdown_timeout
 51.1999 +        self.clear_stats()
 51.2000 +    
 51.2001 +    def _get_numthreads(self):
 51.2002 +        return self.requests.min
 51.2003 +    def _set_numthreads(self, value):
 51.2004 +        self.requests.min = value
 51.2005 +    numthreads = property(_get_numthreads, _set_numthreads)
 51.2006 +
 51.2007 +
 51.2008 +class WSGIGateway(Gateway):
 51.2009 +    
 51.2010 +    def __init__(self, req):
 51.2011 +        self.req = req
 51.2012 +        self.started_response = False
 51.2013 +        self.env = self.get_environ()
 51.2014 +        self.remaining_bytes_out = None
 51.2015 +    
 51.2016 +    def get_environ(self):
 51.2017 +        """Return a new environ dict targeting the given wsgi.version"""
 51.2018 +        raise NotImplemented
 51.2019 +    
 51.2020 +    def respond(self):
 51.2021 +        response = self.req.server.wsgi_app(self.env, self.start_response)
 51.2022 +        try:
 51.2023 +            for chunk in response:
 51.2024 +                # "The start_response callable must not actually transmit
 51.2025 +                # the response headers. Instead, it must store them for the
 51.2026 +                # server or gateway to transmit only after the first
 51.2027 +                # iteration of the application return value that yields
 51.2028 +                # a NON-EMPTY string, or upon the application's first
 51.2029 +                # invocation of the write() callable." (PEP 333)
 51.2030 +                if chunk:
 51.2031 +                    if isinstance(chunk, unicode):
 51.2032 +                        chunk = chunk.encode('ISO-8859-1')
 51.2033 +                    self.write(chunk)
 51.2034 +        finally:
 51.2035 +            if hasattr(response, "close"):
 51.2036 +                response.close()
 51.2037 +    
 51.2038 +    def start_response(self, status, headers, exc_info = None):
 51.2039 +        """WSGI callable to begin the HTTP response."""
 51.2040 +        # "The application may call start_response more than once,
 51.2041 +        # if and only if the exc_info argument is provided."
 51.2042 +        if self.started_response and not exc_info:
 51.2043 +            raise AssertionError("WSGI start_response called a second "
 51.2044 +                                 "time with no exc_info.")
 51.2045 +        self.started_response = True
 51.2046 +        
 51.2047 +        # "if exc_info is provided, and the HTTP headers have already been
 51.2048 +        # sent, start_response must raise an error, and should raise the
 51.2049 +        # exc_info tuple."
 51.2050 +        if self.req.sent_headers:
 51.2051 +            try:
 51.2052 +                raise exc_info[0], exc_info[1], exc_info[2]
 51.2053 +            finally:
 51.2054 +                exc_info = None
 51.2055 +        
 51.2056 +        self.req.status = status
 51.2057 +        for k, v in headers:
 51.2058 +            if not isinstance(k, str):
 51.2059 +                raise TypeError("WSGI response header key %r is not a byte string." % k)
 51.2060 +            if not isinstance(v, str):
 51.2061 +                raise TypeError("WSGI response header value %r is not a byte string." % v)
 51.2062 +            if k.lower() == 'content-length':
 51.2063 +                self.remaining_bytes_out = int(v)
 51.2064 +        self.req.outheaders.extend(headers)
 51.2065 +        
 51.2066 +        return self.write
 51.2067 +    
 51.2068 +    def write(self, chunk):
 51.2069 +        """WSGI callable to write unbuffered data to the client.
 51.2070 +        
 51.2071 +        This method is also used internally by start_response (to write
 51.2072 +        data from the iterable returned by the WSGI application).
 51.2073 +        """
 51.2074 +        if not self.started_response:
 51.2075 +            raise AssertionError("WSGI write called before start_response.")
 51.2076 +        
 51.2077 +        chunklen = len(chunk)
 51.2078 +        rbo = self.remaining_bytes_out
 51.2079 +        if rbo is not None and chunklen > rbo:
 51.2080 +            if not self.req.sent_headers:
 51.2081 +                # Whew. We can send a 500 to the client.
 51.2082 +                self.req.simple_response("500 Internal Server Error",
 51.2083 +                    "The requested resource returned more bytes than the "
 51.2084 +                    "declared Content-Length.")
 51.2085 +            else:
 51.2086 +                # Dang. We have probably already sent data. Truncate the chunk
 51.2087 +                # to fit (so the client doesn't hang) and raise an error later.
 51.2088 +                chunk = chunk[:rbo]
 51.2089 +        
 51.2090 +        if not self.req.sent_headers:
 51.2091 +            self.req.sent_headers = True
 51.2092 +            self.req.send_headers()
 51.2093 +        
 51.2094 +        self.req.write(chunk)
 51.2095 +        
 51.2096 +        if rbo is not None:
 51.2097 +            rbo -= chunklen
 51.2098 +            if rbo < 0:
 51.2099 +                raise ValueError(
 51.2100 +                    "Response body exceeds the declared Content-Length.")
 51.2101 +
 51.2102 +
 51.2103 +class WSGIGateway_10(WSGIGateway):
 51.2104 +    
 51.2105 +    def get_environ(self):
 51.2106 +        """Return a new environ dict targeting the given wsgi.version"""
 51.2107 +        req = self.req
 51.2108 +        env = {
 51.2109 +            # set a non-standard environ entry so the WSGI app can know what
 51.2110 +            # the *real* server protocol is (and what features to support).
 51.2111 +            # See http://www.faqs.org/rfcs/rfc2145.html.
 51.2112 +            'ACTUAL_SERVER_PROTOCOL': req.server.protocol,
 51.2113 +            'PATH_INFO': req.path,
 51.2114 +            'QUERY_STRING': req.qs,
 51.2115 +            'REMOTE_ADDR': req.conn.remote_addr or '',
 51.2116 +            'REMOTE_PORT': str(req.conn.remote_port or ''),
 51.2117 +            'REQUEST_METHOD': req.method,
 51.2118 +            'REQUEST_URI': req.uri,
 51.2119 +            'SCRIPT_NAME': '',
 51.2120 +            'SERVER_NAME': req.server.server_name,
 51.2121 +            # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
 51.2122 +            'SERVER_PROTOCOL': req.request_protocol,
 51.2123 +            'SERVER_SOFTWARE': req.server.software,
 51.2124 +            'wsgi.errors': sys.stderr,
 51.2125 +            'wsgi.input': req.rfile,
 51.2126 +            'wsgi.multiprocess': False,
 51.2127 +            'wsgi.multithread': True,
 51.2128 +            'wsgi.run_once': False,
 51.2129 +            'wsgi.url_scheme': req.scheme,
 51.2130 +            'wsgi.version': (1, 0),
 51.2131 +            }
 51.2132 +        
 51.2133 +        if isinstance(req.server.bind_addr, basestring):
 51.2134 +            # AF_UNIX. This isn't really allowed by WSGI, which doesn't
 51.2135 +            # address unix domain sockets. But it's better than nothing.
 51.2136 +            env["SERVER_PORT"] = ""
 51.2137 +        else:
 51.2138 +            env["SERVER_PORT"] = str(req.server.bind_addr[1])
 51.2139 +        
 51.2140 +        # Request headers
 51.2141 +        for k, v in req.inheaders.iteritems():
 51.2142 +            env["HTTP_" + k.upper().replace("-", "_")] = v
 51.2143 +        
 51.2144 +        # CONTENT_TYPE/CONTENT_LENGTH
 51.2145 +        ct = env.pop("HTTP_CONTENT_TYPE", None)
 51.2146 +        if ct is not None:
 51.2147 +            env["CONTENT_TYPE"] = ct
 51.2148 +        cl = env.pop("HTTP_CONTENT_LENGTH", None)
 51.2149 +        if cl is not None:
 51.2150 +            env["CONTENT_LENGTH"] = cl
 51.2151 +        
 51.2152 +        if req.conn.ssl_env:
 51.2153 +            env.update(req.conn.ssl_env)
 51.2154 +        
 51.2155 +        return env
 51.2156 +
 51.2157 +
 51.2158 +class WSGIGateway_u0(WSGIGateway_10):
 51.2159 +    
 51.2160 +    def get_environ(self):
 51.2161 +        """Return a new environ dict targeting the given wsgi.version"""
 51.2162 +        req = self.req
 51.2163 +        env_10 = WSGIGateway_10.get_environ(self)
 51.2164 +        env = dict([(k.decode('ISO-8859-1'), v) for k, v in env_10.iteritems()])
 51.2165 +        env[u'wsgi.version'] = ('u', 0)
 51.2166 +        
 51.2167 +        # Request-URI
 51.2168 +        env.setdefault(u'wsgi.url_encoding', u'utf-8')
 51.2169 +        try:
 51.2170 +            for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
 51.2171 +                env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
 51.2172 +        except UnicodeDecodeError:
 51.2173 +            # Fall back to latin 1 so apps can transcode if needed.
 51.2174 +            env[u'wsgi.url_encoding'] = u'ISO-8859-1'
 51.2175 +            for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
 51.2176 +                env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
 51.2177 +        
 51.2178 +        for k, v in sorted(env.items()):
 51.2179 +            if isinstance(v, str) and k not in ('REQUEST_URI', 'wsgi.input'):
 51.2180 +                env[k] = v.decode('ISO-8859-1')
 51.2181 +        
 51.2182 +        return env
 51.2183 +
 51.2184 +wsgi_gateways = {
 51.2185 +    (1, 0): WSGIGateway_10,
 51.2186 +    ('u', 0): WSGIGateway_u0,
 51.2187 +}
 51.2188 +
 51.2189 +class WSGIPathInfoDispatcher(object):
 51.2190 +    """A WSGI dispatcher for dispatch based on the PATH_INFO.
 51.2191 +    
 51.2192 +    apps: a dict or list of (path_prefix, app) pairs.
 51.2193 +    """
 51.2194 +    
 51.2195 +    def __init__(self, apps):
 51.2196 +        try:
 51.2197 +            apps = apps.items()
 51.2198 +        except AttributeError:
 51.2199 +            pass
 51.2200 +        
 51.2201 +        # Sort the apps by len(path), descending
 51.2202 +        apps.sort(cmp=lambda x,y: cmp(len(x[0]), len(y[0])))
 51.2203 +        apps.reverse()
 51.2204 +        
 51.2205 +        # The path_prefix strings must start, but not end, with a slash.
 51.2206 +        # Use "" instead of "/".
 51.2207 +        self.apps = [(p.rstrip("/"), a) for p, a in apps]
 51.2208 +    
 51.2209 +    def __call__(self, environ, start_response):
 51.2210 +        path = environ["PATH_INFO"] or "/"
 51.2211 +        for p, app in self.apps:
 51.2212 +            # The apps list should be sorted by length, descending.
 51.2213 +            if path.startswith(p + "/") or path == p:
 51.2214 +                environ = environ.copy()
 51.2215 +                environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
 51.2216 +                environ["PATH_INFO"] = path[len(p):]
 51.2217 +                return app(environ, start_response)
 51.2218 +        
 51.2219 +        start_response('404 Not Found', [('Content-Type', 'text/plain'),
 51.2220 +                                         ('Content-Length', '0')])
 51.2221 +        return ['']
 51.2222 +
    52.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    52.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/wsgiserver/ssl_builtin.py	Mon Dec 02 14:02:05 2013 +0100
    52.3 @@ -0,0 +1,72 @@
    52.4 +"""A library for integrating Python's builtin ``ssl`` library with CherryPy.
    52.5 +
    52.6 +The ssl module must be importable for SSL functionality.
    52.7 +
    52.8 +To use this module, set ``CherryPyWSGIServer.ssl_adapter`` to an instance of
    52.9 +``BuiltinSSLAdapter``.
   52.10 +"""
   52.11 +
   52.12 +try:
   52.13 +    import ssl
   52.14 +except ImportError:
   52.15 +    ssl = None
   52.16 +
   52.17 +from cherrypy import wsgiserver
   52.18 +
   52.19 +
   52.20 +class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
   52.21 +    """A wrapper for integrating Python's builtin ssl module with CherryPy."""
   52.22 +    
   52.23 +    certificate = None
   52.24 +    """The filename of the server SSL certificate."""
   52.25 +    
   52.26 +    private_key = None
   52.27 +    """The filename of the server's private key file."""
   52.28 +    
   52.29 +    def __init__(self, certificate, private_key, certificate_chain=None):
   52.30 +        if ssl is None:
   52.31 +            raise ImportError("You must install the ssl module to use HTTPS.")
   52.32 +        self.certificate = certificate
   52.33 +        self.private_key = private_key
   52.34 +        self.certificate_chain = certificate_chain
   52.35 +    
   52.36 +    def bind(self, sock):
   52.37 +        """Wrap and return the given socket."""
   52.38 +        return sock
   52.39 +    
   52.40 +    def wrap(self, sock):
   52.41 +        """Wrap and return the given socket, plus WSGI environ entries."""
   52.42 +        try:
   52.43 +            s = ssl.wrap_socket(sock, do_handshake_on_connect=True,
   52.44 +                    server_side=True, certfile=self.certificate,
   52.45 +                    keyfile=self.private_key, ssl_version=ssl.PROTOCOL_SSLv23)
   52.46 +        except ssl.SSLError, e:
   52.47 +            if e.errno == ssl.SSL_ERROR_EOF:
   52.48 +                # This is almost certainly due to the cherrypy engine
   52.49 +                # 'pinging' the socket to assert it's connectable;
   52.50 +                # the 'ping' isn't SSL.
   52.51 +                return None, {}
   52.52 +            elif e.errno == ssl.SSL_ERROR_SSL:
   52.53 +                if e.args[1].endswith('http request'):
   52.54 +                    # The client is speaking HTTP to an HTTPS server.
   52.55 +                    raise wsgiserver.NoSSLError
   52.56 +            raise
   52.57 +        return s, self.get_environ(s)
   52.58 +    
   52.59 +    # TODO: fill this out more with mod ssl env
   52.60 +    def get_environ(self, sock):
   52.61 +        """Create WSGI environ entries to be merged into each request."""
   52.62 +        cipher = sock.cipher()
   52.63 +        ssl_environ = {
   52.64 +            "wsgi.url_scheme": "https",
   52.65 +            "HTTPS": "on",
   52.66 +            'SSL_PROTOCOL': cipher[1],
   52.67 +            'SSL_CIPHER': cipher[0]
   52.68 +##            SSL_VERSION_INTERFACE 	string 	The mod_ssl program version
   52.69 +##            SSL_VERSION_LIBRARY 	string 	The OpenSSL program version
   52.70 +            }
   52.71 +        return ssl_environ
   52.72 +    
   52.73 +    def makefile(self, sock, mode='r', bufsize=-1):
   52.74 +        return wsgiserver.CP_fileobject(sock, mode, bufsize)
   52.75 +
    53.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    53.2 +++ b/OpenSecurity/install/web.py-0.37/build/lib/web/wsgiserver/ssl_pyopenssl.py	Mon Dec 02 14:02:05 2013 +0100
    53.3 @@ -0,0 +1,256 @@
    53.4 +"""A library for integrating pyOpenSSL with CherryPy.
    53.5 +
    53.6 +The OpenSSL module must be importable for SSL functionality.
    53.7 +You can obtain it from http://pyopenssl.sourceforge.net/
    53.8 +
    53.9 +To use this module, set CherryPyWSGIServer.ssl_adapter to an instance of
   53.10 +SSLAdapter. There are two ways to use SSL:
   53.11 +
   53.12 +Method One
   53.13 +----------
   53.14 +
   53.15 + * ``ssl_adapter.context``: an instance of SSL.Context.
   53.16 +
   53.17 +If this is not None, it is assumed to be an SSL.Context instance,
   53.18 +and will be passed to SSL.Connection on bind(). The developer is
   53.19 +responsible for forming a valid Context object. This approach is
   53.20 +to be preferred for more flexibility, e.g. if the cert and key are
   53.21 +streams instead of files, or need decryption, or SSL.SSLv3_METHOD
   53.22 +is desired instead of the default SSL.SSLv23_METHOD, etc. Consult
   53.23 +the pyOpenSSL documentation for complete options.
   53.24 +
   53.25 +Method Two (shortcut)
   53.26 +---------------------
   53.27 +
   53.28 + * ``ssl_adapter.certificate``: the filename of the server SSL certificate.
   53.29 + * ``ssl_adapter.private_key``: the filename of the server's private key file.
   53.30 +
   53.31 +Both are None by default. If ssl_adapter.context is None, but .private_key
   53.32 +and .certificate are both given and valid, they will be read, and the
   53.33 +context will be automatically created from them.
   53.34 +"""
   53.35 +
   53.36 +import socket
   53.37 +import threading
   53.38 +import time
   53.39 +
   53.40 +from cherrypy import wsgiserver
   53.41 +
   53.42 +try:
   53.43 +    from OpenSSL import SSL
   53.44 +    from OpenSSL import crypto
   53.45 +except ImportError:
   53.46 +    SSL = None
   53.47 +
   53.48 +
   53.49 +class SSL_fileobject(wsgiserver.CP_fileobject):
   53.50 +    """SSL file object attached to a socket object."""
   53.51 +    
   53.52 +    ssl_timeout = 3
   53.53 +    ssl_retry = .01
   53.54 +    
   53.55 +    def _safe_call(self, is_reader, call, *args, **kwargs):
   53.56 +        """Wrap the given call with SSL error-trapping.
   53.57 +        
   53.58 +        is_reader: if False EOF errors will be raised. If True, EOF errors
   53.59 +        will return "" (to emulate normal sockets).
   53.60 +        """
   53.61 +        start = time.time()
   53.62 +        while True:
   53.63 +            try:
   53.64 +                return call(*args, **kwargs)
   53.65 +            except SSL.WantReadError:
   53.66 +                # Sleep and try again. This is dangerous, because it means
   53.67 +                # the rest of the stack has no way of differentiating
   53.68 +                # between a "new handshake" error and "client dropped".
   53.69 +                # Note this isn't an endless loop: there's a timeout below.
   53.70 +                time.sleep(self.ssl_retry)
   53.71 +            except SSL.WantWriteError:
   53.72 +                time.sleep(self.ssl_retry)
   53.73 +            except SSL.SysCallError, e:
   53.74 +                if is_reader and e.args == (-1, 'Unexpected EOF'):
   53.75 +                    return ""
   53.76 +                
   53.77 +                errnum = e.args[0]
   53.78 +                if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
   53.79 +                    return ""
   53.80 +                raise socket.error(errnum)
   53.81 +            except SSL.Error, e:
   53.82 +                if is_reader and e.args == (-1, 'Unexpected EOF'):
   53.83 +                    return ""
   53.84 +                
   53.85 +                thirdarg = None
   53.86 +                try:
   53.87 +                    thirdarg = e.args[0][0][2]
   53.88 +                except IndexError:
   53.89 +                    pass
   53.90 +                
   53.91 +                if thirdarg == 'http request':
   53.92 +                    # The client is talking HTTP to an HTTPS server.
   53.93 +                    raise wsgiserver.NoSSLError()
   53.94 +                
   53.95 +                raise wsgiserver.FatalSSLAlert(*e.args)
   53.96 +            except:
   53.97 +                raise
   53.98 +            
   53.99 +            if time.time() - start > self.ssl_timeout:
  53.100 +                raise socket.timeout("timed out")
  53.101 +    
  53.102 +    def recv(self, *args, **kwargs):
  53.103 +        buf = []
  53.104 +        r = super(SSL_fileobject, self).recv
  53.105 +        while True:
  53.106 +            data = self._safe_call(True, r, *args, **kwargs)
  53.107 +            buf.append(data)
  53.108 +            p = self._sock.pending()
  53.109 +            if not p:
  53.110 +                return "".join(buf)
  53.111 +    
  53.112 +    def sendall(self, *args, **kwargs):
  53.113 +        return self._safe_call(False, super(SSL_fileobject, self).sendall,
  53.114 +                               *args, **kwargs)
  53.115 +
  53.116 +    def send(self, *args, **kwargs):
  53.117 +        return self._safe_call(False, super(SSL_fileobject, self).send,
  53.118 +                               *args, **kwargs)
  53.119 +
  53.120 +
  53.121 +class SSLConnection:
  53.122 +    """A thread-safe wrapper for an SSL.Connection.
  53.123 +    
  53.124 +    ``*args``: the arguments to create the wrapped ``SSL.Connection(*args)``.
  53.125 +    """
  53.126 +    
  53.127 +    def __init__(self, *args):
  53.128 +        self._ssl_conn = SSL.Connection(*args)
  53.129 +        self._lock = threading.RLock()
  53.130 +    
  53.131 +    for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
  53.132 +              'renegotiate', 'bind', 'listen', 'connect', 'accept',
  53.133 +              'setblocking', 'fileno', 'close', 'get_cipher_list',
  53.134 +              'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
  53.135 +              'makefile', 'get_app_data', 'set_app_data', 'state_string',
  53.136 +              'sock_shutdown', 'get_peer_certificate', 'want_read',
  53.137 +              'want_write', 'set_connect_state', 'set_accept_state',
  53.138 +              'connect_ex', 'sendall', 'settimeout', 'gettimeout'):
  53.139 +        exec("""def %s(self, *args):
  53.140 +        self._lock.acquire()
  53.141 +        try:
  53.142 +            return self._ssl_conn.%s(*args)
  53.143 +        finally:
  53.144 +            self._lock.release()
  53.145 +""" % (f, f))
  53.146 +    
  53.147 +    def shutdown(self, *args):
  53.148 +        self._lock.acquire()
  53.149 +        try:
  53.150 +            # pyOpenSSL.socket.shutdown takes no args
  53.151 +            return self._ssl_conn.shutdown()
  53.152 +        finally:
  53.153 +            self._lock.release()
  53.154 +
  53.155 +
  53.156 +class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
  53.157 +    """A wrapper for integrating pyOpenSSL with CherryPy."""
  53.158 +    
  53.159 +    context = None
  53.160 +    """An instance of SSL.Context."""
  53.161 +    
  53.162 +    certificate = None
  53.163 +    """The filename of the server SSL certificate."""
  53.164 +    
  53.165 +    private_key = None
  53.166 +    """The filename of the server's private key file."""
  53.167 +    
  53.168 +    certificate_chain = None
  53.169 +    """Optional. The filename of CA's intermediate certificate bundle.
  53.170 +    
  53.171 +    This is needed for cheaper "chained root" SSL certificates, and should be
  53.172 +    left as None if not required."""
  53.173 +    
  53.174 +    def __init__(self, certificate, private_key, certificate_chain=None):
  53.175 +        if SSL is None:
  53.176 +            raise ImportError("You must install pyOpenSSL to use HTTPS.")
  53.177 +        
  53.178 +        self.context = None
  53.179 +        self.certificate = certificate
  53.180 +        self.private_key = private_key
  53.181 +        self.certificate_chain = certificate_chain
  53.182 +        self._environ = None
  53.183 +    
  53.184 +    def bind(self, sock):
  53.185 +        """Wrap and return the given socket."""
  53.186 +        if self.context is None:
  53.187 +            self.context = self.get_context()
  53.188 +        conn = SSLConnection(self.context, sock)
  53.189 +        self._environ = self.get_environ()
  53.190 +        return conn
  53.191 +    
  53.192 +    def wrap(self, sock):
  53.193 +        """Wrap and return the given socket, plus WSGI environ entries."""
  53.194 +        return sock, self._environ.copy()
  53.195 +    
  53.196 +    def get_context(self):
  53.197 +        """Return an SSL.Context from self attributes."""
  53.198 +        # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
  53.199 +        c = SSL.Context(SSL.SSLv23_METHOD)
  53.200 +        c.use_privatekey_file(self.private_key)
  53.201 +        if self.certificate_chain:
  53.202 +            c.load_verify_locations(self.certificate_chain)
  53.203 +        c.use_certificate_file(self.certificate)
  53.204 +        return c
  53.205 +    
  53.206 +    def get_environ(self):
  53.207 +        """Return WSGI environ entries to be merged into each request."""
  53.208 +        ssl_environ = {
  53.209 +            "HTTPS": "on",
  53.210 +            # pyOpenSSL doesn't provide access to any of these AFAICT
  53.211 +##            'SSL_PROTOCOL': 'SSLv2',
  53.212 +##            SSL_CIPHER 	string 	The cipher specification name
  53.213 +##            SSL_VERSION_INTERFACE 	string 	The mod_ssl program version
  53.214 +##            SSL_VERSION_LIBRARY 	string 	The OpenSSL program version
  53.215 +            }
  53.216 +        
  53.217 +        if self.certificate:
  53.218 +            # Server certificate attributes
  53.219 +            cert = open(self.certificate, 'rb').read()
  53.220 +            cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
  53.221 +            ssl_environ.update({
  53.222 +                'SSL_SERVER_M_VERSION': cert.get_version(),
  53.223 +                'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
  53.224 +##                'SSL_SERVER_V_START': Validity of server's certificate (start time),
  53.225 +##                'SSL_SERVER_V_END': Validity of server's certificate (end time),
  53.226 +                })
  53.227 +            
  53.228 +            for prefix, dn in [("I", cert.get_issuer()),
  53.229 +                               ("S", cert.get_subject())]:
  53.230 +                # X509Name objects don't seem to have a way to get the
  53.231 +                # complete DN string. Use str() and slice it instead,
  53.232 +                # because str(dn) == "<X509Name object '/C=US/ST=...'>"
  53.233 +                dnstr = str(dn)[18:-2]
  53.234 +                
  53.235 +                wsgikey = 'SSL_SERVER_%s_DN' % prefix
  53.236 +                ssl_environ[wsgikey] = dnstr
  53.237 +                
  53.238 +                # The DN should be of the form: /k1=v1/k2=v2, but we must allow
  53.239 +                # for any value to contain slashes itself (in a URL).
  53.240 +                while dnstr:
  53.241 +                    pos = dnstr.rfind("=")
  53.242 +                    dnstr, value = dnstr[:pos], dnstr[pos + 1:]
  53.243 +                    pos = dnstr.rfind("/")
  53.244 +                    dnstr, key = dnstr[:pos], dnstr[pos + 1:]
  53.245 +                    if key and value:
  53.246 +                        wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
  53.247 +                        ssl_environ[wsgikey] = value
  53.248 +        
  53.249 +        return ssl_environ
  53.250 +    
  53.251 +    def makefile(self, sock, mode='r', bufsize=-1):
  53.252 +        if SSL and isinstance(sock, SSL.ConnectionType):
  53.253 +            timeout = sock.gettimeout()
  53.254 +            f = SSL_fileobject(sock, mode, bufsize)
  53.255 +            f.ssl_timeout = timeout
  53.256 +            return f
  53.257 +        else:
  53.258 +            return wsgiserver.CP_fileobject(sock, mode, bufsize)
  53.259 +
    54.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    54.2 +++ b/OpenSecurity/install/web.py-0.37/setup.py	Mon Dec 02 14:02:05 2013 +0100
    54.3 @@ -0,0 +1,20 @@
    54.4 +#!/usr/bin/env python
    54.5 +
    54.6 +# ...
    54.7 +
    54.8 +from distutils.core import setup
    54.9 +from web import __version__
   54.10 +
   54.11 +setup(name='web.py',
   54.12 +      version=__version__,
   54.13 +      description='web.py: makes web apps',
   54.14 +      author='Aaron Swartz',
   54.15 +      author_email='me@aaronsw.com',
   54.16 +      maintainer='Anand Chitipothu',
   54.17 +      maintainer_email='anandology@gmail.com',
   54.18 +      url=' http://webpy.org/',
   54.19 +      packages=['web', 'web.wsgiserver', 'web.contrib'],
   54.20 +      long_description="Think about the ideal way to write a web app. Write the code to make it happen.",
   54.21 +      license="Public domain",
   54.22 +      platforms=["any"],
   54.23 +     )
    55.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    55.2 +++ b/OpenSecurity/install/web.py-0.37/web/__init__.py	Mon Dec 02 14:02:05 2013 +0100
    55.3 @@ -0,0 +1,33 @@
    55.4 +#!/usr/bin/env python
    55.5 +"""web.py: makes web apps (http://webpy.org)"""
    55.6 +
    55.7 +from __future__ import generators
    55.8 +
    55.9 +__version__ = "0.37"
   55.10 +__author__ = [
   55.11 +    "Aaron Swartz <me@aaronsw.com>",
   55.12 +    "Anand Chitipothu <anandology@gmail.com>"
   55.13 +]
   55.14 +__license__ = "public domain"
   55.15 +__contributors__ = "see http://webpy.org/changes"
   55.16 +
   55.17 +import utils, db, net, wsgi, http, webapi, httpserver, debugerror
   55.18 +import template, form
   55.19 +
   55.20 +import session
   55.21 +
   55.22 +from utils import *
   55.23 +from db import *
   55.24 +from net import *
   55.25 +from wsgi import *
   55.26 +from http import *
   55.27 +from webapi import *
   55.28 +from httpserver import *
   55.29 +from debugerror import *
   55.30 +from application import *
   55.31 +from browser import *
   55.32 +try:
   55.33 +    import webopenid as openid
   55.34 +except ImportError:
   55.35 +    pass # requires openid module
   55.36 +
    56.1 Binary file OpenSecurity/install/web.py-0.37/web/__init__.pyc has changed
    57.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    57.2 +++ b/OpenSecurity/install/web.py-0.37/web/application.py	Mon Dec 02 14:02:05 2013 +0100
    57.3 @@ -0,0 +1,687 @@
    57.4 +"""
    57.5 +Web application
    57.6 +(from web.py)
    57.7 +"""
    57.8 +import webapi as web
    57.9 +import webapi, wsgi, utils
   57.10 +import debugerror
   57.11 +import httpserver
   57.12 +
   57.13 +from utils import lstrips, safeunicode
   57.14 +import sys
   57.15 +
   57.16 +import urllib
   57.17 +import traceback
   57.18 +import itertools
   57.19 +import os
   57.20 +import types
   57.21 +from exceptions import SystemExit
   57.22 +
   57.23 +try:
   57.24 +    import wsgiref.handlers
   57.25 +except ImportError:
   57.26 +    pass # don't break people with old Pythons
   57.27 +
   57.28 +__all__ = [
   57.29 +    "application", "auto_application",
   57.30 +    "subdir_application", "subdomain_application", 
   57.31 +    "loadhook", "unloadhook",
   57.32 +    "autodelegate"
   57.33 +]
   57.34 +
   57.35 +class application:
   57.36 +    """
   57.37 +    Application to delegate requests based on path.
   57.38 +    
   57.39 +        >>> urls = ("/hello", "hello")
   57.40 +        >>> app = application(urls, globals())
   57.41 +        >>> class hello:
   57.42 +        ...     def GET(self): return "hello"
   57.43 +        >>>
   57.44 +        >>> app.request("/hello").data
   57.45 +        'hello'
   57.46 +    """
   57.47 +    def __init__(self, mapping=(), fvars={}, autoreload=None):
   57.48 +        if autoreload is None:
   57.49 +            autoreload = web.config.get('debug', False)
   57.50 +        self.init_mapping(mapping)
   57.51 +        self.fvars = fvars
   57.52 +        self.processors = []
   57.53 +        
   57.54 +        self.add_processor(loadhook(self._load))
   57.55 +        self.add_processor(unloadhook(self._unload))
   57.56 +        
   57.57 +        if autoreload:
   57.58 +            def main_module_name():
   57.59 +                mod = sys.modules['__main__']
   57.60 +                file = getattr(mod, '__file__', None) # make sure this works even from python interpreter
   57.61 +                return file and os.path.splitext(os.path.basename(file))[0]
   57.62 +
   57.63 +            def modname(fvars):
   57.64 +                """find name of the module name from fvars."""
   57.65 +                file, name = fvars.get('__file__'), fvars.get('__name__')
   57.66 +                if file is None or name is None:
   57.67 +                    return None
   57.68 +
   57.69 +                if name == '__main__':
   57.70 +                    # Since the __main__ module can't be reloaded, the module has 
   57.71 +                    # to be imported using its file name.                    
   57.72 +                    name = main_module_name()
   57.73 +                return name
   57.74 +                
   57.75 +            mapping_name = utils.dictfind(fvars, mapping)
   57.76 +            module_name = modname(fvars)
   57.77 +            
   57.78 +            def reload_mapping():
   57.79 +                """loadhook to reload mapping and fvars."""
   57.80 +                mod = __import__(module_name, None, None, [''])
   57.81 +                mapping = getattr(mod, mapping_name, None)
   57.82 +                if mapping:
   57.83 +                    self.fvars = mod.__dict__
   57.84 +                    self.init_mapping(mapping)
   57.85 +
   57.86 +            self.add_processor(loadhook(Reloader()))
   57.87 +            if mapping_name and module_name:
   57.88 +                self.add_processor(loadhook(reload_mapping))
   57.89 +
   57.90 +            # load __main__ module usings its filename, so that it can be reloaded.
   57.91 +            if main_module_name() and '__main__' in sys.argv:
   57.92 +                try:
   57.93 +                    __import__(main_module_name())
   57.94 +                except ImportError:
   57.95 +                    pass
   57.96 +                    
   57.97 +    def _load(self):
   57.98 +        web.ctx.app_stack.append(self)
   57.99 +        
  57.100 +    def _unload(self):
  57.101 +        web.ctx.app_stack = web.ctx.app_stack[:-1]
  57.102 +        
  57.103 +        if web.ctx.app_stack:
  57.104 +            # this is a sub-application, revert ctx to earlier state.
  57.105 +            oldctx = web.ctx.get('_oldctx')
  57.106 +            if oldctx:
  57.107 +                web.ctx.home = oldctx.home
  57.108 +                web.ctx.homepath = oldctx.homepath
  57.109 +                web.ctx.path = oldctx.path
  57.110 +                web.ctx.fullpath = oldctx.fullpath
  57.111 +                
  57.112 +    def _cleanup(self):
  57.113 +        # Threads can be recycled by WSGI servers.
  57.114 +        # Clearing up all thread-local state to avoid interefereing with subsequent requests.
  57.115 +        utils.ThreadedDict.clear_all()
  57.116 +
  57.117 +    def init_mapping(self, mapping):
  57.118 +        self.mapping = list(utils.group(mapping, 2))
  57.119 +
  57.120 +    def add_mapping(self, pattern, classname):
  57.121 +        self.mapping.append((pattern, classname))
  57.122 +
  57.123 +    def add_processor(self, processor):
  57.124 +        """
  57.125 +        Adds a processor to the application. 
  57.126 +        
  57.127 +            >>> urls = ("/(.*)", "echo")
  57.128 +            >>> app = application(urls, globals())
  57.129 +            >>> class echo:
  57.130 +            ...     def GET(self, name): return name
  57.131 +            ...
  57.132 +            >>>
  57.133 +            >>> def hello(handler): return "hello, " +  handler()
  57.134 +            ...
  57.135 +            >>> app.add_processor(hello)
  57.136 +            >>> app.request("/web.py").data
  57.137 +            'hello, web.py'
  57.138 +        """
  57.139 +        self.processors.append(processor)
  57.140 +
  57.141 +    def request(self, localpart='/', method='GET', data=None,
  57.142 +                host="0.0.0.0:8080", headers=None, https=False, **kw):
  57.143 +        """Makes request to this application for the specified path and method.
  57.144 +        Response will be a storage object with data, status and headers.
  57.145 +
  57.146 +            >>> urls = ("/hello", "hello")
  57.147 +            >>> app = application(urls, globals())
  57.148 +            >>> class hello:
  57.149 +            ...     def GET(self): 
  57.150 +            ...         web.header('Content-Type', 'text/plain')
  57.151 +            ...         return "hello"
  57.152 +            ...
  57.153 +            >>> response = app.request("/hello")
  57.154 +            >>> response.data
  57.155 +            'hello'
  57.156 +            >>> response.status
  57.157 +            '200 OK'
  57.158 +            >>> response.headers['Content-Type']
  57.159 +            'text/plain'
  57.160 +
  57.161 +        To use https, use https=True.
  57.162 +
  57.163 +            >>> urls = ("/redirect", "redirect")
  57.164 +            >>> app = application(urls, globals())
  57.165 +            >>> class redirect:
  57.166 +            ...     def GET(self): raise web.seeother("/foo")
  57.167 +            ...
  57.168 +            >>> response = app.request("/redirect")
  57.169 +            >>> response.headers['Location']
  57.170 +            'http://0.0.0.0:8080/foo'
  57.171 +            >>> response = app.request("/redirect", https=True)
  57.172 +            >>> response.headers['Location']
  57.173 +            'https://0.0.0.0:8080/foo'
  57.174 +
  57.175 +        The headers argument specifies HTTP headers as a mapping object
  57.176 +        such as a dict.
  57.177 +
  57.178 +            >>> urls = ('/ua', 'uaprinter')
  57.179 +            >>> class uaprinter:
  57.180 +            ...     def GET(self):
  57.181 +            ...         return 'your user-agent is ' + web.ctx.env['HTTP_USER_AGENT']
  57.182 +            ... 
  57.183 +            >>> app = application(urls, globals())
  57.184 +            >>> app.request('/ua', headers = {
  57.185 +            ...      'User-Agent': 'a small jumping bean/1.0 (compatible)'
  57.186 +            ... }).data
  57.187 +            'your user-agent is a small jumping bean/1.0 (compatible)'
  57.188 +
  57.189 +        """
  57.190 +        path, maybe_query = urllib.splitquery(localpart)
  57.191 +        query = maybe_query or ""
  57.192 +        
  57.193 +        if 'env' in kw:
  57.194 +            env = kw['env']
  57.195 +        else:
  57.196 +            env = {}
  57.197 +        env = dict(env, HTTP_HOST=host, REQUEST_METHOD=method, PATH_INFO=path, QUERY_STRING=query, HTTPS=str(https))
  57.198 +        headers = headers or {}
  57.199 +
  57.200 +        for k, v in headers.items():
  57.201 +            env['HTTP_' + k.upper().replace('-', '_')] = v
  57.202 +
  57.203 +        if 'HTTP_CONTENT_LENGTH' in env:
  57.204 +            env['CONTENT_LENGTH'] = env.pop('HTTP_CONTENT_LENGTH')
  57.205 +
  57.206 +        if 'HTTP_CONTENT_TYPE' in env:
  57.207 +            env['CONTENT_TYPE'] = env.pop('HTTP_CONTENT_TYPE')
  57.208 +
  57.209 +        if method not in ["HEAD", "GET"]:
  57.210 +            data = data or ''
  57.211 +            import StringIO
  57.212 +            if isinstance(data, dict):
  57.213 +                q = urllib.urlencode(data)
  57.214 +            else:
  57.215 +                q = data
  57.216 +            env['wsgi.input'] = StringIO.StringIO(q)
  57.217 +            if not env.get('CONTENT_TYPE', '').lower().startswith('multipart/') and 'CONTENT_LENGTH' not in env:
  57.218 +                env['CONTENT_LENGTH'] = len(q)
  57.219 +        response = web.storage()
  57.220 +        def start_response(status, headers):
  57.221 +            response.status = status
  57.222 +            response.headers = dict(headers)
  57.223 +            response.header_items = headers
  57.224 +        response.data = "".join(self.wsgifunc()(env, start_response))
  57.225 +        return response
  57.226 +
  57.227 +    def browser(self):
  57.228 +        import browser
  57.229 +        return browser.AppBrowser(self)
  57.230 +
  57.231 +    def handle(self):
  57.232 +        fn, args = self._match(self.mapping, web.ctx.path)
  57.233 +        return self._delegate(fn, self.fvars, args)
  57.234 +        
  57.235 +    def handle_with_processors(self):
  57.236 +        def process(processors):
  57.237 +            try:
  57.238 +                if processors:
  57.239 +                    p, processors = processors[0], processors[1:]
  57.240 +                    return p(lambda: process(processors))
  57.241 +                else:
  57.242 +                    return self.handle()
  57.243 +            except web.HTTPError:
  57.244 +                raise
  57.245 +            except (KeyboardInterrupt, SystemExit):
  57.246 +                raise
  57.247 +            except:
  57.248 +                print >> web.debug, traceback.format_exc()
  57.249 +                raise self.internalerror()
  57.250 +        
  57.251 +        # processors must be applied in the resvere order. (??)
  57.252 +        return process(self.processors)
  57.253 +                        
  57.254 +    def wsgifunc(self, *middleware):
  57.255 +        """Returns a WSGI-compatible function for this application."""
  57.256 +        def peep(iterator):
  57.257 +            """Peeps into an iterator by doing an iteration
  57.258 +            and returns an equivalent iterator.
  57.259 +            """
  57.260 +            # wsgi requires the headers first
  57.261 +            # so we need to do an iteration
  57.262 +            # and save the result for later
  57.263 +            try:
  57.264 +                firstchunk = iterator.next()
  57.265 +            except StopIteration:
  57.266 +                firstchunk = ''
  57.267 +
  57.268 +            return itertools.chain([firstchunk], iterator)    
  57.269 +                                
  57.270 +        def is_generator(x): return x and hasattr(x, 'next')
  57.271 +        
  57.272 +        def wsgi(env, start_resp):
  57.273 +            # clear threadlocal to avoid inteference of previous requests
  57.274 +            self._cleanup()
  57.275 +
  57.276 +            self.load(env)
  57.277 +            try:
  57.278 +                # allow uppercase methods only
  57.279 +                if web.ctx.method.upper() != web.ctx.method:
  57.280 +                    raise web.nomethod()
  57.281 +
  57.282 +                result = self.handle_with_processors()
  57.283 +                if is_generator(result):
  57.284 +                    result = peep(result)
  57.285 +                else:
  57.286 +                    result = [result]
  57.287 +            except web.HTTPError, e:
  57.288 +                result = [e.data]
  57.289 +
  57.290 +            result = web.safestr(iter(result))
  57.291 +
  57.292 +            status, headers = web.ctx.status, web.ctx.headers
  57.293 +            start_resp(status, headers)
  57.294 +            
  57.295 +            def cleanup():
  57.296 +                self._cleanup()
  57.297 +                yield '' # force this function to be a generator
  57.298 +                            
  57.299 +            return itertools.chain(result, cleanup())
  57.300 +
  57.301 +        for m in middleware: 
  57.302 +            wsgi = m(wsgi)
  57.303 +
  57.304 +        return wsgi
  57.305 +
  57.306 +    def run(self, *middleware):
  57.307 +        """
  57.308 +        Starts handling requests. If called in a CGI or FastCGI context, it will follow
  57.309 +        that protocol. If called from the command line, it will start an HTTP
  57.310 +        server on the port named in the first command line argument, or, if there
  57.311 +        is no argument, on port 8080.
  57.312 +        
  57.313 +        `middleware` is a list of WSGI middleware which is applied to the resulting WSGI
  57.314 +        function.
  57.315 +        """
  57.316 +        return wsgi.runwsgi(self.wsgifunc(*middleware))
  57.317 +
  57.318 +    def stop(self):
  57.319 +        """Stops the http server started by run.
  57.320 +        """
  57.321 +        if httpserver.server:
  57.322 +            httpserver.server.stop()
  57.323 +            httpserver.server = None
  57.324 +    
  57.325 +    def cgirun(self, *middleware):
  57.326 +        """
  57.327 +        Return a CGI handler. This is mostly useful with Google App Engine.
  57.328 +        There you can just do:
  57.329 +        
  57.330 +            main = app.cgirun()
  57.331 +        """
  57.332 +        wsgiapp = self.wsgifunc(*middleware)
  57.333 +
  57.334 +        try:
  57.335 +            from google.appengine.ext.webapp.util import run_wsgi_app
  57.336 +            return run_wsgi_app(wsgiapp)
  57.337 +        except ImportError:
  57.338 +            # we're not running from within Google App Engine
  57.339 +            return wsgiref.handlers.CGIHandler().run(wsgiapp)
  57.340 +    
  57.341 +    def load(self, env):
  57.342 +        """Initializes ctx using env."""
  57.343 +        ctx = web.ctx
  57.344 +        ctx.clear()
  57.345 +        ctx.status = '200 OK'
  57.346 +        ctx.headers = []
  57.347 +        ctx.output = ''
  57.348 +        ctx.environ = ctx.env = env
  57.349 +        ctx.host = env.get('HTTP_HOST')
  57.350 +
  57.351 +        if env.get('wsgi.url_scheme') in ['http', 'https']:
  57.352 +            ctx.protocol = env['wsgi.url_scheme']
  57.353 +        elif env.get('HTTPS', '').lower() in ['on', 'true', '1']:
  57.354 +            ctx.protocol = 'https'
  57.355 +        else:
  57.356 +            ctx.protocol = 'http'
  57.357 +        ctx.homedomain = ctx.protocol + '://' + env.get('HTTP_HOST', '[unknown]')
  57.358 +        ctx.homepath = os.environ.get('REAL_SCRIPT_NAME', env.get('SCRIPT_NAME', ''))
  57.359 +        ctx.home = ctx.homedomain + ctx.homepath
  57.360 +        #@@ home is changed when the request is handled to a sub-application.
  57.361 +        #@@ but the real home is required for doing absolute redirects.
  57.362 +        ctx.realhome = ctx.home
  57.363 +        ctx.ip = env.get('REMOTE_ADDR')
  57.364 +        ctx.method = env.get('REQUEST_METHOD')
  57.365 +        ctx.path = env.get('PATH_INFO')
  57.366 +        # http://trac.lighttpd.net/trac/ticket/406 requires:
  57.367 +        if env.get('SERVER_SOFTWARE', '').startswith('lighttpd/'):
  57.368 +            ctx.path = lstrips(env.get('REQUEST_URI').split('?')[0], ctx.homepath)
  57.369 +            # Apache and CherryPy webservers unquote the url but lighttpd doesn't. 
  57.370 +            # unquote explicitly for lighttpd to make ctx.path uniform across all servers.
  57.371 +            ctx.path = urllib.unquote(ctx.path)
  57.372 +
  57.373 +        if env.get('QUERY_STRING'):
  57.374 +            ctx.query = '?' + env.get('QUERY_STRING', '')
  57.375 +        else:
  57.376 +            ctx.query = ''
  57.377 +
  57.378 +        ctx.fullpath = ctx.path + ctx.query
  57.379 +        
  57.380 +        for k, v in ctx.iteritems():
  57.381 +            # convert all string values to unicode values and replace 
  57.382 +            # malformed data with a suitable replacement marker.
  57.383 +            if isinstance(v, str):
  57.384 +                ctx[k] = v.decode('utf-8', 'replace') 
  57.385 +
  57.386 +        # status must always be str
  57.387 +        ctx.status = '200 OK'
  57.388 +        
  57.389 +        ctx.app_stack = []
  57.390 +
  57.391 +    def _delegate(self, f, fvars, args=[]):
  57.392 +        def handle_class(cls):
  57.393 +            meth = web.ctx.method
  57.394 +            if meth == 'HEAD' and not hasattr(cls, meth):
  57.395 +                meth = 'GET'
  57.396 +            if not hasattr(cls, meth):
  57.397 +                raise web.nomethod(cls)
  57.398 +            tocall = getattr(cls(), meth)
  57.399 +            return tocall(*args)
  57.400 +            
  57.401 +        def is_class(o): return isinstance(o, (types.ClassType, type))
  57.402 +            
  57.403 +        if f is None:
  57.404 +            raise web.notfound()
  57.405 +        elif isinstance(f, application):
  57.406 +            return f.handle_with_processors()
  57.407 +        elif is_class(f):
  57.408 +            return handle_class(f)
  57.409 +        elif isinstance(f, basestring):
  57.410 +            if f.startswith('redirect '):
  57.411 +                url = f.split(' ', 1)[1]
  57.412 +                if web.ctx.method == "GET":
  57.413 +                    x = web.ctx.env.get('QUERY_STRING', '')
  57.414 +                    if x:
  57.415 +                        url += '?' + x
  57.416 +                raise web.redirect(url)
  57.417 +            elif '.' in f:
  57.418 +                mod, cls = f.rsplit('.', 1)
  57.419 +                mod = __import__(mod, None, None, [''])
  57.420 +                cls = getattr(mod, cls)
  57.421 +            else:
  57.422 +                cls = fvars[f]
  57.423 +            return handle_class(cls)
  57.424 +        elif hasattr(f, '__call__'):
  57.425 +            return f()
  57.426 +        else:
  57.427 +            return web.notfound()
  57.428 +
  57.429 +    def _match(self, mapping, value):
  57.430 +        for pat, what in mapping:
  57.431 +            if isinstance(what, application):
  57.432 +                if value.startswith(pat):
  57.433 +                    f = lambda: self._delegate_sub_application(pat, what)
  57.434 +                    return f, None
  57.435 +                else:
  57.436 +                    continue
  57.437 +            elif isinstance(what, basestring):
  57.438 +                what, result = utils.re_subm('^' + pat + '$', what, value)
  57.439 +            else:
  57.440 +                result = utils.re_compile('^' + pat + '$').match(value)
  57.441 +                
  57.442 +            if result: # it's a match
  57.443 +                return what, [x for x in result.groups()]
  57.444 +        return None, None
  57.445 +        
  57.446 +    def _delegate_sub_application(self, dir, app):
  57.447 +        """Deletes request to sub application `app` rooted at the directory `dir`.
  57.448 +        The home, homepath, path and fullpath values in web.ctx are updated to mimic request
  57.449 +        to the subapp and are restored after it is handled. 
  57.450 +        
  57.451 +        @@Any issues with when used with yield?
  57.452 +        """
  57.453 +        web.ctx._oldctx = web.storage(web.ctx)
  57.454 +        web.ctx.home += dir
  57.455 +        web.ctx.homepath += dir
  57.456 +        web.ctx.path = web.ctx.path[len(dir):]
  57.457 +        web.ctx.fullpath = web.ctx.fullpath[len(dir):]
  57.458 +        return app.handle_with_processors()
  57.459 +            
  57.460 +    def get_parent_app(self):
  57.461 +        if self in web.ctx.app_stack:
  57.462 +            index = web.ctx.app_stack.index(self)
  57.463 +            if index > 0:
  57.464 +                return web.ctx.app_stack[index-1]
  57.465 +        
  57.466 +    def notfound(self):
  57.467 +        """Returns HTTPError with '404 not found' message"""
  57.468 +        parent = self.get_parent_app()
  57.469 +        if parent:
  57.470 +            return parent.notfound()
  57.471 +        else:
  57.472 +            return web._NotFound()
  57.473 +            
  57.474 +    def internalerror(self):
  57.475 +        """Returns HTTPError with '500 internal error' message"""
  57.476 +        parent = self.get_parent_app()
  57.477 +        if parent:
  57.478 +            return parent.internalerror()
  57.479 +        elif web.config.get('debug'):
  57.480 +            import debugerror
  57.481 +            return debugerror.debugerror()
  57.482 +        else:
  57.483 +            return web._InternalError()
  57.484 +
  57.485 +class auto_application(application):
  57.486 +    """Application similar to `application` but urls are constructed 
  57.487 +    automatiacally using metaclass.
  57.488 +
  57.489 +        >>> app = auto_application()
  57.490 +        >>> class hello(app.page):
  57.491 +        ...     def GET(self): return "hello, world"
  57.492 +        ...
  57.493 +        >>> class foo(app.page):
  57.494 +        ...     path = '/foo/.*'
  57.495 +        ...     def GET(self): return "foo"
  57.496 +        >>> app.request("/hello").data
  57.497 +        'hello, world'
  57.498 +        >>> app.request('/foo/bar').data
  57.499 +        'foo'
  57.500 +    """
  57.501 +    def __init__(self):
  57.502 +        application.__init__(self)
  57.503 +
  57.504 +        class metapage(type):
  57.505 +            def __init__(klass, name, bases, attrs):
  57.506 +                type.__init__(klass, name, bases, attrs)
  57.507 +                path = attrs.get('path', '/' + name)
  57.508 +
  57.509 +                # path can be specified as None to ignore that class
  57.510 +                # typically required to create a abstract base class.
  57.511 +                if path is not None:
  57.512 +                    self.add_mapping(path, klass)
  57.513 +
  57.514 +        class page:
  57.515 +            path = None
  57.516 +            __metaclass__ = metapage
  57.517 +
  57.518 +        self.page = page
  57.519 +
  57.520 +# The application class already has the required functionality of subdir_application
  57.521 +subdir_application = application
  57.522 +                
  57.523 +class subdomain_application(application):
  57.524 +    """
  57.525 +    Application to delegate requests based on the host.
  57.526 +
  57.527 +        >>> urls = ("/hello", "hello")
  57.528 +        >>> app = application(urls, globals())
  57.529 +        >>> class hello:
  57.530 +        ...     def GET(self): return "hello"
  57.531 +        >>>
  57.532 +        >>> mapping = (r"hello\.example\.com", app)
  57.533 +        >>> app2 = subdomain_application(mapping)
  57.534 +        >>> app2.request("/hello", host="hello.example.com").data
  57.535 +        'hello'
  57.536 +        >>> response = app2.request("/hello", host="something.example.com")
  57.537 +        >>> response.status
  57.538 +        '404 Not Found'
  57.539 +        >>> response.data
  57.540 +        'not found'
  57.541 +    """
  57.542 +    def handle(self):
  57.543 +        host = web.ctx.host.split(':')[0] #strip port
  57.544 +        fn, args = self._match(self.mapping, host)
  57.545 +        return self._delegate(fn, self.fvars, args)
  57.546 +        
  57.547 +    def _match(self, mapping, value):
  57.548 +        for pat, what in mapping:
  57.549 +            if isinstance(what, basestring):
  57.550 +                what, result = utils.re_subm('^' + pat + '$', what, value)
  57.551 +            else:
  57.552 +                result = utils.re_compile('^' + pat + '$').match(value)
  57.553 +
  57.554 +            if result: # it's a match
  57.555 +                return what, [x for x in result.groups()]
  57.556 +        return None, None
  57.557 +        
  57.558 +def loadhook(h):
  57.559 +    """
  57.560 +    Converts a load hook into an application processor.
  57.561 +    
  57.562 +        >>> app = auto_application()
  57.563 +        >>> def f(): "something done before handling request"
  57.564 +        ...
  57.565 +        >>> app.add_processor(loadhook(f))
  57.566 +    """
  57.567 +    def processor(handler):
  57.568 +        h()
  57.569 +        return handler()
  57.570 +        
  57.571 +    return processor
  57.572 +    
  57.573 +def unloadhook(h):
  57.574 +    """
  57.575 +    Converts an unload hook into an application processor.
  57.576 +    
  57.577 +        >>> app = auto_application()
  57.578 +        >>> def f(): "something done after handling request"
  57.579 +        ...
  57.580 +        >>> app.add_processor(unloadhook(f))    
  57.581 +    """
  57.582 +    def processor(handler):
  57.583 +        try:
  57.584 +            result = handler()
  57.585 +            is_generator = result and hasattr(result, 'next')
  57.586 +        except:
  57.587 +            # run the hook even when handler raises some exception
  57.588 +            h()
  57.589 +            raise
  57.590 +
  57.591 +        if is_generator:
  57.592 +            return wrap(result)
  57.593 +        else:
  57.594 +            h()
  57.595 +            return result
  57.596 +            
  57.597 +    def wrap(result):
  57.598 +        def next():
  57.599 +            try:
  57.600 +                return result.next()
  57.601 +            except:
  57.602 +                # call the hook at the and of iterator
  57.603 +                h()
  57.604 +                raise
  57.605 +
  57.606 +        result = iter(result)
  57.607 +        while True:
  57.608 +            yield next()
  57.609 +            
  57.610 +    return processor
  57.611 +
  57.612 +def autodelegate(prefix=''):
  57.613 +    """
  57.614 +    Returns a method that takes one argument and calls the method named prefix+arg,
  57.615 +    calling `notfound()` if there isn't one. Example:
  57.616 +
  57.617 +        urls = ('/prefs/(.*)', 'prefs')
  57.618 +
  57.619 +        class prefs:
  57.620 +            GET = autodelegate('GET_')
  57.621 +            def GET_password(self): pass
  57.622 +            def GET_privacy(self): pass
  57.623 +
  57.624 +    `GET_password` would get called for `/prefs/password` while `GET_privacy` for 
  57.625 +    `GET_privacy` gets called for `/prefs/privacy`.
  57.626 +    
  57.627 +    If a user visits `/prefs/password/change` then `GET_password(self, '/change')`
  57.628 +    is called.
  57.629 +    """
  57.630 +    def internal(self, arg):
  57.631 +        if '/' in arg:
  57.632 +            first, rest = arg.split('/', 1)
  57.633 +            func = prefix + first
  57.634 +            args = ['/' + rest]
  57.635 +        else:
  57.636 +            func = prefix + arg
  57.637 +            args = []
  57.638 +        
  57.639 +        if hasattr(self, func):
  57.640 +            try:
  57.641 +                return getattr(self, func)(*args)
  57.642 +            except TypeError:
  57.643 +                raise web.notfound()
  57.644 +        else:
  57.645 +            raise web.notfound()
  57.646 +    return internal
  57.647 +
  57.648 +class Reloader:
  57.649 +    """Checks to see if any loaded modules have changed on disk and, 
  57.650 +    if so, reloads them.
  57.651 +    """
  57.652 +
  57.653 +    """File suffix of compiled modules."""
  57.654 +    if sys.platform.startswith('java'):
  57.655 +        SUFFIX = '$py.class'
  57.656 +    else:
  57.657 +        SUFFIX = '.pyc'
  57.658 +    
  57.659 +    def __init__(self):
  57.660 +        self.mtimes = {}
  57.661 +
  57.662 +    def __call__(self):
  57.663 +        for mod in sys.modules.values():
  57.664 +            self.check(mod)
  57.665 +
  57.666 +    def check(self, mod):
  57.667 +        # jython registers java packages as modules but they either
  57.668 +        # don't have a __file__ attribute or its value is None
  57.669 +        if not (mod and hasattr(mod, '__file__') and mod.__file__):
  57.670 +            return
  57.671 +
  57.672 +        try: 
  57.673 +            mtime = os.stat(mod.__file__).st_mtime
  57.674 +        except (OSError, IOError):
  57.675 +            return
  57.676 +        if mod.__file__.endswith(self.__class__.SUFFIX) and os.path.exists(mod.__file__[:-1]):
  57.677 +            mtime = max(os.stat(mod.__file__[:-1]).st_mtime, mtime)
  57.678 +            
  57.679 +        if mod not in self.mtimes:
  57.680 +            self.mtimes[mod] = mtime
  57.681 +        elif self.mtimes[mod] < mtime:
  57.682 +            try: 
  57.683 +                reload(mod)
  57.684 +                self.mtimes[mod] = mtime
  57.685 +            except ImportError: 
  57.686 +                pass
  57.687 +                
  57.688 +if __name__ == "__main__":
  57.689 +    import doctest
  57.690 +    doctest.testmod()
    58.1 Binary file OpenSecurity/install/web.py-0.37/web/application.pyc has changed
    59.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    59.2 +++ b/OpenSecurity/install/web.py-0.37/web/browser.py	Mon Dec 02 14:02:05 2013 +0100
    59.3 @@ -0,0 +1,236 @@
    59.4 +"""Browser to test web applications.
    59.5 +(from web.py)
    59.6 +"""
    59.7 +from utils import re_compile
    59.8 +from net import htmlunquote
    59.9 +
   59.10 +import httplib, urllib, urllib2
   59.11 +import copy
   59.12 +from StringIO import StringIO
   59.13 +
   59.14 +DEBUG = False
   59.15 +
   59.16 +__all__ = [
   59.17 +    "BrowserError",
   59.18 +    "Browser", "AppBrowser",
   59.19 +    "AppHandler"
   59.20 +]
   59.21 +
   59.22 +class BrowserError(Exception):
   59.23 +    pass
   59.24 +
   59.25 +class Browser:
   59.26 +    def __init__(self):
   59.27 +        import cookielib
   59.28 +        self.cookiejar = cookielib.CookieJar()
   59.29 +        self._cookie_processor = urllib2.HTTPCookieProcessor(self.cookiejar)
   59.30 +        self.form = None
   59.31 +
   59.32 +        self.url = "http://0.0.0.0:8080/"
   59.33 +        self.path = "/"
   59.34 +        
   59.35 +        self.status = None
   59.36 +        self.data = None
   59.37 +        self._response = None
   59.38 +        self._forms = None
   59.39 +
   59.40 +    def reset(self):
   59.41 +        """Clears all cookies and history."""
   59.42 +        self.cookiejar.clear()
   59.43 +
   59.44 +    def build_opener(self):
   59.45 +        """Builds the opener using urllib2.build_opener. 
   59.46 +        Subclasses can override this function to prodive custom openers.
   59.47 +        """
   59.48 +        return urllib2.build_opener()
   59.49 +
   59.50 +    def do_request(self, req):
   59.51 +        if DEBUG:
   59.52 +            print 'requesting', req.get_method(), req.get_full_url()
   59.53 +        opener = self.build_opener()
   59.54 +        opener.add_handler(self._cookie_processor)
   59.55 +        try:
   59.56 +            self._response = opener.open(req)
   59.57 +        except urllib2.HTTPError, e:
   59.58 +            self._response = e
   59.59 +
   59.60 +        self.url = self._response.geturl()
   59.61 +        self.path = urllib2.Request(self.url).get_selector()
   59.62 +        self.data = self._response.read()
   59.63 +        self.status = self._response.code
   59.64 +        self._forms = None
   59.65 +        self.form = None
   59.66 +        return self.get_response()
   59.67 +
   59.68 +    def open(self, url, data=None, headers={}):
   59.69 +        """Opens the specified url."""
   59.70 +        url = urllib.basejoin(self.url, url)
   59.71 +        req = urllib2.Request(url, data, headers)
   59.72 +        return self.do_request(req)
   59.73 +
   59.74 +    def show(self):
   59.75 +        """Opens the current page in real web browser."""
   59.76 +        f = open('page.html', 'w')
   59.77 +        f.write(self.data)
   59.78 +        f.close()
   59.79 +
   59.80 +        import webbrowser, os
   59.81 +        url = 'file://' + os.path.abspath('page.html')
   59.82 +        webbrowser.open(url)
   59.83 +
   59.84 +    def get_response(self):
   59.85 +        """Returns a copy of the current response."""
   59.86 +        return urllib.addinfourl(StringIO(self.data), self._response.info(), self._response.geturl())
   59.87 +
   59.88 +    def get_soup(self):
   59.89 +        """Returns beautiful soup of the current document."""
   59.90 +        import BeautifulSoup
   59.91 +        return BeautifulSoup.BeautifulSoup(self.data)
   59.92 +
   59.93 +    def get_text(self, e=None):
   59.94 +        """Returns content of e or the current document as plain text."""
   59.95 +        e = e or self.get_soup()
   59.96 +        return ''.join([htmlunquote(c) for c in e.recursiveChildGenerator() if isinstance(c, unicode)])
   59.97 +
   59.98 +    def _get_links(self):
   59.99 +        soup = self.get_soup()
  59.100 +        return [a for a in soup.findAll(name='a')]
  59.101 +        
  59.102 +    def get_links(self, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
  59.103 +        """Returns all links in the document."""
  59.104 +        return self._filter_links(self._get_links(),
  59.105 +            text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
  59.106 +
  59.107 +    def follow_link(self, link=None, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
  59.108 +        if link is None:
  59.109 +            links = self._filter_links(self.get_links(),
  59.110 +                text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
  59.111 +            link = links and links[0]
  59.112 +            
  59.113 +        if link:
  59.114 +            return self.open(link['href'])
  59.115 +        else:
  59.116 +            raise BrowserError("No link found")
  59.117 +            
  59.118 +    def find_link(self, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
  59.119 +        links = self._filter_links(self.get_links(), 
  59.120 +            text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
  59.121 +        return links and links[0] or None
  59.122 +            
  59.123 +    def _filter_links(self, links, 
  59.124 +            text=None, text_regex=None,
  59.125 +            url=None, url_regex=None,
  59.126 +            predicate=None):
  59.127 +        predicates = []
  59.128 +        if text is not None:
  59.129 +            predicates.append(lambda link: link.string == text)
  59.130 +        if text_regex is not None:
  59.131 +            predicates.append(lambda link: re_compile(text_regex).search(link.string or ''))
  59.132 +        if url is not None:
  59.133 +            predicates.append(lambda link: link.get('href') == url)
  59.134 +        if url_regex is not None:
  59.135 +            predicates.append(lambda link: re_compile(url_regex).search(link.get('href', '')))
  59.136 +        if predicate:
  59.137 +            predicate.append(predicate)
  59.138 +
  59.139 +        def f(link):
  59.140 +            for p in predicates:
  59.141 +                if not p(link):
  59.142 +                    return False
  59.143 +            return True
  59.144 +
  59.145 +        return [link for link in links if f(link)]
  59.146 +
  59.147 +    def get_forms(self):
  59.148 +        """Returns all forms in the current document.
  59.149 +        The returned form objects implement the ClientForm.HTMLForm interface.
  59.150 +        """
  59.151 +        if self._forms is None:
  59.152 +            import ClientForm
  59.153 +            self._forms = ClientForm.ParseResponse(self.get_response(), backwards_compat=False)
  59.154 +        return self._forms
  59.155 +
  59.156 +    def select_form(self, name=None, predicate=None, index=0):
  59.157 +        """Selects the specified form."""
  59.158 +        forms = self.get_forms()
  59.159 +
  59.160 +        if name is not None:
  59.161 +            forms = [f for f in forms if f.name == name]
  59.162 +        if predicate:
  59.163 +            forms = [f for f in forms if predicate(f)]
  59.164 +            
  59.165 +        if forms:
  59.166 +            self.form = forms[index]
  59.167 +            return self.form
  59.168 +        else:
  59.169 +            raise BrowserError("No form selected.")
  59.170 +        
  59.171 +    def submit(self, **kw):
  59.172 +        """submits the currently selected form."""
  59.173 +        if self.form is None:
  59.174 +            raise BrowserError("No form selected.")
  59.175 +        req = self.form.click(**kw)
  59.176 +        return self.do_request(req)
  59.177 +
  59.178 +    def __getitem__(self, key):
  59.179 +        return self.form[key]
  59.180 +
  59.181 +    def __setitem__(self, key, value):
  59.182 +        self.form[key] = value
  59.183 +
  59.184 +class AppBrowser(Browser):
  59.185 +    """Browser interface to test web.py apps.
  59.186 +    
  59.187 +        b = AppBrowser(app)
  59.188 +        b.open('/')
  59.189 +        b.follow_link(text='Login')
  59.190 +        
  59.191 +        b.select_form(name='login')
  59.192 +        b['username'] = 'joe'
  59.193 +        b['password'] = 'secret'
  59.194 +        b.submit()
  59.195 +
  59.196 +        assert b.path == '/'
  59.197 +        assert 'Welcome joe' in b.get_text()
  59.198 +    """
  59.199 +    def __init__(self, app):
  59.200 +        Browser.__init__(self)
  59.201 +        self.app = app
  59.202 +
  59.203 +    def build_opener(self):
  59.204 +        return urllib2.build_opener(AppHandler(self.app))
  59.205 +
  59.206 +class AppHandler(urllib2.HTTPHandler):
  59.207 +    """urllib2 handler to handle requests using web.py application."""
  59.208 +    handler_order = 100
  59.209 +
  59.210 +    def __init__(self, app):
  59.211 +        self.app = app
  59.212 +
  59.213 +    def http_open(self, req):
  59.214 +        result = self.app.request(
  59.215 +            localpart=req.get_selector(),
  59.216 +            method=req.get_method(),
  59.217 +            host=req.get_host(),
  59.218 +            data=req.get_data(),
  59.219 +            headers=dict(req.header_items()),
  59.220 +            https=req.get_type() == "https"
  59.221 +        )
  59.222 +        return self._make_response(result, req.get_full_url())
  59.223 +
  59.224 +    def https_open(self, req):
  59.225 +        return self.http_open(req)
  59.226 +    
  59.227 +    try:
  59.228 +        https_request = urllib2.HTTPHandler.do_request_
  59.229 +    except AttributeError:
  59.230 +        # for python 2.3
  59.231 +        pass
  59.232 +
  59.233 +    def _make_response(self, result, url):
  59.234 +        data = "\r\n".join(["%s: %s" % (k, v) for k, v in result.header_items])
  59.235 +        headers = httplib.HTTPMessage(StringIO(data))
  59.236 +        response = urllib.addinfourl(StringIO(result.data), headers, url)
  59.237 +        code, msg = result.status.split(None, 1)
  59.238 +        response.code, response.msg = int(code), msg
  59.239 +        return response
    60.1 Binary file OpenSecurity/install/web.py-0.37/web/browser.pyc has changed
    61.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    61.2 +++ b/OpenSecurity/install/web.py-0.37/web/contrib/template.py	Mon Dec 02 14:02:05 2013 +0100
    61.3 @@ -0,0 +1,131 @@
    61.4 +"""
    61.5 +Interface to various templating engines.
    61.6 +"""
    61.7 +import os.path
    61.8 +
    61.9 +__all__ = [
   61.10 +    "render_cheetah", "render_genshi", "render_mako",
   61.11 +    "cache", 
   61.12 +]
   61.13 +
   61.14 +class render_cheetah:
   61.15 +    """Rendering interface to Cheetah Templates.
   61.16 +
   61.17 +    Example:
   61.18 +
   61.19 +        render = render_cheetah('templates')
   61.20 +        render.hello(name="cheetah")
   61.21 +    """
   61.22 +    def __init__(self, path):
   61.23 +        # give error if Chetah is not installed
   61.24 +        from Cheetah.Template import Template
   61.25 +        self.path = path
   61.26 +
   61.27 +    def __getattr__(self, name):
   61.28 +        from Cheetah.Template import Template
   61.29 +        path = os.path.join(self.path, name + ".html")
   61.30 +        
   61.31 +        def template(**kw):
   61.32 +            t = Template(file=path, searchList=[kw])
   61.33 +            return t.respond()
   61.34 +
   61.35 +        return template
   61.36 +    
   61.37 +class render_genshi:
   61.38 +    """Rendering interface genshi templates.
   61.39 +    Example:
   61.40 +
   61.41 +    for xml/html templates.
   61.42 +
   61.43 +        render = render_genshi(['templates/'])
   61.44 +        render.hello(name='genshi')
   61.45 +
   61.46 +    For text templates:
   61.47 +
   61.48 +        render = render_genshi(['templates/'], type='text')
   61.49 +        render.hello(name='genshi')
   61.50 +    """
   61.51 +
   61.52 +    def __init__(self, *a, **kwargs):
   61.53 +        from genshi.template import TemplateLoader
   61.54 +
   61.55 +        self._type = kwargs.pop('type', None)
   61.56 +        self._loader = TemplateLoader(*a, **kwargs)
   61.57 +
   61.58 +    def __getattr__(self, name):
   61.59 +        # Assuming all templates are html
   61.60 +        path = name + ".html"
   61.61 +
   61.62 +        if self._type == "text":
   61.63 +            from genshi.template import TextTemplate
   61.64 +            cls = TextTemplate
   61.65 +            type = "text"
   61.66 +        else:
   61.67 +            cls = None
   61.68 +            type = None
   61.69 +
   61.70 +        t = self._loader.load(path, cls=cls)
   61.71 +        def template(**kw):
   61.72 +            stream = t.generate(**kw)
   61.73 +            if type:
   61.74 +                return stream.render(type)
   61.75 +            else:
   61.76 +                return stream.render()
   61.77 +        return template
   61.78 +
   61.79 +class render_jinja:
   61.80 +    """Rendering interface to Jinja2 Templates
   61.81 +    
   61.82 +    Example:
   61.83 +
   61.84 +        render= render_jinja('templates')
   61.85 +        render.hello(name='jinja2')
   61.86 +    """
   61.87 +    def __init__(self, *a, **kwargs):
   61.88 +        extensions = kwargs.pop('extensions', [])
   61.89 +        globals = kwargs.pop('globals', {})
   61.90 +
   61.91 +        from jinja2 import Environment,FileSystemLoader
   61.92 +        self._lookup = Environment(loader=FileSystemLoader(*a, **kwargs), extensions=extensions)
   61.93 +        self._lookup.globals.update(globals)
   61.94 +        
   61.95 +    def __getattr__(self, name):
   61.96 +        # Assuming all templates end with .html
   61.97 +        path = name + '.html'
   61.98 +        t = self._lookup.get_template(path)
   61.99 +        return t.render
  61.100 +        
  61.101 +class render_mako:
  61.102 +    """Rendering interface to Mako Templates.
  61.103 +
  61.104 +    Example:
  61.105 +
  61.106 +        render = render_mako(directories=['templates'])
  61.107 +        render.hello(name="mako")
  61.108 +    """
  61.109 +    def __init__(self, *a, **kwargs):
  61.110 +        from mako.lookup import TemplateLookup
  61.111 +        self._lookup = TemplateLookup(*a, **kwargs)
  61.112 +
  61.113 +    def __getattr__(self, name):
  61.114 +        # Assuming all templates are html
  61.115 +        path = name + ".html"
  61.116 +        t = self._lookup.get_template(path)
  61.117 +        return t.render
  61.118 +
  61.119 +class cache:
  61.120 +    """Cache for any rendering interface.
  61.121 +    
  61.122 +    Example:
  61.123 +
  61.124 +        render = cache(render_cheetah("templates/"))
  61.125 +        render.hello(name='cache')
  61.126 +    """
  61.127 +    def __init__(self, render):
  61.128 +        self._render = render
  61.129 +        self._cache = {}
  61.130 +
  61.131 +    def __getattr__(self, name):
  61.132 +        if name not in self._cache:
  61.133 +            self._cache[name] = getattr(self._render, name)
  61.134 +        return self._cache[name]
    62.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    62.2 +++ b/OpenSecurity/install/web.py-0.37/web/db.py	Mon Dec 02 14:02:05 2013 +0100
    62.3 @@ -0,0 +1,1237 @@
    62.4 +"""
    62.5 +Database API
    62.6 +(part of web.py)
    62.7 +"""
    62.8 +
    62.9 +__all__ = [
   62.10 +  "UnknownParamstyle", "UnknownDB", "TransactionError", 
   62.11 +  "sqllist", "sqlors", "reparam", "sqlquote",
   62.12 +  "SQLQuery", "SQLParam", "sqlparam",
   62.13 +  "SQLLiteral", "sqlliteral",
   62.14 +  "database", 'DB',
   62.15 +]
   62.16 +
   62.17 +import time
   62.18 +try:
   62.19 +    import datetime
   62.20 +except ImportError:
   62.21 +    datetime = None
   62.22 +
   62.23 +try: set
   62.24 +except NameError:
   62.25 +    from sets import Set as set
   62.26 +    
   62.27 +from utils import threadeddict, storage, iters, iterbetter, safestr, safeunicode
   62.28 +
   62.29 +try:
   62.30 +    # db module can work independent of web.py
   62.31 +    from webapi import debug, config
   62.32 +except:
   62.33 +    import sys
   62.34 +    debug = sys.stderr
   62.35 +    config = storage()
   62.36 +
   62.37 +class UnknownDB(Exception):
   62.38 +    """raised for unsupported dbms"""
   62.39 +    pass
   62.40 +
   62.41 +class _ItplError(ValueError): 
   62.42 +    def __init__(self, text, pos):
   62.43 +        ValueError.__init__(self)
   62.44 +        self.text = text
   62.45 +        self.pos = pos
   62.46 +    def __str__(self):
   62.47 +        return "unfinished expression in %s at char %d" % (
   62.48 +            repr(self.text), self.pos)
   62.49 +
   62.50 +class TransactionError(Exception): pass
   62.51 +
   62.52 +class UnknownParamstyle(Exception): 
   62.53 +    """
   62.54 +    raised for unsupported db paramstyles
   62.55 +
   62.56 +    (currently supported: qmark, numeric, format, pyformat)
   62.57 +    """
   62.58 +    pass
   62.59 +    
   62.60 +class SQLParam(object):
   62.61 +    """
   62.62 +    Parameter in SQLQuery.
   62.63 +    
   62.64 +        >>> q = SQLQuery(["SELECT * FROM test WHERE name=", SQLParam("joe")])
   62.65 +        >>> q
   62.66 +        <sql: "SELECT * FROM test WHERE name='joe'">
   62.67 +        >>> q.query()
   62.68 +        'SELECT * FROM test WHERE name=%s'
   62.69 +        >>> q.values()
   62.70 +        ['joe']
   62.71 +    """
   62.72 +    __slots__ = ["value"]
   62.73 +
   62.74 +    def __init__(self, value):
   62.75 +        self.value = value
   62.76 +        
   62.77 +    def get_marker(self, paramstyle='pyformat'):
   62.78 +        if paramstyle == 'qmark':
   62.79 +            return '?'
   62.80 +        elif paramstyle == 'numeric':
   62.81 +            return ':1'
   62.82 +        elif paramstyle is None or paramstyle in ['format', 'pyformat']:
   62.83 +            return '%s'
   62.84 +        raise UnknownParamstyle, paramstyle
   62.85 +        
   62.86 +    def sqlquery(self): 
   62.87 +        return SQLQuery([self])
   62.88 +        
   62.89 +    def __add__(self, other):
   62.90 +        return self.sqlquery() + other
   62.91 +        
   62.92 +    def __radd__(self, other):
   62.93 +        return other + self.sqlquery() 
   62.94 +            
   62.95 +    def __str__(self): 
   62.96 +        return str(self.value)
   62.97 +    
   62.98 +    def __repr__(self):
   62.99 +        return '<param: %s>' % repr(self.value)
  62.100 +
  62.101 +sqlparam =  SQLParam
  62.102 +
  62.103 +class SQLQuery(object):
  62.104 +    """
  62.105 +    You can pass this sort of thing as a clause in any db function.
  62.106 +    Otherwise, you can pass a dictionary to the keyword argument `vars`
  62.107 +    and the function will call reparam for you.
  62.108 +
  62.109 +    Internally, consists of `items`, which is a list of strings and
  62.110 +    SQLParams, which get concatenated to produce the actual query.
  62.111 +    """
  62.112 +    __slots__ = ["items"]
  62.113 +
  62.114 +    # tested in sqlquote's docstring
  62.115 +    def __init__(self, items=None):
  62.116 +        r"""Creates a new SQLQuery.
  62.117 +        
  62.118 +            >>> SQLQuery("x")
  62.119 +            <sql: 'x'>
  62.120 +            >>> q = SQLQuery(['SELECT * FROM ', 'test', ' WHERE x=', SQLParam(1)])
  62.121 +            >>> q
  62.122 +            <sql: 'SELECT * FROM test WHERE x=1'>
  62.123 +            >>> q.query(), q.values()
  62.124 +            ('SELECT * FROM test WHERE x=%s', [1])
  62.125 +            >>> SQLQuery(SQLParam(1))
  62.126 +            <sql: '1'>
  62.127 +        """
  62.128 +        if items is None:
  62.129 +            self.items = []
  62.130 +        elif isinstance(items, list):
  62.131 +            self.items = items
  62.132 +        elif isinstance(items, SQLParam):
  62.133 +            self.items = [items]
  62.134 +        elif isinstance(items, SQLQuery):
  62.135 +            self.items = list(items.items)
  62.136 +        else:
  62.137 +            self.items = [items]
  62.138 +            
  62.139 +        # Take care of SQLLiterals
  62.140 +        for i, item in enumerate(self.items):
  62.141 +            if isinstance(item, SQLParam) and isinstance(item.value, SQLLiteral):
  62.142 +                self.items[i] = item.value.v
  62.143 +
  62.144 +    def append(self, value):
  62.145 +        self.items.append(value)
  62.146 +
  62.147 +    def __add__(self, other):
  62.148 +        if isinstance(other, basestring):
  62.149 +            items = [other]
  62.150 +        elif isinstance(other, SQLQuery):
  62.151 +            items = other.items
  62.152 +        else:
  62.153 +            return NotImplemented
  62.154 +        return SQLQuery(self.items + items)
  62.155 +
  62.156 +    def __radd__(self, other):
  62.157 +        if isinstance(other, basestring):
  62.158 +            items = [other]
  62.159 +        else:
  62.160 +            return NotImplemented
  62.161 +            
  62.162 +        return SQLQuery(items + self.items)
  62.163 +
  62.164 +    def __iadd__(self, other):
  62.165 +        if isinstance(other, (basestring, SQLParam)):
  62.166 +            self.items.append(other)
  62.167 +        elif isinstance(other, SQLQuery):
  62.168 +            self.items.extend(other.items)
  62.169 +        else:
  62.170 +            return NotImplemented
  62.171 +        return self
  62.172 +
  62.173 +    def __len__(self):
  62.174 +        return len(self.query())
  62.175 +        
  62.176 +    def query(self, paramstyle=None):
  62.177 +        """
  62.178 +        Returns the query part of the sql query.
  62.179 +            >>> q = SQLQuery(["SELECT * FROM test WHERE name=", SQLParam('joe')])
  62.180 +            >>> q.query()
  62.181 +            'SELECT * FROM test WHERE name=%s'
  62.182 +            >>> q.query(paramstyle='qmark')
  62.183 +            'SELECT * FROM test WHERE name=?'
  62.184 +        """
  62.185 +        s = []
  62.186 +        for x in self.items:
  62.187 +            if isinstance(x, SQLParam):
  62.188 +                x = x.get_marker(paramstyle)
  62.189 +                s.append(safestr(x))
  62.190 +            else:
  62.191 +                x = safestr(x)
  62.192 +                # automatically escape % characters in the query
  62.193 +                # For backward compatability, ignore escaping when the query looks already escaped
  62.194 +                if paramstyle in ['format', 'pyformat']:
  62.195 +                    if '%' in x and '%%' not in x:
  62.196 +                        x = x.replace('%', '%%')
  62.197 +                s.append(x)
  62.198 +        return "".join(s)
  62.199 +    
  62.200 +    def values(self):
  62.201 +        """
  62.202 +        Returns the values of the parameters used in the sql query.
  62.203 +            >>> q = SQLQuery(["SELECT * FROM test WHERE name=", SQLParam('joe')])
  62.204 +            >>> q.values()
  62.205 +            ['joe']
  62.206 +        """
  62.207 +        return [i.value for i in self.items if isinstance(i, SQLParam)]
  62.208 +        
  62.209 +    def join(items, sep=' ', prefix=None, suffix=None, target=None):
  62.210 +        """
  62.211 +        Joins multiple queries.
  62.212 +        
  62.213 +        >>> SQLQuery.join(['a', 'b'], ', ')
  62.214 +        <sql: 'a, b'>
  62.215 +
  62.216 +        Optinally, prefix and suffix arguments can be provided.
  62.217 +
  62.218 +        >>> SQLQuery.join(['a', 'b'], ', ', prefix='(', suffix=')')
  62.219 +        <sql: '(a, b)'>
  62.220 +
  62.221 +        If target argument is provided, the items are appended to target instead of creating a new SQLQuery.
  62.222 +        """
  62.223 +        if target is None:
  62.224 +            target = SQLQuery()
  62.225 +
  62.226 +        target_items = target.items
  62.227 +
  62.228 +        if prefix:
  62.229 +            target_items.append(prefix)
  62.230 +
  62.231 +        for i, item in enumerate(items):
  62.232 +            if i != 0:
  62.233 +                target_items.append(sep)
  62.234 +            if isinstance(item, SQLQuery):
  62.235 +                target_items.extend(item.items)
  62.236 +            else:
  62.237 +                target_items.append(item)
  62.238 +
  62.239 +        if suffix:
  62.240 +            target_items.append(suffix)
  62.241 +        return target
  62.242 +    
  62.243 +    join = staticmethod(join)
  62.244 +    
  62.245 +    def _str(self):
  62.246 +        try:
  62.247 +            return self.query() % tuple([sqlify(x) for x in self.values()])            
  62.248 +        except (ValueError, TypeError):
  62.249 +            return self.query()
  62.250 +        
  62.251 +    def __str__(self):
  62.252 +        return safestr(self._str())
  62.253 +        
  62.254 +    def __unicode__(self):
  62.255 +        return safeunicode(self._str())
  62.256 +
  62.257 +    def __repr__(self):
  62.258 +        return '<sql: %s>' % repr(str(self))
  62.259 +
  62.260 +class SQLLiteral: 
  62.261 +    """
  62.262 +    Protects a string from `sqlquote`.
  62.263 +
  62.264 +        >>> sqlquote('NOW()')
  62.265 +        <sql: "'NOW()'">
  62.266 +        >>> sqlquote(SQLLiteral('NOW()'))
  62.267 +        <sql: 'NOW()'>
  62.268 +    """
  62.269 +    def __init__(self, v): 
  62.270 +        self.v = v
  62.271 +
  62.272 +    def __repr__(self): 
  62.273 +        return self.v
  62.274 +
  62.275 +sqlliteral = SQLLiteral
  62.276 +
  62.277 +def _sqllist(values):
  62.278 +    """
  62.279 +        >>> _sqllist([1, 2, 3])
  62.280 +        <sql: '(1, 2, 3)'>
  62.281 +    """
  62.282 +    items = []
  62.283 +    items.append('(')
  62.284 +    for i, v in enumerate(values):
  62.285 +        if i != 0:
  62.286 +            items.append(', ')
  62.287 +        items.append(sqlparam(v))
  62.288 +    items.append(')')
  62.289 +    return SQLQuery(items)
  62.290 +
  62.291 +def reparam(string_, dictionary): 
  62.292 +    """
  62.293 +    Takes a string and a dictionary and interpolates the string
  62.294 +    using values from the dictionary. Returns an `SQLQuery` for the result.
  62.295 +
  62.296 +        >>> reparam("s = $s", dict(s=True))
  62.297 +        <sql: "s = 't'">
  62.298 +        >>> reparam("s IN $s", dict(s=[1, 2]))
  62.299 +        <sql: 's IN (1, 2)'>
  62.300 +    """
  62.301 +    dictionary = dictionary.copy() # eval mucks with it
  62.302 +    vals = []
  62.303 +    result = []
  62.304 +    for live, chunk in _interpolate(string_):
  62.305 +        if live:
  62.306 +            v = eval(chunk, dictionary)
  62.307 +            result.append(sqlquote(v))
  62.308 +        else: 
  62.309 +            result.append(chunk)
  62.310 +    return SQLQuery.join(result, '')
  62.311 +
  62.312 +def sqlify(obj): 
  62.313 +    """
  62.314 +    converts `obj` to its proper SQL version
  62.315 +
  62.316 +        >>> sqlify(None)
  62.317 +        'NULL'
  62.318 +        >>> sqlify(True)
  62.319 +        "'t'"
  62.320 +        >>> sqlify(3)
  62.321 +        '3'
  62.322 +    """
  62.323 +    # because `1 == True and hash(1) == hash(True)`
  62.324 +    # we have to do this the hard way...
  62.325 +
  62.326 +    if obj is None:
  62.327 +        return 'NULL'
  62.328 +    elif obj is True:
  62.329 +        return "'t'"
  62.330 +    elif obj is False:
  62.331 +        return "'f'"
  62.332 +    elif datetime and isinstance(obj, datetime.datetime):
  62.333 +        return repr(obj.isoformat())
  62.334 +    else:
  62.335 +        if isinstance(obj, unicode): obj = obj.encode('utf8')
  62.336 +        return repr(obj)
  62.337 +
  62.338 +def sqllist(lst): 
  62.339 +    """
  62.340 +    Converts the arguments for use in something like a WHERE clause.
  62.341 +    
  62.342 +        >>> sqllist(['a', 'b'])
  62.343 +        'a, b'
  62.344 +        >>> sqllist('a')
  62.345 +        'a'
  62.346 +        >>> sqllist(u'abc')
  62.347 +        u'abc'
  62.348 +    """
  62.349 +    if isinstance(lst, basestring): 
  62.350 +        return lst
  62.351 +    else:
  62.352 +        return ', '.join(lst)
  62.353 +
  62.354 +def sqlors(left, lst):
  62.355 +    """
  62.356 +    `left is a SQL clause like `tablename.arg = ` 
  62.357 +    and `lst` is a list of values. Returns a reparam-style
  62.358 +    pair featuring the SQL that ORs together the clause
  62.359 +    for each item in the lst.
  62.360 +
  62.361 +        >>> sqlors('foo = ', [])
  62.362 +        <sql: '1=2'>
  62.363 +        >>> sqlors('foo = ', [1])
  62.364 +        <sql: 'foo = 1'>
  62.365 +        >>> sqlors('foo = ', 1)
  62.366 +        <sql: 'foo = 1'>
  62.367 +        >>> sqlors('foo = ', [1,2,3])
  62.368 +        <sql: '(foo = 1 OR foo = 2 OR foo = 3 OR 1=2)'>
  62.369 +    """
  62.370 +    if isinstance(lst, iters):
  62.371 +        lst = list(lst)
  62.372 +        ln = len(lst)
  62.373 +        if ln == 0:
  62.374 +            return SQLQuery("1=2")
  62.375 +        if ln == 1:
  62.376 +            lst = lst[0]
  62.377 +
  62.378 +    if isinstance(lst, iters):
  62.379 +        return SQLQuery(['('] + 
  62.380 +          sum([[left, sqlparam(x), ' OR '] for x in lst], []) +
  62.381 +          ['1=2)']
  62.382 +        )
  62.383 +    else:
  62.384 +        return left + sqlparam(lst)
  62.385 +        
  62.386 +def sqlwhere(dictionary, grouping=' AND '): 
  62.387 +    """
  62.388 +    Converts a `dictionary` to an SQL WHERE clause `SQLQuery`.
  62.389 +    
  62.390 +        >>> sqlwhere({'cust_id': 2, 'order_id':3})
  62.391 +        <sql: 'order_id = 3 AND cust_id = 2'>
  62.392 +        >>> sqlwhere({'cust_id': 2, 'order_id':3}, grouping=', ')
  62.393 +        <sql: 'order_id = 3, cust_id = 2'>
  62.394 +        >>> sqlwhere({'a': 'a', 'b': 'b'}).query()
  62.395 +        'a = %s AND b = %s'
  62.396 +    """
  62.397 +    return SQLQuery.join([k + ' = ' + sqlparam(v) for k, v in dictionary.items()], grouping)
  62.398 +
  62.399 +def sqlquote(a): 
  62.400 +    """
  62.401 +    Ensures `a` is quoted properly for use in a SQL query.
  62.402 +
  62.403 +        >>> 'WHERE x = ' + sqlquote(True) + ' AND y = ' + sqlquote(3)
  62.404 +        <sql: "WHERE x = 't' AND y = 3">
  62.405 +        >>> 'WHERE x = ' + sqlquote(True) + ' AND y IN ' + sqlquote([2, 3])
  62.406 +        <sql: "WHERE x = 't' AND y IN (2, 3)">
  62.407 +    """
  62.408 +    if isinstance(a, list):
  62.409 +        return _sqllist(a)
  62.410 +    else:
  62.411 +        return sqlparam(a).sqlquery()
  62.412 +
  62.413 +class Transaction:
  62.414 +    """Database transaction."""
  62.415 +    def __init__(self, ctx):
  62.416 +        self.ctx = ctx
  62.417 +        self.transaction_count = transaction_count = len(ctx.transactions)
  62.418 +
  62.419 +        class transaction_engine:
  62.420 +            """Transaction Engine used in top level transactions."""
  62.421 +            def do_transact(self):
  62.422 +                ctx.commit(unload=False)
  62.423 +
  62.424 +            def do_commit(self):
  62.425 +                ctx.commit()
  62.426 +
  62.427 +            def do_rollback(self):
  62.428 +                ctx.rollback()
  62.429 +
  62.430 +        class subtransaction_engine:
  62.431 +            """Transaction Engine used in sub transactions."""
  62.432 +            def query(self, q):
  62.433 +                db_cursor = ctx.db.cursor()
  62.434 +                ctx.db_execute(db_cursor, SQLQuery(q % transaction_count))
  62.435 +
  62.436 +            def do_transact(self):
  62.437 +                self.query('SAVEPOINT webpy_sp_%s')
  62.438 +
  62.439 +            def do_commit(self):
  62.440 +                self.query('RELEASE SAVEPOINT webpy_sp_%s')
  62.441 +
  62.442 +            def do_rollback(self):
  62.443 +                self.query('ROLLBACK TO SAVEPOINT webpy_sp_%s')
  62.444 +
  62.445 +        class dummy_engine:
  62.446 +            """Transaction Engine used instead of subtransaction_engine 
  62.447 +            when sub transactions are not supported."""
  62.448 +            do_transact = do_commit = do_rollback = lambda self: None
  62.449 +
  62.450 +        if self.transaction_count:
  62.451 +            # nested transactions are not supported in some databases
  62.452 +            if self.ctx.get('ignore_nested_transactions'):
  62.453 +                self.engine = dummy_engine()
  62.454 +            else:
  62.455 +                self.engine = subtransaction_engine()
  62.456 +        else:
  62.457 +            self.engine = transaction_engine()
  62.458 +
  62.459 +        self.engine.do_transact()
  62.460 +        self.ctx.transactions.append(self)
  62.461 +
  62.462 +    def __enter__(self):
  62.463 +        return self
  62.464 +
  62.465 +    def __exit__(self, exctype, excvalue, traceback):
  62.466 +        if exctype is not None:
  62.467 +            self.rollback()
  62.468 +        else:
  62.469 +            self.commit()
  62.470 +
  62.471 +    def commit(self):
  62.472 +        if len(self.ctx.transactions) > self.transaction_count:
  62.473 +            self.engine.do_commit()
  62.474 +            self.ctx.transactions = self.ctx.transactions[:self.transaction_count]
  62.475 +
  62.476 +    def rollback(self):
  62.477 +        if len(self.ctx.transactions) > self.transaction_count:
  62.478 +            self.engine.do_rollback()
  62.479 +            self.ctx.transactions = self.ctx.transactions[:self.transaction_count]
  62.480 +
  62.481 +class DB: 
  62.482 +    """Database"""
  62.483 +    def __init__(self, db_module, keywords):
  62.484 +        """Creates a database.
  62.485 +        """
  62.486 +        # some DB implementaions take optional paramater `driver` to use a specific driver modue
  62.487 +        # but it should not be passed to connect
  62.488 +        keywords.pop('driver', None)
  62.489 +
  62.490 +        self.db_module = db_module
  62.491 +        self.keywords = keywords
  62.492 +
  62.493 +        self._ctx = threadeddict()
  62.494 +        # flag to enable/disable printing queries
  62.495 +        self.printing = config.get('debug_sql', config.get('debug', False))
  62.496 +        self.supports_multiple_insert = False
  62.497 +        
  62.498 +        try:
  62.499 +            import DBUtils
  62.500 +            # enable pooling if DBUtils module is available.
  62.501 +            self.has_pooling = True
  62.502 +        except ImportError:
  62.503 +            self.has_pooling = False
  62.504 +            
  62.505 +        # Pooling can be disabled by passing pooling=False in the keywords.
  62.506 +        self.has_pooling = self.keywords.pop('pooling', True) and self.has_pooling
  62.507 +            
  62.508 +    def _getctx(self): 
  62.509 +        if not self._ctx.get('db'):
  62.510 +            self._load_context(self._ctx)
  62.511 +        return self._ctx
  62.512 +    ctx = property(_getctx)
  62.513 +    
  62.514 +    def _load_context(self, ctx):
  62.515 +        ctx.dbq_count = 0
  62.516 +        ctx.transactions = [] # stack of transactions
  62.517 +        
  62.518 +        if self.has_pooling:
  62.519 +            ctx.db = self._connect_with_pooling(self.keywords)
  62.520 +        else:
  62.521 +            ctx.db = self._connect(self.keywords)
  62.522 +        ctx.db_execute = self._db_execute
  62.523 +        
  62.524 +        if not hasattr(ctx.db, 'commit'):
  62.525 +            ctx.db.commit = lambda: None
  62.526 +
  62.527 +        if not hasattr(ctx.db, 'rollback'):
  62.528 +            ctx.db.rollback = lambda: None
  62.529 +            
  62.530 +        def commit(unload=True):
  62.531 +            # do db commit and release the connection if pooling is enabled.            
  62.532 +            ctx.db.commit()
  62.533 +            if unload and self.has_pooling:
  62.534 +                self._unload_context(self._ctx)
  62.535 +                
  62.536 +        def rollback():
  62.537 +            # do db rollback and release the connection if pooling is enabled.
  62.538 +            ctx.db.rollback()
  62.539 +            if self.has_pooling:
  62.540 +                self._unload_context(self._ctx)
  62.541 +                
  62.542 +        ctx.commit = commit
  62.543 +        ctx.rollback = rollback
  62.544 +            
  62.545 +    def _unload_context(self, ctx):
  62.546 +        del ctx.db
  62.547 +            
  62.548 +    def _connect(self, keywords):
  62.549 +        return self.db_module.connect(**keywords)
  62.550 +        
  62.551 +    def _connect_with_pooling(self, keywords):
  62.552 +        def get_pooled_db():
  62.553 +            from DBUtils import PooledDB
  62.554 +
  62.555 +            # In DBUtils 0.9.3, `dbapi` argument is renamed as `creator`
  62.556 +            # see Bug#122112
  62.557 +            
  62.558 +            if PooledDB.__version__.split('.') < '0.9.3'.split('.'):
  62.559 +                return PooledDB.PooledDB(dbapi=self.db_module, **keywords)
  62.560 +            else:
  62.561 +                return PooledDB.PooledDB(creator=self.db_module, **keywords)
  62.562 +        
  62.563 +        if getattr(self, '_pooleddb', None) is None:
  62.564 +            self._pooleddb = get_pooled_db()
  62.565 +        
  62.566 +        return self._pooleddb.connection()
  62.567 +        
  62.568 +    def _db_cursor(self):
  62.569 +        return self.ctx.db.cursor()
  62.570 +
  62.571 +    def _param_marker(self):
  62.572 +        """Returns parameter marker based on paramstyle attribute if this database."""
  62.573 +        style = getattr(self, 'paramstyle', 'pyformat')
  62.574 +
  62.575 +        if style == 'qmark':
  62.576 +            return '?'
  62.577 +        elif style == 'numeric':
  62.578 +            return ':1'
  62.579 +        elif style in ['format', 'pyformat']:
  62.580 +            return '%s'
  62.581 +        raise UnknownParamstyle, style
  62.582 +
  62.583 +    def _db_execute(self, cur, sql_query): 
  62.584 +        """executes an sql query"""
  62.585 +        self.ctx.dbq_count += 1
  62.586 +        
  62.587 +        try:
  62.588 +            a = time.time()
  62.589 +            query, params = self._process_query(sql_query)
  62.590 +            out = cur.execute(query, params)
  62.591 +            b = time.time()
  62.592 +        except:
  62.593 +            if self.printing:
  62.594 +                print >> debug, 'ERR:', str(sql_query)
  62.595 +            if self.ctx.transactions:
  62.596 +                self.ctx.transactions[-1].rollback()
  62.597 +            else:
  62.598 +                self.ctx.rollback()
  62.599 +            raise
  62.600 +
  62.601 +        if self.printing:
  62.602 +            print >> debug, '%s (%s): %s' % (round(b-a, 2), self.ctx.dbq_count, str(sql_query))
  62.603 +        return out
  62.604 +
  62.605 +    def _process_query(self, sql_query):
  62.606 +        """Takes the SQLQuery object and returns query string and parameters.
  62.607 +        """
  62.608 +        paramstyle = getattr(self, 'paramstyle', 'pyformat')
  62.609 +        query = sql_query.query(paramstyle)
  62.610 +        params = sql_query.values()
  62.611 +        return query, params
  62.612 +    
  62.613 +    def _where(self, where, vars): 
  62.614 +        if isinstance(where, (int, long)):
  62.615 +            where = "id = " + sqlparam(where)
  62.616 +        #@@@ for backward-compatibility
  62.617 +        elif isinstance(where, (list, tuple)) and len(where) == 2:
  62.618 +            where = SQLQuery(where[0], where[1])
  62.619 +        elif isinstance(where, SQLQuery):
  62.620 +            pass
  62.621 +        else:
  62.622 +            where = reparam(where, vars)        
  62.623 +        return where
  62.624 +    
  62.625 +    def query(self, sql_query, vars=None, processed=False, _test=False): 
  62.626 +        """
  62.627 +        Execute SQL query `sql_query` using dictionary `vars` to interpolate it.
  62.628 +        If `processed=True`, `vars` is a `reparam`-style list to use 
  62.629 +        instead of interpolating.
  62.630 +        
  62.631 +            >>> db = DB(None, {})
  62.632 +            >>> db.query("SELECT * FROM foo", _test=True)
  62.633 +            <sql: 'SELECT * FROM foo'>
  62.634 +            >>> db.query("SELECT * FROM foo WHERE x = $x", vars=dict(x='f'), _test=True)
  62.635 +            <sql: "SELECT * FROM foo WHERE x = 'f'">
  62.636 +            >>> db.query("SELECT * FROM foo WHERE x = " + sqlquote('f'), _test=True)
  62.637 +            <sql: "SELECT * FROM foo WHERE x = 'f'">
  62.638 +        """
  62.639 +        if vars is None: vars = {}
  62.640 +        
  62.641 +        if not processed and not isinstance(sql_query, SQLQuery):
  62.642 +            sql_query = reparam(sql_query, vars)
  62.643 +        
  62.644 +        if _test: return sql_query
  62.645 +        
  62.646 +        db_cursor = self._db_cursor()
  62.647 +        self._db_execute(db_cursor, sql_query)
  62.648 +        
  62.649 +        if db_cursor.description:
  62.650 +            names = [x[0] for x in db_cursor.description]
  62.651 +            def iterwrapper():
  62.652 +                row = db_cursor.fetchone()
  62.653 +                while row:
  62.654 +                    yield storage(dict(zip(names, row)))
  62.655 +                    row = db_cursor.fetchone()
  62.656 +            out = iterbetter(iterwrapper())
  62.657 +            out.__len__ = lambda: int(db_cursor.rowcount)
  62.658 +            out.list = lambda: [storage(dict(zip(names, x))) \
  62.659 +                               for x in db_cursor.fetchall()]
  62.660 +        else:
  62.661 +            out = db_cursor.rowcount
  62.662 +        
  62.663 +        if not self.ctx.transactions: 
  62.664 +            self.ctx.commit()
  62.665 +        return out
  62.666 +    
  62.667 +    def select(self, tables, vars=None, what='*', where=None, order=None, group=None, 
  62.668 +               limit=None, offset=None, _test=False): 
  62.669 +        """
  62.670 +        Selects `what` from `tables` with clauses `where`, `order`, 
  62.671 +        `group`, `limit`, and `offset`. Uses vars to interpolate. 
  62.672 +        Otherwise, each clause can be a SQLQuery.
  62.673 +        
  62.674 +            >>> db = DB(None, {})
  62.675 +            >>> db.select('foo', _test=True)
  62.676 +            <sql: 'SELECT * FROM foo'>
  62.677 +            >>> db.select(['foo', 'bar'], where="foo.bar_id = bar.id", limit=5, _test=True)
  62.678 +            <sql: 'SELECT * FROM foo, bar WHERE foo.bar_id = bar.id LIMIT 5'>
  62.679 +        """
  62.680 +        if vars is None: vars = {}
  62.681 +        sql_clauses = self.sql_clauses(what, tables, where, group, order, limit, offset)
  62.682 +        clauses = [self.gen_clause(sql, val, vars) for sql, val in sql_clauses if val is not None]
  62.683 +        qout = SQLQuery.join(clauses)
  62.684 +        if _test: return qout
  62.685 +        return self.query(qout, processed=True)
  62.686 +    
  62.687 +    def where(self, table, what='*', order=None, group=None, limit=None, 
  62.688 +              offset=None, _test=False, **kwargs):
  62.689 +        """
  62.690 +        Selects from `table` where keys are equal to values in `kwargs`.
  62.691 +        
  62.692 +            >>> db = DB(None, {})
  62.693 +            >>> db.where('foo', bar_id=3, _test=True)
  62.694 +            <sql: 'SELECT * FROM foo WHERE bar_id = 3'>
  62.695 +            >>> db.where('foo', source=2, crust='dewey', _test=True)
  62.696 +            <sql: "SELECT * FROM foo WHERE source = 2 AND crust = 'dewey'">
  62.697 +            >>> db.where('foo', _test=True)
  62.698 +            <sql: 'SELECT * FROM foo'>
  62.699 +        """
  62.700 +        where_clauses = []
  62.701 +        for k, v in kwargs.iteritems():
  62.702 +            where_clauses.append(k + ' = ' + sqlquote(v))
  62.703 +            
  62.704 +        if where_clauses:
  62.705 +            where = SQLQuery.join(where_clauses, " AND ")
  62.706 +        else:
  62.707 +            where = None
  62.708 +            
  62.709 +        return self.select(table, what=what, order=order, 
  62.710 +               group=group, limit=limit, offset=offset, _test=_test, 
  62.711 +               where=where)
  62.712 +    
  62.713 +    def sql_clauses(self, what, tables, where, group, order, limit, offset): 
  62.714 +        return (
  62.715 +            ('SELECT', what),
  62.716 +            ('FROM', sqllist(tables)),
  62.717 +            ('WHERE', where),
  62.718 +            ('GROUP BY', group),
  62.719 +            ('ORDER BY', order),
  62.720 +            ('LIMIT', limit),
  62.721 +            ('OFFSET', offset))
  62.722 +    
  62.723 +    def gen_clause(self, sql, val, vars): 
  62.724 +        if isinstance(val, (int, long)):
  62.725 +            if sql == 'WHERE':
  62.726 +                nout = 'id = ' + sqlquote(val)
  62.727 +            else:
  62.728 +                nout = SQLQuery(val)
  62.729 +        #@@@
  62.730 +        elif isinstance(val, (list, tuple)) and len(val) == 2:
  62.731 +            nout = SQLQuery(val[0], val[1]) # backwards-compatibility
  62.732 +        elif isinstance(val, SQLQuery):
  62.733 +            nout = val
  62.734 +        else:
  62.735 +            nout = reparam(val, vars)
  62.736 +
  62.737 +        def xjoin(a, b):
  62.738 +            if a and b: return a + ' ' + b
  62.739 +            else: return a or b
  62.740 +
  62.741 +        return xjoin(sql, nout)
  62.742 +
  62.743 +    def insert(self, tablename, seqname=None, _test=False, **values): 
  62.744 +        """
  62.745 +        Inserts `values` into `tablename`. Returns current sequence ID.
  62.746 +        Set `seqname` to the ID if it's not the default, or to `False`
  62.747 +        if there isn't one.
  62.748 +        
  62.749 +            >>> db = DB(None, {})
  62.750 +            >>> q = db.insert('foo', name='bob', age=2, created=SQLLiteral('NOW()'), _test=True)
  62.751 +            >>> q
  62.752 +            <sql: "INSERT INTO foo (age, name, created) VALUES (2, 'bob', NOW())">
  62.753 +            >>> q.query()
  62.754 +            'INSERT INTO foo (age, name, created) VALUES (%s, %s, NOW())'
  62.755 +            >>> q.values()
  62.756 +            [2, 'bob']
  62.757 +        """
  62.758 +        def q(x): return "(" + x + ")"
  62.759 +        
  62.760 +        if values:
  62.761 +            _keys = SQLQuery.join(values.keys(), ', ')
  62.762 +            _values = SQLQuery.join([sqlparam(v) for v in values.values()], ', ')
  62.763 +            sql_query = "INSERT INTO %s " % tablename + q(_keys) + ' VALUES ' + q(_values)
  62.764 +        else:
  62.765 +            sql_query = SQLQuery(self._get_insert_default_values_query(tablename))
  62.766 +
  62.767 +        if _test: return sql_query
  62.768 +        
  62.769 +        db_cursor = self._db_cursor()
  62.770 +        if seqname is not False: 
  62.771 +            sql_query = self._process_insert_query(sql_query, tablename, seqname)
  62.772 +
  62.773 +        if isinstance(sql_query, tuple):
  62.774 +            # for some databases, a separate query has to be made to find 
  62.775 +            # the id of the inserted row.
  62.776 +            q1, q2 = sql_query
  62.777 +            self._db_execute(db_cursor, q1)
  62.778 +            self._db_execute(db_cursor, q2)
  62.779 +        else:
  62.780 +            self._db_execute(db_cursor, sql_query)
  62.781 +
  62.782 +        try: 
  62.783 +            out = db_cursor.fetchone()[0]
  62.784 +        except Exception: 
  62.785 +            out = None
  62.786 +        
  62.787 +        if not self.ctx.transactions: 
  62.788 +            self.ctx.commit()
  62.789 +        return out
  62.790 +        
  62.791 +    def _get_insert_default_values_query(self, table):
  62.792 +        return "INSERT INTO %s DEFAULT VALUES" % table
  62.793 +
  62.794 +    def multiple_insert(self, tablename, values, seqname=None, _test=False):
  62.795 +        """
  62.796 +        Inserts multiple rows into `tablename`. The `values` must be a list of dictioanries, 
  62.797 +        one for each row to be inserted, each with the same set of keys.
  62.798 +        Returns the list of ids of the inserted rows.        
  62.799 +        Set `seqname` to the ID if it's not the default, or to `False`
  62.800 +        if there isn't one.
  62.801 +        
  62.802 +            >>> db = DB(None, {})
  62.803 +            >>> db.supports_multiple_insert = True
  62.804 +            >>> values = [{"name": "foo", "email": "foo@example.com"}, {"name": "bar", "email": "bar@example.com"}]
  62.805 +            >>> db.multiple_insert('person', values=values, _test=True)
  62.806 +            <sql: "INSERT INTO person (name, email) VALUES ('foo', 'foo@example.com'), ('bar', 'bar@example.com')">
  62.807 +        """        
  62.808 +        if not values:
  62.809 +            return []
  62.810 +            
  62.811 +        if not self.supports_multiple_insert:
  62.812 +            out = [self.insert(tablename, seqname=seqname, _test=_test, **v) for v in values]
  62.813 +            if seqname is False:
  62.814 +                return None
  62.815 +            else:
  62.816 +                return out
  62.817 +                
  62.818 +        keys = values[0].keys()
  62.819 +        #@@ make sure all keys are valid
  62.820 +
  62.821 +        # make sure all rows have same keys.
  62.822 +        for v in values:
  62.823 +            if v.keys() != keys:
  62.824 +                raise ValueError, 'Bad data'
  62.825 +
  62.826 +        sql_query = SQLQuery('INSERT INTO %s (%s) VALUES ' % (tablename, ', '.join(keys)))
  62.827 +
  62.828 +        for i, row in enumerate(values):
  62.829 +            if i != 0:
  62.830 +                sql_query.append(", ")
  62.831 +            SQLQuery.join([SQLParam(row[k]) for k in keys], sep=", ", target=sql_query, prefix="(", suffix=")")
  62.832 +        
  62.833 +        if _test: return sql_query
  62.834 +
  62.835 +        db_cursor = self._db_cursor()
  62.836 +        if seqname is not False: 
  62.837 +            sql_query = self._process_insert_query(sql_query, tablename, seqname)
  62.838 +
  62.839 +        if isinstance(sql_query, tuple):
  62.840 +            # for some databases, a separate query has to be made to find 
  62.841 +            # the id of the inserted row.
  62.842 +            q1, q2 = sql_query
  62.843 +            self._db_execute(db_cursor, q1)
  62.844 +            self._db_execute(db_cursor, q2)
  62.845 +        else:
  62.846 +            self._db_execute(db_cursor, sql_query)
  62.847 +
  62.848 +        try: 
  62.849 +            out = db_cursor.fetchone()[0]
  62.850 +            out = range(out-len(values)+1, out+1)        
  62.851 +        except Exception: 
  62.852 +            out = None
  62.853 +
  62.854 +        if not self.ctx.transactions: 
  62.855 +            self.ctx.commit()
  62.856 +        return out
  62.857 +
  62.858 +    
  62.859 +    def update(self, tables, where, vars=None, _test=False, **values): 
  62.860 +        """
  62.861 +        Update `tables` with clause `where` (interpolated using `vars`)
  62.862 +        and setting `values`.
  62.863 +
  62.864 +            >>> db = DB(None, {})
  62.865 +            >>> name = 'Joseph'
  62.866 +            >>> q = db.update('foo', where='name = $name', name='bob', age=2,
  62.867 +            ...     created=SQLLiteral('NOW()'), vars=locals(), _test=True)
  62.868 +            >>> q
  62.869 +            <sql: "UPDATE foo SET age = 2, name = 'bob', created = NOW() WHERE name = 'Joseph'">
  62.870 +            >>> q.query()
  62.871 +            'UPDATE foo SET age = %s, name = %s, created = NOW() WHERE name = %s'
  62.872 +            >>> q.values()
  62.873 +            [2, 'bob', 'Joseph']
  62.874 +        """
  62.875 +        if vars is None: vars = {}
  62.876 +        where = self._where(where, vars)
  62.877 +
  62.878 +        query = (
  62.879 +          "UPDATE " + sqllist(tables) + 
  62.880 +          " SET " + sqlwhere(values, ', ') + 
  62.881 +          " WHERE " + where)
  62.882 +
  62.883 +        if _test: return query
  62.884 +        
  62.885 +        db_cursor = self._db_cursor()
  62.886 +        self._db_execute(db_cursor, query)
  62.887 +        if not self.ctx.transactions: 
  62.888 +            self.ctx.commit()
  62.889 +        return db_cursor.rowcount
  62.890 +    
  62.891 +    def delete(self, table, where, using=None, vars=None, _test=False): 
  62.892 +        """
  62.893 +        Deletes from `table` with clauses `where` and `using`.
  62.894 +
  62.895 +            >>> db = DB(None, {})
  62.896 +            >>> name = 'Joe'
  62.897 +            >>> db.delete('foo', where='name = $name', vars=locals(), _test=True)
  62.898 +            <sql: "DELETE FROM foo WHERE name = 'Joe'">
  62.899 +        """
  62.900 +        if vars is None: vars = {}
  62.901 +        where = self._where(where, vars)
  62.902 +
  62.903 +        q = 'DELETE FROM ' + table
  62.904 +        if using: q += ' USING ' + sqllist(using)
  62.905 +        if where: q += ' WHERE ' + where
  62.906 +
  62.907 +        if _test: return q
  62.908 +
  62.909 +        db_cursor = self._db_cursor()
  62.910 +        self._db_execute(db_cursor, q)
  62.911 +        if not self.ctx.transactions: 
  62.912 +            self.ctx.commit()
  62.913 +        return db_cursor.rowcount
  62.914 +
  62.915 +    def _process_insert_query(self, query, tablename, seqname):
  62.916 +        return query
  62.917 +
  62.918 +    def transaction(self): 
  62.919 +        """Start a transaction."""
  62.920 +        return Transaction(self.ctx)
  62.921 +    
  62.922 +class PostgresDB(DB): 
  62.923 +    """Postgres driver."""
  62.924 +    def __init__(self, **keywords):
  62.925 +        if 'pw' in keywords:
  62.926 +            keywords['password'] = keywords.pop('pw')
  62.927 +            
  62.928 +        db_module = import_driver(["psycopg2", "psycopg", "pgdb"], preferred=keywords.pop('driver', None))
  62.929 +        if db_module.__name__ == "psycopg2":
  62.930 +            import psycopg2.extensions
  62.931 +            psycopg2.extensions.register_type(psycopg2.extensions.UNICODE)
  62.932 +
  62.933 +        # if db is not provided postgres driver will take it from PGDATABASE environment variable
  62.934 +        if 'db' in keywords:
  62.935 +            keywords['database'] = keywords.pop('db')
  62.936 +        
  62.937 +        self.dbname = "postgres"
  62.938 +        self.paramstyle = db_module.paramstyle
  62.939 +        DB.__init__(self, db_module, keywords)
  62.940 +        self.supports_multiple_insert = True
  62.941 +        self._sequences = None
  62.942 +        
  62.943 +    def _process_insert_query(self, query, tablename, seqname):
  62.944 +        if seqname is None:
  62.945 +            # when seqname is not provided guess the seqname and make sure it exists
  62.946 +            seqname = tablename + "_id_seq"
  62.947 +            if seqname not in self._get_all_sequences():
  62.948 +                seqname = None
  62.949 +        
  62.950 +        if seqname:
  62.951 +            query += "; SELECT currval('%s')" % seqname
  62.952 +            
  62.953 +        return query
  62.954 +    
  62.955 +    def _get_all_sequences(self):
  62.956 +        """Query postgres to find names of all sequences used in this database."""
  62.957 +        if self._sequences is None:
  62.958 +            q = "SELECT c.relname FROM pg_class c WHERE c.relkind = 'S'"
  62.959 +            self._sequences = set([c.relname for c in self.query(q)])
  62.960 +        return self._sequences
  62.961 +
  62.962 +    def _connect(self, keywords):
  62.963 +        conn = DB._connect(self, keywords)
  62.964 +        try:
  62.965 +            conn.set_client_encoding('UTF8')
  62.966 +        except AttributeError:
  62.967 +            # fallback for pgdb driver
  62.968 +            conn.cursor().execute("set client_encoding to 'UTF-8'")
  62.969 +        return conn
  62.970 +        
  62.971 +    def _connect_with_pooling(self, keywords):
  62.972 +        conn = DB._connect_with_pooling(self, keywords)
  62.973 +        conn._con._con.set_client_encoding('UTF8')
  62.974 +        return conn
  62.975 +
  62.976 +class MySQLDB(DB): 
  62.977 +    def __init__(self, **keywords):
  62.978 +        import MySQLdb as db
  62.979 +        if 'pw' in keywords:
  62.980 +            keywords['passwd'] = keywords['pw']
  62.981 +            del keywords['pw']
  62.982 +
  62.983 +        if 'charset' not in keywords:
  62.984 +            keywords['charset'] = 'utf8'
  62.985 +        elif keywords['charset'] is None:
  62.986 +            del keywords['charset']
  62.987 +
  62.988 +        self.paramstyle = db.paramstyle = 'pyformat' # it's both, like psycopg
  62.989 +        self.dbname = "mysql"
  62.990 +        DB.__init__(self, db, keywords)
  62.991 +        self.supports_multiple_insert = True
  62.992 +        
  62.993 +    def _process_insert_query(self, query, tablename, seqname):
  62.994 +        return query, SQLQuery('SELECT last_insert_id();')
  62.995 +        
  62.996 +    def _get_insert_default_values_query(self, table):
  62.997 +        return "INSERT INTO %s () VALUES()" % table
  62.998 +
  62.999 +def import_driver(drivers, preferred=None):
 62.1000 +    """Import the first available driver or preferred driver.
 62.1001 +    """
 62.1002 +    if preferred:
 62.1003 +        drivers = [preferred]
 62.1004 +
 62.1005 +    for d in drivers:
 62.1006 +        try:
 62.1007 +            return __import__(d, None, None, ['x'])
 62.1008 +        except ImportError:
 62.1009 +            pass
 62.1010 +    raise ImportError("Unable to import " + " or ".join(drivers))
 62.1011 +
 62.1012 +class SqliteDB(DB): 
 62.1013 +    def __init__(self, **keywords):
 62.1014 +        db = import_driver(["sqlite3", "pysqlite2.dbapi2", "sqlite"], preferred=keywords.pop('driver', None))
 62.1015 +
 62.1016 +        if db.__name__ in ["sqlite3", "pysqlite2.dbapi2"]:
 62.1017 +            db.paramstyle = 'qmark'
 62.1018 +            
 62.1019 +        # sqlite driver doesn't create datatime objects for timestamp columns unless `detect_types` option is passed.
 62.1020 +        # It seems to be supported in sqlite3 and pysqlite2 drivers, not surte about sqlite.
 62.1021 +        keywords.setdefault('detect_types', db.PARSE_DECLTYPES)
 62.1022 +
 62.1023 +        self.paramstyle = db.paramstyle
 62.1024 +        keywords['database'] = keywords.pop('db')
 62.1025 +        keywords['pooling'] = False # sqlite don't allows connections to be shared by threads
 62.1026 +        self.dbname = "sqlite"        
 62.1027 +        DB.__init__(self, db, keywords)
 62.1028 +
 62.1029 +    def _process_insert_query(self, query, tablename, seqname):
 62.1030 +        return query, SQLQuery('SELECT last_insert_rowid();')
 62.1031 +    
 62.1032 +    def query(self, *a, **kw):
 62.1033 +        out = DB.query(self, *a, **kw)
 62.1034 +        if isinstance(out, iterbetter):
 62.1035 +            del out.__len__
 62.1036 +        return out
 62.1037 +
 62.1038 +class FirebirdDB(DB):
 62.1039 +    """Firebird Database.
 62.1040 +    """
 62.1041 +    def __init__(self, **keywords):
 62.1042 +        try:
 62.1043 +            import kinterbasdb as db
 62.1044 +        except Exception:
 62.1045 +            db = None
 62.1046 +            pass
 62.1047 +        if 'pw' in keywords:
 62.1048 +            keywords['passwd'] = keywords['pw']
 62.1049 +            del keywords['pw']
 62.1050 +        keywords['database'] = keywords['db']
 62.1051 +        del keywords['db']
 62.1052 +        DB.__init__(self, db, keywords)
 62.1053 +        
 62.1054 +    def delete(self, table, where=None, using=None, vars=None, _test=False):
 62.1055 +        # firebird doesn't support using clause
 62.1056 +        using=None
 62.1057 +        return DB.delete(self, table, where, using, vars, _test)
 62.1058 +
 62.1059 +    def sql_clauses(self, what, tables, where, group, order, limit, offset):
 62.1060 +        return (
 62.1061 +            ('SELECT', ''),
 62.1062 +            ('FIRST', limit),
 62.1063 +            ('SKIP', offset),
 62.1064 +            ('', what),
 62.1065 +            ('FROM', sqllist(tables)),
 62.1066 +            ('WHERE', where),
 62.1067 +            ('GROUP BY', group),
 62.1068 +            ('ORDER BY', order)
 62.1069 +        )
 62.1070 +
 62.1071 +class MSSQLDB(DB):
 62.1072 +    def __init__(self, **keywords):
 62.1073 +        import pymssql as db    
 62.1074 +        if 'pw' in keywords:
 62.1075 +            keywords['password'] = keywords.pop('pw')
 62.1076 +        keywords['database'] = keywords.pop('db')
 62.1077 +        self.dbname = "mssql"
 62.1078 +        DB.__init__(self, db, keywords)
 62.1079 +
 62.1080 +    def _process_query(self, sql_query):
 62.1081 +        """Takes the SQLQuery object and returns query string and parameters.
 62.1082 +        """
 62.1083 +        # MSSQLDB expects params to be a tuple. 
 62.1084 +        # Overwriting the default implementation to convert params to tuple.
 62.1085 +        paramstyle = getattr(self, 'paramstyle', 'pyformat')
 62.1086 +        query = sql_query.query(paramstyle)
 62.1087 +        params = sql_query.values()
 62.1088 +        return query, tuple(params)
 62.1089 +
 62.1090 +    def sql_clauses(self, what, tables, where, group, order, limit, offset): 
 62.1091 +        return (
 62.1092 +            ('SELECT', what),
 62.1093 +            ('TOP', limit),
 62.1094 +            ('FROM', sqllist(tables)),
 62.1095 +            ('WHERE', where),
 62.1096 +            ('GROUP BY', group),
 62.1097 +            ('ORDER BY', order),
 62.1098 +            ('OFFSET', offset))
 62.1099 +            
 62.1100 +    def _test(self):
 62.1101 +        """Test LIMIT.
 62.1102 +
 62.1103 +            Fake presence of pymssql module for running tests.
 62.1104 +            >>> import sys
 62.1105 +            >>> sys.modules['pymssql'] = sys.modules['sys']
 62.1106 +            
 62.1107 +            MSSQL has TOP clause instead of LIMIT clause.
 62.1108 +            >>> db = MSSQLDB(db='test', user='joe', pw='secret')
 62.1109 +            >>> db.select('foo', limit=4, _test=True)
 62.1110 +            <sql: 'SELECT * TOP 4 FROM foo'>
 62.1111 +        """
 62.1112 +        pass
 62.1113 +
 62.1114 +class OracleDB(DB): 
 62.1115 +    def __init__(self, **keywords): 
 62.1116 +        import cx_Oracle as db 
 62.1117 +        if 'pw' in keywords: 
 62.1118 +            keywords['password'] = keywords.pop('pw') 
 62.1119 +
 62.1120 +        #@@ TODO: use db.makedsn if host, port is specified 
 62.1121 +        keywords['dsn'] = keywords.pop('db') 
 62.1122 +        self.dbname = 'oracle' 
 62.1123 +        db.paramstyle = 'numeric' 
 62.1124 +        self.paramstyle = db.paramstyle
 62.1125 +
 62.1126 +        # oracle doesn't support pooling 
 62.1127 +        keywords.pop('pooling', None) 
 62.1128 +        DB.__init__(self, db, keywords) 
 62.1129 +
 62.1130 +    def _process_insert_query(self, query, tablename, seqname): 
 62.1131 +        if seqname is None: 
 62.1132 +            # It is not possible to get seq name from table name in Oracle
 62.1133 +            return query
 62.1134 +        else:
 62.1135 +            return query + "; SELECT %s.currval FROM dual" % seqname 
 62.1136 +
 62.1137 +_databases = {}
 62.1138 +def database(dburl=None, **params):
 62.1139 +    """Creates appropriate database using params.
 62.1140 +    
 62.1141 +    Pooling will be enabled if DBUtils module is available. 
 62.1142 +    Pooling can be disabled by passing pooling=False in params.
 62.1143 +    """
 62.1144 +    dbn = params.pop('dbn')
 62.1145 +    if dbn in _databases:
 62.1146 +        return _databases[dbn](**params)
 62.1147 +    else:
 62.1148 +        raise UnknownDB, dbn
 62.1149 +
 62.1150 +def register_database(name, clazz):
 62.1151 +    """
 62.1152 +    Register a database.
 62.1153 +
 62.1154 +        >>> class LegacyDB(DB): 
 62.1155 +        ...     def __init__(self, **params): 
 62.1156 +        ...        pass 
 62.1157 +        ...
 62.1158 +        >>> register_database('legacy', LegacyDB)
 62.1159 +        >>> db = database(dbn='legacy', db='test', user='joe', passwd='secret') 
 62.1160 +    """
 62.1161 +    _databases[name] = clazz
 62.1162 +
 62.1163 +register_database('mysql', MySQLDB)
 62.1164 +register_database('postgres', PostgresDB)
 62.1165 +register_database('sqlite', SqliteDB)
 62.1166 +register_database('firebird', FirebirdDB)
 62.1167 +register_database('mssql', MSSQLDB)
 62.1168 +register_database('oracle', OracleDB)
 62.1169 +
 62.1170 +def _interpolate(format): 
 62.1171 +    """
 62.1172 +    Takes a format string and returns a list of 2-tuples of the form
 62.1173 +    (boolean, string) where boolean says whether string should be evaled
 62.1174 +    or not.
 62.1175 +
 62.1176 +    from <http://lfw.org/python/Itpl.py> (public domain, Ka-Ping Yee)
 62.1177 +    """
 62.1178 +    from tokenize import tokenprog
 62.1179 +
 62.1180 +    def matchorfail(text, pos):
 62.1181 +        match = tokenprog.match(text, pos)
 62.1182 +        if match is None:
 62.1183 +            raise _ItplError(text, pos)
 62.1184 +        return match, match.end()
 62.1185 +
 62.1186 +    namechars = "abcdefghijklmnopqrstuvwxyz" \
 62.1187 +        "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789_";
 62.1188 +    chunks = []
 62.1189 +    pos = 0
 62.1190 +
 62.1191 +    while 1:
 62.1192 +        dollar = format.find("$", pos)
 62.1193 +        if dollar < 0: 
 62.1194 +            break
 62.1195 +        nextchar = format[dollar + 1]
 62.1196 +
 62.1197 +        if nextchar == "{":
 62.1198 +            chunks.append((0, format[pos:dollar]))
 62.1199 +            pos, level = dollar + 2, 1
 62.1200 +            while level:
 62.1201 +                match, pos = matchorfail(format, pos)
 62.1202 +                tstart, tend = match.regs[3]
 62.1203 +                token = format[tstart:tend]
 62.1204 +                if token == "{": 
 62.1205 +                    level = level + 1
 62.1206 +                elif token == "}":  
 62.1207 +                    level = level - 1
 62.1208 +            chunks.append((1, format[dollar + 2:pos - 1]))
 62.1209 +
 62.1210 +        elif nextchar in namechars:
 62.1211 +            chunks.append((0, format[pos:dollar]))
 62.1212 +            match, pos = matchorfail(format, dollar + 1)
 62.1213 +            while pos < len(format):
 62.1214 +                if format[pos] == "." and \
 62.1215 +                    pos + 1 < len(format) and format[pos + 1] in namechars:
 62.1216 +                    match, pos = matchorfail(format, pos + 1)
 62.1217 +                elif format[pos] in "([":
 62.1218 +                    pos, level = pos + 1, 1
 62.1219 +                    while level:
 62.1220 +                        match, pos = matchorfail(format, pos)
 62.1221 +                        tstart, tend = match.regs[3]
 62.1222 +                        token = format[tstart:tend]
 62.1223 +                        if token[0] in "([": 
 62.1224 +                            level = level + 1
 62.1225 +                        elif token[0] in ")]":  
 62.1226 +                            level = level - 1
 62.1227 +                else: 
 62.1228 +                    break
 62.1229 +            chunks.append((1, format[dollar + 1:pos]))
 62.1230 +        else:
 62.1231 +            chunks.append((0, format[pos:dollar + 1]))
 62.1232 +            pos = dollar + 1 + (nextchar == "$")
 62.1233 +
 62.1234 +    if pos < len(format): 
 62.1235 +        chunks.append((0, format[pos:]))
 62.1236 +    return chunks
 62.1237 +
 62.1238 +if __name__ == "__main__":
 62.1239 +    import doctest
 62.1240 +    doctest.testmod()
    63.1 Binary file OpenSecurity/install/web.py-0.37/web/db.pyc has changed
    64.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    64.2 +++ b/OpenSecurity/install/web.py-0.37/web/debugerror.py	Mon Dec 02 14:02:05 2013 +0100
    64.3 @@ -0,0 +1,354 @@
    64.4 +"""
    64.5 +pretty debug errors
    64.6 +(part of web.py)
    64.7 +
    64.8 +portions adapted from Django <djangoproject.com> 
    64.9 +Copyright (c) 2005, the Lawrence Journal-World
   64.10 +Used under the modified BSD license:
   64.11 +http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
   64.12 +"""
   64.13 +
   64.14 +__all__ = ["debugerror", "djangoerror", "emailerrors"]
   64.15 +
   64.16 +import sys, urlparse, pprint, traceback
   64.17 +from template import Template
   64.18 +from net import websafe
   64.19 +from utils import sendmail, safestr
   64.20 +import webapi as web
   64.21 +
   64.22 +import os, os.path
   64.23 +whereami = os.path.join(os.getcwd(), __file__)
   64.24 +whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1])
   64.25 +djangoerror_t = """\
   64.26 +$def with (exception_type, exception_value, frames)
   64.27 +<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
   64.28 +<html lang="en">
   64.29 +<head>
   64.30 +  <meta http-equiv="content-type" content="text/html; charset=utf-8" />
   64.31 +  <meta name="robots" content="NONE,NOARCHIVE" />
   64.32 +  <title>$exception_type at $ctx.path</title>
   64.33 +  <style type="text/css">
   64.34 +    html * { padding:0; margin:0; }
   64.35 +    body * { padding:10px 20px; }
   64.36 +    body * * { padding:0; }
   64.37 +    body { font:small sans-serif; }
   64.38 +    body>div { border-bottom:1px solid #ddd; }
   64.39 +    h1 { font-weight:normal; }
   64.40 +    h2 { margin-bottom:.8em; }
   64.41 +    h2 span { font-size:80%; color:#666; font-weight:normal; }
   64.42 +    h3 { margin:1em 0 .5em 0; }
   64.43 +    h4 { margin:0 0 .5em 0; font-weight: normal; }
   64.44 +    table { 
   64.45 +        border:1px solid #ccc; border-collapse: collapse; background:white; }
   64.46 +    tbody td, tbody th { vertical-align:top; padding:2px 3px; }
   64.47 +    thead th { 
   64.48 +        padding:1px 6px 1px 3px; background:#fefefe; text-align:left; 
   64.49 +        font-weight:normal; font-size:11px; border:1px solid #ddd; }
   64.50 +    tbody th { text-align:right; color:#666; padding-right:.5em; }
   64.51 +    table.vars { margin:5px 0 2px 40px; }
   64.52 +    table.vars td, table.req td { font-family:monospace; }
   64.53 +    table td.code { width:100%;}
   64.54 +    table td.code div { overflow:hidden; }
   64.55 +    table.source th { color:#666; }
   64.56 +    table.source td { 
   64.57 +        font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
   64.58 +    ul.traceback { list-style-type:none; }
   64.59 +    ul.traceback li.frame { margin-bottom:1em; }
   64.60 +    div.context { margin: 10px 0; }
   64.61 +    div.context ol { 
   64.62 +        padding-left:30px; margin:0 10px; list-style-position: inside; }
   64.63 +    div.context ol li { 
   64.64 +        font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
   64.65 +    div.context ol.context-line li { color:black; background-color:#ccc; }
   64.66 +    div.context ol.context-line li span { float: right; }
   64.67 +    div.commands { margin-left: 40px; }
   64.68 +    div.commands a { color:black; text-decoration:none; }
   64.69 +    #summary { background: #ffc; }
   64.70 +    #summary h2 { font-weight: normal; color: #666; }
   64.71 +    #explanation { background:#eee; }
   64.72 +    #template, #template-not-exist { background:#f6f6f6; }
   64.73 +    #template-not-exist ul { margin: 0 0 0 20px; }
   64.74 +    #traceback { background:#eee; }
   64.75 +    #requestinfo { background:#f6f6f6; padding-left:120px; }
   64.76 +    #summary table { border:none; background:transparent; }
   64.77 +    #requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
   64.78 +    #requestinfo h3 { margin-bottom:-1em; }
   64.79 +    .error { background: #ffc; }
   64.80 +    .specific { color:#cc3300; font-weight:bold; }
   64.81 +  </style>
   64.82 +  <script type="text/javascript">
   64.83 +  //<!--
   64.84 +    function getElementsByClassName(oElm, strTagName, strClassName){
   64.85 +        // Written by Jonathan Snook, http://www.snook.ca/jon; 
   64.86 +        // Add-ons by Robert Nyman, http://www.robertnyman.com
   64.87 +        var arrElements = (strTagName == "*" && document.all)? document.all :
   64.88 +        oElm.getElementsByTagName(strTagName);
   64.89 +        var arrReturnElements = new Array();
   64.90 +        strClassName = strClassName.replace(/\-/g, "\\-");
   64.91 +        var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
   64.92 +        var oElement;
   64.93 +        for(var i=0; i<arrElements.length; i++){
   64.94 +            oElement = arrElements[i];
   64.95 +            if(oRegExp.test(oElement.className)){
   64.96 +                arrReturnElements.push(oElement);
   64.97 +            }
   64.98 +        }
   64.99 +        return (arrReturnElements)
  64.100 +    }
  64.101 +    function hideAll(elems) {
  64.102 +      for (var e = 0; e < elems.length; e++) {
  64.103 +        elems[e].style.display = 'none';
  64.104 +      }
  64.105 +    }
  64.106 +    window.onload = function() {
  64.107 +      hideAll(getElementsByClassName(document, 'table', 'vars'));
  64.108 +      hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
  64.109 +      hideAll(getElementsByClassName(document, 'ol', 'post-context'));
  64.110 +    }
  64.111 +    function toggle() {
  64.112 +      for (var i = 0; i < arguments.length; i++) {
  64.113 +        var e = document.getElementById(arguments[i]);
  64.114 +        if (e) {
  64.115 +          e.style.display = e.style.display == 'none' ? 'block' : 'none';
  64.116 +        }
  64.117 +      }
  64.118 +      return false;
  64.119 +    }
  64.120 +    function varToggle(link, id) {
  64.121 +      toggle('v' + id);
  64.122 +      var s = link.getElementsByTagName('span')[0];
  64.123 +      var uarr = String.fromCharCode(0x25b6);
  64.124 +      var darr = String.fromCharCode(0x25bc);
  64.125 +      s.innerHTML = s.innerHTML == uarr ? darr : uarr;
  64.126 +      return false;
  64.127 +    }
  64.128 +    //-->
  64.129 +  </script>
  64.130 +</head>
  64.131 +<body>
  64.132 +
  64.133 +$def dicttable (d, kls='req', id=None):
  64.134 +    $ items = d and d.items() or []
  64.135 +    $items.sort()
  64.136 +    $:dicttable_items(items, kls, id)
  64.137 +        
  64.138 +$def dicttable_items(items, kls='req', id=None):
  64.139 +    $if items:
  64.140 +        <table class="$kls"
  64.141 +        $if id: id="$id"
  64.142 +        ><thead><tr><th>Variable</th><th>Value</th></tr></thead>
  64.143 +        <tbody>
  64.144 +        $for k, v in items:
  64.145 +            <tr><td>$k</td><td class="code"><div>$prettify(v)</div></td></tr>
  64.146 +        </tbody>
  64.147 +        </table>
  64.148 +    $else:
  64.149 +        <p>No data.</p>
  64.150 +
  64.151 +<div id="summary">
  64.152 +  <h1>$exception_type at $ctx.path</h1>
  64.153 +  <h2>$exception_value</h2>
  64.154 +  <table><tr>
  64.155 +    <th>Python</th>
  64.156 +    <td>$frames[0].filename in $frames[0].function, line $frames[0].lineno</td>
  64.157 +  </tr><tr>
  64.158 +    <th>Web</th>
  64.159 +    <td>$ctx.method $ctx.home$ctx.path</td>
  64.160 +  </tr></table>
  64.161 +</div>
  64.162 +<div id="traceback">
  64.163 +<h2>Traceback <span>(innermost first)</span></h2>
  64.164 +<ul class="traceback">
  64.165 +$for frame in frames:
  64.166 +    <li class="frame">
  64.167 +    <code>$frame.filename</code> in <code>$frame.function</code>
  64.168 +    $if frame.context_line is not None:
  64.169 +        <div class="context" id="c$frame.id">
  64.170 +        $if frame.pre_context:
  64.171 +            <ol start="$frame.pre_context_lineno" class="pre-context" id="pre$frame.id">
  64.172 +            $for line in frame.pre_context:
  64.173 +                <li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
  64.174 +            </ol>
  64.175 +            <ol start="$frame.lineno" class="context-line"><li onclick="toggle('pre$frame.id', 'post$frame.id')">$frame.context_line <span>...</span></li></ol>
  64.176 +        $if frame.post_context:
  64.177 +            <ol start='${frame.lineno + 1}' class="post-context" id="post$frame.id">
  64.178 +            $for line in frame.post_context:
  64.179 +                <li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
  64.180 +            </ol>
  64.181 +      </div>
  64.182 +    
  64.183 +    $if frame.vars:
  64.184 +        <div class="commands">
  64.185 +        <a href='#' onclick="return varToggle(this, '$frame.id')"><span>&#x25b6;</span> Local vars</a>
  64.186 +        $# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame))
  64.187 +        </div>
  64.188 +        $:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id)))
  64.189 +      </li>
  64.190 +  </ul>
  64.191 +</div>
  64.192 +
  64.193 +<div id="requestinfo">
  64.194 +$if ctx.output or ctx.headers:
  64.195 +    <h2>Response so far</h2>
  64.196 +    <h3>HEADERS</h3>
  64.197 +    $:dicttable_items(ctx.headers)
  64.198 +
  64.199 +    <h3>BODY</h3>
  64.200 +    <p class="req" style="padding-bottom: 2em"><code>
  64.201 +    $ctx.output
  64.202 +    </code></p>
  64.203 +  
  64.204 +<h2>Request information</h2>
  64.205 +
  64.206 +<h3>INPUT</h3>
  64.207 +$:dicttable(web.input(_unicode=False))
  64.208 +
  64.209 +<h3 id="cookie-info">COOKIES</h3>
  64.210 +$:dicttable(web.cookies())
  64.211 +
  64.212 +<h3 id="meta-info">META</h3>
  64.213 +$ newctx = [(k, v) for (k, v) in ctx.iteritems() if not k.startswith('_') and not isinstance(v, dict)]
  64.214 +$:dicttable(dict(newctx))
  64.215 +
  64.216 +<h3 id="meta-info">ENVIRONMENT</h3>
  64.217 +$:dicttable(ctx.env)
  64.218 +</div>
  64.219 +
  64.220 +<div id="explanation">
  64.221 +  <p>
  64.222 +    You're seeing this error because you have <code>web.config.debug</code>
  64.223 +    set to <code>True</code>. Set that to <code>False</code> if you don't want to see this.
  64.224 +  </p>
  64.225 +</div>
  64.226 +
  64.227 +</body>
  64.228 +</html>
  64.229 +"""
  64.230 +
  64.231 +djangoerror_r = None
  64.232 +
  64.233 +def djangoerror():
  64.234 +    def _get_lines_from_file(filename, lineno, context_lines):
  64.235 +        """
  64.236 +        Returns context_lines before and after lineno from file.
  64.237 +        Returns (pre_context_lineno, pre_context, context_line, post_context).
  64.238 +        """
  64.239 +        try:
  64.240 +            source = open(filename).readlines()
  64.241 +            lower_bound = max(0, lineno - context_lines)
  64.242 +            upper_bound = lineno + context_lines
  64.243 +
  64.244 +            pre_context = \
  64.245 +                [line.strip('\n') for line in source[lower_bound:lineno]]
  64.246 +            context_line = source[lineno].strip('\n')
  64.247 +            post_context = \
  64.248 +                [line.strip('\n') for line in source[lineno + 1:upper_bound]]
  64.249 +
  64.250 +            return lower_bound, pre_context, context_line, post_context
  64.251 +        except (OSError, IOError, IndexError):
  64.252 +            return None, [], None, []    
  64.253 +    
  64.254 +    exception_type, exception_value, tback = sys.exc_info()
  64.255 +    frames = []
  64.256 +    while tback is not None:
  64.257 +        filename = tback.tb_frame.f_code.co_filename
  64.258 +        function = tback.tb_frame.f_code.co_name
  64.259 +        lineno = tback.tb_lineno - 1
  64.260 +
  64.261 +        # hack to get correct line number for templates
  64.262 +        lineno += tback.tb_frame.f_locals.get("__lineoffset__", 0)
  64.263 +        
  64.264 +        pre_context_lineno, pre_context, context_line, post_context = \
  64.265 +            _get_lines_from_file(filename, lineno, 7)
  64.266 +
  64.267 +        if '__hidetraceback__' not in tback.tb_frame.f_locals:
  64.268 +            frames.append(web.storage({
  64.269 +                'tback': tback,
  64.270 +                'filename': filename,
  64.271 +                'function': function,
  64.272 +                'lineno': lineno,
  64.273 +                'vars': tback.tb_frame.f_locals,
  64.274 +                'id': id(tback),
  64.275 +                'pre_context': pre_context,
  64.276 +                'context_line': context_line,
  64.277 +                'post_context': post_context,
  64.278 +                'pre_context_lineno': pre_context_lineno,
  64.279 +            }))
  64.280 +        tback = tback.tb_next
  64.281 +    frames.reverse()
  64.282 +    urljoin = urlparse.urljoin
  64.283 +    def prettify(x):
  64.284 +        try: 
  64.285 +            out = pprint.pformat(x)
  64.286 +        except Exception, e: 
  64.287 +            out = '[could not display: <' + e.__class__.__name__ + \
  64.288 +                  ': '+str(e)+'>]'
  64.289 +        return out
  64.290 +        
  64.291 +    global djangoerror_r
  64.292 +    if djangoerror_r is None:
  64.293 +        djangoerror_r = Template(djangoerror_t, filename=__file__, filter=websafe)
  64.294 +        
  64.295 +    t = djangoerror_r
  64.296 +    globals = {'ctx': web.ctx, 'web':web, 'dict':dict, 'str':str, 'prettify': prettify}
  64.297 +    t.t.func_globals.update(globals)
  64.298 +    return t(exception_type, exception_value, frames)
  64.299 +
  64.300 +def debugerror():
  64.301 +    """
  64.302 +    A replacement for `internalerror` that presents a nice page with lots
  64.303 +    of debug information for the programmer.
  64.304 +
  64.305 +    (Based on the beautiful 500 page from [Django](http://djangoproject.com/), 
  64.306 +    designed by [Wilson Miner](http://wilsonminer.com/).)
  64.307 +    """
  64.308 +    return web._InternalError(djangoerror())
  64.309 +
  64.310 +def emailerrors(to_address, olderror, from_address=None):
  64.311 +    """
  64.312 +    Wraps the old `internalerror` handler (pass as `olderror`) to 
  64.313 +    additionally email all errors to `to_address`, to aid in
  64.314 +    debugging production websites.
  64.315 +    
  64.316 +    Emails contain a normal text traceback as well as an
  64.317 +    attachment containing the nice `debugerror` page.
  64.318 +    """
  64.319 +    from_address = from_address or to_address
  64.320 +
  64.321 +    def emailerrors_internal():
  64.322 +        error = olderror()
  64.323 +        tb = sys.exc_info()
  64.324 +        error_name = tb[0]
  64.325 +        error_value = tb[1]
  64.326 +        tb_txt = ''.join(traceback.format_exception(*tb))
  64.327 +        path = web.ctx.path
  64.328 +        request = web.ctx.method + ' ' + web.ctx.home + web.ctx.fullpath
  64.329 +        
  64.330 +        message = "\n%s\n\n%s\n\n" % (request, tb_txt)
  64.331 +        
  64.332 +        sendmail(
  64.333 +            "your buggy site <%s>" % from_address,
  64.334 +            "the bugfixer <%s>" % to_address,
  64.335 +            "bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(),
  64.336 +            message,
  64.337 +            attachments=[
  64.338 +                dict(filename="bug.html", content=safestr(djangoerror()))
  64.339 +            ],
  64.340 +        )
  64.341 +        return error
  64.342 +    
  64.343 +    return emailerrors_internal
  64.344 +
  64.345 +if __name__ == "__main__":
  64.346 +    urls = (
  64.347 +        '/', 'index'
  64.348 +    )
  64.349 +    from application import application
  64.350 +    app = application(urls, globals())
  64.351 +    app.internalerror = debugerror
  64.352 +    
  64.353 +    class index:
  64.354 +        def GET(self):
  64.355 +            thisdoesnotexist
  64.356 +
  64.357 +    app.run()
    65.1 Binary file OpenSecurity/install/web.py-0.37/web/debugerror.pyc has changed
    66.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    66.2 +++ b/OpenSecurity/install/web.py-0.37/web/form.py	Mon Dec 02 14:02:05 2013 +0100
    66.3 @@ -0,0 +1,410 @@
    66.4 +"""
    66.5 +HTML forms
    66.6 +(part of web.py)
    66.7 +"""
    66.8 +
    66.9 +import copy, re
   66.10 +import webapi as web
   66.11 +import utils, net
   66.12 +
   66.13 +def attrget(obj, attr, value=None):
   66.14 +    try:
   66.15 +        if hasattr(obj, 'has_key') and obj.has_key(attr): 
   66.16 +            return obj[attr]
   66.17 +    except TypeError:
   66.18 +        # Handle the case where has_key takes different number of arguments.
   66.19 +        # This is the case with Model objects on appengine. See #134
   66.20 +        pass
   66.21 +    if hasattr(obj, attr):
   66.22 +        return getattr(obj, attr)
   66.23 +    return value
   66.24 +
   66.25 +class Form(object):
   66.26 +    r"""
   66.27 +    HTML form.
   66.28 +    
   66.29 +        >>> f = Form(Textbox("x"))
   66.30 +        >>> f.render()
   66.31 +        u'<table>\n    <tr><th><label for="x">x</label></th><td><input type="text" id="x" name="x"/></td></tr>\n</table>'
   66.32 +    """
   66.33 +    def __init__(self, *inputs, **kw):
   66.34 +        self.inputs = inputs
   66.35 +        self.valid = True
   66.36 +        self.note = None
   66.37 +        self.validators = kw.pop('validators', [])
   66.38 +
   66.39 +    def __call__(self, x=None):
   66.40 +        o = copy.deepcopy(self)
   66.41 +        if x: o.validates(x)
   66.42 +        return o
   66.43 +    
   66.44 +    def render(self):
   66.45 +        out = ''
   66.46 +        out += self.rendernote(self.note)
   66.47 +        out += '<table>\n'
   66.48 +        
   66.49 +        for i in self.inputs:
   66.50 +            html = utils.safeunicode(i.pre) + i.render() + self.rendernote(i.note) + utils.safeunicode(i.post)
   66.51 +            if i.is_hidden():
   66.52 +                out += '    <tr style="display: none;"><th></th><td>%s</td></tr>\n' % (html)
   66.53 +            else:
   66.54 +                out += '    <tr><th><label for="%s">%s</label></th><td>%s</td></tr>\n' % (i.id, net.websafe(i.description), html)
   66.55 +        out += "</table>"
   66.56 +        return out
   66.57 +        
   66.58 +    def render_css(self): 
   66.59 +        out = [] 
   66.60 +        out.append(self.rendernote(self.note)) 
   66.61 +        for i in self.inputs:
   66.62 +            if not i.is_hidden():
   66.63 +                out.append('<label for="%s">%s</label>' % (i.id, net.websafe(i.description))) 
   66.64 +            out.append(i.pre)
   66.65 +            out.append(i.render()) 
   66.66 +            out.append(self.rendernote(i.note))
   66.67 +            out.append(i.post) 
   66.68 +            out.append('\n')
   66.69 +        return ''.join(out) 
   66.70 +        
   66.71 +    def rendernote(self, note):
   66.72 +        if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
   66.73 +        else: return ""
   66.74 +    
   66.75 +    def validates(self, source=None, _validate=True, **kw):
   66.76 +        source = source or kw or web.input()
   66.77 +        out = True
   66.78 +        for i in self.inputs:
   66.79 +            v = attrget(source, i.name)
   66.80 +            if _validate:
   66.81 +                out = i.validate(v) and out
   66.82 +            else:
   66.83 +                i.set_value(v)
   66.84 +        if _validate:
   66.85 +            out = out and self._validate(source)
   66.86 +            self.valid = out
   66.87 +        return out
   66.88 +
   66.89 +    def _validate(self, value):
   66.90 +        self.value = value
   66.91 +        for v in self.validators:
   66.92 +            if not v.valid(value):
   66.93 +                self.note = v.msg
   66.94 +                return False
   66.95 +        return True
   66.96 +
   66.97 +    def fill(self, source=None, **kw):
   66.98 +        return self.validates(source, _validate=False, **kw)
   66.99 +    
  66.100 +    def __getitem__(self, i):
  66.101 +        for x in self.inputs:
  66.102 +            if x.name == i: return x
  66.103 +        raise KeyError, i
  66.104 +
  66.105 +    def __getattr__(self, name):
  66.106 +        # don't interfere with deepcopy
  66.107 +        inputs = self.__dict__.get('inputs') or []
  66.108 +        for x in inputs:
  66.109 +            if x.name == name: return x
  66.110 +        raise AttributeError, name
  66.111 +    
  66.112 +    def get(self, i, default=None):
  66.113 +        try:
  66.114 +            return self[i]
  66.115 +        except KeyError:
  66.116 +            return default
  66.117 +            
  66.118 +    def _get_d(self): #@@ should really be form.attr, no?
  66.119 +        return utils.storage([(i.name, i.get_value()) for i in self.inputs])
  66.120 +    d = property(_get_d)
  66.121 +
  66.122 +class Input(object):
  66.123 +    def __init__(self, name, *validators, **attrs):
  66.124 +        self.name = name
  66.125 +        self.validators = validators
  66.126 +        self.attrs = attrs = AttributeList(attrs)
  66.127 +        
  66.128 +        self.description = attrs.pop('description', name)
  66.129 +        self.value = attrs.pop('value', None)
  66.130 +        self.pre = attrs.pop('pre', "")
  66.131 +        self.post = attrs.pop('post', "")
  66.132 +        self.note = None
  66.133 +        
  66.134 +        self.id = attrs.setdefault('id', self.get_default_id())
  66.135 +        
  66.136 +        if 'class_' in attrs:
  66.137 +            attrs['class'] = attrs['class_']
  66.138 +            del attrs['class_']
  66.139 +        
  66.140 +    def is_hidden(self):
  66.141 +        return False
  66.142 +        
  66.143 +    def get_type(self):
  66.144 +        raise NotImplementedError
  66.145 +        
  66.146 +    def get_default_id(self):
  66.147 +        return self.name
  66.148 +
  66.149 +    def validate(self, value):
  66.150 +        self.set_value(value)
  66.151 +
  66.152 +        for v in self.validators:
  66.153 +            if not v.valid(value):
  66.154 +                self.note = v.msg
  66.155 +                return False
  66.156 +        return True
  66.157 +
  66.158 +    def set_value(self, value):
  66.159 +        self.value = value
  66.160 +
  66.161 +    def get_value(self):
  66.162 +        return self.value
  66.163 +
  66.164 +    def render(self):
  66.165 +        attrs = self.attrs.copy()
  66.166 +        attrs['type'] = self.get_type()
  66.167 +        if self.value is not None:
  66.168 +            attrs['value'] = self.value
  66.169 +        attrs['name'] = self.name
  66.170 +        return '<input %s/>' % attrs
  66.171 +
  66.172 +    def rendernote(self, note):
  66.173 +        if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
  66.174 +        else: return ""
  66.175 +        
  66.176 +    def addatts(self):
  66.177 +        # add leading space for backward-compatibility
  66.178 +        return " " + str(self.attrs)
  66.179 +
  66.180 +class AttributeList(dict):
  66.181 +    """List of atributes of input.
  66.182 +    
  66.183 +    >>> a = AttributeList(type='text', name='x', value=20)
  66.184 +    >>> a
  66.185 +    <attrs: 'type="text" name="x" value="20"'>
  66.186 +    """
  66.187 +    def copy(self):
  66.188 +        return AttributeList(self)
  66.189 +        
  66.190 +    def __str__(self):
  66.191 +        return " ".join(['%s="%s"' % (k, net.websafe(v)) for k, v in self.items()])
  66.192 +        
  66.193 +    def __repr__(self):
  66.194 +        return '<attrs: %s>' % repr(str(self))
  66.195 +
  66.196 +class Textbox(Input):
  66.197 +    """Textbox input.
  66.198 +    
  66.199 +        >>> Textbox(name='foo', value='bar').render()
  66.200 +        u'<input type="text" id="foo" value="bar" name="foo"/>'
  66.201 +        >>> Textbox(name='foo', value=0).render()
  66.202 +        u'<input type="text" id="foo" value="0" name="foo"/>'
  66.203 +    """        
  66.204 +    def get_type(self):
  66.205 +        return 'text'
  66.206 +
  66.207 +class Password(Input):
  66.208 +    """Password input.
  66.209 +
  66.210 +        >>> Password(name='password', value='secret').render()
  66.211 +        u'<input type="password" id="password" value="secret" name="password"/>'
  66.212 +    """
  66.213 +    
  66.214 +    def get_type(self):
  66.215 +        return 'password'
  66.216 +
  66.217 +class Textarea(Input):
  66.218 +    """Textarea input.
  66.219 +    
  66.220 +        >>> Textarea(name='foo', value='bar').render()
  66.221 +        u'<textarea id="foo" name="foo">bar</textarea>'
  66.222 +    """
  66.223 +    def render(self):
  66.224 +        attrs = self.attrs.copy()
  66.225 +        attrs['name'] = self.name
  66.226 +        value = net.websafe(self.value or '')
  66.227 +        return '<textarea %s>%s</textarea>' % (attrs, value)
  66.228 +
  66.229 +class Dropdown(Input):
  66.230 +    r"""Dropdown/select input.
  66.231 +    
  66.232 +        >>> Dropdown(name='foo', args=['a', 'b', 'c'], value='b').render()
  66.233 +        u'<select id="foo" name="foo">\n  <option value="a">a</option>\n  <option selected="selected" value="b">b</option>\n  <option value="c">c</option>\n</select>\n'
  66.234 +        >>> Dropdown(name='foo', args=[('a', 'aa'), ('b', 'bb'), ('c', 'cc')], value='b').render()
  66.235 +        u'<select id="foo" name="foo">\n  <option value="a">aa</option>\n  <option selected="selected" value="b">bb</option>\n  <option value="c">cc</option>\n</select>\n'
  66.236 +    """
  66.237 +    def __init__(self, name, args, *validators, **attrs):
  66.238 +        self.args = args
  66.239 +        super(Dropdown, self).__init__(name, *validators, **attrs)
  66.240 +
  66.241 +    def render(self):
  66.242 +        attrs = self.attrs.copy()
  66.243 +        attrs['name'] = self.name
  66.244 +        
  66.245 +        x = '<select %s>\n' % attrs
  66.246 +        
  66.247 +        for arg in self.args:
  66.248 +            x += self._render_option(arg)
  66.249 +
  66.250 +        x += '</select>\n'
  66.251 +        return x
  66.252 +
  66.253 +    def _render_option(self, arg, indent='  '):
  66.254 +        if isinstance(arg, (tuple, list)):
  66.255 +            value, desc= arg
  66.256 +        else:
  66.257 +            value, desc = arg, arg 
  66.258 +
  66.259 +        if self.value == value or (isinstance(self.value, list) and value in self.value):
  66.260 +            select_p = ' selected="selected"'
  66.261 +        else:
  66.262 +            select_p = ''
  66.263 +        return indent + '<option%s value="%s">%s</option>\n' % (select_p, net.websafe(value), net.websafe(desc))
  66.264 +        
  66.265 +
  66.266 +class GroupedDropdown(Dropdown):
  66.267 +    r"""Grouped Dropdown/select input.
  66.268 +    
  66.269 +        >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', ('Volvo', 'Saab')), ('German Cars', ('Mercedes', 'Audi'))), value='Audi').render()
  66.270 +        u'<select id="car_type" name="car_type">\n  <optgroup label="Swedish Cars">\n    <option value="Volvo">Volvo</option>\n    <option value="Saab">Saab</option>\n  </optgroup>\n  <optgroup label="German Cars">\n    <option value="Mercedes">Mercedes</option>\n    <option selected="selected" value="Audi">Audi</option>\n  </optgroup>\n</select>\n'
  66.271 +        >>> GroupedDropdown(name='car_type', args=(('Swedish Cars', (('v', 'Volvo'), ('s', 'Saab'))), ('German Cars', (('m', 'Mercedes'), ('a', 'Audi')))), value='a').render()
  66.272 +        u'<select id="car_type" name="car_type">\n  <optgroup label="Swedish Cars">\n    <option value="v">Volvo</option>\n    <option value="s">Saab</option>\n  </optgroup>\n  <optgroup label="German Cars">\n    <option value="m">Mercedes</option>\n    <option selected="selected" value="a">Audi</option>\n  </optgroup>\n</select>\n'
  66.273 +
  66.274 +    """
  66.275 +    def __init__(self, name, args, *validators, **attrs):
  66.276 +        self.args = args
  66.277 +        super(Dropdown, self).__init__(name, *validators, **attrs)
  66.278 +
  66.279 +    def render(self):
  66.280 +        attrs = self.attrs.copy()
  66.281 +        attrs['name'] = self.name
  66.282 +        
  66.283 +        x = '<select %s>\n' % attrs
  66.284 +        
  66.285 +        for label, options in self.args:
  66.286 +            x += '  <optgroup label="%s">\n' % net.websafe(label)
  66.287 +            for arg in options:
  66.288 +                x += self._render_option(arg, indent = '    ')
  66.289 +            x +=  '  </optgroup>\n'
  66.290 +            
  66.291 +        x += '</select>\n'
  66.292 +        return x
  66.293 +
  66.294 +class Radio(Input):
  66.295 +    def __init__(self, name, args, *validators, **attrs):
  66.296 +        self.args = args
  66.297 +        super(Radio, self).__init__(name, *validators, **attrs)
  66.298 +
  66.299 +    def render(self):
  66.300 +        x = '<span>'
  66.301 +        for arg in self.args:
  66.302 +            if isinstance(arg, (tuple, list)):
  66.303 +                value, desc= arg
  66.304 +            else:
  66.305 +                value, desc = arg, arg 
  66.306 +            attrs = self.attrs.copy()
  66.307 +            attrs['name'] = self.name
  66.308 +            attrs['type'] = 'radio'
  66.309 +            attrs['value'] = value
  66.310 +            if self.value == value:
  66.311 +                attrs['checked'] = 'checked'
  66.312 +            x += '<input %s/> %s' % (attrs, net.websafe(desc))
  66.313 +        x += '</span>'
  66.314 +        return x
  66.315 +
  66.316 +class Checkbox(Input):
  66.317 +    """Checkbox input.
  66.318 +
  66.319 +    >>> Checkbox('foo', value='bar', checked=True).render()
  66.320 +    u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>'
  66.321 +    >>> Checkbox('foo', value='bar').render()
  66.322 +    u'<input type="checkbox" id="foo_bar" value="bar" name="foo"/>'
  66.323 +    >>> c = Checkbox('foo', value='bar')
  66.324 +    >>> c.validate('on')
  66.325 +    True
  66.326 +    >>> c.render()
  66.327 +    u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>'
  66.328 +    """
  66.329 +    def __init__(self, name, *validators, **attrs):
  66.330 +        self.checked = attrs.pop('checked', False)
  66.331 +        Input.__init__(self, name, *validators, **attrs)
  66.332 +        
  66.333 +    def get_default_id(self):
  66.334 +        value = utils.safestr(self.value or "")
  66.335 +        return self.name + '_' + value.replace(' ', '_')
  66.336 +
  66.337 +    def render(self):
  66.338 +        attrs = self.attrs.copy()
  66.339 +        attrs['type'] = 'checkbox'
  66.340 +        attrs['name'] = self.name
  66.341 +        attrs['value'] = self.value
  66.342 +
  66.343 +        if self.checked:
  66.344 +            attrs['checked'] = 'checked'            
  66.345 +        return '<input %s/>' % attrs
  66.346 +
  66.347 +    def set_value(self, value):
  66.348 +        self.checked = bool(value)
  66.349 +
  66.350 +    def get_value(self):
  66.351 +        return self.checked
  66.352 +
  66.353 +class Button(Input):
  66.354 +    """HTML Button.
  66.355 +    
  66.356 +    >>> Button("save").render()
  66.357 +    u'<button id="save" name="save">save</button>'
  66.358 +    >>> Button("action", value="save", html="<b>Save Changes</b>").render()
  66.359 +    u'<button id="action" value="save" name="action"><b>Save Changes</b></button>'
  66.360 +    """
  66.361 +    def __init__(self, name, *validators, **attrs):
  66.362 +        super(Button, self).__init__(name, *validators, **attrs)
  66.363 +        self.description = ""
  66.364 +
  66.365 +    def render(self):
  66.366 +        attrs = self.attrs.copy()
  66.367 +        attrs['name'] = self.name
  66.368 +        if self.value is not None:
  66.369 +            attrs['value'] = self.value
  66.370 +        html = attrs.pop('html', None) or net.websafe(self.name)
  66.371 +        return '<button %s>%s</button>' % (attrs, html)
  66.372 +
  66.373 +class Hidden(Input):
  66.374 +    """Hidden Input.
  66.375 +    
  66.376 +        >>> Hidden(name='foo', value='bar').render()
  66.377 +        u'<input type="hidden" id="foo" value="bar" name="foo"/>'
  66.378 +    """
  66.379 +    def is_hidden(self):
  66.380 +        return True
  66.381 +        
  66.382 +    def get_type(self):
  66.383 +        return 'hidden'
  66.384 +
  66.385 +class File(Input):
  66.386 +    """File input.
  66.387 +    
  66.388 +        >>> File(name='f').render()
  66.389 +        u'<input type="file" id="f" name="f"/>'
  66.390 +    """
  66.391 +    def get_type(self):
  66.392 +        return 'file'
  66.393 +    
  66.394 +class Validator:
  66.395 +    def __deepcopy__(self, memo): return copy.copy(self)
  66.396 +    def __init__(self, msg, test, jstest=None): utils.autoassign(self, locals())
  66.397 +    def valid(self, value): 
  66.398 +        try: return self.test(value)
  66.399 +        except: return False
  66.400 +
  66.401 +notnull = Validator("Required", bool)
  66.402 +
  66.403 +class regexp(Validator):
  66.404 +    def __init__(self, rexp, msg):
  66.405 +        self.rexp = re.compile(rexp)
  66.406 +        self.msg = msg
  66.407 +    
  66.408 +    def valid(self, value):
  66.409 +        return bool(self.rexp.match(value))
  66.410 +
  66.411 +if __name__ == "__main__":
  66.412 +    import doctest
  66.413 +    doctest.testmod()
    67.1 Binary file OpenSecurity/install/web.py-0.37/web/form.pyc has changed
    68.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    68.2 +++ b/OpenSecurity/install/web.py-0.37/web/http.py	Mon Dec 02 14:02:05 2013 +0100
    68.3 @@ -0,0 +1,150 @@
    68.4 +"""
    68.5 +HTTP Utilities
    68.6 +(from web.py)
    68.7 +"""
    68.8 +
    68.9 +__all__ = [
   68.10 +  "expires", "lastmodified", 
   68.11 +  "prefixurl", "modified", 
   68.12 +  "changequery", "url",
   68.13 +  "profiler",
   68.14 +]
   68.15 +
   68.16 +import sys, os, threading, urllib, urlparse
   68.17 +try: import datetime
   68.18 +except ImportError: pass
   68.19 +import net, utils, webapi as web
   68.20 +
   68.21 +def prefixurl(base=''):
   68.22 +    """
   68.23 +    Sorry, this function is really difficult to explain.
   68.24 +    Maybe some other time.
   68.25 +    """
   68.26 +    url = web.ctx.path.lstrip('/')
   68.27 +    for i in xrange(url.count('/')): 
   68.28 +        base += '../'
   68.29 +    if not base: 
   68.30 +        base = './'
   68.31 +    return base
   68.32 +
   68.33 +def expires(delta):
   68.34 +    """
   68.35 +    Outputs an `Expires` header for `delta` from now. 
   68.36 +    `delta` is a `timedelta` object or a number of seconds.
   68.37 +    """
   68.38 +    if isinstance(delta, (int, long)):
   68.39 +        delta = datetime.timedelta(seconds=delta)
   68.40 +    date_obj = datetime.datetime.utcnow() + delta
   68.41 +    web.header('Expires', net.httpdate(date_obj))
   68.42 +
   68.43 +def lastmodified(date_obj):
   68.44 +    """Outputs a `Last-Modified` header for `datetime`."""
   68.45 +    web.header('Last-Modified', net.httpdate(date_obj))
   68.46 +
   68.47 +def modified(date=None, etag=None):
   68.48 +    """
   68.49 +    Checks to see if the page has been modified since the version in the
   68.50 +    requester's cache.
   68.51 +    
   68.52 +    When you publish pages, you can include `Last-Modified` and `ETag`
   68.53 +    with the date the page was last modified and an opaque token for
   68.54 +    the particular version, respectively. When readers reload the page, 
   68.55 +    the browser sends along the modification date and etag value for
   68.56 +    the version it has in its cache. If the page hasn't changed, 
   68.57 +    the server can just return `304 Not Modified` and not have to 
   68.58 +    send the whole page again.
   68.59 +    
   68.60 +    This function takes the last-modified date `date` and the ETag `etag`
   68.61 +    and checks the headers to see if they match. If they do, it returns 
   68.62 +    `True`, or otherwise it raises NotModified error. It also sets 
   68.63 +    `Last-Modified` and `ETag` output headers.
   68.64 +    """
   68.65 +    try:
   68.66 +        from __builtin__ import set
   68.67 +    except ImportError:
   68.68 +        # for python 2.3
   68.69 +        from sets import Set as set
   68.70 +
   68.71 +    n = set([x.strip('" ') for x in web.ctx.env.get('HTTP_IF_NONE_MATCH', '').split(',')])
   68.72 +    m = net.parsehttpdate(web.ctx.env.get('HTTP_IF_MODIFIED_SINCE', '').split(';')[0])
   68.73 +    validate = False
   68.74 +    if etag:
   68.75 +        if '*' in n or etag in n:
   68.76 +            validate = True
   68.77 +    if date and m:
   68.78 +        # we subtract a second because 
   68.79 +        # HTTP dates don't have sub-second precision
   68.80 +        if date-datetime.timedelta(seconds=1) <= m:
   68.81 +            validate = True
   68.82 +    
   68.83 +    if date: lastmodified(date)
   68.84 +    if etag: web.header('ETag', '"' + etag + '"')
   68.85 +    if validate:
   68.86 +        raise web.notmodified()
   68.87 +    else:
   68.88 +        return True
   68.89 +
   68.90 +def urlencode(query, doseq=0):
   68.91 +    """
   68.92 +    Same as urllib.urlencode, but supports unicode strings.
   68.93 +    
   68.94 +        >>> urlencode({'text':'foo bar'})
   68.95 +        'text=foo+bar'
   68.96 +        >>> urlencode({'x': [1, 2]}, doseq=True)
   68.97 +        'x=1&x=2'
   68.98 +    """
   68.99 +    def convert(value, doseq=False):
  68.100 +        if doseq and isinstance(value, list):
  68.101 +            return [convert(v) for v in value]
  68.102 +        else:
  68.103 +            return utils.safestr(value)
  68.104 +        
  68.105 +    query = dict([(k, convert(v, doseq)) for k, v in query.items()])
  68.106 +    return urllib.urlencode(query, doseq=doseq)
  68.107 +
  68.108 +def changequery(query=None, **kw):
  68.109 +    """
  68.110 +    Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return
  68.111 +    `/foo?a=3&b=2` -- the same URL but with the arguments you requested
  68.112 +    changed.
  68.113 +    """
  68.114 +    if query is None:
  68.115 +        query = web.rawinput(method='get')
  68.116 +    for k, v in kw.iteritems():
  68.117 +        if v is None:
  68.118 +            query.pop(k, None)
  68.119 +        else:
  68.120 +            query[k] = v
  68.121 +    out = web.ctx.path
  68.122 +    if query:
  68.123 +        out += '?' + urlencode(query, doseq=True)
  68.124 +    return out
  68.125 +
  68.126 +def url(path=None, doseq=False, **kw):
  68.127 +    """
  68.128 +    Makes url by concatenating web.ctx.homepath and path and the 
  68.129 +    query string created using the arguments.
  68.130 +    """
  68.131 +    if path is None:
  68.132 +        path = web.ctx.path
  68.133 +    if path.startswith("/"):
  68.134 +        out = web.ctx.homepath + path
  68.135 +    else:
  68.136 +        out = path
  68.137 +
  68.138 +    if kw:
  68.139 +        out += '?' + urlencode(kw, doseq=doseq)
  68.140 +    
  68.141 +    return out
  68.142 +
  68.143 +def profiler(app):
  68.144 +    """Outputs basic profiling information at the bottom of each response."""
  68.145 +    from utils import profile
  68.146 +    def profile_internal(e, o):
  68.147 +        out, result = profile(app)(e, o)
  68.148 +        return list(out) + ['<pre>' + net.websafe(result) + '</pre>']
  68.149 +    return profile_internal
  68.150 +
  68.151 +if __name__ == "__main__":
  68.152 +    import doctest
  68.153 +    doctest.testmod()
    69.1 Binary file OpenSecurity/install/web.py-0.37/web/http.pyc has changed
    70.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    70.2 +++ b/OpenSecurity/install/web.py-0.37/web/httpserver.py	Mon Dec 02 14:02:05 2013 +0100
    70.3 @@ -0,0 +1,319 @@
    70.4 +__all__ = ["runsimple"]
    70.5 +
    70.6 +import sys, os
    70.7 +from SimpleHTTPServer import SimpleHTTPRequestHandler
    70.8 +import urllib
    70.9 +import posixpath
   70.10 +
   70.11 +import webapi as web
   70.12 +import net
   70.13 +import utils
   70.14 +
   70.15 +def runbasic(func, server_address=("0.0.0.0", 8080)):
   70.16 +    """
   70.17 +    Runs a simple HTTP server hosting WSGI app `func`. The directory `static/` 
   70.18 +    is hosted statically.
   70.19 +
   70.20 +    Based on [WsgiServer][ws] from [Colin Stewart][cs].
   70.21 +    
   70.22 +  [ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
   70.23 +  [cs]: http://www.owlfish.com/
   70.24 +    """
   70.25 +    # Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
   70.26 +    # Modified somewhat for simplicity
   70.27 +    # Used under the modified BSD license:
   70.28 +    # http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
   70.29 +
   70.30 +    import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
   70.31 +    import socket, errno
   70.32 +    import traceback
   70.33 +
   70.34 +    class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
   70.35 +        def run_wsgi_app(self):
   70.36 +            protocol, host, path, parameters, query, fragment = \
   70.37 +                urlparse.urlparse('http://dummyhost%s' % self.path)
   70.38 +
   70.39 +            # we only use path, query
   70.40 +            env = {'wsgi.version': (1, 0)
   70.41 +                   ,'wsgi.url_scheme': 'http'
   70.42 +                   ,'wsgi.input': self.rfile
   70.43 +                   ,'wsgi.errors': sys.stderr
   70.44 +                   ,'wsgi.multithread': 1
   70.45 +                   ,'wsgi.multiprocess': 0
   70.46 +                   ,'wsgi.run_once': 0
   70.47 +                   ,'REQUEST_METHOD': self.command
   70.48 +                   ,'REQUEST_URI': self.path
   70.49 +                   ,'PATH_INFO': path
   70.50 +                   ,'QUERY_STRING': query
   70.51 +                   ,'CONTENT_TYPE': self.headers.get('Content-Type', '')
   70.52 +                   ,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
   70.53 +                   ,'REMOTE_ADDR': self.client_address[0]
   70.54 +                   ,'SERVER_NAME': self.server.server_address[0]
   70.55 +                   ,'SERVER_PORT': str(self.server.server_address[1])
   70.56 +                   ,'SERVER_PROTOCOL': self.request_version
   70.57 +                   }
   70.58 +
   70.59 +            for http_header, http_value in self.headers.items():
   70.60 +                env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
   70.61 +                    http_value
   70.62 +
   70.63 +            # Setup the state
   70.64 +            self.wsgi_sent_headers = 0
   70.65 +            self.wsgi_headers = []
   70.66 +
   70.67 +            try:
   70.68 +                # We have there environment, now invoke the application
   70.69 +                result = self.server.app(env, self.wsgi_start_response)
   70.70 +                try:
   70.71 +                    try:
   70.72 +                        for data in result:
   70.73 +                            if data: 
   70.74 +                                self.wsgi_write_data(data)
   70.75 +                    finally:
   70.76 +                        if hasattr(result, 'close'): 
   70.77 +                            result.close()
   70.78 +                except socket.error, socket_err:
   70.79 +                    # Catch common network errors and suppress them
   70.80 +                    if (socket_err.args[0] in \
   70.81 +                       (errno.ECONNABORTED, errno.EPIPE)): 
   70.82 +                        return
   70.83 +                except socket.timeout, socket_timeout: 
   70.84 +                    return
   70.85 +            except:
   70.86 +                print >> web.debug, traceback.format_exc(),
   70.87 +
   70.88 +            if (not self.wsgi_sent_headers):
   70.89 +                # We must write out something!
   70.90 +                self.wsgi_write_data(" ")
   70.91 +            return
   70.92 +
   70.93 +        do_POST = run_wsgi_app
   70.94 +        do_PUT = run_wsgi_app
   70.95 +        do_DELETE = run_wsgi_app
   70.96 +
   70.97 +        def do_GET(self):
   70.98 +            if self.path.startswith('/static/'):
   70.99 +                SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
  70.100 +            else:
  70.101 +                self.run_wsgi_app()
  70.102 +
  70.103 +        def wsgi_start_response(self, response_status, response_headers, 
  70.104 +                              exc_info=None):
  70.105 +            if (self.wsgi_sent_headers):
  70.106 +                raise Exception \
  70.107 +                      ("Headers already sent and start_response called again!")
  70.108 +            # Should really take a copy to avoid changes in the application....
  70.109 +            self.wsgi_headers = (response_status, response_headers)
  70.110 +            return self.wsgi_write_data
  70.111 +
  70.112 +        def wsgi_write_data(self, data):
  70.113 +            if (not self.wsgi_sent_headers):
  70.114 +                status, headers = self.wsgi_headers
  70.115 +                # Need to send header prior to data
  70.116 +                status_code = status[:status.find(' ')]
  70.117 +                status_msg = status[status.find(' ') + 1:]
  70.118 +                self.send_response(int(status_code), status_msg)
  70.119 +                for header, value in headers:
  70.120 +                    self.send_header(header, value)
  70.121 +                self.end_headers()
  70.122 +                self.wsgi_sent_headers = 1
  70.123 +            # Send the data
  70.124 +            self.wfile.write(data)
  70.125 +
  70.126 +    class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
  70.127 +        def __init__(self, func, server_address):
  70.128 +            BaseHTTPServer.HTTPServer.__init__(self, 
  70.129 +                                               server_address, 
  70.130 +                                               WSGIHandler)
  70.131 +            self.app = func
  70.132 +            self.serverShuttingDown = 0
  70.133 +
  70.134 +    print "http://%s:%d/" % server_address
  70.135 +    WSGIServer(func, server_address).serve_forever()
  70.136 +
  70.137 +# The WSGIServer instance. 
  70.138 +# Made global so that it can be stopped in embedded mode.
  70.139 +server = None
  70.140 +
  70.141 +def runsimple(func, server_address=("0.0.0.0", 8080)):
  70.142 +    """
  70.143 +    Runs [CherryPy][cp] WSGI server hosting WSGI app `func`. 
  70.144 +    The directory `static/` is hosted statically.
  70.145 +
  70.146 +    [cp]: http://www.cherrypy.org
  70.147 +    """
  70.148 +    global server
  70.149 +    func = StaticMiddleware(func)
  70.150 +    func = LogMiddleware(func)
  70.151 +    
  70.152 +    server = WSGIServer(server_address, func)
  70.153 +
  70.154 +    if server.ssl_adapter:
  70.155 +        print "https://%s:%d/" % server_address
  70.156 +    else:
  70.157 +        print "http://%s:%d/" % server_address
  70.158 +
  70.159 +    try:
  70.160 +        server.start()
  70.161 +    except (KeyboardInterrupt, SystemExit):
  70.162 +        server.stop()
  70.163 +        server = None
  70.164 +
  70.165 +def WSGIServer(server_address, wsgi_app):
  70.166 +    """Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`.
  70.167 +    This function can be overwritten to customize the webserver or use a different webserver.
  70.168 +    """
  70.169 +    import wsgiserver
  70.170 +    
  70.171 +    # Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver
  70.172 +    # prefix. Overwriting it make it work with web.wsgiserver.
  70.173 +    wsgiserver.ssl_adapters = {
  70.174 +        'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
  70.175 +        'pyopenssl': 'web.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
  70.176 +    }
  70.177 +    
  70.178 +    server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
  70.179 +        
  70.180 +    def create_ssl_adapter(cert, key):
  70.181 +        # wsgiserver tries to import submodules as cherrypy.wsgiserver.foo.
  70.182 +        # That doesn't work as not it is web.wsgiserver. 
  70.183 +        # Patching sys.modules temporarily to make it work.
  70.184 +        import types
  70.185 +        cherrypy = types.ModuleType('cherrypy')
  70.186 +        cherrypy.wsgiserver = wsgiserver
  70.187 +        sys.modules['cherrypy'] = cherrypy
  70.188 +        sys.modules['cherrypy.wsgiserver'] = wsgiserver
  70.189 +        
  70.190 +        from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
  70.191 +        adapter = pyOpenSSLAdapter(cert, key)
  70.192 +        
  70.193 +        # We are done with our work. Cleanup the patches.
  70.194 +        del sys.modules['cherrypy']
  70.195 +        del sys.modules['cherrypy.wsgiserver']
  70.196 +
  70.197 +        return adapter
  70.198 +
  70.199 +    # SSL backward compatibility
  70.200 +    if (server.ssl_adapter is None and
  70.201 +        getattr(server, 'ssl_certificate', None) and
  70.202 +        getattr(server, 'ssl_private_key', None)):
  70.203 +        server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, server.ssl_private_key)
  70.204 +
  70.205 +    server.nodelay = not sys.platform.startswith('java') # TCP_NODELAY isn't supported on the JVM
  70.206 +    return server
  70.207 +
  70.208 +class StaticApp(SimpleHTTPRequestHandler):
  70.209 +    """WSGI application for serving static files."""
  70.210 +    def __init__(self, environ, start_response):
  70.211 +        self.headers = []
  70.212 +        self.environ = environ
  70.213 +        self.start_response = start_response
  70.214 +
  70.215 +    def send_response(self, status, msg=""):
  70.216 +        self.status = str(status) + " " + msg
  70.217 +
  70.218 +    def send_header(self, name, value):
  70.219 +        self.headers.append((name, value))
  70.220 +
  70.221 +    def end_headers(self):
  70.222 +        pass
  70.223 +
  70.224 +    def log_message(*a): pass
  70.225 +
  70.226 +    def __iter__(self):
  70.227 +        environ = self.environ
  70.228 +
  70.229 +        self.path = environ.get('PATH_INFO', '')
  70.230 +        self.client_address = environ.get('REMOTE_ADDR','-'), \
  70.231 +                              environ.get('REMOTE_PORT','-')
  70.232 +        self.command = environ.get('REQUEST_METHOD', '-')
  70.233 +
  70.234 +        from cStringIO import StringIO
  70.235 +        self.wfile = StringIO() # for capturing error
  70.236 +
  70.237 +        try:
  70.238 +            path = self.translate_path(self.path)
  70.239 +            etag = '"%s"' % os.path.getmtime(path)
  70.240 +            client_etag = environ.get('HTTP_IF_NONE_MATCH')
  70.241 +            self.send_header('ETag', etag)
  70.242 +            if etag == client_etag:
  70.243 +                self.send_response(304, "Not Modified")
  70.244 +                self.start_response(self.status, self.headers)
  70.245 +                raise StopIteration
  70.246 +        except OSError:
  70.247 +            pass # Probably a 404
  70.248 +
  70.249 +        f = self.send_head()
  70.250 +        self.start_response(self.status, self.headers)
  70.251 +
  70.252 +        if f:
  70.253 +            block_size = 16 * 1024
  70.254 +            while True:
  70.255 +                buf = f.read(block_size)
  70.256 +                if not buf:
  70.257 +                    break
  70.258 +                yield buf
  70.259 +            f.close()
  70.260 +        else:
  70.261 +            value = self.wfile.getvalue()
  70.262 +            yield value
  70.263 +
  70.264 +class StaticMiddleware:
  70.265 +    """WSGI middleware for serving static files."""
  70.266 +    def __init__(self, app, prefix='/static/'):
  70.267 +        self.app = app
  70.268 +        self.prefix = prefix
  70.269 +        
  70.270 +    def __call__(self, environ, start_response):
  70.271 +        path = environ.get('PATH_INFO', '')
  70.272 +        path = self.normpath(path)
  70.273 +
  70.274 +        if path.startswith(self.prefix):
  70.275 +            return StaticApp(environ, start_response)
  70.276 +        else:
  70.277 +            return self.app(environ, start_response)
  70.278 +
  70.279 +    def normpath(self, path):
  70.280 +        path2 = posixpath.normpath(urllib.unquote(path))
  70.281 +        if path.endswith("/"):
  70.282 +            path2 += "/"
  70.283 +        return path2
  70.284 +
  70.285 +    
  70.286 +class LogMiddleware:
  70.287 +    """WSGI middleware for logging the status."""
  70.288 +    def __init__(self, app):
  70.289 +        self.app = app
  70.290 +        self.format = '%s - - [%s] "%s %s %s" - %s'
  70.291 +    
  70.292 +        from BaseHTTPServer import BaseHTTPRequestHandler
  70.293 +        import StringIO
  70.294 +        f = StringIO.StringIO()
  70.295 +        
  70.296 +        class FakeSocket:
  70.297 +            def makefile(self, *a):
  70.298 +                return f
  70.299 +        
  70.300 +        # take log_date_time_string method from BaseHTTPRequestHandler
  70.301 +        self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string
  70.302 +        
  70.303 +    def __call__(self, environ, start_response):
  70.304 +        def xstart_response(status, response_headers, *args):
  70.305 +            out = start_response(status, response_headers, *args)
  70.306 +            self.log(status, environ)
  70.307 +            return out
  70.308 +
  70.309 +        return self.app(environ, xstart_response)
  70.310 +             
  70.311 +    def log(self, status, environ):
  70.312 +        outfile = environ.get('wsgi.errors', web.debug)
  70.313 +        req = environ.get('PATH_INFO', '_')
  70.314 +        protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
  70.315 +        method = environ.get('REQUEST_METHOD', '-')
  70.316 +        host = "%s:%s" % (environ.get('REMOTE_ADDR','-'), 
  70.317 +                          environ.get('REMOTE_PORT','-'))
  70.318 +
  70.319 +        time = self.log_date_time_string()
  70.320 +
  70.321 +        msg = self.format % (host, time, protocol, method, req, status)
  70.322 +        print >> outfile, utils.safestr(msg)
    71.1 Binary file OpenSecurity/install/web.py-0.37/web/httpserver.pyc has changed
    72.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    72.2 +++ b/OpenSecurity/install/web.py-0.37/web/net.py	Mon Dec 02 14:02:05 2013 +0100
    72.3 @@ -0,0 +1,193 @@
    72.4 +"""
    72.5 +Network Utilities
    72.6 +(from web.py)
    72.7 +"""
    72.8 +
    72.9 +__all__ = [
   72.10 +  "validipaddr", "validipport", "validip", "validaddr", 
   72.11 +  "urlquote",
   72.12 +  "httpdate", "parsehttpdate", 
   72.13 +  "htmlquote", "htmlunquote", "websafe",
   72.14 +]
   72.15 +
   72.16 +import urllib, time
   72.17 +try: import datetime
   72.18 +except ImportError: pass
   72.19 +
   72.20 +def validipaddr(address):
   72.21 +    """
   72.22 +    Returns True if `address` is a valid IPv4 address.
   72.23 +    
   72.24 +        >>> validipaddr('192.168.1.1')
   72.25 +        True
   72.26 +        >>> validipaddr('192.168.1.800')
   72.27 +        False
   72.28 +        >>> validipaddr('192.168.1')
   72.29 +        False
   72.30 +    """
   72.31 +    try:
   72.32 +        octets = address.split('.')
   72.33 +        if len(octets) != 4:
   72.34 +            return False
   72.35 +        for x in octets:
   72.36 +            if not (0 <= int(x) <= 255):
   72.37 +                return False
   72.38 +    except ValueError:
   72.39 +        return False
   72.40 +    return True
   72.41 +
   72.42 +def validipport(port):
   72.43 +    """
   72.44 +    Returns True if `port` is a valid IPv4 port.
   72.45 +    
   72.46 +        >>> validipport('9000')
   72.47 +        True
   72.48 +        >>> validipport('foo')
   72.49 +        False
   72.50 +        >>> validipport('1000000')
   72.51 +        False
   72.52 +    """
   72.53 +    try:
   72.54 +        if not (0 <= int(port) <= 65535):
   72.55 +            return False
   72.56 +    except ValueError:
   72.57 +        return False
   72.58 +    return True
   72.59 +
   72.60 +def validip(ip, defaultaddr="0.0.0.0", defaultport=8080):
   72.61 +    """Returns `(ip_address, port)` from string `ip_addr_port`"""
   72.62 +    addr = defaultaddr
   72.63 +    port = defaultport
   72.64 +    
   72.65 +    ip = ip.split(":", 1)
   72.66 +    if len(ip) == 1:
   72.67 +        if not ip[0]:
   72.68 +            pass
   72.69 +        elif validipaddr(ip[0]):
   72.70 +            addr = ip[0]
   72.71 +        elif validipport(ip[0]):
   72.72 +            port = int(ip[0])
   72.73 +        else:
   72.74 +            raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
   72.75 +    elif len(ip) == 2:
   72.76 +        addr, port = ip
   72.77 +        if not validipaddr(addr) and validipport(port):
   72.78 +            raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
   72.79 +        port = int(port)
   72.80 +    else:
   72.81 +        raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
   72.82 +    return (addr, port)
   72.83 +
   72.84 +def validaddr(string_):
   72.85 +    """
   72.86 +    Returns either (ip_address, port) or "/path/to/socket" from string_
   72.87 +    
   72.88 +        >>> validaddr('/path/to/socket')
   72.89 +        '/path/to/socket'
   72.90 +        >>> validaddr('8000')
   72.91 +        ('0.0.0.0', 8000)
   72.92 +        >>> validaddr('127.0.0.1')
   72.93 +        ('127.0.0.1', 8080)
   72.94 +        >>> validaddr('127.0.0.1:8000')
   72.95 +        ('127.0.0.1', 8000)
   72.96 +        >>> validaddr('fff')
   72.97 +        Traceback (most recent call last):
   72.98 +            ...
   72.99 +        ValueError: fff is not a valid IP address/port
  72.100 +    """
  72.101 +    if '/' in string_:
  72.102 +        return string_
  72.103 +    else:
  72.104 +        return validip(string_)
  72.105 +
  72.106 +def urlquote(val):
  72.107 +    """
  72.108 +    Quotes a string for use in a URL.
  72.109 +    
  72.110 +        >>> urlquote('://?f=1&j=1')
  72.111 +        '%3A//%3Ff%3D1%26j%3D1'
  72.112 +        >>> urlquote(None)
  72.113 +        ''
  72.114 +        >>> urlquote(u'\u203d')
  72.115 +        '%E2%80%BD'
  72.116 +    """
  72.117 +    if val is None: return ''
  72.118 +    if not isinstance(val, unicode): val = str(val)
  72.119 +    else: val = val.encode('utf-8')
  72.120 +    return urllib.quote(val)
  72.121 +
  72.122 +def httpdate(date_obj):
  72.123 +    """
  72.124 +    Formats a datetime object for use in HTTP headers.
  72.125 +    
  72.126 +        >>> import datetime
  72.127 +        >>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1))
  72.128 +        'Thu, 01 Jan 1970 01:01:01 GMT'
  72.129 +    """
  72.130 +    return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT")
  72.131 +
  72.132 +def parsehttpdate(string_):
  72.133 +    """
  72.134 +    Parses an HTTP date into a datetime object.
  72.135 +
  72.136 +        >>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT')
  72.137 +        datetime.datetime(1970, 1, 1, 1, 1, 1)
  72.138 +    """
  72.139 +    try:
  72.140 +        t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z")
  72.141 +    except ValueError:
  72.142 +        return None
  72.143 +    return datetime.datetime(*t[:6])
  72.144 +
  72.145 +def htmlquote(text):
  72.146 +    r"""
  72.147 +    Encodes `text` for raw use in HTML.
  72.148 +    
  72.149 +        >>> htmlquote(u"<'&\">")
  72.150 +        u'&lt;&#39;&amp;&quot;&gt;'
  72.151 +    """
  72.152 +    text = text.replace(u"&", u"&amp;") # Must be done first!
  72.153 +    text = text.replace(u"<", u"&lt;")
  72.154 +    text = text.replace(u">", u"&gt;")
  72.155 +    text = text.replace(u"'", u"&#39;")
  72.156 +    text = text.replace(u'"', u"&quot;")
  72.157 +    return text
  72.158 +
  72.159 +def htmlunquote(text):
  72.160 +    r"""
  72.161 +    Decodes `text` that's HTML quoted.
  72.162 +
  72.163 +        >>> htmlunquote(u'&lt;&#39;&amp;&quot;&gt;')
  72.164 +        u'<\'&">'
  72.165 +    """
  72.166 +    text = text.replace(u"&quot;", u'"')
  72.167 +    text = text.replace(u"&#39;", u"'")
  72.168 +    text = text.replace(u"&gt;", u">")
  72.169 +    text = text.replace(u"&lt;", u"<")
  72.170 +    text = text.replace(u"&amp;", u"&") # Must be done last!
  72.171 +    return text
  72.172 +    
  72.173 +def websafe(val):
  72.174 +    r"""Converts `val` so that it is safe for use in Unicode HTML.
  72.175 +
  72.176 +        >>> websafe("<'&\">")
  72.177 +        u'&lt;&#39;&amp;&quot;&gt;'
  72.178 +        >>> websafe(None)
  72.179 +        u''
  72.180 +        >>> websafe(u'\u203d')
  72.181 +        u'\u203d'
  72.182 +        >>> websafe('\xe2\x80\xbd')
  72.183 +        u'\u203d'
  72.184 +    """
  72.185 +    if val is None:
  72.186 +        return u''
  72.187 +    elif isinstance(val, str):
  72.188 +        val = val.decode('utf-8')
  72.189 +    elif not isinstance(val, unicode):
  72.190 +        val = unicode(val)
  72.191 +        
  72.192 +    return htmlquote(val)
  72.193 +
  72.194 +if __name__ == "__main__":
  72.195 +    import doctest
  72.196 +    doctest.testmod()
    73.1 Binary file OpenSecurity/install/web.py-0.37/web/net.pyc has changed
    74.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    74.2 +++ b/OpenSecurity/install/web.py-0.37/web/python23.py	Mon Dec 02 14:02:05 2013 +0100
    74.3 @@ -0,0 +1,46 @@
    74.4 +"""Python 2.3 compatabilty"""
    74.5 +import threading
    74.6 +
    74.7 +class threadlocal(object):
    74.8 +    """Implementation of threading.local for python2.3.
    74.9 +    """
   74.10 +    def __getattribute__(self, name):
   74.11 +        if name == "__dict__":
   74.12 +            return threadlocal._getd(self)
   74.13 +        else:
   74.14 +            try:
   74.15 +                return object.__getattribute__(self, name)
   74.16 +            except AttributeError:
   74.17 +                try:
   74.18 +                    return self.__dict__[name]
   74.19 +                except KeyError:
   74.20 +                    raise AttributeError, name
   74.21 +            
   74.22 +    def __setattr__(self, name, value):
   74.23 +        self.__dict__[name] = value
   74.24 +        
   74.25 +    def __delattr__(self, name):
   74.26 +        try:
   74.27 +            del self.__dict__[name]
   74.28 +        except KeyError:
   74.29 +            raise AttributeError, name
   74.30 +    
   74.31 +    def _getd(self):
   74.32 +        t = threading.currentThread()
   74.33 +        if not hasattr(t, '_d'):
   74.34 +            # using __dict__ of thread as thread local storage
   74.35 +            t._d = {}
   74.36 +        
   74.37 +        _id = id(self)
   74.38 +        # there could be multiple instances of threadlocal.
   74.39 +        # use id(self) as key
   74.40 +        if _id not in t._d:
   74.41 +            t._d[_id] = {}
   74.42 +        return t._d[_id]
   74.43 +        
   74.44 +if __name__ == '__main__':
   74.45 +     d = threadlocal()
   74.46 +     d.x = 1
   74.47 +     print d.__dict__
   74.48 +     print d.x
   74.49 +     
   74.50 \ No newline at end of file
    75.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    75.2 +++ b/OpenSecurity/install/web.py-0.37/web/session.py	Mon Dec 02 14:02:05 2013 +0100
    75.3 @@ -0,0 +1,358 @@
    75.4 +"""
    75.5 +Session Management
    75.6 +(from web.py)
    75.7 +"""
    75.8 +
    75.9 +import os, time, datetime, random, base64
   75.10 +import os.path
   75.11 +from copy import deepcopy
   75.12 +try:
   75.13 +    import cPickle as pickle
   75.14 +except ImportError:
   75.15 +    import pickle
   75.16 +try:
   75.17 +    import hashlib
   75.18 +    sha1 = hashlib.sha1
   75.19 +except ImportError:
   75.20 +    import sha
   75.21 +    sha1 = sha.new
   75.22 +
   75.23 +import utils
   75.24 +import webapi as web
   75.25 +
   75.26 +__all__ = [
   75.27 +    'Session', 'SessionExpired',
   75.28 +    'Store', 'DiskStore', 'DBStore',
   75.29 +]
   75.30 +
   75.31 +web.config.session_parameters = utils.storage({
   75.32 +    'cookie_name': 'webpy_session_id',
   75.33 +    'cookie_domain': None,
   75.34 +    'cookie_path' : None,
   75.35 +    'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds
   75.36 +    'ignore_expiry': True,
   75.37 +    'ignore_change_ip': True,
   75.38 +    'secret_key': 'fLjUfxqXtfNoIldA0A0J',
   75.39 +    'expired_message': 'Session expired',
   75.40 +    'httponly': True,
   75.41 +    'secure': False
   75.42 +})
   75.43 +
   75.44 +class SessionExpired(web.HTTPError): 
   75.45 +    def __init__(self, message):
   75.46 +        web.HTTPError.__init__(self, '200 OK', {}, data=message)
   75.47 +
   75.48 +class Session(object):
   75.49 +    """Session management for web.py
   75.50 +    """
   75.51 +    __slots__ = [
   75.52 +        "store", "_initializer", "_last_cleanup_time", "_config", "_data", 
   75.53 +        "__getitem__", "__setitem__", "__delitem__"
   75.54 +    ]
   75.55 +
   75.56 +    def __init__(self, app, store, initializer=None):
   75.57 +        self.store = store
   75.58 +        self._initializer = initializer
   75.59 +        self._last_cleanup_time = 0
   75.60 +        self._config = utils.storage(web.config.session_parameters)
   75.61 +        self._data = utils.threadeddict()
   75.62 +        
   75.63 +        self.__getitem__ = self._data.__getitem__
   75.64 +        self.__setitem__ = self._data.__setitem__
   75.65 +        self.__delitem__ = self._data.__delitem__
   75.66 +
   75.67 +        if app:
   75.68 +            app.add_processor(self._processor)
   75.69 +
   75.70 +    def __contains__(self, name):
   75.71 +        return name in self._data
   75.72 +
   75.73 +    def __getattr__(self, name):
   75.74 +        return getattr(self._data, name)
   75.75 +    
   75.76 +    def __setattr__(self, name, value):
   75.77 +        if name in self.__slots__:
   75.78 +            object.__setattr__(self, name, value)
   75.79 +        else:
   75.80 +            setattr(self._data, name, value)
   75.81 +        
   75.82 +    def __delattr__(self, name):
   75.83 +        delattr(self._data, name)
   75.84 +
   75.85 +    def _processor(self, handler):
   75.86 +        """Application processor to setup session for every request"""
   75.87 +        self._cleanup()
   75.88 +        self._load()
   75.89 +
   75.90 +        try:
   75.91 +            return handler()
   75.92 +        finally:
   75.93 +            self._save()
   75.94 +
   75.95 +    def _load(self):
   75.96 +        """Load the session from the store, by the id from cookie"""
   75.97 +        cookie_name = self._config.cookie_name
   75.98 +        cookie_domain = self._config.cookie_domain
   75.99 +        cookie_path = self._config.cookie_path
  75.100 +        httponly = self._config.httponly
  75.101 +        self.session_id = web.cookies().get(cookie_name)
  75.102 +
  75.103 +        # protection against session_id tampering
  75.104 +        if self.session_id and not self._valid_session_id(self.session_id):
  75.105 +            self.session_id = None
  75.106 +
  75.107 +        self._check_expiry()
  75.108 +        if self.session_id:
  75.109 +            d = self.store[self.session_id]
  75.110 +            self.update(d)
  75.111 +            self._validate_ip()
  75.112 +        
  75.113 +        if not self.session_id:
  75.114 +            self.session_id = self._generate_session_id()
  75.115 +
  75.116 +            if self._initializer:
  75.117 +                if isinstance(self._initializer, dict):
  75.118 +                    self.update(deepcopy(self._initializer))
  75.119 +                elif hasattr(self._initializer, '__call__'):
  75.120 +                    self._initializer()
  75.121 + 
  75.122 +        self.ip = web.ctx.ip
  75.123 +
  75.124 +    def _check_expiry(self):
  75.125 +        # check for expiry
  75.126 +        if self.session_id and self.session_id not in self.store:
  75.127 +            if self._config.ignore_expiry:
  75.128 +                self.session_id = None
  75.129 +            else:
  75.130 +                return self.expired()
  75.131 +
  75.132 +    def _validate_ip(self):
  75.133 +        # check for change of IP
  75.134 +        if self.session_id and self.get('ip', None) != web.ctx.ip:
  75.135 +            if not self._config.ignore_change_ip:
  75.136 +               return self.expired() 
  75.137 +    
  75.138 +    def _save(self):
  75.139 +        if not self.get('_killed'):
  75.140 +            self._setcookie(self.session_id)
  75.141 +            self.store[self.session_id] = dict(self._data)
  75.142 +        else:
  75.143 +            self._setcookie(self.session_id, expires=-1)
  75.144 +            
  75.145 +    def _setcookie(self, session_id, expires='', **kw):
  75.146 +        cookie_name = self._config.cookie_name
  75.147 +        cookie_domain = self._config.cookie_domain
  75.148 +        cookie_path = self._config.cookie_path
  75.149 +        httponly = self._config.httponly
  75.150 +        secure = self._config.secure
  75.151 +        web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path)
  75.152 +    
  75.153 +    def _generate_session_id(self):
  75.154 +        """Generate a random id for session"""
  75.155 +
  75.156 +        while True:
  75.157 +            rand = os.urandom(16)
  75.158 +            now = time.time()
  75.159 +            secret_key = self._config.secret_key
  75.160 +            session_id = sha1("%s%s%s%s" %(rand, now, utils.safestr(web.ctx.ip), secret_key))
  75.161 +            session_id = session_id.hexdigest()
  75.162 +            if session_id not in self.store:
  75.163 +                break
  75.164 +        return session_id
  75.165 +
  75.166 +    def _valid_session_id(self, session_id):
  75.167 +        rx = utils.re_compile('^[0-9a-fA-F]+$')
  75.168 +        return rx.match(session_id)
  75.169 +        
  75.170 +    def _cleanup(self):
  75.171 +        """Cleanup the stored sessions"""
  75.172 +        current_time = time.time()
  75.173 +        timeout = self._config.timeout
  75.174 +        if current_time - self._last_cleanup_time > timeout:
  75.175 +            self.store.cleanup(timeout)
  75.176 +            self._last_cleanup_time = current_time
  75.177 +
  75.178 +    def expired(self):
  75.179 +        """Called when an expired session is atime"""
  75.180 +        self._killed = True
  75.181 +        self._save()
  75.182 +        raise SessionExpired(self._config.expired_message)
  75.183 + 
  75.184 +    def kill(self):
  75.185 +        """Kill the session, make it no longer available"""
  75.186 +        del self.store[self.session_id]
  75.187 +        self._killed = True
  75.188 +
  75.189 +class Store:
  75.190 +    """Base class for session stores"""
  75.191 +
  75.192 +    def __contains__(self, key):
  75.193 +        raise NotImplementedError
  75.194 +
  75.195 +    def __getitem__(self, key):
  75.196 +        raise NotImplementedError
  75.197 +
  75.198 +    def __setitem__(self, key, value):
  75.199 +        raise NotImplementedError
  75.200 +
  75.201 +    def cleanup(self, timeout):
  75.202 +        """removes all the expired sessions"""
  75.203 +        raise NotImplementedError
  75.204 +
  75.205 +    def encode(self, session_dict):
  75.206 +        """encodes session dict as a string"""
  75.207 +        pickled = pickle.dumps(session_dict)
  75.208 +        return base64.encodestring(pickled)
  75.209 +
  75.210 +    def decode(self, session_data):
  75.211 +        """decodes the data to get back the session dict """
  75.212 +        pickled = base64.decodestring(session_data)
  75.213 +        return pickle.loads(pickled)
  75.214 +
  75.215 +class DiskStore(Store):
  75.216 +    """
  75.217 +    Store for saving a session on disk.
  75.218 +
  75.219 +        >>> import tempfile
  75.220 +        >>> root = tempfile.mkdtemp()
  75.221 +        >>> s = DiskStore(root)
  75.222 +        >>> s['a'] = 'foo'
  75.223 +        >>> s['a']
  75.224 +        'foo'
  75.225 +        >>> time.sleep(0.01)
  75.226 +        >>> s.cleanup(0.01)
  75.227 +        >>> s['a']
  75.228 +        Traceback (most recent call last):
  75.229 +            ...
  75.230 +        KeyError: 'a'
  75.231 +    """
  75.232 +    def __init__(self, root):
  75.233 +        # if the storage root doesn't exists, create it.
  75.234 +        if not os.path.exists(root):
  75.235 +            os.makedirs(
  75.236 +                    os.path.abspath(root)
  75.237 +                    )
  75.238 +        self.root = root
  75.239 +
  75.240 +    def _get_path(self, key):
  75.241 +        if os.path.sep in key: 
  75.242 +            raise ValueError, "Bad key: %s" % repr(key)
  75.243 +        return os.path.join(self.root, key)
  75.244 +    
  75.245 +    def __contains__(self, key):
  75.246 +        path = self._get_path(key)
  75.247 +        return os.path.exists(path)
  75.248 +
  75.249 +    def __getitem__(self, key):
  75.250 +        path = self._get_path(key)
  75.251 +        if os.path.exists(path): 
  75.252 +            pickled = open(path).read()
  75.253 +            return self.decode(pickled)
  75.254 +        else:
  75.255 +            raise KeyError, key
  75.256 +
  75.257 +    def __setitem__(self, key, value):
  75.258 +        path = self._get_path(key)
  75.259 +        pickled = self.encode(value)    
  75.260 +        try:
  75.261 +            f = open(path, 'w')
  75.262 +            try:
  75.263 +                f.write(pickled)
  75.264 +            finally: 
  75.265 +                f.close()
  75.266 +        except IOError:
  75.267 +            pass
  75.268 +
  75.269 +    def __delitem__(self, key):
  75.270 +        path = self._get_path(key)
  75.271 +        if os.path.exists(path):
  75.272 +            os.remove(path)
  75.273 +    
  75.274 +    def cleanup(self, timeout):
  75.275 +        now = time.time()
  75.276 +        for f in os.listdir(self.root):
  75.277 +            path = self._get_path(f)
  75.278 +            atime = os.stat(path).st_atime
  75.279 +            if now - atime > timeout :
  75.280 +                os.remove(path)
  75.281 +
  75.282 +class DBStore(Store):
  75.283 +    """Store for saving a session in database
  75.284 +    Needs a table with the following columns:
  75.285 +
  75.286 +        session_id CHAR(128) UNIQUE NOT NULL,
  75.287 +        atime DATETIME NOT NULL default current_timestamp,
  75.288 +        data TEXT
  75.289 +    """
  75.290 +    def __init__(self, db, table_name):
  75.291 +        self.db = db
  75.292 +        self.table = table_name
  75.293 +    
  75.294 +    def __contains__(self, key):
  75.295 +        data = self.db.select(self.table, where="session_id=$key", vars=locals())
  75.296 +        return bool(list(data)) 
  75.297 +
  75.298 +    def __getitem__(self, key):
  75.299 +        now = datetime.datetime.now()
  75.300 +        try:
  75.301 +            s = self.db.select(self.table, where="session_id=$key", vars=locals())[0]
  75.302 +            self.db.update(self.table, where="session_id=$key", atime=now, vars=locals())
  75.303 +        except IndexError:
  75.304 +            raise KeyError
  75.305 +        else:
  75.306 +            return self.decode(s.data)
  75.307 +
  75.308 +    def __setitem__(self, key, value):
  75.309 +        pickled = self.encode(value)
  75.310 +        now = datetime.datetime.now()
  75.311 +        if key in self:
  75.312 +            self.db.update(self.table, where="session_id=$key", data=pickled, vars=locals())
  75.313 +        else:
  75.314 +            self.db.insert(self.table, False, session_id=key, data=pickled )
  75.315 +                
  75.316 +    def __delitem__(self, key):
  75.317 +        self.db.delete(self.table, where="session_id=$key", vars=locals())
  75.318 +
  75.319 +    def cleanup(self, timeout):
  75.320 +        timeout = datetime.timedelta(timeout/(24.0*60*60)) #timedelta takes numdays as arg
  75.321 +        last_allowed_time = datetime.datetime.now() - timeout
  75.322 +        self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals())
  75.323 +
  75.324 +class ShelfStore:
  75.325 +    """Store for saving session using `shelve` module.
  75.326 +
  75.327 +        import shelve
  75.328 +        store = ShelfStore(shelve.open('session.shelf'))
  75.329 +
  75.330 +    XXX: is shelve thread-safe?
  75.331 +    """
  75.332 +    def __init__(self, shelf):
  75.333 +        self.shelf = shelf
  75.334 +
  75.335 +    def __contains__(self, key):
  75.336 +        return key in self.shelf
  75.337 +
  75.338 +    def __getitem__(self, key):
  75.339 +        atime, v = self.shelf[key]
  75.340 +        self[key] = v # update atime
  75.341 +        return v
  75.342 +
  75.343 +    def __setitem__(self, key, value):
  75.344 +        self.shelf[key] = time.time(), value
  75.345 +        
  75.346 +    def __delitem__(self, key):
  75.347 +        try:
  75.348 +            del self.shelf[key]
  75.349 +        except KeyError:
  75.350 +            pass
  75.351 +
  75.352 +    def cleanup(self, timeout):
  75.353 +        now = time.time()
  75.354 +        for k in self.shelf.keys():
  75.355 +            atime, v = self.shelf[k]
  75.356 +            if now - atime > timeout :
  75.357 +                del self[k]
  75.358 +
  75.359 +if __name__ == '__main__' :
  75.360 +    import doctest
  75.361 +    doctest.testmod()
    76.1 Binary file OpenSecurity/install/web.py-0.37/web/session.pyc has changed
    77.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    77.2 +++ b/OpenSecurity/install/web.py-0.37/web/template.py	Mon Dec 02 14:02:05 2013 +0100
    77.3 @@ -0,0 +1,1515 @@
    77.4 +"""
    77.5 +simple, elegant templating
    77.6 +(part of web.py)
    77.7 +
    77.8 +Template design:
    77.9 +
   77.10 +Template string is split into tokens and the tokens are combined into nodes. 
   77.11 +Parse tree is a nodelist. TextNode and ExpressionNode are simple nodes and 
   77.12 +for-loop, if-loop etc are block nodes, which contain multiple child nodes. 
   77.13 +
   77.14 +Each node can emit some python string. python string emitted by the 
   77.15 +root node is validated for safeeval and executed using python in the given environment.
   77.16 +
   77.17 +Enough care is taken to make sure the generated code and the template has line to line match, 
   77.18 +so that the error messages can point to exact line number in template. (It doesn't work in some cases still.)
   77.19 +
   77.20 +Grammar:
   77.21 +
   77.22 +    template -> defwith sections 
   77.23 +    defwith -> '$def with (' arguments ')' | ''
   77.24 +    sections -> section*
   77.25 +    section -> block | assignment | line
   77.26 +
   77.27 +    assignment -> '$ ' <assignment expression>
   77.28 +    line -> (text|expr)*
   77.29 +    text -> <any characters other than $>
   77.30 +    expr -> '$' pyexpr | '$(' pyexpr ')' | '${' pyexpr '}'
   77.31 +    pyexpr -> <python expression>
   77.32 +"""
   77.33 +
   77.34 +__all__ = [
   77.35 +    "Template",
   77.36 +    "Render", "render", "frender",
   77.37 +    "ParseError", "SecurityError",
   77.38 +    "test"
   77.39 +]
   77.40 +
   77.41 +import tokenize
   77.42 +import os
   77.43 +import sys
   77.44 +import glob
   77.45 +import re
   77.46 +from UserDict import DictMixin
   77.47 +import warnings
   77.48 +
   77.49 +from utils import storage, safeunicode, safestr, re_compile
   77.50 +from webapi import config
   77.51 +from net import websafe
   77.52 +
   77.53 +def splitline(text):
   77.54 +    r"""
   77.55 +    Splits the given text at newline.
   77.56 +    
   77.57 +        >>> splitline('foo\nbar')
   77.58 +        ('foo\n', 'bar')
   77.59 +        >>> splitline('foo')
   77.60 +        ('foo', '')
   77.61 +        >>> splitline('')
   77.62 +        ('', '')
   77.63 +    """
   77.64 +    index = text.find('\n') + 1
   77.65 +    if index:
   77.66 +        return text[:index], text[index:]
   77.67 +    else:
   77.68 +        return text, ''
   77.69 +
   77.70 +class Parser:
   77.71 +    """Parser Base.
   77.72 +    """
   77.73 +    def __init__(self):
   77.74 +        self.statement_nodes = STATEMENT_NODES
   77.75 +        self.keywords = KEYWORDS
   77.76 +
   77.77 +    def parse(self, text, name="<template>"):
   77.78 +        self.text = text
   77.79 +        self.name = name
   77.80 +        
   77.81 +        defwith, text = self.read_defwith(text)
   77.82 +        suite = self.read_suite(text)
   77.83 +        return DefwithNode(defwith, suite)
   77.84 +
   77.85 +    def read_defwith(self, text):
   77.86 +        if text.startswith('$def with'):
   77.87 +            defwith, text = splitline(text)
   77.88 +            defwith = defwith[1:].strip() # strip $ and spaces
   77.89 +            return defwith, text
   77.90 +        else:
   77.91 +            return '', text
   77.92 +    
   77.93 +    def read_section(self, text):
   77.94 +        r"""Reads one section from the given text.
   77.95 +        
   77.96 +        section -> block | assignment | line
   77.97 +        
   77.98 +            >>> read_section = Parser().read_section
   77.99 +            >>> read_section('foo\nbar\n')
  77.100 +            (<line: [t'foo\n']>, 'bar\n')
  77.101 +            >>> read_section('$ a = b + 1\nfoo\n')
  77.102 +            (<assignment: 'a = b + 1'>, 'foo\n')
  77.103 +            
  77.104 +        read_section('$for in range(10):\n    hello $i\nfoo)
  77.105 +        """
  77.106 +        if text.lstrip(' ').startswith('$'):
  77.107 +            index = text.index('$')
  77.108 +            begin_indent, text2 = text[:index], text[index+1:]
  77.109 +            ahead = self.python_lookahead(text2)
  77.110 +            
  77.111 +            if ahead == 'var':
  77.112 +                return self.read_var(text2)
  77.113 +            elif ahead in self.statement_nodes:
  77.114 +                return self.read_block_section(text2, begin_indent)
  77.115 +            elif ahead in self.keywords:
  77.116 +                return self.read_keyword(text2)
  77.117 +            elif ahead.strip() == '':
  77.118 +                # assignments starts with a space after $
  77.119 +                # ex: $ a = b + 2
  77.120 +                return self.read_assignment(text2)
  77.121 +        return self.readline(text)
  77.122 +        
  77.123 +    def read_var(self, text):
  77.124 +        r"""Reads a var statement.
  77.125 +        
  77.126 +            >>> read_var = Parser().read_var
  77.127 +            >>> read_var('var x=10\nfoo')
  77.128 +            (<var: x = 10>, 'foo')
  77.129 +            >>> read_var('var x: hello $name\nfoo')
  77.130 +            (<var: x = join_(u'hello ', escape_(name, True))>, 'foo')
  77.131 +        """
  77.132 +        line, text = splitline(text)
  77.133 +        tokens = self.python_tokens(line)
  77.134 +        if len(tokens) < 4:
  77.135 +            raise SyntaxError('Invalid var statement')
  77.136 +            
  77.137 +        name = tokens[1]
  77.138 +        sep = tokens[2]
  77.139 +        value = line.split(sep, 1)[1].strip()
  77.140 +        
  77.141 +        if sep == '=':
  77.142 +            pass # no need to process value
  77.143 +        elif sep == ':': 
  77.144 +            #@@ Hack for backward-compatability
  77.145 +            if tokens[3] == '\n': # multi-line var statement
  77.146 +                block, text = self.read_indented_block(text, '    ')
  77.147 +                lines = [self.readline(x)[0] for x in block.splitlines()]
  77.148 +                nodes = []
  77.149 +                for x in lines:
  77.150 +                    nodes.extend(x.nodes)
  77.151 +                    nodes.append(TextNode('\n'))         
  77.152 +            else: # single-line var statement
  77.153 +                linenode, _ = self.readline(value)
  77.154 +                nodes = linenode.nodes                
  77.155 +            parts = [node.emit('') for node in nodes]
  77.156 +            value = "join_(%s)" % ", ".join(parts)
  77.157 +        else:
  77.158 +            raise SyntaxError('Invalid var statement')
  77.159 +        return VarNode(name, value), text
  77.160 +                    
  77.161 +    def read_suite(self, text):
  77.162 +        r"""Reads section by section till end of text.
  77.163 +        
  77.164 +            >>> read_suite = Parser().read_suite
  77.165 +            >>> read_suite('hello $name\nfoo\n')
  77.166 +            [<line: [t'hello ', $name, t'\n']>, <line: [t'foo\n']>]
  77.167 +        """
  77.168 +        sections = []
  77.169 +        while text:
  77.170 +            section, text = self.read_section(text)
  77.171 +            sections.append(section)
  77.172 +        return SuiteNode(sections)
  77.173 +    
  77.174 +    def readline(self, text):
  77.175 +        r"""Reads one line from the text. Newline is supressed if the line ends with \.
  77.176 +        
  77.177 +            >>> readline = Parser().readline
  77.178 +            >>> readline('hello $name!\nbye!')
  77.179 +            (<line: [t'hello ', $name, t'!\n']>, 'bye!')
  77.180 +            >>> readline('hello $name!\\\nbye!')
  77.181 +            (<line: [t'hello ', $name, t'!']>, 'bye!')
  77.182 +            >>> readline('$f()\n\n')
  77.183 +            (<line: [$f(), t'\n']>, '\n')
  77.184 +        """
  77.185 +        line, text = splitline(text)
  77.186 +
  77.187 +        # supress new line if line ends with \
  77.188 +        if line.endswith('\\\n'):
  77.189 +            line = line[:-2]
  77.190 +                
  77.191 +        nodes = []
  77.192 +        while line:
  77.193 +            node, line = self.read_node(line)
  77.194 +            nodes.append(node)
  77.195 +            
  77.196 +        return LineNode(nodes), text
  77.197 +
  77.198 +    def read_node(self, text):
  77.199 +        r"""Reads a node from the given text and returns the node and remaining text.
  77.200 +
  77.201 +            >>> read_node = Parser().read_node
  77.202 +            >>> read_node('hello $name')
  77.203 +            (t'hello ', '$name')
  77.204 +            >>> read_node('$name')
  77.205 +            ($name, '')
  77.206 +        """
  77.207 +        if text.startswith('$$'):
  77.208 +            return TextNode('$'), text[2:]
  77.209 +        elif text.startswith('$#'): # comment
  77.210 +            line, text = splitline(text)
  77.211 +            return TextNode('\n'), text
  77.212 +        elif text.startswith('$'):
  77.213 +            text = text[1:] # strip $
  77.214 +            if text.startswith(':'):
  77.215 +                escape = False
  77.216 +                text = text[1:] # strip :
  77.217 +            else:
  77.218 +                escape = True
  77.219 +            return self.read_expr(text, escape=escape)
  77.220 +        else:
  77.221 +            return self.read_text(text)
  77.222 +    
  77.223 +    def read_text(self, text):
  77.224 +        r"""Reads a text node from the given text.
  77.225 +        
  77.226 +            >>> read_text = Parser().read_text
  77.227 +            >>> read_text('hello $name')
  77.228 +            (t'hello ', '$name')
  77.229 +        """
  77.230 +        index = text.find('$')
  77.231 +        if index < 0:
  77.232 +            return TextNode(text), ''
  77.233 +        else:
  77.234 +            return TextNode(text[:index]), text[index:]
  77.235 +            
  77.236 +    def read_keyword(self, text):
  77.237 +        line, text = splitline(text)
  77.238 +        return StatementNode(line.strip() + "\n"), text
  77.239 +
  77.240 +    def read_expr(self, text, escape=True):
  77.241 +        """Reads a python expression from the text and returns the expression and remaining text.
  77.242 +
  77.243 +        expr -> simple_expr | paren_expr
  77.244 +        simple_expr -> id extended_expr
  77.245 +        extended_expr -> attr_access | paren_expr extended_expr | ''
  77.246 +        attr_access -> dot id extended_expr
  77.247 +        paren_expr -> [ tokens ] | ( tokens ) | { tokens }
  77.248 +     
  77.249 +            >>> read_expr = Parser().read_expr
  77.250 +            >>> read_expr("name")
  77.251 +            ($name, '')
  77.252 +            >>> read_expr("a.b and c")
  77.253 +            ($a.b, ' and c')
  77.254 +            >>> read_expr("a. b")
  77.255 +            ($a, '. b')
  77.256 +            >>> read_expr("name</h1>")
  77.257 +            ($name, '</h1>')
  77.258 +            >>> read_expr("(limit)ing")
  77.259 +            ($(limit), 'ing')
  77.260 +            >>> read_expr('a[1, 2][:3].f(1+2, "weird string[).", 3 + 4) done.')
  77.261 +            ($a[1, 2][:3].f(1+2, "weird string[).", 3 + 4), ' done.')
  77.262 +        """
  77.263 +        def simple_expr():
  77.264 +            identifier()
  77.265 +            extended_expr()
  77.266 +        
  77.267 +        def identifier():
  77.268 +            tokens.next()
  77.269 +        
  77.270 +        def extended_expr():
  77.271 +            lookahead = tokens.lookahead()
  77.272 +            if lookahead is None:
  77.273 +                return
  77.274 +            elif lookahead.value == '.':
  77.275 +                attr_access()
  77.276 +            elif lookahead.value in parens:
  77.277 +                paren_expr()
  77.278 +                extended_expr()
  77.279 +            else:
  77.280 +                return
  77.281 +        
  77.282 +        def attr_access():
  77.283 +            from token import NAME # python token constants
  77.284 +            dot = tokens.lookahead()
  77.285 +            if tokens.lookahead2().type == NAME:
  77.286 +                tokens.next() # consume dot
  77.287 +                identifier()
  77.288 +                extended_expr()
  77.289 +        
  77.290 +        def paren_expr():
  77.291 +            begin = tokens.next().value
  77.292 +            end = parens[begin]
  77.293 +            while True:
  77.294 +                if tokens.lookahead().value in parens:
  77.295 +                    paren_expr()
  77.296 +                else:
  77.297 +                    t = tokens.next()
  77.298 +                    if t.value == end:
  77.299 +                        break
  77.300 +            return
  77.301 +
  77.302 +        parens = {
  77.303 +            "(": ")",
  77.304 +            "[": "]",
  77.305 +            "{": "}"
  77.306 +        }
  77.307 +        
  77.308 +        def get_tokens(text):
  77.309 +            """tokenize text using python tokenizer.
  77.310 +            Python tokenizer ignores spaces, but they might be important in some cases. 
  77.311 +            This function introduces dummy space tokens when it identifies any ignored space.
  77.312 +            Each token is a storage object containing type, value, begin and end.
  77.313 +            """
  77.314 +            readline = iter([text]).next
  77.315 +            end = None
  77.316 +            for t in tokenize.generate_tokens(readline):
  77.317 +                t = storage(type=t[0], value=t[1], begin=t[2], end=t[3])
  77.318 +                if end is not None and end != t.begin:
  77.319 +                    _, x1 = end
  77.320 +                    _, x2 = t.begin
  77.321 +                    yield storage(type=-1, value=text[x1:x2], begin=end, end=t.begin)
  77.322 +                end = t.end
  77.323 +                yield t
  77.324 +                
  77.325 +        class BetterIter:
  77.326 +            """Iterator like object with 2 support for 2 look aheads."""
  77.327 +            def __init__(self, items):
  77.328 +                self.iteritems = iter(items)
  77.329 +                self.items = []
  77.330 +                self.position = 0
  77.331 +                self.current_item = None
  77.332 +            
  77.333 +            def lookahead(self):
  77.334 +                if len(self.items) <= self.position:
  77.335 +                    self.items.append(self._next())
  77.336 +                return self.items[self.position]
  77.337 +
  77.338 +            def _next(self):
  77.339 +                try:
  77.340 +                    return self.iteritems.next()
  77.341 +                except StopIteration:
  77.342 +                    return None
  77.343 +                
  77.344 +            def lookahead2(self):
  77.345 +                if len(self.items) <= self.position+1:
  77.346 +                    self.items.append(self._next())
  77.347 +                return self.items[self.position+1]
  77.348 +                    
  77.349 +            def next(self):
  77.350 +                self.current_item = self.lookahead()
  77.351 +                self.position += 1
  77.352 +                return self.current_item
  77.353 +
  77.354 +        tokens = BetterIter(get_tokens(text))
  77.355 +                
  77.356 +        if tokens.lookahead().value in parens:
  77.357 +            paren_expr()
  77.358 +        else:
  77.359 +            simple_expr()
  77.360 +        row, col = tokens.current_item.end
  77.361 +        return ExpressionNode(text[:col], escape=escape), text[col:]    
  77.362 +
  77.363 +    def read_assignment(self, text):
  77.364 +        r"""Reads assignment statement from text.
  77.365 +    
  77.366 +            >>> read_assignment = Parser().read_assignment
  77.367 +            >>> read_assignment('a = b + 1\nfoo')
  77.368 +            (<assignment: 'a = b + 1'>, 'foo')
  77.369 +        """
  77.370 +        line, text = splitline(text)
  77.371 +        return AssignmentNode(line.strip()), text
  77.372 +    
  77.373 +    def python_lookahead(self, text):
  77.374 +        """Returns the first python token from the given text.
  77.375 +        
  77.376 +            >>> python_lookahead = Parser().python_lookahead
  77.377 +            >>> python_lookahead('for i in range(10):')
  77.378 +            'for'
  77.379 +            >>> python_lookahead('else:')
  77.380 +            'else'
  77.381 +            >>> python_lookahead(' x = 1')
  77.382 +            ' '
  77.383 +        """
  77.384 +        readline = iter([text]).next
  77.385 +        tokens = tokenize.generate_tokens(readline)
  77.386 +        return tokens.next()[1]
  77.387 +        
  77.388 +    def python_tokens(self, text):
  77.389 +        readline = iter([text]).next
  77.390 +        tokens = tokenize.generate_tokens(readline)
  77.391 +        return [t[1] for t in tokens]
  77.392 +        
  77.393 +    def read_indented_block(self, text, indent):
  77.394 +        r"""Read a block of text. A block is what typically follows a for or it statement.
  77.395 +        It can be in the same line as that of the statement or an indented block.
  77.396 +
  77.397 +            >>> read_indented_block = Parser().read_indented_block
  77.398 +            >>> read_indented_block('  a\n  b\nc', '  ')
  77.399 +            ('a\nb\n', 'c')
  77.400 +            >>> read_indented_block('  a\n    b\n  c\nd', '  ')
  77.401 +            ('a\n  b\nc\n', 'd')
  77.402 +            >>> read_indented_block('  a\n\n    b\nc', '  ')
  77.403 +            ('a\n\n  b\n', 'c')
  77.404 +        """
  77.405 +        if indent == '':
  77.406 +            return '', text
  77.407 +            
  77.408 +        block = ""
  77.409 +        while text:
  77.410 +            line, text2 = splitline(text)
  77.411 +            if line.strip() == "":
  77.412 +                block += '\n'
  77.413 +            elif line.startswith(indent):
  77.414 +                block += line[len(indent):]
  77.415 +            else:
  77.416 +                break
  77.417 +            text = text2
  77.418 +        return block, text
  77.419 +
  77.420 +    def read_statement(self, text):
  77.421 +        r"""Reads a python statement.
  77.422 +        
  77.423 +            >>> read_statement = Parser().read_statement
  77.424 +            >>> read_statement('for i in range(10): hello $name')
  77.425 +            ('for i in range(10):', ' hello $name')
  77.426 +        """
  77.427 +        tok = PythonTokenizer(text)
  77.428 +        tok.consume_till(':')
  77.429 +        return text[:tok.index], text[tok.index:]
  77.430 +        
  77.431 +    def read_block_section(self, text, begin_indent=''):
  77.432 +        r"""
  77.433 +            >>> read_block_section = Parser().read_block_section
  77.434 +            >>> read_block_section('for i in range(10): hello $i\nfoo')
  77.435 +            (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, 'foo')
  77.436 +            >>> read_block_section('for i in range(10):\n        hello $i\n    foo', begin_indent='    ')
  77.437 +            (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, '    foo')
  77.438 +            >>> read_block_section('for i in range(10):\n  hello $i\nfoo')
  77.439 +            (<block: 'for i in range(10):', [<line: [t'hello ', $i, t'\n']>]>, 'foo')
  77.440 +        """
  77.441 +        line, text = splitline(text)
  77.442 +        stmt, line = self.read_statement(line)
  77.443 +        keyword = self.python_lookahead(stmt)
  77.444 +        
  77.445 +        # if there is some thing left in the line
  77.446 +        if line.strip():
  77.447 +            block = line.lstrip()
  77.448 +        else:
  77.449 +            def find_indent(text):
  77.450 +                rx = re_compile('  +')
  77.451 +                match = rx.match(text)    
  77.452 +                first_indent = match and match.group(0)
  77.453 +                return first_indent or ""
  77.454 +
  77.455 +            # find the indentation of the block by looking at the first line
  77.456 +            first_indent = find_indent(text)[len(begin_indent):]
  77.457 +
  77.458 +            #TODO: fix this special case
  77.459 +            if keyword == "code":
  77.460 +                indent = begin_indent + first_indent
  77.461 +            else:
  77.462 +                indent = begin_indent + min(first_indent, INDENT)
  77.463 +            
  77.464 +            block, text = self.read_indented_block(text, indent)
  77.465 +            
  77.466 +        return self.create_block_node(keyword, stmt, block, begin_indent), text
  77.467 +        
  77.468 +    def create_block_node(self, keyword, stmt, block, begin_indent):
  77.469 +        if keyword in self.statement_nodes:
  77.470 +            return self.statement_nodes[keyword](stmt, block, begin_indent)
  77.471 +        else:
  77.472 +            raise ParseError, 'Unknown statement: %s' % repr(keyword)
  77.473 +        
  77.474 +class PythonTokenizer:
  77.475 +    """Utility wrapper over python tokenizer."""
  77.476 +    def __init__(self, text):
  77.477 +        self.text = text
  77.478 +        readline = iter([text]).next
  77.479 +        self.tokens = tokenize.generate_tokens(readline)
  77.480 +        self.index = 0
  77.481 +        
  77.482 +    def consume_till(self, delim):        
  77.483 +        """Consumes tokens till colon.
  77.484 +        
  77.485 +            >>> tok = PythonTokenizer('for i in range(10): hello $i')
  77.486 +            >>> tok.consume_till(':')
  77.487 +            >>> tok.text[:tok.index]
  77.488 +            'for i in range(10):'
  77.489 +            >>> tok.text[tok.index:]
  77.490 +            ' hello $i'
  77.491 +        """
  77.492 +        try:
  77.493 +            while True:
  77.494 +                t = self.next()
  77.495 +                if t.value == delim:
  77.496 +                    break
  77.497 +                elif t.value == '(':
  77.498 +                    self.consume_till(')')
  77.499 +                elif t.value == '[':
  77.500 +                    self.consume_till(']')
  77.501 +                elif t.value == '{':
  77.502 +                    self.consume_till('}')
  77.503 +
  77.504 +                # if end of line is found, it is an exception.
  77.505 +                # Since there is no easy way to report the line number,
  77.506 +                # leave the error reporting to the python parser later  
  77.507 +                #@@ This should be fixed.
  77.508 +                if t.value == '\n':
  77.509 +                    break
  77.510 +        except:
  77.511 +            #raise ParseError, "Expected %s, found end of line." % repr(delim)
  77.512 +
  77.513 +            # raising ParseError doesn't show the line number. 
  77.514 +            # if this error is ignored, then it will be caught when compiling the python code.
  77.515 +            return
  77.516 +    
  77.517 +    def next(self):
  77.518 +        type, t, begin, end, line = self.tokens.next()
  77.519 +        row, col = end
  77.520 +        self.index = col
  77.521 +        return storage(type=type, value=t, begin=begin, end=end)
  77.522 +        
  77.523 +class DefwithNode:
  77.524 +    def __init__(self, defwith, suite):
  77.525 +        if defwith:
  77.526 +            self.defwith = defwith.replace('with', '__template__') + ':'
  77.527 +            # offset 4 lines. for encoding, __lineoffset__, loop and self.
  77.528 +            self.defwith += "\n    __lineoffset__ = -4"
  77.529 +        else:
  77.530 +            self.defwith = 'def __template__():'
  77.531 +            # offset 4 lines for encoding, __template__, __lineoffset__, loop and self.
  77.532 +            self.defwith += "\n    __lineoffset__ = -5"
  77.533 +
  77.534 +        self.defwith += "\n    loop = ForLoop()"
  77.535 +        self.defwith += "\n    self = TemplateResult(); extend_ = self.extend"
  77.536 +        self.suite = suite
  77.537 +        self.end = "\n    return self"
  77.538 +
  77.539 +    def emit(self, indent):
  77.540 +        encoding = "# coding: utf-8\n"
  77.541 +        return encoding + self.defwith + self.suite.emit(indent + INDENT) + self.end
  77.542 +
  77.543 +    def __repr__(self):
  77.544 +        return "<defwith: %s, %s>" % (self.defwith, self.suite)
  77.545 +
  77.546 +class TextNode:
  77.547 +    def __init__(self, value):
  77.548 +        self.value = value
  77.549 +
  77.550 +    def emit(self, indent, begin_indent=''):
  77.551 +        return repr(safeunicode(self.value))
  77.552 +        
  77.553 +    def __repr__(self):
  77.554 +        return 't' + repr(self.value)
  77.555 +
  77.556 +class ExpressionNode:
  77.557 +    def __init__(self, value, escape=True):
  77.558 +        self.value = value.strip()
  77.559 +        
  77.560 +        # convert ${...} to $(...)
  77.561 +        if value.startswith('{') and value.endswith('}'):
  77.562 +            self.value = '(' + self.value[1:-1] + ')'
  77.563 +            
  77.564 +        self.escape = escape
  77.565 +
  77.566 +    def emit(self, indent, begin_indent=''):
  77.567 +        return 'escape_(%s, %s)' % (self.value, bool(self.escape))
  77.568 +        
  77.569 +    def __repr__(self):
  77.570 +        if self.escape:
  77.571 +            escape = ''
  77.572 +        else:
  77.573 +            escape = ':'
  77.574 +        return "$%s%s" % (escape, self.value)
  77.575 +        
  77.576 +class AssignmentNode:
  77.577 +    def __init__(self, code):
  77.578 +        self.code = code
  77.579 +        
  77.580 +    def emit(self, indent, begin_indent=''):
  77.581 +        return indent + self.code + "\n"
  77.582 +        
  77.583 +    def __repr__(self):
  77.584 +        return "<assignment: %s>" % repr(self.code)
  77.585 +        
  77.586 +class LineNode:
  77.587 +    def __init__(self, nodes):
  77.588 +        self.nodes = nodes
  77.589 +        
  77.590 +    def emit(self, indent, text_indent='', name=''):
  77.591 +        text = [node.emit('') for node in self.nodes]
  77.592 +        if text_indent:
  77.593 +            text = [repr(text_indent)] + text
  77.594 +
  77.595 +        return indent + "extend_([%s])\n" % ", ".join(text)        
  77.596 +    
  77.597 +    def __repr__(self):
  77.598 +        return "<line: %s>" % repr(self.nodes)
  77.599 +
  77.600 +INDENT = '    ' # 4 spaces
  77.601 +        
  77.602 +class BlockNode:
  77.603 +    def __init__(self, stmt, block, begin_indent=''):
  77.604 +        self.stmt = stmt
  77.605 +        self.suite = Parser().read_suite(block)
  77.606 +        self.begin_indent = begin_indent
  77.607 +
  77.608 +    def emit(self, indent, text_indent=''):
  77.609 +        text_indent = self.begin_indent + text_indent
  77.610 +        out = indent + self.stmt + self.suite.emit(indent + INDENT, text_indent)
  77.611 +        return out
  77.612 +        
  77.613 +    def __repr__(self):
  77.614 +        return "<block: %s, %s>" % (repr(self.stmt), repr(self.suite))
  77.615 +
  77.616 +class ForNode(BlockNode):
  77.617 +    def __init__(self, stmt, block, begin_indent=''):
  77.618 +        self.original_stmt = stmt
  77.619 +        tok = PythonTokenizer(stmt)
  77.620 +        tok.consume_till('in')
  77.621 +        a = stmt[:tok.index] # for i in
  77.622 +        b = stmt[tok.index:-1] # rest of for stmt excluding :
  77.623 +        stmt = a + ' loop.setup(' + b.strip() + '):'
  77.624 +        BlockNode.__init__(self, stmt, block, begin_indent)
  77.625 +        
  77.626 +    def __repr__(self):
  77.627 +        return "<block: %s, %s>" % (repr(self.original_stmt), repr(self.suite))
  77.628 +
  77.629 +class CodeNode:
  77.630 +    def __init__(self, stmt, block, begin_indent=''):
  77.631 +        # compensate one line for $code:
  77.632 +        self.code = "\n" + block
  77.633 +        
  77.634 +    def emit(self, indent, text_indent=''):
  77.635 +        import re
  77.636 +        rx = re.compile('^', re.M)
  77.637 +        return rx.sub(indent, self.code).rstrip(' ')
  77.638 +        
  77.639 +    def __repr__(self):
  77.640 +        return "<code: %s>" % repr(self.code)
  77.641 +        
  77.642 +class StatementNode:
  77.643 +    def __init__(self, stmt):
  77.644 +        self.stmt = stmt
  77.645 +        
  77.646 +    def emit(self, indent, begin_indent=''):
  77.647 +        return indent + self.stmt
  77.648 +        
  77.649 +    def __repr__(self):
  77.650 +        return "<stmt: %s>" % repr(self.stmt)
  77.651 +        
  77.652 +class IfNode(BlockNode):
  77.653 +    pass
  77.654 +
  77.655 +class ElseNode(BlockNode):
  77.656 +    pass
  77.657 +
  77.658 +class ElifNode(BlockNode):
  77.659 +    pass
  77.660 +
  77.661 +class DefNode(BlockNode):
  77.662 +    def __init__(self, *a, **kw):
  77.663 +        BlockNode.__init__(self, *a, **kw)
  77.664 +
  77.665 +        code = CodeNode("", "")
  77.666 +        code.code = "self = TemplateResult(); extend_ = self.extend\n"
  77.667 +        self.suite.sections.insert(0, code)
  77.668 +
  77.669 +        code = CodeNode("", "")
  77.670 +        code.code = "return self\n"
  77.671 +        self.suite.sections.append(code)
  77.672 +        
  77.673 +    def emit(self, indent, text_indent=''):
  77.674 +        text_indent = self.begin_indent + text_indent
  77.675 +        out = indent + self.stmt + self.suite.emit(indent + INDENT, text_indent)
  77.676 +        return indent + "__lineoffset__ -= 3\n" + out
  77.677 +
  77.678 +class VarNode:
  77.679 +    def __init__(self, name, value):
  77.680 +        self.name = name
  77.681 +        self.value = value
  77.682 +        
  77.683 +    def emit(self, indent, text_indent):
  77.684 +        return indent + "self[%s] = %s\n" % (repr(self.name), self.value)
  77.685 +        
  77.686 +    def __repr__(self):
  77.687 +        return "<var: %s = %s>" % (self.name, self.value)
  77.688 +
  77.689 +class SuiteNode:
  77.690 +    """Suite is a list of sections."""
  77.691 +    def __init__(self, sections):
  77.692 +        self.sections = sections
  77.693 +        
  77.694 +    def emit(self, indent, text_indent=''):
  77.695 +        return "\n" + "".join([s.emit(indent, text_indent) for s in self.sections])
  77.696 +        
  77.697 +    def __repr__(self):
  77.698 +        return repr(self.sections)
  77.699 +
  77.700 +STATEMENT_NODES = {
  77.701 +    'for': ForNode,
  77.702 +    'while': BlockNode,
  77.703 +    'if': IfNode,
  77.704 +    'elif': ElifNode,
  77.705 +    'else': ElseNode,
  77.706 +    'def': DefNode,
  77.707 +    'code': CodeNode
  77.708 +}
  77.709 +
  77.710 +KEYWORDS = [
  77.711 +    "pass",
  77.712 +    "break",
  77.713 +    "continue",
  77.714 +    "return"
  77.715 +]
  77.716 +
  77.717 +TEMPLATE_BUILTIN_NAMES = [
  77.718 +    "dict", "enumerate", "float", "int", "bool", "list", "long", "reversed", 
  77.719 +    "set", "slice", "tuple", "xrange",
  77.720 +    "abs", "all", "any", "callable", "chr", "cmp", "divmod", "filter", "hex", 
  77.721 +    "id", "isinstance", "iter", "len", "max", "min", "oct", "ord", "pow", "range",
  77.722 +    "True", "False",
  77.723 +    "None",
  77.724 +    "__import__", # some c-libraries like datetime requires __import__ to present in the namespace
  77.725 +]
  77.726 +
  77.727 +import __builtin__
  77.728 +TEMPLATE_BUILTINS = dict([(name, getattr(__builtin__, name)) for name in TEMPLATE_BUILTIN_NAMES if name in __builtin__.__dict__])
  77.729 +
  77.730 +class ForLoop:
  77.731 +    """
  77.732 +    Wrapper for expression in for stament to support loop.xxx helpers.
  77.733 +    
  77.734 +        >>> loop = ForLoop()
  77.735 +        >>> for x in loop.setup(['a', 'b', 'c']):
  77.736 +        ...     print loop.index, loop.revindex, loop.parity, x
  77.737 +        ...
  77.738 +        1 3 odd a
  77.739 +        2 2 even b
  77.740 +        3 1 odd c
  77.741 +        >>> loop.index
  77.742 +        Traceback (most recent call last):
  77.743 +            ...
  77.744 +        AttributeError: index
  77.745 +    """
  77.746 +    def __init__(self):
  77.747 +        self._ctx = None
  77.748 +        
  77.749 +    def __getattr__(self, name):
  77.750 +        if self._ctx is None:
  77.751 +            raise AttributeError, name
  77.752 +        else:
  77.753 +            return getattr(self._ctx, name)
  77.754 +        
  77.755 +    def setup(self, seq):        
  77.756 +        self._push()
  77.757 +        return self._ctx.setup(seq)
  77.758 +        
  77.759 +    def _push(self):
  77.760 +        self._ctx = ForLoopContext(self, self._ctx)
  77.761 +        
  77.762 +    def _pop(self):
  77.763 +        self._ctx = self._ctx.parent
  77.764 +                
  77.765 +class ForLoopContext:
  77.766 +    """Stackable context for ForLoop to support nested for loops.
  77.767 +    """
  77.768 +    def __init__(self, forloop, parent):
  77.769 +        self._forloop = forloop
  77.770 +        self.parent = parent
  77.771 +        
  77.772 +    def setup(self, seq):
  77.773 +        try:
  77.774 +            self.length = len(seq)
  77.775 +        except:
  77.776 +            self.length = 0
  77.777 +
  77.778 +        self.index = 0
  77.779 +        for a in seq:
  77.780 +            self.index += 1
  77.781 +            yield a
  77.782 +        self._forloop._pop()
  77.783 +            
  77.784 +    index0 = property(lambda self: self.index-1)
  77.785 +    first = property(lambda self: self.index == 1)
  77.786 +    last = property(lambda self: self.index == self.length)
  77.787 +    odd = property(lambda self: self.index % 2 == 1)
  77.788 +    even = property(lambda self: self.index % 2 == 0)
  77.789 +    parity = property(lambda self: ['odd', 'even'][self.even])
  77.790 +    revindex0 = property(lambda self: self.length - self.index)
  77.791 +    revindex = property(lambda self: self.length - self.index + 1)
  77.792 +        
  77.793 +class BaseTemplate:
  77.794 +    def __init__(self, code, filename, filter, globals, builtins):
  77.795 +        self.filename = filename
  77.796 +        self.filter = filter
  77.797 +        self._globals = globals
  77.798 +        self._builtins = builtins
  77.799 +        if code:
  77.800 +            self.t = self._compile(code)
  77.801 +        else:
  77.802 +            self.t = lambda: ''
  77.803 +        
  77.804 +    def _compile(self, code):
  77.805 +        env = self.make_env(self._globals or {}, self._builtins)
  77.806 +        exec(code, env)
  77.807 +        return env['__template__']
  77.808 +
  77.809 +    def __call__(self, *a, **kw):
  77.810 +        __hidetraceback__ = True
  77.811 +        return self.t(*a, **kw)
  77.812 +
  77.813 +    def make_env(self, globals, builtins):
  77.814 +        return dict(globals,
  77.815 +            __builtins__=builtins, 
  77.816 +            ForLoop=ForLoop,
  77.817 +            TemplateResult=TemplateResult,
  77.818 +            escape_=self._escape,
  77.819 +            join_=self._join
  77.820 +        )
  77.821 +    def _join(self, *items):
  77.822 +        return u"".join(items)
  77.823 +            
  77.824 +    def _escape(self, value, escape=False):
  77.825 +        if value is None: 
  77.826 +            value = ''
  77.827 +            
  77.828 +        value = safeunicode(value)
  77.829 +        if escape and self.filter:
  77.830 +            value = self.filter(value)
  77.831 +        return value
  77.832 +
  77.833 +class Template(BaseTemplate):
  77.834 +    CONTENT_TYPES = {
  77.835 +        '.html' : 'text/html; charset=utf-8',
  77.836 +        '.xhtml' : 'application/xhtml+xml; charset=utf-8',
  77.837 +        '.txt' : 'text/plain',
  77.838 +    }
  77.839 +    FILTERS = {
  77.840 +        '.html': websafe,
  77.841 +        '.xhtml': websafe,
  77.842 +        '.xml': websafe
  77.843 +    }
  77.844 +    globals = {}
  77.845 +    
  77.846 +    def __init__(self, text, filename='<template>', filter=None, globals=None, builtins=None, extensions=None):
  77.847 +        self.extensions = extensions or []
  77.848 +        text = Template.normalize_text(text)
  77.849 +        code = self.compile_template(text, filename)
  77.850 +                
  77.851 +        _, ext = os.path.splitext(filename)
  77.852 +        filter = filter or self.FILTERS.get(ext, None)
  77.853 +        self.content_type = self.CONTENT_TYPES.get(ext, None)
  77.854 +
  77.855 +        if globals is None:
  77.856 +            globals = self.globals
  77.857 +        if builtins is None:
  77.858 +            builtins = TEMPLATE_BUILTINS
  77.859 +                
  77.860 +        BaseTemplate.__init__(self, code=code, filename=filename, filter=filter, globals=globals, builtins=builtins)
  77.861 +        
  77.862 +    def normalize_text(text):
  77.863 +        """Normalizes template text by correcting \r\n, tabs and BOM chars."""
  77.864 +        text = text.replace('\r\n', '\n').replace('\r', '\n').expandtabs()
  77.865 +        if not text.endswith('\n'):
  77.866 +            text += '\n'
  77.867 +
  77.868 +        # ignore BOM chars at the begining of template
  77.869 +        BOM = '\xef\xbb\xbf'
  77.870 +        if isinstance(text, str) and text.startswith(BOM):
  77.871 +            text = text[len(BOM):]
  77.872 +        
  77.873 +        # support fort \$ for backward-compatibility 
  77.874 +        text = text.replace(r'\$', '$$')
  77.875 +        return text
  77.876 +    normalize_text = staticmethod(normalize_text)
  77.877 +                
  77.878 +    def __call__(self, *a, **kw):
  77.879 +        __hidetraceback__ = True
  77.880 +        import webapi as web
  77.881 +        if 'headers' in web.ctx and self.content_type:
  77.882 +            web.header('Content-Type', self.content_type, unique=True)
  77.883 +            
  77.884 +        return BaseTemplate.__call__(self, *a, **kw)
  77.885 +        
  77.886 +    def generate_code(text, filename, parser=None):
  77.887 +        # parse the text
  77.888 +        parser = parser or Parser()
  77.889 +        rootnode = parser.parse(text, filename)
  77.890 +                
  77.891 +        # generate python code from the parse tree
  77.892 +        code = rootnode.emit(indent="").strip()
  77.893 +        return safestr(code)
  77.894 +        
  77.895 +    generate_code = staticmethod(generate_code)
  77.896 +    
  77.897 +    def create_parser(self):
  77.898 +        p = Parser()
  77.899 +        for ext in self.extensions:
  77.900 +            p = ext(p)
  77.901 +        return p
  77.902 +                
  77.903 +    def compile_template(self, template_string, filename):
  77.904 +        code = Template.generate_code(template_string, filename, parser=self.create_parser())
  77.905 +
  77.906 +        def get_source_line(filename, lineno):
  77.907 +            try:
  77.908 +                lines = open(filename).read().splitlines()
  77.909 +                return lines[lineno]
  77.910 +            except:
  77.911 +                return None
  77.912 +        
  77.913 +        try:
  77.914 +            # compile the code first to report the errors, if any, with the filename
  77.915 +            compiled_code = compile(code, filename, 'exec')
  77.916 +        except SyntaxError, e:
  77.917 +            # display template line that caused the error along with the traceback.
  77.918 +            try:
  77.919 +                e.msg += '\n\nTemplate traceback:\n    File %s, line %s\n        %s' % \
  77.920 +                    (repr(e.filename), e.lineno, get_source_line(e.filename, e.lineno-1))
  77.921 +            except: 
  77.922 +                pass
  77.923 +            raise
  77.924 +        
  77.925 +        # make sure code is safe - but not with jython, it doesn't have a working compiler module
  77.926 +        if not sys.platform.startswith('java'):
  77.927 +            try:
  77.928 +                import compiler
  77.929 +                ast = compiler.parse(code)
  77.930 +                SafeVisitor().walk(ast, filename)
  77.931 +            except ImportError:
  77.932 +                warnings.warn("Unabled to import compiler module. Unable to check templates for safety.")
  77.933 +        else:
  77.934 +            warnings.warn("SECURITY ISSUE: You are using Jython, which does not support checking templates for safety. Your templates can execute arbitrary code.")
  77.935 +
  77.936 +        return compiled_code
  77.937 +        
  77.938 +class CompiledTemplate(Template):
  77.939 +    def __init__(self, f, filename):
  77.940 +        Template.__init__(self, '', filename)
  77.941 +        self.t = f
  77.942 +        
  77.943 +    def compile_template(self, *a):
  77.944 +        return None
  77.945 +    
  77.946 +    def _compile(self, *a):
  77.947 +        return None
  77.948 +                
  77.949 +class Render:
  77.950 +    """The most preferred way of using templates.
  77.951 +    
  77.952 +        render = web.template.render('templates')
  77.953 +        print render.foo()
  77.954 +        
  77.955 +    Optional parameter can be `base` can be used to pass output of 
  77.956 +    every template through the base template.
  77.957 +    
  77.958 +        render = web.template.render('templates', base='layout')
  77.959 +    """
  77.960 +    def __init__(self, loc='templates', cache=None, base=None, **keywords):
  77.961 +        self._loc = loc
  77.962 +        self._keywords = keywords
  77.963 +
  77.964 +        if cache is None:
  77.965 +            cache = not config.get('debug', False)
  77.966 +        
  77.967 +        if cache:
  77.968 +            self._cache = {}
  77.969 +        else:
  77.970 +            self._cache = None
  77.971 +        
  77.972 +        if base and not hasattr(base, '__call__'):
  77.973 +            # make base a function, so that it can be passed to sub-renders
  77.974 +            self._base = lambda page: self._template(base)(page)
  77.975 +        else:
  77.976 +            self._base = base
  77.977 +    
  77.978 +    def _add_global(self, obj, name=None):
  77.979 +        """Add a global to this rendering instance."""
  77.980 +        if 'globals' not in self._keywords: self._keywords['globals'] = {}
  77.981 +        if not name:
  77.982 +            name = obj.__name__
  77.983 +        self._keywords['globals'][name] = obj
  77.984 +    
  77.985 +    def _lookup(self, name):
  77.986 +        path = os.path.join(self._loc, name)
  77.987 +        if os.path.isdir(path):
  77.988 +            return 'dir', path
  77.989 +        else:
  77.990 +            path = self._findfile(path)
  77.991 +            if path:
  77.992 +                return 'file', path
  77.993 +            else:
  77.994 +                return 'none', None
  77.995 +        
  77.996 +    def _load_template(self, name):
  77.997 +        kind, path = self._lookup(name)
  77.998 +        
  77.999 +        if kind == 'dir':
 77.1000 +            return Render(path, cache=self._cache is not None, base=self._base, **self._keywords)
 77.1001 +        elif kind == 'file':
 77.1002 +            return Template(open(path).read(), filename=path, **self._keywords)
 77.1003 +        else:
 77.1004 +            raise AttributeError, "No template named " + name            
 77.1005 +
 77.1006 +    def _findfile(self, path_prefix): 
 77.1007 +        p = [f for f in glob.glob(path_prefix + '.*') if not f.endswith('~')] # skip backup files
 77.1008 +        p.sort() # sort the matches for deterministic order
 77.1009 +        return p and p[0]
 77.1010 +            
 77.1011 +    def _template(self, name):
 77.1012 +        if self._cache is not None:
 77.1013 +            if name not in self._cache:
 77.1014 +                self._cache[name] = self._load_template(name)
 77.1015 +            return self._cache[name]
 77.1016 +        else:
 77.1017 +            return self._load_template(name)
 77.1018 +        
 77.1019 +    def __getattr__(self, name):
 77.1020 +        t = self._template(name)
 77.1021 +        if self._base and isinstance(t, Template):
 77.1022 +            def template(*a, **kw):
 77.1023 +                return self._base(t(*a, **kw))
 77.1024 +            return template
 77.1025 +        else:
 77.1026 +            return self._template(name)
 77.1027 +
 77.1028 +class GAE_Render(Render):
 77.1029 +    # Render gets over-written. make a copy here.
 77.1030 +    super = Render
 77.1031 +    def __init__(self, loc, *a, **kw):
 77.1032 +        GAE_Render.super.__init__(self, loc, *a, **kw)
 77.1033 +        
 77.1034 +        import types
 77.1035 +        if isinstance(loc, types.ModuleType):
 77.1036 +            self.mod = loc
 77.1037 +        else:
 77.1038 +            name = loc.rstrip('/').replace('/', '.')
 77.1039 +            self.mod = __import__(name, None, None, ['x'])
 77.1040 +
 77.1041 +        self.mod.__dict__.update(kw.get('builtins', TEMPLATE_BUILTINS))
 77.1042 +        self.mod.__dict__.update(Template.globals)
 77.1043 +        self.mod.__dict__.update(kw.get('globals', {}))
 77.1044 +
 77.1045 +    def _load_template(self, name):
 77.1046 +        t = getattr(self.mod, name)
 77.1047 +        import types
 77.1048 +        if isinstance(t, types.ModuleType):
 77.1049 +            return GAE_Render(t, cache=self._cache is not None, base=self._base, **self._keywords)
 77.1050 +        else:
 77.1051 +            return t
 77.1052 +
 77.1053 +render = Render
 77.1054 +# setup render for Google App Engine.
 77.1055 +try:
 77.1056 +    from google import appengine
 77.1057 +    render = Render = GAE_Render
 77.1058 +except ImportError:
 77.1059 +    pass
 77.1060 +        
 77.1061 +def frender(path, **keywords):
 77.1062 +    """Creates a template from the given file path.
 77.1063 +    """
 77.1064 +    return Template(open(path).read(), filename=path, **keywords)
 77.1065 +    
 77.1066 +def compile_templates(root):
 77.1067 +    """Compiles templates to python code."""
 77.1068 +    re_start = re_compile('^', re.M)
 77.1069 +    
 77.1070 +    for dirpath, dirnames, filenames in os.walk(root):
 77.1071 +        filenames = [f for f in filenames if not f.startswith('.') and not f.endswith('~') and not f.startswith('__init__.py')]
 77.1072 +
 77.1073 +        for d in dirnames[:]:
 77.1074 +            if d.startswith('.'):
 77.1075 +                dirnames.remove(d) # don't visit this dir
 77.1076 +
 77.1077 +        out = open(os.path.join(dirpath, '__init__.py'), 'w')
 77.1078 +        out.write('from web.template import CompiledTemplate, ForLoop, TemplateResult\n\n')
 77.1079 +        if dirnames:
 77.1080 +            out.write("import " + ", ".join(dirnames))
 77.1081 +        out.write("\n")
 77.1082 +
 77.1083 +        for f in filenames:
 77.1084 +            path = os.path.join(dirpath, f)
 77.1085 +
 77.1086 +            if '.' in f:
 77.1087 +                name, _ = f.split('.', 1)
 77.1088 +            else:
 77.1089 +                name = f
 77.1090 +                
 77.1091 +            text = open(path).read()
 77.1092 +            text = Template.normalize_text(text)
 77.1093 +            code = Template.generate_code(text, path)
 77.1094 +
 77.1095 +            code = code.replace("__template__", name, 1)
 77.1096 +            
 77.1097 +            out.write(code)
 77.1098 +
 77.1099 +            out.write('\n\n')
 77.1100 +            out.write('%s = CompiledTemplate(%s, %s)\n' % (name, name, repr(path)))
 77.1101 +            out.write("join_ = %s._join; escape_ = %s._escape\n\n" % (name, name))
 77.1102 +
 77.1103 +            # create template to make sure it compiles
 77.1104 +            t = Template(open(path).read(), path)
 77.1105 +        out.close()
 77.1106 +                
 77.1107 +class ParseError(Exception):
 77.1108 +    pass
 77.1109 +    
 77.1110 +class SecurityError(Exception):
 77.1111 +    """The template seems to be trying to do something naughty."""
 77.1112 +    pass
 77.1113 +
 77.1114 +# Enumerate all the allowed AST nodes
 77.1115 +ALLOWED_AST_NODES = [
 77.1116 +    "Add", "And",
 77.1117 +#   "AssAttr",
 77.1118 +    "AssList", "AssName", "AssTuple",
 77.1119 +#   "Assert",
 77.1120 +    "Assign", "AugAssign",
 77.1121 +#   "Backquote",
 77.1122 +    "Bitand", "Bitor", "Bitxor", "Break",
 77.1123 +    "CallFunc","Class", "Compare", "Const", "Continue",
 77.1124 +    "Decorators", "Dict", "Discard", "Div",
 77.1125 +    "Ellipsis", "EmptyNode",
 77.1126 +#   "Exec",
 77.1127 +    "Expression", "FloorDiv", "For",
 77.1128 +#   "From",
 77.1129 +    "Function", 
 77.1130 +    "GenExpr", "GenExprFor", "GenExprIf", "GenExprInner",
 77.1131 +    "Getattr", 
 77.1132 +#   "Global", 
 77.1133 +    "If", "IfExp",
 77.1134 +#   "Import",
 77.1135 +    "Invert", "Keyword", "Lambda", "LeftShift",
 77.1136 +    "List", "ListComp", "ListCompFor", "ListCompIf", "Mod",
 77.1137 +    "Module",
 77.1138 +    "Mul", "Name", "Not", "Or", "Pass", "Power",
 77.1139 +#   "Print", "Printnl", "Raise",
 77.1140 +    "Return", "RightShift", "Slice", "Sliceobj",
 77.1141 +    "Stmt", "Sub", "Subscript",
 77.1142 +#   "TryExcept", "TryFinally",
 77.1143 +    "Tuple", "UnaryAdd", "UnarySub",
 77.1144 +    "While", "With", "Yield",
 77.1145 +]
 77.1146 +
 77.1147 +class SafeVisitor(object):
 77.1148 +    """
 77.1149 +    Make sure code is safe by walking through the AST.
 77.1150 +    
 77.1151 +    Code considered unsafe if:
 77.1152 +        * it has restricted AST nodes
 77.1153 +        * it is trying to access resricted attributes   
 77.1154 +        
 77.1155 +    Adopted from http://www.zafar.se/bkz/uploads/safe.txt (public domain, Babar K. Zafar)
 77.1156 +    """
 77.1157 +    def __init__(self):
 77.1158 +        "Initialize visitor by generating callbacks for all AST node types."
 77.1159 +        self.errors = []
 77.1160 +
 77.1161 +    def walk(self, ast, filename):
 77.1162 +        "Validate each node in AST and raise SecurityError if the code is not safe."
 77.1163 +        self.filename = filename
 77.1164 +        self.visit(ast)
 77.1165 +        
 77.1166 +        if self.errors:        
 77.1167 +            raise SecurityError, '\n'.join([str(err) for err in self.errors])
 77.1168 +        
 77.1169 +    def visit(self, node, *args):
 77.1170 +        "Recursively validate node and all of its children."
 77.1171 +        def classname(obj):
 77.1172 +            return obj.__class__.__name__
 77.1173 +        nodename = classname(node)
 77.1174 +        fn = getattr(self, 'visit' + nodename, None)
 77.1175 +        
 77.1176 +        if fn:
 77.1177 +            fn(node, *args)
 77.1178 +        else:
 77.1179 +            if nodename not in ALLOWED_AST_NODES:
 77.1180 +                self.fail(node, *args)
 77.1181 +            
 77.1182 +        for child in node.getChildNodes():
 77.1183 +            self.visit(child, *args)
 77.1184 +
 77.1185 +    def visitName(self, node, *args):
 77.1186 +        "Disallow any attempts to access a restricted attr."
 77.1187 +        #self.assert_attr(node.getChildren()[0], node)
 77.1188 +        pass
 77.1189 +        
 77.1190 +    def visitGetattr(self, node, *args):
 77.1191 +        "Disallow any attempts to access a restricted attribute."
 77.1192 +        self.assert_attr(node.attrname, node)
 77.1193 +            
 77.1194 +    def assert_attr(self, attrname, node):
 77.1195 +        if self.is_unallowed_attr(attrname):
 77.1196 +            lineno = self.get_node_lineno(node)
 77.1197 +            e = SecurityError("%s:%d - access to attribute '%s' is denied" % (self.filename, lineno, attrname))
 77.1198 +            self.errors.append(e)
 77.1199 +
 77.1200 +    def is_unallowed_attr(self, name):
 77.1201 +        return name.startswith('_') \
 77.1202 +            or name.startswith('func_') \
 77.1203 +            or name.startswith('im_')
 77.1204 +            
 77.1205 +    def get_node_lineno(self, node):
 77.1206 +        return (node.lineno) and node.lineno or 0
 77.1207 +        
 77.1208 +    def fail(self, node, *args):
 77.1209 +        "Default callback for unallowed AST nodes."
 77.1210 +        lineno = self.get_node_lineno(node)
 77.1211 +        nodename = node.__class__.__name__
 77.1212 +        e = SecurityError("%s:%d - execution of '%s' statements is denied" % (self.filename, lineno, nodename))
 77.1213 +        self.errors.append(e)
 77.1214 +
 77.1215 +class TemplateResult(object, DictMixin):
 77.1216 +    """Dictionary like object for storing template output.
 77.1217 +    
 77.1218 +    The result of a template execution is usally a string, but sometimes it
 77.1219 +    contains attributes set using $var. This class provides a simple
 77.1220 +    dictionary like interface for storing the output of the template and the
 77.1221 +    attributes. The output is stored with a special key __body__. Convering
 77.1222 +    the the TemplateResult to string or unicode returns the value of __body__.
 77.1223 +    
 77.1224 +    When the template is in execution, the output is generated part by part
 77.1225 +    and those parts are combined at the end. Parts are added to the
 77.1226 +    TemplateResult by calling the `extend` method and the parts are combined
 77.1227 +    seemlessly when __body__ is accessed.
 77.1228 +    
 77.1229 +        >>> d = TemplateResult(__body__='hello, world', x='foo')
 77.1230 +        >>> d
 77.1231 +        <TemplateResult: {'__body__': 'hello, world', 'x': 'foo'}>
 77.1232 +        >>> print d
 77.1233 +        hello, world
 77.1234 +        >>> d.x
 77.1235 +        'foo'
 77.1236 +        >>> d = TemplateResult()
 77.1237 +        >>> d.extend([u'hello', u'world'])
 77.1238 +        >>> d
 77.1239 +        <TemplateResult: {'__body__': u'helloworld'}>
 77.1240 +    """
 77.1241 +    def __init__(self, *a, **kw):
 77.1242 +        self.__dict__["_d"] = dict(*a, **kw)
 77.1243 +        self._d.setdefault("__body__", u'')
 77.1244 +        
 77.1245 +        self.__dict__['_parts'] = []
 77.1246 +        self.__dict__["extend"] = self._parts.extend
 77.1247 +        
 77.1248 +        self._d.setdefault("__body__", None)
 77.1249 +    
 77.1250 +    def keys(self):
 77.1251 +        return self._d.keys()
 77.1252 +        
 77.1253 +    def _prepare_body(self):
 77.1254 +        """Prepare value of __body__ by joining parts.
 77.1255 +        """
 77.1256 +        if self._parts:
 77.1257 +            value = u"".join(self._parts)
 77.1258 +            self._parts[:] = []
 77.1259 +            body = self._d.get('__body__')
 77.1260 +            if body:
 77.1261 +                self._d['__body__'] = body + value
 77.1262 +            else:
 77.1263 +                self._d['__body__'] = value
 77.1264 +                
 77.1265 +    def __getitem__(self, name):
 77.1266 +        if name == "__body__":
 77.1267 +            self._prepare_body()
 77.1268 +        return self._d[name]
 77.1269 +        
 77.1270 +    def __setitem__(self, name, value):
 77.1271 +        if name == "__body__":
 77.1272 +            self._prepare_body()
 77.1273 +        return self._d.__setitem__(name, value)
 77.1274 +        
 77.1275 +    def __delitem__(self, name):
 77.1276 +        if name == "__body__":
 77.1277 +            self._prepare_body()
 77.1278 +        return self._d.__delitem__(name)
 77.1279 +
 77.1280 +    def __getattr__(self, key): 
 77.1281 +        try:
 77.1282 +            return self[key]
 77.1283 +        except KeyError, k:
 77.1284 +            raise AttributeError, k
 77.1285 +
 77.1286 +    def __setattr__(self, key, value): 
 77.1287 +        self[key] = value
 77.1288 +
 77.1289 +    def __delattr__(self, key):
 77.1290 +        try:
 77.1291 +            del self[key]
 77.1292 +        except KeyError, k:
 77.1293 +            raise AttributeError, k
 77.1294 +        
 77.1295 +    def __unicode__(self):
 77.1296 +        self._prepare_body()
 77.1297 +        return self["__body__"]
 77.1298 +    
 77.1299 +    def __str__(self):
 77.1300 +        self._prepare_body()
 77.1301 +        return self["__body__"].encode('utf-8')
 77.1302 +        
 77.1303 +    def __repr__(self):
 77.1304 +        self._prepare_body()
 77.1305 +        return "<TemplateResult: %s>" % self._d
 77.1306 +
 77.1307 +def test():
 77.1308 +    r"""Doctest for testing template module.
 77.1309 +
 77.1310 +    Define a utility function to run template test.
 77.1311 +    
 77.1312 +        >>> class TestResult:
 77.1313 +        ...     def __init__(self, t): self.t = t
 77.1314 +        ...     def __getattr__(self, name): return getattr(self.t, name)
 77.1315 +        ...     def __repr__(self): return repr(unicode(self))
 77.1316 +        ...
 77.1317 +        >>> def t(code, **keywords):
 77.1318 +        ...     tmpl = Template(code, **keywords)
 77.1319 +        ...     return lambda *a, **kw: TestResult(tmpl(*a, **kw))
 77.1320 +        ...
 77.1321 +    
 77.1322 +    Simple tests.
 77.1323 +    
 77.1324 +        >>> t('1')()
 77.1325 +        u'1\n'
 77.1326 +        >>> t('$def with ()\n1')()
 77.1327 +        u'1\n'
 77.1328 +        >>> t('$def with (a)\n$a')(1)
 77.1329 +        u'1\n'
 77.1330 +        >>> t('$def with (a=0)\n$a')(1)
 77.1331 +        u'1\n'
 77.1332 +        >>> t('$def with (a=0)\n$a')(a=1)
 77.1333 +        u'1\n'
 77.1334 +    
 77.1335 +    Test complicated expressions.
 77.1336 +        
 77.1337 +        >>> t('$def with (x)\n$x.upper()')('hello')
 77.1338 +        u'HELLO\n'
 77.1339 +        >>> t('$(2 * 3 + 4 * 5)')()
 77.1340 +        u'26\n'
 77.1341 +        >>> t('${2 * 3 + 4 * 5}')()
 77.1342 +        u'26\n'
 77.1343 +        >>> t('$def with (limit)\nkeep $(limit)ing.')('go')
 77.1344 +        u'keep going.\n'
 77.1345 +        >>> t('$def with (a)\n$a.b[0]')(storage(b=[1]))
 77.1346 +        u'1\n'
 77.1347 +        
 77.1348 +    Test html escaping.
 77.1349 +    
 77.1350 +        >>> t('$def with (x)\n$x', filename='a.html')('<html>')
 77.1351 +        u'&lt;html&gt;\n'
 77.1352 +        >>> t('$def with (x)\n$x', filename='a.txt')('<html>')
 77.1353 +        u'<html>\n'
 77.1354 +                
 77.1355 +    Test if, for and while.
 77.1356 +    
 77.1357 +        >>> t('$if 1: 1')()
 77.1358 +        u'1\n'
 77.1359 +        >>> t('$if 1:\n    1')()
 77.1360 +        u'1\n'
 77.1361 +        >>> t('$if 1:\n    1\\')()
 77.1362 +        u'1'
 77.1363 +        >>> t('$if 0: 0\n$elif 1: 1')()
 77.1364 +        u'1\n'
 77.1365 +        >>> t('$if 0: 0\n$elif None: 0\n$else: 1')()
 77.1366 +        u'1\n'
 77.1367 +        >>> t('$if 0 < 1 and 1 < 2: 1')()
 77.1368 +        u'1\n'
 77.1369 +        >>> t('$for x in [1, 2, 3]: $x')()
 77.1370 +        u'1\n2\n3\n'
 77.1371 +        >>> t('$def with (d)\n$for k, v in d.iteritems(): $k')({1: 1})
 77.1372 +        u'1\n'
 77.1373 +        >>> t('$for x in [1, 2, 3]:\n\t$x')()
 77.1374 +        u'    1\n    2\n    3\n'
 77.1375 +        >>> t('$def with (a)\n$while a and a.pop():1')([1, 2, 3])
 77.1376 +        u'1\n1\n1\n'
 77.1377 +
 77.1378 +    The space after : must be ignored.
 77.1379 +    
 77.1380 +        >>> t('$if True: foo')()
 77.1381 +        u'foo\n'
 77.1382 +    
 77.1383 +    Test loop.xxx.
 77.1384 +
 77.1385 +        >>> t("$for i in range(5):$loop.index, $loop.parity")()
 77.1386 +        u'1, odd\n2, even\n3, odd\n4, even\n5, odd\n'
 77.1387 +        >>> t("$for i in range(2):\n    $for j in range(2):$loop.parent.parity $loop.parity")()
 77.1388 +        u'odd odd\nodd even\neven odd\neven even\n'
 77.1389 +        
 77.1390 +    Test assignment.
 77.1391 +    
 77.1392 +        >>> t('$ a = 1\n$a')()
 77.1393 +        u'1\n'
 77.1394 +        >>> t('$ a = [1]\n$a[0]')()
 77.1395 +        u'1\n'
 77.1396 +        >>> t('$ a = {1: 1}\n$a.keys()[0]')()
 77.1397 +        u'1\n'
 77.1398 +        >>> t('$ a = []\n$if not a: 1')()
 77.1399 +        u'1\n'
 77.1400 +        >>> t('$ a = {}\n$if not a: 1')()
 77.1401 +        u'1\n'
 77.1402 +        >>> t('$ a = -1\n$a')()
 77.1403 +        u'-1\n'
 77.1404 +        >>> t('$ a = "1"\n$a')()
 77.1405 +        u'1\n'
 77.1406 +
 77.1407 +    Test comments.
 77.1408 +    
 77.1409 +        >>> t('$# 0')()
 77.1410 +        u'\n'
 77.1411 +        >>> t('hello$#comment1\nhello$#comment2')()
 77.1412 +        u'hello\nhello\n'
 77.1413 +        >>> t('$#comment0\nhello$#comment1\nhello$#comment2')()
 77.1414 +        u'\nhello\nhello\n'
 77.1415 +        
 77.1416 +    Test unicode.
 77.1417 +    
 77.1418 +        >>> t('$def with (a)\n$a')(u'\u203d')
 77.1419 +        u'\u203d\n'
 77.1420 +        >>> t('$def with (a)\n$a')(u'\u203d'.encode('utf-8'))
 77.1421 +        u'\u203d\n'
 77.1422 +        >>> t(u'$def with (a)\n$a $:a')(u'\u203d')
 77.1423 +        u'\u203d \u203d\n'
 77.1424 +        >>> t(u'$def with ()\nfoo')()
 77.1425 +        u'foo\n'
 77.1426 +        >>> def f(x): return x
 77.1427 +        ...
 77.1428 +        >>> t(u'$def with (f)\n$:f("x")')(f)
 77.1429 +        u'x\n'
 77.1430 +        >>> t('$def with (f)\n$:f("x")')(f)
 77.1431 +        u'x\n'
 77.1432 +    
 77.1433 +    Test dollar escaping.
 77.1434 +    
 77.1435 +        >>> t("Stop, $$money isn't evaluated.")()
 77.1436 +        u"Stop, $money isn't evaluated.\n"
 77.1437 +        >>> t("Stop, \$money isn't evaluated.")()
 77.1438 +        u"Stop, $money isn't evaluated.\n"
 77.1439 +        
 77.1440 +    Test space sensitivity.
 77.1441 +    
 77.1442 +        >>> t('$def with (x)\n$x')(1)
 77.1443 +        u'1\n'
 77.1444 +        >>> t('$def with(x ,y)\n$x')(1, 1)
 77.1445 +        u'1\n'
 77.1446 +        >>> t('$(1 + 2*3 + 4)')()
 77.1447 +        u'11\n'
 77.1448 +        
 77.1449 +    Make sure globals are working.
 77.1450 +            
 77.1451 +        >>> t('$x')()
 77.1452 +        Traceback (most recent call last):
 77.1453 +            ...
 77.1454 +        NameError: global name 'x' is not defined
 77.1455 +        >>> t('$x', globals={'x': 1})()
 77.1456 +        u'1\n'
 77.1457 +        
 77.1458 +    Can't change globals.
 77.1459 +    
 77.1460 +        >>> t('$ x = 2\n$x', globals={'x': 1})()
 77.1461 +        u'2\n'
 77.1462 +        >>> t('$ x = x + 1\n$x', globals={'x': 1})()
 77.1463 +        Traceback (most recent call last):
 77.1464 +            ...
 77.1465 +        UnboundLocalError: local variable 'x' referenced before assignment
 77.1466 +    
 77.1467 +    Make sure builtins are customizable.
 77.1468 +    
 77.1469 +        >>> t('$min(1, 2)')()
 77.1470 +        u'1\n'
 77.1471 +        >>> t('$min(1, 2)', builtins={})()
 77.1472 +        Traceback (most recent call last):
 77.1473 +            ...
 77.1474 +        NameError: global name 'min' is not defined
 77.1475 +        
 77.1476 +    Test vars.
 77.1477 +    
 77.1478 +        >>> x = t('$var x: 1')()
 77.1479 +        >>> x.x
 77.1480 +        u'1'
 77.1481 +        >>> x = t('$var x = 1')()
 77.1482 +        >>> x.x
 77.1483 +        1
 77.1484 +        >>> x = t('$var x:  \n    foo\n    bar')()
 77.1485 +        >>> x.x
 77.1486 +        u'foo\nbar\n'
 77.1487 +
 77.1488 +    Test BOM chars.
 77.1489 +
 77.1490 +        >>> t('\xef\xbb\xbf$def with(x)\n$x')('foo')
 77.1491 +        u'foo\n'
 77.1492 +
 77.1493 +    Test for with weird cases.
 77.1494 +
 77.1495 +        >>> t('$for i in range(10)[1:5]:\n    $i')()
 77.1496 +        u'1\n2\n3\n4\n'
 77.1497 +        >>> t("$for k, v in {'a': 1, 'b': 2}.items():\n    $k $v")()
 77.1498 +        u'a 1\nb 2\n'
 77.1499 +        >>> t("$for k, v in ({'a': 1, 'b': 2}.items():\n    $k $v")()
 77.1500 +        Traceback (most recent call last):
 77.1501 +            ...
 77.1502 +        SyntaxError: invalid syntax
 77.1503 +
 77.1504 +    Test datetime.
 77.1505 +
 77.1506 +        >>> import datetime
 77.1507 +        >>> t("$def with (date)\n$date.strftime('%m %Y')")(datetime.datetime(2009, 1, 1))
 77.1508 +        u'01 2009\n'
 77.1509 +    """
 77.1510 +    pass
 77.1511 +            
 77.1512 +if __name__ == "__main__":
 77.1513 +    import sys
 77.1514 +    if '--compile' in sys.argv:
 77.1515 +        compile_templates(sys.argv[2])
 77.1516 +    else:
 77.1517 +        import doctest
 77.1518 +        doctest.testmod()
    78.1 Binary file OpenSecurity/install/web.py-0.37/web/template.pyc has changed
    79.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    79.2 +++ b/OpenSecurity/install/web.py-0.37/web/test.py	Mon Dec 02 14:02:05 2013 +0100
    79.3 @@ -0,0 +1,51 @@
    79.4 +"""test utilities
    79.5 +(part of web.py)
    79.6 +"""
    79.7 +import unittest
    79.8 +import sys, os
    79.9 +import web
   79.10 +
   79.11 +TestCase = unittest.TestCase
   79.12 +TestSuite = unittest.TestSuite
   79.13 +
   79.14 +def load_modules(names):
   79.15 +    return [__import__(name, None, None, "x") for name in names]
   79.16 +
   79.17 +def module_suite(module, classnames=None):
   79.18 +    """Makes a suite from a module."""
   79.19 +    if classnames:
   79.20 +        return unittest.TestLoader().loadTestsFromNames(classnames, module)
   79.21 +    elif hasattr(module, 'suite'):
   79.22 +        return module.suite()
   79.23 +    else:
   79.24 +        return unittest.TestLoader().loadTestsFromModule(module)
   79.25 +
   79.26 +def doctest_suite(module_names):
   79.27 +    """Makes a test suite from doctests."""
   79.28 +    import doctest
   79.29 +    suite = TestSuite()
   79.30 +    for mod in load_modules(module_names):
   79.31 +        suite.addTest(doctest.DocTestSuite(mod))
   79.32 +    return suite
   79.33 +    
   79.34 +def suite(module_names):
   79.35 +    """Creates a suite from multiple modules."""
   79.36 +    suite = TestSuite()
   79.37 +    for mod in load_modules(module_names):
   79.38 +        suite.addTest(module_suite(mod))
   79.39 +    return suite
   79.40 +
   79.41 +def runTests(suite):
   79.42 +    runner = unittest.TextTestRunner()
   79.43 +    return runner.run(suite)
   79.44 +
   79.45 +def main(suite=None):
   79.46 +    if not suite:
   79.47 +        main_module = __import__('__main__')
   79.48 +        # allow command line switches
   79.49 +        args = [a for a in sys.argv[1:] if not a.startswith('-')]
   79.50 +        suite = module_suite(main_module, args or None)
   79.51 +
   79.52 +    result = runTests(suite)
   79.53 +    sys.exit(not result.wasSuccessful())
   79.54 +
    80.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    80.2 +++ b/OpenSecurity/install/web.py-0.37/web/utils.py	Mon Dec 02 14:02:05 2013 +0100
    80.3 @@ -0,0 +1,1526 @@
    80.4 +#!/usr/bin/env python
    80.5 +"""
    80.6 +General Utilities
    80.7 +(part of web.py)
    80.8 +"""
    80.9 +
   80.10 +__all__ = [
   80.11 +  "Storage", "storage", "storify", 
   80.12 +  "Counter", "counter",
   80.13 +  "iters", 
   80.14 +  "rstrips", "lstrips", "strips", 
   80.15 +  "safeunicode", "safestr", "utf8",
   80.16 +  "TimeoutError", "timelimit",
   80.17 +  "Memoize", "memoize",
   80.18 +  "re_compile", "re_subm",
   80.19 +  "group", "uniq", "iterview",
   80.20 +  "IterBetter", "iterbetter",
   80.21 +  "safeiter", "safewrite",
   80.22 +  "dictreverse", "dictfind", "dictfindall", "dictincr", "dictadd",
   80.23 +  "requeue", "restack",
   80.24 +  "listget", "intget", "datestr",
   80.25 +  "numify", "denumify", "commify", "dateify",
   80.26 +  "nthstr", "cond",
   80.27 +  "CaptureStdout", "capturestdout", "Profile", "profile",
   80.28 +  "tryall",
   80.29 +  "ThreadedDict", "threadeddict",
   80.30 +  "autoassign",
   80.31 +  "to36",
   80.32 +  "safemarkdown",
   80.33 +  "sendmail"
   80.34 +]
   80.35 +
   80.36 +import re, sys, time, threading, itertools, traceback, os
   80.37 +
   80.38 +try:
   80.39 +    import subprocess
   80.40 +except ImportError: 
   80.41 +    subprocess = None
   80.42 +
   80.43 +try: import datetime
   80.44 +except ImportError: pass
   80.45 +
   80.46 +try: set
   80.47 +except NameError:
   80.48 +    from sets import Set as set
   80.49 +    
   80.50 +try:
   80.51 +    from threading import local as threadlocal
   80.52 +except ImportError:
   80.53 +    from python23 import threadlocal
   80.54 +
   80.55 +class Storage(dict):
   80.56 +    """
   80.57 +    A Storage object is like a dictionary except `obj.foo` can be used
   80.58 +    in addition to `obj['foo']`.
   80.59 +    
   80.60 +        >>> o = storage(a=1)
   80.61 +        >>> o.a
   80.62 +        1
   80.63 +        >>> o['a']
   80.64 +        1
   80.65 +        >>> o.a = 2
   80.66 +        >>> o['a']
   80.67 +        2
   80.68 +        >>> del o.a
   80.69 +        >>> o.a
   80.70 +        Traceback (most recent call last):
   80.71 +            ...
   80.72 +        AttributeError: 'a'
   80.73 +    
   80.74 +    """
   80.75 +    def __getattr__(self, key): 
   80.76 +        try:
   80.77 +            return self[key]
   80.78 +        except KeyError, k:
   80.79 +            raise AttributeError, k
   80.80 +    
   80.81 +    def __setattr__(self, key, value): 
   80.82 +        self[key] = value
   80.83 +    
   80.84 +    def __delattr__(self, key):
   80.85 +        try:
   80.86 +            del self[key]
   80.87 +        except KeyError, k:
   80.88 +            raise AttributeError, k
   80.89 +    
   80.90 +    def __repr__(self):     
   80.91 +        return '<Storage ' + dict.__repr__(self) + '>'
   80.92 +
   80.93 +storage = Storage
   80.94 +
   80.95 +def storify(mapping, *requireds, **defaults):
   80.96 +    """
   80.97 +    Creates a `storage` object from dictionary `mapping`, raising `KeyError` if
   80.98 +    d doesn't have all of the keys in `requireds` and using the default 
   80.99 +    values for keys found in `defaults`.
  80.100 +
  80.101 +    For example, `storify({'a':1, 'c':3}, b=2, c=0)` will return the equivalent of
  80.102 +    `storage({'a':1, 'b':2, 'c':3})`.
  80.103 +    
  80.104 +    If a `storify` value is a list (e.g. multiple values in a form submission), 
  80.105 +    `storify` returns the last element of the list, unless the key appears in 
  80.106 +    `defaults` as a list. Thus:
  80.107 +    
  80.108 +        >>> storify({'a':[1, 2]}).a
  80.109 +        2
  80.110 +        >>> storify({'a':[1, 2]}, a=[]).a
  80.111 +        [1, 2]
  80.112 +        >>> storify({'a':1}, a=[]).a
  80.113 +        [1]
  80.114 +        >>> storify({}, a=[]).a
  80.115 +        []
  80.116 +    
  80.117 +    Similarly, if the value has a `value` attribute, `storify will return _its_
  80.118 +    value, unless the key appears in `defaults` as a dictionary.
  80.119 +    
  80.120 +        >>> storify({'a':storage(value=1)}).a
  80.121 +        1
  80.122 +        >>> storify({'a':storage(value=1)}, a={}).a
  80.123 +        <Storage {'value': 1}>
  80.124 +        >>> storify({}, a={}).a
  80.125 +        {}
  80.126 +        
  80.127 +    Optionally, keyword parameter `_unicode` can be passed to convert all values to unicode.
  80.128 +    
  80.129 +        >>> storify({'x': 'a'}, _unicode=True)
  80.130 +        <Storage {'x': u'a'}>
  80.131 +        >>> storify({'x': storage(value='a')}, x={}, _unicode=True)
  80.132 +        <Storage {'x': <Storage {'value': 'a'}>}>
  80.133 +        >>> storify({'x': storage(value='a')}, _unicode=True)
  80.134 +        <Storage {'x': u'a'}>
  80.135 +    """
  80.136 +    _unicode = defaults.pop('_unicode', False)
  80.137 +
  80.138 +    # if _unicode is callable object, use it convert a string to unicode.
  80.139 +    to_unicode = safeunicode
  80.140 +    if _unicode is not False and hasattr(_unicode, "__call__"):
  80.141 +        to_unicode = _unicode
  80.142 +    
  80.143 +    def unicodify(s):
  80.144 +        if _unicode and isinstance(s, str): return to_unicode(s)
  80.145 +        else: return s
  80.146 +        
  80.147 +    def getvalue(x):
  80.148 +        if hasattr(x, 'file') and hasattr(x, 'value'):
  80.149 +            return x.value
  80.150 +        elif hasattr(x, 'value'):
  80.151 +            return unicodify(x.value)
  80.152 +        else:
  80.153 +            return unicodify(x)
  80.154 +    
  80.155 +    stor = Storage()
  80.156 +    for key in requireds + tuple(mapping.keys()):
  80.157 +        value = mapping[key]
  80.158 +        if isinstance(value, list):
  80.159 +            if isinstance(defaults.get(key), list):
  80.160 +                value = [getvalue(x) for x in value]
  80.161 +            else:
  80.162 +                value = value[-1]
  80.163 +        if not isinstance(defaults.get(key), dict):
  80.164 +            value = getvalue(value)
  80.165 +        if isinstance(defaults.get(key), list) and not isinstance(value, list):
  80.166 +            value = [value]
  80.167 +        setattr(stor, key, value)
  80.168 +
  80.169 +    for (key, value) in defaults.iteritems():
  80.170 +        result = value
  80.171 +        if hasattr(stor, key): 
  80.172 +            result = stor[key]
  80.173 +        if value == () and not isinstance(result, tuple): 
  80.174 +            result = (result,)
  80.175 +        setattr(stor, key, result)
  80.176 +    
  80.177 +    return stor
  80.178 +
  80.179 +class Counter(storage):
  80.180 +    """Keeps count of how many times something is added.
  80.181 +        
  80.182 +        >>> c = counter()
  80.183 +        >>> c.add('x')
  80.184 +        >>> c.add('x')
  80.185 +        >>> c.add('x')
  80.186 +        >>> c.add('x')
  80.187 +        >>> c.add('x')
  80.188 +        >>> c.add('y')
  80.189 +        >>> c
  80.190 +        <Counter {'y': 1, 'x': 5}>
  80.191 +        >>> c.most()
  80.192 +        ['x']
  80.193 +    """
  80.194 +    def add(self, n):
  80.195 +        self.setdefault(n, 0)
  80.196 +        self[n] += 1
  80.197 +    
  80.198 +    def most(self):
  80.199 +        """Returns the keys with maximum count."""
  80.200 +        m = max(self.itervalues())
  80.201 +        return [k for k, v in self.iteritems() if v == m]
  80.202 +        
  80.203 +    def least(self):
  80.204 +        """Returns the keys with mininum count."""
  80.205 +        m = min(self.itervalues())
  80.206 +        return [k for k, v in self.iteritems() if v == m]
  80.207 +
  80.208 +    def percent(self, key):
  80.209 +       """Returns what percentage a certain key is of all entries.
  80.210 +
  80.211 +           >>> c = counter()
  80.212 +           >>> c.add('x')
  80.213 +           >>> c.add('x')
  80.214 +           >>> c.add('x')
  80.215 +           >>> c.add('y')
  80.216 +           >>> c.percent('x')
  80.217 +           0.75
  80.218 +           >>> c.percent('y')
  80.219 +           0.25
  80.220 +       """
  80.221 +       return float(self[key])/sum(self.values())
  80.222 +             
  80.223 +    def sorted_keys(self):
  80.224 +        """Returns keys sorted by value.
  80.225 +             
  80.226 +             >>> c = counter()
  80.227 +             >>> c.add('x')
  80.228 +             >>> c.add('x')
  80.229 +             >>> c.add('y')
  80.230 +             >>> c.sorted_keys()
  80.231 +             ['x', 'y']
  80.232 +        """
  80.233 +        return sorted(self.keys(), key=lambda k: self[k], reverse=True)
  80.234 +    
  80.235 +    def sorted_values(self):
  80.236 +        """Returns values sorted by value.
  80.237 +            
  80.238 +            >>> c = counter()
  80.239 +            >>> c.add('x')
  80.240 +            >>> c.add('x')
  80.241 +            >>> c.add('y')
  80.242 +            >>> c.sorted_values()
  80.243 +            [2, 1]
  80.244 +        """
  80.245 +        return [self[k] for k in self.sorted_keys()]
  80.246 +    
  80.247 +    def sorted_items(self):
  80.248 +        """Returns items sorted by value.
  80.249 +            
  80.250 +            >>> c = counter()
  80.251 +            >>> c.add('x')
  80.252 +            >>> c.add('x')
  80.253 +            >>> c.add('y')
  80.254 +            >>> c.sorted_items()
  80.255 +            [('x', 2), ('y', 1)]
  80.256 +        """
  80.257 +        return [(k, self[k]) for k in self.sorted_keys()]
  80.258 +    
  80.259 +    def __repr__(self):
  80.260 +        return '<Counter ' + dict.__repr__(self) + '>'
  80.261 +       
  80.262 +counter = Counter
  80.263 +
  80.264 +iters = [list, tuple]
  80.265 +import __builtin__
  80.266 +if hasattr(__builtin__, 'set'):
  80.267 +    iters.append(set)
  80.268 +if hasattr(__builtin__, 'frozenset'):
  80.269 +    iters.append(set)
  80.270 +if sys.version_info < (2,6): # sets module deprecated in 2.6
  80.271 +    try:
  80.272 +        from sets import Set
  80.273 +        iters.append(Set)
  80.274 +    except ImportError: 
  80.275 +        pass
  80.276 +    
  80.277 +class _hack(tuple): pass
  80.278 +iters = _hack(iters)
  80.279 +iters.__doc__ = """
  80.280 +A list of iterable items (like lists, but not strings). Includes whichever
  80.281 +of lists, tuples, sets, and Sets are available in this version of Python.
  80.282 +"""
  80.283 +
  80.284 +def _strips(direction, text, remove):
  80.285 +    if isinstance(remove, iters):
  80.286 +        for subr in remove:
  80.287 +            text = _strips(direction, text, subr)
  80.288 +        return text
  80.289 +    
  80.290 +    if direction == 'l': 
  80.291 +        if text.startswith(remove): 
  80.292 +            return text[len(remove):]
  80.293 +    elif direction == 'r':
  80.294 +        if text.endswith(remove):   
  80.295 +            return text[:-len(remove)]
  80.296 +    else: 
  80.297 +        raise ValueError, "Direction needs to be r or l."
  80.298 +    return text
  80.299 +
  80.300 +def rstrips(text, remove):
  80.301 +    """
  80.302 +    removes the string `remove` from the right of `text`
  80.303 +
  80.304 +        >>> rstrips("foobar", "bar")
  80.305 +        'foo'
  80.306 +    
  80.307 +    """
  80.308 +    return _strips('r', text, remove)
  80.309 +
  80.310 +def lstrips(text, remove):
  80.311 +    """
  80.312 +    removes the string `remove` from the left of `text`
  80.313 +    
  80.314 +        >>> lstrips("foobar", "foo")
  80.315 +        'bar'
  80.316 +        >>> lstrips('http://foo.org/', ['http://', 'https://'])
  80.317 +        'foo.org/'
  80.318 +        >>> lstrips('FOOBARBAZ', ['FOO', 'BAR'])
  80.319 +        'BAZ'
  80.320 +        >>> lstrips('FOOBARBAZ', ['BAR', 'FOO'])
  80.321 +        'BARBAZ'
  80.322 +    
  80.323 +    """
  80.324 +    return _strips('l', text, remove)
  80.325 +
  80.326 +def strips(text, remove):
  80.327 +    """
  80.328 +    removes the string `remove` from the both sides of `text`
  80.329 +
  80.330 +        >>> strips("foobarfoo", "foo")
  80.331 +        'bar'
  80.332 +    
  80.333 +    """
  80.334 +    return rstrips(lstrips(text, remove), remove)
  80.335 +
  80.336 +def safeunicode(obj, encoding='utf-8'):
  80.337 +    r"""
  80.338 +    Converts any given object to unicode string.
  80.339 +    
  80.340 +        >>> safeunicode('hello')
  80.341 +        u'hello'
  80.342 +        >>> safeunicode(2)
  80.343 +        u'2'
  80.344 +        >>> safeunicode('\xe1\x88\xb4')
  80.345 +        u'\u1234'
  80.346 +    """
  80.347 +    t = type(obj)
  80.348 +    if t is unicode:
  80.349 +        return obj
  80.350 +    elif t is str:
  80.351 +        return obj.decode(encoding)
  80.352 +    elif t in [int, float, bool]:
  80.353 +        return unicode(obj)
  80.354 +    elif hasattr(obj, '__unicode__') or isinstance(obj, unicode):
  80.355 +        return unicode(obj)
  80.356 +    else:
  80.357 +        return str(obj).decode(encoding)
  80.358 +    
  80.359 +def safestr(obj, encoding='utf-8'):
  80.360 +    r"""
  80.361 +    Converts any given object to utf-8 encoded string. 
  80.362 +    
  80.363 +        >>> safestr('hello')
  80.364 +        'hello'
  80.365 +        >>> safestr(u'\u1234')
  80.366 +        '\xe1\x88\xb4'
  80.367 +        >>> safestr(2)
  80.368 +        '2'
  80.369 +    """
  80.370 +    if isinstance(obj, unicode):
  80.371 +        return obj.encode(encoding)
  80.372 +    elif isinstance(obj, str):
  80.373 +        return obj
  80.374 +    elif hasattr(obj, 'next'): # iterator
  80.375 +        return itertools.imap(safestr, obj)
  80.376 +    else:
  80.377 +        return str(obj)
  80.378 +
  80.379 +# for backward-compatibility
  80.380 +utf8 = safestr
  80.381 +    
  80.382 +class TimeoutError(Exception): pass
  80.383 +def timelimit(timeout):
  80.384 +    """
  80.385 +    A decorator to limit a function to `timeout` seconds, raising `TimeoutError`
  80.386 +    if it takes longer.
  80.387 +    
  80.388 +        >>> import time
  80.389 +        >>> def meaningoflife():
  80.390 +        ...     time.sleep(.2)
  80.391 +        ...     return 42
  80.392 +        >>> 
  80.393 +        >>> timelimit(.1)(meaningoflife)()
  80.394 +        Traceback (most recent call last):
  80.395 +            ...
  80.396 +        TimeoutError: took too long
  80.397 +        >>> timelimit(1)(meaningoflife)()
  80.398 +        42
  80.399 +
  80.400 +    _Caveat:_ The function isn't stopped after `timeout` seconds but continues 
  80.401 +    executing in a separate thread. (There seems to be no way to kill a thread.)
  80.402 +
  80.403 +    inspired by <http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/473878>
  80.404 +    """
  80.405 +    def _1(function):
  80.406 +        def _2(*args, **kw):
  80.407 +            class Dispatch(threading.Thread):
  80.408 +                def __init__(self):
  80.409 +                    threading.Thread.__init__(self)
  80.410 +                    self.result = None
  80.411 +                    self.error = None
  80.412 +
  80.413 +                    self.setDaemon(True)
  80.414 +                    self.start()
  80.415 +
  80.416 +                def run(self):
  80.417 +                    try:
  80.418 +                        self.result = function(*args, **kw)
  80.419 +                    except:
  80.420 +                        self.error = sys.exc_info()
  80.421 +
  80.422 +            c = Dispatch()
  80.423 +            c.join(timeout)
  80.424 +            if c.isAlive():
  80.425 +                raise TimeoutError, 'took too long'
  80.426 +            if c.error:
  80.427 +                raise c.error[0], c.error[1]
  80.428 +            return c.result
  80.429 +        return _2
  80.430 +    return _1
  80.431 +
  80.432 +class Memoize:
  80.433 +    """
  80.434 +    'Memoizes' a function, caching its return values for each input.
  80.435 +    If `expires` is specified, values are recalculated after `expires` seconds.
  80.436 +    If `background` is specified, values are recalculated in a separate thread.
  80.437 +    
  80.438 +        >>> calls = 0
  80.439 +        >>> def howmanytimeshaveibeencalled():
  80.440 +        ...     global calls
  80.441 +        ...     calls += 1
  80.442 +        ...     return calls
  80.443 +        >>> fastcalls = memoize(howmanytimeshaveibeencalled)
  80.444 +        >>> howmanytimeshaveibeencalled()
  80.445 +        1
  80.446 +        >>> howmanytimeshaveibeencalled()
  80.447 +        2
  80.448 +        >>> fastcalls()
  80.449 +        3
  80.450 +        >>> fastcalls()
  80.451 +        3
  80.452 +        >>> import time
  80.453 +        >>> fastcalls = memoize(howmanytimeshaveibeencalled, .1, background=False)
  80.454 +        >>> fastcalls()
  80.455 +        4
  80.456 +        >>> fastcalls()
  80.457 +        4
  80.458 +        >>> time.sleep(.2)
  80.459 +        >>> fastcalls()
  80.460 +        5
  80.461 +        >>> def slowfunc():
  80.462 +        ...     time.sleep(.1)
  80.463 +        ...     return howmanytimeshaveibeencalled()
  80.464 +        >>> fastcalls = memoize(slowfunc, .2, background=True)
  80.465 +        >>> fastcalls()
  80.466 +        6
  80.467 +        >>> timelimit(.05)(fastcalls)()
  80.468 +        6
  80.469 +        >>> time.sleep(.2)
  80.470 +        >>> timelimit(.05)(fastcalls)()
  80.471 +        6
  80.472 +        >>> timelimit(.05)(fastcalls)()
  80.473 +        6
  80.474 +        >>> time.sleep(.2)
  80.475 +        >>> timelimit(.05)(fastcalls)()
  80.476 +        7
  80.477 +        >>> fastcalls = memoize(slowfunc, None, background=True)
  80.478 +        >>> threading.Thread(target=fastcalls).start()
  80.479 +        >>> time.sleep(.01)
  80.480 +        >>> fastcalls()
  80.481 +        9
  80.482 +    """
  80.483 +    def __init__(self, func, expires=None, background=True): 
  80.484 +        self.func = func
  80.485 +        self.cache = {}
  80.486 +        self.expires = expires
  80.487 +        self.background = background
  80.488 +        self.running = {}
  80.489 +    
  80.490 +    def __call__(self, *args, **keywords):
  80.491 +        key = (args, tuple(keywords.items()))
  80.492 +        if not self.running.get(key):
  80.493 +            self.running[key] = threading.Lock()
  80.494 +        def update(block=False):
  80.495 +            if self.running[key].acquire(block):
  80.496 +                try:
  80.497 +                    self.cache[key] = (self.func(*args, **keywords), time.time())
  80.498 +                finally:
  80.499 +                    self.running[key].release()
  80.500 +        
  80.501 +        if key not in self.cache: 
  80.502 +            update(block=True)
  80.503 +        elif self.expires and (time.time() - self.cache[key][1]) > self.expires:
  80.504 +            if self.background:
  80.505 +                threading.Thread(target=update).start()
  80.506 +            else:
  80.507 +                update()
  80.508 +        return self.cache[key][0]
  80.509 +
  80.510 +memoize = Memoize
  80.511 +
  80.512 +re_compile = memoize(re.compile) #@@ threadsafe?
  80.513 +re_compile.__doc__ = """
  80.514 +A memoized version of re.compile.
  80.515 +"""
  80.516 +
  80.517 +class _re_subm_proxy:
  80.518 +    def __init__(self): 
  80.519 +        self.match = None
  80.520 +    def __call__(self, match): 
  80.521 +        self.match = match
  80.522 +        return ''
  80.523 +
  80.524 +def re_subm(pat, repl, string):
  80.525 +    """
  80.526 +    Like re.sub, but returns the replacement _and_ the match object.
  80.527 +    
  80.528 +        >>> t, m = re_subm('g(oo+)fball', r'f\\1lish', 'goooooofball')
  80.529 +        >>> t
  80.530 +        'foooooolish'
  80.531 +        >>> m.groups()
  80.532 +        ('oooooo',)
  80.533 +    """
  80.534 +    compiled_pat = re_compile(pat)
  80.535 +    proxy = _re_subm_proxy()
  80.536 +    compiled_pat.sub(proxy.__call__, string)
  80.537 +    return compiled_pat.sub(repl, string), proxy.match
  80.538 +
  80.539 +def group(seq, size): 
  80.540 +    """
  80.541 +    Returns an iterator over a series of lists of length size from iterable.
  80.542 +
  80.543 +        >>> list(group([1,2,3,4], 2))
  80.544 +        [[1, 2], [3, 4]]
  80.545 +        >>> list(group([1,2,3,4,5], 2))
  80.546 +        [[1, 2], [3, 4], [5]]
  80.547 +    """
  80.548 +    def take(seq, n):
  80.549 +        for i in xrange(n):
  80.550 +            yield seq.next()
  80.551 +
  80.552 +    if not hasattr(seq, 'next'):  
  80.553 +        seq = iter(seq)
  80.554 +    while True: 
  80.555 +        x = list(take(seq, size))
  80.556 +        if x:
  80.557 +            yield x
  80.558 +        else:
  80.559 +            break
  80.560 +
  80.561 +def uniq(seq, key=None):
  80.562 +    """
  80.563 +    Removes duplicate elements from a list while preserving the order of the rest.
  80.564 +
  80.565 +        >>> uniq([9,0,2,1,0])
  80.566 +        [9, 0, 2, 1]
  80.567 +
  80.568 +    The value of the optional `key` parameter should be a function that
  80.569 +    takes a single argument and returns a key to test the uniqueness.
  80.570 +
  80.571 +        >>> uniq(["Foo", "foo", "bar"], key=lambda s: s.lower())
  80.572 +        ['Foo', 'bar']
  80.573 +    """
  80.574 +    key = key or (lambda x: x)
  80.575 +    seen = set()
  80.576 +    result = []
  80.577 +    for v in seq:
  80.578 +        k = key(v)
  80.579 +        if k in seen:
  80.580 +            continue
  80.581 +        seen.add(k)
  80.582 +        result.append(v)
  80.583 +    return result
  80.584 +
  80.585 +def iterview(x):
  80.586 +   """
  80.587 +   Takes an iterable `x` and returns an iterator over it
  80.588 +   which prints its progress to stderr as it iterates through.
  80.589 +   """
  80.590 +   WIDTH = 70
  80.591 +
  80.592 +   def plainformat(n, lenx):
  80.593 +       return '%5.1f%% (%*d/%d)' % ((float(n)/lenx)*100, len(str(lenx)), n, lenx)
  80.594 +
  80.595 +   def bars(size, n, lenx):
  80.596 +       val = int((float(n)*size)/lenx + 0.5)
  80.597 +       if size - val:
  80.598 +           spacing = ">" + (" "*(size-val))[1:]
  80.599 +       else:
  80.600 +           spacing = ""
  80.601 +       return "[%s%s]" % ("="*val, spacing)
  80.602 +
  80.603 +   def eta(elapsed, n, lenx):
  80.604 +       if n == 0:
  80.605 +           return '--:--:--'
  80.606 +       if n == lenx:
  80.607 +           secs = int(elapsed)
  80.608 +       else:
  80.609 +           secs = int((elapsed/n) * (lenx-n))
  80.610 +       mins, secs = divmod(secs, 60)
  80.611 +       hrs, mins = divmod(mins, 60)
  80.612 +
  80.613 +       return '%02d:%02d:%02d' % (hrs, mins, secs)
  80.614 +
  80.615 +   def format(starttime, n, lenx):
  80.616 +       out = plainformat(n, lenx) + ' '
  80.617 +       if n == lenx:
  80.618 +           end = '     '
  80.619 +       else:
  80.620 +           end = ' ETA '
  80.621 +       end += eta(time.time() - starttime, n, lenx)
  80.622 +       out += bars(WIDTH - len(out) - len(end), n, lenx)
  80.623 +       out += end
  80.624 +       return out
  80.625 +
  80.626 +   starttime = time.time()
  80.627 +   lenx = len(x)
  80.628 +   for n, y in enumerate(x):
  80.629 +       sys.stderr.write('\r' + format(starttime, n, lenx))
  80.630 +       yield y
  80.631 +   sys.stderr.write('\r' + format(starttime, n+1, lenx) + '\n')
  80.632 +
  80.633 +class IterBetter:
  80.634 +    """
  80.635 +    Returns an object that can be used as an iterator 
  80.636 +    but can also be used via __getitem__ (although it 
  80.637 +    cannot go backwards -- that is, you cannot request 
  80.638 +    `iterbetter[0]` after requesting `iterbetter[1]`).
  80.639 +    
  80.640 +        >>> import itertools
  80.641 +        >>> c = iterbetter(itertools.count())
  80.642 +        >>> c[1]
  80.643 +        1
  80.644 +        >>> c[5]
  80.645 +        5
  80.646 +        >>> c[3]
  80.647 +        Traceback (most recent call last):
  80.648 +            ...
  80.649 +        IndexError: already passed 3
  80.650 +
  80.651 +    For boolean test, IterBetter peeps at first value in the itertor without effecting the iteration.
  80.652 +
  80.653 +        >>> c = iterbetter(iter(range(5)))
  80.654 +        >>> bool(c)
  80.655 +        True
  80.656 +        >>> list(c)
  80.657 +        [0, 1, 2, 3, 4]
  80.658 +        >>> c = iterbetter(iter([]))
  80.659 +        >>> bool(c)
  80.660 +        False
  80.661 +        >>> list(c)
  80.662 +        []
  80.663 +    """
  80.664 +    def __init__(self, iterator): 
  80.665 +        self.i, self.c = iterator, 0
  80.666 +
  80.667 +    def __iter__(self): 
  80.668 +        if hasattr(self, "_head"):
  80.669 +            yield self._head
  80.670 +
  80.671 +        while 1:    
  80.672 +            yield self.i.next()
  80.673 +            self.c += 1
  80.674 +
  80.675 +    def __getitem__(self, i):
  80.676 +        #todo: slices
  80.677 +        if i < self.c: 
  80.678 +            raise IndexError, "already passed "+str(i)
  80.679 +        try:
  80.680 +            while i > self.c: 
  80.681 +                self.i.next()
  80.682 +                self.c += 1
  80.683 +            # now self.c == i
  80.684 +            self.c += 1
  80.685 +            return self.i.next()
  80.686 +        except StopIteration: 
  80.687 +            raise IndexError, str(i)
  80.688 +            
  80.689 +    def __nonzero__(self):
  80.690 +        if hasattr(self, "__len__"):
  80.691 +            return len(self) != 0
  80.692 +        elif hasattr(self, "_head"):
  80.693 +            return True
  80.694 +        else:
  80.695 +            try:
  80.696 +                self._head = self.i.next()
  80.697 +            except StopIteration:
  80.698 +                return False
  80.699 +            else:
  80.700 +                return True
  80.701 +
  80.702 +iterbetter = IterBetter
  80.703 +
  80.704 +def safeiter(it, cleanup=None, ignore_errors=True):
  80.705 +    """Makes an iterator safe by ignoring the exceptions occured during the iteration.
  80.706 +    """
  80.707 +    def next():
  80.708 +        while True:
  80.709 +            try:
  80.710 +                return it.next()
  80.711 +            except StopIteration:
  80.712 +                raise
  80.713 +            except:
  80.714 +                traceback.print_exc()
  80.715 +
  80.716 +    it = iter(it)
  80.717 +    while True:
  80.718 +        yield next()
  80.719 +
  80.720 +def safewrite(filename, content):
  80.721 +    """Writes the content to a temp file and then moves the temp file to 
  80.722 +    given filename to avoid overwriting the existing file in case of errors.
  80.723 +    """
  80.724 +    f = file(filename + '.tmp', 'w')
  80.725 +    f.write(content)
  80.726 +    f.close()
  80.727 +    os.rename(f.name, filename)
  80.728 +
  80.729 +def dictreverse(mapping):
  80.730 +    """
  80.731 +    Returns a new dictionary with keys and values swapped.
  80.732 +    
  80.733 +        >>> dictreverse({1: 2, 3: 4})
  80.734 +        {2: 1, 4: 3}
  80.735 +    """
  80.736 +    return dict([(value, key) for (key, value) in mapping.iteritems()])
  80.737 +
  80.738 +def dictfind(dictionary, element):
  80.739 +    """
  80.740 +    Returns a key whose value in `dictionary` is `element` 
  80.741 +    or, if none exists, None.
  80.742 +    
  80.743 +        >>> d = {1:2, 3:4}
  80.744 +        >>> dictfind(d, 4)
  80.745 +        3
  80.746 +        >>> dictfind(d, 5)
  80.747 +    """
  80.748 +    for (key, value) in dictionary.iteritems():
  80.749 +        if element is value: 
  80.750 +            return key
  80.751 +
  80.752 +def dictfindall(dictionary, element):
  80.753 +    """
  80.754 +    Returns the keys whose values in `dictionary` are `element`
  80.755 +    or, if none exists, [].
  80.756 +    
  80.757 +        >>> d = {1:4, 3:4}
  80.758 +        >>> dictfindall(d, 4)
  80.759 +        [1, 3]
  80.760 +        >>> dictfindall(d, 5)
  80.761 +        []
  80.762 +    """
  80.763 +    res = []
  80.764 +    for (key, value) in dictionary.iteritems():
  80.765 +        if element is value:
  80.766 +            res.append(key)
  80.767 +    return res
  80.768 +
  80.769 +def dictincr(dictionary, element):
  80.770 +    """
  80.771 +    Increments `element` in `dictionary`, 
  80.772 +    setting it to one if it doesn't exist.
  80.773 +    
  80.774 +        >>> d = {1:2, 3:4}
  80.775 +        >>> dictincr(d, 1)
  80.776 +        3
  80.777 +        >>> d[1]
  80.778 +        3
  80.779 +        >>> dictincr(d, 5)
  80.780 +        1
  80.781 +        >>> d[5]
  80.782 +        1
  80.783 +    """
  80.784 +    dictionary.setdefault(element, 0)
  80.785 +    dictionary[element] += 1
  80.786 +    return dictionary[element]
  80.787 +
  80.788 +def dictadd(*dicts):
  80.789 +    """
  80.790 +    Returns a dictionary consisting of the keys in the argument dictionaries.
  80.791 +    If they share a key, the value from the last argument is used.
  80.792 +    
  80.793 +        >>> dictadd({1: 0, 2: 0}, {2: 1, 3: 1})
  80.794 +        {1: 0, 2: 1, 3: 1}
  80.795 +    """
  80.796 +    result = {}
  80.797 +    for dct in dicts:
  80.798 +        result.update(dct)
  80.799 +    return result
  80.800 +
  80.801 +def requeue(queue, index=-1):
  80.802 +    """Returns the element at index after moving it to the beginning of the queue.
  80.803 +
  80.804 +        >>> x = [1, 2, 3, 4]
  80.805 +        >>> requeue(x)
  80.806 +        4
  80.807 +        >>> x
  80.808 +        [4, 1, 2, 3]
  80.809 +    """
  80.810 +    x = queue.pop(index)
  80.811 +    queue.insert(0, x)
  80.812 +    return x
  80.813 +
  80.814 +def restack(stack, index=0):
  80.815 +    """Returns the element at index after moving it to the top of stack.
  80.816 +
  80.817 +           >>> x = [1, 2, 3, 4]
  80.818 +           >>> restack(x)
  80.819 +           1
  80.820 +           >>> x
  80.821 +           [2, 3, 4, 1]
  80.822 +    """
  80.823 +    x = stack.pop(index)
  80.824 +    stack.append(x)
  80.825 +    return x
  80.826 +
  80.827 +def listget(lst, ind, default=None):
  80.828 +    """
  80.829 +    Returns `lst[ind]` if it exists, `default` otherwise.
  80.830 +    
  80.831 +        >>> listget(['a'], 0)
  80.832 +        'a'
  80.833 +        >>> listget(['a'], 1)
  80.834 +        >>> listget(['a'], 1, 'b')
  80.835 +        'b'
  80.836 +    """
  80.837 +    if len(lst)-1 < ind: 
  80.838 +        return default
  80.839 +    return lst[ind]
  80.840 +
  80.841 +def intget(integer, default=None):
  80.842 +    """
  80.843 +    Returns `integer` as an int or `default` if it can't.
  80.844 +    
  80.845 +        >>> intget('3')
  80.846 +        3
  80.847 +        >>> intget('3a')
  80.848 +        >>> intget('3a', 0)
  80.849 +        0
  80.850 +    """
  80.851 +    try:
  80.852 +        return int(integer)
  80.853 +    except (TypeError, ValueError):
  80.854 +        return default
  80.855 +
  80.856 +def datestr(then, now=None):
  80.857 +    """
  80.858 +    Converts a (UTC) datetime object to a nice string representation.
  80.859 +    
  80.860 +        >>> from datetime import datetime, timedelta
  80.861 +        >>> d = datetime(1970, 5, 1)
  80.862 +        >>> datestr(d, now=d)
  80.863 +        '0 microseconds ago'
  80.864 +        >>> for t, v in {
  80.865 +        ...   timedelta(microseconds=1): '1 microsecond ago',
  80.866 +        ...   timedelta(microseconds=2): '2 microseconds ago',
  80.867 +        ...   -timedelta(microseconds=1): '1 microsecond from now',
  80.868 +        ...   -timedelta(microseconds=2): '2 microseconds from now',
  80.869 +        ...   timedelta(microseconds=2000): '2 milliseconds ago',
  80.870 +        ...   timedelta(seconds=2): '2 seconds ago',
  80.871 +        ...   timedelta(seconds=2*60): '2 minutes ago',
  80.872 +        ...   timedelta(seconds=2*60*60): '2 hours ago',
  80.873 +        ...   timedelta(days=2): '2 days ago',
  80.874 +        ... }.iteritems():
  80.875 +        ...     assert datestr(d, now=d+t) == v
  80.876 +        >>> datestr(datetime(1970, 1, 1), now=d)
  80.877 +        'January  1'
  80.878 +        >>> datestr(datetime(1969, 1, 1), now=d)
  80.879 +        'January  1, 1969'
  80.880 +        >>> datestr(datetime(1970, 6, 1), now=d)
  80.881 +        'June  1, 1970'
  80.882 +        >>> datestr(None)
  80.883 +        ''
  80.884 +    """
  80.885 +    def agohence(n, what, divisor=None):
  80.886 +        if divisor: n = n // divisor
  80.887 +
  80.888 +        out = str(abs(n)) + ' ' + what       # '2 day'
  80.889 +        if abs(n) != 1: out += 's'           # '2 days'
  80.890 +        out += ' '                           # '2 days '
  80.891 +        if n < 0:
  80.892 +            out += 'from now'
  80.893 +        else:
  80.894 +            out += 'ago'
  80.895 +        return out                           # '2 days ago'
  80.896 +
  80.897 +    oneday = 24 * 60 * 60
  80.898 +
  80.899 +    if not then: return ""
  80.900 +    if not now: now = datetime.datetime.utcnow()
  80.901 +    if type(now).__name__ == "DateTime":
  80.902 +        now = datetime.datetime.fromtimestamp(now)
  80.903 +    if type(then).__name__ == "DateTime":
  80.904 +        then = datetime.datetime.fromtimestamp(then)
  80.905 +    elif type(then).__name__ == "date":
  80.906 +        then = datetime.datetime(then.year, then.month, then.day)
  80.907 +
  80.908 +    delta = now - then
  80.909 +    deltaseconds = int(delta.days * oneday + delta.seconds + delta.microseconds * 1e-06)
  80.910 +    deltadays = abs(deltaseconds) // oneday
  80.911 +    if deltaseconds < 0: deltadays *= -1 # fix for oddity of floor
  80.912 +
  80.913 +    if deltadays:
  80.914 +        if abs(deltadays) < 4:
  80.915 +            return agohence(deltadays, 'day')
  80.916 +
  80.917 +        try:
  80.918 +            out = then.strftime('%B %e') # e.g. 'June  3'
  80.919 +        except ValueError:
  80.920 +            # %e doesn't work on Windows.
  80.921 +            out = then.strftime('%B %d') # e.g. 'June 03'
  80.922 +
  80.923 +        if then.year != now.year or deltadays < 0:
  80.924 +            out += ', %s' % then.year
  80.925 +        return out
  80.926 +
  80.927 +    if int(deltaseconds):
  80.928 +        if abs(deltaseconds) > (60 * 60):
  80.929 +            return agohence(deltaseconds, 'hour', 60 * 60)
  80.930 +        elif abs(deltaseconds) > 60:
  80.931 +            return agohence(deltaseconds, 'minute', 60)
  80.932 +        else:
  80.933 +            return agohence(deltaseconds, 'second')
  80.934 +
  80.935 +    deltamicroseconds = delta.microseconds
  80.936 +    if delta.days: deltamicroseconds = int(delta.microseconds - 1e6) # datetime oddity
  80.937 +    if abs(deltamicroseconds) > 1000:
  80.938 +        return agohence(deltamicroseconds, 'millisecond', 1000)
  80.939 +
  80.940 +    return agohence(deltamicroseconds, 'microsecond')
  80.941 +
  80.942 +def numify(string):
  80.943 +    """
  80.944 +    Removes all non-digit characters from `string`.
  80.945 +    
  80.946 +        >>> numify('800-555-1212')
  80.947 +        '8005551212'
  80.948 +        >>> numify('800.555.1212')
  80.949 +        '8005551212'
  80.950 +    
  80.951 +    """
  80.952 +    return ''.join([c for c in str(string) if c.isdigit()])
  80.953 +
  80.954 +def denumify(string, pattern):
  80.955 +    """
  80.956 +    Formats `string` according to `pattern`, where the letter X gets replaced
  80.957 +    by characters from `string`.
  80.958 +    
  80.959 +        >>> denumify("8005551212", "(XXX) XXX-XXXX")
  80.960 +        '(800) 555-1212'
  80.961 +    
  80.962 +    """
  80.963 +    out = []
  80.964 +    for c in pattern:
  80.965 +        if c == "X":
  80.966 +            out.append(string[0])
  80.967 +            string = string[1:]
  80.968 +        else:
  80.969 +            out.append(c)
  80.970 +    return ''.join(out)
  80.971 +
  80.972 +def commify(n):
  80.973 +    """
  80.974 +    Add commas to an integer `n`.
  80.975 +
  80.976 +        >>> commify(1)
  80.977 +        '1'
  80.978 +        >>> commify(123)
  80.979 +        '123'
  80.980 +        >>> commify(1234)
  80.981 +        '1,234'
  80.982 +        >>> commify(1234567890)
  80.983 +        '1,234,567,890'
  80.984 +        >>> commify(123.0)
  80.985 +        '123.0'
  80.986 +        >>> commify(1234.5)
  80.987 +        '1,234.5'
  80.988 +        >>> commify(1234.56789)
  80.989 +        '1,234.56789'
  80.990 +        >>> commify('%.2f' % 1234.5)
  80.991 +        '1,234.50'
  80.992 +        >>> commify(None)
  80.993 +        >>>
  80.994 +
  80.995 +    """
  80.996 +    if n is None: return None
  80.997 +    n = str(n)
  80.998 +    if '.' in n:
  80.999 +        dollars, cents = n.split('.')
 80.1000 +    else:
 80.1001 +        dollars, cents = n, None
 80.1002 +
 80.1003 +    r = []
 80.1004 +    for i, c in enumerate(str(dollars)[::-1]):
 80.1005 +        if i and (not (i % 3)):
 80.1006 +            r.insert(0, ',')
 80.1007 +        r.insert(0, c)
 80.1008 +    out = ''.join(r)
 80.1009 +    if cents:
 80.1010 +        out += '.' + cents
 80.1011 +    return out
 80.1012 +
 80.1013 +def dateify(datestring):
 80.1014 +    """
 80.1015 +    Formats a numified `datestring` properly.
 80.1016 +    """
 80.1017 +    return denumify(datestring, "XXXX-XX-XX XX:XX:XX")
 80.1018 +
 80.1019 +
 80.1020 +def nthstr(n):
 80.1021 +    """
 80.1022 +    Formats an ordinal.
 80.1023 +    Doesn't handle negative numbers.
 80.1024 +
 80.1025 +        >>> nthstr(1)
 80.1026 +        '1st'
 80.1027 +        >>> nthstr(0)
 80.1028 +        '0th'
 80.1029 +        >>> [nthstr(x) for x in [2, 3, 4, 5, 10, 11, 12, 13, 14, 15]]
 80.1030 +        ['2nd', '3rd', '4th', '5th', '10th', '11th', '12th', '13th', '14th', '15th']
 80.1031 +        >>> [nthstr(x) for x in [91, 92, 93, 94, 99, 100, 101, 102]]
 80.1032 +        ['91st', '92nd', '93rd', '94th', '99th', '100th', '101st', '102nd']
 80.1033 +        >>> [nthstr(x) for x in [111, 112, 113, 114, 115]]
 80.1034 +        ['111th', '112th', '113th', '114th', '115th']
 80.1035 +
 80.1036 +    """
 80.1037 +    
 80.1038 +    assert n >= 0
 80.1039 +    if n % 100 in [11, 12, 13]: return '%sth' % n
 80.1040 +    return {1: '%sst', 2: '%snd', 3: '%srd'}.get(n % 10, '%sth') % n
 80.1041 +
 80.1042 +def cond(predicate, consequence, alternative=None):
 80.1043 +    """
 80.1044 +    Function replacement for if-else to use in expressions.
 80.1045 +        
 80.1046 +        >>> x = 2
 80.1047 +        >>> cond(x % 2 == 0, "even", "odd")
 80.1048 +        'even'
 80.1049 +        >>> cond(x % 2 == 0, "even", "odd") + '_row'
 80.1050 +        'even_row'
 80.1051 +    """
 80.1052 +    if predicate:
 80.1053 +        return consequence
 80.1054 +    else:
 80.1055 +        return alternative
 80.1056 +
 80.1057 +class CaptureStdout:
 80.1058 +    """
 80.1059 +    Captures everything `func` prints to stdout and returns it instead.
 80.1060 +    
 80.1061 +        >>> def idiot():
 80.1062 +        ...     print "foo"
 80.1063 +        >>> capturestdout(idiot)()
 80.1064 +        'foo\\n'
 80.1065 +    
 80.1066 +    **WARNING:** Not threadsafe!
 80.1067 +    """
 80.1068 +    def __init__(self, func): 
 80.1069 +        self.func = func
 80.1070 +    def __call__(self, *args, **keywords):
 80.1071 +        from cStringIO import StringIO
 80.1072 +        # Not threadsafe!
 80.1073 +        out = StringIO()
 80.1074 +        oldstdout = sys.stdout
 80.1075 +        sys.stdout = out
 80.1076 +        try: 
 80.1077 +            self.func(*args, **keywords)
 80.1078 +        finally: 
 80.1079 +            sys.stdout = oldstdout
 80.1080 +        return out.getvalue()
 80.1081 +
 80.1082 +capturestdout = CaptureStdout
 80.1083 +
 80.1084 +class Profile:
 80.1085 +    """
 80.1086 +    Profiles `func` and returns a tuple containing its output
 80.1087 +    and a string with human-readable profiling information.
 80.1088 +        
 80.1089 +        >>> import time
 80.1090 +        >>> out, inf = profile(time.sleep)(.001)
 80.1091 +        >>> out
 80.1092 +        >>> inf[:10].strip()
 80.1093 +        'took 0.0'
 80.1094 +    """
 80.1095 +    def __init__(self, func): 
 80.1096 +        self.func = func
 80.1097 +    def __call__(self, *args): ##, **kw):   kw unused
 80.1098 +        import hotshot, hotshot.stats, os, tempfile ##, time already imported
 80.1099 +        f, filename = tempfile.mkstemp()
 80.1100 +        os.close(f)
 80.1101 +        
 80.1102 +        prof = hotshot.Profile(filename)
 80.1103 +
 80.1104 +        stime = time.time()
 80.1105 +        result = prof.runcall(self.func, *args)
 80.1106 +        stime = time.time() - stime
 80.1107 +        prof.close()
 80.1108 +
 80.1109 +        import cStringIO
 80.1110 +        out = cStringIO.StringIO()
 80.1111 +        stats = hotshot.stats.load(filename)
 80.1112 +        stats.stream = out
 80.1113 +        stats.strip_dirs()
 80.1114 +        stats.sort_stats('time', 'calls')
 80.1115 +        stats.print_stats(40)
 80.1116 +        stats.print_callers()
 80.1117 +
 80.1118 +        x =  '\n\ntook '+ str(stime) + ' seconds\n'
 80.1119 +        x += out.getvalue()
 80.1120 +
 80.1121 +        # remove the tempfile
 80.1122 +        try:
 80.1123 +            os.remove(filename)
 80.1124 +        except IOError:
 80.1125 +            pass
 80.1126 +            
 80.1127 +        return result, x
 80.1128 +
 80.1129 +profile = Profile
 80.1130 +
 80.1131 +
 80.1132 +import traceback
 80.1133 +# hack for compatibility with Python 2.3:
 80.1134 +if not hasattr(traceback, 'format_exc'):
 80.1135 +    from cStringIO import StringIO
 80.1136 +    def format_exc(limit=None):
 80.1137 +        strbuf = StringIO()
 80.1138 +        traceback.print_exc(limit, strbuf)
 80.1139 +        return strbuf.getvalue()
 80.1140 +    traceback.format_exc = format_exc
 80.1141 +
 80.1142 +def tryall(context, prefix=None):
 80.1143 +    """
 80.1144 +    Tries a series of functions and prints their results. 
 80.1145 +    `context` is a dictionary mapping names to values; 
 80.1146 +    the value will only be tried if it's callable.
 80.1147 +    
 80.1148 +        >>> tryall(dict(j=lambda: True))
 80.1149 +        j: True
 80.1150 +        ----------------------------------------
 80.1151 +        results:
 80.1152 +           True: 1
 80.1153 +
 80.1154 +    For example, you might have a file `test/stuff.py` 
 80.1155 +    with a series of functions testing various things in it. 
 80.1156 +    At the bottom, have a line:
 80.1157 +
 80.1158 +        if __name__ == "__main__": tryall(globals())
 80.1159 +
 80.1160 +    Then you can run `python test/stuff.py` and get the results of 
 80.1161 +    all the tests.
 80.1162 +    """
 80.1163 +    context = context.copy() # vars() would update
 80.1164 +    results = {}
 80.1165 +    for (key, value) in context.iteritems():
 80.1166 +        if not hasattr(value, '__call__'): 
 80.1167 +            continue
 80.1168 +        if prefix and not key.startswith(prefix): 
 80.1169 +            continue
 80.1170 +        print key + ':',
 80.1171 +        try:
 80.1172 +            r = value()
 80.1173 +            dictincr(results, r)
 80.1174 +            print r
 80.1175 +        except:
 80.1176 +            print 'ERROR'
 80.1177 +            dictincr(results, 'ERROR')
 80.1178 +            print '   ' + '\n   '.join(traceback.format_exc().split('\n'))
 80.1179 +        
 80.1180 +    print '-'*40
 80.1181 +    print 'results:'
 80.1182 +    for (key, value) in results.iteritems():
 80.1183 +        print ' '*2, str(key)+':', value
 80.1184 +        
 80.1185 +class ThreadedDict(threadlocal):
 80.1186 +    """
 80.1187 +    Thread local storage.
 80.1188 +    
 80.1189 +        >>> d = ThreadedDict()
 80.1190 +        >>> d.x = 1
 80.1191 +        >>> d.x
 80.1192 +        1
 80.1193 +        >>> import threading
 80.1194 +        >>> def f(): d.x = 2
 80.1195 +        ...
 80.1196 +        >>> t = threading.Thread(target=f)
 80.1197 +        >>> t.start()
 80.1198 +        >>> t.join()
 80.1199 +        >>> d.x
 80.1200 +        1
 80.1201 +    """
 80.1202 +    _instances = set()
 80.1203 +    
 80.1204 +    def __init__(self):
 80.1205 +        ThreadedDict._instances.add(self)
 80.1206 +        
 80.1207 +    def __del__(self):
 80.1208 +        ThreadedDict._instances.remove(self)
 80.1209 +        
 80.1210 +    def __hash__(self):
 80.1211 +        return id(self)
 80.1212 +    
 80.1213 +    def clear_all():
 80.1214 +        """Clears all ThreadedDict instances.
 80.1215 +        """
 80.1216 +        for t in list(ThreadedDict._instances):
 80.1217 +            t.clear()
 80.1218 +    clear_all = staticmethod(clear_all)
 80.1219 +    
 80.1220 +    # Define all these methods to more or less fully emulate dict -- attribute access
 80.1221 +    # is built into threading.local.
 80.1222 +
 80.1223 +    def __getitem__(self, key):
 80.1224 +        return self.__dict__[key]
 80.1225 +
 80.1226 +    def __setitem__(self, key, value):
 80.1227 +        self.__dict__[key] = value
 80.1228 +
 80.1229 +    def __delitem__(self, key):
 80.1230 +        del self.__dict__[key]
 80.1231 +
 80.1232 +    def __contains__(self, key):
 80.1233 +        return key in self.__dict__
 80.1234 +
 80.1235 +    has_key = __contains__
 80.1236 +        
 80.1237 +    def clear(self):
 80.1238 +        self.__dict__.clear()
 80.1239 +
 80.1240 +    def copy(self):
 80.1241 +        return self.__dict__.copy()
 80.1242 +
 80.1243 +    def get(self, key, default=None):
 80.1244 +        return self.__dict__.get(key, default)
 80.1245 +
 80.1246 +    def items(self):
 80.1247 +        return self.__dict__.items()
 80.1248 +
 80.1249 +    def iteritems(self):
 80.1250 +        return self.__dict__.iteritems()
 80.1251 +
 80.1252 +    def keys(self):
 80.1253 +        return self.__dict__.keys()
 80.1254 +
 80.1255 +    def iterkeys(self):
 80.1256 +        return self.__dict__.iterkeys()
 80.1257 +
 80.1258 +    iter = iterkeys
 80.1259 +
 80.1260 +    def values(self):
 80.1261 +        return self.__dict__.values()
 80.1262 +
 80.1263 +    def itervalues(self):
 80.1264 +        return self.__dict__.itervalues()
 80.1265 +
 80.1266 +    def pop(self, key, *args):
 80.1267 +        return self.__dict__.pop(key, *args)
 80.1268 +
 80.1269 +    def popitem(self):
 80.1270 +        return self.__dict__.popitem()
 80.1271 +
 80.1272 +    def setdefault(self, key, default=None):
 80.1273 +        return self.__dict__.setdefault(key, default)
 80.1274 +
 80.1275 +    def update(self, *args, **kwargs):
 80.1276 +        self.__dict__.update(*args, **kwargs)
 80.1277 +
 80.1278 +    def __repr__(self):
 80.1279 +        return '<ThreadedDict %r>' % self.__dict__
 80.1280 +
 80.1281 +    __str__ = __repr__
 80.1282 +    
 80.1283 +threadeddict = ThreadedDict
 80.1284 +
 80.1285 +def autoassign(self, locals):
 80.1286 +    """
 80.1287 +    Automatically assigns local variables to `self`.
 80.1288 +    
 80.1289 +        >>> self = storage()
 80.1290 +        >>> autoassign(self, dict(a=1, b=2))
 80.1291 +        >>> self
 80.1292 +        <Storage {'a': 1, 'b': 2}>
 80.1293 +    
 80.1294 +    Generally used in `__init__` methods, as in:
 80.1295 +
 80.1296 +        def __init__(self, foo, bar, baz=1): autoassign(self, locals())
 80.1297 +    """
 80.1298 +    for (key, value) in locals.iteritems():
 80.1299 +        if key == 'self': 
 80.1300 +            continue
 80.1301 +        setattr(self, key, value)
 80.1302 +
 80.1303 +def to36(q):
 80.1304 +    """
 80.1305 +    Converts an integer to base 36 (a useful scheme for human-sayable IDs).
 80.1306 +    
 80.1307 +        >>> to36(35)
 80.1308 +        'z'
 80.1309 +        >>> to36(119292)
 80.1310 +        '2k1o'
 80.1311 +        >>> int(to36(939387374), 36)
 80.1312 +        939387374
 80.1313 +        >>> to36(0)
 80.1314 +        '0'
 80.1315 +        >>> to36(-393)
 80.1316 +        Traceback (most recent call last):
 80.1317 +            ... 
 80.1318 +        ValueError: must supply a positive integer
 80.1319 +    
 80.1320 +    """
 80.1321 +    if q < 0: raise ValueError, "must supply a positive integer"
 80.1322 +    letters = "0123456789abcdefghijklmnopqrstuvwxyz"
 80.1323 +    converted = []
 80.1324 +    while q != 0:
 80.1325 +        q, r = divmod(q, 36)
 80.1326 +        converted.insert(0, letters[r])
 80.1327 +    return "".join(converted) or '0'
 80.1328 +
 80.1329 +
 80.1330 +r_url = re_compile('(?<!\()(http://(\S+))')
 80.1331 +def safemarkdown(text):
 80.1332 +    """
 80.1333 +    Converts text to HTML following the rules of Markdown, but blocking any
 80.1334 +    outside HTML input, so that only the things supported by Markdown
 80.1335 +    can be used. Also converts raw URLs to links.
 80.1336 +
 80.1337 +    (requires [markdown.py](http://webpy.org/markdown.py))
 80.1338 +    """
 80.1339 +    from markdown import markdown
 80.1340 +    if text:
 80.1341 +        text = text.replace('<', '&lt;')
 80.1342 +        # TODO: automatically get page title?
 80.1343 +        text = r_url.sub(r'<\1>', text)
 80.1344 +        text = markdown(text)
 80.1345 +        return text
 80.1346 +
 80.1347 +def sendmail(from_address, to_address, subject, message, headers=None, **kw):
 80.1348 +    """
 80.1349 +    Sends the email message `message` with mail and envelope headers
 80.1350 +    for from `from_address_` to `to_address` with `subject`. 
 80.1351 +    Additional email headers can be specified with the dictionary 
 80.1352 +    `headers.
 80.1353 +    
 80.1354 +    Optionally cc, bcc and attachments can be specified as keyword arguments.
 80.1355 +    Attachments must be an iterable and each attachment can be either a 
 80.1356 +    filename or a file object or a dictionary with filename, content and 
 80.1357 +    optionally content_type keys.
 80.1358 +
 80.1359 +    If `web.config.smtp_server` is set, it will send the message
 80.1360 +    to that SMTP server. Otherwise it will look for 
 80.1361 +    `/usr/sbin/sendmail`, the typical location for the sendmail-style
 80.1362 +    binary. To use sendmail from a different path, set `web.config.sendmail_path`.
 80.1363 +    """
 80.1364 +    attachments = kw.pop("attachments", [])
 80.1365 +    mail = _EmailMessage(from_address, to_address, subject, message, headers, **kw)
 80.1366 +
 80.1367 +    for a in attachments:
 80.1368 +        if isinstance(a, dict):
 80.1369 +            mail.attach(a['filename'], a['content'], a.get('content_type'))
 80.1370 +        elif hasattr(a, 'read'): # file
 80.1371 +            filename = os.path.basename(getattr(a, "name", ""))
 80.1372 +            content_type = getattr(a, 'content_type', None)
 80.1373 +            mail.attach(filename, a.read(), content_type)
 80.1374 +        elif isinstance(a, basestring):
 80.1375 +            f = open(a, 'rb')
 80.1376 +            content = f.read()
 80.1377 +            f.close()
 80.1378 +            filename = os.path.basename(a)
 80.1379 +            mail.attach(filename, content, None)
 80.1380 +        else:
 80.1381 +            raise ValueError, "Invalid attachment: %s" % repr(a)
 80.1382 +            
 80.1383 +    mail.send()
 80.1384 +
 80.1385 +class _EmailMessage:
 80.1386 +    def __init__(self, from_address, to_address, subject, message, headers=None, **kw):
 80.1387 +        def listify(x):
 80.1388 +            if not isinstance(x, list):
 80.1389 +                return [safestr(x)]
 80.1390 +            else:
 80.1391 +                return [safestr(a) for a in x]
 80.1392 +    
 80.1393 +        subject = safestr(subject)
 80.1394 +        message = safestr(message)
 80.1395 +
 80.1396 +        from_address = safestr(from_address)
 80.1397 +        to_address = listify(to_address)    
 80.1398 +        cc = listify(kw.get('cc', []))
 80.1399 +        bcc = listify(kw.get('bcc', []))
 80.1400 +        recipients = to_address + cc + bcc
 80.1401 +
 80.1402 +        import email.Utils
 80.1403 +        self.from_address = email.Utils.parseaddr(from_address)[1]
 80.1404 +        self.recipients = [email.Utils.parseaddr(r)[1] for r in recipients]        
 80.1405 +    
 80.1406 +        self.headers = dictadd({
 80.1407 +          'From': from_address,
 80.1408 +          'To': ", ".join(to_address),
 80.1409 +          'Subject': subject
 80.1410 +        }, headers or {})
 80.1411 +
 80.1412 +        if cc:
 80.1413 +            self.headers['Cc'] = ", ".join(cc)
 80.1414 +    
 80.1415 +        self.message = self.new_message()
 80.1416 +        self.message.add_header("Content-Transfer-Encoding", "7bit")
 80.1417 +        self.message.add_header("Content-Disposition", "inline")
 80.1418 +        self.message.add_header("MIME-Version", "1.0")
 80.1419 +        self.message.set_payload(message, 'utf-8')
 80.1420 +        self.multipart = False
 80.1421 +        
 80.1422 +    def new_message(self):
 80.1423 +        from email.Message import Message
 80.1424 +        return Message()
 80.1425 +        
 80.1426 +    def attach(self, filename, content, content_type=None):
 80.1427 +        if not self.multipart:
 80.1428 +            msg = self.new_message()
 80.1429 +            msg.add_header("Content-Type", "multipart/mixed")
 80.1430 +            msg.attach(self.message)
 80.1431 +            self.message = msg
 80.1432 +            self.multipart = True
 80.1433 +                        
 80.1434 +        import mimetypes
 80.1435 +        try:
 80.1436 +            from email import encoders
 80.1437 +        except:
 80.1438 +            from email import Encoders as encoders
 80.1439 +            
 80.1440 +        content_type = content_type or mimetypes.guess_type(filename)[0] or "applcation/octet-stream"
 80.1441 +        
 80.1442 +        msg = self.new_message()
 80.1443 +        msg.set_payload(content)
 80.1444 +        msg.add_header('Content-Type', content_type)
 80.1445 +        msg.add_header('Content-Disposition', 'attachment', filename=filename)
 80.1446 +        
 80.1447 +        if not content_type.startswith("text/"):
 80.1448 +            encoders.encode_base64(msg)
 80.1449 +            
 80.1450 +        self.message.attach(msg)
 80.1451 +
 80.1452 +    def prepare_message(self):
 80.1453 +        for k, v in self.headers.iteritems():
 80.1454 +            if k.lower() == "content-type":
 80.1455 +                self.message.set_type(v)
 80.1456 +            else:
 80.1457 +                self.message.add_header(k, v)
 80.1458 +
 80.1459 +        self.headers = {}
 80.1460 +
 80.1461 +    def send(self):
 80.1462 +        try:
 80.1463 +            import webapi
 80.1464 +        except ImportError:
 80.1465 +            webapi = Storage(config=Storage())
 80.1466 +
 80.1467 +        self.prepare_message()
 80.1468 +        message_text = self.message.as_string()
 80.1469 +    
 80.1470 +        if webapi.config.get('smtp_server'):
 80.1471 +            server = webapi.config.get('smtp_server')
 80.1472 +            port = webapi.config.get('smtp_port', 0)
 80.1473 +            username = webapi.config.get('smtp_username') 
 80.1474 +            password = webapi.config.get('smtp_password')
 80.1475 +            debug_level = webapi.config.get('smtp_debuglevel', None)
 80.1476 +            starttls = webapi.config.get('smtp_starttls', False)
 80.1477 +
 80.1478 +            import smtplib
 80.1479 +            smtpserver = smtplib.SMTP(server, port)
 80.1480 +
 80.1481 +            if debug_level:
 80.1482 +                smtpserver.set_debuglevel(debug_level)
 80.1483 +
 80.1484 +            if starttls:
 80.1485 +                smtpserver.ehlo()
 80.1486 +                smtpserver.starttls()
 80.1487 +                smtpserver.ehlo()
 80.1488 +
 80.1489 +            if username and password:
 80.1490 +                smtpserver.login(username, password)
 80.1491 +
 80.1492 +            smtpserver.sendmail(self.from_address, self.recipients, message_text)
 80.1493 +            smtpserver.quit()
 80.1494 +        elif webapi.config.get('email_engine') == 'aws':
 80.1495 +            import boto.ses
 80.1496 +            c = boto.ses.SESConnection(
 80.1497 +              aws_access_key_id=webapi.config.get('aws_access_key_id'),
 80.1498 +              aws_secret_access_key=web.api.config.get('aws_secret_access_key'))
 80.1499 +            c.send_raw_email(self.from_address, message_text, self.from_recipients)
 80.1500 +        else:
 80.1501 +            sendmail = webapi.config.get('sendmail_path', '/usr/sbin/sendmail')
 80.1502 +        
 80.1503 +            assert not self.from_address.startswith('-'), 'security'
 80.1504 +            for r in self.recipients:
 80.1505 +                assert not r.startswith('-'), 'security'
 80.1506 +                
 80.1507 +            cmd = [sendmail, '-f', self.from_address] + self.recipients
 80.1508 +
 80.1509 +            if subprocess:
 80.1510 +                p = subprocess.Popen(cmd, stdin=subprocess.PIPE)
 80.1511 +                p.stdin.write(message_text)
 80.1512 +                p.stdin.close()
 80.1513 +                p.wait()
 80.1514 +            else:
 80.1515 +                i, o = os.popen2(cmd)
 80.1516 +                i.write(message)
 80.1517 +                i.close()
 80.1518 +                o.close()
 80.1519 +                del i, o
 80.1520 +                
 80.1521 +    def __repr__(self):
 80.1522 +        return "<EmailMessage>"
 80.1523 +    
 80.1524 +    def __str__(self):
 80.1525 +        return self.message.as_string()
 80.1526 +
 80.1527 +if __name__ == "__main__":
 80.1528 +    import doctest
 80.1529 +    doctest.testmod()
    81.1 Binary file OpenSecurity/install/web.py-0.37/web/utils.pyc has changed
    82.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    82.2 +++ b/OpenSecurity/install/web.py-0.37/web/webapi.py	Mon Dec 02 14:02:05 2013 +0100
    82.3 @@ -0,0 +1,525 @@
    82.4 +"""
    82.5 +Web API (wrapper around WSGI)
    82.6 +(from web.py)
    82.7 +"""
    82.8 +
    82.9 +__all__ = [
   82.10 +    "config",
   82.11 +    "header", "debug",
   82.12 +    "input", "data",
   82.13 +    "setcookie", "cookies",
   82.14 +    "ctx", 
   82.15 +    "HTTPError", 
   82.16 +
   82.17 +    # 200, 201, 202
   82.18 +    "OK", "Created", "Accepted",    
   82.19 +    "ok", "created", "accepted",
   82.20 +    
   82.21 +    # 301, 302, 303, 304, 307
   82.22 +    "Redirect", "Found", "SeeOther", "NotModified", "TempRedirect", 
   82.23 +    "redirect", "found", "seeother", "notmodified", "tempredirect",
   82.24 +
   82.25 +    # 400, 401, 403, 404, 405, 406, 409, 410, 412, 415
   82.26 +    "BadRequest", "Unauthorized", "Forbidden", "NotFound", "NoMethod", "NotAcceptable", "Conflict", "Gone", "PreconditionFailed", "UnsupportedMediaType",
   82.27 +    "badrequest", "unauthorized", "forbidden", "notfound", "nomethod", "notacceptable", "conflict", "gone", "preconditionfailed", "unsupportedmediatype",
   82.28 +
   82.29 +    # 500
   82.30 +    "InternalError", 
   82.31 +    "internalerror",
   82.32 +]
   82.33 +
   82.34 +import sys, cgi, Cookie, pprint, urlparse, urllib
   82.35 +from utils import storage, storify, threadeddict, dictadd, intget, safestr
   82.36 +
   82.37 +config = storage()
   82.38 +config.__doc__ = """
   82.39 +A configuration object for various aspects of web.py.
   82.40 +
   82.41 +`debug`
   82.42 +   : when True, enables reloading, disabled template caching and sets internalerror to debugerror.
   82.43 +"""
   82.44 +
   82.45 +class HTTPError(Exception):
   82.46 +    def __init__(self, status, headers={}, data=""):
   82.47 +        ctx.status = status
   82.48 +        for k, v in headers.items():
   82.49 +            header(k, v)
   82.50 +        self.data = data
   82.51 +        Exception.__init__(self, status)
   82.52 +        
   82.53 +def _status_code(status, data=None, classname=None, docstring=None):
   82.54 +    if data is None:
   82.55 +        data = status.split(" ", 1)[1]
   82.56 +    classname = status.split(" ", 1)[1].replace(' ', '') # 304 Not Modified -> NotModified    
   82.57 +    docstring = docstring or '`%s` status' % status
   82.58 +
   82.59 +    def __init__(self, data=data, headers={}):
   82.60 +        HTTPError.__init__(self, status, headers, data)
   82.61 +        
   82.62 +    # trick to create class dynamically with dynamic docstring.
   82.63 +    return type(classname, (HTTPError, object), {
   82.64 +        '__doc__': docstring,
   82.65 +        '__init__': __init__
   82.66 +    })
   82.67 +
   82.68 +ok = OK = _status_code("200 OK", data="")
   82.69 +created = Created = _status_code("201 Created")
   82.70 +accepted = Accepted = _status_code("202 Accepted")
   82.71 +
   82.72 +class Redirect(HTTPError):
   82.73 +    """A `301 Moved Permanently` redirect."""
   82.74 +    def __init__(self, url, status='301 Moved Permanently', absolute=False):
   82.75 +        """
   82.76 +        Returns a `status` redirect to the new URL. 
   82.77 +        `url` is joined with the base URL so that things like 
   82.78 +        `redirect("about") will work properly.
   82.79 +        """
   82.80 +        newloc = urlparse.urljoin(ctx.path, url)
   82.81 +
   82.82 +        if newloc.startswith('/'):
   82.83 +            if absolute:
   82.84 +                home = ctx.realhome
   82.85 +            else:
   82.86 +                home = ctx.home
   82.87 +            newloc = home + newloc
   82.88 +
   82.89 +        headers = {
   82.90 +            'Content-Type': 'text/html',
   82.91 +            'Location': newloc
   82.92 +        }
   82.93 +        HTTPError.__init__(self, status, headers, "")
   82.94 +
   82.95 +redirect = Redirect
   82.96 +
   82.97 +class Found(Redirect):
   82.98 +    """A `302 Found` redirect."""
   82.99 +    def __init__(self, url, absolute=False):
  82.100 +        Redirect.__init__(self, url, '302 Found', absolute=absolute)
  82.101 +
  82.102 +found = Found
  82.103 +
  82.104 +class SeeOther(Redirect):
  82.105 +    """A `303 See Other` redirect."""
  82.106 +    def __init__(self, url, absolute=False):
  82.107 +        Redirect.__init__(self, url, '303 See Other', absolute=absolute)
  82.108 +    
  82.109 +seeother = SeeOther
  82.110 +
  82.111 +class NotModified(HTTPError):
  82.112 +    """A `304 Not Modified` status."""
  82.113 +    def __init__(self):
  82.114 +        HTTPError.__init__(self, "304 Not Modified")
  82.115 +
  82.116 +notmodified = NotModified
  82.117 +
  82.118 +class TempRedirect(Redirect):
  82.119 +    """A `307 Temporary Redirect` redirect."""
  82.120 +    def __init__(self, url, absolute=False):
  82.121 +        Redirect.__init__(self, url, '307 Temporary Redirect', absolute=absolute)
  82.122 +
  82.123 +tempredirect = TempRedirect
  82.124 +
  82.125 +class BadRequest(HTTPError):
  82.126 +    """`400 Bad Request` error."""
  82.127 +    message = "bad request"
  82.128 +    def __init__(self, message=None):
  82.129 +        status = "400 Bad Request"
  82.130 +        headers = {'Content-Type': 'text/html'}
  82.131 +        HTTPError.__init__(self, status, headers, message or self.message)
  82.132 +
  82.133 +badrequest = BadRequest
  82.134 +
  82.135 +class Unauthorized(HTTPError):
  82.136 +    """`401 Unauthorized` error."""
  82.137 +    message = "unauthorized"
  82.138 +    def __init__(self):
  82.139 +        status = "401 Unauthorized"
  82.140 +        headers = {'Content-Type': 'text/html'}
  82.141 +        HTTPError.__init__(self, status, headers, self.message)
  82.142 +
  82.143 +unauthorized = Unauthorized
  82.144 +
  82.145 +class Forbidden(HTTPError):
  82.146 +    """`403 Forbidden` error."""
  82.147 +    message = "forbidden"
  82.148 +    def __init__(self):
  82.149 +        status = "403 Forbidden"
  82.150 +        headers = {'Content-Type': 'text/html'}
  82.151 +        HTTPError.__init__(self, status, headers, self.message)
  82.152 +
  82.153 +forbidden = Forbidden
  82.154 +
  82.155 +class _NotFound(HTTPError):
  82.156 +    """`404 Not Found` error."""
  82.157 +    message = "not found"
  82.158 +    def __init__(self, message=None):
  82.159 +        status = '404 Not Found'
  82.160 +        headers = {'Content-Type': 'text/html'}
  82.161 +        HTTPError.__init__(self, status, headers, message or self.message)
  82.162 +
  82.163 +def NotFound(message=None):
  82.164 +    """Returns HTTPError with '404 Not Found' error from the active application.
  82.165 +    """
  82.166 +    if message:
  82.167 +        return _NotFound(message)
  82.168 +    elif ctx.get('app_stack'):
  82.169 +        return ctx.app_stack[-1].notfound()
  82.170 +    else:
  82.171 +        return _NotFound()
  82.172 +
  82.173 +notfound = NotFound
  82.174 +
  82.175 +class NoMethod(HTTPError):
  82.176 +    """A `405 Method Not Allowed` error."""
  82.177 +    def __init__(self, cls=None):
  82.178 +        status = '405 Method Not Allowed'
  82.179 +        headers = {}
  82.180 +        headers['Content-Type'] = 'text/html'
  82.181 +        
  82.182 +        methods = ['GET', 'HEAD', 'POST', 'PUT', 'DELETE']
  82.183 +        if cls:
  82.184 +            methods = [method for method in methods if hasattr(cls, method)]
  82.185 +
  82.186 +        headers['Allow'] = ', '.join(methods)
  82.187 +        data = None
  82.188 +        HTTPError.__init__(self, status, headers, data)
  82.189 +        
  82.190 +nomethod = NoMethod
  82.191 +
  82.192 +class NotAcceptable(HTTPError):
  82.193 +    """`406 Not Acceptable` error."""
  82.194 +    message = "not acceptable"
  82.195 +    def __init__(self):
  82.196 +        status = "406 Not Acceptable"
  82.197 +        headers = {'Content-Type': 'text/html'}
  82.198 +        HTTPError.__init__(self, status, headers, self.message)
  82.199 +
  82.200 +notacceptable = NotAcceptable
  82.201 +
  82.202 +class Conflict(HTTPError):
  82.203 +    """`409 Conflict` error."""
  82.204 +    message = "conflict"
  82.205 +    def __init__(self):
  82.206 +        status = "409 Conflict"
  82.207 +        headers = {'Content-Type': 'text/html'}
  82.208 +        HTTPError.__init__(self, status, headers, self.message)
  82.209 +
  82.210 +conflict = Conflict
  82.211 +
  82.212 +class Gone(HTTPError):
  82.213 +    """`410 Gone` error."""
  82.214 +    message = "gone"
  82.215 +    def __init__(self):
  82.216 +        status = '410 Gone'
  82.217 +        headers = {'Content-Type': 'text/html'}
  82.218 +        HTTPError.__init__(self, status, headers, self.message)
  82.219 +
  82.220 +gone = Gone
  82.221 +
  82.222 +class PreconditionFailed(HTTPError):
  82.223 +    """`412 Precondition Failed` error."""
  82.224 +    message = "precondition failed"
  82.225 +    def __init__(self):
  82.226 +        status = "412 Precondition Failed"
  82.227 +        headers = {'Content-Type': 'text/html'}
  82.228 +        HTTPError.__init__(self, status, headers, self.message)
  82.229 +
  82.230 +preconditionfailed = PreconditionFailed
  82.231 +
  82.232 +class UnsupportedMediaType(HTTPError):
  82.233 +    """`415 Unsupported Media Type` error."""
  82.234 +    message = "unsupported media type"
  82.235 +    def __init__(self):
  82.236 +        status = "415 Unsupported Media Type"
  82.237 +        headers = {'Content-Type': 'text/html'}
  82.238 +        HTTPError.__init__(self, status, headers, self.message)
  82.239 +
  82.240 +unsupportedmediatype = UnsupportedMediaType
  82.241 +
  82.242 +class _InternalError(HTTPError):
  82.243 +    """500 Internal Server Error`."""
  82.244 +    message = "internal server error"
  82.245 +    
  82.246 +    def __init__(self, message=None):
  82.247 +        status = '500 Internal Server Error'
  82.248 +        headers = {'Content-Type': 'text/html'}
  82.249 +        HTTPError.__init__(self, status, headers, message or self.message)
  82.250 +
  82.251 +def InternalError(message=None):
  82.252 +    """Returns HTTPError with '500 internal error' error from the active application.
  82.253 +    """
  82.254 +    if message:
  82.255 +        return _InternalError(message)
  82.256 +    elif ctx.get('app_stack'):
  82.257 +        return ctx.app_stack[-1].internalerror()
  82.258 +    else:
  82.259 +        return _InternalError()
  82.260 +
  82.261 +internalerror = InternalError
  82.262 +
  82.263 +def header(hdr, value, unique=False):
  82.264 +    """
  82.265 +    Adds the header `hdr: value` with the response.
  82.266 +    
  82.267 +    If `unique` is True and a header with that name already exists,
  82.268 +    it doesn't add a new one. 
  82.269 +    """
  82.270 +    hdr, value = safestr(hdr), safestr(value)
  82.271 +    # protection against HTTP response splitting attack
  82.272 +    if '\n' in hdr or '\r' in hdr or '\n' in value or '\r' in value:
  82.273 +        raise ValueError, 'invalid characters in header'
  82.274 +        
  82.275 +    if unique is True:
  82.276 +        for h, v in ctx.headers:
  82.277 +            if h.lower() == hdr.lower(): return
  82.278 +    
  82.279 +    ctx.headers.append((hdr, value))
  82.280 +    
  82.281 +def rawinput(method=None):
  82.282 +    """Returns storage object with GET or POST arguments.
  82.283 +    """
  82.284 +    method = method or "both"
  82.285 +    from cStringIO import StringIO
  82.286 +
  82.287 +    def dictify(fs): 
  82.288 +        # hack to make web.input work with enctype='text/plain.
  82.289 +        if fs.list is None:
  82.290 +            fs.list = [] 
  82.291 +
  82.292 +        return dict([(k, fs[k]) for k in fs.keys()])
  82.293 +    
  82.294 +    e = ctx.env.copy()
  82.295 +    a = b = {}
  82.296 +    
  82.297 +    if method.lower() in ['both', 'post', 'put']:
  82.298 +        if e['REQUEST_METHOD'] in ['POST', 'PUT']:
  82.299 +            if e.get('CONTENT_TYPE', '').lower().startswith('multipart/'):
  82.300 +                # since wsgi.input is directly passed to cgi.FieldStorage, 
  82.301 +                # it can not be called multiple times. Saving the FieldStorage
  82.302 +                # object in ctx to allow calling web.input multiple times.
  82.303 +                a = ctx.get('_fieldstorage')
  82.304 +                if not a:
  82.305 +                    fp = e['wsgi.input']
  82.306 +                    a = cgi.FieldStorage(fp=fp, environ=e, keep_blank_values=1)
  82.307 +                    ctx._fieldstorage = a
  82.308 +            else:
  82.309 +                fp = StringIO(data())
  82.310 +                a = cgi.FieldStorage(fp=fp, environ=e, keep_blank_values=1)
  82.311 +            a = dictify(a)
  82.312 +
  82.313 +    if method.lower() in ['both', 'get']:
  82.314 +        e['REQUEST_METHOD'] = 'GET'
  82.315 +        b = dictify(cgi.FieldStorage(environ=e, keep_blank_values=1))
  82.316 +
  82.317 +    def process_fieldstorage(fs):
  82.318 +        if isinstance(fs, list):
  82.319 +            return [process_fieldstorage(x) for x in fs]
  82.320 +        elif fs.filename is None:
  82.321 +            return fs.value
  82.322 +        else:
  82.323 +            return fs
  82.324 +
  82.325 +    return storage([(k, process_fieldstorage(v)) for k, v in dictadd(b, a).items()])
  82.326 +
  82.327 +def input(*requireds, **defaults):
  82.328 +    """
  82.329 +    Returns a `storage` object with the GET and POST arguments. 
  82.330 +    See `storify` for how `requireds` and `defaults` work.
  82.331 +    """
  82.332 +    _method = defaults.pop('_method', 'both')
  82.333 +    out = rawinput(_method)
  82.334 +    try:
  82.335 +        defaults.setdefault('_unicode', True) # force unicode conversion by default.
  82.336 +        return storify(out, *requireds, **defaults)
  82.337 +    except KeyError:
  82.338 +        raise badrequest()
  82.339 +
  82.340 +def data():
  82.341 +    """Returns the data sent with the request."""
  82.342 +    if 'data' not in ctx:
  82.343 +        cl = intget(ctx.env.get('CONTENT_LENGTH'), 0)
  82.344 +        ctx.data = ctx.env['wsgi.input'].read(cl)
  82.345 +    return ctx.data
  82.346 +
  82.347 +def setcookie(name, value, expires='', domain=None,
  82.348 +              secure=False, httponly=False, path=None):
  82.349 +    """Sets a cookie."""
  82.350 +    morsel = Cookie.Morsel()
  82.351 +    name, value = safestr(name), safestr(value)
  82.352 +    morsel.set(name, value, urllib.quote(value))
  82.353 +    if expires < 0:
  82.354 +        expires = -1000000000
  82.355 +    morsel['expires'] = expires
  82.356 +    morsel['path'] = path or ctx.homepath+'/'
  82.357 +    if domain:
  82.358 +        morsel['domain'] = domain
  82.359 +    if secure:
  82.360 +        morsel['secure'] = secure
  82.361 +    value = morsel.OutputString()
  82.362 +    if httponly:
  82.363 +        value += '; httponly'
  82.364 +    header('Set-Cookie', value)
  82.365 +        
  82.366 +def decode_cookie(value):
  82.367 +    r"""Safely decodes a cookie value to unicode. 
  82.368 +    
  82.369 +    Tries us-ascii, utf-8 and io8859 encodings, in that order.
  82.370 +
  82.371 +    >>> decode_cookie('')
  82.372 +    u''
  82.373 +    >>> decode_cookie('asdf')
  82.374 +    u'asdf'
  82.375 +    >>> decode_cookie('foo \xC3\xA9 bar')
  82.376 +    u'foo \xe9 bar'
  82.377 +    >>> decode_cookie('foo \xE9 bar')
  82.378 +    u'foo \xe9 bar'
  82.379 +    """
  82.380 +    try:
  82.381 +        # First try plain ASCII encoding
  82.382 +        return unicode(value, 'us-ascii')
  82.383 +    except UnicodeError:
  82.384 +        # Then try UTF-8, and if that fails, ISO8859
  82.385 +        try:
  82.386 +            return unicode(value, 'utf-8')
  82.387 +        except UnicodeError:
  82.388 +            return unicode(value, 'iso8859', 'ignore')
  82.389 +
  82.390 +def parse_cookies(http_cookie):
  82.391 +    r"""Parse a HTTP_COOKIE header and return dict of cookie names and decoded values.
  82.392 +        
  82.393 +    >>> sorted(parse_cookies('').items())
  82.394 +    []
  82.395 +    >>> sorted(parse_cookies('a=1').items())
  82.396 +    [('a', '1')]
  82.397 +    >>> sorted(parse_cookies('a=1%202').items())
  82.398 +    [('a', '1 2')]
  82.399 +    >>> sorted(parse_cookies('a=Z%C3%A9Z').items())
  82.400 +    [('a', 'Z\xc3\xa9Z')]
  82.401 +    >>> sorted(parse_cookies('a=1; b=2; c=3').items())
  82.402 +    [('a', '1'), ('b', '2'), ('c', '3')]
  82.403 +    >>> sorted(parse_cookies('a=1; b=w("x")|y=z; c=3').items())
  82.404 +    [('a', '1'), ('b', 'w('), ('c', '3')]
  82.405 +    >>> sorted(parse_cookies('a=1; b=w(%22x%22)|y=z; c=3').items())
  82.406 +    [('a', '1'), ('b', 'w("x")|y=z'), ('c', '3')]
  82.407 +
  82.408 +    >>> sorted(parse_cookies('keebler=E=mc2').items())
  82.409 +    [('keebler', 'E=mc2')]
  82.410 +    >>> sorted(parse_cookies(r'keebler="E=mc2; L=\"Loves\"; fudge=\012;"').items())
  82.411 +    [('keebler', 'E=mc2; L="Loves"; fudge=\n;')]
  82.412 +    """
  82.413 +    #print "parse_cookies"
  82.414 +    if '"' in http_cookie:
  82.415 +        # HTTP_COOKIE has quotes in it, use slow but correct cookie parsing
  82.416 +        cookie = Cookie.SimpleCookie()
  82.417 +        try:
  82.418 +            cookie.load(http_cookie)
  82.419 +        except Cookie.CookieError:
  82.420 +            # If HTTP_COOKIE header is malformed, try at least to load the cookies we can by
  82.421 +            # first splitting on ';' and loading each attr=value pair separately
  82.422 +            cookie = Cookie.SimpleCookie()
  82.423 +            for attr_value in http_cookie.split(';'):
  82.424 +                try:
  82.425 +                    cookie.load(attr_value)
  82.426 +                except Cookie.CookieError:
  82.427 +                    pass
  82.428 +        cookies = dict((k, urllib.unquote(v.value)) for k, v in cookie.iteritems())
  82.429 +    else:
  82.430 +        # HTTP_COOKIE doesn't have quotes, use fast cookie parsing
  82.431 +        cookies = {}
  82.432 +        for key_value in http_cookie.split(';'):
  82.433 +            key_value = key_value.split('=', 1)
  82.434 +            if len(key_value) == 2:
  82.435 +                key, value = key_value
  82.436 +                cookies[key.strip()] = urllib.unquote(value.strip())
  82.437 +    return cookies
  82.438 +
  82.439 +def cookies(*requireds, **defaults):
  82.440 +    r"""Returns a `storage` object with all the request cookies in it.
  82.441 +    
  82.442 +    See `storify` for how `requireds` and `defaults` work.
  82.443 +
  82.444 +    This is forgiving on bad HTTP_COOKIE input, it tries to parse at least
  82.445 +    the cookies it can.
  82.446 +    
  82.447 +    The values are converted to unicode if _unicode=True is passed.
  82.448 +    """
  82.449 +    # If _unicode=True is specified, use decode_cookie to convert cookie value to unicode 
  82.450 +    if defaults.get("_unicode") is True:
  82.451 +        defaults['_unicode'] = decode_cookie
  82.452 +        
  82.453 +    # parse cookie string and cache the result for next time.
  82.454 +    if '_parsed_cookies' not in ctx:
  82.455 +        http_cookie = ctx.env.get("HTTP_COOKIE", "")
  82.456 +        ctx._parsed_cookies = parse_cookies(http_cookie)
  82.457 +
  82.458 +    try:
  82.459 +        return storify(ctx._parsed_cookies, *requireds, **defaults)
  82.460 +    except KeyError:
  82.461 +        badrequest()
  82.462 +        raise StopIteration
  82.463 +
  82.464 +def debug(*args):
  82.465 +    """
  82.466 +    Prints a prettyprinted version of `args` to stderr.
  82.467 +    """
  82.468 +    try: 
  82.469 +        out = ctx.environ['wsgi.errors']
  82.470 +    except: 
  82.471 +        out = sys.stderr
  82.472 +    for arg in args:
  82.473 +        print >> out, pprint.pformat(arg)
  82.474 +    return ''
  82.475 +
  82.476 +def _debugwrite(x):
  82.477 +    try: 
  82.478 +        out = ctx.environ['wsgi.errors']
  82.479 +    except: 
  82.480 +        out = sys.stderr
  82.481 +    out.write(x)
  82.482 +debug.write = _debugwrite
  82.483 +
  82.484 +ctx = context = threadeddict()
  82.485 +
  82.486 +ctx.__doc__ = """
  82.487 +A `storage` object containing various information about the request:
  82.488 +  
  82.489 +`environ` (aka `env`)
  82.490 +   : A dictionary containing the standard WSGI environment variables.
  82.491 +
  82.492 +`host`
  82.493 +   : The domain (`Host` header) requested by the user.
  82.494 +
  82.495 +`home`
  82.496 +   : The base path for the application.
  82.497 +
  82.498 +`ip`
  82.499 +   : The IP address of the requester.
  82.500 +
  82.501 +`method`
  82.502 +   : The HTTP method used.
  82.503 +
  82.504 +`path`
  82.505 +   : The path request.
  82.506 +   
  82.507 +`query`
  82.508 +   : If there are no query arguments, the empty string. Otherwise, a `?` followed
  82.509 +     by the query string.
  82.510 +
  82.511 +`fullpath`
  82.512 +   : The full path requested, including query arguments (`== path + query`).
  82.513 +
  82.514 +### Response Data
  82.515 +
  82.516 +`status` (default: "200 OK")
  82.517 +   : The status code to be used in the response.
  82.518 +
  82.519 +`headers`
  82.520 +   : A list of 2-tuples to be used in the response.
  82.521 +
  82.522 +`output`
  82.523 +   : A string to be used as the response.
  82.524 +"""
  82.525 +
  82.526 +if __name__ == "__main__":
  82.527 +    import doctest
  82.528 +    doctest.testmod()
  82.529 \ No newline at end of file
    83.1 Binary file OpenSecurity/install/web.py-0.37/web/webapi.pyc has changed
    84.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    84.2 +++ b/OpenSecurity/install/web.py-0.37/web/webopenid.py	Mon Dec 02 14:02:05 2013 +0100
    84.3 @@ -0,0 +1,115 @@
    84.4 +"""openid.py: an openid library for web.py
    84.5 +
    84.6 +Notes:
    84.7 +
    84.8 + - This will create a file called .openid_secret_key in the 
    84.9 +   current directory with your secret key in it. If someone 
   84.10 +   has access to this file they can log in as any user. And 
   84.11 +   if the app can't find this file for any reason (e.g. you 
   84.12 +   moved the app somewhere else) then each currently logged 
   84.13 +   in user will get logged out.
   84.14 +
   84.15 + - State must be maintained through the entire auth process 
   84.16 +   -- this means that if you have multiple web.py processes 
   84.17 +   serving one set of URLs or if you restart your app often 
   84.18 +   then log ins will fail. You have to replace sessions and 
   84.19 +   store for things to work.
   84.20 +
   84.21 + - We set cookies starting with "openid_".
   84.22 +
   84.23 +"""
   84.24 +
   84.25 +import os
   84.26 +import random
   84.27 +import hmac
   84.28 +import __init__ as web
   84.29 +import openid.consumer.consumer
   84.30 +import openid.store.memstore
   84.31 +
   84.32 +sessions = {}
   84.33 +store = openid.store.memstore.MemoryStore()
   84.34 +
   84.35 +def _secret():
   84.36 +    try:
   84.37 +        secret = file('.openid_secret_key').read()
   84.38 +    except IOError:
   84.39 +        # file doesn't exist
   84.40 +        secret = os.urandom(20)
   84.41 +        file('.openid_secret_key', 'w').write(secret)
   84.42 +    return secret
   84.43 +
   84.44 +def _hmac(identity_url):
   84.45 +    return hmac.new(_secret(), identity_url).hexdigest()
   84.46 +
   84.47 +def _random_session():
   84.48 +    n = random.random()
   84.49 +    while n in sessions:
   84.50 +        n = random.random()
   84.51 +    n = str(n)
   84.52 +    return n
   84.53 +
   84.54 +def status():
   84.55 +    oid_hash = web.cookies().get('openid_identity_hash', '').split(',', 1)
   84.56 +    if len(oid_hash) > 1:
   84.57 +        oid_hash, identity_url = oid_hash
   84.58 +        if oid_hash == _hmac(identity_url):
   84.59 +            return identity_url
   84.60 +    return None
   84.61 +
   84.62 +def form(openid_loc):
   84.63 +    oid = status()
   84.64 +    if oid:
   84.65 +        return '''
   84.66 +        <form method="post" action="%s">
   84.67 +          <img src="http://openid.net/login-bg.gif" alt="OpenID" />
   84.68 +          <strong>%s</strong>
   84.69 +          <input type="hidden" name="action" value="logout" />
   84.70 +          <input type="hidden" name="return_to" value="%s" />
   84.71 +          <button type="submit">log out</button>
   84.72 +        </form>''' % (openid_loc, oid, web.ctx.fullpath)
   84.73 +    else:
   84.74 +        return '''
   84.75 +        <form method="post" action="%s">
   84.76 +          <input type="text" name="openid" value="" 
   84.77 +            style="background: url(http://openid.net/login-bg.gif) no-repeat; padding-left: 18px; background-position: 0 50%%;" />
   84.78 +          <input type="hidden" name="return_to" value="%s" />
   84.79 +          <button type="submit">log in</button>
   84.80 +        </form>''' % (openid_loc, web.ctx.fullpath)
   84.81 +
   84.82 +def logout():
   84.83 +    web.setcookie('openid_identity_hash', '', expires=-1)
   84.84 +
   84.85 +class host:
   84.86 +    def POST(self):
   84.87 +        # unlike the usual scheme of things, the POST is actually called
   84.88 +        # first here
   84.89 +        i = web.input(return_to='/')
   84.90 +        if i.get('action') == 'logout':
   84.91 +            logout()
   84.92 +            return web.redirect(i.return_to)
   84.93 +
   84.94 +        i = web.input('openid', return_to='/')
   84.95 +
   84.96 +        n = _random_session()
   84.97 +        sessions[n] = {'webpy_return_to': i.return_to}
   84.98 +        
   84.99 +        c = openid.consumer.consumer.Consumer(sessions[n], store)
  84.100 +        a = c.begin(i.openid)
  84.101 +        f = a.redirectURL(web.ctx.home, web.ctx.home + web.ctx.fullpath)
  84.102 +
  84.103 +        web.setcookie('openid_session_id', n)
  84.104 +        return web.redirect(f)
  84.105 +
  84.106 +    def GET(self):
  84.107 +        n = web.cookies('openid_session_id').openid_session_id
  84.108 +        web.setcookie('openid_session_id', '', expires=-1)
  84.109 +        return_to = sessions[n]['webpy_return_to']
  84.110 +
  84.111 +        c = openid.consumer.consumer.Consumer(sessions[n], store)
  84.112 +        a = c.complete(web.input(), web.ctx.home + web.ctx.fullpath)
  84.113 +
  84.114 +        if a.status.lower() == 'success':
  84.115 +            web.setcookie('openid_identity_hash', _hmac(a.identity_url) + ',' + a.identity_url)
  84.116 +
  84.117 +        del sessions[n]
  84.118 +        return web.redirect(return_to)
    85.1 Binary file OpenSecurity/install/web.py-0.37/web/webopenid.pyc has changed
    86.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    86.2 +++ b/OpenSecurity/install/web.py-0.37/web/wsgi.py	Mon Dec 02 14:02:05 2013 +0100
    86.3 @@ -0,0 +1,70 @@
    86.4 +"""
    86.5 +WSGI Utilities
    86.6 +(from web.py)
    86.7 +"""
    86.8 +
    86.9 +import os, sys
   86.10 +
   86.11 +import http
   86.12 +import webapi as web
   86.13 +from utils import listget
   86.14 +from net import validaddr, validip
   86.15 +import httpserver
   86.16 +    
   86.17 +def runfcgi(func, addr=('localhost', 8000)):
   86.18 +    """Runs a WSGI function as a FastCGI server."""
   86.19 +    import flup.server.fcgi as flups
   86.20 +    return flups.WSGIServer(func, multiplexed=True, bindAddress=addr, debug=False).run()
   86.21 +
   86.22 +def runscgi(func, addr=('localhost', 4000)):
   86.23 +    """Runs a WSGI function as an SCGI server."""
   86.24 +    import flup.server.scgi as flups
   86.25 +    return flups.WSGIServer(func, bindAddress=addr, debug=False).run()
   86.26 +
   86.27 +def runwsgi(func):
   86.28 +    """
   86.29 +    Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server,
   86.30 +    as appropriate based on context and `sys.argv`.
   86.31 +    """
   86.32 +    
   86.33 +    if os.environ.has_key('SERVER_SOFTWARE'): # cgi
   86.34 +        os.environ['FCGI_FORCE_CGI'] = 'Y'
   86.35 +
   86.36 +    if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi
   86.37 +      or os.environ.has_key('SERVER_SOFTWARE')):
   86.38 +        return runfcgi(func, None)
   86.39 +    
   86.40 +    if 'fcgi' in sys.argv or 'fastcgi' in sys.argv:
   86.41 +        args = sys.argv[1:]
   86.42 +        if 'fastcgi' in args: args.remove('fastcgi')
   86.43 +        elif 'fcgi' in args: args.remove('fcgi')
   86.44 +        if args:
   86.45 +            return runfcgi(func, validaddr(args[0]))
   86.46 +        else:
   86.47 +            return runfcgi(func, None)
   86.48 +    
   86.49 +    if 'scgi' in sys.argv:
   86.50 +        args = sys.argv[1:]
   86.51 +        args.remove('scgi')
   86.52 +        if args:
   86.53 +            return runscgi(func, validaddr(args[0]))
   86.54 +        else:
   86.55 +            return runscgi(func)
   86.56 +    
   86.57 +    return httpserver.runsimple(func, validip(listget(sys.argv, 1, '')))
   86.58 +    
   86.59 +def _is_dev_mode():
   86.60 +    # Some embedded python interpreters won't have sys.arv
   86.61 +    # For details, see https://github.com/webpy/webpy/issues/87
   86.62 +    argv = getattr(sys, "argv", [])
   86.63 +
   86.64 +    # quick hack to check if the program is running in dev mode.
   86.65 +    if os.environ.has_key('SERVER_SOFTWARE') \
   86.66 +        or os.environ.has_key('PHP_FCGI_CHILDREN') \
   86.67 +        or 'fcgi' in argv or 'fastcgi' in argv \
   86.68 +        or 'mod_wsgi' in argv:
   86.69 +            return False
   86.70 +    return True
   86.71 +
   86.72 +# When running the builtin-server, enable debug mode if not already set.
   86.73 +web.config.setdefault('debug', _is_dev_mode())
    87.1 Binary file OpenSecurity/install/web.py-0.37/web/wsgi.pyc has changed
    88.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    88.2 +++ b/OpenSecurity/install/web.py-0.37/web/wsgiserver/__init__.py	Mon Dec 02 14:02:05 2013 +0100
    88.3 @@ -0,0 +1,2219 @@
    88.4 +"""A high-speed, production ready, thread pooled, generic HTTP server.
    88.5 +
    88.6 +Simplest example on how to use this module directly
    88.7 +(without using CherryPy's application machinery)::
    88.8 +
    88.9 +    from cherrypy import wsgiserver
   88.10 +    
   88.11 +    def my_crazy_app(environ, start_response):
   88.12 +        status = '200 OK'
   88.13 +        response_headers = [('Content-type','text/plain')]
   88.14 +        start_response(status, response_headers)
   88.15 +        return ['Hello world!']
   88.16 +    
   88.17 +    server = wsgiserver.CherryPyWSGIServer(
   88.18 +                ('0.0.0.0', 8070), my_crazy_app,
   88.19 +                server_name='www.cherrypy.example')
   88.20 +    server.start()
   88.21 +    
   88.22 +The CherryPy WSGI server can serve as many WSGI applications 
   88.23 +as you want in one instance by using a WSGIPathInfoDispatcher::
   88.24 +    
   88.25 +    d = WSGIPathInfoDispatcher({'/': my_crazy_app, '/blog': my_blog_app})
   88.26 +    server = wsgiserver.CherryPyWSGIServer(('0.0.0.0', 80), d)
   88.27 +    
   88.28 +Want SSL support? Just set server.ssl_adapter to an SSLAdapter instance.
   88.29 +
   88.30 +This won't call the CherryPy engine (application side) at all, only the
   88.31 +HTTP server, which is independent from the rest of CherryPy. Don't
   88.32 +let the name "CherryPyWSGIServer" throw you; the name merely reflects
   88.33 +its origin, not its coupling.
   88.34 +
   88.35 +For those of you wanting to understand internals of this module, here's the
   88.36 +basic call flow. The server's listening thread runs a very tight loop,
   88.37 +sticking incoming connections onto a Queue::
   88.38 +
   88.39 +    server = CherryPyWSGIServer(...)
   88.40 +    server.start()
   88.41 +    while True:
   88.42 +        tick()
   88.43 +        # This blocks until a request comes in:
   88.44 +        child = socket.accept()
   88.45 +        conn = HTTPConnection(child, ...)
   88.46 +        server.requests.put(conn)
   88.47 +
   88.48 +Worker threads are kept in a pool and poll the Queue, popping off and then
   88.49 +handling each connection in turn. Each connection can consist of an arbitrary
   88.50 +number of requests and their responses, so we run a nested loop::
   88.51 +
   88.52 +    while True:
   88.53 +        conn = server.requests.get()
   88.54 +        conn.communicate()
   88.55 +        ->  while True:
   88.56 +                req = HTTPRequest(...)
   88.57 +                req.parse_request()
   88.58 +                ->  # Read the Request-Line, e.g. "GET /page HTTP/1.1"
   88.59 +                    req.rfile.readline()
   88.60 +                    read_headers(req.rfile, req.inheaders)
   88.61 +                req.respond()
   88.62 +                ->  response = app(...)
   88.63 +                    try:
   88.64 +                        for chunk in response:
   88.65 +                            if chunk:
   88.66 +                                req.write(chunk)
   88.67 +                    finally:
   88.68 +                        if hasattr(response, "close"):
   88.69 +                            response.close()
   88.70 +                if req.close_connection:
   88.71 +                    return
   88.72 +"""
   88.73 +
   88.74 +CRLF = '\r\n'
   88.75 +import os
   88.76 +import Queue
   88.77 +import re
   88.78 +quoted_slash = re.compile("(?i)%2F")
   88.79 +import rfc822
   88.80 +import socket
   88.81 +import sys
   88.82 +if 'win' in sys.platform and not hasattr(socket, 'IPPROTO_IPV6'):
   88.83 +    socket.IPPROTO_IPV6 = 41
   88.84 +try:
   88.85 +    import cStringIO as StringIO
   88.86 +except ImportError:
   88.87 +    import StringIO
   88.88 +DEFAULT_BUFFER_SIZE = -1
   88.89 +
   88.90 +_fileobject_uses_str_type = isinstance(socket._fileobject(None)._rbuf, basestring)
   88.91 +
   88.92 +import threading
   88.93 +import time
   88.94 +import traceback
   88.95 +def format_exc(limit=None):
   88.96 +    """Like print_exc() but return a string. Backport for Python 2.3."""
   88.97 +    try:
   88.98 +        etype, value, tb = sys.exc_info()
   88.99 +        return ''.join(traceback.format_exception(etype, value, tb, limit))
  88.100 +    finally:
  88.101 +        etype = value = tb = None
  88.102 +
  88.103 +
  88.104 +from urllib import unquote
  88.105 +from urlparse import urlparse
  88.106 +import warnings
  88.107 +
  88.108 +import errno
  88.109 +
  88.110 +def plat_specific_errors(*errnames):
  88.111 +    """Return error numbers for all errors in errnames on this platform.
  88.112 +    
  88.113 +    The 'errno' module contains different global constants depending on
  88.114 +    the specific platform (OS). This function will return the list of
  88.115 +    numeric values for a given list of potential names.
  88.116 +    """
  88.117 +    errno_names = dir(errno)
  88.118 +    nums = [getattr(errno, k) for k in errnames if k in errno_names]
  88.119 +    # de-dupe the list
  88.120 +    return dict.fromkeys(nums).keys()
  88.121 +
  88.122 +socket_error_eintr = plat_specific_errors("EINTR", "WSAEINTR")
  88.123 +
  88.124 +socket_errors_to_ignore = plat_specific_errors(
  88.125 +    "EPIPE",
  88.126 +    "EBADF", "WSAEBADF",
  88.127 +    "ENOTSOCK", "WSAENOTSOCK",
  88.128 +    "ETIMEDOUT", "WSAETIMEDOUT",
  88.129 +    "ECONNREFUSED", "WSAECONNREFUSED",
  88.130 +    "ECONNRESET", "WSAECONNRESET",
  88.131 +    "ECONNABORTED", "WSAECONNABORTED",
  88.132 +    "ENETRESET", "WSAENETRESET",
  88.133 +    "EHOSTDOWN", "EHOSTUNREACH",
  88.134 +    )
  88.135 +socket_errors_to_ignore.append("timed out")
  88.136 +socket_errors_to_ignore.append("The read operation timed out")
  88.137 +
  88.138 +socket_errors_nonblocking = plat_specific_errors(
  88.139 +    'EAGAIN', 'EWOULDBLOCK', 'WSAEWOULDBLOCK')
  88.140 +
  88.141 +comma_separated_headers = ['Accept', 'Accept-Charset', 'Accept-Encoding',
  88.142 +    'Accept-Language', 'Accept-Ranges', 'Allow', 'Cache-Control',
  88.143 +    'Connection', 'Content-Encoding', 'Content-Language', 'Expect',
  88.144 +    'If-Match', 'If-None-Match', 'Pragma', 'Proxy-Authenticate', 'TE',
  88.145 +    'Trailer', 'Transfer-Encoding', 'Upgrade', 'Vary', 'Via', 'Warning',
  88.146 +    'WWW-Authenticate']
  88.147 +
  88.148 +
  88.149 +import logging
  88.150 +if not hasattr(logging, 'statistics'): logging.statistics = {}
  88.151 +
  88.152 +
  88.153 +def read_headers(rfile, hdict=None):
  88.154 +    """Read headers from the given stream into the given header dict.
  88.155 +    
  88.156 +    If hdict is None, a new header dict is created. Returns the populated
  88.157 +    header dict.
  88.158 +    
  88.159 +    Headers which are repeated are folded together using a comma if their
  88.160 +    specification so dictates.
  88.161 +    
  88.162 +    This function raises ValueError when the read bytes violate the HTTP spec.
  88.163 +    You should probably return "400 Bad Request" if this happens.
  88.164 +    """
  88.165 +    if hdict is None:
  88.166 +        hdict = {}
  88.167 +    
  88.168 +    while True:
  88.169 +        line = rfile.readline()
  88.170 +        if not line:
  88.171 +            # No more data--illegal end of headers
  88.172 +            raise ValueError("Illegal end of headers.")
  88.173 +        
  88.174 +        if line == CRLF:
  88.175 +            # Normal end of headers
  88.176 +            break
  88.177 +        if not line.endswith(CRLF):
  88.178 +            raise ValueError("HTTP requires CRLF terminators")
  88.179 +        
  88.180 +        if line[0] in ' \t':
  88.181 +            # It's a continuation line.
  88.182 +            v = line.strip()
  88.183 +        else:
  88.184 +            try:
  88.185 +                k, v = line.split(":", 1)
  88.186 +            except ValueError:
  88.187 +                raise ValueError("Illegal header line.")
  88.188 +            # TODO: what about TE and WWW-Authenticate?
  88.189 +            k = k.strip().title()
  88.190 +            v = v.strip()
  88.191 +            hname = k
  88.192 +        
  88.193 +        if k in comma_separated_headers:
  88.194 +            existing = hdict.get(hname)
  88.195 +            if existing:
  88.196 +                v = ", ".join((existing, v))
  88.197 +        hdict[hname] = v
  88.198 +    
  88.199 +    return hdict
  88.200 +
  88.201 +
  88.202 +class MaxSizeExceeded(Exception):
  88.203 +    pass
  88.204 +
  88.205 +class SizeCheckWrapper(object):
  88.206 +    """Wraps a file-like object, raising MaxSizeExceeded if too large."""
  88.207 +    
  88.208 +    def __init__(self, rfile, maxlen):
  88.209 +        self.rfile = rfile
  88.210 +        self.maxlen = maxlen
  88.211 +        self.bytes_read = 0
  88.212 +    
  88.213 +    def _check_length(self):
  88.214 +        if self.maxlen and self.bytes_read > self.maxlen:
  88.215 +            raise MaxSizeExceeded()
  88.216 +    
  88.217 +    def read(self, size=None):
  88.218 +        data = self.rfile.read(size)
  88.219 +        self.bytes_read += len(data)
  88.220 +        self._check_length()
  88.221 +        return data
  88.222 +    
  88.223 +    def readline(self, size=None):
  88.224 +        if size is not None:
  88.225 +            data = self.rfile.readline(size)
  88.226 +            self.bytes_read += len(data)
  88.227 +            self._check_length()
  88.228 +            return data
  88.229 +        
  88.230 +        # User didn't specify a size ...
  88.231 +        # We read the line in chunks to make sure it's not a 100MB line !
  88.232 +        res = []
  88.233 +        while True:
  88.234 +            data = self.rfile.readline(256)
  88.235 +            self.bytes_read += len(data)
  88.236 +            self._check_length()
  88.237 +            res.append(data)
  88.238 +            # See http://www.cherrypy.org/ticket/421
  88.239 +            if len(data) < 256 or data[-1:] == "\n":
  88.240 +                return ''.join(res)
  88.241 +    
  88.242 +    def readlines(self, sizehint=0):
  88.243 +        # Shamelessly stolen from StringIO
  88.244 +        total = 0
  88.245 +        lines = []
  88.246 +        line = self.readline()
  88.247 +        while line:
  88.248 +            lines.append(line)
  88.249 +            total += len(line)
  88.250 +            if 0 < sizehint <= total:
  88.251 +                break
  88.252 +            line = self.readline()
  88.253 +        return lines
  88.254 +    
  88.255 +    def close(self):
  88.256 +        self.rfile.close()
  88.257 +    
  88.258 +    def __iter__(self):
  88.259 +        return self
  88.260 +    
  88.261 +    def next(self):
  88.262 +        data = self.rfile.next()
  88.263 +        self.bytes_read += len(data)
  88.264 +        self._check_length()
  88.265 +        return data
  88.266 +
  88.267 +
  88.268 +class KnownLengthRFile(object):
  88.269 +    """Wraps a file-like object, returning an empty string when exhausted."""
  88.270 +    
  88.271 +    def __init__(self, rfile, content_length):
  88.272 +        self.rfile = rfile
  88.273 +        self.remaining = content_length
  88.274 +    
  88.275 +    def read(self, size=None):
  88.276 +        if self.remaining == 0:
  88.277 +            return ''
  88.278 +        if size is None:
  88.279 +            size = self.remaining
  88.280 +        else:
  88.281 +            size = min(size, self.remaining)
  88.282 +        
  88.283 +        data = self.rfile.read(size)
  88.284 +        self.remaining -= len(data)
  88.285 +        return data
  88.286 +    
  88.287 +    def readline(self, size=None):
  88.288 +        if self.remaining == 0:
  88.289 +            return ''
  88.290 +        if size is None:
  88.291 +            size = self.remaining
  88.292 +        else:
  88.293 +            size = min(size, self.remaining)
  88.294 +        
  88.295 +        data = self.rfile.readline(size)
  88.296 +        self.remaining -= len(data)
  88.297 +        return data
  88.298 +    
  88.299 +    def readlines(self, sizehint=0):
  88.300 +        # Shamelessly stolen from StringIO
  88.301 +        total = 0
  88.302 +        lines = []
  88.303 +        line = self.readline(sizehint)
  88.304 +        while line:
  88.305 +            lines.append(line)
  88.306 +            total += len(line)
  88.307 +            if 0 < sizehint <= total:
  88.308 +                break
  88.309 +            line = self.readline(sizehint)
  88.310 +        return lines
  88.311 +    
  88.312 +    def close(self):
  88.313 +        self.rfile.close()
  88.314 +    
  88.315 +    def __iter__(self):
  88.316 +        return self
  88.317 +    
  88.318 +    def __next__(self):
  88.319 +        data = next(self.rfile)
  88.320 +        self.remaining -= len(data)
  88.321 +        return data
  88.322 +
  88.323 +
  88.324 +class ChunkedRFile(object):
  88.325 +    """Wraps a file-like object, returning an empty string when exhausted.
  88.326 +    
  88.327 +    This class is intended to provide a conforming wsgi.input value for
  88.328 +    request entities that have been encoded with the 'chunked' transfer
  88.329 +    encoding.
  88.330 +    """
  88.331 +    
  88.332 +    def __init__(self, rfile, maxlen, bufsize=8192):
  88.333 +        self.rfile = rfile
  88.334 +        self.maxlen = maxlen
  88.335 +        self.bytes_read = 0
  88.336 +        self.buffer = ''
  88.337 +        self.bufsize = bufsize
  88.338 +        self.closed = False
  88.339 +    
  88.340 +    def _fetch(self):
  88.341 +        if self.closed:
  88.342 +            return
  88.343 +        
  88.344 +        line = self.rfile.readline()
  88.345 +        self.bytes_read += len(line)
  88.346 +        
  88.347 +        if self.maxlen and self.bytes_read > self.maxlen:
  88.348 +            raise MaxSizeExceeded("Request Entity Too Large", self.maxlen)
  88.349 +        
  88.350 +        line = line.strip().split(";", 1)
  88.351 +        
  88.352 +        try:
  88.353 +            chunk_size = line.pop(0)
  88.354 +            chunk_size = int(chunk_size, 16)
  88.355 +        except ValueError:
  88.356 +            raise ValueError("Bad chunked transfer size: " + repr(chunk_size))
  88.357 +        
  88.358 +        if chunk_size <= 0:
  88.359 +            self.closed = True
  88.360 +            return
  88.361 +        
  88.362 +##            if line: chunk_extension = line[0]
  88.363 +        
  88.364 +        if self.maxlen and self.bytes_read + chunk_size > self.maxlen:
  88.365 +            raise IOError("Request Entity Too Large")
  88.366 +        
  88.367 +        chunk = self.rfile.read(chunk_size)
  88.368 +        self.bytes_read += len(chunk)
  88.369 +        self.buffer += chunk
  88.370 +        
  88.371 +        crlf = self.rfile.read(2)
  88.372 +        if crlf != CRLF:
  88.373 +            raise ValueError(
  88.374 +                 "Bad chunked transfer coding (expected '\\r\\n', "
  88.375 +                 "got " + repr(crlf) + ")")
  88.376 +    
  88.377 +    def read(self, size=None):
  88.378 +        data = ''
  88.379 +        while True:
  88.380 +            if size and len(data) >= size:
  88.381 +                return data
  88.382 +            
  88.383 +            if not self.buffer:
  88.384 +                self._fetch()
  88.385 +                if not self.buffer:
  88.386 +                    # EOF
  88.387 +                    return data
  88.388 +            
  88.389 +            if size:
  88.390 +                remaining = size - len(data)
  88.391 +                data += self.buffer[:remaining]
  88.392 +                self.buffer = self.buffer[remaining:]
  88.393 +            else:
  88.394 +                data += self.buffer
  88.395 +    
  88.396 +    def readline(self, size=None):
  88.397 +        data = ''
  88.398 +        while True:
  88.399 +            if size and len(data) >= size:
  88.400 +                return data
  88.401 +            
  88.402 +            if not self.buffer:
  88.403 +                self._fetch()
  88.404 +                if not self.buffer:
  88.405 +                    # EOF
  88.406 +                    return data
  88.407 +            
  88.408 +            newline_pos = self.buffer.find('\n')
  88.409 +            if size:
  88.410 +                if newline_pos == -1:
  88.411 +                    remaining = size - len(data)
  88.412 +                    data += self.buffer[:remaining]
  88.413 +                    self.buffer = self.buffer[remaining:]
  88.414 +                else:
  88.415 +                    remaining = min(size - len(data), newline_pos)
  88.416 +                    data += self.buffer[:remaining]
  88.417 +                    self.buffer = self.buffer[remaining:]
  88.418 +            else:
  88.419 +                if newline_pos == -1:
  88.420 +                    data += self.buffer
  88.421 +                else:
  88.422 +                    data += self.buffer[:newline_pos]
  88.423 +                    self.buffer = self.buffer[newline_pos:]
  88.424 +    
  88.425 +    def readlines(self, sizehint=0):
  88.426 +        # Shamelessly stolen from StringIO
  88.427 +        total = 0
  88.428 +        lines = []
  88.429 +        line = self.readline(sizehint)
  88.430 +        while line:
  88.431 +            lines.append(line)
  88.432 +            total += len(line)
  88.433 +            if 0 < sizehint <= total:
  88.434 +                break
  88.435 +            line = self.readline(sizehint)
  88.436 +        return lines
  88.437 +    
  88.438 +    def read_trailer_lines(self):
  88.439 +        if not self.closed:
  88.440 +            raise ValueError(
  88.441 +                "Cannot read trailers until the request body has been read.")
  88.442 +        
  88.443 +        while True:
  88.444 +            line = self.rfile.readline()
  88.445 +            if not line:
  88.446 +                # No more data--illegal end of headers
  88.447 +                raise ValueError("Illegal end of headers.")
  88.448 +            
  88.449 +            self.bytes_read += len(line)
  88.450 +            if self.maxlen and self.bytes_read > self.maxlen:
  88.451 +                raise IOError("Request Entity Too Large")
  88.452 +            
  88.453 +            if line == CRLF:
  88.454 +                # Normal end of headers
  88.455 +                break
  88.456 +            if not line.endswith(CRLF):
  88.457 +                raise ValueError("HTTP requires CRLF terminators")
  88.458 +            
  88.459 +            yield line
  88.460 +    
  88.461 +    def close(self):
  88.462 +        self.rfile.close()
  88.463 +    
  88.464 +    def __iter__(self):
  88.465 +        # Shamelessly stolen from StringIO
  88.466 +        total = 0
  88.467 +        line = self.readline(sizehint)
  88.468 +        while line:
  88.469 +            yield line
  88.470 +            total += len(line)
  88.471 +            if 0 < sizehint <= total:
  88.472 +                break
  88.473 +            line = self.readline(sizehint)
  88.474 +
  88.475 +
  88.476 +class HTTPRequest(object):
  88.477 +    """An HTTP Request (and response).
  88.478 +    
  88.479 +    A single HTTP connection may consist of multiple request/response pairs.
  88.480 +    """
  88.481 +    
  88.482 +    server = None
  88.483 +    """The HTTPServer object which is receiving this request."""
  88.484 +    
  88.485 +    conn = None
  88.486 +    """The HTTPConnection object on which this request connected."""
  88.487 +    
  88.488 +    inheaders = {}
  88.489 +    """A dict of request headers."""
  88.490 +    
  88.491 +    outheaders = []
  88.492 +    """A list of header tuples to write in the response."""
  88.493 +    
  88.494 +    ready = False
  88.495 +    """When True, the request has been parsed and is ready to begin generating
  88.496 +    the response. When False, signals the calling Connection that the response
  88.497 +    should not be generated and the connection should close."""
  88.498 +    
  88.499 +    close_connection = False
  88.500 +    """Signals the calling Connection that the request should close. This does
  88.501 +    not imply an error! The client and/or server may each request that the
  88.502 +    connection be closed."""
  88.503 +    
  88.504 +    chunked_write = False
  88.505 +    """If True, output will be encoded with the "chunked" transfer-coding.
  88.506 +    
  88.507 +    This value is set automatically inside send_headers."""
  88.508 +    
  88.509 +    def __init__(self, server, conn):
  88.510 +        self.server= server
  88.511 +        self.conn = conn
  88.512 +        
  88.513 +        self.ready = False
  88.514 +        self.started_request = False
  88.515 +        self.scheme = "http"
  88.516 +        if self.server.ssl_adapter is not None:
  88.517 +            self.scheme = "https"
  88.518 +        # Use the lowest-common protocol in case read_request_line errors.
  88.519 +        self.response_protocol = 'HTTP/1.0'
  88.520 +        self.inheaders = {}
  88.521 +        
  88.522 +        self.status = ""
  88.523 +        self.outheaders = []
  88.524 +        self.sent_headers = False
  88.525 +        self.close_connection = self.__class__.close_connection
  88.526 +        self.chunked_read = False
  88.527 +        self.chunked_write = self.__class__.chunked_write
  88.528 +    
  88.529 +    def parse_request(self):
  88.530 +        """Parse the next HTTP request start-line and message-headers."""
  88.531 +        self.rfile = SizeCheckWrapper(self.conn.rfile,
  88.532 +                                      self.server.max_request_header_size)
  88.533 +        try:
  88.534 +            self.read_request_line()
  88.535 +        except MaxSizeExceeded:
  88.536 +            self.simple_response("414 Request-URI Too Long",
  88.537 +                "The Request-URI sent with the request exceeds the maximum "
  88.538 +                "allowed bytes.")
  88.539 +            return
  88.540 +        
  88.541 +        try:
  88.542 +            success = self.read_request_headers()
  88.543 +        except MaxSizeExceeded:
  88.544 +            self.simple_response("413 Request Entity Too Large",
  88.545 +                "The headers sent with the request exceed the maximum "
  88.546 +                "allowed bytes.")
  88.547 +            return
  88.548 +        else:
  88.549 +            if not success:
  88.550 +                return
  88.551 +        
  88.552 +        self.ready = True
  88.553 +    
  88.554 +    def read_request_line(self):
  88.555 +        # HTTP/1.1 connections are persistent by default. If a client
  88.556 +        # requests a page, then idles (leaves the connection open),
  88.557 +        # then rfile.readline() will raise socket.error("timed out").
  88.558 +        # Note that it does this based on the value given to settimeout(),
  88.559 +        # and doesn't need the client to request or acknowledge the close
  88.560 +        # (although your TCP stack might suffer for it: cf Apache's history
  88.561 +        # with FIN_WAIT_2).
  88.562 +        request_line = self.rfile.readline()
  88.563 +        
  88.564 +        # Set started_request to True so communicate() knows to send 408
  88.565 +        # from here on out.
  88.566 +        self.started_request = True
  88.567 +        if not request_line:
  88.568 +            # Force self.ready = False so the connection will close.
  88.569 +            self.ready = False
  88.570 +            return
  88.571 +        
  88.572 +        if request_line == CRLF:
  88.573 +            # RFC 2616 sec 4.1: "...if the server is reading the protocol
  88.574 +            # stream at the beginning of a message and receives a CRLF
  88.575 +            # first, it should ignore the CRLF."
  88.576 +            # But only ignore one leading line! else we enable a DoS.
  88.577 +            request_line = self.rfile.readline()
  88.578 +            if not request_line:
  88.579 +                self.ready = False
  88.580 +                return
  88.581 +        
  88.582 +        if not request_line.endswith(CRLF):
  88.583 +            self.simple_response("400 Bad Request", "HTTP requires CRLF terminators")
  88.584 +            return
  88.585 +        
  88.586 +        try:
  88.587 +            method, uri, req_protocol = request_line.strip().split(" ", 2)
  88.588 +            rp = int(req_protocol[5]), int(req_protocol[7])
  88.589 +        except (ValueError, IndexError):
  88.590 +            self.simple_response("400 Bad Request", "Malformed Request-Line")
  88.591 +            return
  88.592 +        
  88.593 +        self.uri = uri
  88.594 +        self.method = method
  88.595 +        
  88.596 +        # uri may be an abs_path (including "http://host.domain.tld");
  88.597 +        scheme, authority, path = self.parse_request_uri(uri)
  88.598 +        if '#' in path:
  88.599 +            self.simple_response("400 Bad Request",
  88.600 +                                 "Illegal #fragment in Request-URI.")
  88.601 +            return
  88.602 +        
  88.603 +        if scheme:
  88.604 +            self.scheme = scheme
  88.605 +        
  88.606 +        qs = ''
  88.607 +        if '?' in path:
  88.608 +            path, qs = path.split('?', 1)
  88.609 +        
  88.610 +        # Unquote the path+params (e.g. "/this%20path" -> "/this path").
  88.611 +        # http://www.w3.org/Protocols/rfc2616/rfc2616-sec5.html#sec5.1.2
  88.612 +        #
  88.613 +        # But note that "...a URI must be separated into its components
  88.614 +        # before the escaped characters within those components can be
  88.615 +        # safely decoded." http://www.ietf.org/rfc/rfc2396.txt, sec 2.4.2
  88.616 +        # Therefore, "/this%2Fpath" becomes "/this%2Fpath", not "/this/path".
  88.617 +        try:
  88.618 +            atoms = [unquote(x) for x in quoted_slash.split(path)]
  88.619 +        except ValueError, ex:
  88.620 +            self.simple_response("400 Bad Request", ex.args[0])
  88.621 +            return
  88.622 +        path = "%2F".join(atoms)
  88.623 +        self.path = path
  88.624 +        
  88.625 +        # Note that, like wsgiref and most other HTTP servers,
  88.626 +        # we "% HEX HEX"-unquote the path but not the query string.
  88.627 +        self.qs = qs
  88.628 +        
  88.629 +        # Compare request and server HTTP protocol versions, in case our
  88.630 +        # server does not support the requested protocol. Limit our output
  88.631 +        # to min(req, server). We want the following output:
  88.632 +        #     request    server     actual written   supported response
  88.633 +        #     protocol   protocol  response protocol    feature set
  88.634 +        # a     1.0        1.0           1.0                1.0
  88.635 +        # b     1.0        1.1           1.1                1.0
  88.636 +        # c     1.1        1.0           1.0                1.0
  88.637 +        # d     1.1        1.1           1.1                1.1
  88.638 +        # Notice that, in (b), the response will be "HTTP/1.1" even though
  88.639 +        # the client only understands 1.0. RFC 2616 10.5.6 says we should
  88.640 +        # only return 505 if the _major_ version is different.
  88.641 +        sp = int(self.server.protocol[5]), int(self.server.protocol[7])
  88.642 +        
  88.643 +        if sp[0] != rp[0]:
  88.644 +            self.simple_response("505 HTTP Version Not Supported")
  88.645 +            return
  88.646 +        self.request_protocol = req_protocol
  88.647 +        self.response_protocol = "HTTP/%s.%s" % min(rp, sp)
  88.648 +    
  88.649 +    def read_request_headers(self):
  88.650 +        """Read self.rfile into self.inheaders. Return success."""
  88.651 +        
  88.652 +        # then all the http headers
  88.653 +        try:
  88.654 +            read_headers(self.rfile, self.inheaders)
  88.655 +        except ValueError, ex:
  88.656 +            self.simple_response("400 Bad Request", ex.args[0])
  88.657 +            return False
  88.658 +        
  88.659 +        mrbs = self.server.max_request_body_size
  88.660 +        if mrbs and int(self.inheaders.get("Content-Length", 0)) > mrbs:
  88.661 +            self.simple_response("413 Request Entity Too Large",
  88.662 +                "The entity sent with the request exceeds the maximum "
  88.663 +                "allowed bytes.")
  88.664 +            return False
  88.665 +        
  88.666 +        # Persistent connection support
  88.667 +        if self.response_protocol == "HTTP/1.1":
  88.668 +            # Both server and client are HTTP/1.1
  88.669 +            if self.inheaders.get("Connection", "") == "close":
  88.670 +                self.close_connection = True
  88.671 +        else:
  88.672 +            # Either the server or client (or both) are HTTP/1.0
  88.673 +            if self.inheaders.get("Connection", "") != "Keep-Alive":
  88.674 +                self.close_connection = True
  88.675 +        
  88.676 +        # Transfer-Encoding support
  88.677 +        te = None
  88.678 +        if self.response_protocol == "HTTP/1.1":
  88.679 +            te = self.inheaders.get("Transfer-Encoding")
  88.680 +            if te:
  88.681 +                te = [x.strip().lower() for x in te.split(",") if x.strip()]
  88.682 +        
  88.683 +        self.chunked_read = False
  88.684 +        
  88.685 +        if te:
  88.686 +            for enc in te:
  88.687 +                if enc == "chunked":
  88.688 +                    self.chunked_read = True
  88.689 +                else:
  88.690 +                    # Note that, even if we see "chunked", we must reject
  88.691 +                    # if there is an extension we don't recognize.
  88.692 +                    self.simple_response("501 Unimplemented")
  88.693 +                    self.close_connection = True
  88.694 +                    return False
  88.695 +        
  88.696 +        # From PEP 333:
  88.697 +        # "Servers and gateways that implement HTTP 1.1 must provide
  88.698 +        # transparent support for HTTP 1.1's "expect/continue" mechanism.
  88.699 +        # This may be done in any of several ways:
  88.700 +        #   1. Respond to requests containing an Expect: 100-continue request
  88.701 +        #      with an immediate "100 Continue" response, and proceed normally.
  88.702 +        #   2. Proceed with the request normally, but provide the application
  88.703 +        #      with a wsgi.input stream that will send the "100 Continue"
  88.704 +        #      response if/when the application first attempts to read from
  88.705 +        #      the input stream. The read request must then remain blocked
  88.706 +        #      until the client responds.
  88.707 +        #   3. Wait until the client decides that the server does not support
  88.708 +        #      expect/continue, and sends the request body on its own.
  88.709 +        #      (This is suboptimal, and is not recommended.)
  88.710 +        #
  88.711 +        # We used to do 3, but are now doing 1. Maybe we'll do 2 someday,
  88.712 +        # but it seems like it would be a big slowdown for such a rare case.
  88.713 +        if self.inheaders.get("Expect", "") == "100-continue":
  88.714 +            # Don't use simple_response here, because it emits headers
  88.715 +            # we don't want. See http://www.cherrypy.org/ticket/951
  88.716 +            msg = self.server.protocol + " 100 Continue\r\n\r\n"
  88.717 +            try:
  88.718 +                self.conn.wfile.sendall(msg)
  88.719 +            except socket.error, x:
  88.720 +                if x.args[0] not in socket_errors_to_ignore:
  88.721 +                    raise
  88.722 +        return True
  88.723 +    
  88.724 +    def parse_request_uri(self, uri):
  88.725 +        """Parse a Request-URI into (scheme, authority, path).
  88.726 +        
  88.727 +        Note that Request-URI's must be one of::
  88.728 +            
  88.729 +            Request-URI    = "*" | absoluteURI | abs_path | authority
  88.730 +        
  88.731 +        Therefore, a Request-URI which starts with a double forward-slash
  88.732 +        cannot be a "net_path"::
  88.733 +        
  88.734 +            net_path      = "//" authority [ abs_path ]
  88.735 +        
  88.736 +        Instead, it must be interpreted as an "abs_path" with an empty first
  88.737 +        path segment::
  88.738 +        
  88.739 +            abs_path      = "/"  path_segments
  88.740 +            path_segments = segment *( "/" segment )
  88.741 +            segment       = *pchar *( ";" param )
  88.742 +            param         = *pchar
  88.743 +        """
  88.744 +        if uri == "*":
  88.745 +            return None, None, uri
  88.746 +        
  88.747 +        i = uri.find('://')
  88.748 +        if i > 0 and '?' not in uri[:i]:
  88.749 +            # An absoluteURI.
  88.750 +            # If there's a scheme (and it must be http or https), then:
  88.751 +            # http_URL = "http:" "//" host [ ":" port ] [ abs_path [ "?" query ]]
  88.752 +            scheme, remainder = uri[:i].lower(), uri[i + 3:]
  88.753 +            authority, path = remainder.split("/", 1)
  88.754 +            return scheme, authority, path
  88.755 +        
  88.756 +        if uri.startswith('/'):
  88.757 +            # An abs_path.
  88.758 +            return None, None, uri
  88.759 +        else:
  88.760 +            # An authority.
  88.761 +            return None, uri, None
  88.762 +    
  88.763 +    def respond(self):
  88.764 +        """Call the gateway and write its iterable output."""
  88.765 +        mrbs = self.server.max_request_body_size
  88.766 +        if self.chunked_read:
  88.767 +            self.rfile = ChunkedRFile(self.conn.rfile, mrbs)
  88.768 +        else:
  88.769 +            cl = int(self.inheaders.get("Content-Length", 0))
  88.770 +            if mrbs and mrbs < cl:
  88.771 +                if not self.sent_headers:
  88.772 +                    self.simple_response("413 Request Entity Too Large",
  88.773 +                        "The entity sent with the request exceeds the maximum "
  88.774 +                        "allowed bytes.")
  88.775 +                return
  88.776 +            self.rfile = KnownLengthRFile(self.conn.rfile, cl)
  88.777 +        
  88.778 +        self.server.gateway(self).respond()
  88.779 +        
  88.780 +        if (self.ready and not self.sent_headers):
  88.781 +            self.sent_headers = True
  88.782 +            self.send_headers()
  88.783 +        if self.chunked_write:
  88.784 +            self.conn.wfile.sendall("0\r\n\r\n")
  88.785 +    
  88.786 +    def simple_response(self, status, msg=""):
  88.787 +        """Write a simple response back to the client."""
  88.788 +        status = str(status)
  88.789 +        buf = [self.server.protocol + " " +
  88.790 +               status + CRLF,
  88.791 +               "Content-Length: %s\r\n" % len(msg),
  88.792 +               "Content-Type: text/plain\r\n"]
  88.793 +        
  88.794 +        if status[:3] in ("413", "414"):
  88.795 +            # Request Entity Too Large / Request-URI Too Long
  88.796 +            self.close_connection = True
  88.797 +            if self.response_protocol == 'HTTP/1.1':
  88.798 +                # This will not be true for 414, since read_request_line
  88.799 +                # usually raises 414 before reading the whole line, and we
  88.800 +                # therefore cannot know the proper response_protocol.
  88.801 +                buf.append("Connection: close\r\n")
  88.802 +            else:
  88.803 +                # HTTP/1.0 had no 413/414 status nor Connection header.
  88.804 +                # Emit 400 instead and trust the message body is enough.
  88.805 +                status = "400 Bad Request"
  88.806 +        
  88.807 +        buf.append(CRLF)
  88.808 +        if msg:
  88.809 +            if isinstance(msg, unicode):
  88.810 +                msg = msg.encode("ISO-8859-1")
  88.811 +            buf.append(msg)
  88.812 +        
  88.813 +        try:
  88.814 +            self.conn.wfile.sendall("".join(buf))
  88.815 +        except socket.error, x:
  88.816 +            if x.args[0] not in socket_errors_to_ignore:
  88.817 +                raise
  88.818 +    
  88.819 +    def write(self, chunk):
  88.820 +        """Write unbuffered data to the client."""
  88.821 +        if self.chunked_write and chunk:
  88.822 +            buf = [hex(len(chunk))[2:], CRLF, chunk, CRLF]
  88.823 +            self.conn.wfile.sendall("".join(buf))
  88.824 +        else:
  88.825 +            self.conn.wfile.sendall(chunk)
  88.826 +    
  88.827 +    def send_headers(self):
  88.828 +        """Assert, process, and send the HTTP response message-headers.
  88.829 +        
  88.830 +        You must set self.status, and self.outheaders before calling this.
  88.831 +        """
  88.832 +        hkeys = [key.lower() for key, value in self.outheaders]
  88.833 +        status = int(self.status[:3])
  88.834 +        
  88.835 +        if status == 413:
  88.836 +            # Request Entity Too Large. Close conn to avoid garbage.
  88.837 +            self.close_connection = True
  88.838 +        elif "content-length" not in hkeys:
  88.839 +            # "All 1xx (informational), 204 (no content),
  88.840 +            # and 304 (not modified) responses MUST NOT
  88.841 +            # include a message-body." So no point chunking.
  88.842 +            if status < 200 or status in (204, 205, 304):
  88.843 +                pass
  88.844 +            else:
  88.845 +                if (self.response_protocol == 'HTTP/1.1'
  88.846 +                    and self.method != 'HEAD'):
  88.847 +                    # Use the chunked transfer-coding
  88.848 +                    self.chunked_write = True
  88.849 +                    self.outheaders.append(("Transfer-Encoding", "chunked"))
  88.850 +                else:
  88.851 +                    # Closing the conn is the only way to determine len.
  88.852 +                    self.close_connection = True
  88.853 +        
  88.854 +        if "connection" not in hkeys:
  88.855 +            if self.response_protocol == 'HTTP/1.1':
  88.856 +                # Both server and client are HTTP/1.1 or better
  88.857 +                if self.close_connection:
  88.858 +                    self.outheaders.append(("Connection", "close"))
  88.859 +            else:
  88.860 +                # Server and/or client are HTTP/1.0
  88.861 +                if not self.close_connection:
  88.862 +                    self.outheaders.append(("Connection", "Keep-Alive"))
  88.863 +        
  88.864 +        if (not self.close_connection) and (not self.chunked_read):
  88.865 +            # Read any remaining request body data on the socket.
  88.866 +            # "If an origin server receives a request that does not include an
  88.867 +            # Expect request-header field with the "100-continue" expectation,
  88.868 +            # the request includes a request body, and the server responds
  88.869 +            # with a final status code before reading the entire request body
  88.870 +            # from the transport connection, then the server SHOULD NOT close
  88.871 +            # the transport connection until it has read the entire request,
  88.872 +            # or until the client closes the connection. Otherwise, the client
  88.873 +            # might not reliably receive the response message. However, this
  88.874 +            # requirement is not be construed as preventing a server from
  88.875 +            # defending itself against denial-of-service attacks, or from
  88.876 +            # badly broken client implementations."
  88.877 +            remaining = getattr(self.rfile, 'remaining', 0)
  88.878 +            if remaining > 0:
  88.879 +                self.rfile.read(remaining)
  88.880 +        
  88.881 +        if "date" not in hkeys:
  88.882 +            self.outheaders.append(("Date", rfc822.formatdate()))
  88.883 +        
  88.884 +        if "server" not in hkeys:
  88.885 +            self.outheaders.append(("Server", self.server.server_name))
  88.886 +        
  88.887 +        buf = [self.server.protocol + " " + self.status + CRLF]
  88.888 +        for k, v in self.outheaders:
  88.889 +            buf.append(k + ": " + v + CRLF)
  88.890 +        buf.append(CRLF)
  88.891 +        self.conn.wfile.sendall("".join(buf))
  88.892 +
  88.893 +
  88.894 +class NoSSLError(Exception):
  88.895 +    """Exception raised when a client speaks HTTP to an HTTPS socket."""
  88.896 +    pass
  88.897 +
  88.898 +
  88.899 +class FatalSSLAlert(Exception):
  88.900 +    """Exception raised when the SSL implementation signals a fatal alert."""
  88.901 +    pass
  88.902 +
  88.903 +
  88.904 +class CP_fileobject(socket._fileobject):
  88.905 +    """Faux file object attached to a socket object."""
  88.906 +
  88.907 +    def __init__(self, *args, **kwargs):
  88.908 +        self.bytes_read = 0
  88.909 +        self.bytes_written = 0
  88.910 +        socket._fileobject.__init__(self, *args, **kwargs)
  88.911 +    
  88.912 +    def sendall(self, data):
  88.913 +        """Sendall for non-blocking sockets."""
  88.914 +        while data:
  88.915 +            try:
  88.916 +                bytes_sent = self.send(data)
  88.917 +                data = data[bytes_sent:]
  88.918 +            except socket.error, e:
  88.919 +                if e.args[0] not in socket_errors_nonblocking:
  88.920 +                    raise
  88.921 +
  88.922 +    def send(self, data):
  88.923 +        bytes_sent = self._sock.send(data)
  88.924 +        self.bytes_written += bytes_sent
  88.925 +        return bytes_sent
  88.926 +
  88.927 +    def flush(self):
  88.928 +        if self._wbuf:
  88.929 +            buffer = "".join(self._wbuf)
  88.930 +            self._wbuf = []
  88.931 +            self.sendall(buffer)
  88.932 +
  88.933 +    def recv(self, size):
  88.934 +        while True:
  88.935 +            try:
  88.936 +                data = self._sock.recv(size)
  88.937 +                self.bytes_read += len(data)
  88.938 +                return data
  88.939 +            except socket.error, e:
  88.940 +                if (e.args[0] not in socket_errors_nonblocking
  88.941 +                    and e.args[0] not in socket_error_eintr):
  88.942 +                    raise
  88.943 +
  88.944 +    if not _fileobject_uses_str_type:
  88.945 +        def read(self, size=-1):
  88.946 +            # Use max, disallow tiny reads in a loop as they are very inefficient.
  88.947 +            # We never leave read() with any leftover data from a new recv() call
  88.948 +            # in our internal buffer.
  88.949 +            rbufsize = max(self._rbufsize, self.default_bufsize)
  88.950 +            # Our use of StringIO rather than lists of string objects returned by
  88.951 +            # recv() minimizes memory usage and fragmentation that occurs when
  88.952 +            # rbufsize is large compared to the typical return value of recv().
  88.953 +            buf = self._rbuf
  88.954 +            buf.seek(0, 2)  # seek end
  88.955 +            if size < 0:
  88.956 +                # Read until EOF
  88.957 +                self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
  88.958 +                while True:
  88.959 +                    data = self.recv(rbufsize)
  88.960 +                    if not data:
  88.961 +                        break
  88.962 +                    buf.write(data)
  88.963 +                return buf.getvalue()
  88.964 +            else:
  88.965 +                # Read until size bytes or EOF seen, whichever comes first
  88.966 +                buf_len = buf.tell()
  88.967 +                if buf_len >= size:
  88.968 +                    # Already have size bytes in our buffer?  Extract and return.
  88.969 +                    buf.seek(0)
  88.970 +                    rv = buf.read(size)
  88.971 +                    self._rbuf = StringIO.StringIO()
  88.972 +                    self._rbuf.write(buf.read())
  88.973 +                    return rv
  88.974 +
  88.975 +                self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
  88.976 +                while True:
  88.977 +                    left = size - buf_len
  88.978 +                    # recv() will malloc the amount of memory given as its
  88.979 +                    # parameter even though it often returns much less data
  88.980 +                    # than that.  The returned data string is short lived
  88.981 +                    # as we copy it into a StringIO and free it.  This avoids
  88.982 +                    # fragmentation issues on many platforms.
  88.983 +                    data = self.recv(left)
  88.984 +                    if not data:
  88.985 +                        break
  88.986 +                    n = len(data)
  88.987 +                    if n == size and not buf_len:
  88.988 +                        # Shortcut.  Avoid buffer data copies when:
  88.989 +                        # - We have no data in our buffer.
  88.990 +                        # AND
  88.991 +                        # - Our call to recv returned exactly the
  88.992 +                        #   number of bytes we were asked to read.
  88.993 +                        return data
  88.994 +                    if n == left:
  88.995 +                        buf.write(data)
  88.996 +                        del data  # explicit free
  88.997 +                        break
  88.998 +                    assert n <= left, "recv(%d) returned %d bytes" % (left, n)
  88.999 +                    buf.write(data)
 88.1000 +                    buf_len += n
 88.1001 +                    del data  # explicit free
 88.1002 +                    #assert buf_len == buf.tell()
 88.1003 +                return buf.getvalue()
 88.1004 +
 88.1005 +        def readline(self, size=-1):
 88.1006 +            buf = self._rbuf
 88.1007 +            buf.seek(0, 2)  # seek end
 88.1008 +            if buf.tell() > 0:
 88.1009 +                # check if we already have it in our buffer
 88.1010 +                buf.seek(0)
 88.1011 +                bline = buf.readline(size)
 88.1012 +                if bline.endswith('\n') or len(bline) == size:
 88.1013 +                    self._rbuf = StringIO.StringIO()
 88.1014 +                    self._rbuf.write(buf.read())
 88.1015 +                    return bline
 88.1016 +                del bline
 88.1017 +            if size < 0:
 88.1018 +                # Read until \n or EOF, whichever comes first
 88.1019 +                if self._rbufsize <= 1:
 88.1020 +                    # Speed up unbuffered case
 88.1021 +                    buf.seek(0)
 88.1022 +                    buffers = [buf.read()]
 88.1023 +                    self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
 88.1024 +                    data = None
 88.1025 +                    recv = self.recv
 88.1026 +                    while data != "\n":
 88.1027 +                        data = recv(1)
 88.1028 +                        if not data:
 88.1029 +                            break
 88.1030 +                        buffers.append(data)
 88.1031 +                    return "".join(buffers)
 88.1032 +
 88.1033 +                buf.seek(0, 2)  # seek end
 88.1034 +                self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
 88.1035 +                while True:
 88.1036 +                    data = self.recv(self._rbufsize)
 88.1037 +                    if not data:
 88.1038 +                        break
 88.1039 +                    nl = data.find('\n')
 88.1040 +                    if nl >= 0:
 88.1041 +                        nl += 1
 88.1042 +                        buf.write(data[:nl])
 88.1043 +                        self._rbuf.write(data[nl:])
 88.1044 +                        del data
 88.1045 +                        break
 88.1046 +                    buf.write(data)
 88.1047 +                return buf.getvalue()
 88.1048 +            else:
 88.1049 +                # Read until size bytes or \n or EOF seen, whichever comes first
 88.1050 +                buf.seek(0, 2)  # seek end
 88.1051 +                buf_len = buf.tell()
 88.1052 +                if buf_len >= size:
 88.1053 +                    buf.seek(0)
 88.1054 +                    rv = buf.read(size)
 88.1055 +                    self._rbuf = StringIO.StringIO()
 88.1056 +                    self._rbuf.write(buf.read())
 88.1057 +                    return rv
 88.1058 +                self._rbuf = StringIO.StringIO()  # reset _rbuf.  we consume it via buf.
 88.1059 +                while True:
 88.1060 +                    data = self.recv(self._rbufsize)
 88.1061 +                    if not data:
 88.1062 +                        break
 88.1063 +                    left = size - buf_len
 88.1064 +                    # did we just receive a newline?
 88.1065 +                    nl = data.find('\n', 0, left)
 88.1066 +                    if nl >= 0:
 88.1067 +                        nl += 1
 88.1068 +                        # save the excess data to _rbuf
 88.1069 +                        self._rbuf.write(data[nl:])
 88.1070 +                        if buf_len:
 88.1071 +                            buf.write(data[:nl])
 88.1072 +                            break
 88.1073 +                        else:
 88.1074 +                            # Shortcut.  Avoid data copy through buf when returning
 88.1075 +                            # a substring of our first recv().
 88.1076 +                            return data[:nl]
 88.1077 +                    n = len(data)
 88.1078 +                    if n == size and not buf_len:
 88.1079 +                        # Shortcut.  Avoid data copy through buf when
 88.1080 +                        # returning exactly all of our first recv().
 88.1081 +                        return data
 88.1082 +                    if n >= left:
 88.1083 +                        buf.write(data[:left])
 88.1084 +                        self._rbuf.write(data[left:])
 88.1085 +                        break
 88.1086 +                    buf.write(data)
 88.1087 +                    buf_len += n
 88.1088 +                    #assert buf_len == buf.tell()
 88.1089 +                return buf.getvalue()
 88.1090 +    else:
 88.1091 +        def read(self, size=-1):
 88.1092 +            if size < 0:
 88.1093 +                # Read until EOF
 88.1094 +                buffers = [self._rbuf]
 88.1095 +                self._rbuf = ""
 88.1096 +                if self._rbufsize <= 1:
 88.1097 +                    recv_size = self.default_bufsize
 88.1098 +                else:
 88.1099 +                    recv_size = self._rbufsize
 88.1100 +
 88.1101 +                while True:
 88.1102 +                    data = self.recv(recv_size)
 88.1103 +                    if not data:
 88.1104 +                        break
 88.1105 +                    buffers.append(data)
 88.1106 +                return "".join(buffers)
 88.1107 +            else:
 88.1108 +                # Read until size bytes or EOF seen, whichever comes first
 88.1109 +                data = self._rbuf
 88.1110 +                buf_len = len(data)
 88.1111 +                if buf_len >= size:
 88.1112 +                    self._rbuf = data[size:]
 88.1113 +                    return data[:size]
 88.1114 +                buffers = []
 88.1115 +                if data:
 88.1116 +                    buffers.append(data)
 88.1117 +                self._rbuf = ""
 88.1118 +                while True:
 88.1119 +                    left = size - buf_len
 88.1120 +                    recv_size = max(self._rbufsize, left)
 88.1121 +                    data = self.recv(recv_size)
 88.1122 +                    if not data:
 88.1123 +                        break
 88.1124 +                    buffers.append(data)
 88.1125 +                    n = len(data)
 88.1126 +                    if n >= left:
 88.1127 +                        self._rbuf = data[left:]
 88.1128 +                        buffers[-1] = data[:left]
 88.1129 +                        break
 88.1130 +                    buf_len += n
 88.1131 +                return "".join(buffers)
 88.1132 +
 88.1133 +        def readline(self, size=-1):
 88.1134 +            data = self._rbuf
 88.1135 +            if size < 0:
 88.1136 +                # Read until \n or EOF, whichever comes first
 88.1137 +                if self._rbufsize <= 1:
 88.1138 +                    # Speed up unbuffered case
 88.1139 +                    assert data == ""
 88.1140 +                    buffers = []
 88.1141 +                    while data != "\n":
 88.1142 +                        data = self.recv(1)
 88.1143 +                        if not data:
 88.1144 +                            break
 88.1145 +                        buffers.append(data)
 88.1146 +                    return "".join(buffers)
 88.1147 +                nl = data.find('\n')
 88.1148 +                if nl >= 0:
 88.1149 +                    nl += 1
 88.1150 +                    self._rbuf = data[nl:]
 88.1151 +                    return data[:nl]
 88.1152 +                buffers = []
 88.1153 +                if data:
 88.1154 +                    buffers.append(data)
 88.1155 +                self._rbuf = ""
 88.1156 +                while True:
 88.1157 +                    data = self.recv(self._rbufsize)
 88.1158 +                    if not data:
 88.1159 +                        break
 88.1160 +                    buffers.append(data)
 88.1161 +                    nl = data.find('\n')
 88.1162 +                    if nl >= 0:
 88.1163 +                        nl += 1
 88.1164 +                        self._rbuf = data[nl:]
 88.1165 +                        buffers[-1] = data[:nl]
 88.1166 +                        break
 88.1167 +                return "".join(buffers)
 88.1168 +            else:
 88.1169 +                # Read until size bytes or \n or EOF seen, whichever comes first
 88.1170 +                nl = data.find('\n', 0, size)
 88.1171 +                if nl >= 0:
 88.1172 +                    nl += 1
 88.1173 +                    self._rbuf = data[nl:]
 88.1174 +                    return data[:nl]
 88.1175 +                buf_len = len(data)
 88.1176 +                if buf_len >= size:
 88.1177 +                    self._rbuf = data[size:]
 88.1178 +                    return data[:size]
 88.1179 +                buffers = []
 88.1180 +                if data:
 88.1181 +                    buffers.append(data)
 88.1182 +                self._rbuf = ""
 88.1183 +                while True:
 88.1184 +                    data = self.recv(self._rbufsize)
 88.1185 +                    if not data:
 88.1186 +                        break
 88.1187 +                    buffers.append(data)
 88.1188 +                    left = size - buf_len
 88.1189 +                    nl = data.find('\n', 0, left)
 88.1190 +                    if nl >= 0:
 88.1191 +                        nl += 1
 88.1192 +                        self._rbuf = data[nl:]
 88.1193 +                        buffers[-1] = data[:nl]
 88.1194 +                        break
 88.1195 +                    n = len(data)
 88.1196 +                    if n >= left:
 88.1197 +                        self._rbuf = data[left:]
 88.1198 +                        buffers[-1] = data[:left]
 88.1199 +                        break
 88.1200 +                    buf_len += n
 88.1201 +                return "".join(buffers)
 88.1202 +
 88.1203 +
 88.1204 +class HTTPConnection(object):
 88.1205 +    """An HTTP connection (active socket).
 88.1206 +    
 88.1207 +    server: the Server object which received this connection.
 88.1208 +    socket: the raw socket object (usually TCP) for this connection.
 88.1209 +    makefile: a fileobject class for reading from the socket.
 88.1210 +    """
 88.1211 +    
 88.1212 +    remote_addr = None
 88.1213 +    remote_port = None
 88.1214 +    ssl_env = None
 88.1215 +    rbufsize = DEFAULT_BUFFER_SIZE
 88.1216 +    wbufsize = DEFAULT_BUFFER_SIZE
 88.1217 +    RequestHandlerClass = HTTPRequest
 88.1218 +    
 88.1219 +    def __init__(self, server, sock, makefile=CP_fileobject):
 88.1220 +        self.server = server
 88.1221 +        self.socket = sock
 88.1222 +        self.rfile = makefile(sock, "rb", self.rbufsize)
 88.1223 +        self.wfile = makefile(sock, "wb", self.wbufsize)
 88.1224 +        self.requests_seen = 0
 88.1225 +    
 88.1226 +    def communicate(self):
 88.1227 +        """Read each request and respond appropriately."""
 88.1228 +        request_seen = False
 88.1229 +        try:
 88.1230 +            while True:
 88.1231 +                # (re)set req to None so that if something goes wrong in
 88.1232 +                # the RequestHandlerClass constructor, the error doesn't
 88.1233 +                # get written to the previous request.
 88.1234 +                req = None
 88.1235 +                req = self.RequestHandlerClass(self.server, self)
 88.1236 +                
 88.1237 +                # This order of operations should guarantee correct pipelining.
 88.1238 +                req.parse_request()
 88.1239 +                if self.server.stats['Enabled']:
 88.1240 +                    self.requests_seen += 1
 88.1241 +                if not req.ready:
 88.1242 +                    # Something went wrong in the parsing (and the server has
 88.1243 +                    # probably already made a simple_response). Return and
 88.1244 +                    # let the conn close.
 88.1245 +                    return
 88.1246 +                
 88.1247 +                request_seen = True
 88.1248 +                req.respond()
 88.1249 +                if req.close_connection:
 88.1250 +                    return
 88.1251 +        except socket.error, e:
 88.1252 +            errnum = e.args[0]
 88.1253 +            # sadly SSL sockets return a different (longer) time out string
 88.1254 +            if errnum == 'timed out' or errnum == 'The read operation timed out':
 88.1255 +                # Don't error if we're between requests; only error
 88.1256 +                # if 1) no request has been started at all, or 2) we're
 88.1257 +                # in the middle of a request.
 88.1258 +                # See http://www.cherrypy.org/ticket/853
 88.1259 +                if (not request_seen) or (req and req.started_request):
 88.1260 +                    # Don't bother writing the 408 if the response
 88.1261 +                    # has already started being written.
 88.1262 +                    if req and not req.sent_headers:
 88.1263 +                        try:
 88.1264 +                            req.simple_response("408 Request Timeout")
 88.1265 +                        except FatalSSLAlert:
 88.1266 +                            # Close the connection.
 88.1267 +                            return
 88.1268 +            elif errnum not in socket_errors_to_ignore:
 88.1269 +                if req and not req.sent_headers:
 88.1270 +                    try:
 88.1271 +                        req.simple_response("500 Internal Server Error",
 88.1272 +                                            format_exc())
 88.1273 +                    except FatalSSLAlert:
 88.1274 +                        # Close the connection.
 88.1275 +                        return
 88.1276 +            return
 88.1277 +        except (KeyboardInterrupt, SystemExit):
 88.1278 +            raise
 88.1279 +        except FatalSSLAlert:
 88.1280 +            # Close the connection.
 88.1281 +            return
 88.1282 +        except NoSSLError:
 88.1283 +            if req and not req.sent_headers:
 88.1284 +                # Unwrap our wfile
 88.1285 +                self.wfile = CP_fileobject(self.socket._sock, "wb", self.wbufsize)
 88.1286 +                req.simple_response("400 Bad Request",
 88.1287 +                    "The client sent a plain HTTP request, but "
 88.1288 +                    "this server only speaks HTTPS on this port.")
 88.1289 +                self.linger = True
 88.1290 +        except Exception:
 88.1291 +            if req and not req.sent_headers:
 88.1292 +                try:
 88.1293 +                    req.simple_response("500 Internal Server Error", format_exc())
 88.1294 +                except FatalSSLAlert:
 88.1295 +                    # Close the connection.
 88.1296 +                    return
 88.1297 +    
 88.1298 +    linger = False
 88.1299 +    
 88.1300 +    def close(self):
 88.1301 +        """Close the socket underlying this connection."""
 88.1302 +        self.rfile.close()
 88.1303 +        
 88.1304 +        if not self.linger:
 88.1305 +            # Python's socket module does NOT call close on the kernel socket
 88.1306 +            # when you call socket.close(). We do so manually here because we
 88.1307 +            # want this server to send a FIN TCP segment immediately. Note this
 88.1308 +            # must be called *before* calling socket.close(), because the latter
 88.1309 +            # drops its reference to the kernel socket.
 88.1310 +            if hasattr(self.socket, '_sock'):
 88.1311 +                self.socket._sock.close()
 88.1312 +            self.socket.close()
 88.1313 +        else:
 88.1314 +            # On the other hand, sometimes we want to hang around for a bit
 88.1315 +            # to make sure the client has a chance to read our entire
 88.1316 +            # response. Skipping the close() calls here delays the FIN
 88.1317 +            # packet until the socket object is garbage-collected later.
 88.1318 +            # Someday, perhaps, we'll do the full lingering_close that
 88.1319 +            # Apache does, but not today.
 88.1320 +            pass
 88.1321 +
 88.1322 +
 88.1323 +_SHUTDOWNREQUEST = None
 88.1324 +
 88.1325 +class WorkerThread(threading.Thread):
 88.1326 +    """Thread which continuously polls a Queue for Connection objects.
 88.1327 +    
 88.1328 +    Due to the timing issues of polling a Queue, a WorkerThread does not
 88.1329 +    check its own 'ready' flag after it has started. To stop the thread,
 88.1330 +    it is necessary to stick a _SHUTDOWNREQUEST object onto the Queue
 88.1331 +    (one for each running WorkerThread).
 88.1332 +    """
 88.1333 +    
 88.1334 +    conn = None
 88.1335 +    """The current connection pulled off the Queue, or None."""
 88.1336 +    
 88.1337 +    server = None
 88.1338 +    """The HTTP Server which spawned this thread, and which owns the
 88.1339 +    Queue and is placing active connections into it."""
 88.1340 +    
 88.1341 +    ready = False
 88.1342 +    """A simple flag for the calling server to know when this thread
 88.1343 +    has begun polling the Queue."""
 88.1344 +    
 88.1345 +    
 88.1346 +    def __init__(self, server):
 88.1347 +        self.ready = False
 88.1348 +        self.server = server
 88.1349 +        
 88.1350 +        self.requests_seen = 0
 88.1351 +        self.bytes_read = 0
 88.1352 +        self.bytes_written = 0
 88.1353 +        self.start_time = None
 88.1354 +        self.work_time = 0
 88.1355 +        self.stats = {
 88.1356 +            'Requests': lambda s: self.requests_seen + ((self.start_time is None) and 0 or self.conn.requests_seen),
 88.1357 +            'Bytes Read': lambda s: self.bytes_read + ((self.start_time is None) and 0 or self.conn.rfile.bytes_read),
 88.1358 +            'Bytes Written': lambda s: self.bytes_written + ((self.start_time is None) and 0 or self.conn.wfile.bytes_written),
 88.1359 +            'Work Time': lambda s: self.work_time + ((self.start_time is None) and 0 or time.time() - self.start_time),
 88.1360 +            'Read Throughput': lambda s: s['Bytes Read'](s) / (s['Work Time'](s) or 1e-6),
 88.1361 +            'Write Throughput': lambda s: s['Bytes Written'](s) / (s['Work Time'](s) or 1e-6),
 88.1362 +        }
 88.1363 +        threading.Thread.__init__(self)
 88.1364 +    
 88.1365 +    def run(self):
 88.1366 +        self.server.stats['Worker Threads'][self.getName()] = self.stats
 88.1367 +        try:
 88.1368 +            self.ready = True
 88.1369 +            while True:
 88.1370 +                conn = self.server.requests.get()
 88.1371 +                if conn is _SHUTDOWNREQUEST:
 88.1372 +                    return
 88.1373 +                
 88.1374 +                self.conn = conn
 88.1375 +                if self.server.stats['Enabled']:
 88.1376 +                    self.start_time = time.time()
 88.1377 +                try:
 88.1378 +                    conn.communicate()
 88.1379 +                finally:
 88.1380 +                    conn.close()
 88.1381 +                    if self.server.stats['Enabled']:
 88.1382 +                        self.requests_seen += self.conn.requests_seen
 88.1383 +                        self.bytes_read += self.conn.rfile.bytes_read
 88.1384 +                        self.bytes_written += self.conn.wfile.bytes_written
 88.1385 +                        self.work_time += time.time() - self.start_time
 88.1386 +                        self.start_time = None
 88.1387 +                    self.conn = None
 88.1388 +        except (KeyboardInterrupt, SystemExit), exc:
 88.1389 +            self.server.interrupt = exc
 88.1390 +
 88.1391 +
 88.1392 +class ThreadPool(object):
 88.1393 +    """A Request Queue for the CherryPyWSGIServer which pools threads.
 88.1394 +    
 88.1395 +    ThreadPool objects must provide min, get(), put(obj), start()
 88.1396 +    and stop(timeout) attributes.
 88.1397 +    """
 88.1398 +    
 88.1399 +    def __init__(self, server, min=10, max=-1):
 88.1400 +        self.server = server
 88.1401 +        self.min = min
 88.1402 +        self.max = max
 88.1403 +        self._threads = []
 88.1404 +        self._queue = Queue.Queue()
 88.1405 +        self.get = self._queue.get
 88.1406 +    
 88.1407 +    def start(self):
 88.1408 +        """Start the pool of threads."""
 88.1409 +        for i in range(self.min):
 88.1410 +            self._threads.append(WorkerThread(self.server))
 88.1411 +        for worker in self._threads:
 88.1412 +            worker.setName("CP Server " + worker.getName())
 88.1413 +            worker.start()
 88.1414 +        for worker in self._threads:
 88.1415 +            while not worker.ready:
 88.1416 +                time.sleep(.1)
 88.1417 +    
 88.1418 +    def _get_idle(self):
 88.1419 +        """Number of worker threads which are idle. Read-only."""
 88.1420 +        return len([t for t in self._threads if t.conn is None])
 88.1421 +    idle = property(_get_idle, doc=_get_idle.__doc__)
 88.1422 +    
 88.1423 +    def put(self, obj):
 88.1424 +        self._queue.put(obj)
 88.1425 +        if obj is _SHUTDOWNREQUEST:
 88.1426 +            return
 88.1427 +    
 88.1428 +    def grow(self, amount):
 88.1429 +        """Spawn new worker threads (not above self.max)."""
 88.1430 +        for i in range(amount):
 88.1431 +            if self.max > 0 and len(self._threads) >= self.max:
 88.1432 +                break
 88.1433 +            worker = WorkerThread(self.server)
 88.1434 +            worker.setName("CP Server " + worker.getName())
 88.1435 +            self._threads.append(worker)
 88.1436 +            worker.start()
 88.1437 +    
 88.1438 +    def shrink(self, amount):
 88.1439 +        """Kill off worker threads (not below self.min)."""
 88.1440 +        # Grow/shrink the pool if necessary.
 88.1441 +        # Remove any dead threads from our list
 88.1442 +        for t in self._threads:
 88.1443 +            if not t.isAlive():
 88.1444 +                self._threads.remove(t)
 88.1445 +                amount -= 1
 88.1446 +        
 88.1447 +        if amount > 0:
 88.1448 +            for i in range(min(amount, len(self._threads) - self.min)):
 88.1449 +                # Put a number of shutdown requests on the queue equal
 88.1450 +                # to 'amount'. Once each of those is processed by a worker,
 88.1451 +                # that worker will terminate and be culled from our list
 88.1452 +                # in self.put.
 88.1453 +                self._queue.put(_SHUTDOWNREQUEST)
 88.1454 +    
 88.1455 +    def stop(self, timeout=5):
 88.1456 +        # Must shut down threads here so the code that calls
 88.1457 +        # this method can know when all threads are stopped.
 88.1458 +        for worker in self._threads:
 88.1459 +            self._queue.put(_SHUTDOWNREQUEST)
 88.1460 +        
 88.1461 +        # Don't join currentThread (when stop is called inside a request).
 88.1462 +        current = threading.currentThread()
 88.1463 +        if timeout and timeout >= 0:
 88.1464 +            endtime = time.time() + timeout
 88.1465 +        while self._threads:
 88.1466 +            worker = self._threads.pop()
 88.1467 +            if worker is not current and worker.isAlive():
 88.1468 +                try:
 88.1469 +                    if timeout is None or timeout < 0:
 88.1470 +                        worker.join()
 88.1471 +                    else:
 88.1472 +                        remaining_time = endtime - time.time()
 88.1473 +                        if remaining_time > 0:
 88.1474 +                            worker.join(remaining_time)
 88.1475 +                        if worker.isAlive():
 88.1476 +                            # We exhausted the timeout.
 88.1477 +                            # Forcibly shut down the socket.
 88.1478 +                            c = worker.conn
 88.1479 +                            if c and not c.rfile.closed:
 88.1480 +                                try:
 88.1481 +                                    c.socket.shutdown(socket.SHUT_RD)
 88.1482 +                                except TypeError:
 88.1483 +                                    # pyOpenSSL sockets don't take an arg
 88.1484 +                                    c.socket.shutdown()
 88.1485 +                            worker.join()
 88.1486 +                except (AssertionError,
 88.1487 +                        # Ignore repeated Ctrl-C.
 88.1488 +                        # See http://www.cherrypy.org/ticket/691.
 88.1489 +                        KeyboardInterrupt), exc1:
 88.1490 +                    pass
 88.1491 +    
 88.1492 +    def _get_qsize(self):
 88.1493 +        return self._queue.qsize()
 88.1494 +    qsize = property(_get_qsize)
 88.1495 +
 88.1496 +
 88.1497 +
 88.1498 +try:
 88.1499 +    import fcntl
 88.1500 +except ImportError:
 88.1501 +    try:
 88.1502 +        from ctypes import windll, WinError
 88.1503 +    except ImportError:
 88.1504 +        def prevent_socket_inheritance(sock):
 88.1505 +            """Dummy function, since neither fcntl nor ctypes are available."""
 88.1506 +            pass
 88.1507 +    else:
 88.1508 +        def prevent_socket_inheritance(sock):
 88.1509 +            """Mark the given socket fd as non-inheritable (Windows)."""
 88.1510 +            if not windll.kernel32.SetHandleInformation(sock.fileno(), 1, 0):
 88.1511 +                raise WinError()
 88.1512 +else:
 88.1513 +    def prevent_socket_inheritance(sock):
 88.1514 +        """Mark the given socket fd as non-inheritable (POSIX)."""
 88.1515 +        fd = sock.fileno()
 88.1516 +        old_flags = fcntl.fcntl(fd, fcntl.F_GETFD)
 88.1517 +        fcntl.fcntl(fd, fcntl.F_SETFD, old_flags | fcntl.FD_CLOEXEC)
 88.1518 +
 88.1519 +
 88.1520 +class SSLAdapter(object):
 88.1521 +    """Base class for SSL driver library adapters.
 88.1522 +    
 88.1523 +    Required methods:
 88.1524 +    
 88.1525 +        * ``wrap(sock) -> (wrapped socket, ssl environ dict)``
 88.1526 +        * ``makefile(sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE) -> socket file object``
 88.1527 +    """
 88.1528 +    
 88.1529 +    def __init__(self, certificate, private_key, certificate_chain=None):
 88.1530 +        self.certificate = certificate
 88.1531 +        self.private_key = private_key
 88.1532 +        self.certificate_chain = certificate_chain
 88.1533 +    
 88.1534 +    def wrap(self, sock):
 88.1535 +        raise NotImplemented
 88.1536 +    
 88.1537 +    def makefile(self, sock, mode='r', bufsize=DEFAULT_BUFFER_SIZE):
 88.1538 +        raise NotImplemented
 88.1539 +
 88.1540 +
 88.1541 +class HTTPServer(object):
 88.1542 +    """An HTTP server."""
 88.1543 +    
 88.1544 +    _bind_addr = "127.0.0.1"
 88.1545 +    _interrupt = None
 88.1546 +    
 88.1547 +    gateway = None
 88.1548 +    """A Gateway instance."""
 88.1549 +    
 88.1550 +    minthreads = None
 88.1551 +    """The minimum number of worker threads to create (default 10)."""
 88.1552 +    
 88.1553 +    maxthreads = None
 88.1554 +    """The maximum number of worker threads to create (default -1 = no limit)."""
 88.1555 +    
 88.1556 +    server_name = None
 88.1557 +    """The name of the server; defaults to socket.gethostname()."""
 88.1558 +    
 88.1559 +    protocol = "HTTP/1.1"
 88.1560 +    """The version string to write in the Status-Line of all HTTP responses.
 88.1561 +    
 88.1562 +    For example, "HTTP/1.1" is the default. This also limits the supported
 88.1563 +    features used in the response."""
 88.1564 +    
 88.1565 +    request_queue_size = 5
 88.1566 +    """The 'backlog' arg to socket.listen(); max queued connections (default 5)."""
 88.1567 +    
 88.1568 +    shutdown_timeout = 5
 88.1569 +    """The total time, in seconds, to wait for worker threads to cleanly exit."""
 88.1570 +    
 88.1571 +    timeout = 10
 88.1572 +    """The timeout in seconds for accepted connections (default 10)."""
 88.1573 +    
 88.1574 +    version = "CherryPy/3.2.0"
 88.1575 +    """A version string for the HTTPServer."""
 88.1576 +    
 88.1577 +    software = None
 88.1578 +    """The value to set for the SERVER_SOFTWARE entry in the WSGI environ.
 88.1579 +    
 88.1580 +    If None, this defaults to ``'%s Server' % self.version``."""
 88.1581 +    
 88.1582 +    ready = False
 88.1583 +    """An internal flag which marks whether the socket is accepting connections."""
 88.1584 +    
 88.1585 +    max_request_header_size = 0
 88.1586 +    """The maximum size, in bytes, for request headers, or 0 for no limit."""
 88.1587 +    
 88.1588 +    max_request_body_size = 0
 88.1589 +    """The maximum size, in bytes, for request bodies, or 0 for no limit."""
 88.1590 +    
 88.1591 +    nodelay = True
 88.1592 +    """If True (the default since 3.1), sets the TCP_NODELAY socket option."""
 88.1593 +    
 88.1594 +    ConnectionClass = HTTPConnection
 88.1595 +    """The class to use for handling HTTP connections."""
 88.1596 +    
 88.1597 +    ssl_adapter = None
 88.1598 +    """An instance of SSLAdapter (or a subclass).
 88.1599 +    
 88.1600 +    You must have the corresponding SSL driver library installed."""
 88.1601 +    
 88.1602 +    def __init__(self, bind_addr, gateway, minthreads=10, maxthreads=-1,
 88.1603 +                 server_name=None):
 88.1604 +        self.bind_addr = bind_addr
 88.1605 +        self.gateway = gateway
 88.1606 +        
 88.1607 +        self.requests = ThreadPool(self, min=minthreads or 1, max=maxthreads)
 88.1608 +        
 88.1609 +        if not server_name:
 88.1610 +            server_name = socket.gethostname()
 88.1611 +        self.server_name = server_name
 88.1612 +        self.clear_stats()
 88.1613 +    
 88.1614 +    def clear_stats(self):
 88.1615 +        self._start_time = None
 88.1616 +        self._run_time = 0
 88.1617 +        self.stats = {
 88.1618 +            'Enabled': False,
 88.1619 +            'Bind Address': lambda s: repr(self.bind_addr),
 88.1620 +            'Run time': lambda s: (not s['Enabled']) and 0 or self.runtime(),
 88.1621 +            'Accepts': 0,
 88.1622 +            'Accepts/sec': lambda s: s['Accepts'] / self.runtime(),
 88.1623 +            'Queue': lambda s: getattr(self.requests, "qsize", None),
 88.1624 +            'Threads': lambda s: len(getattr(self.requests, "_threads", [])),
 88.1625 +            'Threads Idle': lambda s: getattr(self.requests, "idle", None),
 88.1626 +            'Socket Errors': 0,
 88.1627 +            'Requests': lambda s: (not s['Enabled']) and 0 or sum([w['Requests'](w) for w
 88.1628 +                                       in s['Worker Threads'].values()], 0),
 88.1629 +            'Bytes Read': lambda s: (not s['Enabled']) and 0 or sum([w['Bytes Read'](w) for w
 88.1630 +                                         in s['Worker Threads'].values()], 0),
 88.1631 +            'Bytes Written': lambda s: (not s['Enabled']) and 0 or sum([w['Bytes Written'](w) for w
 88.1632 +                                            in s['Worker Threads'].values()], 0),
 88.1633 +            'Work Time': lambda s: (not s['Enabled']) and 0 or sum([w['Work Time'](w) for w
 88.1634 +                                         in s['Worker Threads'].values()], 0),
 88.1635 +            'Read Throughput': lambda s: (not s['Enabled']) and 0 or sum(
 88.1636 +                [w['Bytes Read'](w) / (w['Work Time'](w) or 1e-6)
 88.1637 +                 for w in s['Worker Threads'].values()], 0),
 88.1638 +            'Write Throughput': lambda s: (not s['Enabled']) and 0 or sum(
 88.1639 +                [w['Bytes Written'](w) / (w['Work Time'](w) or 1e-6)
 88.1640 +                 for w in s['Worker Threads'].values()], 0),
 88.1641 +            'Worker Threads': {},
 88.1642 +            }
 88.1643 +        logging.statistics["CherryPy HTTPServer %d" % id(self)] = self.stats
 88.1644 +    
 88.1645 +    def runtime(self):
 88.1646 +        if self._start_time is None:
 88.1647 +            return self._run_time
 88.1648 +        else:
 88.1649 +            return self._run_time + (time.time() - self._start_time)
 88.1650 +    
 88.1651 +    def __str__(self):
 88.1652 +        return "%s.%s(%r)" % (self.__module__, self.__class__.__name__,
 88.1653 +                              self.bind_addr)
 88.1654 +    
 88.1655 +    def _get_bind_addr(self):
 88.1656 +        return self._bind_addr
 88.1657 +    def _set_bind_addr(self, value):
 88.1658 +        if isinstance(value, tuple) and value[0] in ('', None):
 88.1659 +            # Despite the socket module docs, using '' does not
 88.1660 +            # allow AI_PASSIVE to work. Passing None instead
 88.1661 +            # returns '0.0.0.0' like we want. In other words:
 88.1662 +            #     host    AI_PASSIVE     result
 88.1663 +            #      ''         Y         192.168.x.y
 88.1664 +            #      ''         N         192.168.x.y
 88.1665 +            #     None        Y         0.0.0.0
 88.1666 +            #     None        N         127.0.0.1
 88.1667 +            # But since you can get the same effect with an explicit
 88.1668 +            # '0.0.0.0', we deny both the empty string and None as values.
 88.1669 +            raise ValueError("Host values of '' or None are not allowed. "
 88.1670 +                             "Use '0.0.0.0' (IPv4) or '::' (IPv6) instead "
 88.1671 +                             "to listen on all active interfaces.")
 88.1672 +        self._bind_addr = value
 88.1673 +    bind_addr = property(_get_bind_addr, _set_bind_addr,
 88.1674 +        doc="""The interface on which to listen for connections.
 88.1675 +        
 88.1676 +        For TCP sockets, a (host, port) tuple. Host values may be any IPv4
 88.1677 +        or IPv6 address, or any valid hostname. The string 'localhost' is a
 88.1678 +        synonym for '127.0.0.1' (or '::1', if your hosts file prefers IPv6).
 88.1679 +        The string '0.0.0.0' is a special IPv4 entry meaning "any active
 88.1680 +        interface" (INADDR_ANY), and '::' is the similar IN6ADDR_ANY for
 88.1681 +        IPv6. The empty string or None are not allowed.
 88.1682 +        
 88.1683 +        For UNIX sockets, supply the filename as a string.""")
 88.1684 +    
 88.1685 +    def start(self):
 88.1686 +        """Run the server forever."""
 88.1687 +        # We don't have to trap KeyboardInterrupt or SystemExit here,
 88.1688 +        # because cherrpy.server already does so, calling self.stop() for us.
 88.1689 +        # If you're using this server with another framework, you should
 88.1690 +        # trap those exceptions in whatever code block calls start().
 88.1691 +        self._interrupt = None
 88.1692 +        
 88.1693 +        if self.software is None:
 88.1694 +            self.software = "%s Server" % self.version
 88.1695 +        
 88.1696 +        # SSL backward compatibility
 88.1697 +        if (self.ssl_adapter is None and
 88.1698 +            getattr(self, 'ssl_certificate', None) and
 88.1699 +            getattr(self, 'ssl_private_key', None)):
 88.1700 +            warnings.warn(
 88.1701 +                    "SSL attributes are deprecated in CherryPy 3.2, and will "
 88.1702 +                    "be removed in CherryPy 3.3. Use an ssl_adapter attribute "
 88.1703 +                    "instead.",
 88.1704 +                    DeprecationWarning
 88.1705 +                )
 88.1706 +            try:
 88.1707 +                from cherrypy.wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
 88.1708 +            except ImportError:
 88.1709 +                pass
 88.1710 +            else:
 88.1711 +                self.ssl_adapter = pyOpenSSLAdapter(
 88.1712 +                    self.ssl_certificate, self.ssl_private_key,
 88.1713 +                    getattr(self, 'ssl_certificate_chain', None))
 88.1714 +        
 88.1715 +        # Select the appropriate socket
 88.1716 +        if isinstance(self.bind_addr, basestring):
 88.1717 +            # AF_UNIX socket
 88.1718 +            
 88.1719 +            # So we can reuse the socket...
 88.1720 +            try: os.unlink(self.bind_addr)
 88.1721 +            except: pass
 88.1722 +            
 88.1723 +            # So everyone can access the socket...
 88.1724 +            try: os.chmod(self.bind_addr, 0777)
 88.1725 +            except: pass
 88.1726 +            
 88.1727 +            info = [(socket.AF_UNIX, socket.SOCK_STREAM, 0, "", self.bind_addr)]
 88.1728 +        else:
 88.1729 +            # AF_INET or AF_INET6 socket
 88.1730 +            # Get the correct address family for our host (allows IPv6 addresses)
 88.1731 +            host, port = self.bind_addr
 88.1732 +            try:
 88.1733 +                info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
 88.1734 +                                          socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
 88.1735 +            except socket.gaierror:
 88.1736 +                if ':' in self.bind_addr[0]:
 88.1737 +                    info = [(socket.AF_INET6, socket.SOCK_STREAM,
 88.1738 +                             0, "", self.bind_addr + (0, 0))]
 88.1739 +                else:
 88.1740 +                    info = [(socket.AF_INET, socket.SOCK_STREAM,
 88.1741 +                             0, "", self.bind_addr)]
 88.1742 +        
 88.1743 +        self.socket = None
 88.1744 +        msg = "No socket could be created"
 88.1745 +        for res in info:
 88.1746 +            af, socktype, proto, canonname, sa = res
 88.1747 +            try:
 88.1748 +                self.bind(af, socktype, proto)
 88.1749 +            except socket.error:
 88.1750 +                if self.socket:
 88.1751 +                    self.socket.close()
 88.1752 +                self.socket = None
 88.1753 +                continue
 88.1754 +            break
 88.1755 +        if not self.socket:
 88.1756 +            raise socket.error(msg)
 88.1757 +        
 88.1758 +        # Timeout so KeyboardInterrupt can be caught on Win32
 88.1759 +        self.socket.settimeout(1)
 88.1760 +        self.socket.listen(self.request_queue_size)
 88.1761 +        
 88.1762 +        # Create worker threads
 88.1763 +        self.requests.start()
 88.1764 +        
 88.1765 +        self.ready = True
 88.1766 +        self._start_time = time.time()
 88.1767 +        while self.ready:
 88.1768 +            self.tick()
 88.1769 +            if self.interrupt:
 88.1770 +                while self.interrupt is True:
 88.1771 +                    # Wait for self.stop() to complete. See _set_interrupt.
 88.1772 +                    time.sleep(0.1)
 88.1773 +                if self.interrupt:
 88.1774 +                    raise self.interrupt
 88.1775 +    
 88.1776 +    def bind(self, family, type, proto=0):
 88.1777 +        """Create (or recreate) the actual socket object."""
 88.1778 +        self.socket = socket.socket(family, type, proto)
 88.1779 +        prevent_socket_inheritance(self.socket)
 88.1780 +        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 88.1781 +        if self.nodelay and not isinstance(self.bind_addr, str):
 88.1782 +            self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)
 88.1783 +        
 88.1784 +        if self.ssl_adapter is not None:
 88.1785 +            self.socket = self.ssl_adapter.bind(self.socket)
 88.1786 +        
 88.1787 +        # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
 88.1788 +        # activate dual-stack. See http://www.cherrypy.org/ticket/871.
 88.1789 +        if (hasattr(socket, 'AF_INET6') and family == socket.AF_INET6
 88.1790 +            and self.bind_addr[0] in ('::', '::0', '::0.0.0.0')):
 88.1791 +            try:
 88.1792 +                self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
 88.1793 +            except (AttributeError, socket.error):
 88.1794 +                # Apparently, the socket option is not available in
 88.1795 +                # this machine's TCP stack
 88.1796 +                pass
 88.1797 +        
 88.1798 +        self.socket.bind(self.bind_addr)
 88.1799 +    
 88.1800 +    def tick(self):
 88.1801 +        """Accept a new connection and put it on the Queue."""
 88.1802 +        try:
 88.1803 +            s, addr = self.socket.accept()
 88.1804 +            if self.stats['Enabled']:
 88.1805 +                self.stats['Accepts'] += 1
 88.1806 +            if not self.ready:
 88.1807 +                return
 88.1808 +            
 88.1809 +            prevent_socket_inheritance(s)
 88.1810 +            if hasattr(s, 'settimeout'):
 88.1811 +                s.settimeout(self.timeout)
 88.1812 +            
 88.1813 +            makefile = CP_fileobject
 88.1814 +            ssl_env = {}
 88.1815 +            # if ssl cert and key are set, we try to be a secure HTTP server
 88.1816 +            if self.ssl_adapter is not None:
 88.1817 +                try:
 88.1818 +                    s, ssl_env = self.ssl_adapter.wrap(s)
 88.1819 +                except NoSSLError:
 88.1820 +                    msg = ("The client sent a plain HTTP request, but "
 88.1821 +                           "this server only speaks HTTPS on this port.")
 88.1822 +                    buf = ["%s 400 Bad Request\r\n" % self.protocol,
 88.1823 +                           "Content-Length: %s\r\n" % len(msg),
 88.1824 +                           "Content-Type: text/plain\r\n\r\n",
 88.1825 +                           msg]
 88.1826 +                    
 88.1827 +                    wfile = CP_fileobject(s, "wb", DEFAULT_BUFFER_SIZE)
 88.1828 +                    try:
 88.1829 +                        wfile.sendall("".join(buf))
 88.1830 +                    except socket.error, x:
 88.1831 +                        if x.args[0] not in socket_errors_to_ignore:
 88.1832 +                            raise
 88.1833 +                    return
 88.1834 +                if not s:
 88.1835 +                    return
 88.1836 +                makefile = self.ssl_adapter.makefile
 88.1837 +                # Re-apply our timeout since we may have a new socket object
 88.1838 +                if hasattr(s, 'settimeout'):
 88.1839 +                    s.settimeout(self.timeout)
 88.1840 +            
 88.1841 +            conn = self.ConnectionClass(self, s, makefile)
 88.1842 +            
 88.1843 +            if not isinstance(self.bind_addr, basestring):
 88.1844 +                # optional values
 88.1845 +                # Until we do DNS lookups, omit REMOTE_HOST
 88.1846 +                if addr is None: # sometimes this can happen
 88.1847 +                    # figure out if AF_INET or AF_INET6.
 88.1848 +                    if len(s.getsockname()) == 2:
 88.1849 +                        # AF_INET
 88.1850 +                        addr = ('0.0.0.0', 0)
 88.1851 +                    else:
 88.1852 +                        # AF_INET6
 88.1853 +                        addr = ('::', 0)
 88.1854 +                conn.remote_addr = addr[0]
 88.1855 +                conn.remote_port = addr[1]
 88.1856 +            
 88.1857 +            conn.ssl_env = ssl_env
 88.1858 +            
 88.1859 +            self.requests.put(conn)
 88.1860 +        except socket.timeout:
 88.1861 +            # The only reason for the timeout in start() is so we can
 88.1862 +            # notice keyboard interrupts on Win32, which don't interrupt
 88.1863 +            # accept() by default
 88.1864 +            return
 88.1865 +        except socket.error, x:
 88.1866 +            if self.stats['Enabled']:
 88.1867 +                self.stats['Socket Errors'] += 1
 88.1868 +            if x.args[0] in socket_error_eintr:
 88.1869 +                # I *think* this is right. EINTR should occur when a signal
 88.1870 +                # is received during the accept() call; all docs say retry
 88.1871 +                # the call, and I *think* I'm reading it right that Python
 88.1872 +                # will then go ahead and poll for and handle the signal
 88.1873 +                # elsewhere. See http://www.cherrypy.org/ticket/707.
 88.1874 +                return
 88.1875 +            if x.args[0] in socket_errors_nonblocking:
 88.1876 +                # Just try again. See http://www.cherrypy.org/ticket/479.
 88.1877 +                return
 88.1878 +            if x.args[0] in socket_errors_to_ignore:
 88.1879 +                # Our socket was closed.
 88.1880 +                # See http://www.cherrypy.org/ticket/686.
 88.1881 +                return
 88.1882 +            raise
 88.1883 +    
 88.1884 +    def _get_interrupt(self):
 88.1885 +        return self._interrupt
 88.1886 +    def _set_interrupt(self, interrupt):
 88.1887 +        self._interrupt = True
 88.1888 +        self.stop()
 88.1889 +        self._interrupt = interrupt
 88.1890 +    interrupt = property(_get_interrupt, _set_interrupt,
 88.1891 +                         doc="Set this to an Exception instance to "
 88.1892 +                             "interrupt the server.")
 88.1893 +    
 88.1894 +    def stop(self):
 88.1895 +        """Gracefully shutdown a server that is serving forever."""
 88.1896 +        self.ready = False
 88.1897 +        if self._start_time is not None:
 88.1898 +            self._run_time += (time.time() - self._start_time)
 88.1899 +        self._start_time = None
 88.1900 +        
 88.1901 +        sock = getattr(self, "socket", None)
 88.1902 +        if sock:
 88.1903 +            if not isinstance(self.bind_addr, basestring):
 88.1904 +                # Touch our own socket to make accept() return immediately.
 88.1905 +                try:
 88.1906 +                    host, port = sock.getsockname()[:2]
 88.1907 +                except socket.error, x:
 88.1908 +                    if x.args[0] not in socket_errors_to_ignore:
 88.1909 +                        # Changed to use error code and not message
 88.1910 +                        # See http://www.cherrypy.org/ticket/860.
 88.1911 +                        raise
 88.1912 +                else:
 88.1913 +                    # Note that we're explicitly NOT using AI_PASSIVE,
 88.1914 +                    # here, because we want an actual IP to touch.
 88.1915 +                    # localhost won't work if we've bound to a public IP,
 88.1916 +                    # but it will if we bound to '0.0.0.0' (INADDR_ANY).
 88.1917 +                    for res in socket.getaddrinfo(host, port, socket.AF_UNSPEC,
 88.1918 +                                                  socket.SOCK_STREAM):
 88.1919 +                        af, socktype, proto, canonname, sa = res
 88.1920 +                        s = None
 88.1921 +                        try:
 88.1922 +                            s = socket.socket(af, socktype, proto)
 88.1923 +                            # See http://groups.google.com/group/cherrypy-users/
 88.1924 +                            #        browse_frm/thread/bbfe5eb39c904fe0
 88.1925 +                            s.settimeout(1.0)
 88.1926 +                            s.connect((host, port))
 88.1927 +                            s.close()
 88.1928 +                        except socket.error:
 88.1929 +                            if s:
 88.1930 +                                s.close()
 88.1931 +            if hasattr(sock, "close"):
 88.1932 +                sock.close()
 88.1933 +            self.socket = None
 88.1934 +        
 88.1935 +        self.requests.stop(self.shutdown_timeout)
 88.1936 +
 88.1937 +
 88.1938 +class Gateway(object):
 88.1939 +    
 88.1940 +    def __init__(self, req):
 88.1941 +        self.req = req
 88.1942 +    
 88.1943 +    def respond(self):
 88.1944 +        raise NotImplemented
 88.1945 +
 88.1946 +
 88.1947 +# These may either be wsgiserver.SSLAdapter subclasses or the string names
 88.1948 +# of such classes (in which case they will be lazily loaded).
 88.1949 +ssl_adapters = {
 88.1950 +    'builtin': 'cherrypy.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
 88.1951 +    'pyopenssl': 'cherrypy.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
 88.1952 +    }
 88.1953 +
 88.1954 +def get_ssl_adapter_class(name='pyopenssl'):
 88.1955 +    adapter = ssl_adapters[name.lower()]
 88.1956 +    if isinstance(adapter, basestring):
 88.1957 +        last_dot = adapter.rfind(".")
 88.1958 +        attr_name = adapter[last_dot + 1:]
 88.1959 +        mod_path = adapter[:last_dot]
 88.1960 +        
 88.1961 +        try:
 88.1962 +            mod = sys.modules[mod_path]
 88.1963 +            if mod is None:
 88.1964 +                raise KeyError()
 88.1965 +        except KeyError:
 88.1966 +            # The last [''] is important.
 88.1967 +            mod = __import__(mod_path, globals(), locals(), [''])
 88.1968 +        
 88.1969 +        # Let an AttributeError propagate outward.
 88.1970 +        try:
 88.1971 +            adapter = getattr(mod, attr_name)
 88.1972 +        except AttributeError:
 88.1973 +            raise AttributeError("'%s' object has no attribute '%s'"
 88.1974 +                                 % (mod_path, attr_name))
 88.1975 +    
 88.1976 +    return adapter
 88.1977 +
 88.1978 +# -------------------------------- WSGI Stuff -------------------------------- #
 88.1979 +
 88.1980 +
 88.1981 +class CherryPyWSGIServer(HTTPServer):
 88.1982 +    
 88.1983 +    wsgi_version = (1, 0)
 88.1984 +    
 88.1985 +    def __init__(self, bind_addr, wsgi_app, numthreads=10, server_name=None,
 88.1986 +                 max=-1, request_queue_size=5, timeout=10, shutdown_timeout=5):
 88.1987 +        self.requests = ThreadPool(self, min=numthreads or 1, max=max)
 88.1988 +        self.wsgi_app = wsgi_app
 88.1989 +        self.gateway = wsgi_gateways[self.wsgi_version]
 88.1990 +        
 88.1991 +        self.bind_addr = bind_addr
 88.1992 +        if not server_name:
 88.1993 +            server_name = socket.gethostname()
 88.1994 +        self.server_name = server_name
 88.1995 +        self.request_queue_size = request_queue_size
 88.1996 +        
 88.1997 +        self.timeout = timeout
 88.1998 +        self.shutdown_timeout = shutdown_timeout
 88.1999 +        self.clear_stats()
 88.2000 +    
 88.2001 +    def _get_numthreads(self):
 88.2002 +        return self.requests.min
 88.2003 +    def _set_numthreads(self, value):
 88.2004 +        self.requests.min = value
 88.2005 +    numthreads = property(_get_numthreads, _set_numthreads)
 88.2006 +
 88.2007 +
 88.2008 +class WSGIGateway(Gateway):
 88.2009 +    
 88.2010 +    def __init__(self, req):
 88.2011 +        self.req = req
 88.2012 +        self.started_response = False
 88.2013 +        self.env = self.get_environ()
 88.2014 +        self.remaining_bytes_out = None
 88.2015 +    
 88.2016 +    def get_environ(self):
 88.2017 +        """Return a new environ dict targeting the given wsgi.version"""
 88.2018 +        raise NotImplemented
 88.2019 +    
 88.2020 +    def respond(self):
 88.2021 +        response = self.req.server.wsgi_app(self.env, self.start_response)
 88.2022 +        try:
 88.2023 +            for chunk in response:
 88.2024 +                # "The start_response callable must not actually transmit
 88.2025 +                # the response headers. Instead, it must store them for the
 88.2026 +                # server or gateway to transmit only after the first
 88.2027 +                # iteration of the application return value that yields
 88.2028 +                # a NON-EMPTY string, or upon the application's first
 88.2029 +                # invocation of the write() callable." (PEP 333)
 88.2030 +                if chunk:
 88.2031 +                    if isinstance(chunk, unicode):
 88.2032 +                        chunk = chunk.encode('ISO-8859-1')
 88.2033 +                    self.write(chunk)
 88.2034 +        finally:
 88.2035 +            if hasattr(response, "close"):
 88.2036 +                response.close()
 88.2037 +    
 88.2038 +    def start_response(self, status, headers, exc_info = None):
 88.2039 +        """WSGI callable to begin the HTTP response."""
 88.2040 +        # "The application may call start_response more than once,
 88.2041 +        # if and only if the exc_info argument is provided."
 88.2042 +        if self.started_response and not exc_info:
 88.2043 +            raise AssertionError("WSGI start_response called a second "
 88.2044 +                                 "time with no exc_info.")
 88.2045 +        self.started_response = True
 88.2046 +        
 88.2047 +        # "if exc_info is provided, and the HTTP headers have already been
 88.2048 +        # sent, start_response must raise an error, and should raise the
 88.2049 +        # exc_info tuple."
 88.2050 +        if self.req.sent_headers:
 88.2051 +            try:
 88.2052 +                raise exc_info[0], exc_info[1], exc_info[2]
 88.2053 +            finally:
 88.2054 +                exc_info = None
 88.2055 +        
 88.2056 +        self.req.status = status
 88.2057 +        for k, v in headers:
 88.2058 +            if not isinstance(k, str):
 88.2059 +                raise TypeError("WSGI response header key %r is not a byte string." % k)
 88.2060 +            if not isinstance(v, str):
 88.2061 +                raise TypeError("WSGI response header value %r is not a byte string." % v)
 88.2062 +            if k.lower() == 'content-length':
 88.2063 +                self.remaining_bytes_out = int(v)
 88.2064 +        self.req.outheaders.extend(headers)
 88.2065 +        
 88.2066 +        return self.write
 88.2067 +    
 88.2068 +    def write(self, chunk):
 88.2069 +        """WSGI callable to write unbuffered data to the client.
 88.2070 +        
 88.2071 +        This method is also used internally by start_response (to write
 88.2072 +        data from the iterable returned by the WSGI application).
 88.2073 +        """
 88.2074 +        if not self.started_response:
 88.2075 +            raise AssertionError("WSGI write called before start_response.")
 88.2076 +        
 88.2077 +        chunklen = len(chunk)
 88.2078 +        rbo = self.remaining_bytes_out
 88.2079 +        if rbo is not None and chunklen > rbo:
 88.2080 +            if not self.req.sent_headers:
 88.2081 +                # Whew. We can send a 500 to the client.
 88.2082 +                self.req.simple_response("500 Internal Server Error",
 88.2083 +                    "The requested resource returned more bytes than the "
 88.2084 +                    "declared Content-Length.")
 88.2085 +            else:
 88.2086 +                # Dang. We have probably already sent data. Truncate the chunk
 88.2087 +                # to fit (so the client doesn't hang) and raise an error later.
 88.2088 +                chunk = chunk[:rbo]
 88.2089 +        
 88.2090 +        if not self.req.sent_headers:
 88.2091 +            self.req.sent_headers = True
 88.2092 +            self.req.send_headers()
 88.2093 +        
 88.2094 +        self.req.write(chunk)
 88.2095 +        
 88.2096 +        if rbo is not None:
 88.2097 +            rbo -= chunklen
 88.2098 +            if rbo < 0:
 88.2099 +                raise ValueError(
 88.2100 +                    "Response body exceeds the declared Content-Length.")
 88.2101 +
 88.2102 +
 88.2103 +class WSGIGateway_10(WSGIGateway):
 88.2104 +    
 88.2105 +    def get_environ(self):
 88.2106 +        """Return a new environ dict targeting the given wsgi.version"""
 88.2107 +        req = self.req
 88.2108 +        env = {
 88.2109 +            # set a non-standard environ entry so the WSGI app can know what
 88.2110 +            # the *real* server protocol is (and what features to support).
 88.2111 +            # See http://www.faqs.org/rfcs/rfc2145.html.
 88.2112 +            'ACTUAL_SERVER_PROTOCOL': req.server.protocol,
 88.2113 +            'PATH_INFO': req.path,
 88.2114 +            'QUERY_STRING': req.qs,
 88.2115 +            'REMOTE_ADDR': req.conn.remote_addr or '',
 88.2116 +            'REMOTE_PORT': str(req.conn.remote_port or ''),
 88.2117 +            'REQUEST_METHOD': req.method,
 88.2118 +            'REQUEST_URI': req.uri,
 88.2119 +            'SCRIPT_NAME': '',
 88.2120 +            'SERVER_NAME': req.server.server_name,
 88.2121 +            # Bah. "SERVER_PROTOCOL" is actually the REQUEST protocol.
 88.2122 +            'SERVER_PROTOCOL': req.request_protocol,
 88.2123 +            'SERVER_SOFTWARE': req.server.software,
 88.2124 +            'wsgi.errors': sys.stderr,
 88.2125 +            'wsgi.input': req.rfile,
 88.2126 +            'wsgi.multiprocess': False,
 88.2127 +            'wsgi.multithread': True,
 88.2128 +            'wsgi.run_once': False,
 88.2129 +            'wsgi.url_scheme': req.scheme,
 88.2130 +            'wsgi.version': (1, 0),
 88.2131 +            }
 88.2132 +        
 88.2133 +        if isinstance(req.server.bind_addr, basestring):
 88.2134 +            # AF_UNIX. This isn't really allowed by WSGI, which doesn't
 88.2135 +            # address unix domain sockets. But it's better than nothing.
 88.2136 +            env["SERVER_PORT"] = ""
 88.2137 +        else:
 88.2138 +            env["SERVER_PORT"] = str(req.server.bind_addr[1])
 88.2139 +        
 88.2140 +        # Request headers
 88.2141 +        for k, v in req.inheaders.iteritems():
 88.2142 +            env["HTTP_" + k.upper().replace("-", "_")] = v
 88.2143 +        
 88.2144 +        # CONTENT_TYPE/CONTENT_LENGTH
 88.2145 +        ct = env.pop("HTTP_CONTENT_TYPE", None)
 88.2146 +        if ct is not None:
 88.2147 +            env["CONTENT_TYPE"] = ct
 88.2148 +        cl = env.pop("HTTP_CONTENT_LENGTH", None)
 88.2149 +        if cl is not None:
 88.2150 +            env["CONTENT_LENGTH"] = cl
 88.2151 +        
 88.2152 +        if req.conn.ssl_env:
 88.2153 +            env.update(req.conn.ssl_env)
 88.2154 +        
 88.2155 +        return env
 88.2156 +
 88.2157 +
 88.2158 +class WSGIGateway_u0(WSGIGateway_10):
 88.2159 +    
 88.2160 +    def get_environ(self):
 88.2161 +        """Return a new environ dict targeting the given wsgi.version"""
 88.2162 +        req = self.req
 88.2163 +        env_10 = WSGIGateway_10.get_environ(self)
 88.2164 +        env = dict([(k.decode('ISO-8859-1'), v) for k, v in env_10.iteritems()])
 88.2165 +        env[u'wsgi.version'] = ('u', 0)
 88.2166 +        
 88.2167 +        # Request-URI
 88.2168 +        env.setdefault(u'wsgi.url_encoding', u'utf-8')
 88.2169 +        try:
 88.2170 +            for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
 88.2171 +                env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
 88.2172 +        except UnicodeDecodeError:
 88.2173 +            # Fall back to latin 1 so apps can transcode if needed.
 88.2174 +            env[u'wsgi.url_encoding'] = u'ISO-8859-1'
 88.2175 +            for key in [u"PATH_INFO", u"SCRIPT_NAME", u"QUERY_STRING"]:
 88.2176 +                env[key] = env_10[str(key)].decode(env[u'wsgi.url_encoding'])
 88.2177 +        
 88.2178 +        for k, v in sorted(env.items()):
 88.2179 +            if isinstance(v, str) and k not in ('REQUEST_URI', 'wsgi.input'):
 88.2180 +                env[k] = v.decode('ISO-8859-1')
 88.2181 +        
 88.2182 +        return env
 88.2183 +
 88.2184 +wsgi_gateways = {
 88.2185 +    (1, 0): WSGIGateway_10,
 88.2186 +    ('u', 0): WSGIGateway_u0,
 88.2187 +}
 88.2188 +
 88.2189 +class WSGIPathInfoDispatcher(object):
 88.2190 +    """A WSGI dispatcher for dispatch based on the PATH_INFO.
 88.2191 +    
 88.2192 +    apps: a dict or list of (path_prefix, app) pairs.
 88.2193 +    """
 88.2194 +    
 88.2195 +    def __init__(self, apps):
 88.2196 +        try:
 88.2197 +            apps = apps.items()
 88.2198 +        except AttributeError:
 88.2199 +            pass
 88.2200 +        
 88.2201 +        # Sort the apps by len(path), descending
 88.2202 +        apps.sort(cmp=lambda x,y: cmp(len(x[0]), len(y[0])))
 88.2203 +        apps.reverse()
 88.2204 +        
 88.2205 +        # The path_prefix strings must start, but not end, with a slash.
 88.2206 +        # Use "" instead of "/".
 88.2207 +        self.apps = [(p.rstrip("/"), a) for p, a in apps]
 88.2208 +    
 88.2209 +    def __call__(self, environ, start_response):
 88.2210 +        path = environ["PATH_INFO"] or "/"
 88.2211 +        for p, app in self.apps:
 88.2212 +            # The apps list should be sorted by length, descending.
 88.2213 +            if path.startswith(p + "/") or path == p:
 88.2214 +                environ = environ.copy()
 88.2215 +                environ["SCRIPT_NAME"] = environ["SCRIPT_NAME"] + p
 88.2216 +                environ["PATH_INFO"] = path[len(p):]
 88.2217 +                return app(environ, start_response)
 88.2218 +        
 88.2219 +        start_response('404 Not Found', [('Content-Type', 'text/plain'),
 88.2220 +                                         ('Content-Length', '0')])
 88.2221 +        return ['']
 88.2222 +
    89.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    89.2 +++ b/OpenSecurity/install/web.py-0.37/web/wsgiserver/ssl_builtin.py	Mon Dec 02 14:02:05 2013 +0100
    89.3 @@ -0,0 +1,72 @@
    89.4 +"""A library for integrating Python's builtin ``ssl`` library with CherryPy.
    89.5 +
    89.6 +The ssl module must be importable for SSL functionality.
    89.7 +
    89.8 +To use this module, set ``CherryPyWSGIServer.ssl_adapter`` to an instance of
    89.9 +``BuiltinSSLAdapter``.
   89.10 +"""
   89.11 +
   89.12 +try:
   89.13 +    import ssl
   89.14 +except ImportError:
   89.15 +    ssl = None
   89.16 +
   89.17 +from cherrypy import wsgiserver
   89.18 +
   89.19 +
   89.20 +class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
   89.21 +    """A wrapper for integrating Python's builtin ssl module with CherryPy."""
   89.22 +    
   89.23 +    certificate = None
   89.24 +    """The filename of the server SSL certificate."""
   89.25 +    
   89.26 +    private_key = None
   89.27 +    """The filename of the server's private key file."""
   89.28 +    
   89.29 +    def __init__(self, certificate, private_key, certificate_chain=None):
   89.30 +        if ssl is None:
   89.31 +            raise ImportError("You must install the ssl module to use HTTPS.")
   89.32 +        self.certificate = certificate
   89.33 +        self.private_key = private_key
   89.34 +        self.certificate_chain = certificate_chain
   89.35 +    
   89.36 +    def bind(self, sock):
   89.37 +        """Wrap and return the given socket."""
   89.38 +        return sock
   89.39 +    
   89.40 +    def wrap(self, sock):
   89.41 +        """Wrap and return the given socket, plus WSGI environ entries."""
   89.42 +        try:
   89.43 +            s = ssl.wrap_socket(sock, do_handshake_on_connect=True,
   89.44 +                    server_side=True, certfile=self.certificate,
   89.45 +                    keyfile=self.private_key, ssl_version=ssl.PROTOCOL_SSLv23)
   89.46 +        except ssl.SSLError, e:
   89.47 +            if e.errno == ssl.SSL_ERROR_EOF:
   89.48 +                # This is almost certainly due to the cherrypy engine
   89.49 +                # 'pinging' the socket to assert it's connectable;
   89.50 +                # the 'ping' isn't SSL.
   89.51 +                return None, {}
   89.52 +            elif e.errno == ssl.SSL_ERROR_SSL:
   89.53 +                if e.args[1].endswith('http request'):
   89.54 +                    # The client is speaking HTTP to an HTTPS server.
   89.55 +                    raise wsgiserver.NoSSLError
   89.56 +            raise
   89.57 +        return s, self.get_environ(s)
   89.58 +    
   89.59 +    # TODO: fill this out more with mod ssl env
   89.60 +    def get_environ(self, sock):
   89.61 +        """Create WSGI environ entries to be merged into each request."""
   89.62 +        cipher = sock.cipher()
   89.63 +        ssl_environ = {
   89.64 +            "wsgi.url_scheme": "https",
   89.65 +            "HTTPS": "on",
   89.66 +            'SSL_PROTOCOL': cipher[1],
   89.67 +            'SSL_CIPHER': cipher[0]
   89.68 +##            SSL_VERSION_INTERFACE 	string 	The mod_ssl program version
   89.69 +##            SSL_VERSION_LIBRARY 	string 	The OpenSSL program version
   89.70 +            }
   89.71 +        return ssl_environ
   89.72 +    
   89.73 +    def makefile(self, sock, mode='r', bufsize=-1):
   89.74 +        return wsgiserver.CP_fileobject(sock, mode, bufsize)
   89.75 +
    90.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    90.2 +++ b/OpenSecurity/install/web.py-0.37/web/wsgiserver/ssl_pyopenssl.py	Mon Dec 02 14:02:05 2013 +0100
    90.3 @@ -0,0 +1,256 @@
    90.4 +"""A library for integrating pyOpenSSL with CherryPy.
    90.5 +
    90.6 +The OpenSSL module must be importable for SSL functionality.
    90.7 +You can obtain it from http://pyopenssl.sourceforge.net/
    90.8 +
    90.9 +To use this module, set CherryPyWSGIServer.ssl_adapter to an instance of
   90.10 +SSLAdapter. There are two ways to use SSL:
   90.11 +
   90.12 +Method One
   90.13 +----------
   90.14 +
   90.15 + * ``ssl_adapter.context``: an instance of SSL.Context.
   90.16 +
   90.17 +If this is not None, it is assumed to be an SSL.Context instance,
   90.18 +and will be passed to SSL.Connection on bind(). The developer is
   90.19 +responsible for forming a valid Context object. This approach is
   90.20 +to be preferred for more flexibility, e.g. if the cert and key are
   90.21 +streams instead of files, or need decryption, or SSL.SSLv3_METHOD
   90.22 +is desired instead of the default SSL.SSLv23_METHOD, etc. Consult
   90.23 +the pyOpenSSL documentation for complete options.
   90.24 +
   90.25 +Method Two (shortcut)
   90.26 +---------------------
   90.27 +
   90.28 + * ``ssl_adapter.certificate``: the filename of the server SSL certificate.
   90.29 + * ``ssl_adapter.private_key``: the filename of the server's private key file.
   90.30 +
   90.31 +Both are None by default. If ssl_adapter.context is None, but .private_key
   90.32 +and .certificate are both given and valid, they will be read, and the
   90.33 +context will be automatically created from them.
   90.34 +"""
   90.35 +
   90.36 +import socket
   90.37 +import threading
   90.38 +import time
   90.39 +
   90.40 +from cherrypy import wsgiserver
   90.41 +
   90.42 +try:
   90.43 +    from OpenSSL import SSL
   90.44 +    from OpenSSL import crypto
   90.45 +except ImportError:
   90.46 +    SSL = None
   90.47 +
   90.48 +
   90.49 +class SSL_fileobject(wsgiserver.CP_fileobject):
   90.50 +    """SSL file object attached to a socket object."""
   90.51 +    
   90.52 +    ssl_timeout = 3
   90.53 +    ssl_retry = .01
   90.54 +    
   90.55 +    def _safe_call(self, is_reader, call, *args, **kwargs):
   90.56 +        """Wrap the given call with SSL error-trapping.
   90.57 +        
   90.58 +        is_reader: if False EOF errors will be raised. If True, EOF errors
   90.59 +        will return "" (to emulate normal sockets).
   90.60 +        """
   90.61 +        start = time.time()
   90.62 +        while True:
   90.63 +            try:
   90.64 +                return call(*args, **kwargs)
   90.65 +            except SSL.WantReadError:
   90.66 +                # Sleep and try again. This is dangerous, because it means
   90.67 +                # the rest of the stack has no way of differentiating
   90.68 +                # between a "new handshake" error and "client dropped".
   90.69 +                # Note this isn't an endless loop: there's a timeout below.
   90.70 +                time.sleep(self.ssl_retry)
   90.71 +            except SSL.WantWriteError:
   90.72 +                time.sleep(self.ssl_retry)
   90.73 +            except SSL.SysCallError, e:
   90.74 +                if is_reader and e.args == (-1, 'Unexpected EOF'):
   90.75 +                    return ""
   90.76 +                
   90.77 +                errnum = e.args[0]
   90.78 +                if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
   90.79 +                    return ""
   90.80 +                raise socket.error(errnum)
   90.81 +            except SSL.Error, e:
   90.82 +                if is_reader and e.args == (-1, 'Unexpected EOF'):
   90.83 +                    return ""
   90.84 +                
   90.85 +                thirdarg = None
   90.86 +                try:
   90.87 +                    thirdarg = e.args[0][0][2]
   90.88 +                except IndexError:
   90.89 +                    pass
   90.90 +                
   90.91 +                if thirdarg == 'http request':
   90.92 +                    # The client is talking HTTP to an HTTPS server.
   90.93 +                    raise wsgiserver.NoSSLError()
   90.94 +                
   90.95 +                raise wsgiserver.FatalSSLAlert(*e.args)
   90.96 +            except:
   90.97 +                raise
   90.98 +            
   90.99 +            if time.time() - start > self.ssl_timeout:
  90.100 +                raise socket.timeout("timed out")
  90.101 +    
  90.102 +    def recv(self, *args, **kwargs):
  90.103 +        buf = []
  90.104 +        r = super(SSL_fileobject, self).recv
  90.105 +        while True:
  90.106 +            data = self._safe_call(True, r, *args, **kwargs)
  90.107 +            buf.append(data)
  90.108 +            p = self._sock.pending()
  90.109 +            if not p:
  90.110 +                return "".join(buf)
  90.111 +    
  90.112 +    def sendall(self, *args, **kwargs):
  90.113 +        return self._safe_call(False, super(SSL_fileobject, self).sendall,
  90.114 +                               *args, **kwargs)
  90.115 +
  90.116 +    def send(self, *args, **kwargs):
  90.117 +        return self._safe_call(False, super(SSL_fileobject, self).send,
  90.118 +                               *args, **kwargs)
  90.119 +
  90.120 +
  90.121 +class SSLConnection:
  90.122 +    """A thread-safe wrapper for an SSL.Connection.
  90.123 +    
  90.124 +    ``*args``: the arguments to create the wrapped ``SSL.Connection(*args)``.
  90.125 +    """
  90.126 +    
  90.127 +    def __init__(self, *args):
  90.128 +        self._ssl_conn = SSL.Connection(*args)
  90.129 +        self._lock = threading.RLock()
  90.130 +    
  90.131 +    for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
  90.132 +              'renegotiate', 'bind', 'listen', 'connect', 'accept',
  90.133 +              'setblocking', 'fileno', 'close', 'get_cipher_list',
  90.134 +              'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
  90.135 +              'makefile', 'get_app_data', 'set_app_data', 'state_string',
  90.136 +              'sock_shutdown', 'get_peer_certificate', 'want_read',
  90.137 +              'want_write', 'set_connect_state', 'set_accept_state',
  90.138 +              'connect_ex', 'sendall', 'settimeout', 'gettimeout'):
  90.139 +        exec("""def %s(self, *args):
  90.140 +        self._lock.acquire()
  90.141 +        try:
  90.142 +            return self._ssl_conn.%s(*args)
  90.143 +        finally:
  90.144 +            self._lock.release()
  90.145 +""" % (f, f))
  90.146 +    
  90.147 +    def shutdown(self, *args):
  90.148 +        self._lock.acquire()
  90.149 +        try:
  90.150 +            # pyOpenSSL.socket.shutdown takes no args
  90.151 +            return self._ssl_conn.shutdown()
  90.152 +        finally:
  90.153 +            self._lock.release()
  90.154 +
  90.155 +
  90.156 +class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
  90.157 +    """A wrapper for integrating pyOpenSSL with CherryPy."""
  90.158 +    
  90.159 +    context = None
  90.160 +    """An instance of SSL.Context."""
  90.161 +    
  90.162 +    certificate = None
  90.163 +    """The filename of the server SSL certificate."""
  90.164 +    
  90.165 +    private_key = None
  90.166 +    """The filename of the server's private key file."""
  90.167 +    
  90.168 +    certificate_chain = None
  90.169 +    """Optional. The filename of CA's intermediate certificate bundle.
  90.170 +    
  90.171 +    This is needed for cheaper "chained root" SSL certificates, and should be
  90.172 +    left as None if not required."""
  90.173 +    
  90.174 +    def __init__(self, certificate, private_key, certificate_chain=None):
  90.175 +        if SSL is None:
  90.176 +            raise ImportError("You must install pyOpenSSL to use HTTPS.")
  90.177 +        
  90.178 +        self.context = None
  90.179 +        self.certificate = certificate
  90.180 +        self.private_key = private_key
  90.181 +        self.certificate_chain = certificate_chain
  90.182 +        self._environ = None
  90.183 +    
  90.184 +    def bind(self, sock):
  90.185 +        """Wrap and return the given socket."""
  90.186 +        if self.context is None:
  90.187 +            self.context = self.get_context()
  90.188 +        conn = SSLConnection(self.context, sock)
  90.189 +        self._environ = self.get_environ()
  90.190 +        return conn
  90.191 +    
  90.192 +    def wrap(self, sock):
  90.193 +        """Wrap and return the given socket, plus WSGI environ entries."""
  90.194 +        return sock, self._environ.copy()
  90.195 +    
  90.196 +    def get_context(self):
  90.197 +        """Return an SSL.Context from self attributes."""
  90.198 +        # See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
  90.199 +        c = SSL.Context(SSL.SSLv23_METHOD)
  90.200 +        c.use_privatekey_file(self.private_key)
  90.201 +        if self.certificate_chain:
  90.202 +            c.load_verify_locations(self.certificate_chain)
  90.203 +        c.use_certificate_file(self.certificate)
  90.204 +        return c
  90.205 +    
  90.206 +    def get_environ(self):
  90.207 +        """Return WSGI environ entries to be merged into each request."""
  90.208 +        ssl_environ = {
  90.209 +            "HTTPS": "on",
  90.210 +            # pyOpenSSL doesn't provide access to any of these AFAICT
  90.211 +##            'SSL_PROTOCOL': 'SSLv2',
  90.212 +##            SSL_CIPHER 	string 	The cipher specification name
  90.213 +##            SSL_VERSION_INTERFACE 	string 	The mod_ssl program version
  90.214 +##            SSL_VERSION_LIBRARY 	string 	The OpenSSL program version
  90.215 +            }
  90.216 +        
  90.217 +        if self.certificate:
  90.218 +            # Server certificate attributes
  90.219 +            cert = open(self.certificate, 'rb').read()
  90.220 +            cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
  90.221 +            ssl_environ.update({
  90.222 +                'SSL_SERVER_M_VERSION': cert.get_version(),
  90.223 +                'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
  90.224 +##                'SSL_SERVER_V_START': Validity of server's certificate (start time),
  90.225 +##                'SSL_SERVER_V_END': Validity of server's certificate (end time),
  90.226 +                })
  90.227 +            
  90.228 +            for prefix, dn in [("I", cert.get_issuer()),
  90.229 +                               ("S", cert.get_subject())]:
  90.230 +                # X509Name objects don't seem to have a way to get the
  90.231 +                # complete DN string. Use str() and slice it instead,
  90.232 +                # because str(dn) == "<X509Name object '/C=US/ST=...'>"
  90.233 +                dnstr = str(dn)[18:-2]
  90.234 +                
  90.235 +                wsgikey = 'SSL_SERVER_%s_DN' % prefix
  90.236 +                ssl_environ[wsgikey] = dnstr
  90.237 +                
  90.238 +                # The DN should be of the form: /k1=v1/k2=v2, but we must allow
  90.239 +                # for any value to contain slashes itself (in a URL).
  90.240 +                while dnstr:
  90.241 +                    pos = dnstr.rfind("=")
  90.242 +                    dnstr, value = dnstr[:pos], dnstr[pos + 1:]
  90.243 +                    pos = dnstr.rfind("/")
  90.244 +                    dnstr, key = dnstr[:pos], dnstr[pos + 1:]
  90.245 +                    if key and value:
  90.246 +                        wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
  90.247 +                        ssl_environ[wsgikey] = value
  90.248 +        
  90.249 +        return ssl_environ
  90.250 +    
  90.251 +    def makefile(self, sock, mode='r', bufsize=-1):
  90.252 +        if SSL and isinstance(sock, SSL.ConnectionType):
  90.253 +            timeout = sock.gettimeout()
  90.254 +            f = SSL_fileobject(sock, mode, bufsize)
  90.255 +            f.ssl_timeout = timeout
  90.256 +            return f
  90.257 +        else:
  90.258 +            return wsgiserver.CP_fileobject(sock, mode, bufsize)
  90.259 +
    91.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
    91.2 +++ b/OpenSecurity/server/opensecurityd.py	Mon Dec 02 14:02:05 2013 +0100
    91.3 @@ -0,0 +1,178 @@
    91.4 +#!/bin/env python
    91.5 +# -*- coding: utf-8 -*-
    91.6 +
    91.7 +# ------------------------------------------------------------
    91.8 +# opensecurityd
    91.9 +# 
   91.10 +# the opensecurityd as RESTful server
   91.11 +#
   91.12 +# Autor: Oliver Maurhart, <oliver.maurhart@ait.ac.at>
   91.13 +#
   91.14 +# Copyright (C) 2013 AIT Austrian Institute of Technology
   91.15 +# AIT Austrian Institute of Technology GmbH
   91.16 +# Donau-City-Strasse 1 | 1220 Vienna | Austria
   91.17 +# http://www.ait.ac.at
   91.18 +#
   91.19 +# This program is free software; you can redistribute it and/or
   91.20 +# modify it under the terms of the GNU General Public License
   91.21 +# as published by the Free Software Foundation version 2.
   91.22 +# 
   91.23 +# This program is distributed in the hope that it will be useful,
   91.24 +# but WITHOUT ANY WARRANTY; without even the implied warranty of
   91.25 +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   91.26 +# GNU General Public License for more details.
   91.27 +# 
   91.28 +# You should have received a copy of the GNU General Public License
   91.29 +# along with this program; if not, write to the Free Software
   91.30 +# Foundation, Inc., 51 Franklin Street, Fifth Floor, 
   91.31 +# Boston, MA  02110-1301, USA.
   91.32 +# ------------------------------------------------------------
   91.33 +
   91.34 +
   91.35 +# ------------------------------------------------------------
   91.36 +# imports
   91.37 +
   91.38 +import os
   91.39 +import os.path
   91.40 +import subprocess
   91.41 +import sys
   91.42 +import web
   91.43 +
   91.44 +# local
   91.45 +from environment import Environment
   91.46 +
   91.47 +
   91.48 +# ------------------------------------------------------------
   91.49 +# const
   91.50 +
   91.51 +
   91.52 +__version__ = "0.1"
   91.53 +
   91.54 +
   91.55 +"""All the URLs we know mapping to class handler"""
   91.56 +opensecurity_urls = (
   91.57 +    '/application',             'os_application',
   91.58 +    '/device',                  'os_device',
   91.59 +    '/device/credentials',      'os_device_credentials',
   91.60 +    '/device/password',         'os_device_password',
   91.61 +    '/',                        'os_root'
   91.62 +)
   91.63 +
   91.64 +
   91.65 +# ------------------------------------------------------------
   91.66 +# code
   91.67 +
   91.68 +
   91.69 +class os_application:
   91.70 +    
   91.71 +    """OpenSecurity '/application' handler.
   91.72 +    
   91.73 +    This is called on GET /application?vm=VM-ID&app=APP-ID
   91.74 +    This tries to access the vm identified with the label VM-ID
   91.75 +    and launched the application identified APP-ID
   91.76 +    """
   91.77 +    
   91.78 +    def GET(self):
   91.79 +        
   91.80 +        # pick the arguments
   91.81 +        args = web.input()
   91.82 +        
   91.83 +        # we _need_ a vm
   91.84 +        if not "vm" in args:
   91.85 +            raise web.badrequest()
   91.86 +        
   91.87 +        # we _need_ a app
   91.88 +        if not "app" in args:
   91.89 +            raise web.badrequest()
   91.90 +        
   91.91 +        ## TODO: HARD CODED STUFF HERE! THIS SHOULD BE FLEXIBLE!
   91.92 +        ssh_private_key = os.path.join(Environment("opensecurity").data_path, 'share', '192.168.56.15.ppk')
   91.93 +        putty_session = '192.168.56.15'
   91.94 +        process_command = ['plink.exe', '-i', ssh_private_key, putty_session, args.app]
   91.95 +        si = subprocess.STARTUPINFO()
   91.96 +        si.dwFlags = subprocess.STARTF_USESHOWWINDOW
   91.97 +        si.wShowWindow = subprocess.SW_HIDE
   91.98 +        print('tyring to launch: ' + ' '.join(process_command))
   91.99 +        process = subprocess.Popen(process_command, shell = True)
  91.100 +        return 'launched: ' + ' '.join(process_command)
  91.101 +
  91.102 +
  91.103 +class os_device:
  91.104 +    
  91.105 +    """OpenSecurity '/device' handler"""
  91.106 +    
  91.107 +    def GET(self):
  91.108 +        return "os_device"
  91.109 +
  91.110 +
  91.111 +class os_device_credentials:
  91.112 +    
  91.113 +    """OpenSecurity '/device/credentials' handler.
  91.114 +    
  91.115 +    This is called on GET /device/credentials?id=DEVICE-ID.
  91.116 +    Ideally this should pop up a user dialog to insert his
  91.117 +    credentials based the DEVICE-ID
  91.118 +    """
  91.119 +    
  91.120 +    def GET(self):
  91.121 +        
  91.122 +        # pick the arguments
  91.123 +        args = web.input()
  91.124 +        
  91.125 +        # we _need_ a device id
  91.126 +        if not "id" in args:
  91.127 +            raise web.badrequest()
  91.128 +        
  91.129 +        # invoke the user dialog as a subprocess
  91.130 +        dlg_credentials_image = os.path.join(sys.path[0], 'opensecurity-dialog.py')
  91.131 +        process_command = [sys.executable, dlg_credentials_image, 'credentials', 'Please provide credentials for accessing \ndevice: "{0}".'.format(args.id)]
  91.132 +        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
  91.133 +        result = process.communicate()[0]
  91.134 +        if process.returncode != 0:
  91.135 +            return 'Credentials request has been aborted.'
  91.136 +        
  91.137 +        return result
  91.138 +
  91.139 +
  91.140 +class os_device_password:
  91.141 +    
  91.142 +    """OpenSecurity '/device/password' handler.
  91.143 +    
  91.144 +    This is called on GET /device/password?id=DEVICE-ID.
  91.145 +    Ideally this should pop up a user dialog to insert his
  91.146 +    password based the DEVICE-ID
  91.147 +    """
  91.148 +    
  91.149 +    def GET(self):
  91.150 +        
  91.151 +        # pick the arguments
  91.152 +        args = web.input()
  91.153 +        
  91.154 +        # we _need_ a device id
  91.155 +        if not "id" in args:
  91.156 +            raise web.badrequest()
  91.157 +            
  91.158 +        # invoke the user dialog as a subprocess
  91.159 +        dlg_credentials_image = os.path.join(sys.path[0], 'opensecurity-dialog.py')
  91.160 +        process_command = [sys.executable, dlg_credentials_image, 'password', 'Please provide a password for accessing \ndevice: "{0}".'.format(args.id)]
  91.161 +        process = subprocess.Popen(process_command, shell = False, stdout = subprocess.PIPE)
  91.162 +        result = process.communicate()[0]
  91.163 +        if process.returncode != 0:
  91.164 +            return 'Credentials request has been aborted.'
  91.165 +        
  91.166 +        return result
  91.167 +
  91.168 +
  91.169 +class os_root:
  91.170 +    
  91.171 +    """OpenSecurity '/' handler"""
  91.172 +    
  91.173 +    def GET(self):
  91.174 +        return "OpenSecurity-Server { \"version\": \"%s\" }" % __version__
  91.175 +
  91.176 +
  91.177 +# start
  91.178 +if __name__ == "__main__":
  91.179 +    server = web.application(opensecurity_urls, globals())
  91.180 +    server.run()
  91.181 +