mirror of https://github.com/noisymime/OSPi.git
Merge branch 'master' into remote_master
Conflicts: README.md ospi.py
This commit is contained in:
commit
9585087749
|
@ -1,6 +1,6 @@
|
|||
web/
|
||||
/data
|
||||
/.settings
|
||||
/data/programs.json
|
||||
/data/sd.json
|
||||
/data/snames.txt
|
||||
*.pyc
|
||||
.project
|
||||
.pydevproject
|
||||
/static/log/
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<projectDescription>
|
||||
<name>OSPi</name>
|
||||
<comment></comment>
|
||||
<projects>
|
||||
</projects>
|
||||
<buildSpec>
|
||||
<buildCommand>
|
||||
<name>org.python.pydev.PyDevBuilder</name>
|
||||
<arguments>
|
||||
</arguments>
|
||||
</buildCommand>
|
||||
</buildSpec>
|
||||
<natures>
|
||||
<nature>org.python.pydev.pythonNature</nature>
|
||||
</natures>
|
||||
</projectDescription>
|
|
@ -0,0 +1,10 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<?eclipse-pydev version="1.0"?>
|
||||
|
||||
<pydev_project>
|
||||
<pydev_pathproperty name="org.python.pydev.PROJECT_SOURCE_PATH">
|
||||
<path>/${PROJECT_DIR_NAME}</path>
|
||||
</pydev_pathproperty>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_VERSION">python 2.7</pydev_property>
|
||||
<pydev_property name="org.python.pydev.PYTHON_PROJECT_INTERPRETER">Default</pydev_property>
|
||||
</pydev_project>
|
|
@ -11,7 +11,14 @@ June 2013, http://rayshobby.net
|
|||
UPDATES
|
||||
===========
|
||||
***********
|
||||
|
||||
|
||||
October 16 2013
|
||||
--------------
|
||||
(Dan)<br/>
|
||||
Additions, bug fixes:<br/>
|
||||
1. Fixed a bug that would cause an error in program preview when a master was enabled.<br/>
|
||||
2. Changing to manual mode would clear rain delay setting, Setting rain delay in manual mode would switch to program mode - fixed.
|
||||
|
||||
October 11 2013
|
||||
--------------
|
||||
(Dan)<br/>
|
||||
|
|
|
@ -1,3 +1,3 @@
|
|||
<meta name=viewport content="width=640">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<link href="./static/images/icons/favicon.ico" rel="icon" type="image/x-icon"/>
|
||||
<meta name=viewport content="width=640">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<link href="./static/images/icons/favicon.ico" rel="icon" type="image/x-icon"/>
|
||||
|
|
14
ospi.py
14
ospi.py
|
@ -11,10 +11,10 @@ except ImportError:
|
|||
|
||||
#### Revision information ####
|
||||
gv.ver = 183
|
||||
gv.rev = 138
|
||||
gv.rev_date = '11/October/2013'
|
||||
gv.rev = 139
|
||||
gv.rev_date = '16/October/2013'
|
||||
|
||||
#### urls is a feature of web.py. When a GET request is recieved , the corrisponding class is executed.
|
||||
#### urls is a feature of web.py. When a GET request is received , the corresponding class is executed.
|
||||
urls = [
|
||||
'/', 'home',
|
||||
'/cv', 'change_values',
|
||||
|
@ -193,8 +193,8 @@ def stop_stations():
|
|||
return
|
||||
|
||||
def main_loop(): # Runs in a separate thread
|
||||
""" ***** Main algorithm.***** """
|
||||
print 'Starting main loop \n'
|
||||
""" ***** Main timing algorithm.***** """
|
||||
print 'Starting timing loop \n'
|
||||
last_min = 0
|
||||
while True: # infinite loop
|
||||
gv.now = time.time()+((gv.sd['tz']/4)-12)*3600 # Current time based on UTC time from the Pi adjusted by the Time Zone setting from options. updated once per second.
|
||||
|
@ -309,7 +309,7 @@ def main_loop(): # Runs in a separate thread
|
|||
gv.sd['rdst'] = 0 # Rain delay stop time
|
||||
jsave(gv.sd, 'sd')
|
||||
time.sleep(1)
|
||||
#### End of main loop ####
|
||||
#### End of timing loop ####
|
||||
|
||||
def data(dataf):
|
||||
"""Return contents of requested text file as string or create file if a missing config file."""
|
||||
|
@ -556,7 +556,7 @@ class change_values:
|
|||
gv.srvals = [0]*(gv.sd['nst']) # turn off all stations
|
||||
set_output()
|
||||
if qdict.has_key('mm') and qdict['mm'] == '0': clear_mm()
|
||||
if qdict.has_key('rd') and qdict['rd'] != '0':
|
||||
if qdict.has_key('rd') and qdict['rd'] != '0' and qdict['rd'] != '':
|
||||
gv.sd['rdst'] = (gv.now+(int(qdict['rd'])*3600))
|
||||
stop_onrain()
|
||||
elif qdict.has_key('rd') and qdict['rd'] == '0': gv.sd['rdst'] = 0
|
||||
|
|
340
ospi.sh
340
ospi.sh
|
@ -1,170 +1,170 @@
|
|||
#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: ospi
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: OpenSprinkler + Raspberry Pi
|
||||
# Description: OpenSprinkler + Raspberry Pi - Raspberry Pi with
|
||||
# OpenSprinkler Pi board from Ray's Hobby
|
||||
### END INIT INFO
|
||||
|
||||
#
|
||||
# To auto start on boot execute (once) as root
|
||||
#
|
||||
# update-rc.d ospi defaults
|
||||
#
|
||||
# To stop auto start on boot execute
|
||||
#
|
||||
# update-rc.d ospi remove
|
||||
#
|
||||
|
||||
# Author: Denny Fox <dennyf at dfox.us>
|
||||
#
|
||||
# Please remove the "Author" lines above and replace them
|
||||
# with your own name if you copy and modify this script.
|
||||
|
||||
# Do NOT "set -e"
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="OpenSprinkler Raspberry Pi"
|
||||
NAME=ospi.py
|
||||
DAEMON=/usr/bin/python
|
||||
DAEMON_ARGS="ospi.py"
|
||||
HOMEDIR=/home/pi/OSPi/ # Edit if different on your Raspberry Pi
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
|
||||
# and status_of_proc is working.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --chdir $HOMEDIR --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --chdir $HOMEDIR --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON -- \
|
||||
$DAEMON_ARGS \
|
||||
|| return 2
|
||||
# Add code here, if necessary, that waits for the process to be ready
|
||||
# to handle requests from services started subsequently which depend
|
||||
# on this one. As a last resort, sleep for some time.
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
# If the above conditions are not satisfied then add some other code
|
||||
# that waits for the process to drop all resources that could be
|
||||
# needed by services started subsequently. A last resort is to
|
||||
# sleep for some time.
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGHUP to the daemon/service
|
||||
#
|
||||
do_reload() {
|
||||
#
|
||||
# If the daemon can reload its configuration without
|
||||
# restarting (for example, when it is sent a SIGHUP),
|
||||
# then implement that here.
|
||||
#
|
||||
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
#reload|force-reload)
|
||||
#
|
||||
# If do_reload() is not implemented then leave this commented out
|
||||
# and leave 'force-reload' as an alias for 'restart'.
|
||||
#
|
||||
#log_daemon_msg "Reloading $DESC" "$NAME"
|
||||
#do_reload
|
||||
#log_end_msg $?
|
||||
#;;
|
||||
restart|force-reload)
|
||||
#
|
||||
# If the "reload" option is implemented then remove the
|
||||
# 'force-reload' alias
|
||||
#
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
:
|
||||
#! /bin/sh
|
||||
### BEGIN INIT INFO
|
||||
# Provides: ospi
|
||||
# Required-Start: $remote_fs $syslog
|
||||
# Required-Stop: $remote_fs $syslog
|
||||
# Default-Start: 2 3 4 5
|
||||
# Default-Stop: 0 1 6
|
||||
# Short-Description: OpenSprinkler + Raspberry Pi
|
||||
# Description: OpenSprinkler + Raspberry Pi - Raspberry Pi with
|
||||
# OpenSprinkler Pi board from Ray's Hobby
|
||||
### END INIT INFO
|
||||
|
||||
#
|
||||
# To auto start on boot execute (once) as root
|
||||
#
|
||||
# update-rc.d ospi defaults
|
||||
#
|
||||
# To stop auto start on boot execute
|
||||
#
|
||||
# update-rc.d ospi remove
|
||||
#
|
||||
|
||||
# Author: Denny Fox <dennyf at dfox.us>
|
||||
#
|
||||
# Please remove the "Author" lines above and replace them
|
||||
# with your own name if you copy and modify this script.
|
||||
|
||||
# Do NOT "set -e"
|
||||
|
||||
# PATH should only include /usr/* if it runs after the mountnfs.sh script
|
||||
PATH=/sbin:/usr/sbin:/bin:/usr/bin
|
||||
DESC="OpenSprinkler Raspberry Pi"
|
||||
NAME=ospi.py
|
||||
DAEMON=/usr/bin/python
|
||||
DAEMON_ARGS="ospi.py"
|
||||
HOMEDIR=/home/pi/OSPi/ # Edit if different on your Raspberry Pi
|
||||
PIDFILE=/var/run/$NAME.pid
|
||||
SCRIPTNAME=/etc/init.d/$NAME
|
||||
|
||||
# Exit if the package is not installed
|
||||
[ -x "$DAEMON" ] || exit 0
|
||||
|
||||
# Read configuration variable file if it is present
|
||||
[ -r /etc/default/$NAME ] && . /etc/default/$NAME
|
||||
|
||||
# Load the VERBOSE setting and other rcS variables
|
||||
. /lib/init/vars.sh
|
||||
|
||||
# Define LSB log_* functions.
|
||||
# Depend on lsb-base (>= 3.2-14) to ensure that this file is present
|
||||
# and status_of_proc is working.
|
||||
. /lib/lsb/init-functions
|
||||
|
||||
#
|
||||
# Function that starts the daemon/service
|
||||
#
|
||||
do_start()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been started
|
||||
# 1 if daemon was already running
|
||||
# 2 if daemon could not be started
|
||||
start-stop-daemon --start --quiet --chdir $HOMEDIR --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON --test > /dev/null \
|
||||
|| return 1
|
||||
start-stop-daemon --start --quiet --chdir $HOMEDIR --pidfile $PIDFILE --make-pidfile --background --exec $DAEMON -- \
|
||||
$DAEMON_ARGS \
|
||||
|| return 2
|
||||
# Add code here, if necessary, that waits for the process to be ready
|
||||
# to handle requests from services started subsequently which depend
|
||||
# on this one. As a last resort, sleep for some time.
|
||||
}
|
||||
|
||||
#
|
||||
# Function that stops the daemon/service
|
||||
#
|
||||
do_stop()
|
||||
{
|
||||
# Return
|
||||
# 0 if daemon has been stopped
|
||||
# 1 if daemon was already stopped
|
||||
# 2 if daemon could not be stopped
|
||||
# other if a failure occurred
|
||||
start-stop-daemon --stop --quiet --retry=TERM/30/KILL/5 --pidfile $PIDFILE
|
||||
RETVAL="$?"
|
||||
[ "$RETVAL" = 2 ] && return 2
|
||||
# Wait for children to finish too if this is a daemon that forks
|
||||
# and if the daemon is only ever run from this initscript.
|
||||
# If the above conditions are not satisfied then add some other code
|
||||
# that waits for the process to drop all resources that could be
|
||||
# needed by services started subsequently. A last resort is to
|
||||
# sleep for some time.
|
||||
start-stop-daemon --stop --quiet --oknodo --retry=0/30/KILL/5 --exec $DAEMON
|
||||
[ "$?" = 2 ] && return 2
|
||||
# Many daemons don't delete their pidfiles when they exit.
|
||||
rm -f $PIDFILE
|
||||
return "$RETVAL"
|
||||
}
|
||||
|
||||
#
|
||||
# Function that sends a SIGHUP to the daemon/service
|
||||
#
|
||||
do_reload() {
|
||||
#
|
||||
# If the daemon can reload its configuration without
|
||||
# restarting (for example, when it is sent a SIGHUP),
|
||||
# then implement that here.
|
||||
#
|
||||
start-stop-daemon --stop --signal 1 --quiet --pidfile $PIDFILE --name $NAME
|
||||
return 0
|
||||
}
|
||||
|
||||
case "$1" in
|
||||
start)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
|
||||
do_start
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
stop)
|
||||
[ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
|
||||
2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
|
||||
esac
|
||||
;;
|
||||
status)
|
||||
status_of_proc "$DAEMON" "$NAME" && exit 0 || exit $?
|
||||
;;
|
||||
#reload|force-reload)
|
||||
#
|
||||
# If do_reload() is not implemented then leave this commented out
|
||||
# and leave 'force-reload' as an alias for 'restart'.
|
||||
#
|
||||
#log_daemon_msg "Reloading $DESC" "$NAME"
|
||||
#do_reload
|
||||
#log_end_msg $?
|
||||
#;;
|
||||
restart|force-reload)
|
||||
#
|
||||
# If the "reload" option is implemented then remove the
|
||||
# 'force-reload' alias
|
||||
#
|
||||
log_daemon_msg "Restarting $DESC" "$NAME"
|
||||
do_stop
|
||||
case "$?" in
|
||||
0|1)
|
||||
do_start
|
||||
case "$?" in
|
||||
0) log_end_msg 0 ;;
|
||||
1) log_end_msg 1 ;; # Old process is still running
|
||||
*) log_end_msg 1 ;; # Failed to start
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
# Failed to stop
|
||||
log_end_msg 1
|
||||
;;
|
||||
esac
|
||||
;;
|
||||
*)
|
||||
#echo "Usage: $SCRIPTNAME {start|stop|restart|reload|force-reload}" >&2
|
||||
echo "Usage: $SCRIPTNAME {start|stop|status|restart|force-reload}" >&2
|
||||
exit 3
|
||||
;;
|
||||
esac
|
||||
|
||||
:
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
#!/usr/bin/python
|
||||
import ospi
|
||||
|
||||
#### Add any new page urls here ####
|
||||
ospi.urls.extend(['/c1', 'ospi_addon.custom_page_1']) # example: (['/c1', 'ospi_addon.custom_page_1', '/c2', 'ospi_addon.custom_page_2', '/c3', 'ospi_addon.custom_page_3'])
|
||||
|
||||
#### add new functions and classes here ####
|
||||
### Example custom class ###
|
||||
class custom_page_1:
|
||||
"""Add description here"""
|
||||
def GET(self):
|
||||
custpg = '<!DOCTYPE html>\n'
|
||||
#Insert Custom Code here.
|
||||
custpg += '<body>Hello form an ospi_addon program!</body>'
|
||||
return custpg
|
||||
|
||||
|
||||
|
||||
|
||||
#!/usr/bin/python
|
||||
import ospi
|
||||
|
||||
#### Add any new page urls here ####
|
||||
ospi.urls.extend(['/c1', 'ospi_addon.custom_page_1']) # example: (['/c1', 'ospi_addon.custom_page_1', '/c2', 'ospi_addon.custom_page_2', '/c3', 'ospi_addon.custom_page_3'])
|
||||
|
||||
#### add new functions and classes here ####
|
||||
### Example custom class ###
|
||||
class custom_page_1:
|
||||
"""Add description here"""
|
||||
def GET(self):
|
||||
custpg = '<!DOCTYPE html>\n'
|
||||
#Insert Custom Code here.
|
||||
custpg += '<body>Hello form an ospi_addon program!</body>'
|
||||
return custpg
|
||||
|
||||
|
||||
|
||||
|
||||
|
|
|
@ -1 +1,4 @@
|
|||
Program, Zone, Duration, Finish Time, Date
|
||||
Program, Zone, Duration, Finish Time, Date
|
||||
Manual, S02, 0m5s, 12:06:53, Wed. 16 Oct 2013
|
||||
Manual, S01, 0m6s, 20:51:03, Tue. 15 Oct 2013
|
||||
Manual, S02, 0m8s, 20:50:54, Tue. 15 Oct 2013
|
||||
|
|
|
|
@ -51,7 +51,7 @@ else w("<br><b>Log</b>: <font color=gray>n/a</font>");
|
|||
w("<hr>");
|
||||
// print html form
|
||||
w("<form name=hf action=cv method=get><p>Password:<input type=password "+(sd['ipas']?"disabled":"")+" size=10 id=pwd name=pw></p>");
|
||||
w("<input type=hidden name=en><input type=hidden name=rd value=0><input type=hidden name=rbt value=0><input type=hidden name=mm value=0></form>");
|
||||
w("<input type=hidden name=en><input type=hidden name=rd value=''><input type=hidden name=rbt value=0><input type=hidden name=mm value=''></form>");
|
||||
w("<button style=\"height:36\" onclick=\"hf.elements[1].value="+(1-sd['en'])+";hf.submit();\">"+imgstr(sd['en']?"stop":"start")+(sd['en']?"Stop Operation":"Start Operation")+"</button>");
|
||||
w("<button style=\"height:36\" onclick=\"hf.elements[4].value="+(1-sd['mm'])+";hf.submit();\">"+imgstr(sd['mm']?"auto":"manual")+(sd['mm']?"Manual Off":"Manual On")+"</button>");
|
||||
w("<button style=\"height:36\" onclick=\"setrd(hf,2)\">"+imgstr("rain")+"Rain Delay</button>");
|
||||
|
|
|
@ -1,42 +1,42 @@
|
|||
// Javascript for printing OpenSprinkler homepage (manual mode)
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
function id(s) {return document.getElementById(s);}
|
||||
function snf(sid,sbit) {
|
||||
if(sbit==1) window.location="/sn"+(sid+1)+"=0"; // turn off station
|
||||
else {
|
||||
var strmm=id("mm"+sid).value, strss=id("ss"+sid).value;
|
||||
var mm=(strmm=="")?0:parseInt(strmm);
|
||||
var ss=(strss=="")?0:parseInt(strss);
|
||||
if(!(mm>=0&&ss>=0&&ss<60)) {alert("Timer values wrong: "+strmm+":"+strss);return;}
|
||||
window.location="/sn"+(sid+1)+"=1"+"&t="+(mm*60+ss); // turn it off with timer
|
||||
}
|
||||
}
|
||||
w("<b>Manual Control:</b> (timer is optional)<p></p>");
|
||||
w("<table border=1>");
|
||||
var bid,s,sid,sn,rem,remm,rems,sbit;
|
||||
for(bid=0;bid<sd['nbrd'];bid++){
|
||||
for(s=0;s<8;s++){
|
||||
w("<tr><td bgcolor='#E4E4E4'>");
|
||||
sid=bid*8+s;
|
||||
sn=sid+1;
|
||||
//w("Station "+(sn/10>>0)+(sn%10)+": ");
|
||||
w(snames[sid]+": </td><td>");
|
||||
if(sn==sd['mas']) {w(((sbits[bid]>>s)&1?("<b>On</b>").fontcolor("green"):("Off").fontcolor("black"))+" (<b>Master</b>)");}
|
||||
else {
|
||||
rem=ps[sid][1];
|
||||
if(rem>65536) rem=0;
|
||||
remm=rem/60>>0;rems=rem%60;sbit=(sbits[bid]>>s)&1;
|
||||
var bg=(sbit?"#FFCCCC":"#CCFFCC"),tx=(sbit?"off":"on"),dis=(sbit?"disabled":"");
|
||||
w("<button style=\"width:100px;height:32px;background-color:"+bg+";border-radius:8px;\" id=bb"+sid+" onclick=\"snf("+sid+","+sbit+")\">Turn "+tx+"</button>");
|
||||
w(sbit?" in ":" with timer ");
|
||||
w("<input type=text id=mm"+sid+" size=2 maxlength=3 value="+remm+" "+dis+" />:");
|
||||
w("<input type=text id=ss"+sid+" size=2 maxlength=2 value="+rems+" "+dis+" /> (mm:ss)");
|
||||
}
|
||||
w("</td>");
|
||||
}
|
||||
}
|
||||
w("</table>");
|
||||
// Javascript for printing OpenSprinkler homepage (manual mode)
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
function id(s) {return document.getElementById(s);}
|
||||
function snf(sid,sbit) {
|
||||
if(sbit==1) window.location="/sn"+(sid+1)+"=0"; // turn off station
|
||||
else {
|
||||
var strmm=id("mm"+sid).value, strss=id("ss"+sid).value;
|
||||
var mm=(strmm=="")?0:parseInt(strmm);
|
||||
var ss=(strss=="")?0:parseInt(strss);
|
||||
if(!(mm>=0&&ss>=0&&ss<60)) {alert("Timer values wrong: "+strmm+":"+strss);return;}
|
||||
window.location="/sn"+(sid+1)+"=1"+"&t="+(mm*60+ss); // turn it off with timer
|
||||
}
|
||||
}
|
||||
w("<b>Manual Control:</b> (timer is optional)<p></p>");
|
||||
w("<table border=1>");
|
||||
var bid,s,sid,sn,rem,remm,rems,sbit;
|
||||
for(bid=0;bid<sd['nbrd'];bid++){
|
||||
for(s=0;s<8;s++){
|
||||
w("<tr><td bgcolor='#E4E4E4'>");
|
||||
sid=bid*8+s;
|
||||
sn=sid+1;
|
||||
//w("Station "+(sn/10>>0)+(sn%10)+": ");
|
||||
w(snames[sid]+": </td><td>");
|
||||
if(sn==sd['mas']) {w(((sbits[bid]>>s)&1?("<b>On</b>").fontcolor("green"):("Off").fontcolor("black"))+" (<b>Master</b>)");}
|
||||
else {
|
||||
rem=ps[sid][1];
|
||||
if(rem>65536) rem=0;
|
||||
remm=rem/60>>0;rems=rem%60;sbit=(sbits[bid]>>s)&1;
|
||||
var bg=(sbit?"#FFCCCC":"#CCFFCC"),tx=(sbit?"off":"on"),dis=(sbit?"disabled":"");
|
||||
w("<button style=\"width:100px;height:32px;background-color:"+bg+";border-radius:8px;\" id=bb"+sid+" onclick=\"snf("+sid+","+sbit+")\">Turn "+tx+"</button>");
|
||||
w(sbit?" in ":" with timer ");
|
||||
w("<input type=text id=mm"+sid+" size=2 maxlength=3 value="+remm+" "+dis+" />:");
|
||||
w("<input type=text id=ss"+sid+" size=2 maxlength=2 value="+rems+" "+dis+" /> (mm:ss)");
|
||||
}
|
||||
w("</td>");
|
||||
}
|
||||
}
|
||||
w("</table>");
|
||||
|
|
|
@ -1,142 +1,142 @@
|
|||
// Javascript for printing OpenSprinkler modify program page
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
function w(s) {document.writeln(s);}
|
||||
function id(s){return document.getElementById(s);}
|
||||
function imgstr(s) {return "<img src=\""+baseurl+"/static/images/icons/svc_"+s+".png\" height=20 align=absmiddle> ";}
|
||||
// parse time
|
||||
function parse_time(prefix) {
|
||||
var h=parseInt(id(prefix+"h").value,10);
|
||||
var m=parseInt(id(prefix+"m").value,10);
|
||||
if(!(h>=0&&h<24&&m>=0&&m<60)) {alert("Error: Incorrect time input "+prefix+".");return -1;}
|
||||
return h*60+m;
|
||||
}
|
||||
// fill time
|
||||
function fill_time(prefix,idx) {
|
||||
var t=prog[idx];
|
||||
id(prefix+"h").value=""+((t/60>>0)/10>>0)+((t/60>>0)%10);
|
||||
id(prefix+"m").value=""+((t%60)/10>>0)+((t%60)%10);
|
||||
}
|
||||
// check/uncheck all days
|
||||
function seldays(v) {
|
||||
var i;
|
||||
for(i=0;i<7;i++) id("d"+i).checked=(v>0)?true:false;
|
||||
}
|
||||
// handle form submit
|
||||
function fsubmit(f) {
|
||||
var errmsg = "",days=[0,0],i,s,sid;
|
||||
var en=0;
|
||||
if(id("en_on").checked) en=1;
|
||||
// process days
|
||||
if(id("days_week").checked) {
|
||||
for(i=0;i<7;i++) {if(id("d"+i).checked) {days[0] |= (1<<i); }}
|
||||
if(id("days_odd").checked) {days[0]|=0x80; days[1]=1;}
|
||||
else if(id("days_even").checked) {days[0]|=0x80; days[1]=0;}
|
||||
} else if(id("days_n").checked) {
|
||||
days[1]=parseInt(id("dn").value,10);
|
||||
if(!(days[1]>=2&&days[1]<=128)) {alert("Error: interval days must be between 2 and 128.");return;}
|
||||
days[0]=parseInt(id("drem").value,10);
|
||||
if(!(days[0]>=0&&days[0]<days[1])) {alert("Error: starting in days wrong.");return;}
|
||||
days[0]|=0x80;
|
||||
}
|
||||
if(days[0]==0||(days[1]<2&&(days[0]&0x7f)==0)) {alert("Error: You have not selected any day.");return;}
|
||||
// process stations
|
||||
var stations=[0],station_selected=0,bid;
|
||||
for(bid=0;bid<sd['nbrd'];bid++) {
|
||||
stations[bid]=0;
|
||||
for(s=0;s<8;s++) {
|
||||
sid=bid*8+s;
|
||||
if(id("s"+sid).checked) {
|
||||
stations[bid] |= 1<<s; station_selected=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(station_selected==0) {alert("Error: You have not selected any station.");return;}
|
||||
// process time
|
||||
var start_time,end_time,interval,duration;
|
||||
if((start_time=parse_time("ts")) < 0) return;
|
||||
if((end_time=parse_time("te")) < 0) return;
|
||||
if(!(start_time<end_time)) {alert("Error: Start time must be prior to end time.");return;}
|
||||
if((interval=parse_time("ti")) < 0) return;
|
||||
var dm=parseInt(id("tdm").value,10);
|
||||
var ds=parseInt(id("tds").value,10);
|
||||
duration=dm*60+ds;
|
||||
if(!(dm>=0&&ds>=0&&ds<60&&duration>0)) {alert("Error: Incorrect duration.");return;}
|
||||
// password
|
||||
var p="";
|
||||
if(!sd['ipas']) p=prompt("Please enter your password:","");
|
||||
if(p!=null){
|
||||
f.elements[0].value=p;
|
||||
f.elements[1].value=pid;
|
||||
f.elements[2].value="["+en+","+days[0]+","+days[1]+","+start_time+","+end_time+","+interval+","+duration;
|
||||
for(i=0;i<sd['nbrd'];i++) {f.elements[2].value+=","+stations[i];}
|
||||
f.elements[2].value+="]";
|
||||
f.submit();
|
||||
}
|
||||
}
|
||||
// handle form cancel
|
||||
function fcancel() {window.location="/vp";}
|
||||
// print html form
|
||||
w("<div style=\"padding-top:10px;padding-bottom:0px;\"><b>"+((pid>-1)?"Modify Program "+(pid+1):"Add a New Program")+"</b></div><hr>");
|
||||
w("<form name=mf action=cp method=get><input type=hidden name=pw><input type=hidden name=pid><input type=hidden name=v>");
|
||||
w("<div style=\"padding-left:5px;padding-right:5px;\">");
|
||||
w("<p><b>This program is: </b><input type=radio name=rad_en id=en_on><b>On</b><input type=radio name=rad_en id=en_off><b>Off</b></p>");
|
||||
w("<p><b>Select Days:</b></p><input type=radio name=rad_day id=days_week><b><u>Weekly</u>:</b><input type=checkbox id=d0>Mon<input type=checkbox id=d1>Tue<input type=checkbox id=d2>Wed<input type=checkbox id=d3>Thu<input type=checkbox id=d4>Fri<input type=checkbox id=d5>Sat<input type=checkbox id=d6>Sun<br>")
|
||||
w("<div style=\"padding-left:80px;\"><button onclick=\"seldays(1);return false;\">Select All</button><button onclick=\"seldays(0);return false;\">Select None</button></div>");
|
||||
w("<div style=\"padding-left:20px;\"><p><b>Select Restrictions:</b><br><input type=radio name=rad_rst id=days_norst>No restriction<br><input type=radio name=rad_rst id=days_odd>Odd days only (except 31st and Feb 29th)<br><input type=radio name=rad_rst id=days_even>Even days only<br></p></div>");
|
||||
w("<input type=radio name=rad_day id=days_n><b><u>Interval</u>:</b> Every <input type=text size=2 maxlength=2 id=dn> days, starting in <input type=text size=2 maxlength=2 id=drem> days.<hr>");
|
||||
w("<p><b>Select Stations:</b></p>");
|
||||
w("<table border=1 cellpadding=3>");
|
||||
var bid,s,sid;
|
||||
for(bid=0;bid<sd['nbrd'];bid++) {
|
||||
for(s=0;s<8;s++) {
|
||||
sid=bid*8+s;
|
||||
if(sid%4==0) w("<tr>");
|
||||
w("<td>");
|
||||
w("<input type=checkbox id=s"+sid+">"+snames[sid]);
|
||||
w("</td>");
|
||||
if(sid%4==3) w("</tr>");
|
||||
}
|
||||
}
|
||||
w("</table><p></p><hr>");
|
||||
w("<p></p><b>Time:</b> <input type=text size=2 maxlength=2 id=tsh> : <input type=text size=2 maxlength=2 id=tsm> -> <input type=text size=2 maxlength=2 id=teh> : <input type=text size=2 maxlength=2 id=tem> (hh:mm)<br><b>Every:</b> <input type=text size=2 maxlength=2 id=tih> hours <input type=text size=2 maxlength=2 id=tim> minutes <br><b>Duration:</b> <input type=text size=2 maxlength=3 id=tdm> minutes <input type=text size=2 maxlength=2 id=tds> seconds<p></p><hr>");
|
||||
w("</div></form>");
|
||||
w("<button style=\"height:36\" onclick=\"fsubmit(mf)\">"+imgstr("submit")+"<b>Submit</b></button>");
|
||||
w("<button style=\"height:36\" onclick=\"fcancel()\">"+imgstr("delall")+"Cancel</button>");
|
||||
// default values
|
||||
id("en_on").checked=true;
|
||||
id("days_week").checked=true;id("days_norst").checked=true;
|
||||
id("dn").value="3";id("drem").value="0";
|
||||
id("tsh").value="06";id("tsm").value="00";id("teh").value="18";id("tem").value="00";
|
||||
id("tih").value="04";id("tim").value="00";id("tdm").value="15";id("tds").value="00";
|
||||
// fill in existing program values
|
||||
if(pid>-1) {
|
||||
if(prog[0]==0) id("en_off").checked=true;
|
||||
// process days
|
||||
var _days=[prog[1],prog[2]];
|
||||
if((_days[0]&0x80)&&(_days[1]>1)) {
|
||||
id("days_n").checked=true;
|
||||
id("dn").value=_days[1];id("drem").value=_days[0]&0x7f;
|
||||
} else {
|
||||
id("days_week").checked=true;
|
||||
for(i=0;i<7;i++) {if(_days[0]&(1<<i)) id("d"+i).checked=true;}
|
||||
if((_days[0]&0x80)&&(_days[1]==0)) {id("days_even").checked=true;}
|
||||
if((_days[0]&0x80)&&(_days[1]==1)) {id("days_odd").checked=true;}
|
||||
}
|
||||
// process time
|
||||
fill_time("ts",3);
|
||||
fill_time("te",4);
|
||||
fill_time("ti",5);
|
||||
var t=prog[6];
|
||||
id("tdm").value=""+((t/60>>0)/10>>0)+((t/60>>0)%10);
|
||||
id("tds").value=""+((t%60)/10>>0)+((t%60)%10);
|
||||
// process stations
|
||||
var bits;
|
||||
for(bid=0;bid<sd['nbrd'];bid++) {
|
||||
bits=prog[bid+7];
|
||||
for(s=0;s<8;s++) {sid=bid*8+s;id("s"+sid).checked=(bits&(1<<s)?true:false);}
|
||||
}
|
||||
}
|
||||
// Javascript for printing OpenSprinkler modify program page
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
function w(s) {document.writeln(s);}
|
||||
function id(s){return document.getElementById(s);}
|
||||
function imgstr(s) {return "<img src=\""+baseurl+"/static/images/icons/svc_"+s+".png\" height=20 align=absmiddle> ";}
|
||||
// parse time
|
||||
function parse_time(prefix) {
|
||||
var h=parseInt(id(prefix+"h").value,10);
|
||||
var m=parseInt(id(prefix+"m").value,10);
|
||||
if(!(h>=0&&h<24&&m>=0&&m<60)) {alert("Error: Incorrect time input "+prefix+".");return -1;}
|
||||
return h*60+m;
|
||||
}
|
||||
// fill time
|
||||
function fill_time(prefix,idx) {
|
||||
var t=prog[idx];
|
||||
id(prefix+"h").value=""+((t/60>>0)/10>>0)+((t/60>>0)%10);
|
||||
id(prefix+"m").value=""+((t%60)/10>>0)+((t%60)%10);
|
||||
}
|
||||
// check/uncheck all days
|
||||
function seldays(v) {
|
||||
var i;
|
||||
for(i=0;i<7;i++) id("d"+i).checked=(v>0)?true:false;
|
||||
}
|
||||
// handle form submit
|
||||
function fsubmit(f) {
|
||||
var errmsg = "",days=[0,0],i,s,sid;
|
||||
var en=0;
|
||||
if(id("en_on").checked) en=1;
|
||||
// process days
|
||||
if(id("days_week").checked) {
|
||||
for(i=0;i<7;i++) {if(id("d"+i).checked) {days[0] |= (1<<i); }}
|
||||
if(id("days_odd").checked) {days[0]|=0x80; days[1]=1;}
|
||||
else if(id("days_even").checked) {days[0]|=0x80; days[1]=0;}
|
||||
} else if(id("days_n").checked) {
|
||||
days[1]=parseInt(id("dn").value,10);
|
||||
if(!(days[1]>=2&&days[1]<=128)) {alert("Error: interval days must be between 2 and 128.");return;}
|
||||
days[0]=parseInt(id("drem").value,10);
|
||||
if(!(days[0]>=0&&days[0]<days[1])) {alert("Error: starting in days wrong.");return;}
|
||||
days[0]|=0x80;
|
||||
}
|
||||
if(days[0]==0||(days[1]<2&&(days[0]&0x7f)==0)) {alert("Error: You have not selected any day.");return;}
|
||||
// process stations
|
||||
var stations=[0],station_selected=0,bid;
|
||||
for(bid=0;bid<sd['nbrd'];bid++) {
|
||||
stations[bid]=0;
|
||||
for(s=0;s<8;s++) {
|
||||
sid=bid*8+s;
|
||||
if(id("s"+sid).checked) {
|
||||
stations[bid] |= 1<<s; station_selected=1;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(station_selected==0) {alert("Error: You have not selected any station.");return;}
|
||||
// process time
|
||||
var start_time,end_time,interval,duration;
|
||||
if((start_time=parse_time("ts")) < 0) return;
|
||||
if((end_time=parse_time("te")) < 0) return;
|
||||
if(!(start_time<end_time)) {alert("Error: Start time must be prior to end time.");return;}
|
||||
if((interval=parse_time("ti")) < 0) return;
|
||||
var dm=parseInt(id("tdm").value,10);
|
||||
var ds=parseInt(id("tds").value,10);
|
||||
duration=dm*60+ds;
|
||||
if(!(dm>=0&&ds>=0&&ds<60&&duration>0)) {alert("Error: Incorrect duration.");return;}
|
||||
// password
|
||||
var p="";
|
||||
if(!sd['ipas']) p=prompt("Please enter your password:","");
|
||||
if(p!=null){
|
||||
f.elements[0].value=p;
|
||||
f.elements[1].value=pid;
|
||||
f.elements[2].value="["+en+","+days[0]+","+days[1]+","+start_time+","+end_time+","+interval+","+duration;
|
||||
for(i=0;i<sd['nbrd'];i++) {f.elements[2].value+=","+stations[i];}
|
||||
f.elements[2].value+="]";
|
||||
f.submit();
|
||||
}
|
||||
}
|
||||
// handle form cancel
|
||||
function fcancel() {window.location="/vp";}
|
||||
// print html form
|
||||
w("<div style=\"padding-top:10px;padding-bottom:0px;\"><b>"+((pid>-1)?"Modify Program "+(pid+1):"Add a New Program")+"</b></div><hr>");
|
||||
w("<form name=mf action=cp method=get><input type=hidden name=pw><input type=hidden name=pid><input type=hidden name=v>");
|
||||
w("<div style=\"padding-left:5px;padding-right:5px;\">");
|
||||
w("<p><b>This program is: </b><input type=radio name=rad_en id=en_on><b>On</b><input type=radio name=rad_en id=en_off><b>Off</b></p>");
|
||||
w("<p><b>Select Days:</b></p><input type=radio name=rad_day id=days_week><b><u>Weekly</u>:</b><input type=checkbox id=d0>Mon<input type=checkbox id=d1>Tue<input type=checkbox id=d2>Wed<input type=checkbox id=d3>Thu<input type=checkbox id=d4>Fri<input type=checkbox id=d5>Sat<input type=checkbox id=d6>Sun<br>")
|
||||
w("<div style=\"padding-left:80px;\"><button onclick=\"seldays(1);return false;\">Select All</button><button onclick=\"seldays(0);return false;\">Select None</button></div>");
|
||||
w("<div style=\"padding-left:20px;\"><p><b>Select Restrictions:</b><br><input type=radio name=rad_rst id=days_norst>No restriction<br><input type=radio name=rad_rst id=days_odd>Odd days only (except 31st and Feb 29th)<br><input type=radio name=rad_rst id=days_even>Even days only<br></p></div>");
|
||||
w("<input type=radio name=rad_day id=days_n><b><u>Interval</u>:</b> Every <input type=text size=2 maxlength=2 id=dn> days, starting in <input type=text size=2 maxlength=2 id=drem> days.<hr>");
|
||||
w("<p><b>Select Stations:</b></p>");
|
||||
w("<table border=1 cellpadding=3>");
|
||||
var bid,s,sid;
|
||||
for(bid=0;bid<sd['nbrd'];bid++) {
|
||||
for(s=0;s<8;s++) {
|
||||
sid=bid*8+s;
|
||||
if(sid%4==0) w("<tr>");
|
||||
w("<td>");
|
||||
w("<input type=checkbox id=s"+sid+">"+snames[sid]);
|
||||
w("</td>");
|
||||
if(sid%4==3) w("</tr>");
|
||||
}
|
||||
}
|
||||
w("</table><p></p><hr>");
|
||||
w("<p></p><b>Time:</b> <input type=text size=2 maxlength=2 id=tsh> : <input type=text size=2 maxlength=2 id=tsm> -> <input type=text size=2 maxlength=2 id=teh> : <input type=text size=2 maxlength=2 id=tem> (hh:mm)<br><b>Every:</b> <input type=text size=2 maxlength=2 id=tih> hours <input type=text size=2 maxlength=2 id=tim> minutes <br><b>Duration:</b> <input type=text size=2 maxlength=3 id=tdm> minutes <input type=text size=2 maxlength=2 id=tds> seconds<p></p><hr>");
|
||||
w("</div></form>");
|
||||
w("<button style=\"height:36\" onclick=\"fsubmit(mf)\">"+imgstr("submit")+"<b>Submit</b></button>");
|
||||
w("<button style=\"height:36\" onclick=\"fcancel()\">"+imgstr("delall")+"Cancel</button>");
|
||||
// default values
|
||||
id("en_on").checked=true;
|
||||
id("days_week").checked=true;id("days_norst").checked=true;
|
||||
id("dn").value="3";id("drem").value="0";
|
||||
id("tsh").value="06";id("tsm").value="00";id("teh").value="18";id("tem").value="00";
|
||||
id("tih").value="04";id("tim").value="00";id("tdm").value="15";id("tds").value="00";
|
||||
// fill in existing program values
|
||||
if(pid>-1) {
|
||||
if(prog[0]==0) id("en_off").checked=true;
|
||||
// process days
|
||||
var _days=[prog[1],prog[2]];
|
||||
if((_days[0]&0x80)&&(_days[1]>1)) {
|
||||
id("days_n").checked=true;
|
||||
id("dn").value=_days[1];id("drem").value=_days[0]&0x7f;
|
||||
} else {
|
||||
id("days_week").checked=true;
|
||||
for(i=0;i<7;i++) {if(_days[0]&(1<<i)) id("d"+i).checked=true;}
|
||||
if((_days[0]&0x80)&&(_days[1]==0)) {id("days_even").checked=true;}
|
||||
if((_days[0]&0x80)&&(_days[1]==1)) {id("days_odd").checked=true;}
|
||||
}
|
||||
// process time
|
||||
fill_time("ts",3);
|
||||
fill_time("te",4);
|
||||
fill_time("ti",5);
|
||||
var t=prog[6];
|
||||
id("tdm").value=""+((t/60>>0)/10>>0)+((t/60>>0)%10);
|
||||
id("tds").value=""+((t%60)/10>>0)+((t%60)%10);
|
||||
// process stations
|
||||
var bits;
|
||||
for(bid=0;bid<sd['nbrd'];bid++) {
|
||||
bits=prog[bid+7];
|
||||
for(s=0;s<8;s++) {sid=bid*8+s;id("s"+sid).checked=(bits&(1<<s)?true:false);}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,164 +1,164 @@
|
|||
// Javascript for printing OpenSprinkler schedule page
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
// colors to draw different programs
|
||||
var prog_color=["rgba(0,0,200,0.5)","rgba(0,200,0,0.5)","rgba(200,0,0,0.5)","rgba(0,200,200,0.5)"];
|
||||
var days_str=["Sun","Mon","Tue","Wed","Thur","Fri","Sat"];
|
||||
var xstart=80,ystart=80,stwidth=40,stheight=180;
|
||||
var winwidth=stwidth*sd['nbrd']*8+xstart, winheight=26*stheight+ystart;
|
||||
var sid,sn,t;
|
||||
var simt=Date.UTC(yy,mm-1,dd,0,0,0,0);
|
||||
var simdate=new Date(simt);
|
||||
var simday = (simt/1000/3600/24)>>0;
|
||||
function w(s) {document.writeln(s);}
|
||||
function check_match(prog,simminutes,simdate,simday) {
|
||||
// simdate is Java date object, simday is the #days since 1970 01-01
|
||||
var wd,dn,drem;
|
||||
if(prog[0]==0) return 0;
|
||||
if ((prog[1]&0x80)&&(prog[2]>1)) { // inverval checking
|
||||
dn=prog[2];drem=prog[1]&0x7f;
|
||||
if((simday%dn)!=((devday+drem)%dn)) return 0; // remainder checking
|
||||
} else {
|
||||
wd=(simdate.getUTCDay()+6)%7; // getDay assumes sunday is 0, converts to Monday 0
|
||||
if((prog[1]&(1<<wd))==0) return 0; // weekday checking
|
||||
dt=simdate.getUTCDate(); // day of the month
|
||||
if((prog[1]&0x80)&&(prog[2]==0)) {if((dt%2)!=0) return 0;} // even day checking
|
||||
if((prog[1]&0x80)&&(prog[2]==1)) { // odd day checking
|
||||
if(dt==31) return 0;
|
||||
else if (dt==29 && simdate.getUTCMonth()==1) return 0;
|
||||
else if ((dt%2)!=1) return 0;
|
||||
}
|
||||
}
|
||||
if(simminutes<prog[3] || simminutes>prog[4]) return 0; // start and end time checking
|
||||
if(prog[5]==0) return 0;
|
||||
if(((simminutes-prog[3])/prog[5]>>0)*prog[5] == (simminutes-prog[3])) { // interval checking
|
||||
return 1;
|
||||
}
|
||||
return 0; // no match found
|
||||
}
|
||||
function getx(sid) {return xstart+sid*stwidth-stwidth/2;} // x coordinate given a station
|
||||
function gety(t) {return ystart+t*stheight/60;} // y coordinate given a time
|
||||
function getrunstr(start,end){ // run time string
|
||||
var h,m,s,str;
|
||||
h=start/3600>>0;m=(start/60>>0)%60;s=start%60;
|
||||
str=""+(h/10>>0)+(h%10)+":"+(m/10>>0)+(m%10)+":"+(s/10>>0)+(s%10);
|
||||
h=end/3600>>0;m=(end/60>>0)%60;s=end%60;
|
||||
str+="->"+(h/10>>0)+(h%10)+":"+(m/10>>0)+(m%10)+":"+(s/10>>0)+(s%10);
|
||||
return str;
|
||||
}
|
||||
function plot_bar(sid,start,pid,end) { // plot program bar
|
||||
w("<div title=\""+snames[sid]+" ["+getrunstr(start,end)+"]\" align=\"center\" style=\"position:absolute;background-color:"+prog_color[(pid+3)%4]+";left:"+getx(sid)+"px;top:"+gety(start/60)+"px;border:0;width:"+stwidth+"px;height:"+((end-start)/60*stheight/60)+"px\">P"+pid+"</div>");
|
||||
}
|
||||
function plot_master(start,end) { // plot master station
|
||||
w("<div title=\"Master ["+getrunstr(start,end)+"]\" style=\"position:absolute;background-color:#CCCC80;left:"+getx(mas-1)+"px;top:"+gety(start/60)+"px;border:0;width:"+stwidth+"px;height:"+((end-start)/60*stheight/60)+"px\"></div>");
|
||||
//if(mas==0||start==end) return;
|
||||
//ctx.fillStyle="rgba(64,64,64,0.5)";
|
||||
//ctx.fillRect(getx(mas-1),gety(start/60),stwidth,(end-start)/60*stheight/60);
|
||||
}
|
||||
function plot_currtime() {
|
||||
w("<div style=\"position:absolute;left:"+(xstart-stwidth/2-10)+"px;top:"+gety(devmin)+"px;border:1px solid rgba(200,0,0,0.5);width:"+(winwidth-xstart+stwidth/2)+"px;height:0px;\"></div>");
|
||||
}
|
||||
function run_sched(simseconds,st_array,pid_array,et_array) { // run and plot schedule stored in array data
|
||||
var sid,endtime=simseconds;
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
if(pid_array[sid]) {
|
||||
if(sd['seq']==1) { // sequential
|
||||
plot_bar(sid,st_array[sid],pid_array[sid],et_array[sid]);
|
||||
if((sd['mas']>0)&&(sd['mas']!=sid+1)&&(sd['mo'][sid>>3]&(1<<(sid%8))))
|
||||
plot_master(st_array[sid]+sd['mton'], et_array[sid]+sd['mtoff']);
|
||||
endtime=et_array[sid];
|
||||
} else { // concurrent
|
||||
plot_bar(sid,simseconds,pid_array[sid],et_array[sid]);
|
||||
// check if this station activates master
|
||||
if((sd['mas']>0)&&(sd['mas']!=sid+1)&&(sd['mo'][sid>>3]&(1<<(sid%8))))
|
||||
endtime=(endtime>et_array[sid])?endtime:et_array[sid];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(sd['seq']==0&&sd['mas']>0) plot_master(simseconds,endtime);
|
||||
return endtime;
|
||||
}
|
||||
function draw_title() {
|
||||
w("<div align=\"center\" style=\"background-color:#EEEEEE;position:absolute;left:0px;top:10px;border:2px solid gray;padding:5px 0px;width:"+(winwidth)+"px;border-radius:10px;box-shadow:3px 3px 2px #888888;\"><b>Program Preview of</b> ");
|
||||
w(days_str[simdate.getUTCDay()]+" "+(simdate.getUTCMonth()+1)+"/"+(simdate.getUTCDate())+" "+(simdate.getUTCFullYear()));
|
||||
w("<br><font size=2>(Hover over each colored bar to see tooltip)</font>");
|
||||
w("</div>");
|
||||
}
|
||||
|
||||
function draw_grid() {
|
||||
// draw table and grid
|
||||
for(sid=0;sid<=sd['nbrd']*8;sid++) {
|
||||
sn=sid+1;
|
||||
if(sid<sd['nbrd']*8) w("<div style=\"position:absolute;left:"+(xstart+sid*stwidth-10)+"px;top:"+(ystart-15)+"px;width:"+stwidth+"px;height:20px;border:0;padding:0;\"><font size=2>S"+(sn/10>>0)+(sn%10)+"</font></div>");
|
||||
w("<div style=\"position:absolute;left:"+getx(sid)+"px;top:"+(ystart-10)+"px;border:1px solid gray;width:0px;height:"+(winheight-ystart+30)+"px;\"></div>");
|
||||
}
|
||||
// horizontal grid, time
|
||||
for(t=0;t<=24;t++) {
|
||||
w("<div style=\"position:absolute;left:"+(xstart-stwidth/2-15)+"px;top:"+gety(t*60)+"px;border:1px solid gray;width:15px;height:0px;\"></div>");
|
||||
w("<div style=\"position:absolute;left:"+(xstart-stwidth/2-8)+"px;top:"+(gety(t*60)+stheight/2)+"px;border:1px solid gray;width:8px;height:0px;\"></div>");
|
||||
w("<div style=\"position:absolute;left:"+(xstart-70)+"px;top:"+(ystart+t*stheight-7)+"px;width=70;height:20px;border:0;padding:0;\"><font size=2>"+(t/10>>0)+(t%10)+":00</font></div>");
|
||||
}
|
||||
plot_currtime();
|
||||
}
|
||||
function draw_program() {
|
||||
// plot program data by a full simulation
|
||||
var simminutes=0,busy=0,match_found=0,bid,s,sid,pid,match=[0,0];
|
||||
var st_array=new Array(sd['nbrd']*8),pid_array=new Array(sd['nbrd']*8);
|
||||
var et_array=new Array(sd['nbrd']*8);
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
st_array[sid]=0;pid_array[sid]=0;et_array[sid]=0;
|
||||
}
|
||||
do { // check through every program
|
||||
busy=0;
|
||||
match_found=0;
|
||||
for(pid=0;pid<nprogs;pid++) {
|
||||
var prog=pd[pid];
|
||||
if(check_match(prog,simminutes,simdate,simday)) {
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
bid=sid>>3;s=sid%8;
|
||||
if(sd['mas']==(sid+1)) continue; // skip master station
|
||||
if(prog[7+bid]&(1<<s)) {
|
||||
et_array[sid]=prog[6]*sd['wl']/100>>0;pid_array[sid]=pid+1;
|
||||
match_found=1;
|
||||
}//if
|
||||
}//for_sid
|
||||
}//if_match
|
||||
}//for_pid
|
||||
if(match_found) {
|
||||
var acctime=simminutes*60;
|
||||
if(sd['seq']) { // sequential
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
if(et_array[sid]) {
|
||||
st_array[sid]=acctime;acctime+=et_array[sid];
|
||||
et_array[sid]=acctime;acctime+=sd['sdt'];
|
||||
busy=1;
|
||||
}//if
|
||||
}//for
|
||||
} else {
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
if(et_array[sid]) {
|
||||
st_array[sid]=simminutes*60;
|
||||
et_array[sid]=simminutes*60+et_array[sid];
|
||||
busy=1;
|
||||
}//if(et_array)
|
||||
}//for(sid)
|
||||
}//else(seq)
|
||||
}//if(match_found)
|
||||
if (busy) {
|
||||
var endminutes=run_sched(simminutes*60,st_array,pid_array,et_array)/60>>0;
|
||||
if(sd['seq']&&simminutes!=endminutes) simminutes=endminutes;
|
||||
else simminutes++;
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {st_array[sid]=0;pid_array[sid]=0;et_array[sid]=0;} // clear program data
|
||||
} else {
|
||||
simminutes++; // increment simulation time
|
||||
}
|
||||
} while(simminutes<24*60); // simulation ends
|
||||
window.scrollTo(0,gety((devmin/60>>0)*60)); // scroll to the hour line cloest to the current time
|
||||
}
|
||||
|
||||
draw_title();
|
||||
draw_grid();
|
||||
draw_program();
|
||||
// Javascript for printing OpenSprinkler schedule page
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
// colors to draw different programs
|
||||
var prog_color=["rgba(0,0,200,0.5)","rgba(0,200,0,0.5)","rgba(200,0,0,0.5)","rgba(0,200,200,0.5)"];
|
||||
var days_str=["Sun","Mon","Tue","Wed","Thur","Fri","Sat"];
|
||||
var xstart=80,ystart=80,stwidth=40,stheight=180;
|
||||
var winwidth=stwidth*sd['nbrd']*8+xstart, winheight=26*stheight+ystart;
|
||||
var sid,sn,t;
|
||||
var simt=Date.UTC(yy,mm-1,dd,0,0,0,0);
|
||||
var simdate=new Date(simt);
|
||||
var simday = (simt/1000/3600/24)>>0;
|
||||
function w(s) {document.writeln(s);}
|
||||
function check_match(prog,simminutes,simdate,simday) {
|
||||
// simdate is Java date object, simday is the #days since 1970 01-01
|
||||
var wd,dn,drem;
|
||||
if(prog[0]==0) return 0;
|
||||
if ((prog[1]&0x80)&&(prog[2]>1)) { // inverval checking
|
||||
dn=prog[2];drem=prog[1]&0x7f;
|
||||
if((simday%dn)!=((devday+drem)%dn)) return 0; // remainder checking
|
||||
} else {
|
||||
wd=(simdate.getUTCDay()+6)%7; // getDay assumes sunday is 0, converts to Monday 0
|
||||
if((prog[1]&(1<<wd))==0) return 0; // weekday checking
|
||||
dt=simdate.getUTCDate(); // day of the month
|
||||
if((prog[1]&0x80)&&(prog[2]==0)) {if((dt%2)!=0) return 0;} // even day checking
|
||||
if((prog[1]&0x80)&&(prog[2]==1)) { // odd day checking
|
||||
if(dt==31) return 0;
|
||||
else if (dt==29 && simdate.getUTCMonth()==1) return 0;
|
||||
else if ((dt%2)!=1) return 0;
|
||||
}
|
||||
}
|
||||
if(simminutes<prog[3] || simminutes>prog[4]) return 0; // start and end time checking
|
||||
if(prog[5]==0) return 0;
|
||||
if(((simminutes-prog[3])/prog[5]>>0)*prog[5] == (simminutes-prog[3])) { // interval checking
|
||||
return 1;
|
||||
}
|
||||
return 0; // no match found
|
||||
}
|
||||
function getx(sid) {return xstart+sid*stwidth-stwidth/2;} // x coordinate given a station
|
||||
function gety(t) {return ystart+t*stheight/60;} // y coordinate given a time
|
||||
function getrunstr(start,end){ // run time string
|
||||
var h,m,s,str;
|
||||
h=start/3600>>0;m=(start/60>>0)%60;s=start%60;
|
||||
str=""+(h/10>>0)+(h%10)+":"+(m/10>>0)+(m%10)+":"+(s/10>>0)+(s%10);
|
||||
h=end/3600>>0;m=(end/60>>0)%60;s=end%60;
|
||||
str+="->"+(h/10>>0)+(h%10)+":"+(m/10>>0)+(m%10)+":"+(s/10>>0)+(s%10);
|
||||
return str;
|
||||
}
|
||||
function plot_bar(sid,start,pid,end) { // plot program bar
|
||||
w("<div title=\""+snames[sid]+" ["+getrunstr(start,end)+"]\" align=\"center\" style=\"position:absolute;background-color:"+prog_color[(pid+3)%4]+";left:"+getx(sid)+"px;top:"+gety(start/60)+"px;border:0;width:"+stwidth+"px;height:"+((end-start)/60*stheight/60)+"px\">P"+pid+"</div>");
|
||||
}
|
||||
function plot_master(start,end) { // plot master station
|
||||
w("<div title=\"Master ["+getrunstr(start,end)+"]\" style=\"position:absolute;background-color:#CCCC80;left:"+getx(sd['mas']-1)+"px;top:"+gety(start/60)+"px;border:0;width:"+stwidth+"px;height:"+((end-start)/60*stheight/60)+"px\"></div>");
|
||||
//if(sd['mas']==0||start==end) return;
|
||||
//ctx.fillStyle="rgba(64,64,64,0.5)";
|
||||
//ctx.fillRect(getx(mas-1),gety(start/60),stwidth,(end-start)/60*stheight/60);
|
||||
}
|
||||
function plot_currtime() {
|
||||
w("<div style=\"position:absolute;left:"+(xstart-stwidth/2-10)+"px;top:"+gety(devmin)+"px;border:1px solid rgba(200,0,0,0.5);width:"+(winwidth-xstart+stwidth/2)+"px;height:0px;\"></div>");
|
||||
}
|
||||
function run_sched(simseconds,st_array,pid_array,et_array) { // run and plot schedule stored in array data
|
||||
var sid,endtime=simseconds;
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
if(pid_array[sid]) {
|
||||
if(sd['seq']==1) { // sequential
|
||||
plot_bar(sid,st_array[sid],pid_array[sid],et_array[sid]);
|
||||
if((sd['mas']>0)&&(sd['mas']!=sid+1)&&(sd['mo'][sid>>3]&(1<<(sid%8))))
|
||||
plot_master(st_array[sid]+sd['mton'], et_array[sid]+sd['mtoff']);
|
||||
endtime=et_array[sid];
|
||||
} else { // concurrent
|
||||
plot_bar(sid,simseconds,pid_array[sid],et_array[sid]);
|
||||
// check if this station activates master
|
||||
if((sd['mas']>0)&&(sd['mas']!=sid+1)&&(sd['mo'][sid>>3]&(1<<(sid%8))))
|
||||
endtime=(endtime>et_array[sid])?endtime:et_array[sid];
|
||||
}
|
||||
}
|
||||
}
|
||||
if(sd['seq']==0&&sd['mas']>0) plot_master(simseconds,endtime);
|
||||
return endtime;
|
||||
}
|
||||
function draw_title() {
|
||||
w("<div align=\"center\" style=\"background-color:#EEEEEE;position:absolute;left:0px;top:10px;border:2px solid gray;padding:5px 0px;width:"+(winwidth)+"px;border-radius:10px;box-shadow:3px 3px 2px #888888;\"><b>Program Preview of</b> ");
|
||||
w(days_str[simdate.getUTCDay()]+" "+(simdate.getUTCMonth()+1)+"/"+(simdate.getUTCDate())+" "+(simdate.getUTCFullYear()));
|
||||
w("<br><font size=2>(Hover over each colored bar to see tooltip)</font>");
|
||||
w("</div>");
|
||||
}
|
||||
|
||||
function draw_grid() {
|
||||
// draw table and grid
|
||||
for(sid=0;sid<=sd['nbrd']*8;sid++) {
|
||||
sn=sid+1;
|
||||
if(sid<sd['nbrd']*8) w("<div style=\"position:absolute;left:"+(xstart+sid*stwidth-10)+"px;top:"+(ystart-15)+"px;width:"+stwidth+"px;height:20px;border:0;padding:0;\"><font size=2>S"+(sn/10>>0)+(sn%10)+"</font></div>");
|
||||
w("<div style=\"position:absolute;left:"+getx(sid)+"px;top:"+(ystart-10)+"px;border:1px solid gray;width:0px;height:"+(winheight-ystart+30)+"px;\"></div>");
|
||||
}
|
||||
// horizontal grid, time
|
||||
for(t=0;t<=24;t++) {
|
||||
w("<div style=\"position:absolute;left:"+(xstart-stwidth/2-15)+"px;top:"+gety(t*60)+"px;border:1px solid gray;width:15px;height:0px;\"></div>");
|
||||
w("<div style=\"position:absolute;left:"+(xstart-stwidth/2-8)+"px;top:"+(gety(t*60)+stheight/2)+"px;border:1px solid gray;width:8px;height:0px;\"></div>");
|
||||
w("<div style=\"position:absolute;left:"+(xstart-70)+"px;top:"+(ystart+t*stheight-7)+"px;width=70;height:20px;border:0;padding:0;\"><font size=2>"+(t/10>>0)+(t%10)+":00</font></div>");
|
||||
}
|
||||
plot_currtime();
|
||||
}
|
||||
function draw_program() {
|
||||
// plot program data by a full simulation
|
||||
var simminutes=0,busy=0,match_found=0,bid,s,sid,pid,match=[0,0];
|
||||
var st_array=new Array(sd['nbrd']*8),pid_array=new Array(sd['nbrd']*8);
|
||||
var et_array=new Array(sd['nbrd']*8);
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
st_array[sid]=0;pid_array[sid]=0;et_array[sid]=0;
|
||||
}
|
||||
do { // check through every program
|
||||
busy=0;
|
||||
match_found=0;
|
||||
for(pid=0;pid<nprogs;pid++) {
|
||||
var prog=pd[pid];
|
||||
if(check_match(prog,simminutes,simdate,simday)) {
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
bid=sid>>3;s=sid%8;
|
||||
if(sd['mas']==(sid+1)) continue; // skip master station
|
||||
if(prog[7+bid]&(1<<s)) {
|
||||
et_array[sid]=prog[6]*sd['wl']/100>>0;pid_array[sid]=pid+1;
|
||||
match_found=1;
|
||||
}//if
|
||||
}//for_sid
|
||||
}//if_match
|
||||
}//for_pid
|
||||
if(match_found) {
|
||||
var acctime=simminutes*60;
|
||||
if(sd['seq']) { // sequential
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
if(et_array[sid]) {
|
||||
st_array[sid]=acctime;acctime+=et_array[sid];
|
||||
et_array[sid]=acctime;acctime+=sd['sdt'];
|
||||
busy=1;
|
||||
}//if
|
||||
}//for
|
||||
} else {
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
if(et_array[sid]) {
|
||||
st_array[sid]=simminutes*60;
|
||||
et_array[sid]=simminutes*60+et_array[sid];
|
||||
busy=1;
|
||||
}//if(et_array)
|
||||
}//for(sid)
|
||||
}//else(seq)
|
||||
}//if(match_found)
|
||||
if (busy) {
|
||||
var endminutes=run_sched(simminutes*60,st_array,pid_array,et_array)/60>>0;
|
||||
if(sd['seq']&&simminutes!=endminutes) simminutes=endminutes;
|
||||
else simminutes++;
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {st_array[sid]=0;pid_array[sid]=0;et_array[sid]=0;} // clear program data
|
||||
} else {
|
||||
simminutes++; // increment simulation time
|
||||
}
|
||||
} while(simminutes<24*60); // simulation ends
|
||||
window.scrollTo(0,gety((devmin/60>>0)*60)); // scroll to the hour line cloest to the current time
|
||||
}
|
||||
|
||||
draw_title();
|
||||
draw_grid();
|
||||
draw_program();
|
||||
|
|
|
@ -1,49 +1,49 @@
|
|||
// Javascript for printing OpenSprinkler homepage (program mode)
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
// print station status
|
||||
function rsn() {
|
||||
var p="";
|
||||
if(!sd['ipas']) p=prompt("Please enter your password:","");
|
||||
if(p!=null) window.location="/cv?pw="+p+"&rsn=1";
|
||||
}
|
||||
|
||||
w("<button style=\"height:32\" onclick=linkn(\"/gp?d=0\")>"+imgstr("preview")+"Program Preview</button>");
|
||||
w("<button style=\"height:32\" onclick=rsn()>"+imgstr("del")+"Stop All Stations</button>");
|
||||
w("<button style=\"height:32\" onclick=link(\"/vr\")>"+imgstr("start")+"Run-Once Program</button><br>");
|
||||
w("<p><b>Station Status</b>:</p>");
|
||||
w("<table border=1>");
|
||||
var bid,s,sid,sn,rem,remm,rems,off,pname;
|
||||
//off=((en==0||rd!=0||(urs!=0&&rs!=0))?1:0); // move rain stuff to after sid = ...
|
||||
off=((sd['en']==0)?1:0);
|
||||
for(bid=0;bid<sd['nbrd'];bid++){
|
||||
for(s=0;s<8;s++){
|
||||
w("<tr><td bgcolor=\"#E4E4E4\">");
|
||||
sid=bid*8+s;
|
||||
exempt=((sd['ir'][bid]&1<<s)?1:0);
|
||||
if(sd['en']==1) {off=(((sd['rd']!=0||(sd['urs']!=0&&sd['rs']!=0))&&exempt!=1)?1:0);}
|
||||
sn=sid+1;
|
||||
w(snames[sid]+': ');
|
||||
w("</td><td>");
|
||||
if(off) w("<strike>");
|
||||
if(sn==sd['mas']) {w(((sbits[bid]>>s)&1?("<b>On</b>").fontcolor("green"):("Off").fontcolor("black"))+" (<b>Master</b>)");}
|
||||
else {
|
||||
rem=ps[sid][1];remm=rem/60>>0;rems=rem%60;
|
||||
pname="P"+ps[sid][0];
|
||||
if(ps[sid][0]==255||ps[sid][0]==99) pname="Manual Program";
|
||||
if(ps[sid][0]==254||ps[sid][0]==98) pname="Run-once Program";
|
||||
if((sbits[bid]>>s)&1) {
|
||||
w(("<b>Running "+pname).fontcolor("green")+"</b> ("+(remm/10>>0)+(remm%10)+":"+(rems/10>>0)+(rems%10)+" remaining)");
|
||||
} else {
|
||||
if(ps[sid][0]==0) w("<font color=lightgray>(closed)</font>");
|
||||
else w(("Waiting "+pname+" ("+(remm/10>>0)+(remm%10)+":"+(rems/10>>0)+(rems%10)+" scheduled)").fontcolor("gray"));
|
||||
}
|
||||
}
|
||||
if(off) w("</strike>");
|
||||
w("</td></tr>");
|
||||
}
|
||||
}
|
||||
w("</table>");
|
||||
// Javascript for printing OpenSprinkler homepage (program mode)
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
// print station status
|
||||
function rsn() {
|
||||
var p="";
|
||||
if(!sd['ipas']) p=prompt("Please enter your password:","");
|
||||
if(p!=null) window.location="/cv?pw="+p+"&rsn=1";
|
||||
}
|
||||
|
||||
w("<button style=\"height:32\" onclick=linkn(\"/gp?d=0\")>"+imgstr("preview")+"Program Preview</button>");
|
||||
w("<button style=\"height:32\" onclick=rsn()>"+imgstr("del")+"Stop All Stations</button>");
|
||||
w("<button style=\"height:32\" onclick=link(\"/vr\")>"+imgstr("start")+"Run-Once Program</button><br>");
|
||||
w("<p><b>Station Status</b>:</p>");
|
||||
w("<table border=1>");
|
||||
var bid,s,sid,sn,rem,remm,rems,off,pname;
|
||||
//off=((en==0||rd!=0||(urs!=0&&rs!=0))?1:0); // move rain stuff to after sid = ...
|
||||
off=((sd['en']==0)?1:0);
|
||||
for(bid=0;bid<sd['nbrd'];bid++){
|
||||
for(s=0;s<8;s++){
|
||||
w("<tr><td bgcolor=\"#E4E4E4\">");
|
||||
sid=bid*8+s;
|
||||
exempt=((sd['ir'][bid]&1<<s)?1:0);
|
||||
if(sd['en']==1) {off=(((sd['rd']!=0||(sd['urs']!=0&&sd['rs']!=0))&&exempt!=1)?1:0);}
|
||||
sn=sid+1;
|
||||
w(snames[sid]+': ');
|
||||
w("</td><td>");
|
||||
if(off) w("<strike>");
|
||||
if(sn==sd['mas']) {w(((sbits[bid]>>s)&1?("<b>On</b>").fontcolor("green"):("Off").fontcolor("black"))+" (<b>Master</b>)");}
|
||||
else {
|
||||
rem=ps[sid][1];remm=rem/60>>0;rems=rem%60;
|
||||
pname="P"+ps[sid][0];
|
||||
if(ps[sid][0]==255||ps[sid][0]==99) pname="Manual Program";
|
||||
if(ps[sid][0]==254||ps[sid][0]==98) pname="Run-once Program";
|
||||
if((sbits[bid]>>s)&1) {
|
||||
w(("<b>Running "+pname).fontcolor("green")+"</b> ("+(remm/10>>0)+(remm%10)+":"+(rems/10>>0)+(rems%10)+" remaining)");
|
||||
} else {
|
||||
if(ps[sid][0]==0) w("<font color=lightgray>(closed)</font>");
|
||||
else w(("Waiting "+pname+" ("+(remm/10>>0)+(remm%10)+":"+(rems/10>>0)+(rems%10)+" scheduled)").fontcolor("gray"));
|
||||
}
|
||||
}
|
||||
if(off) w("</strike>");
|
||||
w("</td></tr>");
|
||||
}
|
||||
}
|
||||
w("</table>");
|
||||
|
|
|
@ -1,83 +1,83 @@
|
|||
// Javascript for printing OpenSprinkler option page
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
function w(s) {document.writeln(s);}
|
||||
function imgstr(s) {return "<img src=\""+baseurl+"/static/images/icons/svc_"+s+".png\" height=20 align=absmiddle> ";}
|
||||
function submit_form(f) {
|
||||
// process time zone value
|
||||
var th=parseInt(f.elements["th"].value,10);
|
||||
var tq=parseInt(f.elements["tq"].value,10);
|
||||
tq=(tq/15>>0)/4.0;
|
||||
th=th+(th>=0?tq:-tq);
|
||||
// huge hack, needs to find a more elegant way
|
||||
f.elements["otz"].value=((th+12)*4)>>0;
|
||||
f.elements["ohtp"].value=(f.elements["htp"].value)&0xff;
|
||||
f.elements["ohtp2"].value=(f.elements["htp"].value>>8)&0xff;
|
||||
//f.elements["omas"].value=f.elements["mas"].value;
|
||||
f.submit();
|
||||
}
|
||||
function fcancel() {window.location="/";}
|
||||
function ftoggle() {
|
||||
var oid,tip;
|
||||
var state=document.getElementById("tip0").style.display=="none";
|
||||
for(oid=0;oid<opts.length;oid++){
|
||||
tip=document.getElementById("tip"+oid);
|
||||
if(tip!=null) tip.style.display=state?"inline":"none";
|
||||
}
|
||||
document.getElementById("tooltips").innerHTML = (state?"Hide Tooltips":"Show Tooltips");
|
||||
}
|
||||
w("<div align=\"center\" style=\"background-color:#EEEEEE;border:2px solid gray;padding:5px 10px;width:240px;border-radius:10px;box-shadow:3px 3px 2px #888888;\">");
|
||||
w("<b>Set Options</b>:<br><font size=2>(Hover on each option to see tooltip)</font></div>");
|
||||
w("<p></p>");
|
||||
w("<button id=\"tooltips\" style=\"height:24\" onclick=\"ftoggle();return false;\">Show Tooltips</button>");
|
||||
// print html form
|
||||
w("<form name=of action=co method=get>");
|
||||
var oid,label,isbool,value,name,ipasvalue=0;
|
||||
for(oid=0;oid<opts.length;oid++){
|
||||
label=opts[oid][0];
|
||||
datatype=opts[oid][1];
|
||||
value=sd[opts[oid][2]];
|
||||
name=opts[oid][2];
|
||||
tooltip=opts[oid][3];
|
||||
if(name=="ipas") ipasvalue=value;
|
||||
if(datatype == "boolean") {
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <input type=checkbox "+(value>0?"checked":"")+" name=o"+name+">");
|
||||
} else if (datatype == "string") {
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <input type=text size=31 maxlength=31 value='"+value+"' name=o"+name+">");
|
||||
} else {
|
||||
switch (name) {
|
||||
case "tz":
|
||||
w("<input type=hidden value=0 name=o"+name+">");
|
||||
tz=value-48;
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> GMT<input type=text size=3 maxlength=3 value="+(tz>=0?"+":"-")+(Math.abs(tz)/4>>0)+" name=th>");
|
||||
w(":<input type=text size=3 maxlength=3 value="+((Math.abs(tz)%4)*15/10>>0)+((Math.abs(tz)%4)*15%10)+" name=tq>");
|
||||
break;
|
||||
case "mas":
|
||||
//w("<input type=hidden value=0 name=o"+name+">");
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <select name=o"+name+"><option "+(value==0?" selected ":" ")+"value=0>None</option>");
|
||||
for(i=1;i<=8;i++) w("<option "+(value==i?" selected ":" ")+"value="+i+">Station 0"+i+"</option>");
|
||||
w("</select>");
|
||||
break;
|
||||
case "htp":
|
||||
w("<input type=hidden value=0 name=o"+name+"><input type=hidden value=0 name=o"+name+"2>");
|
||||
var port=value+(opts[(oid+1)][2]<<8);
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <input type=text size=5 maxlength=5 value="+port+" name=ohtp>");
|
||||
break;
|
||||
case "nbrd":
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <input type=text size=3 maxlength=3 value="+(value-1)+" name=o"+name+">");
|
||||
break;
|
||||
default:
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <input type=text size=3 maxlength=3 value="+value+" name=o"+name+">");
|
||||
}
|
||||
}
|
||||
//w("</p>");
|
||||
w(" <span style=\"background-color:#FFF2B8;display:none\" id=tip"+oid+"><font size=2>"+tooltip+"</font></span></p>");
|
||||
}
|
||||
w("<h4>Password:<input type=password size=10 "+(ipasvalue?"disabled":"")+" name=pw></h4>");
|
||||
w("<button style=\"height:36\" onclick=\"submit_form(of)\">"+imgstr("submit")+"<b>Submit Changes</b></button>");
|
||||
w("<button style=\"height:36\" onclick=\"fcancel();return false;\">"+imgstr("delall")+"Cancel</button>");
|
||||
w("<h4>Change password</b>:<input type=password size=10 name=npw> Confirm: <input type=password size=10 name=cpw></h4>");
|
||||
w("</form>");
|
||||
// Javascript for printing OpenSprinkler option page
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
function w(s) {document.writeln(s);}
|
||||
function imgstr(s) {return "<img src=\""+baseurl+"/static/images/icons/svc_"+s+".png\" height=20 align=absmiddle> ";}
|
||||
function submit_form(f) {
|
||||
// process time zone value
|
||||
var th=parseInt(f.elements["th"].value,10);
|
||||
var tq=parseInt(f.elements["tq"].value,10);
|
||||
tq=(tq/15>>0)/4.0;
|
||||
th=th+(th>=0?tq:-tq);
|
||||
// huge hack, needs to find a more elegant way
|
||||
f.elements["otz"].value=((th+12)*4)>>0;
|
||||
f.elements["ohtp"].value=(f.elements["htp"].value)&0xff;
|
||||
f.elements["ohtp2"].value=(f.elements["htp"].value>>8)&0xff;
|
||||
//f.elements["omas"].value=f.elements["mas"].value;
|
||||
f.submit();
|
||||
}
|
||||
function fcancel() {window.location="/";}
|
||||
function ftoggle() {
|
||||
var oid,tip;
|
||||
var state=document.getElementById("tip0").style.display=="none";
|
||||
for(oid=0;oid<opts.length;oid++){
|
||||
tip=document.getElementById("tip"+oid);
|
||||
if(tip!=null) tip.style.display=state?"inline":"none";
|
||||
}
|
||||
document.getElementById("tooltips").innerHTML = (state?"Hide Tooltips":"Show Tooltips");
|
||||
}
|
||||
w("<div align=\"center\" style=\"background-color:#EEEEEE;border:2px solid gray;padding:5px 10px;width:240px;border-radius:10px;box-shadow:3px 3px 2px #888888;\">");
|
||||
w("<b>Set Options</b>:<br><font size=2>(Hover on each option to see tooltip)</font></div>");
|
||||
w("<p></p>");
|
||||
w("<button id=\"tooltips\" style=\"height:24\" onclick=\"ftoggle();return false;\">Show Tooltips</button>");
|
||||
// print html form
|
||||
w("<form name=of action=co method=get>");
|
||||
var oid,label,isbool,value,name,ipasvalue=0;
|
||||
for(oid=0;oid<opts.length;oid++){
|
||||
label=opts[oid][0];
|
||||
datatype=opts[oid][1];
|
||||
value=sd[opts[oid][2]];
|
||||
name=opts[oid][2];
|
||||
tooltip=opts[oid][3];
|
||||
if(name=="ipas") ipasvalue=value;
|
||||
if(datatype == "boolean") {
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <input type=checkbox "+(value>0?"checked":"")+" name=o"+name+">");
|
||||
} else if (datatype == "string") {
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <input type=text size=31 maxlength=31 value='"+value+"' name=o"+name+">");
|
||||
} else {
|
||||
switch (name) {
|
||||
case "tz":
|
||||
w("<input type=hidden value=0 name=o"+name+">");
|
||||
tz=value-48;
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> GMT<input type=text size=3 maxlength=3 value="+(tz>=0?"+":"-")+(Math.abs(tz)/4>>0)+" name=th>");
|
||||
w(":<input type=text size=3 maxlength=3 value="+((Math.abs(tz)%4)*15/10>>0)+((Math.abs(tz)%4)*15%10)+" name=tq>");
|
||||
break;
|
||||
case "mas":
|
||||
//w("<input type=hidden value=0 name=o"+name+">");
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <select name=o"+name+"><option "+(value==0?" selected ":" ")+"value=0>None</option>");
|
||||
for(i=1;i<=8;i++) w("<option "+(value==i?" selected ":" ")+"value="+i+">Station 0"+i+"</option>");
|
||||
w("</select>");
|
||||
break;
|
||||
case "htp":
|
||||
w("<input type=hidden value=0 name=o"+name+"><input type=hidden value=0 name=o"+name+"2>");
|
||||
var port=value+(opts[(oid+1)][2]<<8);
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <input type=text size=5 maxlength=5 value="+port+" name=ohtp>");
|
||||
break;
|
||||
case "nbrd":
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <input type=text size=3 maxlength=3 value="+(value-1)+" name=o"+name+">");
|
||||
break;
|
||||
default:
|
||||
w("<p title=\""+tooltip+"\"><b>"+label+":</b> <input type=text size=3 maxlength=3 value="+value+" name=o"+name+">");
|
||||
}
|
||||
}
|
||||
//w("</p>");
|
||||
w(" <span style=\"background-color:#FFF2B8;display:none\" id=tip"+oid+"><font size=2>"+tooltip+"</font></span></p>");
|
||||
}
|
||||
w("<h4>Password:<input type=password size=10 "+(ipasvalue?"disabled":"")+" name=pw></h4>");
|
||||
w("<button style=\"height:36\" onclick=\"submit_form(of)\">"+imgstr("submit")+"<b>Submit Changes</b></button>");
|
||||
w("<button style=\"height:36\" onclick=\"fcancel();return false;\">"+imgstr("delall")+"Cancel</button>");
|
||||
w("<h4>Change password</b>:<input type=password size=10 name=npw> Confirm: <input type=password size=10 name=cpw></h4>");
|
||||
w("</form>");
|
||||
|
|
|
@ -1,92 +1,92 @@
|
|||
// Javascript for printing OpenSprinkler schedule page
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Rayshobby.net, Sep 2012
|
||||
|
||||
var str_days=["Mon","Tue","Wed","Thur","Fri","Sat","Sun"];
|
||||
function w(s) {document.writeln(s);}
|
||||
function imgstr(s) {return "<img src=\""+baseurl+"/static/images/icons/svc_"+s+".png\" height=20 align=absmiddle> ";}
|
||||
function del(form,idx) {
|
||||
var p="";
|
||||
if(!sd['ipas']) p=prompt("Please enter your password:","");
|
||||
if(p!=null){form.elements[0].value=p;form.elements[1].value=idx;form.submit();}
|
||||
}
|
||||
function mod(form,idx) {form.elements[0].value=idx;form.submit();}
|
||||
|
||||
function rnow(form,idx) {
|
||||
//form.elements[0].value=idx;form.submit();
|
||||
var p="";
|
||||
if(!ipas) p=prompt("Please enter your password:","");
|
||||
if(p!=null){form.elements[0].value=p;form.elements[1].value=idx;form.submit();}
|
||||
}
|
||||
|
||||
// parse and print days
|
||||
function pdays(days){
|
||||
if((days[0]&0x80)&&(days[1]>1)){
|
||||
// this is an interval program
|
||||
days[0]=days[0]&0x7f;
|
||||
w("Every "+days[1]+" days, starting in "+days[0]+" days.");
|
||||
} else {
|
||||
// this is a weekly program
|
||||
for(d=0;d<7;d++) {if(days[0]&(1<<d)) {w(str_days[d]);}}
|
||||
if((days[0]&0x80)&&(days[1]==0)) {w("(Even days only)");}
|
||||
if((days[0]&0x80)&&(days[1]==1)) {w("(Odd days only)");}
|
||||
}
|
||||
}
|
||||
// parse and print stations
|
||||
function pstations(data){
|
||||
w("<table border=1 cellpadding=3px>");
|
||||
var bid,s,bits,sid;
|
||||
for(bid=0;bid<sd['nbrd'];bid++){
|
||||
bits=data[bid+7];
|
||||
for(s=0;s<8;s++){
|
||||
sid=bid*8+s;
|
||||
if(sid%4==0) w("<tr>");
|
||||
w("<td style=\"background-color:");
|
||||
if(bits&(1<<s)) w("#9AFA9A\"><font size=2 color=black>"+snames[sid]);
|
||||
else w("white\"><font size=2 color=lightgray>"+snames[sid]);
|
||||
w("</font></td>");
|
||||
if(sid%4==3) w("</tr>");
|
||||
}
|
||||
}
|
||||
w("</table>\n");
|
||||
}
|
||||
function fcancel() {window.location="/";}
|
||||
function fplot() {window.open("/gp?d=0","_blank");}
|
||||
w("<form name=df action=dp method=get><input type=hidden name=pw><input type=hidden name=pid></form>");
|
||||
w("<form name=rn action=rp method=get><input type=hidden name=pw><input type=hidden name=pid></form>");
|
||||
w("<form name=mf action=mp method=get><input type=hidden name=pid></form>");
|
||||
w("<button style=\"height:44\" onclick=\"fcancel()\">"+imgstr("back")+"Back</button>");
|
||||
w("<button style=\"height:44\" onclick=\"mod(mf,-1)\">"+imgstr("addall")+"<b>Add a New Program</b></button>");
|
||||
w("<button style=\"height:44\" onclick=\"del(df,-1)\">"+imgstr("delall")+"Delete All</button>");
|
||||
w("<button style=\"height:44\" onclick=\"fplot()\">"+imgstr("preview")+"Preview</button><hr>");
|
||||
w("<b>Total number of programs: "+nprogs+" (maximum is "+sd['mnp']+")</b><br>");
|
||||
// print programs
|
||||
var pid,st,et,iv,du,sd;
|
||||
for(pid=0;pid<nprogs;pid++) {
|
||||
w("<span style=\"line-height:22px\">");
|
||||
if(pd[pid][0]==0) w("<strike>");
|
||||
w("<br><b>Program "+(pid+1)+": ");
|
||||
// parse and print days
|
||||
pdays([pd[pid][1],pd[pid][2]]);
|
||||
w("</b>");
|
||||
if((pd[pid][0]&0x01)==0) w("</strike><font color=red>(Disabled)</font>");
|
||||
// print time
|
||||
st=pd[pid][3];
|
||||
et=pd[pid][4];
|
||||
iv=pd[pid][5];
|
||||
du=pd[pid][6];
|
||||
w("<br><b>Time</b>: "+((st/60>>0)/10>>0)+((st/60>>0)%10)+":"+((st%60)/10>>0)+((st%60)%10));
|
||||
w(" - "+((et/60>>0)/10>>0)+((et/60>>0)%10)+":"+((et%60)/10>>0)+((et%60)%10));
|
||||
w(",<b> Every</b> "+(iv/60>>0)+" hrs "+(iv%60)+" mins,");
|
||||
w("<br><b>Run</b>: "+(du/60>>0)+" mins "+(du%60)+" secs.<br>");
|
||||
// parse and print stations
|
||||
pstations(pd[pid]);
|
||||
w("</span>");
|
||||
// print buttons
|
||||
w("<br><button style=\"height:28\" onclick=del(df,"+pid+")>Delete</button>");
|
||||
w("<button style=\"height:28\" onclick=mod(mf,"+pid+")>Modify</button>");
|
||||
w("<button style=\"height:28\" onclick=rnow(rn,"+pid+")>Run Now</button>");
|
||||
w("<hr>");
|
||||
}
|
||||
// Javascript for printing OpenSprinkler schedule page
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Rayshobby.net, Sep 2012
|
||||
|
||||
var str_days=["Mon","Tue","Wed","Thur","Fri","Sat","Sun"];
|
||||
function w(s) {document.writeln(s);}
|
||||
function imgstr(s) {return "<img src=\""+baseurl+"/static/images/icons/svc_"+s+".png\" height=20 align=absmiddle> ";}
|
||||
function del(form,idx) {
|
||||
var p="";
|
||||
if(!sd['ipas']) p=prompt("Please enter your password:","");
|
||||
if(p!=null){form.elements[0].value=p;form.elements[1].value=idx;form.submit();}
|
||||
}
|
||||
function mod(form,idx) {form.elements[0].value=idx;form.submit();}
|
||||
|
||||
function rnow(form,idx) {
|
||||
//form.elements[0].value=idx;form.submit();
|
||||
var p="";
|
||||
if(!ipas) p=prompt("Please enter your password:","");
|
||||
if(p!=null){form.elements[0].value=p;form.elements[1].value=idx;form.submit();}
|
||||
}
|
||||
|
||||
// parse and print days
|
||||
function pdays(days){
|
||||
if((days[0]&0x80)&&(days[1]>1)){
|
||||
// this is an interval program
|
||||
days[0]=days[0]&0x7f;
|
||||
w("Every "+days[1]+" days, starting in "+days[0]+" days.");
|
||||
} else {
|
||||
// this is a weekly program
|
||||
for(d=0;d<7;d++) {if(days[0]&(1<<d)) {w(str_days[d]);}}
|
||||
if((days[0]&0x80)&&(days[1]==0)) {w("(Even days only)");}
|
||||
if((days[0]&0x80)&&(days[1]==1)) {w("(Odd days only)");}
|
||||
}
|
||||
}
|
||||
// parse and print stations
|
||||
function pstations(data){
|
||||
w("<table border=1 cellpadding=3px>");
|
||||
var bid,s,bits,sid;
|
||||
for(bid=0;bid<sd['nbrd'];bid++){
|
||||
bits=data[bid+7];
|
||||
for(s=0;s<8;s++){
|
||||
sid=bid*8+s;
|
||||
if(sid%4==0) w("<tr>");
|
||||
w("<td style=\"background-color:");
|
||||
if(bits&(1<<s)) w("#9AFA9A\"><font size=2 color=black>"+snames[sid]);
|
||||
else w("white\"><font size=2 color=lightgray>"+snames[sid]);
|
||||
w("</font></td>");
|
||||
if(sid%4==3) w("</tr>");
|
||||
}
|
||||
}
|
||||
w("</table>\n");
|
||||
}
|
||||
function fcancel() {window.location="/";}
|
||||
function fplot() {window.open("/gp?d=0","_blank");}
|
||||
w("<form name=df action=dp method=get><input type=hidden name=pw><input type=hidden name=pid></form>");
|
||||
w("<form name=rn action=rp method=get><input type=hidden name=pw><input type=hidden name=pid></form>");
|
||||
w("<form name=mf action=mp method=get><input type=hidden name=pid></form>");
|
||||
w("<button style=\"height:44\" onclick=\"fcancel()\">"+imgstr("back")+"Back</button>");
|
||||
w("<button style=\"height:44\" onclick=\"mod(mf,-1)\">"+imgstr("addall")+"<b>Add a New Program</b></button>");
|
||||
w("<button style=\"height:44\" onclick=\"del(df,-1)\">"+imgstr("delall")+"Delete All</button>");
|
||||
w("<button style=\"height:44\" onclick=\"fplot()\">"+imgstr("preview")+"Preview</button><hr>");
|
||||
w("<b>Total number of programs: "+nprogs+" (maximum is "+sd['mnp']+")</b><br>");
|
||||
// print programs
|
||||
var pid,st,et,iv,du,sd;
|
||||
for(pid=0;pid<nprogs;pid++) {
|
||||
w("<span style=\"line-height:22px\">");
|
||||
if(pd[pid][0]==0) w("<strike>");
|
||||
w("<br><b>Program "+(pid+1)+": ");
|
||||
// parse and print days
|
||||
pdays([pd[pid][1],pd[pid][2]]);
|
||||
w("</b>");
|
||||
if((pd[pid][0]&0x01)==0) w("</strike><font color=red>(Disabled)</font>");
|
||||
// print time
|
||||
st=pd[pid][3];
|
||||
et=pd[pid][4];
|
||||
iv=pd[pid][5];
|
||||
du=pd[pid][6];
|
||||
w("<br><b>Time</b>: "+((st/60>>0)/10>>0)+((st/60>>0)%10)+":"+((st%60)/10>>0)+((st%60)%10));
|
||||
w(" - "+((et/60>>0)/10>>0)+((et/60>>0)%10)+":"+((et%60)/10>>0)+((et%60)%10));
|
||||
w(",<b> Every</b> "+(iv/60>>0)+" hrs "+(iv%60)+" mins,");
|
||||
w("<br><b>Run</b>: "+(du/60>>0)+" mins "+(du%60)+" secs.<br>");
|
||||
// parse and print stations
|
||||
pstations(pd[pid]);
|
||||
w("</span>");
|
||||
// print buttons
|
||||
w("<br><button style=\"height:28\" onclick=del(df,"+pid+")>Delete</button>");
|
||||
w("<button style=\"height:28\" onclick=mod(mf,"+pid+")>Modify</button>");
|
||||
w("<button style=\"height:28\" onclick=rnow(rn,"+pid+")>Run Now</button>");
|
||||
w("<hr>");
|
||||
}
|
||||
|
|
|
@ -1,53 +1,53 @@
|
|||
// Javascript for printing OpenSprinkler Run Once page
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
function w(s) {document.writeln(s);}
|
||||
function imgstr(s) {return "<img src=\""+baseurl+"/static/images/icons/svc_"+s+".png\" height=20 align=absmiddle> ";}
|
||||
function rst(f) {
|
||||
var sid,sn;
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
if(sid+1==sd['mas']) continue;
|
||||
f.elements["mm"+sid].value=0;
|
||||
f.elements["ss"+sid].value=0;
|
||||
}
|
||||
}
|
||||
function fsubmit(f) {
|
||||
var comm="/cr?pw="+(sd['ipas']?"":f.elements["pw"].value)+"&t=[";
|
||||
var sid,strmm,strss,mm,ss,matchfound=0;
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
if(sid+1==sd['mas']) {comm+="0,";continue;}
|
||||
strmm=f.elements["mm"+sid].value;
|
||||
strss=f.elements["ss"+sid].value;
|
||||
mm=(strmm=="")?0:parseInt(strmm);
|
||||
ss=(strss=="")?0:parseInt(strss);
|
||||
if(!(mm>=0&&ss>=0&&ss<60)) {alert("Timer values wrong: "+strmm+":"+strss);return;}
|
||||
if(mm*60+ss>0) matchfound=1;
|
||||
comm+=(mm*60+ss)+",";
|
||||
}
|
||||
comm+="0]"
|
||||
if(!matchfound) {alert("No station is schedule to run");return;}
|
||||
window.location=comm;
|
||||
}
|
||||
function fcancel() {window.location="/";}
|
||||
w("<div align=\"center\" style=\"background-color:#EEEEEE;border:2px solid gray;padding:5px 10px;width:240px;border-radius:10px;box-shadow:3px 3px 2px #888888;\">");
|
||||
w("<font size=3><b>Run-Once Program:</b></font></div><p></p>");
|
||||
var sid;
|
||||
w("<table border=1>");
|
||||
w("<form name=rf action=cr method=get>");
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
w("<tr><td bgcolor=\"#E4E4E4\">");
|
||||
w(snames[sid]+": </td><td>");
|
||||
if (sid+1==sd['mas']) {w("(<b>Master</b>)<br>");continue;}
|
||||
w("<input type=text size=3 maxlength=3 value=0 name=mm"+sid+">:");
|
||||
w("<input type=text size=2 maxlength=2 value=0 name=ss"+sid+"> (mm:ss)<br>");
|
||||
w("</td>");
|
||||
}
|
||||
w("</table>");
|
||||
w("<hr><font size=3><b>Password:</b><input type=password size=10 "+(sd['ipas']?"disabled":"")+" name=pw></font><p></p>");
|
||||
w("</form></span>");
|
||||
w("<button style=\"height:36\" onclick=\"fsubmit(rf)\">"+imgstr("submit")+"<b>Run Now</b></button>");
|
||||
w("<button style=\"height:36\" onclick=\"rst(rf)\">"+imgstr("reset")+"Reset Time</button>");
|
||||
w("<button style=\"height:36\" onclick=\"fcancel()\">"+imgstr("delall")+"Cancel</button>");
|
||||
// Javascript for printing OpenSprinkler Run Once page
|
||||
// Firmware v1.8
|
||||
// All content is published under:
|
||||
// Creative Commons Attribution ShareAlike 3.0 License
|
||||
// Sep 2012, Rayshobby.net
|
||||
|
||||
function w(s) {document.writeln(s);}
|
||||
function imgstr(s) {return "<img src=\""+baseurl+"/static/images/icons/svc_"+s+".png\" height=20 align=absmiddle> ";}
|
||||
function rst(f) {
|
||||
var sid,sn;
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
if(sid+1==sd['mas']) continue;
|
||||
f.elements["mm"+sid].value=0;
|
||||
f.elements["ss"+sid].value=0;
|
||||
}
|
||||
}
|
||||
function fsubmit(f) {
|
||||
var comm="/cr?pw="+(sd['ipas']?"":f.elements["pw"].value)+"&t=[";
|
||||
var sid,strmm,strss,mm,ss,matchfound=0;
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
if(sid+1==sd['mas']) {comm+="0,";continue;}
|
||||
strmm=f.elements["mm"+sid].value;
|
||||
strss=f.elements["ss"+sid].value;
|
||||
mm=(strmm=="")?0:parseInt(strmm);
|
||||
ss=(strss=="")?0:parseInt(strss);
|
||||
if(!(mm>=0&&ss>=0&&ss<60)) {alert("Timer values wrong: "+strmm+":"+strss);return;}
|
||||
if(mm*60+ss>0) matchfound=1;
|
||||
comm+=(mm*60+ss)+",";
|
||||
}
|
||||
comm+="0]"
|
||||
if(!matchfound) {alert("No station is schedule to run");return;}
|
||||
window.location=comm;
|
||||
}
|
||||
function fcancel() {window.location="/";}
|
||||
w("<div align=\"center\" style=\"background-color:#EEEEEE;border:2px solid gray;padding:5px 10px;width:240px;border-radius:10px;box-shadow:3px 3px 2px #888888;\">");
|
||||
w("<font size=3><b>Run-Once Program:</b></font></div><p></p>");
|
||||
var sid;
|
||||
w("<table border=1>");
|
||||
w("<form name=rf action=cr method=get>");
|
||||
for(sid=0;sid<sd['nbrd']*8;sid++) {
|
||||
w("<tr><td bgcolor=\"#E4E4E4\">");
|
||||
w(snames[sid]+": </td><td>");
|
||||
if (sid+1==sd['mas']) {w("(<b>Master</b>)<br>");continue;}
|
||||
w("<input type=text size=3 maxlength=3 value=0 name=mm"+sid+">:");
|
||||
w("<input type=text size=2 maxlength=2 value=0 name=ss"+sid+"> (mm:ss)<br>");
|
||||
w("</td>");
|
||||
}
|
||||
w("</table>");
|
||||
w("<hr><font size=3><b>Password:</b><input type=password size=10 "+(sd['ipas']?"disabled":"")+" name=pw></font><p></p>");
|
||||
w("</form></span>");
|
||||
w("<button style=\"height:36\" onclick=\"fsubmit(rf)\">"+imgstr("submit")+"<b>Run Now</b></button>");
|
||||
w("<button style=\"height:36\" onclick=\"rst(rf)\">"+imgstr("reset")+"Reset Time</button>");
|
||||
w("<button style=\"height:36\" onclick=\"fcancel()\">"+imgstr("delall")+"Cancel</button>");
|
||||
|
|
|
@ -1,136 +1,136 @@
|
|||
$def with (records)
|
||||
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Water Log</title>
|
||||
<meta content="width=640" name="viewport">
|
||||
<link href="./static/images/icons/favicon.ico" rel="icon" type="image/x-icon" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<style>
|
||||
#overlay {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
height:100%;
|
||||
text-align:center;
|
||||
z-index: 1000;
|
||||
background-image:url(../static/images/misc/BlueTile.png);
|
||||
}
|
||||
#overlay div {
|
||||
width:200px;
|
||||
margin: 100px auto;
|
||||
background-color: #fff;
|
||||
border:1px solid #000;
|
||||
padding:10px;
|
||||
text-align:left;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-family: Sans-serif;
|
||||
}
|
||||
|
||||
.pwspan {
|
||||
float:left;
|
||||
border-style:none;
|
||||
clear:both;
|
||||
margin:10px 0px 10px 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function fcancel() {window.location="/";}
|
||||
function ocancel() {window.location="/vl";}
|
||||
function link(s) {window.location=s;}
|
||||
function del(form) {
|
||||
var p="";
|
||||
if(!$sd['ipas']) p=prompt("Password Required","");
|
||||
if(p!=null){form.elements[0].value=p;form.submit();}
|
||||
}
|
||||
function subm(form) {
|
||||
form.submit();
|
||||
}
|
||||
function hidepw(){
|
||||
if ($sd['ipas']) {
|
||||
document.getElementById("pwarea").style.display = "none";
|
||||
}
|
||||
else {
|
||||
document.getElementById("pwarea").style.display = "block";
|
||||
}
|
||||
}
|
||||
function overlay() {
|
||||
el = document.getElementById("overlay");
|
||||
el.style.visibility = (el.style.visibility == "visible") ? "hidden" : "visible";
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="hidepw()">
|
||||
|
||||
<form method="get" action="cl" name="df">
|
||||
<input type="hidden" name="pw">
|
||||
</form>
|
||||
<form method="get" action="mp" name="mf">
|
||||
<input type="hidden" name="pid" value="-1">
|
||||
</form>
|
||||
<button onclick="fcancel()" style="height:44">
|
||||
<img align="absmiddle" height="20" src="../static/images/icons/svc_back.png">
|
||||
Back
|
||||
</button>
|
||||
<button onclick="link('/vl')" style="height:44">
|
||||
<img align="absmiddle" height="20" src="../static/images/icons/svc_refresh.png">
|
||||
<b>Refresh</b>
|
||||
</button>
|
||||
<button onclick="del(df, 0)" style="height:44">
|
||||
<img align="absmiddle" height="20" src="../static/images/icons/svc_delall.png">
|
||||
Delete All
|
||||
</button>
|
||||
<button onclick="overlay()" style="height:44">
|
||||
<img align="absmiddle" height="20" src="../static/images/icons/svc_options.png">
|
||||
Log Options
|
||||
</button>
|
||||
|
||||
$code:
|
||||
if sd['lg'] == 1:
|
||||
log_state = "Enabled"
|
||||
log_option = "checked"
|
||||
else:
|
||||
log_state = "Disabled"
|
||||
log_option = ""
|
||||
|
||||
<br><br>
|
||||
Logging $log_state
|
||||
<br>
|
||||
<b>Total number of records: $(len(records)-1)</b>
|
||||
<hr>
|
||||
|
||||
<table>
|
||||
$for r in records:
|
||||
<tr class="log_rec">
|
||||
$for d in r:
|
||||
<td align='center'>$d</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id="overlay">
|
||||
<div>
|
||||
<form name="logopts" action="/lo" method="get">
|
||||
<p><h2>Log Options</h2></p>
|
||||
<label for="log">Enable Logging</label> <input type="checkbox" id="log" name="log" $log_option ><br>
|
||||
<label for="max">Maximum records to keep:</label> <input type="text" size="4" value="$sd['lr']" id="max" name="nrecords">(0 = no limit)<br>
|
||||
<span id='pwarea' style='display:block'; class="pwspan">
|
||||
<label for="pw">Password Required:</label> <input type="password" size="10"id="pw">
|
||||
</span><br>
|
||||
<input type="submit" value="Submit">
|
||||
<button type="button" onclick="overlay()"; return false;>Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
$def with (records)
|
||||
|
||||
<!doctype html>
|
||||
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>Water Log</title>
|
||||
<meta content="width=640" name="viewport">
|
||||
<link href="./static/images/icons/favicon.ico" rel="icon" type="image/x-icon" />
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
|
||||
<style>
|
||||
#overlay {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
left: 0px;
|
||||
top: 0px;
|
||||
width: 100%;
|
||||
height:100%;
|
||||
text-align:center;
|
||||
z-index: 1000;
|
||||
background-image:url(../static/images/misc/BlueTile.png);
|
||||
}
|
||||
#overlay div {
|
||||
width:200px;
|
||||
margin: 100px auto;
|
||||
background-color: #fff;
|
||||
border:1px solid #000;
|
||||
padding:10px;
|
||||
text-align:left;
|
||||
}
|
||||
h2 {
|
||||
text-align: center;
|
||||
font-family: Sans-serif;
|
||||
}
|
||||
|
||||
.pwspan {
|
||||
float:left;
|
||||
border-style:none;
|
||||
clear:both;
|
||||
margin:10px 0px 10px 0px;
|
||||
}
|
||||
|
||||
</style>
|
||||
|
||||
<script>
|
||||
function fcancel() {window.location="/";}
|
||||
function ocancel() {window.location="/vl";}
|
||||
function link(s) {window.location=s;}
|
||||
function del(form) {
|
||||
var p="";
|
||||
if(!$sd['ipas']) p=prompt("Password Required","");
|
||||
if(p!=null){form.elements[0].value=p;form.submit();}
|
||||
}
|
||||
function subm(form) {
|
||||
form.submit();
|
||||
}
|
||||
function hidepw(){
|
||||
if ($sd['ipas']) {
|
||||
document.getElementById("pwarea").style.display = "none";
|
||||
}
|
||||
else {
|
||||
document.getElementById("pwarea").style.display = "block";
|
||||
}
|
||||
}
|
||||
function overlay() {
|
||||
el = document.getElementById("overlay");
|
||||
el.style.visibility = (el.style.visibility == "visible") ? "hidden" : "visible";
|
||||
}
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body onload="hidepw()">
|
||||
|
||||
<form method="get" action="cl" name="df">
|
||||
<input type="hidden" name="pw">
|
||||
</form>
|
||||
<form method="get" action="mp" name="mf">
|
||||
<input type="hidden" name="pid" value="-1">
|
||||
</form>
|
||||
<button onclick="fcancel()" style="height:44">
|
||||
<img align="absmiddle" height="20" src="../static/images/icons/svc_back.png">
|
||||
Back
|
||||
</button>
|
||||
<button onclick="link('/vl')" style="height:44">
|
||||
<img align="absmiddle" height="20" src="../static/images/icons/svc_refresh.png">
|
||||
<b>Refresh</b>
|
||||
</button>
|
||||
<button onclick="del(df, 0)" style="height:44">
|
||||
<img align="absmiddle" height="20" src="../static/images/icons/svc_delall.png">
|
||||
Delete All
|
||||
</button>
|
||||
<button onclick="overlay()" style="height:44">
|
||||
<img align="absmiddle" height="20" src="../static/images/icons/svc_options.png">
|
||||
Log Options
|
||||
</button>
|
||||
|
||||
$code:
|
||||
if sd['lg'] == 1:
|
||||
log_state = "Enabled"
|
||||
log_option = "checked"
|
||||
else:
|
||||
log_state = "Disabled"
|
||||
log_option = ""
|
||||
|
||||
<br><br>
|
||||
Logging $log_state
|
||||
<br>
|
||||
<b>Total number of records: $(len(records)-1)</b>
|
||||
<hr>
|
||||
|
||||
<table>
|
||||
$for r in records:
|
||||
<tr class="log_rec">
|
||||
$for d in r:
|
||||
<td align='center'>$d</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<div id="overlay">
|
||||
<div>
|
||||
<form name="logopts" action="/lo" method="get">
|
||||
<p><h2>Log Options</h2></p>
|
||||
<label for="log">Enable Logging</label> <input type="checkbox" id="log" name="log" $log_option ><br>
|
||||
<label for="max">Maximum records to keep:</label> <input type="text" size="4" value="$sd['lr']" id="max" name="nrecords">(0 = no limit)<br>
|
||||
<span id='pwarea' style='display:block'; class="pwspan">
|
||||
<label for="pw">Password Required:</label> <input type="password" size="10"id="pw">
|
||||
</span><br>
|
||||
<input type="submit" value="Submit">
|
||||
<button type="button" onclick="overlay()"; return false;>Cancel</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
|
@ -1,33 +1,33 @@
|
|||
#!/usr/bin/env python
|
||||
"""web.py: makes web apps (http://webpy.org)"""
|
||||
|
||||
from __future__ import generators
|
||||
|
||||
__version__ = "0.37"
|
||||
__author__ = [
|
||||
"Aaron Swartz <me@aaronsw.com>",
|
||||
"Anand Chitipothu <anandology@gmail.com>"
|
||||
]
|
||||
__license__ = "public domain"
|
||||
__contributors__ = "see http://webpy.org/changes"
|
||||
|
||||
import utils, db, net, wsgi, http, webapi, httpserver, debugerror
|
||||
import template, form
|
||||
|
||||
import session
|
||||
|
||||
from utils import *
|
||||
from db import *
|
||||
from net import *
|
||||
from wsgi import *
|
||||
from http import *
|
||||
from webapi import *
|
||||
from httpserver import *
|
||||
from debugerror import *
|
||||
from application import *
|
||||
from browser import *
|
||||
try:
|
||||
import webopenid as openid
|
||||
except ImportError:
|
||||
pass # requires openid module
|
||||
|
||||
#!/usr/bin/env python
|
||||
"""web.py: makes web apps (http://webpy.org)"""
|
||||
|
||||
from __future__ import generators
|
||||
|
||||
__version__ = "0.37"
|
||||
__author__ = [
|
||||
"Aaron Swartz <me@aaronsw.com>",
|
||||
"Anand Chitipothu <anandology@gmail.com>"
|
||||
]
|
||||
__license__ = "public domain"
|
||||
__contributors__ = "see http://webpy.org/changes"
|
||||
|
||||
import utils, db, net, wsgi, http, webapi, httpserver, debugerror
|
||||
import template, form
|
||||
|
||||
import session
|
||||
|
||||
from utils import *
|
||||
from db import *
|
||||
from net import *
|
||||
from wsgi import *
|
||||
from http import *
|
||||
from webapi import *
|
||||
from httpserver import *
|
||||
from debugerror import *
|
||||
from application import *
|
||||
from browser import *
|
||||
try:
|
||||
import webopenid as openid
|
||||
except ImportError:
|
||||
pass # requires openid module
|
||||
|
||||
|
|
472
web/browser.py
472
web/browser.py
|
@ -1,236 +1,236 @@
|
|||
"""Browser to test web applications.
|
||||
(from web.py)
|
||||
"""
|
||||
from utils import re_compile
|
||||
from net import htmlunquote
|
||||
|
||||
import httplib, urllib, urllib2
|
||||
import copy
|
||||
from StringIO import StringIO
|
||||
|
||||
DEBUG = False
|
||||
|
||||
__all__ = [
|
||||
"BrowserError",
|
||||
"Browser", "AppBrowser",
|
||||
"AppHandler"
|
||||
]
|
||||
|
||||
class BrowserError(Exception):
|
||||
pass
|
||||
|
||||
class Browser:
|
||||
def __init__(self):
|
||||
import cookielib
|
||||
self.cookiejar = cookielib.CookieJar()
|
||||
self._cookie_processor = urllib2.HTTPCookieProcessor(self.cookiejar)
|
||||
self.form = None
|
||||
|
||||
self.url = "http://0.0.0.0:8080/"
|
||||
self.path = "/"
|
||||
|
||||
self.status = None
|
||||
self.data = None
|
||||
self._response = None
|
||||
self._forms = None
|
||||
|
||||
def reset(self):
|
||||
"""Clears all cookies and history."""
|
||||
self.cookiejar.clear()
|
||||
|
||||
def build_opener(self):
|
||||
"""Builds the opener using urllib2.build_opener.
|
||||
Subclasses can override this function to prodive custom openers.
|
||||
"""
|
||||
return urllib2.build_opener()
|
||||
|
||||
def do_request(self, req):
|
||||
if DEBUG:
|
||||
print 'requesting', req.get_method(), req.get_full_url()
|
||||
opener = self.build_opener()
|
||||
opener.add_handler(self._cookie_processor)
|
||||
try:
|
||||
self._response = opener.open(req)
|
||||
except urllib2.HTTPError, e:
|
||||
self._response = e
|
||||
|
||||
self.url = self._response.geturl()
|
||||
self.path = urllib2.Request(self.url).get_selector()
|
||||
self.data = self._response.read()
|
||||
self.status = self._response.code
|
||||
self._forms = None
|
||||
self.form = None
|
||||
return self.get_response()
|
||||
|
||||
def open(self, url, data=None, headers={}):
|
||||
"""Opens the specified url."""
|
||||
url = urllib.basejoin(self.url, url)
|
||||
req = urllib2.Request(url, data, headers)
|
||||
return self.do_request(req)
|
||||
|
||||
def show(self):
|
||||
"""Opens the current page in real web browser."""
|
||||
f = open('page.html', 'w')
|
||||
f.write(self.data)
|
||||
f.close()
|
||||
|
||||
import webbrowser, os
|
||||
url = 'file://' + os.path.abspath('page.html')
|
||||
webbrowser.open(url)
|
||||
|
||||
def get_response(self):
|
||||
"""Returns a copy of the current response."""
|
||||
return urllib.addinfourl(StringIO(self.data), self._response.info(), self._response.geturl())
|
||||
|
||||
def get_soup(self):
|
||||
"""Returns beautiful soup of the current document."""
|
||||
import BeautifulSoup
|
||||
return BeautifulSoup.BeautifulSoup(self.data)
|
||||
|
||||
def get_text(self, e=None):
|
||||
"""Returns content of e or the current document as plain text."""
|
||||
e = e or self.get_soup()
|
||||
return ''.join([htmlunquote(c) for c in e.recursiveChildGenerator() if isinstance(c, unicode)])
|
||||
|
||||
def _get_links(self):
|
||||
soup = self.get_soup()
|
||||
return [a for a in soup.findAll(name='a')]
|
||||
|
||||
def get_links(self, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
|
||||
"""Returns all links in the document."""
|
||||
return self._filter_links(self._get_links(),
|
||||
text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
|
||||
|
||||
def follow_link(self, link=None, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
|
||||
if link is None:
|
||||
links = self._filter_links(self.get_links(),
|
||||
text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
|
||||
link = links and links[0]
|
||||
|
||||
if link:
|
||||
return self.open(link['href'])
|
||||
else:
|
||||
raise BrowserError("No link found")
|
||||
|
||||
def find_link(self, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
|
||||
links = self._filter_links(self.get_links(),
|
||||
text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
|
||||
return links and links[0] or None
|
||||
|
||||
def _filter_links(self, links,
|
||||
text=None, text_regex=None,
|
||||
url=None, url_regex=None,
|
||||
predicate=None):
|
||||
predicates = []
|
||||
if text is not None:
|
||||
predicates.append(lambda link: link.string == text)
|
||||
if text_regex is not None:
|
||||
predicates.append(lambda link: re_compile(text_regex).search(link.string or ''))
|
||||
if url is not None:
|
||||
predicates.append(lambda link: link.get('href') == url)
|
||||
if url_regex is not None:
|
||||
predicates.append(lambda link: re_compile(url_regex).search(link.get('href', '')))
|
||||
if predicate:
|
||||
predicate.append(predicate)
|
||||
|
||||
def f(link):
|
||||
for p in predicates:
|
||||
if not p(link):
|
||||
return False
|
||||
return True
|
||||
|
||||
return [link for link in links if f(link)]
|
||||
|
||||
def get_forms(self):
|
||||
"""Returns all forms in the current document.
|
||||
The returned form objects implement the ClientForm.HTMLForm interface.
|
||||
"""
|
||||
if self._forms is None:
|
||||
import ClientForm
|
||||
self._forms = ClientForm.ParseResponse(self.get_response(), backwards_compat=False)
|
||||
return self._forms
|
||||
|
||||
def select_form(self, name=None, predicate=None, index=0):
|
||||
"""Selects the specified form."""
|
||||
forms = self.get_forms()
|
||||
|
||||
if name is not None:
|
||||
forms = [f for f in forms if f.name == name]
|
||||
if predicate:
|
||||
forms = [f for f in forms if predicate(f)]
|
||||
|
||||
if forms:
|
||||
self.form = forms[index]
|
||||
return self.form
|
||||
else:
|
||||
raise BrowserError("No form selected.")
|
||||
|
||||
def submit(self, **kw):
|
||||
"""submits the currently selected form."""
|
||||
if self.form is None:
|
||||
raise BrowserError("No form selected.")
|
||||
req = self.form.click(**kw)
|
||||
return self.do_request(req)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.form[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.form[key] = value
|
||||
|
||||
class AppBrowser(Browser):
|
||||
"""Browser interface to test web.py apps.
|
||||
|
||||
b = AppBrowser(app)
|
||||
b.open('/')
|
||||
b.follow_link(text='Login')
|
||||
|
||||
b.select_form(name='login')
|
||||
b['username'] = 'joe'
|
||||
b['password'] = 'secret'
|
||||
b.submit()
|
||||
|
||||
assert b.path == '/'
|
||||
assert 'Welcome joe' in b.get_text()
|
||||
"""
|
||||
def __init__(self, app):
|
||||
Browser.__init__(self)
|
||||
self.app = app
|
||||
|
||||
def build_opener(self):
|
||||
return urllib2.build_opener(AppHandler(self.app))
|
||||
|
||||
class AppHandler(urllib2.HTTPHandler):
|
||||
"""urllib2 handler to handle requests using web.py application."""
|
||||
handler_order = 100
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def http_open(self, req):
|
||||
result = self.app.request(
|
||||
localpart=req.get_selector(),
|
||||
method=req.get_method(),
|
||||
host=req.get_host(),
|
||||
data=req.get_data(),
|
||||
headers=dict(req.header_items()),
|
||||
https=req.get_type() == "https"
|
||||
)
|
||||
return self._make_response(result, req.get_full_url())
|
||||
|
||||
def https_open(self, req):
|
||||
return self.http_open(req)
|
||||
|
||||
try:
|
||||
https_request = urllib2.HTTPHandler.do_request_
|
||||
except AttributeError:
|
||||
# for python 2.3
|
||||
pass
|
||||
|
||||
def _make_response(self, result, url):
|
||||
data = "\r\n".join(["%s: %s" % (k, v) for k, v in result.header_items])
|
||||
headers = httplib.HTTPMessage(StringIO(data))
|
||||
response = urllib.addinfourl(StringIO(result.data), headers, url)
|
||||
code, msg = result.status.split(None, 1)
|
||||
response.code, response.msg = int(code), msg
|
||||
return response
|
||||
"""Browser to test web applications.
|
||||
(from web.py)
|
||||
"""
|
||||
from utils import re_compile
|
||||
from net import htmlunquote
|
||||
|
||||
import httplib, urllib, urllib2
|
||||
import copy
|
||||
from StringIO import StringIO
|
||||
|
||||
DEBUG = False
|
||||
|
||||
__all__ = [
|
||||
"BrowserError",
|
||||
"Browser", "AppBrowser",
|
||||
"AppHandler"
|
||||
]
|
||||
|
||||
class BrowserError(Exception):
|
||||
pass
|
||||
|
||||
class Browser:
|
||||
def __init__(self):
|
||||
import cookielib
|
||||
self.cookiejar = cookielib.CookieJar()
|
||||
self._cookie_processor = urllib2.HTTPCookieProcessor(self.cookiejar)
|
||||
self.form = None
|
||||
|
||||
self.url = "http://0.0.0.0:8080/"
|
||||
self.path = "/"
|
||||
|
||||
self.status = None
|
||||
self.data = None
|
||||
self._response = None
|
||||
self._forms = None
|
||||
|
||||
def reset(self):
|
||||
"""Clears all cookies and history."""
|
||||
self.cookiejar.clear()
|
||||
|
||||
def build_opener(self):
|
||||
"""Builds the opener using urllib2.build_opener.
|
||||
Subclasses can override this function to prodive custom openers.
|
||||
"""
|
||||
return urllib2.build_opener()
|
||||
|
||||
def do_request(self, req):
|
||||
if DEBUG:
|
||||
print 'requesting', req.get_method(), req.get_full_url()
|
||||
opener = self.build_opener()
|
||||
opener.add_handler(self._cookie_processor)
|
||||
try:
|
||||
self._response = opener.open(req)
|
||||
except urllib2.HTTPError, e:
|
||||
self._response = e
|
||||
|
||||
self.url = self._response.geturl()
|
||||
self.path = urllib2.Request(self.url).get_selector()
|
||||
self.data = self._response.read()
|
||||
self.status = self._response.code
|
||||
self._forms = None
|
||||
self.form = None
|
||||
return self.get_response()
|
||||
|
||||
def open(self, url, data=None, headers={}):
|
||||
"""Opens the specified url."""
|
||||
url = urllib.basejoin(self.url, url)
|
||||
req = urllib2.Request(url, data, headers)
|
||||
return self.do_request(req)
|
||||
|
||||
def show(self):
|
||||
"""Opens the current page in real web browser."""
|
||||
f = open('page.html', 'w')
|
||||
f.write(self.data)
|
||||
f.close()
|
||||
|
||||
import webbrowser, os
|
||||
url = 'file://' + os.path.abspath('page.html')
|
||||
webbrowser.open(url)
|
||||
|
||||
def get_response(self):
|
||||
"""Returns a copy of the current response."""
|
||||
return urllib.addinfourl(StringIO(self.data), self._response.info(), self._response.geturl())
|
||||
|
||||
def get_soup(self):
|
||||
"""Returns beautiful soup of the current document."""
|
||||
import BeautifulSoup
|
||||
return BeautifulSoup.BeautifulSoup(self.data)
|
||||
|
||||
def get_text(self, e=None):
|
||||
"""Returns content of e or the current document as plain text."""
|
||||
e = e or self.get_soup()
|
||||
return ''.join([htmlunquote(c) for c in e.recursiveChildGenerator() if isinstance(c, unicode)])
|
||||
|
||||
def _get_links(self):
|
||||
soup = self.get_soup()
|
||||
return [a for a in soup.findAll(name='a')]
|
||||
|
||||
def get_links(self, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
|
||||
"""Returns all links in the document."""
|
||||
return self._filter_links(self._get_links(),
|
||||
text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
|
||||
|
||||
def follow_link(self, link=None, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
|
||||
if link is None:
|
||||
links = self._filter_links(self.get_links(),
|
||||
text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
|
||||
link = links and links[0]
|
||||
|
||||
if link:
|
||||
return self.open(link['href'])
|
||||
else:
|
||||
raise BrowserError("No link found")
|
||||
|
||||
def find_link(self, text=None, text_regex=None, url=None, url_regex=None, predicate=None):
|
||||
links = self._filter_links(self.get_links(),
|
||||
text=text, text_regex=text_regex, url=url, url_regex=url_regex, predicate=predicate)
|
||||
return links and links[0] or None
|
||||
|
||||
def _filter_links(self, links,
|
||||
text=None, text_regex=None,
|
||||
url=None, url_regex=None,
|
||||
predicate=None):
|
||||
predicates = []
|
||||
if text is not None:
|
||||
predicates.append(lambda link: link.string == text)
|
||||
if text_regex is not None:
|
||||
predicates.append(lambda link: re_compile(text_regex).search(link.string or ''))
|
||||
if url is not None:
|
||||
predicates.append(lambda link: link.get('href') == url)
|
||||
if url_regex is not None:
|
||||
predicates.append(lambda link: re_compile(url_regex).search(link.get('href', '')))
|
||||
if predicate:
|
||||
predicate.append(predicate)
|
||||
|
||||
def f(link):
|
||||
for p in predicates:
|
||||
if not p(link):
|
||||
return False
|
||||
return True
|
||||
|
||||
return [link for link in links if f(link)]
|
||||
|
||||
def get_forms(self):
|
||||
"""Returns all forms in the current document.
|
||||
The returned form objects implement the ClientForm.HTMLForm interface.
|
||||
"""
|
||||
if self._forms is None:
|
||||
import ClientForm
|
||||
self._forms = ClientForm.ParseResponse(self.get_response(), backwards_compat=False)
|
||||
return self._forms
|
||||
|
||||
def select_form(self, name=None, predicate=None, index=0):
|
||||
"""Selects the specified form."""
|
||||
forms = self.get_forms()
|
||||
|
||||
if name is not None:
|
||||
forms = [f for f in forms if f.name == name]
|
||||
if predicate:
|
||||
forms = [f for f in forms if predicate(f)]
|
||||
|
||||
if forms:
|
||||
self.form = forms[index]
|
||||
return self.form
|
||||
else:
|
||||
raise BrowserError("No form selected.")
|
||||
|
||||
def submit(self, **kw):
|
||||
"""submits the currently selected form."""
|
||||
if self.form is None:
|
||||
raise BrowserError("No form selected.")
|
||||
req = self.form.click(**kw)
|
||||
return self.do_request(req)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.form[key]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.form[key] = value
|
||||
|
||||
class AppBrowser(Browser):
|
||||
"""Browser interface to test web.py apps.
|
||||
|
||||
b = AppBrowser(app)
|
||||
b.open('/')
|
||||
b.follow_link(text='Login')
|
||||
|
||||
b.select_form(name='login')
|
||||
b['username'] = 'joe'
|
||||
b['password'] = 'secret'
|
||||
b.submit()
|
||||
|
||||
assert b.path == '/'
|
||||
assert 'Welcome joe' in b.get_text()
|
||||
"""
|
||||
def __init__(self, app):
|
||||
Browser.__init__(self)
|
||||
self.app = app
|
||||
|
||||
def build_opener(self):
|
||||
return urllib2.build_opener(AppHandler(self.app))
|
||||
|
||||
class AppHandler(urllib2.HTTPHandler):
|
||||
"""urllib2 handler to handle requests using web.py application."""
|
||||
handler_order = 100
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def http_open(self, req):
|
||||
result = self.app.request(
|
||||
localpart=req.get_selector(),
|
||||
method=req.get_method(),
|
||||
host=req.get_host(),
|
||||
data=req.get_data(),
|
||||
headers=dict(req.header_items()),
|
||||
https=req.get_type() == "https"
|
||||
)
|
||||
return self._make_response(result, req.get_full_url())
|
||||
|
||||
def https_open(self, req):
|
||||
return self.http_open(req)
|
||||
|
||||
try:
|
||||
https_request = urllib2.HTTPHandler.do_request_
|
||||
except AttributeError:
|
||||
# for python 2.3
|
||||
pass
|
||||
|
||||
def _make_response(self, result, url):
|
||||
data = "\r\n".join(["%s: %s" % (k, v) for k, v in result.header_items])
|
||||
headers = httplib.HTTPMessage(StringIO(data))
|
||||
response = urllib.addinfourl(StringIO(result.data), headers, url)
|
||||
code, msg = result.status.split(None, 1)
|
||||
response.code, response.msg = int(code), msg
|
||||
return response
|
||||
|
|
|
@ -1,131 +1,131 @@
|
|||
"""
|
||||
Interface to various templating engines.
|
||||
"""
|
||||
import os.path
|
||||
|
||||
__all__ = [
|
||||
"render_cheetah", "render_genshi", "render_mako",
|
||||
"cache",
|
||||
]
|
||||
|
||||
class render_cheetah:
|
||||
"""Rendering interface to Cheetah Templates.
|
||||
|
||||
Example:
|
||||
|
||||
render = render_cheetah('templates')
|
||||
render.hello(name="cheetah")
|
||||
"""
|
||||
def __init__(self, path):
|
||||
# give error if Chetah is not installed
|
||||
from Cheetah.Template import Template
|
||||
self.path = path
|
||||
|
||||
def __getattr__(self, name):
|
||||
from Cheetah.Template import Template
|
||||
path = os.path.join(self.path, name + ".html")
|
||||
|
||||
def template(**kw):
|
||||
t = Template(file=path, searchList=[kw])
|
||||
return t.respond()
|
||||
|
||||
return template
|
||||
|
||||
class render_genshi:
|
||||
"""Rendering interface genshi templates.
|
||||
Example:
|
||||
|
||||
for xml/html templates.
|
||||
|
||||
render = render_genshi(['templates/'])
|
||||
render.hello(name='genshi')
|
||||
|
||||
For text templates:
|
||||
|
||||
render = render_genshi(['templates/'], type='text')
|
||||
render.hello(name='genshi')
|
||||
"""
|
||||
|
||||
def __init__(self, *a, **kwargs):
|
||||
from genshi.template import TemplateLoader
|
||||
|
||||
self._type = kwargs.pop('type', None)
|
||||
self._loader = TemplateLoader(*a, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Assuming all templates are html
|
||||
path = name + ".html"
|
||||
|
||||
if self._type == "text":
|
||||
from genshi.template import TextTemplate
|
||||
cls = TextTemplate
|
||||
type = "text"
|
||||
else:
|
||||
cls = None
|
||||
type = None
|
||||
|
||||
t = self._loader.load(path, cls=cls)
|
||||
def template(**kw):
|
||||
stream = t.generate(**kw)
|
||||
if type:
|
||||
return stream.render(type)
|
||||
else:
|
||||
return stream.render()
|
||||
return template
|
||||
|
||||
class render_jinja:
|
||||
"""Rendering interface to Jinja2 Templates
|
||||
|
||||
Example:
|
||||
|
||||
render= render_jinja('templates')
|
||||
render.hello(name='jinja2')
|
||||
"""
|
||||
def __init__(self, *a, **kwargs):
|
||||
extensions = kwargs.pop('extensions', [])
|
||||
globals = kwargs.pop('globals', {})
|
||||
|
||||
from jinja2 import Environment,FileSystemLoader
|
||||
self._lookup = Environment(loader=FileSystemLoader(*a, **kwargs), extensions=extensions)
|
||||
self._lookup.globals.update(globals)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Assuming all templates end with .html
|
||||
path = name + '.html'
|
||||
t = self._lookup.get_template(path)
|
||||
return t.render
|
||||
|
||||
class render_mako:
|
||||
"""Rendering interface to Mako Templates.
|
||||
|
||||
Example:
|
||||
|
||||
render = render_mako(directories=['templates'])
|
||||
render.hello(name="mako")
|
||||
"""
|
||||
def __init__(self, *a, **kwargs):
|
||||
from mako.lookup import TemplateLookup
|
||||
self._lookup = TemplateLookup(*a, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Assuming all templates are html
|
||||
path = name + ".html"
|
||||
t = self._lookup.get_template(path)
|
||||
return t.render
|
||||
|
||||
class cache:
|
||||
"""Cache for any rendering interface.
|
||||
|
||||
Example:
|
||||
|
||||
render = cache(render_cheetah("templates/"))
|
||||
render.hello(name='cache')
|
||||
"""
|
||||
def __init__(self, render):
|
||||
self._render = render
|
||||
self._cache = {}
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name not in self._cache:
|
||||
self._cache[name] = getattr(self._render, name)
|
||||
return self._cache[name]
|
||||
"""
|
||||
Interface to various templating engines.
|
||||
"""
|
||||
import os.path
|
||||
|
||||
__all__ = [
|
||||
"render_cheetah", "render_genshi", "render_mako",
|
||||
"cache",
|
||||
]
|
||||
|
||||
class render_cheetah:
|
||||
"""Rendering interface to Cheetah Templates.
|
||||
|
||||
Example:
|
||||
|
||||
render = render_cheetah('templates')
|
||||
render.hello(name="cheetah")
|
||||
"""
|
||||
def __init__(self, path):
|
||||
# give error if Chetah is not installed
|
||||
from Cheetah.Template import Template
|
||||
self.path = path
|
||||
|
||||
def __getattr__(self, name):
|
||||
from Cheetah.Template import Template
|
||||
path = os.path.join(self.path, name + ".html")
|
||||
|
||||
def template(**kw):
|
||||
t = Template(file=path, searchList=[kw])
|
||||
return t.respond()
|
||||
|
||||
return template
|
||||
|
||||
class render_genshi:
|
||||
"""Rendering interface genshi templates.
|
||||
Example:
|
||||
|
||||
for xml/html templates.
|
||||
|
||||
render = render_genshi(['templates/'])
|
||||
render.hello(name='genshi')
|
||||
|
||||
For text templates:
|
||||
|
||||
render = render_genshi(['templates/'], type='text')
|
||||
render.hello(name='genshi')
|
||||
"""
|
||||
|
||||
def __init__(self, *a, **kwargs):
|
||||
from genshi.template import TemplateLoader
|
||||
|
||||
self._type = kwargs.pop('type', None)
|
||||
self._loader = TemplateLoader(*a, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Assuming all templates are html
|
||||
path = name + ".html"
|
||||
|
||||
if self._type == "text":
|
||||
from genshi.template import TextTemplate
|
||||
cls = TextTemplate
|
||||
type = "text"
|
||||
else:
|
||||
cls = None
|
||||
type = None
|
||||
|
||||
t = self._loader.load(path, cls=cls)
|
||||
def template(**kw):
|
||||
stream = t.generate(**kw)
|
||||
if type:
|
||||
return stream.render(type)
|
||||
else:
|
||||
return stream.render()
|
||||
return template
|
||||
|
||||
class render_jinja:
|
||||
"""Rendering interface to Jinja2 Templates
|
||||
|
||||
Example:
|
||||
|
||||
render= render_jinja('templates')
|
||||
render.hello(name='jinja2')
|
||||
"""
|
||||
def __init__(self, *a, **kwargs):
|
||||
extensions = kwargs.pop('extensions', [])
|
||||
globals = kwargs.pop('globals', {})
|
||||
|
||||
from jinja2 import Environment,FileSystemLoader
|
||||
self._lookup = Environment(loader=FileSystemLoader(*a, **kwargs), extensions=extensions)
|
||||
self._lookup.globals.update(globals)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Assuming all templates end with .html
|
||||
path = name + '.html'
|
||||
t = self._lookup.get_template(path)
|
||||
return t.render
|
||||
|
||||
class render_mako:
|
||||
"""Rendering interface to Mako Templates.
|
||||
|
||||
Example:
|
||||
|
||||
render = render_mako(directories=['templates'])
|
||||
render.hello(name="mako")
|
||||
"""
|
||||
def __init__(self, *a, **kwargs):
|
||||
from mako.lookup import TemplateLookup
|
||||
self._lookup = TemplateLookup(*a, **kwargs)
|
||||
|
||||
def __getattr__(self, name):
|
||||
# Assuming all templates are html
|
||||
path = name + ".html"
|
||||
t = self._lookup.get_template(path)
|
||||
return t.render
|
||||
|
||||
class cache:
|
||||
"""Cache for any rendering interface.
|
||||
|
||||
Example:
|
||||
|
||||
render = cache(render_cheetah("templates/"))
|
||||
render.hello(name='cache')
|
||||
"""
|
||||
def __init__(self, render):
|
||||
self._render = render
|
||||
self._cache = {}
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name not in self._cache:
|
||||
self._cache[name] = getattr(self._render, name)
|
||||
return self._cache[name]
|
||||
|
|
|
@ -1,354 +1,354 @@
|
|||
"""
|
||||
pretty debug errors
|
||||
(part of web.py)
|
||||
|
||||
portions adapted from Django <djangoproject.com>
|
||||
Copyright (c) 2005, the Lawrence Journal-World
|
||||
Used under the modified BSD license:
|
||||
http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
||||
"""
|
||||
|
||||
__all__ = ["debugerror", "djangoerror", "emailerrors"]
|
||||
|
||||
import sys, urlparse, pprint, traceback
|
||||
from template import Template
|
||||
from net import websafe
|
||||
from utils import sendmail, safestr
|
||||
import webapi as web
|
||||
|
||||
import os, os.path
|
||||
whereami = os.path.join(os.getcwd(), __file__)
|
||||
whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1])
|
||||
djangoerror_t = """\
|
||||
$def with (exception_type, exception_value, frames)
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
||||
<title>$exception_type at $ctx.path</title>
|
||||
<style type="text/css">
|
||||
html * { padding:0; margin:0; }
|
||||
body * { padding:10px 20px; }
|
||||
body * * { padding:0; }
|
||||
body { font:small sans-serif; }
|
||||
body>div { border-bottom:1px solid #ddd; }
|
||||
h1 { font-weight:normal; }
|
||||
h2 { margin-bottom:.8em; }
|
||||
h2 span { font-size:80%; color:#666; font-weight:normal; }
|
||||
h3 { margin:1em 0 .5em 0; }
|
||||
h4 { margin:0 0 .5em 0; font-weight: normal; }
|
||||
table {
|
||||
border:1px solid #ccc; border-collapse: collapse; background:white; }
|
||||
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
|
||||
thead th {
|
||||
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
|
||||
font-weight:normal; font-size:11px; border:1px solid #ddd; }
|
||||
tbody th { text-align:right; color:#666; padding-right:.5em; }
|
||||
table.vars { margin:5px 0 2px 40px; }
|
||||
table.vars td, table.req td { font-family:monospace; }
|
||||
table td.code { width:100%;}
|
||||
table td.code div { overflow:hidden; }
|
||||
table.source th { color:#666; }
|
||||
table.source td {
|
||||
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
|
||||
ul.traceback { list-style-type:none; }
|
||||
ul.traceback li.frame { margin-bottom:1em; }
|
||||
div.context { margin: 10px 0; }
|
||||
div.context ol {
|
||||
padding-left:30px; margin:0 10px; list-style-position: inside; }
|
||||
div.context ol li {
|
||||
font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
|
||||
div.context ol.context-line li { color:black; background-color:#ccc; }
|
||||
div.context ol.context-line li span { float: right; }
|
||||
div.commands { margin-left: 40px; }
|
||||
div.commands a { color:black; text-decoration:none; }
|
||||
#summary { background: #ffc; }
|
||||
#summary h2 { font-weight: normal; color: #666; }
|
||||
#explanation { background:#eee; }
|
||||
#template, #template-not-exist { background:#f6f6f6; }
|
||||
#template-not-exist ul { margin: 0 0 0 20px; }
|
||||
#traceback { background:#eee; }
|
||||
#requestinfo { background:#f6f6f6; padding-left:120px; }
|
||||
#summary table { border:none; background:transparent; }
|
||||
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
||||
#requestinfo h3 { margin-bottom:-1em; }
|
||||
.error { background: #ffc; }
|
||||
.specific { color:#cc3300; font-weight:bold; }
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
//<!--
|
||||
function getElementsByClassName(oElm, strTagName, strClassName){
|
||||
// Written by Jonathan Snook, http://www.snook.ca/jon;
|
||||
// Add-ons by Robert Nyman, http://www.robertnyman.com
|
||||
var arrElements = (strTagName == "*" && document.all)? document.all :
|
||||
oElm.getElementsByTagName(strTagName);
|
||||
var arrReturnElements = new Array();
|
||||
strClassName = strClassName.replace(/\-/g, "\\-");
|
||||
var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
|
||||
var oElement;
|
||||
for(var i=0; i<arrElements.length; i++){
|
||||
oElement = arrElements[i];
|
||||
if(oRegExp.test(oElement.className)){
|
||||
arrReturnElements.push(oElement);
|
||||
}
|
||||
}
|
||||
return (arrReturnElements)
|
||||
}
|
||||
function hideAll(elems) {
|
||||
for (var e = 0; e < elems.length; e++) {
|
||||
elems[e].style.display = 'none';
|
||||
}
|
||||
}
|
||||
window.onload = function() {
|
||||
hideAll(getElementsByClassName(document, 'table', 'vars'));
|
||||
hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
|
||||
hideAll(getElementsByClassName(document, 'ol', 'post-context'));
|
||||
}
|
||||
function toggle() {
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var e = document.getElementById(arguments[i]);
|
||||
if (e) {
|
||||
e.style.display = e.style.display == 'none' ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function varToggle(link, id) {
|
||||
toggle('v' + id);
|
||||
var s = link.getElementsByTagName('span')[0];
|
||||
var uarr = String.fromCharCode(0x25b6);
|
||||
var darr = String.fromCharCode(0x25bc);
|
||||
s.innerHTML = s.innerHTML == uarr ? darr : uarr;
|
||||
return false;
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
$def dicttable (d, kls='req', id=None):
|
||||
$ items = d and d.items() or []
|
||||
$items.sort()
|
||||
$:dicttable_items(items, kls, id)
|
||||
|
||||
$def dicttable_items(items, kls='req', id=None):
|
||||
$if items:
|
||||
<table class="$kls"
|
||||
$if id: id="$id"
|
||||
><thead><tr><th>Variable</th><th>Value</th></tr></thead>
|
||||
<tbody>
|
||||
$for k, v in items:
|
||||
<tr><td>$k</td><td class="code"><div>$prettify(v)</div></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
$else:
|
||||
<p>No data.</p>
|
||||
|
||||
<div id="summary">
|
||||
<h1>$exception_type at $ctx.path</h1>
|
||||
<h2>$exception_value</h2>
|
||||
<table><tr>
|
||||
<th>Python</th>
|
||||
<td>$frames[0].filename in $frames[0].function, line $frames[0].lineno</td>
|
||||
</tr><tr>
|
||||
<th>Web</th>
|
||||
<td>$ctx.method $ctx.home$ctx.path</td>
|
||||
</tr></table>
|
||||
</div>
|
||||
<div id="traceback">
|
||||
<h2>Traceback <span>(innermost first)</span></h2>
|
||||
<ul class="traceback">
|
||||
$for frame in frames:
|
||||
<li class="frame">
|
||||
<code>$frame.filename</code> in <code>$frame.function</code>
|
||||
$if frame.context_line is not None:
|
||||
<div class="context" id="c$frame.id">
|
||||
$if frame.pre_context:
|
||||
<ol start="$frame.pre_context_lineno" class="pre-context" id="pre$frame.id">
|
||||
$for line in frame.pre_context:
|
||||
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
|
||||
</ol>
|
||||
<ol start="$frame.lineno" class="context-line"><li onclick="toggle('pre$frame.id', 'post$frame.id')">$frame.context_line <span>...</span></li></ol>
|
||||
$if frame.post_context:
|
||||
<ol start='${frame.lineno + 1}' class="post-context" id="post$frame.id">
|
||||
$for line in frame.post_context:
|
||||
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
$if frame.vars:
|
||||
<div class="commands">
|
||||
<a href='#' onclick="return varToggle(this, '$frame.id')"><span>▶</span> Local vars</a>
|
||||
$# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame))
|
||||
</div>
|
||||
$:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id)))
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="requestinfo">
|
||||
$if ctx.output or ctx.headers:
|
||||
<h2>Response so far</h2>
|
||||
<h3>HEADERS</h3>
|
||||
$:dicttable_items(ctx.headers)
|
||||
|
||||
<h3>BODY</h3>
|
||||
<p class="req" style="padding-bottom: 2em"><code>
|
||||
$ctx.output
|
||||
</code></p>
|
||||
|
||||
<h2>Request information</h2>
|
||||
|
||||
<h3>INPUT</h3>
|
||||
$:dicttable(web.input(_unicode=False))
|
||||
|
||||
<h3 id="cookie-info">COOKIES</h3>
|
||||
$:dicttable(web.cookies())
|
||||
|
||||
<h3 id="meta-info">META</h3>
|
||||
$ newctx = [(k, v) for (k, v) in ctx.iteritems() if not k.startswith('_') and not isinstance(v, dict)]
|
||||
$:dicttable(dict(newctx))
|
||||
|
||||
<h3 id="meta-info">ENVIRONMENT</h3>
|
||||
$:dicttable(ctx.env)
|
||||
</div>
|
||||
|
||||
<div id="explanation">
|
||||
<p>
|
||||
You're seeing this error because you have <code>web.config.debug</code>
|
||||
set to <code>True</code>. Set that to <code>False</code> if you don't want to see this.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
djangoerror_r = None
|
||||
|
||||
def djangoerror():
|
||||
def _get_lines_from_file(filename, lineno, context_lines):
|
||||
"""
|
||||
Returns context_lines before and after lineno from file.
|
||||
Returns (pre_context_lineno, pre_context, context_line, post_context).
|
||||
"""
|
||||
try:
|
||||
source = open(filename).readlines()
|
||||
lower_bound = max(0, lineno - context_lines)
|
||||
upper_bound = lineno + context_lines
|
||||
|
||||
pre_context = \
|
||||
[line.strip('\n') for line in source[lower_bound:lineno]]
|
||||
context_line = source[lineno].strip('\n')
|
||||
post_context = \
|
||||
[line.strip('\n') for line in source[lineno + 1:upper_bound]]
|
||||
|
||||
return lower_bound, pre_context, context_line, post_context
|
||||
except (OSError, IOError, IndexError):
|
||||
return None, [], None, []
|
||||
|
||||
exception_type, exception_value, tback = sys.exc_info()
|
||||
frames = []
|
||||
while tback is not None:
|
||||
filename = tback.tb_frame.f_code.co_filename
|
||||
function = tback.tb_frame.f_code.co_name
|
||||
lineno = tback.tb_lineno - 1
|
||||
|
||||
# hack to get correct line number for templates
|
||||
lineno += tback.tb_frame.f_locals.get("__lineoffset__", 0)
|
||||
|
||||
pre_context_lineno, pre_context, context_line, post_context = \
|
||||
_get_lines_from_file(filename, lineno, 7)
|
||||
|
||||
if '__hidetraceback__' not in tback.tb_frame.f_locals:
|
||||
frames.append(web.storage({
|
||||
'tback': tback,
|
||||
'filename': filename,
|
||||
'function': function,
|
||||
'lineno': lineno,
|
||||
'vars': tback.tb_frame.f_locals,
|
||||
'id': id(tback),
|
||||
'pre_context': pre_context,
|
||||
'context_line': context_line,
|
||||
'post_context': post_context,
|
||||
'pre_context_lineno': pre_context_lineno,
|
||||
}))
|
||||
tback = tback.tb_next
|
||||
frames.reverse()
|
||||
urljoin = urlparse.urljoin
|
||||
def prettify(x):
|
||||
try:
|
||||
out = pprint.pformat(x)
|
||||
except Exception, e:
|
||||
out = '[could not display: <' + e.__class__.__name__ + \
|
||||
': '+str(e)+'>]'
|
||||
return out
|
||||
|
||||
global djangoerror_r
|
||||
if djangoerror_r is None:
|
||||
djangoerror_r = Template(djangoerror_t, filename=__file__, filter=websafe)
|
||||
|
||||
t = djangoerror_r
|
||||
globals = {'ctx': web.ctx, 'web':web, 'dict':dict, 'str':str, 'prettify': prettify}
|
||||
t.t.func_globals.update(globals)
|
||||
return t(exception_type, exception_value, frames)
|
||||
|
||||
def debugerror():
|
||||
"""
|
||||
A replacement for `internalerror` that presents a nice page with lots
|
||||
of debug information for the programmer.
|
||||
|
||||
(Based on the beautiful 500 page from [Django](http://djangoproject.com/),
|
||||
designed by [Wilson Miner](http://wilsonminer.com/).)
|
||||
"""
|
||||
return web._InternalError(djangoerror())
|
||||
|
||||
def emailerrors(to_address, olderror, from_address=None):
|
||||
"""
|
||||
Wraps the old `internalerror` handler (pass as `olderror`) to
|
||||
additionally email all errors to `to_address`, to aid in
|
||||
debugging production websites.
|
||||
|
||||
Emails contain a normal text traceback as well as an
|
||||
attachment containing the nice `debugerror` page.
|
||||
"""
|
||||
from_address = from_address or to_address
|
||||
|
||||
def emailerrors_internal():
|
||||
error = olderror()
|
||||
tb = sys.exc_info()
|
||||
error_name = tb[0]
|
||||
error_value = tb[1]
|
||||
tb_txt = ''.join(traceback.format_exception(*tb))
|
||||
path = web.ctx.path
|
||||
request = web.ctx.method + ' ' + web.ctx.home + web.ctx.fullpath
|
||||
|
||||
message = "\n%s\n\n%s\n\n" % (request, tb_txt)
|
||||
|
||||
sendmail(
|
||||
"your buggy site <%s>" % from_address,
|
||||
"the bugfixer <%s>" % to_address,
|
||||
"bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(),
|
||||
message,
|
||||
attachments=[
|
||||
dict(filename="bug.html", content=safestr(djangoerror()))
|
||||
],
|
||||
)
|
||||
return error
|
||||
|
||||
return emailerrors_internal
|
||||
|
||||
if __name__ == "__main__":
|
||||
urls = (
|
||||
'/', 'index'
|
||||
)
|
||||
from application import application
|
||||
app = application(urls, globals())
|
||||
app.internalerror = debugerror
|
||||
|
||||
class index:
|
||||
def GET(self):
|
||||
thisdoesnotexist
|
||||
|
||||
app.run()
|
||||
"""
|
||||
pretty debug errors
|
||||
(part of web.py)
|
||||
|
||||
portions adapted from Django <djangoproject.com>
|
||||
Copyright (c) 2005, the Lawrence Journal-World
|
||||
Used under the modified BSD license:
|
||||
http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
||||
"""
|
||||
|
||||
__all__ = ["debugerror", "djangoerror", "emailerrors"]
|
||||
|
||||
import sys, urlparse, pprint, traceback
|
||||
from template import Template
|
||||
from net import websafe
|
||||
from utils import sendmail, safestr
|
||||
import webapi as web
|
||||
|
||||
import os, os.path
|
||||
whereami = os.path.join(os.getcwd(), __file__)
|
||||
whereami = os.path.sep.join(whereami.split(os.path.sep)[:-1])
|
||||
djangoerror_t = """\
|
||||
$def with (exception_type, exception_value, frames)
|
||||
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta http-equiv="content-type" content="text/html; charset=utf-8" />
|
||||
<meta name="robots" content="NONE,NOARCHIVE" />
|
||||
<title>$exception_type at $ctx.path</title>
|
||||
<style type="text/css">
|
||||
html * { padding:0; margin:0; }
|
||||
body * { padding:10px 20px; }
|
||||
body * * { padding:0; }
|
||||
body { font:small sans-serif; }
|
||||
body>div { border-bottom:1px solid #ddd; }
|
||||
h1 { font-weight:normal; }
|
||||
h2 { margin-bottom:.8em; }
|
||||
h2 span { font-size:80%; color:#666; font-weight:normal; }
|
||||
h3 { margin:1em 0 .5em 0; }
|
||||
h4 { margin:0 0 .5em 0; font-weight: normal; }
|
||||
table {
|
||||
border:1px solid #ccc; border-collapse: collapse; background:white; }
|
||||
tbody td, tbody th { vertical-align:top; padding:2px 3px; }
|
||||
thead th {
|
||||
padding:1px 6px 1px 3px; background:#fefefe; text-align:left;
|
||||
font-weight:normal; font-size:11px; border:1px solid #ddd; }
|
||||
tbody th { text-align:right; color:#666; padding-right:.5em; }
|
||||
table.vars { margin:5px 0 2px 40px; }
|
||||
table.vars td, table.req td { font-family:monospace; }
|
||||
table td.code { width:100%;}
|
||||
table td.code div { overflow:hidden; }
|
||||
table.source th { color:#666; }
|
||||
table.source td {
|
||||
font-family:monospace; white-space:pre; border-bottom:1px solid #eee; }
|
||||
ul.traceback { list-style-type:none; }
|
||||
ul.traceback li.frame { margin-bottom:1em; }
|
||||
div.context { margin: 10px 0; }
|
||||
div.context ol {
|
||||
padding-left:30px; margin:0 10px; list-style-position: inside; }
|
||||
div.context ol li {
|
||||
font-family:monospace; white-space:pre; color:#666; cursor:pointer; }
|
||||
div.context ol.context-line li { color:black; background-color:#ccc; }
|
||||
div.context ol.context-line li span { float: right; }
|
||||
div.commands { margin-left: 40px; }
|
||||
div.commands a { color:black; text-decoration:none; }
|
||||
#summary { background: #ffc; }
|
||||
#summary h2 { font-weight: normal; color: #666; }
|
||||
#explanation { background:#eee; }
|
||||
#template, #template-not-exist { background:#f6f6f6; }
|
||||
#template-not-exist ul { margin: 0 0 0 20px; }
|
||||
#traceback { background:#eee; }
|
||||
#requestinfo { background:#f6f6f6; padding-left:120px; }
|
||||
#summary table { border:none; background:transparent; }
|
||||
#requestinfo h2, #requestinfo h3 { position:relative; margin-left:-100px; }
|
||||
#requestinfo h3 { margin-bottom:-1em; }
|
||||
.error { background: #ffc; }
|
||||
.specific { color:#cc3300; font-weight:bold; }
|
||||
</style>
|
||||
<script type="text/javascript">
|
||||
//<!--
|
||||
function getElementsByClassName(oElm, strTagName, strClassName){
|
||||
// Written by Jonathan Snook, http://www.snook.ca/jon;
|
||||
// Add-ons by Robert Nyman, http://www.robertnyman.com
|
||||
var arrElements = (strTagName == "*" && document.all)? document.all :
|
||||
oElm.getElementsByTagName(strTagName);
|
||||
var arrReturnElements = new Array();
|
||||
strClassName = strClassName.replace(/\-/g, "\\-");
|
||||
var oRegExp = new RegExp("(^|\\s)" + strClassName + "(\\s|$$)");
|
||||
var oElement;
|
||||
for(var i=0; i<arrElements.length; i++){
|
||||
oElement = arrElements[i];
|
||||
if(oRegExp.test(oElement.className)){
|
||||
arrReturnElements.push(oElement);
|
||||
}
|
||||
}
|
||||
return (arrReturnElements)
|
||||
}
|
||||
function hideAll(elems) {
|
||||
for (var e = 0; e < elems.length; e++) {
|
||||
elems[e].style.display = 'none';
|
||||
}
|
||||
}
|
||||
window.onload = function() {
|
||||
hideAll(getElementsByClassName(document, 'table', 'vars'));
|
||||
hideAll(getElementsByClassName(document, 'ol', 'pre-context'));
|
||||
hideAll(getElementsByClassName(document, 'ol', 'post-context'));
|
||||
}
|
||||
function toggle() {
|
||||
for (var i = 0; i < arguments.length; i++) {
|
||||
var e = document.getElementById(arguments[i]);
|
||||
if (e) {
|
||||
e.style.display = e.style.display == 'none' ? 'block' : 'none';
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function varToggle(link, id) {
|
||||
toggle('v' + id);
|
||||
var s = link.getElementsByTagName('span')[0];
|
||||
var uarr = String.fromCharCode(0x25b6);
|
||||
var darr = String.fromCharCode(0x25bc);
|
||||
s.innerHTML = s.innerHTML == uarr ? darr : uarr;
|
||||
return false;
|
||||
}
|
||||
//-->
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
$def dicttable (d, kls='req', id=None):
|
||||
$ items = d and d.items() or []
|
||||
$items.sort()
|
||||
$:dicttable_items(items, kls, id)
|
||||
|
||||
$def dicttable_items(items, kls='req', id=None):
|
||||
$if items:
|
||||
<table class="$kls"
|
||||
$if id: id="$id"
|
||||
><thead><tr><th>Variable</th><th>Value</th></tr></thead>
|
||||
<tbody>
|
||||
$for k, v in items:
|
||||
<tr><td>$k</td><td class="code"><div>$prettify(v)</div></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
$else:
|
||||
<p>No data.</p>
|
||||
|
||||
<div id="summary">
|
||||
<h1>$exception_type at $ctx.path</h1>
|
||||
<h2>$exception_value</h2>
|
||||
<table><tr>
|
||||
<th>Python</th>
|
||||
<td>$frames[0].filename in $frames[0].function, line $frames[0].lineno</td>
|
||||
</tr><tr>
|
||||
<th>Web</th>
|
||||
<td>$ctx.method $ctx.home$ctx.path</td>
|
||||
</tr></table>
|
||||
</div>
|
||||
<div id="traceback">
|
||||
<h2>Traceback <span>(innermost first)</span></h2>
|
||||
<ul class="traceback">
|
||||
$for frame in frames:
|
||||
<li class="frame">
|
||||
<code>$frame.filename</code> in <code>$frame.function</code>
|
||||
$if frame.context_line is not None:
|
||||
<div class="context" id="c$frame.id">
|
||||
$if frame.pre_context:
|
||||
<ol start="$frame.pre_context_lineno" class="pre-context" id="pre$frame.id">
|
||||
$for line in frame.pre_context:
|
||||
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
|
||||
</ol>
|
||||
<ol start="$frame.lineno" class="context-line"><li onclick="toggle('pre$frame.id', 'post$frame.id')">$frame.context_line <span>...</span></li></ol>
|
||||
$if frame.post_context:
|
||||
<ol start='${frame.lineno + 1}' class="post-context" id="post$frame.id">
|
||||
$for line in frame.post_context:
|
||||
<li onclick="toggle('pre$frame.id', 'post$frame.id')">$line</li>
|
||||
</ol>
|
||||
</div>
|
||||
|
||||
$if frame.vars:
|
||||
<div class="commands">
|
||||
<a href='#' onclick="return varToggle(this, '$frame.id')"><span>▶</span> Local vars</a>
|
||||
$# $inspect.formatargvalues(*inspect.getargvalues(frame['tb'].tb_frame))
|
||||
</div>
|
||||
$:dicttable(frame.vars, kls='vars', id=('v' + str(frame.id)))
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div id="requestinfo">
|
||||
$if ctx.output or ctx.headers:
|
||||
<h2>Response so far</h2>
|
||||
<h3>HEADERS</h3>
|
||||
$:dicttable_items(ctx.headers)
|
||||
|
||||
<h3>BODY</h3>
|
||||
<p class="req" style="padding-bottom: 2em"><code>
|
||||
$ctx.output
|
||||
</code></p>
|
||||
|
||||
<h2>Request information</h2>
|
||||
|
||||
<h3>INPUT</h3>
|
||||
$:dicttable(web.input(_unicode=False))
|
||||
|
||||
<h3 id="cookie-info">COOKIES</h3>
|
||||
$:dicttable(web.cookies())
|
||||
|
||||
<h3 id="meta-info">META</h3>
|
||||
$ newctx = [(k, v) for (k, v) in ctx.iteritems() if not k.startswith('_') and not isinstance(v, dict)]
|
||||
$:dicttable(dict(newctx))
|
||||
|
||||
<h3 id="meta-info">ENVIRONMENT</h3>
|
||||
$:dicttable(ctx.env)
|
||||
</div>
|
||||
|
||||
<div id="explanation">
|
||||
<p>
|
||||
You're seeing this error because you have <code>web.config.debug</code>
|
||||
set to <code>True</code>. Set that to <code>False</code> if you don't want to see this.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
djangoerror_r = None
|
||||
|
||||
def djangoerror():
|
||||
def _get_lines_from_file(filename, lineno, context_lines):
|
||||
"""
|
||||
Returns context_lines before and after lineno from file.
|
||||
Returns (pre_context_lineno, pre_context, context_line, post_context).
|
||||
"""
|
||||
try:
|
||||
source = open(filename).readlines()
|
||||
lower_bound = max(0, lineno - context_lines)
|
||||
upper_bound = lineno + context_lines
|
||||
|
||||
pre_context = \
|
||||
[line.strip('\n') for line in source[lower_bound:lineno]]
|
||||
context_line = source[lineno].strip('\n')
|
||||
post_context = \
|
||||
[line.strip('\n') for line in source[lineno + 1:upper_bound]]
|
||||
|
||||
return lower_bound, pre_context, context_line, post_context
|
||||
except (OSError, IOError, IndexError):
|
||||
return None, [], None, []
|
||||
|
||||
exception_type, exception_value, tback = sys.exc_info()
|
||||
frames = []
|
||||
while tback is not None:
|
||||
filename = tback.tb_frame.f_code.co_filename
|
||||
function = tback.tb_frame.f_code.co_name
|
||||
lineno = tback.tb_lineno - 1
|
||||
|
||||
# hack to get correct line number for templates
|
||||
lineno += tback.tb_frame.f_locals.get("__lineoffset__", 0)
|
||||
|
||||
pre_context_lineno, pre_context, context_line, post_context = \
|
||||
_get_lines_from_file(filename, lineno, 7)
|
||||
|
||||
if '__hidetraceback__' not in tback.tb_frame.f_locals:
|
||||
frames.append(web.storage({
|
||||
'tback': tback,
|
||||
'filename': filename,
|
||||
'function': function,
|
||||
'lineno': lineno,
|
||||
'vars': tback.tb_frame.f_locals,
|
||||
'id': id(tback),
|
||||
'pre_context': pre_context,
|
||||
'context_line': context_line,
|
||||
'post_context': post_context,
|
||||
'pre_context_lineno': pre_context_lineno,
|
||||
}))
|
||||
tback = tback.tb_next
|
||||
frames.reverse()
|
||||
urljoin = urlparse.urljoin
|
||||
def prettify(x):
|
||||
try:
|
||||
out = pprint.pformat(x)
|
||||
except Exception, e:
|
||||
out = '[could not display: <' + e.__class__.__name__ + \
|
||||
': '+str(e)+'>]'
|
||||
return out
|
||||
|
||||
global djangoerror_r
|
||||
if djangoerror_r is None:
|
||||
djangoerror_r = Template(djangoerror_t, filename=__file__, filter=websafe)
|
||||
|
||||
t = djangoerror_r
|
||||
globals = {'ctx': web.ctx, 'web':web, 'dict':dict, 'str':str, 'prettify': prettify}
|
||||
t.t.func_globals.update(globals)
|
||||
return t(exception_type, exception_value, frames)
|
||||
|
||||
def debugerror():
|
||||
"""
|
||||
A replacement for `internalerror` that presents a nice page with lots
|
||||
of debug information for the programmer.
|
||||
|
||||
(Based on the beautiful 500 page from [Django](http://djangoproject.com/),
|
||||
designed by [Wilson Miner](http://wilsonminer.com/).)
|
||||
"""
|
||||
return web._InternalError(djangoerror())
|
||||
|
||||
def emailerrors(to_address, olderror, from_address=None):
|
||||
"""
|
||||
Wraps the old `internalerror` handler (pass as `olderror`) to
|
||||
additionally email all errors to `to_address`, to aid in
|
||||
debugging production websites.
|
||||
|
||||
Emails contain a normal text traceback as well as an
|
||||
attachment containing the nice `debugerror` page.
|
||||
"""
|
||||
from_address = from_address or to_address
|
||||
|
||||
def emailerrors_internal():
|
||||
error = olderror()
|
||||
tb = sys.exc_info()
|
||||
error_name = tb[0]
|
||||
error_value = tb[1]
|
||||
tb_txt = ''.join(traceback.format_exception(*tb))
|
||||
path = web.ctx.path
|
||||
request = web.ctx.method + ' ' + web.ctx.home + web.ctx.fullpath
|
||||
|
||||
message = "\n%s\n\n%s\n\n" % (request, tb_txt)
|
||||
|
||||
sendmail(
|
||||
"your buggy site <%s>" % from_address,
|
||||
"the bugfixer <%s>" % to_address,
|
||||
"bug: %(error_name)s: %(error_value)s (%(path)s)" % locals(),
|
||||
message,
|
||||
attachments=[
|
||||
dict(filename="bug.html", content=safestr(djangoerror()))
|
||||
],
|
||||
)
|
||||
return error
|
||||
|
||||
return emailerrors_internal
|
||||
|
||||
if __name__ == "__main__":
|
||||
urls = (
|
||||
'/', 'index'
|
||||
)
|
||||
from application import application
|
||||
app = application(urls, globals())
|
||||
app.internalerror = debugerror
|
||||
|
||||
class index:
|
||||
def GET(self):
|
||||
thisdoesnotexist
|
||||
|
||||
app.run()
|
||||
|
|
820
web/form.py
820
web/form.py
|
@ -1,410 +1,410 @@
|
|||
"""
|
||||
HTML forms
|
||||
(part of web.py)
|
||||
"""
|
||||
|
||||
import copy, re
|
||||
import webapi as web
|
||||
import utils, net
|
||||
|
||||
def attrget(obj, attr, value=None):
|
||||
try:
|
||||
if hasattr(obj, 'has_key') and obj.has_key(attr):
|
||||
return obj[attr]
|
||||
except TypeError:
|
||||
# Handle the case where has_key takes different number of arguments.
|
||||
# This is the case with Model objects on appengine. See #134
|
||||
pass
|
||||
if hasattr(obj, attr):
|
||||
return getattr(obj, attr)
|
||||
return value
|
||||
|
||||
class Form(object):
|
||||
r"""
|
||||
HTML form.
|
||||
|
||||
>>> f = Form(Textbox("x"))
|
||||
>>> f.render()
|
||||
u'<table>\n <tr><th><label for="x">x</label></th><td><input type="text" id="x" name="x"/></td></tr>\n</table>'
|
||||
"""
|
||||
def __init__(self, *inputs, **kw):
|
||||
self.inputs = inputs
|
||||
self.valid = True
|
||||
self.note = None
|
||||
self.validators = kw.pop('validators', [])
|
||||
|
||||
def __call__(self, x=None):
|
||||
o = copy.deepcopy(self)
|
||||
if x: o.validates(x)
|
||||
return o
|
||||
|
||||
def render(self):
|
||||
out = ''
|
||||
out += self.rendernote(self.note)
|
||||
out += '<table>\n'
|
||||
|
||||
for i in self.inputs:
|
||||
html = utils.safeunicode(i.pre) + i.render() + self.rendernote(i.note) + utils.safeunicode(i.post)
|
||||
if i.is_hidden():
|
||||
out += ' <tr style="display: none;"><th></th><td>%s</td></tr>\n' % (html)
|
||||
else:
|
||||
out += ' <tr><th><label for="%s">%s</label></th><td>%s</td></tr>\n' % (i.id, net.websafe(i.description), html)
|
||||
out += "</table>"
|
||||
return out
|
||||
|
||||
def render_css(self):
|
||||
out = []
|
||||
out.append(self.rendernote(self.note))
|
||||
for i in self.inputs:
|
||||
if not i.is_hidden():
|
||||
out.append('<label for="%s">%s</label>' % (i.id, net.websafe(i.description)))
|
||||
out.append(i.pre)
|
||||
out.append(i.render())
|
||||
out.append(self.rendernote(i.note))
|
||||
out.append(i.post)
|
||||
out.append('\n')
|
||||
return ''.join(out)
|
||||
|
||||
def rendernote(self, note):
|
||||
if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
|
||||
else: return ""
|
||||
|
||||
def validates(self, source=None, _validate=True, **kw):
|
||||
source = source or kw or web.input()
|
||||
out = True
|
||||
for i in self.inputs:
|
||||
v = attrget(source, i.name)
|
||||
if _validate:
|
||||
out = i.validate(v) and out
|
||||
else:
|
||||
i.set_value(v)
|
||||
if _validate:
|
||||
out = out and self._validate(source)
|
||||
self.valid = out
|
||||
return out
|
||||
|
||||
def _validate(self, value):
|
||||
self.value = value
|
||||
for v in self.validators:
|
||||
if not v.valid(value):
|
||||
self.note = v.msg
|
||||
return False
|
||||
return True
|
||||
|
||||
def fill(self, source=None, **kw):
|
||||
return self.validates(source, _validate=False, **kw)
|
||||
|
||||
def __getitem__(self, i):
|
||||
for x in self.inputs:
|
||||
if x.name == i: return x
|
||||
raise KeyError, i
|
||||
|
||||
def __getattr__(self, name):
|
||||
# don't interfere with deepcopy
|
||||
inputs = self.__dict__.get('inputs') or []
|
||||
for x in inputs:
|
||||
if x.name == name: return x
|
||||
raise AttributeError, name
|
||||
|
||||
def get(self, i, default=None):
|
||||
try:
|
||||
return self[i]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def _get_d(self): #@@ should really be form.attr, no?
|
||||
return utils.storage([(i.name, i.get_value()) for i in self.inputs])
|
||||
d = property(_get_d)
|
||||
|
||||
class Input(object):
|
||||
def __init__(self, name, *validators, **attrs):
|
||||
self.name = name
|
||||
self.validators = validators
|
||||
self.attrs = attrs = AttributeList(attrs)
|
||||
|
||||
self.description = attrs.pop('description', name)
|
||||
self.value = attrs.pop('value', None)
|
||||
self.pre = attrs.pop('pre', "")
|
||||
self.post = attrs.pop('post', "")
|
||||
self.note = None
|
||||
|
||||
self.id = attrs.setdefault('id', self.get_default_id())
|
||||
|
||||
if 'class_' in attrs:
|
||||
attrs['class'] = attrs['class_']
|
||||
del attrs['class_']
|
||||
|
||||
def is_hidden(self):
|
||||
return False
|
||||
|
||||
def get_type(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_default_id(self):
|
||||
return self.name
|
||||
|
||||
def validate(self, value):
|
||||
self.set_value(value)
|
||||
|
||||
for v in self.validators:
|
||||
if not v.valid(value):
|
||||
self.note = v.msg
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_value(self, value):
|
||||
self.value = value
|
||||
|
||||
def get_value(self):
|
||||
return self.value
|
||||
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['type'] = self.get_type()
|
||||
if self.value is not None:
|
||||
attrs['value'] = self.value
|
||||
attrs['name'] = self.name
|
||||
return '<input %s/>' % attrs
|
||||
|
||||
def rendernote(self, note):
|
||||
if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
|
||||
else: return ""
|
||||
|
||||
def addatts(self):
|
||||
# add leading space for backward-compatibility
|
||||
return " " + str(self.attrs)
|
||||
|
||||
class AttributeList(dict):
|
||||
"""List of atributes of input.
|
||||
|
||||
>>> a = AttributeList(type='text', name='x', value=20)
|
||||
>>> a
|
||||
<attrs: 'type="text" name="x" value="20"'>
|
||||
"""
|
||||
def copy(self):
|
||||
return AttributeList(self)
|
||||
|
||||
def __str__(self):
|
||||
return " ".join(['%s="%s"' % (k, net.websafe(v)) for k, v in self.items()])
|
||||
|
||||
def __repr__(self):
|
||||
return '<attrs: %s>' % repr(str(self))
|
||||
|
||||
class Textbox(Input):
|
||||
"""Textbox input.
|
||||
|
||||
>>> Textbox(name='foo', value='bar').render()
|
||||
u'<input type="text" id="foo" value="bar" name="foo"/>'
|
||||
>>> Textbox(name='foo', value=0).render()
|
||||
u'<input type="text" id="foo" value="0" name="foo"/>'
|
||||
"""
|
||||
def get_type(self):
|
||||
return 'text'
|
||||
|
||||
class Password(Input):
|
||||
"""Password input.
|
||||
|
||||
>>> Password(name='password', value='secret').render()
|
||||
u'<input type="password" id="password" value="secret" name="password"/>'
|
||||
"""
|
||||
|
||||
def get_type(self):
|
||||
return 'password'
|
||||
|
||||
class Textarea(Input):
|
||||
"""Textarea input.
|
||||
|
||||
>>> Textarea(name='foo', value='bar').render()
|
||||
u'<textarea id="foo" name="foo">bar</textarea>'
|
||||
"""
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['name'] = self.name
|
||||
value = net.websafe(self.value or '')
|
||||
return '<textarea %s>%s</textarea>' % (attrs, value)
|
||||
|
||||
class Dropdown(Input):
|
||||
r"""Dropdown/select input.
|
||||
|
||||
>>> Dropdown(name='foo', args=['a', 'b', 'c'], value='b').render()
|
||||
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'
|
||||
>>> Dropdown(name='foo', args=[('a', 'aa'), ('b', 'bb'), ('c', 'cc')], value='b').render()
|
||||
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'
|
||||
"""
|
||||
def __init__(self, name, args, *validators, **attrs):
|
||||
self.args = args
|
||||
super(Dropdown, self).__init__(name, *validators, **attrs)
|
||||
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['name'] = self.name
|
||||
|
||||
x = '<select %s>\n' % attrs
|
||||
|
||||
for arg in self.args:
|
||||
x += self._render_option(arg)
|
||||
|
||||
x += '</select>\n'
|
||||
return x
|
||||
|
||||
def _render_option(self, arg, indent=' '):
|
||||
if isinstance(arg, (tuple, list)):
|
||||
value, desc= arg
|
||||
else:
|
||||
value, desc = arg, arg
|
||||
|
||||
if self.value == value or (isinstance(self.value, list) and value in self.value):
|
||||
select_p = ' selected="selected"'
|
||||
else:
|
||||
select_p = ''
|
||||
return indent + '<option%s value="%s">%s</option>\n' % (select_p, net.websafe(value), net.websafe(desc))
|
||||
|
||||
|
||||
class GroupedDropdown(Dropdown):
|
||||
r"""Grouped Dropdown/select input.
|
||||
|
||||
>>> GroupedDropdown(name='car_type', args=(('Swedish Cars', ('Volvo', 'Saab')), ('German Cars', ('Mercedes', 'Audi'))), value='Audi').render()
|
||||
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'
|
||||
>>> GroupedDropdown(name='car_type', args=(('Swedish Cars', (('v', 'Volvo'), ('s', 'Saab'))), ('German Cars', (('m', 'Mercedes'), ('a', 'Audi')))), value='a').render()
|
||||
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'
|
||||
|
||||
"""
|
||||
def __init__(self, name, args, *validators, **attrs):
|
||||
self.args = args
|
||||
super(Dropdown, self).__init__(name, *validators, **attrs)
|
||||
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['name'] = self.name
|
||||
|
||||
x = '<select %s>\n' % attrs
|
||||
|
||||
for label, options in self.args:
|
||||
x += ' <optgroup label="%s">\n' % net.websafe(label)
|
||||
for arg in options:
|
||||
x += self._render_option(arg, indent = ' ')
|
||||
x += ' </optgroup>\n'
|
||||
|
||||
x += '</select>\n'
|
||||
return x
|
||||
|
||||
class Radio(Input):
|
||||
def __init__(self, name, args, *validators, **attrs):
|
||||
self.args = args
|
||||
super(Radio, self).__init__(name, *validators, **attrs)
|
||||
|
||||
def render(self):
|
||||
x = '<span>'
|
||||
for arg in self.args:
|
||||
if isinstance(arg, (tuple, list)):
|
||||
value, desc= arg
|
||||
else:
|
||||
value, desc = arg, arg
|
||||
attrs = self.attrs.copy()
|
||||
attrs['name'] = self.name
|
||||
attrs['type'] = 'radio'
|
||||
attrs['value'] = value
|
||||
if self.value == value:
|
||||
attrs['checked'] = 'checked'
|
||||
x += '<input %s/> %s' % (attrs, net.websafe(desc))
|
||||
x += '</span>'
|
||||
return x
|
||||
|
||||
class Checkbox(Input):
|
||||
"""Checkbox input.
|
||||
|
||||
>>> Checkbox('foo', value='bar', checked=True).render()
|
||||
u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>'
|
||||
>>> Checkbox('foo', value='bar').render()
|
||||
u'<input type="checkbox" id="foo_bar" value="bar" name="foo"/>'
|
||||
>>> c = Checkbox('foo', value='bar')
|
||||
>>> c.validate('on')
|
||||
True
|
||||
>>> c.render()
|
||||
u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>'
|
||||
"""
|
||||
def __init__(self, name, *validators, **attrs):
|
||||
self.checked = attrs.pop('checked', False)
|
||||
Input.__init__(self, name, *validators, **attrs)
|
||||
|
||||
def get_default_id(self):
|
||||
value = utils.safestr(self.value or "")
|
||||
return self.name + '_' + value.replace(' ', '_')
|
||||
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['type'] = 'checkbox'
|
||||
attrs['name'] = self.name
|
||||
attrs['value'] = self.value
|
||||
|
||||
if self.checked:
|
||||
attrs['checked'] = 'checked'
|
||||
return '<input %s/>' % attrs
|
||||
|
||||
def set_value(self, value):
|
||||
self.checked = bool(value)
|
||||
|
||||
def get_value(self):
|
||||
return self.checked
|
||||
|
||||
class Button(Input):
|
||||
"""HTML Button.
|
||||
|
||||
>>> Button("save").render()
|
||||
u'<button id="save" name="save">save</button>'
|
||||
>>> Button("action", value="save", html="<b>Save Changes</b>").render()
|
||||
u'<button id="action" value="save" name="action"><b>Save Changes</b></button>'
|
||||
"""
|
||||
def __init__(self, name, *validators, **attrs):
|
||||
super(Button, self).__init__(name, *validators, **attrs)
|
||||
self.description = ""
|
||||
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['name'] = self.name
|
||||
if self.value is not None:
|
||||
attrs['value'] = self.value
|
||||
html = attrs.pop('html', None) or net.websafe(self.name)
|
||||
return '<button %s>%s</button>' % (attrs, html)
|
||||
|
||||
class Hidden(Input):
|
||||
"""Hidden Input.
|
||||
|
||||
>>> Hidden(name='foo', value='bar').render()
|
||||
u'<input type="hidden" id="foo" value="bar" name="foo"/>'
|
||||
"""
|
||||
def is_hidden(self):
|
||||
return True
|
||||
|
||||
def get_type(self):
|
||||
return 'hidden'
|
||||
|
||||
class File(Input):
|
||||
"""File input.
|
||||
|
||||
>>> File(name='f').render()
|
||||
u'<input type="file" id="f" name="f"/>'
|
||||
"""
|
||||
def get_type(self):
|
||||
return 'file'
|
||||
|
||||
class Validator:
|
||||
def __deepcopy__(self, memo): return copy.copy(self)
|
||||
def __init__(self, msg, test, jstest=None): utils.autoassign(self, locals())
|
||||
def valid(self, value):
|
||||
try: return self.test(value)
|
||||
except: return False
|
||||
|
||||
notnull = Validator("Required", bool)
|
||||
|
||||
class regexp(Validator):
|
||||
def __init__(self, rexp, msg):
|
||||
self.rexp = re.compile(rexp)
|
||||
self.msg = msg
|
||||
|
||||
def valid(self, value):
|
||||
return bool(self.rexp.match(value))
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
"""
|
||||
HTML forms
|
||||
(part of web.py)
|
||||
"""
|
||||
|
||||
import copy, re
|
||||
import webapi as web
|
||||
import utils, net
|
||||
|
||||
def attrget(obj, attr, value=None):
|
||||
try:
|
||||
if hasattr(obj, 'has_key') and obj.has_key(attr):
|
||||
return obj[attr]
|
||||
except TypeError:
|
||||
# Handle the case where has_key takes different number of arguments.
|
||||
# This is the case with Model objects on appengine. See #134
|
||||
pass
|
||||
if hasattr(obj, attr):
|
||||
return getattr(obj, attr)
|
||||
return value
|
||||
|
||||
class Form(object):
|
||||
r"""
|
||||
HTML form.
|
||||
|
||||
>>> f = Form(Textbox("x"))
|
||||
>>> f.render()
|
||||
u'<table>\n <tr><th><label for="x">x</label></th><td><input type="text" id="x" name="x"/></td></tr>\n</table>'
|
||||
"""
|
||||
def __init__(self, *inputs, **kw):
|
||||
self.inputs = inputs
|
||||
self.valid = True
|
||||
self.note = None
|
||||
self.validators = kw.pop('validators', [])
|
||||
|
||||
def __call__(self, x=None):
|
||||
o = copy.deepcopy(self)
|
||||
if x: o.validates(x)
|
||||
return o
|
||||
|
||||
def render(self):
|
||||
out = ''
|
||||
out += self.rendernote(self.note)
|
||||
out += '<table>\n'
|
||||
|
||||
for i in self.inputs:
|
||||
html = utils.safeunicode(i.pre) + i.render() + self.rendernote(i.note) + utils.safeunicode(i.post)
|
||||
if i.is_hidden():
|
||||
out += ' <tr style="display: none;"><th></th><td>%s</td></tr>\n' % (html)
|
||||
else:
|
||||
out += ' <tr><th><label for="%s">%s</label></th><td>%s</td></tr>\n' % (i.id, net.websafe(i.description), html)
|
||||
out += "</table>"
|
||||
return out
|
||||
|
||||
def render_css(self):
|
||||
out = []
|
||||
out.append(self.rendernote(self.note))
|
||||
for i in self.inputs:
|
||||
if not i.is_hidden():
|
||||
out.append('<label for="%s">%s</label>' % (i.id, net.websafe(i.description)))
|
||||
out.append(i.pre)
|
||||
out.append(i.render())
|
||||
out.append(self.rendernote(i.note))
|
||||
out.append(i.post)
|
||||
out.append('\n')
|
||||
return ''.join(out)
|
||||
|
||||
def rendernote(self, note):
|
||||
if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
|
||||
else: return ""
|
||||
|
||||
def validates(self, source=None, _validate=True, **kw):
|
||||
source = source or kw or web.input()
|
||||
out = True
|
||||
for i in self.inputs:
|
||||
v = attrget(source, i.name)
|
||||
if _validate:
|
||||
out = i.validate(v) and out
|
||||
else:
|
||||
i.set_value(v)
|
||||
if _validate:
|
||||
out = out and self._validate(source)
|
||||
self.valid = out
|
||||
return out
|
||||
|
||||
def _validate(self, value):
|
||||
self.value = value
|
||||
for v in self.validators:
|
||||
if not v.valid(value):
|
||||
self.note = v.msg
|
||||
return False
|
||||
return True
|
||||
|
||||
def fill(self, source=None, **kw):
|
||||
return self.validates(source, _validate=False, **kw)
|
||||
|
||||
def __getitem__(self, i):
|
||||
for x in self.inputs:
|
||||
if x.name == i: return x
|
||||
raise KeyError, i
|
||||
|
||||
def __getattr__(self, name):
|
||||
# don't interfere with deepcopy
|
||||
inputs = self.__dict__.get('inputs') or []
|
||||
for x in inputs:
|
||||
if x.name == name: return x
|
||||
raise AttributeError, name
|
||||
|
||||
def get(self, i, default=None):
|
||||
try:
|
||||
return self[i]
|
||||
except KeyError:
|
||||
return default
|
||||
|
||||
def _get_d(self): #@@ should really be form.attr, no?
|
||||
return utils.storage([(i.name, i.get_value()) for i in self.inputs])
|
||||
d = property(_get_d)
|
||||
|
||||
class Input(object):
|
||||
def __init__(self, name, *validators, **attrs):
|
||||
self.name = name
|
||||
self.validators = validators
|
||||
self.attrs = attrs = AttributeList(attrs)
|
||||
|
||||
self.description = attrs.pop('description', name)
|
||||
self.value = attrs.pop('value', None)
|
||||
self.pre = attrs.pop('pre', "")
|
||||
self.post = attrs.pop('post', "")
|
||||
self.note = None
|
||||
|
||||
self.id = attrs.setdefault('id', self.get_default_id())
|
||||
|
||||
if 'class_' in attrs:
|
||||
attrs['class'] = attrs['class_']
|
||||
del attrs['class_']
|
||||
|
||||
def is_hidden(self):
|
||||
return False
|
||||
|
||||
def get_type(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def get_default_id(self):
|
||||
return self.name
|
||||
|
||||
def validate(self, value):
|
||||
self.set_value(value)
|
||||
|
||||
for v in self.validators:
|
||||
if not v.valid(value):
|
||||
self.note = v.msg
|
||||
return False
|
||||
return True
|
||||
|
||||
def set_value(self, value):
|
||||
self.value = value
|
||||
|
||||
def get_value(self):
|
||||
return self.value
|
||||
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['type'] = self.get_type()
|
||||
if self.value is not None:
|
||||
attrs['value'] = self.value
|
||||
attrs['name'] = self.name
|
||||
return '<input %s/>' % attrs
|
||||
|
||||
def rendernote(self, note):
|
||||
if note: return '<strong class="wrong">%s</strong>' % net.websafe(note)
|
||||
else: return ""
|
||||
|
||||
def addatts(self):
|
||||
# add leading space for backward-compatibility
|
||||
return " " + str(self.attrs)
|
||||
|
||||
class AttributeList(dict):
|
||||
"""List of atributes of input.
|
||||
|
||||
>>> a = AttributeList(type='text', name='x', value=20)
|
||||
>>> a
|
||||
<attrs: 'type="text" name="x" value="20"'>
|
||||
"""
|
||||
def copy(self):
|
||||
return AttributeList(self)
|
||||
|
||||
def __str__(self):
|
||||
return " ".join(['%s="%s"' % (k, net.websafe(v)) for k, v in self.items()])
|
||||
|
||||
def __repr__(self):
|
||||
return '<attrs: %s>' % repr(str(self))
|
||||
|
||||
class Textbox(Input):
|
||||
"""Textbox input.
|
||||
|
||||
>>> Textbox(name='foo', value='bar').render()
|
||||
u'<input type="text" id="foo" value="bar" name="foo"/>'
|
||||
>>> Textbox(name='foo', value=0).render()
|
||||
u'<input type="text" id="foo" value="0" name="foo"/>'
|
||||
"""
|
||||
def get_type(self):
|
||||
return 'text'
|
||||
|
||||
class Password(Input):
|
||||
"""Password input.
|
||||
|
||||
>>> Password(name='password', value='secret').render()
|
||||
u'<input type="password" id="password" value="secret" name="password"/>'
|
||||
"""
|
||||
|
||||
def get_type(self):
|
||||
return 'password'
|
||||
|
||||
class Textarea(Input):
|
||||
"""Textarea input.
|
||||
|
||||
>>> Textarea(name='foo', value='bar').render()
|
||||
u'<textarea id="foo" name="foo">bar</textarea>'
|
||||
"""
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['name'] = self.name
|
||||
value = net.websafe(self.value or '')
|
||||
return '<textarea %s>%s</textarea>' % (attrs, value)
|
||||
|
||||
class Dropdown(Input):
|
||||
r"""Dropdown/select input.
|
||||
|
||||
>>> Dropdown(name='foo', args=['a', 'b', 'c'], value='b').render()
|
||||
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'
|
||||
>>> Dropdown(name='foo', args=[('a', 'aa'), ('b', 'bb'), ('c', 'cc')], value='b').render()
|
||||
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'
|
||||
"""
|
||||
def __init__(self, name, args, *validators, **attrs):
|
||||
self.args = args
|
||||
super(Dropdown, self).__init__(name, *validators, **attrs)
|
||||
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['name'] = self.name
|
||||
|
||||
x = '<select %s>\n' % attrs
|
||||
|
||||
for arg in self.args:
|
||||
x += self._render_option(arg)
|
||||
|
||||
x += '</select>\n'
|
||||
return x
|
||||
|
||||
def _render_option(self, arg, indent=' '):
|
||||
if isinstance(arg, (tuple, list)):
|
||||
value, desc= arg
|
||||
else:
|
||||
value, desc = arg, arg
|
||||
|
||||
if self.value == value or (isinstance(self.value, list) and value in self.value):
|
||||
select_p = ' selected="selected"'
|
||||
else:
|
||||
select_p = ''
|
||||
return indent + '<option%s value="%s">%s</option>\n' % (select_p, net.websafe(value), net.websafe(desc))
|
||||
|
||||
|
||||
class GroupedDropdown(Dropdown):
|
||||
r"""Grouped Dropdown/select input.
|
||||
|
||||
>>> GroupedDropdown(name='car_type', args=(('Swedish Cars', ('Volvo', 'Saab')), ('German Cars', ('Mercedes', 'Audi'))), value='Audi').render()
|
||||
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'
|
||||
>>> GroupedDropdown(name='car_type', args=(('Swedish Cars', (('v', 'Volvo'), ('s', 'Saab'))), ('German Cars', (('m', 'Mercedes'), ('a', 'Audi')))), value='a').render()
|
||||
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'
|
||||
|
||||
"""
|
||||
def __init__(self, name, args, *validators, **attrs):
|
||||
self.args = args
|
||||
super(Dropdown, self).__init__(name, *validators, **attrs)
|
||||
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['name'] = self.name
|
||||
|
||||
x = '<select %s>\n' % attrs
|
||||
|
||||
for label, options in self.args:
|
||||
x += ' <optgroup label="%s">\n' % net.websafe(label)
|
||||
for arg in options:
|
||||
x += self._render_option(arg, indent = ' ')
|
||||
x += ' </optgroup>\n'
|
||||
|
||||
x += '</select>\n'
|
||||
return x
|
||||
|
||||
class Radio(Input):
|
||||
def __init__(self, name, args, *validators, **attrs):
|
||||
self.args = args
|
||||
super(Radio, self).__init__(name, *validators, **attrs)
|
||||
|
||||
def render(self):
|
||||
x = '<span>'
|
||||
for arg in self.args:
|
||||
if isinstance(arg, (tuple, list)):
|
||||
value, desc= arg
|
||||
else:
|
||||
value, desc = arg, arg
|
||||
attrs = self.attrs.copy()
|
||||
attrs['name'] = self.name
|
||||
attrs['type'] = 'radio'
|
||||
attrs['value'] = value
|
||||
if self.value == value:
|
||||
attrs['checked'] = 'checked'
|
||||
x += '<input %s/> %s' % (attrs, net.websafe(desc))
|
||||
x += '</span>'
|
||||
return x
|
||||
|
||||
class Checkbox(Input):
|
||||
"""Checkbox input.
|
||||
|
||||
>>> Checkbox('foo', value='bar', checked=True).render()
|
||||
u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>'
|
||||
>>> Checkbox('foo', value='bar').render()
|
||||
u'<input type="checkbox" id="foo_bar" value="bar" name="foo"/>'
|
||||
>>> c = Checkbox('foo', value='bar')
|
||||
>>> c.validate('on')
|
||||
True
|
||||
>>> c.render()
|
||||
u'<input checked="checked" type="checkbox" id="foo_bar" value="bar" name="foo"/>'
|
||||
"""
|
||||
def __init__(self, name, *validators, **attrs):
|
||||
self.checked = attrs.pop('checked', False)
|
||||
Input.__init__(self, name, *validators, **attrs)
|
||||
|
||||
def get_default_id(self):
|
||||
value = utils.safestr(self.value or "")
|
||||
return self.name + '_' + value.replace(' ', '_')
|
||||
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['type'] = 'checkbox'
|
||||
attrs['name'] = self.name
|
||||
attrs['value'] = self.value
|
||||
|
||||
if self.checked:
|
||||
attrs['checked'] = 'checked'
|
||||
return '<input %s/>' % attrs
|
||||
|
||||
def set_value(self, value):
|
||||
self.checked = bool(value)
|
||||
|
||||
def get_value(self):
|
||||
return self.checked
|
||||
|
||||
class Button(Input):
|
||||
"""HTML Button.
|
||||
|
||||
>>> Button("save").render()
|
||||
u'<button id="save" name="save">save</button>'
|
||||
>>> Button("action", value="save", html="<b>Save Changes</b>").render()
|
||||
u'<button id="action" value="save" name="action"><b>Save Changes</b></button>'
|
||||
"""
|
||||
def __init__(self, name, *validators, **attrs):
|
||||
super(Button, self).__init__(name, *validators, **attrs)
|
||||
self.description = ""
|
||||
|
||||
def render(self):
|
||||
attrs = self.attrs.copy()
|
||||
attrs['name'] = self.name
|
||||
if self.value is not None:
|
||||
attrs['value'] = self.value
|
||||
html = attrs.pop('html', None) or net.websafe(self.name)
|
||||
return '<button %s>%s</button>' % (attrs, html)
|
||||
|
||||
class Hidden(Input):
|
||||
"""Hidden Input.
|
||||
|
||||
>>> Hidden(name='foo', value='bar').render()
|
||||
u'<input type="hidden" id="foo" value="bar" name="foo"/>'
|
||||
"""
|
||||
def is_hidden(self):
|
||||
return True
|
||||
|
||||
def get_type(self):
|
||||
return 'hidden'
|
||||
|
||||
class File(Input):
|
||||
"""File input.
|
||||
|
||||
>>> File(name='f').render()
|
||||
u'<input type="file" id="f" name="f"/>'
|
||||
"""
|
||||
def get_type(self):
|
||||
return 'file'
|
||||
|
||||
class Validator:
|
||||
def __deepcopy__(self, memo): return copy.copy(self)
|
||||
def __init__(self, msg, test, jstest=None): utils.autoassign(self, locals())
|
||||
def valid(self, value):
|
||||
try: return self.test(value)
|
||||
except: return False
|
||||
|
||||
notnull = Validator("Required", bool)
|
||||
|
||||
class regexp(Validator):
|
||||
def __init__(self, rexp, msg):
|
||||
self.rexp = re.compile(rexp)
|
||||
self.msg = msg
|
||||
|
||||
def valid(self, value):
|
||||
return bool(self.rexp.match(value))
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
|
300
web/http.py
300
web/http.py
|
@ -1,150 +1,150 @@
|
|||
"""
|
||||
HTTP Utilities
|
||||
(from web.py)
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"expires", "lastmodified",
|
||||
"prefixurl", "modified",
|
||||
"changequery", "url",
|
||||
"profiler",
|
||||
]
|
||||
|
||||
import sys, os, threading, urllib, urlparse
|
||||
try: import datetime
|
||||
except ImportError: pass
|
||||
import net, utils, webapi as web
|
||||
|
||||
def prefixurl(base=''):
|
||||
"""
|
||||
Sorry, this function is really difficult to explain.
|
||||
Maybe some other time.
|
||||
"""
|
||||
url = web.ctx.path.lstrip('/')
|
||||
for i in xrange(url.count('/')):
|
||||
base += '../'
|
||||
if not base:
|
||||
base = './'
|
||||
return base
|
||||
|
||||
def expires(delta):
|
||||
"""
|
||||
Outputs an `Expires` header for `delta` from now.
|
||||
`delta` is a `timedelta` object or a number of seconds.
|
||||
"""
|
||||
if isinstance(delta, (int, long)):
|
||||
delta = datetime.timedelta(seconds=delta)
|
||||
date_obj = datetime.datetime.utcnow() + delta
|
||||
web.header('Expires', net.httpdate(date_obj))
|
||||
|
||||
def lastmodified(date_obj):
|
||||
"""Outputs a `Last-Modified` header for `datetime`."""
|
||||
web.header('Last-Modified', net.httpdate(date_obj))
|
||||
|
||||
def modified(date=None, etag=None):
|
||||
"""
|
||||
Checks to see if the page has been modified since the version in the
|
||||
requester's cache.
|
||||
|
||||
When you publish pages, you can include `Last-Modified` and `ETag`
|
||||
with the date the page was last modified and an opaque token for
|
||||
the particular version, respectively. When readers reload the page,
|
||||
the browser sends along the modification date and etag value for
|
||||
the version it has in its cache. If the page hasn't changed,
|
||||
the server can just return `304 Not Modified` and not have to
|
||||
send the whole page again.
|
||||
|
||||
This function takes the last-modified date `date` and the ETag `etag`
|
||||
and checks the headers to see if they match. If they do, it returns
|
||||
`True`, or otherwise it raises NotModified error. It also sets
|
||||
`Last-Modified` and `ETag` output headers.
|
||||
"""
|
||||
try:
|
||||
from __builtin__ import set
|
||||
except ImportError:
|
||||
# for python 2.3
|
||||
from sets import Set as set
|
||||
|
||||
n = set([x.strip('" ') for x in web.ctx.env.get('HTTP_IF_NONE_MATCH', '').split(',')])
|
||||
m = net.parsehttpdate(web.ctx.env.get('HTTP_IF_MODIFIED_SINCE', '').split(';')[0])
|
||||
validate = False
|
||||
if etag:
|
||||
if '*' in n or etag in n:
|
||||
validate = True
|
||||
if date and m:
|
||||
# we subtract a second because
|
||||
# HTTP dates don't have sub-second precision
|
||||
if date-datetime.timedelta(seconds=1) <= m:
|
||||
validate = True
|
||||
|
||||
if date: lastmodified(date)
|
||||
if etag: web.header('ETag', '"' + etag + '"')
|
||||
if validate:
|
||||
raise web.notmodified()
|
||||
else:
|
||||
return True
|
||||
|
||||
def urlencode(query, doseq=0):
|
||||
"""
|
||||
Same as urllib.urlencode, but supports unicode strings.
|
||||
|
||||
>>> urlencode({'text':'foo bar'})
|
||||
'text=foo+bar'
|
||||
>>> urlencode({'x': [1, 2]}, doseq=True)
|
||||
'x=1&x=2'
|
||||
"""
|
||||
def convert(value, doseq=False):
|
||||
if doseq and isinstance(value, list):
|
||||
return [convert(v) for v in value]
|
||||
else:
|
||||
return utils.safestr(value)
|
||||
|
||||
query = dict([(k, convert(v, doseq)) for k, v in query.items()])
|
||||
return urllib.urlencode(query, doseq=doseq)
|
||||
|
||||
def changequery(query=None, **kw):
|
||||
"""
|
||||
Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return
|
||||
`/foo?a=3&b=2` -- the same URL but with the arguments you requested
|
||||
changed.
|
||||
"""
|
||||
if query is None:
|
||||
query = web.rawinput(method='get')
|
||||
for k, v in kw.iteritems():
|
||||
if v is None:
|
||||
query.pop(k, None)
|
||||
else:
|
||||
query[k] = v
|
||||
out = web.ctx.path
|
||||
if query:
|
||||
out += '?' + urlencode(query, doseq=True)
|
||||
return out
|
||||
|
||||
def url(path=None, doseq=False, **kw):
|
||||
"""
|
||||
Makes url by concatenating web.ctx.homepath and path and the
|
||||
query string created using the arguments.
|
||||
"""
|
||||
if path is None:
|
||||
path = web.ctx.path
|
||||
if path.startswith("/"):
|
||||
out = web.ctx.homepath + path
|
||||
else:
|
||||
out = path
|
||||
|
||||
if kw:
|
||||
out += '?' + urlencode(kw, doseq=doseq)
|
||||
|
||||
return out
|
||||
|
||||
def profiler(app):
|
||||
"""Outputs basic profiling information at the bottom of each response."""
|
||||
from utils import profile
|
||||
def profile_internal(e, o):
|
||||
out, result = profile(app)(e, o)
|
||||
return list(out) + ['<pre>' + net.websafe(result) + '</pre>']
|
||||
return profile_internal
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
"""
|
||||
HTTP Utilities
|
||||
(from web.py)
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"expires", "lastmodified",
|
||||
"prefixurl", "modified",
|
||||
"changequery", "url",
|
||||
"profiler",
|
||||
]
|
||||
|
||||
import sys, os, threading, urllib, urlparse
|
||||
try: import datetime
|
||||
except ImportError: pass
|
||||
import net, utils, webapi as web
|
||||
|
||||
def prefixurl(base=''):
|
||||
"""
|
||||
Sorry, this function is really difficult to explain.
|
||||
Maybe some other time.
|
||||
"""
|
||||
url = web.ctx.path.lstrip('/')
|
||||
for i in xrange(url.count('/')):
|
||||
base += '../'
|
||||
if not base:
|
||||
base = './'
|
||||
return base
|
||||
|
||||
def expires(delta):
|
||||
"""
|
||||
Outputs an `Expires` header for `delta` from now.
|
||||
`delta` is a `timedelta` object or a number of seconds.
|
||||
"""
|
||||
if isinstance(delta, (int, long)):
|
||||
delta = datetime.timedelta(seconds=delta)
|
||||
date_obj = datetime.datetime.utcnow() + delta
|
||||
web.header('Expires', net.httpdate(date_obj))
|
||||
|
||||
def lastmodified(date_obj):
|
||||
"""Outputs a `Last-Modified` header for `datetime`."""
|
||||
web.header('Last-Modified', net.httpdate(date_obj))
|
||||
|
||||
def modified(date=None, etag=None):
|
||||
"""
|
||||
Checks to see if the page has been modified since the version in the
|
||||
requester's cache.
|
||||
|
||||
When you publish pages, you can include `Last-Modified` and `ETag`
|
||||
with the date the page was last modified and an opaque token for
|
||||
the particular version, respectively. When readers reload the page,
|
||||
the browser sends along the modification date and etag value for
|
||||
the version it has in its cache. If the page hasn't changed,
|
||||
the server can just return `304 Not Modified` and not have to
|
||||
send the whole page again.
|
||||
|
||||
This function takes the last-modified date `date` and the ETag `etag`
|
||||
and checks the headers to see if they match. If they do, it returns
|
||||
`True`, or otherwise it raises NotModified error. It also sets
|
||||
`Last-Modified` and `ETag` output headers.
|
||||
"""
|
||||
try:
|
||||
from __builtin__ import set
|
||||
except ImportError:
|
||||
# for python 2.3
|
||||
from sets import Set as set
|
||||
|
||||
n = set([x.strip('" ') for x in web.ctx.env.get('HTTP_IF_NONE_MATCH', '').split(',')])
|
||||
m = net.parsehttpdate(web.ctx.env.get('HTTP_IF_MODIFIED_SINCE', '').split(';')[0])
|
||||
validate = False
|
||||
if etag:
|
||||
if '*' in n or etag in n:
|
||||
validate = True
|
||||
if date and m:
|
||||
# we subtract a second because
|
||||
# HTTP dates don't have sub-second precision
|
||||
if date-datetime.timedelta(seconds=1) <= m:
|
||||
validate = True
|
||||
|
||||
if date: lastmodified(date)
|
||||
if etag: web.header('ETag', '"' + etag + '"')
|
||||
if validate:
|
||||
raise web.notmodified()
|
||||
else:
|
||||
return True
|
||||
|
||||
def urlencode(query, doseq=0):
|
||||
"""
|
||||
Same as urllib.urlencode, but supports unicode strings.
|
||||
|
||||
>>> urlencode({'text':'foo bar'})
|
||||
'text=foo+bar'
|
||||
>>> urlencode({'x': [1, 2]}, doseq=True)
|
||||
'x=1&x=2'
|
||||
"""
|
||||
def convert(value, doseq=False):
|
||||
if doseq and isinstance(value, list):
|
||||
return [convert(v) for v in value]
|
||||
else:
|
||||
return utils.safestr(value)
|
||||
|
||||
query = dict([(k, convert(v, doseq)) for k, v in query.items()])
|
||||
return urllib.urlencode(query, doseq=doseq)
|
||||
|
||||
def changequery(query=None, **kw):
|
||||
"""
|
||||
Imagine you're at `/foo?a=1&b=2`. Then `changequery(a=3)` will return
|
||||
`/foo?a=3&b=2` -- the same URL but with the arguments you requested
|
||||
changed.
|
||||
"""
|
||||
if query is None:
|
||||
query = web.rawinput(method='get')
|
||||
for k, v in kw.iteritems():
|
||||
if v is None:
|
||||
query.pop(k, None)
|
||||
else:
|
||||
query[k] = v
|
||||
out = web.ctx.path
|
||||
if query:
|
||||
out += '?' + urlencode(query, doseq=True)
|
||||
return out
|
||||
|
||||
def url(path=None, doseq=False, **kw):
|
||||
"""
|
||||
Makes url by concatenating web.ctx.homepath and path and the
|
||||
query string created using the arguments.
|
||||
"""
|
||||
if path is None:
|
||||
path = web.ctx.path
|
||||
if path.startswith("/"):
|
||||
out = web.ctx.homepath + path
|
||||
else:
|
||||
out = path
|
||||
|
||||
if kw:
|
||||
out += '?' + urlencode(kw, doseq=doseq)
|
||||
|
||||
return out
|
||||
|
||||
def profiler(app):
|
||||
"""Outputs basic profiling information at the bottom of each response."""
|
||||
from utils import profile
|
||||
def profile_internal(e, o):
|
||||
out, result = profile(app)(e, o)
|
||||
return list(out) + ['<pre>' + net.websafe(result) + '</pre>']
|
||||
return profile_internal
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
|
|
@ -1,319 +1,319 @@
|
|||
__all__ = ["runsimple"]
|
||||
|
||||
import sys, os
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
import urllib
|
||||
import posixpath
|
||||
|
||||
import webapi as web
|
||||
import net
|
||||
import utils
|
||||
|
||||
def runbasic(func, server_address=("0.0.0.0", 8080)):
|
||||
"""
|
||||
Runs a simple HTTP server hosting WSGI app `func`. The directory `static/`
|
||||
is hosted statically.
|
||||
|
||||
Based on [WsgiServer][ws] from [Colin Stewart][cs].
|
||||
|
||||
[ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
|
||||
[cs]: http://www.owlfish.com/
|
||||
"""
|
||||
# Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
|
||||
# Modified somewhat for simplicity
|
||||
# Used under the modified BSD license:
|
||||
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
||||
|
||||
import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
|
||||
import socket, errno
|
||||
import traceback
|
||||
|
||||
class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
def run_wsgi_app(self):
|
||||
protocol, host, path, parameters, query, fragment = \
|
||||
urlparse.urlparse('http://dummyhost%s' % self.path)
|
||||
|
||||
# we only use path, query
|
||||
env = {'wsgi.version': (1, 0)
|
||||
,'wsgi.url_scheme': 'http'
|
||||
,'wsgi.input': self.rfile
|
||||
,'wsgi.errors': sys.stderr
|
||||
,'wsgi.multithread': 1
|
||||
,'wsgi.multiprocess': 0
|
||||
,'wsgi.run_once': 0
|
||||
,'REQUEST_METHOD': self.command
|
||||
,'REQUEST_URI': self.path
|
||||
,'PATH_INFO': path
|
||||
,'QUERY_STRING': query
|
||||
,'CONTENT_TYPE': self.headers.get('Content-Type', '')
|
||||
,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
|
||||
,'REMOTE_ADDR': self.client_address[0]
|
||||
,'SERVER_NAME': self.server.server_address[0]
|
||||
,'SERVER_PORT': str(self.server.server_address[1])
|
||||
,'SERVER_PROTOCOL': self.request_version
|
||||
}
|
||||
|
||||
for http_header, http_value in self.headers.items():
|
||||
env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
|
||||
http_value
|
||||
|
||||
# Setup the state
|
||||
self.wsgi_sent_headers = 0
|
||||
self.wsgi_headers = []
|
||||
|
||||
try:
|
||||
# We have there environment, now invoke the application
|
||||
result = self.server.app(env, self.wsgi_start_response)
|
||||
try:
|
||||
try:
|
||||
for data in result:
|
||||
if data:
|
||||
self.wsgi_write_data(data)
|
||||
finally:
|
||||
if hasattr(result, 'close'):
|
||||
result.close()
|
||||
except socket.error, socket_err:
|
||||
# Catch common network errors and suppress them
|
||||
if (socket_err.args[0] in \
|
||||
(errno.ECONNABORTED, errno.EPIPE)):
|
||||
return
|
||||
except socket.timeout, socket_timeout:
|
||||
return
|
||||
except:
|
||||
print >> web.debug, traceback.format_exc(),
|
||||
|
||||
if (not self.wsgi_sent_headers):
|
||||
# We must write out something!
|
||||
self.wsgi_write_data(" ")
|
||||
return
|
||||
|
||||
do_POST = run_wsgi_app
|
||||
do_PUT = run_wsgi_app
|
||||
do_DELETE = run_wsgi_app
|
||||
|
||||
def do_GET(self):
|
||||
if self.path.startswith('/static/'):
|
||||
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
||||
else:
|
||||
self.run_wsgi_app()
|
||||
|
||||
def wsgi_start_response(self, response_status, response_headers,
|
||||
exc_info=None):
|
||||
if (self.wsgi_sent_headers):
|
||||
raise Exception \
|
||||
("Headers already sent and start_response called again!")
|
||||
# Should really take a copy to avoid changes in the application....
|
||||
self.wsgi_headers = (response_status, response_headers)
|
||||
return self.wsgi_write_data
|
||||
|
||||
def wsgi_write_data(self, data):
|
||||
if (not self.wsgi_sent_headers):
|
||||
status, headers = self.wsgi_headers
|
||||
# Need to send header prior to data
|
||||
status_code = status[:status.find(' ')]
|
||||
status_msg = status[status.find(' ') + 1:]
|
||||
self.send_response(int(status_code), status_msg)
|
||||
for header, value in headers:
|
||||
self.send_header(header, value)
|
||||
self.end_headers()
|
||||
self.wsgi_sent_headers = 1
|
||||
# Send the data
|
||||
self.wfile.write(data)
|
||||
|
||||
class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
||||
def __init__(self, func, server_address):
|
||||
BaseHTTPServer.HTTPServer.__init__(self,
|
||||
server_address,
|
||||
WSGIHandler)
|
||||
self.app = func
|
||||
self.serverShuttingDown = 0
|
||||
|
||||
print "http://%s:%d/" % server_address
|
||||
WSGIServer(func, server_address).serve_forever()
|
||||
|
||||
# The WSGIServer instance.
|
||||
# Made global so that it can be stopped in embedded mode.
|
||||
server = None
|
||||
|
||||
def runsimple(func, server_address=("0.0.0.0", 8080)):
|
||||
"""
|
||||
Runs [CherryPy][cp] WSGI server hosting WSGI app `func`.
|
||||
The directory `static/` is hosted statically.
|
||||
|
||||
[cp]: http://www.cherrypy.org
|
||||
"""
|
||||
global server
|
||||
func = StaticMiddleware(func)
|
||||
func = LogMiddleware(func)
|
||||
|
||||
server = WSGIServer(server_address, func)
|
||||
|
||||
if server.ssl_adapter:
|
||||
print "https://%s:%d/" % server_address
|
||||
else:
|
||||
print "http://%s:%d/" % server_address
|
||||
|
||||
try:
|
||||
server.start()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
server.stop()
|
||||
server = None
|
||||
|
||||
def WSGIServer(server_address, wsgi_app):
|
||||
"""Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`.
|
||||
This function can be overwritten to customize the webserver or use a different webserver.
|
||||
"""
|
||||
import wsgiserver
|
||||
|
||||
# Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver
|
||||
# prefix. Overwriting it make it work with web.wsgiserver.
|
||||
wsgiserver.ssl_adapters = {
|
||||
'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
|
||||
'pyopenssl': 'web.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
|
||||
}
|
||||
|
||||
server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
|
||||
|
||||
def create_ssl_adapter(cert, key):
|
||||
# wsgiserver tries to import submodules as cherrypy.wsgiserver.foo.
|
||||
# That doesn't work as not it is web.wsgiserver.
|
||||
# Patching sys.modules temporarily to make it work.
|
||||
import types
|
||||
cherrypy = types.ModuleType('cherrypy')
|
||||
cherrypy.wsgiserver = wsgiserver
|
||||
sys.modules['cherrypy'] = cherrypy
|
||||
sys.modules['cherrypy.wsgiserver'] = wsgiserver
|
||||
|
||||
from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
|
||||
adapter = pyOpenSSLAdapter(cert, key)
|
||||
|
||||
# We are done with our work. Cleanup the patches.
|
||||
del sys.modules['cherrypy']
|
||||
del sys.modules['cherrypy.wsgiserver']
|
||||
|
||||
return adapter
|
||||
|
||||
# SSL backward compatibility
|
||||
if (server.ssl_adapter is None and
|
||||
getattr(server, 'ssl_certificate', None) and
|
||||
getattr(server, 'ssl_private_key', None)):
|
||||
server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, server.ssl_private_key)
|
||||
|
||||
server.nodelay = not sys.platform.startswith('java') # TCP_NODELAY isn't supported on the JVM
|
||||
return server
|
||||
|
||||
class StaticApp(SimpleHTTPRequestHandler):
|
||||
"""WSGI application for serving static files."""
|
||||
def __init__(self, environ, start_response):
|
||||
self.headers = []
|
||||
self.environ = environ
|
||||
self.start_response = start_response
|
||||
|
||||
def send_response(self, status, msg=""):
|
||||
self.status = str(status) + " " + msg
|
||||
|
||||
def send_header(self, name, value):
|
||||
self.headers.append((name, value))
|
||||
|
||||
def end_headers(self):
|
||||
pass
|
||||
|
||||
def log_message(*a): pass
|
||||
|
||||
def __iter__(self):
|
||||
environ = self.environ
|
||||
|
||||
self.path = environ.get('PATH_INFO', '')
|
||||
self.client_address = environ.get('REMOTE_ADDR','-'), \
|
||||
environ.get('REMOTE_PORT','-')
|
||||
self.command = environ.get('REQUEST_METHOD', '-')
|
||||
|
||||
from cStringIO import StringIO
|
||||
self.wfile = StringIO() # for capturing error
|
||||
|
||||
try:
|
||||
path = self.translate_path(self.path)
|
||||
etag = '"%s"' % os.path.getmtime(path)
|
||||
client_etag = environ.get('HTTP_IF_NONE_MATCH')
|
||||
self.send_header('ETag', etag)
|
||||
if etag == client_etag:
|
||||
self.send_response(304, "Not Modified")
|
||||
self.start_response(self.status, self.headers)
|
||||
raise StopIteration
|
||||
except OSError:
|
||||
pass # Probably a 404
|
||||
|
||||
f = self.send_head()
|
||||
self.start_response(self.status, self.headers)
|
||||
|
||||
if f:
|
||||
block_size = 16 * 1024
|
||||
while True:
|
||||
buf = f.read(block_size)
|
||||
if not buf:
|
||||
break
|
||||
yield buf
|
||||
f.close()
|
||||
else:
|
||||
value = self.wfile.getvalue()
|
||||
yield value
|
||||
|
||||
class StaticMiddleware:
|
||||
"""WSGI middleware for serving static files."""
|
||||
def __init__(self, app, prefix='/static/'):
|
||||
self.app = app
|
||||
self.prefix = prefix
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
path = environ.get('PATH_INFO', '')
|
||||
path = self.normpath(path)
|
||||
|
||||
if path.startswith(self.prefix):
|
||||
return StaticApp(environ, start_response)
|
||||
else:
|
||||
return self.app(environ, start_response)
|
||||
|
||||
def normpath(self, path):
|
||||
path2 = posixpath.normpath(urllib.unquote(path))
|
||||
if path.endswith("/"):
|
||||
path2 += "/"
|
||||
return path2
|
||||
|
||||
|
||||
class LogMiddleware:
|
||||
"""WSGI middleware for logging the status."""
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.format = '%s - - [%s] "%s %s %s" - %s'
|
||||
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
import StringIO
|
||||
f = StringIO.StringIO()
|
||||
|
||||
class FakeSocket:
|
||||
def makefile(self, *a):
|
||||
return f
|
||||
|
||||
# take log_date_time_string method from BaseHTTPRequestHandler
|
||||
self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
def xstart_response(status, response_headers, *args):
|
||||
out = start_response(status, response_headers, *args)
|
||||
self.log(status, environ)
|
||||
return out
|
||||
|
||||
return self.app(environ, xstart_response)
|
||||
|
||||
def log(self, status, environ):
|
||||
outfile = environ.get('wsgi.errors', web.debug)
|
||||
req = environ.get('PATH_INFO', '_')
|
||||
protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
|
||||
method = environ.get('REQUEST_METHOD', '-')
|
||||
host = "%s:%s" % (environ.get('REMOTE_ADDR','-'),
|
||||
environ.get('REMOTE_PORT','-'))
|
||||
|
||||
time = self.log_date_time_string()
|
||||
|
||||
msg = self.format % (host, time, protocol, method, req, status)
|
||||
print >> outfile, utils.safestr(msg)
|
||||
__all__ = ["runsimple"]
|
||||
|
||||
import sys, os
|
||||
from SimpleHTTPServer import SimpleHTTPRequestHandler
|
||||
import urllib
|
||||
import posixpath
|
||||
|
||||
import webapi as web
|
||||
import net
|
||||
import utils
|
||||
|
||||
def runbasic(func, server_address=("0.0.0.0", 8080)):
|
||||
"""
|
||||
Runs a simple HTTP server hosting WSGI app `func`. The directory `static/`
|
||||
is hosted statically.
|
||||
|
||||
Based on [WsgiServer][ws] from [Colin Stewart][cs].
|
||||
|
||||
[ws]: http://www.owlfish.com/software/wsgiutils/documentation/wsgi-server-api.html
|
||||
[cs]: http://www.owlfish.com/
|
||||
"""
|
||||
# Copyright (c) 2004 Colin Stewart (http://www.owlfish.com/)
|
||||
# Modified somewhat for simplicity
|
||||
# Used under the modified BSD license:
|
||||
# http://www.xfree86.org/3.3.6/COPYRIGHT2.html#5
|
||||
|
||||
import SimpleHTTPServer, SocketServer, BaseHTTPServer, urlparse
|
||||
import socket, errno
|
||||
import traceback
|
||||
|
||||
class WSGIHandler(SimpleHTTPServer.SimpleHTTPRequestHandler):
|
||||
def run_wsgi_app(self):
|
||||
protocol, host, path, parameters, query, fragment = \
|
||||
urlparse.urlparse('http://dummyhost%s' % self.path)
|
||||
|
||||
# we only use path, query
|
||||
env = {'wsgi.version': (1, 0)
|
||||
,'wsgi.url_scheme': 'http'
|
||||
,'wsgi.input': self.rfile
|
||||
,'wsgi.errors': sys.stderr
|
||||
,'wsgi.multithread': 1
|
||||
,'wsgi.multiprocess': 0
|
||||
,'wsgi.run_once': 0
|
||||
,'REQUEST_METHOD': self.command
|
||||
,'REQUEST_URI': self.path
|
||||
,'PATH_INFO': path
|
||||
,'QUERY_STRING': query
|
||||
,'CONTENT_TYPE': self.headers.get('Content-Type', '')
|
||||
,'CONTENT_LENGTH': self.headers.get('Content-Length', '')
|
||||
,'REMOTE_ADDR': self.client_address[0]
|
||||
,'SERVER_NAME': self.server.server_address[0]
|
||||
,'SERVER_PORT': str(self.server.server_address[1])
|
||||
,'SERVER_PROTOCOL': self.request_version
|
||||
}
|
||||
|
||||
for http_header, http_value in self.headers.items():
|
||||
env ['HTTP_%s' % http_header.replace('-', '_').upper()] = \
|
||||
http_value
|
||||
|
||||
# Setup the state
|
||||
self.wsgi_sent_headers = 0
|
||||
self.wsgi_headers = []
|
||||
|
||||
try:
|
||||
# We have there environment, now invoke the application
|
||||
result = self.server.app(env, self.wsgi_start_response)
|
||||
try:
|
||||
try:
|
||||
for data in result:
|
||||
if data:
|
||||
self.wsgi_write_data(data)
|
||||
finally:
|
||||
if hasattr(result, 'close'):
|
||||
result.close()
|
||||
except socket.error, socket_err:
|
||||
# Catch common network errors and suppress them
|
||||
if (socket_err.args[0] in \
|
||||
(errno.ECONNABORTED, errno.EPIPE)):
|
||||
return
|
||||
except socket.timeout, socket_timeout:
|
||||
return
|
||||
except:
|
||||
print >> web.debug, traceback.format_exc(),
|
||||
|
||||
if (not self.wsgi_sent_headers):
|
||||
# We must write out something!
|
||||
self.wsgi_write_data(" ")
|
||||
return
|
||||
|
||||
do_POST = run_wsgi_app
|
||||
do_PUT = run_wsgi_app
|
||||
do_DELETE = run_wsgi_app
|
||||
|
||||
def do_GET(self):
|
||||
if self.path.startswith('/static/'):
|
||||
SimpleHTTPServer.SimpleHTTPRequestHandler.do_GET(self)
|
||||
else:
|
||||
self.run_wsgi_app()
|
||||
|
||||
def wsgi_start_response(self, response_status, response_headers,
|
||||
exc_info=None):
|
||||
if (self.wsgi_sent_headers):
|
||||
raise Exception \
|
||||
("Headers already sent and start_response called again!")
|
||||
# Should really take a copy to avoid changes in the application....
|
||||
self.wsgi_headers = (response_status, response_headers)
|
||||
return self.wsgi_write_data
|
||||
|
||||
def wsgi_write_data(self, data):
|
||||
if (not self.wsgi_sent_headers):
|
||||
status, headers = self.wsgi_headers
|
||||
# Need to send header prior to data
|
||||
status_code = status[:status.find(' ')]
|
||||
status_msg = status[status.find(' ') + 1:]
|
||||
self.send_response(int(status_code), status_msg)
|
||||
for header, value in headers:
|
||||
self.send_header(header, value)
|
||||
self.end_headers()
|
||||
self.wsgi_sent_headers = 1
|
||||
# Send the data
|
||||
self.wfile.write(data)
|
||||
|
||||
class WSGIServer(SocketServer.ThreadingMixIn, BaseHTTPServer.HTTPServer):
|
||||
def __init__(self, func, server_address):
|
||||
BaseHTTPServer.HTTPServer.__init__(self,
|
||||
server_address,
|
||||
WSGIHandler)
|
||||
self.app = func
|
||||
self.serverShuttingDown = 0
|
||||
|
||||
print "http://%s:%d/" % server_address
|
||||
WSGIServer(func, server_address).serve_forever()
|
||||
|
||||
# The WSGIServer instance.
|
||||
# Made global so that it can be stopped in embedded mode.
|
||||
server = None
|
||||
|
||||
def runsimple(func, server_address=("0.0.0.0", 8080)):
|
||||
"""
|
||||
Runs [CherryPy][cp] WSGI server hosting WSGI app `func`.
|
||||
The directory `static/` is hosted statically.
|
||||
|
||||
[cp]: http://www.cherrypy.org
|
||||
"""
|
||||
global server
|
||||
func = StaticMiddleware(func)
|
||||
func = LogMiddleware(func)
|
||||
|
||||
server = WSGIServer(server_address, func)
|
||||
|
||||
if server.ssl_adapter:
|
||||
print "https://%s:%d/" % server_address
|
||||
else:
|
||||
print "http://%s:%d/" % server_address
|
||||
|
||||
try:
|
||||
server.start()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
server.stop()
|
||||
server = None
|
||||
|
||||
def WSGIServer(server_address, wsgi_app):
|
||||
"""Creates CherryPy WSGI server listening at `server_address` to serve `wsgi_app`.
|
||||
This function can be overwritten to customize the webserver or use a different webserver.
|
||||
"""
|
||||
import wsgiserver
|
||||
|
||||
# Default values of wsgiserver.ssl_adapters uses cherrypy.wsgiserver
|
||||
# prefix. Overwriting it make it work with web.wsgiserver.
|
||||
wsgiserver.ssl_adapters = {
|
||||
'builtin': 'web.wsgiserver.ssl_builtin.BuiltinSSLAdapter',
|
||||
'pyopenssl': 'web.wsgiserver.ssl_pyopenssl.pyOpenSSLAdapter',
|
||||
}
|
||||
|
||||
server = wsgiserver.CherryPyWSGIServer(server_address, wsgi_app, server_name="localhost")
|
||||
|
||||
def create_ssl_adapter(cert, key):
|
||||
# wsgiserver tries to import submodules as cherrypy.wsgiserver.foo.
|
||||
# That doesn't work as not it is web.wsgiserver.
|
||||
# Patching sys.modules temporarily to make it work.
|
||||
import types
|
||||
cherrypy = types.ModuleType('cherrypy')
|
||||
cherrypy.wsgiserver = wsgiserver
|
||||
sys.modules['cherrypy'] = cherrypy
|
||||
sys.modules['cherrypy.wsgiserver'] = wsgiserver
|
||||
|
||||
from wsgiserver.ssl_pyopenssl import pyOpenSSLAdapter
|
||||
adapter = pyOpenSSLAdapter(cert, key)
|
||||
|
||||
# We are done with our work. Cleanup the patches.
|
||||
del sys.modules['cherrypy']
|
||||
del sys.modules['cherrypy.wsgiserver']
|
||||
|
||||
return adapter
|
||||
|
||||
# SSL backward compatibility
|
||||
if (server.ssl_adapter is None and
|
||||
getattr(server, 'ssl_certificate', None) and
|
||||
getattr(server, 'ssl_private_key', None)):
|
||||
server.ssl_adapter = create_ssl_adapter(server.ssl_certificate, server.ssl_private_key)
|
||||
|
||||
server.nodelay = not sys.platform.startswith('java') # TCP_NODELAY isn't supported on the JVM
|
||||
return server
|
||||
|
||||
class StaticApp(SimpleHTTPRequestHandler):
|
||||
"""WSGI application for serving static files."""
|
||||
def __init__(self, environ, start_response):
|
||||
self.headers = []
|
||||
self.environ = environ
|
||||
self.start_response = start_response
|
||||
|
||||
def send_response(self, status, msg=""):
|
||||
self.status = str(status) + " " + msg
|
||||
|
||||
def send_header(self, name, value):
|
||||
self.headers.append((name, value))
|
||||
|
||||
def end_headers(self):
|
||||
pass
|
||||
|
||||
def log_message(*a): pass
|
||||
|
||||
def __iter__(self):
|
||||
environ = self.environ
|
||||
|
||||
self.path = environ.get('PATH_INFO', '')
|
||||
self.client_address = environ.get('REMOTE_ADDR','-'), \
|
||||
environ.get('REMOTE_PORT','-')
|
||||
self.command = environ.get('REQUEST_METHOD', '-')
|
||||
|
||||
from cStringIO import StringIO
|
||||
self.wfile = StringIO() # for capturing error
|
||||
|
||||
try:
|
||||
path = self.translate_path(self.path)
|
||||
etag = '"%s"' % os.path.getmtime(path)
|
||||
client_etag = environ.get('HTTP_IF_NONE_MATCH')
|
||||
self.send_header('ETag', etag)
|
||||
if etag == client_etag:
|
||||
self.send_response(304, "Not Modified")
|
||||
self.start_response(self.status, self.headers)
|
||||
raise StopIteration
|
||||
except OSError:
|
||||
pass # Probably a 404
|
||||
|
||||
f = self.send_head()
|
||||
self.start_response(self.status, self.headers)
|
||||
|
||||
if f:
|
||||
block_size = 16 * 1024
|
||||
while True:
|
||||
buf = f.read(block_size)
|
||||
if not buf:
|
||||
break
|
||||
yield buf
|
||||
f.close()
|
||||
else:
|
||||
value = self.wfile.getvalue()
|
||||
yield value
|
||||
|
||||
class StaticMiddleware:
|
||||
"""WSGI middleware for serving static files."""
|
||||
def __init__(self, app, prefix='/static/'):
|
||||
self.app = app
|
||||
self.prefix = prefix
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
path = environ.get('PATH_INFO', '')
|
||||
path = self.normpath(path)
|
||||
|
||||
if path.startswith(self.prefix):
|
||||
return StaticApp(environ, start_response)
|
||||
else:
|
||||
return self.app(environ, start_response)
|
||||
|
||||
def normpath(self, path):
|
||||
path2 = posixpath.normpath(urllib.unquote(path))
|
||||
if path.endswith("/"):
|
||||
path2 += "/"
|
||||
return path2
|
||||
|
||||
|
||||
class LogMiddleware:
|
||||
"""WSGI middleware for logging the status."""
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
self.format = '%s - - [%s] "%s %s %s" - %s'
|
||||
|
||||
from BaseHTTPServer import BaseHTTPRequestHandler
|
||||
import StringIO
|
||||
f = StringIO.StringIO()
|
||||
|
||||
class FakeSocket:
|
||||
def makefile(self, *a):
|
||||
return f
|
||||
|
||||
# take log_date_time_string method from BaseHTTPRequestHandler
|
||||
self.log_date_time_string = BaseHTTPRequestHandler(FakeSocket(), None, None).log_date_time_string
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
def xstart_response(status, response_headers, *args):
|
||||
out = start_response(status, response_headers, *args)
|
||||
self.log(status, environ)
|
||||
return out
|
||||
|
||||
return self.app(environ, xstart_response)
|
||||
|
||||
def log(self, status, environ):
|
||||
outfile = environ.get('wsgi.errors', web.debug)
|
||||
req = environ.get('PATH_INFO', '_')
|
||||
protocol = environ.get('ACTUAL_SERVER_PROTOCOL', '-')
|
||||
method = environ.get('REQUEST_METHOD', '-')
|
||||
host = "%s:%s" % (environ.get('REMOTE_ADDR','-'),
|
||||
environ.get('REMOTE_PORT','-'))
|
||||
|
||||
time = self.log_date_time_string()
|
||||
|
||||
msg = self.format % (host, time, protocol, method, req, status)
|
||||
print >> outfile, utils.safestr(msg)
|
||||
|
|
386
web/net.py
386
web/net.py
|
@ -1,193 +1,193 @@
|
|||
"""
|
||||
Network Utilities
|
||||
(from web.py)
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"validipaddr", "validipport", "validip", "validaddr",
|
||||
"urlquote",
|
||||
"httpdate", "parsehttpdate",
|
||||
"htmlquote", "htmlunquote", "websafe",
|
||||
]
|
||||
|
||||
import urllib, time
|
||||
try: import datetime
|
||||
except ImportError: pass
|
||||
|
||||
def validipaddr(address):
|
||||
"""
|
||||
Returns True if `address` is a valid IPv4 address.
|
||||
|
||||
>>> validipaddr('192.168.1.1')
|
||||
True
|
||||
>>> validipaddr('192.168.1.800')
|
||||
False
|
||||
>>> validipaddr('192.168.1')
|
||||
False
|
||||
"""
|
||||
try:
|
||||
octets = address.split('.')
|
||||
if len(octets) != 4:
|
||||
return False
|
||||
for x in octets:
|
||||
if not (0 <= int(x) <= 255):
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def validipport(port):
|
||||
"""
|
||||
Returns True if `port` is a valid IPv4 port.
|
||||
|
||||
>>> validipport('9000')
|
||||
True
|
||||
>>> validipport('foo')
|
||||
False
|
||||
>>> validipport('1000000')
|
||||
False
|
||||
"""
|
||||
try:
|
||||
if not (0 <= int(port) <= 65535):
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def validip(ip, defaultaddr="0.0.0.0", defaultport=8080):
|
||||
"""Returns `(ip_address, port)` from string `ip_addr_port`"""
|
||||
addr = defaultaddr
|
||||
port = defaultport
|
||||
|
||||
ip = ip.split(":", 1)
|
||||
if len(ip) == 1:
|
||||
if not ip[0]:
|
||||
pass
|
||||
elif validipaddr(ip[0]):
|
||||
addr = ip[0]
|
||||
elif validipport(ip[0]):
|
||||
port = int(ip[0])
|
||||
else:
|
||||
raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
|
||||
elif len(ip) == 2:
|
||||
addr, port = ip
|
||||
if not validipaddr(addr) and validipport(port):
|
||||
raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
|
||||
port = int(port)
|
||||
else:
|
||||
raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
|
||||
return (addr, port)
|
||||
|
||||
def validaddr(string_):
|
||||
"""
|
||||
Returns either (ip_address, port) or "/path/to/socket" from string_
|
||||
|
||||
>>> validaddr('/path/to/socket')
|
||||
'/path/to/socket'
|
||||
>>> validaddr('8000')
|
||||
('0.0.0.0', 8000)
|
||||
>>> validaddr('127.0.0.1')
|
||||
('127.0.0.1', 8080)
|
||||
>>> validaddr('127.0.0.1:8000')
|
||||
('127.0.0.1', 8000)
|
||||
>>> validaddr('fff')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: fff is not a valid IP address/port
|
||||
"""
|
||||
if '/' in string_:
|
||||
return string_
|
||||
else:
|
||||
return validip(string_)
|
||||
|
||||
def urlquote(val):
|
||||
"""
|
||||
Quotes a string for use in a URL.
|
||||
|
||||
>>> urlquote('://?f=1&j=1')
|
||||
'%3A//%3Ff%3D1%26j%3D1'
|
||||
>>> urlquote(None)
|
||||
''
|
||||
>>> urlquote(u'\u203d')
|
||||
'%E2%80%BD'
|
||||
"""
|
||||
if val is None: return ''
|
||||
if not isinstance(val, unicode): val = str(val)
|
||||
else: val = val.encode('utf-8')
|
||||
return urllib.quote(val)
|
||||
|
||||
def httpdate(date_obj):
|
||||
"""
|
||||
Formats a datetime object for use in HTTP headers.
|
||||
|
||||
>>> import datetime
|
||||
>>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1))
|
||||
'Thu, 01 Jan 1970 01:01:01 GMT'
|
||||
"""
|
||||
return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
|
||||
def parsehttpdate(string_):
|
||||
"""
|
||||
Parses an HTTP date into a datetime object.
|
||||
|
||||
>>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT')
|
||||
datetime.datetime(1970, 1, 1, 1, 1, 1)
|
||||
"""
|
||||
try:
|
||||
t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z")
|
||||
except ValueError:
|
||||
return None
|
||||
return datetime.datetime(*t[:6])
|
||||
|
||||
def htmlquote(text):
|
||||
r"""
|
||||
Encodes `text` for raw use in HTML.
|
||||
|
||||
>>> htmlquote(u"<'&\">")
|
||||
u'<'&">'
|
||||
"""
|
||||
text = text.replace(u"&", u"&") # Must be done first!
|
||||
text = text.replace(u"<", u"<")
|
||||
text = text.replace(u">", u">")
|
||||
text = text.replace(u"'", u"'")
|
||||
text = text.replace(u'"', u""")
|
||||
return text
|
||||
|
||||
def htmlunquote(text):
|
||||
r"""
|
||||
Decodes `text` that's HTML quoted.
|
||||
|
||||
>>> htmlunquote(u'<'&">')
|
||||
u'<\'&">'
|
||||
"""
|
||||
text = text.replace(u""", u'"')
|
||||
text = text.replace(u"'", u"'")
|
||||
text = text.replace(u">", u">")
|
||||
text = text.replace(u"<", u"<")
|
||||
text = text.replace(u"&", u"&") # Must be done last!
|
||||
return text
|
||||
|
||||
def websafe(val):
|
||||
r"""Converts `val` so that it is safe for use in Unicode HTML.
|
||||
|
||||
>>> websafe("<'&\">")
|
||||
u'<'&">'
|
||||
>>> websafe(None)
|
||||
u''
|
||||
>>> websafe(u'\u203d')
|
||||
u'\u203d'
|
||||
>>> websafe('\xe2\x80\xbd')
|
||||
u'\u203d'
|
||||
"""
|
||||
if val is None:
|
||||
return u''
|
||||
elif isinstance(val, str):
|
||||
val = val.decode('utf-8')
|
||||
elif not isinstance(val, unicode):
|
||||
val = unicode(val)
|
||||
|
||||
return htmlquote(val)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
"""
|
||||
Network Utilities
|
||||
(from web.py)
|
||||
"""
|
||||
|
||||
__all__ = [
|
||||
"validipaddr", "validipport", "validip", "validaddr",
|
||||
"urlquote",
|
||||
"httpdate", "parsehttpdate",
|
||||
"htmlquote", "htmlunquote", "websafe",
|
||||
]
|
||||
|
||||
import urllib, time
|
||||
try: import datetime
|
||||
except ImportError: pass
|
||||
|
||||
def validipaddr(address):
|
||||
"""
|
||||
Returns True if `address` is a valid IPv4 address.
|
||||
|
||||
>>> validipaddr('192.168.1.1')
|
||||
True
|
||||
>>> validipaddr('192.168.1.800')
|
||||
False
|
||||
>>> validipaddr('192.168.1')
|
||||
False
|
||||
"""
|
||||
try:
|
||||
octets = address.split('.')
|
||||
if len(octets) != 4:
|
||||
return False
|
||||
for x in octets:
|
||||
if not (0 <= int(x) <= 255):
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def validipport(port):
|
||||
"""
|
||||
Returns True if `port` is a valid IPv4 port.
|
||||
|
||||
>>> validipport('9000')
|
||||
True
|
||||
>>> validipport('foo')
|
||||
False
|
||||
>>> validipport('1000000')
|
||||
False
|
||||
"""
|
||||
try:
|
||||
if not (0 <= int(port) <= 65535):
|
||||
return False
|
||||
except ValueError:
|
||||
return False
|
||||
return True
|
||||
|
||||
def validip(ip, defaultaddr="0.0.0.0", defaultport=8080):
|
||||
"""Returns `(ip_address, port)` from string `ip_addr_port`"""
|
||||
addr = defaultaddr
|
||||
port = defaultport
|
||||
|
||||
ip = ip.split(":", 1)
|
||||
if len(ip) == 1:
|
||||
if not ip[0]:
|
||||
pass
|
||||
elif validipaddr(ip[0]):
|
||||
addr = ip[0]
|
||||
elif validipport(ip[0]):
|
||||
port = int(ip[0])
|
||||
else:
|
||||
raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
|
||||
elif len(ip) == 2:
|
||||
addr, port = ip
|
||||
if not validipaddr(addr) and validipport(port):
|
||||
raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
|
||||
port = int(port)
|
||||
else:
|
||||
raise ValueError, ':'.join(ip) + ' is not a valid IP address/port'
|
||||
return (addr, port)
|
||||
|
||||
def validaddr(string_):
|
||||
"""
|
||||
Returns either (ip_address, port) or "/path/to/socket" from string_
|
||||
|
||||
>>> validaddr('/path/to/socket')
|
||||
'/path/to/socket'
|
||||
>>> validaddr('8000')
|
||||
('0.0.0.0', 8000)
|
||||
>>> validaddr('127.0.0.1')
|
||||
('127.0.0.1', 8080)
|
||||
>>> validaddr('127.0.0.1:8000')
|
||||
('127.0.0.1', 8000)
|
||||
>>> validaddr('fff')
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
ValueError: fff is not a valid IP address/port
|
||||
"""
|
||||
if '/' in string_:
|
||||
return string_
|
||||
else:
|
||||
return validip(string_)
|
||||
|
||||
def urlquote(val):
|
||||
"""
|
||||
Quotes a string for use in a URL.
|
||||
|
||||
>>> urlquote('://?f=1&j=1')
|
||||
'%3A//%3Ff%3D1%26j%3D1'
|
||||
>>> urlquote(None)
|
||||
''
|
||||
>>> urlquote(u'\u203d')
|
||||
'%E2%80%BD'
|
||||
"""
|
||||
if val is None: return ''
|
||||
if not isinstance(val, unicode): val = str(val)
|
||||
else: val = val.encode('utf-8')
|
||||
return urllib.quote(val)
|
||||
|
||||
def httpdate(date_obj):
|
||||
"""
|
||||
Formats a datetime object for use in HTTP headers.
|
||||
|
||||
>>> import datetime
|
||||
>>> httpdate(datetime.datetime(1970, 1, 1, 1, 1, 1))
|
||||
'Thu, 01 Jan 1970 01:01:01 GMT'
|
||||
"""
|
||||
return date_obj.strftime("%a, %d %b %Y %H:%M:%S GMT")
|
||||
|
||||
def parsehttpdate(string_):
|
||||
"""
|
||||
Parses an HTTP date into a datetime object.
|
||||
|
||||
>>> parsehttpdate('Thu, 01 Jan 1970 01:01:01 GMT')
|
||||
datetime.datetime(1970, 1, 1, 1, 1, 1)
|
||||
"""
|
||||
try:
|
||||
t = time.strptime(string_, "%a, %d %b %Y %H:%M:%S %Z")
|
||||
except ValueError:
|
||||
return None
|
||||
return datetime.datetime(*t[:6])
|
||||
|
||||
def htmlquote(text):
|
||||
r"""
|
||||
Encodes `text` for raw use in HTML.
|
||||
|
||||
>>> htmlquote(u"<'&\">")
|
||||
u'<'&">'
|
||||
"""
|
||||
text = text.replace(u"&", u"&") # Must be done first!
|
||||
text = text.replace(u"<", u"<")
|
||||
text = text.replace(u">", u">")
|
||||
text = text.replace(u"'", u"'")
|
||||
text = text.replace(u'"', u""")
|
||||
return text
|
||||
|
||||
def htmlunquote(text):
|
||||
r"""
|
||||
Decodes `text` that's HTML quoted.
|
||||
|
||||
>>> htmlunquote(u'<'&">')
|
||||
u'<\'&">'
|
||||
"""
|
||||
text = text.replace(u""", u'"')
|
||||
text = text.replace(u"'", u"'")
|
||||
text = text.replace(u">", u">")
|
||||
text = text.replace(u"<", u"<")
|
||||
text = text.replace(u"&", u"&") # Must be done last!
|
||||
return text
|
||||
|
||||
def websafe(val):
|
||||
r"""Converts `val` so that it is safe for use in Unicode HTML.
|
||||
|
||||
>>> websafe("<'&\">")
|
||||
u'<'&">'
|
||||
>>> websafe(None)
|
||||
u''
|
||||
>>> websafe(u'\u203d')
|
||||
u'\u203d'
|
||||
>>> websafe('\xe2\x80\xbd')
|
||||
u'\u203d'
|
||||
"""
|
||||
if val is None:
|
||||
return u''
|
||||
elif isinstance(val, str):
|
||||
val = val.decode('utf-8')
|
||||
elif not isinstance(val, unicode):
|
||||
val = unicode(val)
|
||||
|
||||
return htmlquote(val)
|
||||
|
||||
if __name__ == "__main__":
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
|
|
@ -1,46 +1,46 @@
|
|||
"""Python 2.3 compatabilty"""
|
||||
import threading
|
||||
|
||||
class threadlocal(object):
|
||||
"""Implementation of threading.local for python2.3.
|
||||
"""
|
||||
def __getattribute__(self, name):
|
||||
if name == "__dict__":
|
||||
return threadlocal._getd(self)
|
||||
else:
|
||||
try:
|
||||
return object.__getattribute__(self, name)
|
||||
except AttributeError:
|
||||
try:
|
||||
return self.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError, name
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
self.__dict__[name] = value
|
||||
|
||||
def __delattr__(self, name):
|
||||
try:
|
||||
del self.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError, name
|
||||
|
||||
def _getd(self):
|
||||
t = threading.currentThread()
|
||||
if not hasattr(t, '_d'):
|
||||
# using __dict__ of thread as thread local storage
|
||||
t._d = {}
|
||||
|
||||
_id = id(self)
|
||||
# there could be multiple instances of threadlocal.
|
||||
# use id(self) as key
|
||||
if _id not in t._d:
|
||||
t._d[_id] = {}
|
||||
return t._d[_id]
|
||||
|
||||
if __name__ == '__main__':
|
||||
d = threadlocal()
|
||||
d.x = 1
|
||||
print d.__dict__
|
||||
print d.x
|
||||
"""Python 2.3 compatabilty"""
|
||||
import threading
|
||||
|
||||
class threadlocal(object):
|
||||
"""Implementation of threading.local for python2.3.
|
||||
"""
|
||||
def __getattribute__(self, name):
|
||||
if name == "__dict__":
|
||||
return threadlocal._getd(self)
|
||||
else:
|
||||
try:
|
||||
return object.__getattribute__(self, name)
|
||||
except AttributeError:
|
||||
try:
|
||||
return self.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError, name
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
self.__dict__[name] = value
|
||||
|
||||
def __delattr__(self, name):
|
||||
try:
|
||||
del self.__dict__[name]
|
||||
except KeyError:
|
||||
raise AttributeError, name
|
||||
|
||||
def _getd(self):
|
||||
t = threading.currentThread()
|
||||
if not hasattr(t, '_d'):
|
||||
# using __dict__ of thread as thread local storage
|
||||
t._d = {}
|
||||
|
||||
_id = id(self)
|
||||
# there could be multiple instances of threadlocal.
|
||||
# use id(self) as key
|
||||
if _id not in t._d:
|
||||
t._d[_id] = {}
|
||||
return t._d[_id]
|
||||
|
||||
if __name__ == '__main__':
|
||||
d = threadlocal()
|
||||
d.x = 1
|
||||
print d.__dict__
|
||||
print d.x
|
||||
|
716
web/session.py
716
web/session.py
|
@ -1,358 +1,358 @@
|
|||
"""
|
||||
Session Management
|
||||
(from web.py)
|
||||
"""
|
||||
|
||||
import os, time, datetime, random, base64
|
||||
import os.path
|
||||
from copy import deepcopy
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
try:
|
||||
import hashlib
|
||||
sha1 = hashlib.sha1
|
||||
except ImportError:
|
||||
import sha
|
||||
sha1 = sha.new
|
||||
|
||||
import utils
|
||||
import webapi as web
|
||||
|
||||
__all__ = [
|
||||
'Session', 'SessionExpired',
|
||||
'Store', 'DiskStore', 'DBStore',
|
||||
]
|
||||
|
||||
web.config.session_parameters = utils.storage({
|
||||
'cookie_name': 'webpy_session_id',
|
||||
'cookie_domain': None,
|
||||
'cookie_path' : None,
|
||||
'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds
|
||||
'ignore_expiry': True,
|
||||
'ignore_change_ip': True,
|
||||
'secret_key': 'fLjUfxqXtfNoIldA0A0J',
|
||||
'expired_message': 'Session expired',
|
||||
'httponly': True,
|
||||
'secure': False
|
||||
})
|
||||
|
||||
class SessionExpired(web.HTTPError):
|
||||
def __init__(self, message):
|
||||
web.HTTPError.__init__(self, '200 OK', {}, data=message)
|
||||
|
||||
class Session(object):
|
||||
"""Session management for web.py
|
||||
"""
|
||||
__slots__ = [
|
||||
"store", "_initializer", "_last_cleanup_time", "_config", "_data",
|
||||
"__getitem__", "__setitem__", "__delitem__"
|
||||
]
|
||||
|
||||
def __init__(self, app, store, initializer=None):
|
||||
self.store = store
|
||||
self._initializer = initializer
|
||||
self._last_cleanup_time = 0
|
||||
self._config = utils.storage(web.config.session_parameters)
|
||||
self._data = utils.threadeddict()
|
||||
|
||||
self.__getitem__ = self._data.__getitem__
|
||||
self.__setitem__ = self._data.__setitem__
|
||||
self.__delitem__ = self._data.__delitem__
|
||||
|
||||
if app:
|
||||
app.add_processor(self._processor)
|
||||
|
||||
def __contains__(self, name):
|
||||
return name in self._data
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._data, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in self.__slots__:
|
||||
object.__setattr__(self, name, value)
|
||||
else:
|
||||
setattr(self._data, name, value)
|
||||
|
||||
def __delattr__(self, name):
|
||||
delattr(self._data, name)
|
||||
|
||||
def _processor(self, handler):
|
||||
"""Application processor to setup session for every request"""
|
||||
self._cleanup()
|
||||
self._load()
|
||||
|
||||
try:
|
||||
return handler()
|
||||
finally:
|
||||
self._save()
|
||||
|
||||
def _load(self):
|
||||
"""Load the session from the store, by the id from cookie"""
|
||||
cookie_name = self._config.cookie_name
|
||||
cookie_domain = self._config.cookie_domain
|
||||
cookie_path = self._config.cookie_path
|
||||
httponly = self._config.httponly
|
||||
self.session_id = web.cookies().get(cookie_name)
|
||||
|
||||
# protection against session_id tampering
|
||||
if self.session_id and not self._valid_session_id(self.session_id):
|
||||
self.session_id = None
|
||||
|
||||
self._check_expiry()
|
||||
if self.session_id:
|
||||
d = self.store[self.session_id]
|
||||
self.update(d)
|
||||
self._validate_ip()
|
||||
|
||||
if not self.session_id:
|
||||
self.session_id = self._generate_session_id()
|
||||
|
||||
if self._initializer:
|
||||
if isinstance(self._initializer, dict):
|
||||
self.update(deepcopy(self._initializer))
|
||||
elif hasattr(self._initializer, '__call__'):
|
||||
self._initializer()
|
||||
|
||||
self.ip = web.ctx.ip
|
||||
|
||||
def _check_expiry(self):
|
||||
# check for expiry
|
||||
if self.session_id and self.session_id not in self.store:
|
||||
if self._config.ignore_expiry:
|
||||
self.session_id = None
|
||||
else:
|
||||
return self.expired()
|
||||
|
||||
def _validate_ip(self):
|
||||
# check for change of IP
|
||||
if self.session_id and self.get('ip', None) != web.ctx.ip:
|
||||
if not self._config.ignore_change_ip:
|
||||
return self.expired()
|
||||
|
||||
def _save(self):
|
||||
if not self.get('_killed'):
|
||||
self._setcookie(self.session_id)
|
||||
self.store[self.session_id] = dict(self._data)
|
||||
else:
|
||||
self._setcookie(self.session_id, expires=-1)
|
||||
|
||||
def _setcookie(self, session_id, expires='', **kw):
|
||||
cookie_name = self._config.cookie_name
|
||||
cookie_domain = self._config.cookie_domain
|
||||
cookie_path = self._config.cookie_path
|
||||
httponly = self._config.httponly
|
||||
secure = self._config.secure
|
||||
web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path)
|
||||
|
||||
def _generate_session_id(self):
|
||||
"""Generate a random id for session"""
|
||||
|
||||
while True:
|
||||
rand = os.urandom(16)
|
||||
now = time.time()
|
||||
secret_key = self._config.secret_key
|
||||
session_id = sha1("%s%s%s%s" %(rand, now, utils.safestr(web.ctx.ip), secret_key))
|
||||
session_id = session_id.hexdigest()
|
||||
if session_id not in self.store:
|
||||
break
|
||||
return session_id
|
||||
|
||||
def _valid_session_id(self, session_id):
|
||||
rx = utils.re_compile('^[0-9a-fA-F]+$')
|
||||
return rx.match(session_id)
|
||||
|
||||
def _cleanup(self):
|
||||
"""Cleanup the stored sessions"""
|
||||
current_time = time.time()
|
||||
timeout = self._config.timeout
|
||||
if current_time - self._last_cleanup_time > timeout:
|
||||
self.store.cleanup(timeout)
|
||||
self._last_cleanup_time = current_time
|
||||
|
||||
def expired(self):
|
||||
"""Called when an expired session is atime"""
|
||||
self._killed = True
|
||||
self._save()
|
||||
raise SessionExpired(self._config.expired_message)
|
||||
|
||||
def kill(self):
|
||||
"""Kill the session, make it no longer available"""
|
||||
del self.store[self.session_id]
|
||||
self._killed = True
|
||||
|
||||
class Store:
|
||||
"""Base class for session stores"""
|
||||
|
||||
def __contains__(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def __getitem__(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
raise NotImplementedError
|
||||
|
||||
def cleanup(self, timeout):
|
||||
"""removes all the expired sessions"""
|
||||
raise NotImplementedError
|
||||
|
||||
def encode(self, session_dict):
|
||||
"""encodes session dict as a string"""
|
||||
pickled = pickle.dumps(session_dict)
|
||||
return base64.encodestring(pickled)
|
||||
|
||||
def decode(self, session_data):
|
||||
"""decodes the data to get back the session dict """
|
||||
pickled = base64.decodestring(session_data)
|
||||
return pickle.loads(pickled)
|
||||
|
||||
class DiskStore(Store):
|
||||
"""
|
||||
Store for saving a session on disk.
|
||||
|
||||
>>> import tempfile
|
||||
>>> root = tempfile.mkdtemp()
|
||||
>>> s = DiskStore(root)
|
||||
>>> s['a'] = 'foo'
|
||||
>>> s['a']
|
||||
'foo'
|
||||
>>> time.sleep(0.01)
|
||||
>>> s.cleanup(0.01)
|
||||
>>> s['a']
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'a'
|
||||
"""
|
||||
def __init__(self, root):
|
||||
# if the storage root doesn't exists, create it.
|
||||
if not os.path.exists(root):
|
||||
os.makedirs(
|
||||
os.path.abspath(root)
|
||||
)
|
||||
self.root = root
|
||||
|
||||
def _get_path(self, key):
|
||||
if os.path.sep in key:
|
||||
raise ValueError, "Bad key: %s" % repr(key)
|
||||
return os.path.join(self.root, key)
|
||||
|
||||
def __contains__(self, key):
|
||||
path = self._get_path(key)
|
||||
return os.path.exists(path)
|
||||
|
||||
def __getitem__(self, key):
|
||||
path = self._get_path(key)
|
||||
if os.path.exists(path):
|
||||
pickled = open(path).read()
|
||||
return self.decode(pickled)
|
||||
else:
|
||||
raise KeyError, key
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
path = self._get_path(key)
|
||||
pickled = self.encode(value)
|
||||
try:
|
||||
f = open(path, 'w')
|
||||
try:
|
||||
f.write(pickled)
|
||||
finally:
|
||||
f.close()
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
def __delitem__(self, key):
|
||||
path = self._get_path(key)
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
def cleanup(self, timeout):
|
||||
now = time.time()
|
||||
for f in os.listdir(self.root):
|
||||
path = self._get_path(f)
|
||||
atime = os.stat(path).st_atime
|
||||
if now - atime > timeout :
|
||||
os.remove(path)
|
||||
|
||||
class DBStore(Store):
|
||||
"""Store for saving a session in database
|
||||
Needs a table with the following columns:
|
||||
|
||||
session_id CHAR(128) UNIQUE NOT NULL,
|
||||
atime DATETIME NOT NULL default current_timestamp,
|
||||
data TEXT
|
||||
"""
|
||||
def __init__(self, db, table_name):
|
||||
self.db = db
|
||||
self.table = table_name
|
||||
|
||||
def __contains__(self, key):
|
||||
data = self.db.select(self.table, where="session_id=$key", vars=locals())
|
||||
return bool(list(data))
|
||||
|
||||
def __getitem__(self, key):
|
||||
now = datetime.datetime.now()
|
||||
try:
|
||||
s = self.db.select(self.table, where="session_id=$key", vars=locals())[0]
|
||||
self.db.update(self.table, where="session_id=$key", atime=now, vars=locals())
|
||||
except IndexError:
|
||||
raise KeyError
|
||||
else:
|
||||
return self.decode(s.data)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
pickled = self.encode(value)
|
||||
now = datetime.datetime.now()
|
||||
if key in self:
|
||||
self.db.update(self.table, where="session_id=$key", data=pickled, vars=locals())
|
||||
else:
|
||||
self.db.insert(self.table, False, session_id=key, data=pickled )
|
||||
|
||||
def __delitem__(self, key):
|
||||
self.db.delete(self.table, where="session_id=$key", vars=locals())
|
||||
|
||||
def cleanup(self, timeout):
|
||||
timeout = datetime.timedelta(timeout/(24.0*60*60)) #timedelta takes numdays as arg
|
||||
last_allowed_time = datetime.datetime.now() - timeout
|
||||
self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals())
|
||||
|
||||
class ShelfStore:
|
||||
"""Store for saving session using `shelve` module.
|
||||
|
||||
import shelve
|
||||
store = ShelfStore(shelve.open('session.shelf'))
|
||||
|
||||
XXX: is shelve thread-safe?
|
||||
"""
|
||||
def __init__(self, shelf):
|
||||
self.shelf = shelf
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.shelf
|
||||
|
||||
def __getitem__(self, key):
|
||||
atime, v = self.shelf[key]
|
||||
self[key] = v # update atime
|
||||
return v
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.shelf[key] = time.time(), value
|
||||
|
||||
def __delitem__(self, key):
|
||||
try:
|
||||
del self.shelf[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def cleanup(self, timeout):
|
||||
now = time.time()
|
||||
for k in self.shelf.keys():
|
||||
atime, v = self.shelf[k]
|
||||
if now - atime > timeout :
|
||||
del self[k]
|
||||
|
||||
if __name__ == '__main__' :
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
"""
|
||||
Session Management
|
||||
(from web.py)
|
||||
"""
|
||||
|
||||
import os, time, datetime, random, base64
|
||||
import os.path
|
||||
from copy import deepcopy
|
||||
try:
|
||||
import cPickle as pickle
|
||||
except ImportError:
|
||||
import pickle
|
||||
try:
|
||||
import hashlib
|
||||
sha1 = hashlib.sha1
|
||||
except ImportError:
|
||||
import sha
|
||||
sha1 = sha.new
|
||||
|
||||
import utils
|
||||
import webapi as web
|
||||
|
||||
__all__ = [
|
||||
'Session', 'SessionExpired',
|
||||
'Store', 'DiskStore', 'DBStore',
|
||||
]
|
||||
|
||||
web.config.session_parameters = utils.storage({
|
||||
'cookie_name': 'webpy_session_id',
|
||||
'cookie_domain': None,
|
||||
'cookie_path' : None,
|
||||
'timeout': 86400, #24 * 60 * 60, # 24 hours in seconds
|
||||
'ignore_expiry': True,
|
||||
'ignore_change_ip': True,
|
||||
'secret_key': 'fLjUfxqXtfNoIldA0A0J',
|
||||
'expired_message': 'Session expired',
|
||||
'httponly': True,
|
||||
'secure': False
|
||||
})
|
||||
|
||||
class SessionExpired(web.HTTPError):
|
||||
def __init__(self, message):
|
||||
web.HTTPError.__init__(self, '200 OK', {}, data=message)
|
||||
|
||||
class Session(object):
|
||||
"""Session management for web.py
|
||||
"""
|
||||
__slots__ = [
|
||||
"store", "_initializer", "_last_cleanup_time", "_config", "_data",
|
||||
"__getitem__", "__setitem__", "__delitem__"
|
||||
]
|
||||
|
||||
def __init__(self, app, store, initializer=None):
|
||||
self.store = store
|
||||
self._initializer = initializer
|
||||
self._last_cleanup_time = 0
|
||||
self._config = utils.storage(web.config.session_parameters)
|
||||
self._data = utils.threadeddict()
|
||||
|
||||
self.__getitem__ = self._data.__getitem__
|
||||
self.__setitem__ = self._data.__setitem__
|
||||
self.__delitem__ = self._data.__delitem__
|
||||
|
||||
if app:
|
||||
app.add_processor(self._processor)
|
||||
|
||||
def __contains__(self, name):
|
||||
return name in self._data
|
||||
|
||||
def __getattr__(self, name):
|
||||
return getattr(self._data, name)
|
||||
|
||||
def __setattr__(self, name, value):
|
||||
if name in self.__slots__:
|
||||
object.__setattr__(self, name, value)
|
||||
else:
|
||||
setattr(self._data, name, value)
|
||||
|
||||
def __delattr__(self, name):
|
||||
delattr(self._data, name)
|
||||
|
||||
def _processor(self, handler):
|
||||
"""Application processor to setup session for every request"""
|
||||
self._cleanup()
|
||||
self._load()
|
||||
|
||||
try:
|
||||
return handler()
|
||||
finally:
|
||||
self._save()
|
||||
|
||||
def _load(self):
|
||||
"""Load the session from the store, by the id from cookie"""
|
||||
cookie_name = self._config.cookie_name
|
||||
cookie_domain = self._config.cookie_domain
|
||||
cookie_path = self._config.cookie_path
|
||||
httponly = self._config.httponly
|
||||
self.session_id = web.cookies().get(cookie_name)
|
||||
|
||||
# protection against session_id tampering
|
||||
if self.session_id and not self._valid_session_id(self.session_id):
|
||||
self.session_id = None
|
||||
|
||||
self._check_expiry()
|
||||
if self.session_id:
|
||||
d = self.store[self.session_id]
|
||||
self.update(d)
|
||||
self._validate_ip()
|
||||
|
||||
if not self.session_id:
|
||||
self.session_id = self._generate_session_id()
|
||||
|
||||
if self._initializer:
|
||||
if isinstance(self._initializer, dict):
|
||||
self.update(deepcopy(self._initializer))
|
||||
elif hasattr(self._initializer, '__call__'):
|
||||
self._initializer()
|
||||
|
||||
self.ip = web.ctx.ip
|
||||
|
||||
def _check_expiry(self):
|
||||
# check for expiry
|
||||
if self.session_id and self.session_id not in self.store:
|
||||
if self._config.ignore_expiry:
|
||||
self.session_id = None
|
||||
else:
|
||||
return self.expired()
|
||||
|
||||
def _validate_ip(self):
|
||||
# check for change of IP
|
||||
if self.session_id and self.get('ip', None) != web.ctx.ip:
|
||||
if not self._config.ignore_change_ip:
|
||||
return self.expired()
|
||||
|
||||
def _save(self):
|
||||
if not self.get('_killed'):
|
||||
self._setcookie(self.session_id)
|
||||
self.store[self.session_id] = dict(self._data)
|
||||
else:
|
||||
self._setcookie(self.session_id, expires=-1)
|
||||
|
||||
def _setcookie(self, session_id, expires='', **kw):
|
||||
cookie_name = self._config.cookie_name
|
||||
cookie_domain = self._config.cookie_domain
|
||||
cookie_path = self._config.cookie_path
|
||||
httponly = self._config.httponly
|
||||
secure = self._config.secure
|
||||
web.setcookie(cookie_name, session_id, expires=expires, domain=cookie_domain, httponly=httponly, secure=secure, path=cookie_path)
|
||||
|
||||
def _generate_session_id(self):
|
||||
"""Generate a random id for session"""
|
||||
|
||||
while True:
|
||||
rand = os.urandom(16)
|
||||
now = time.time()
|
||||
secret_key = self._config.secret_key
|
||||
session_id = sha1("%s%s%s%s" %(rand, now, utils.safestr(web.ctx.ip), secret_key))
|
||||
session_id = session_id.hexdigest()
|
||||
if session_id not in self.store:
|
||||
break
|
||||
return session_id
|
||||
|
||||
def _valid_session_id(self, session_id):
|
||||
rx = utils.re_compile('^[0-9a-fA-F]+$')
|
||||
return rx.match(session_id)
|
||||
|
||||
def _cleanup(self):
|
||||
"""Cleanup the stored sessions"""
|
||||
current_time = time.time()
|
||||
timeout = self._config.timeout
|
||||
if current_time - self._last_cleanup_time > timeout:
|
||||
self.store.cleanup(timeout)
|
||||
self._last_cleanup_time = current_time
|
||||
|
||||
def expired(self):
|
||||
"""Called when an expired session is atime"""
|
||||
self._killed = True
|
||||
self._save()
|
||||
raise SessionExpired(self._config.expired_message)
|
||||
|
||||
def kill(self):
|
||||
"""Kill the session, make it no longer available"""
|
||||
del self.store[self.session_id]
|
||||
self._killed = True
|
||||
|
||||
class Store:
|
||||
"""Base class for session stores"""
|
||||
|
||||
def __contains__(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def __getitem__(self, key):
|
||||
raise NotImplementedError
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
raise NotImplementedError
|
||||
|
||||
def cleanup(self, timeout):
|
||||
"""removes all the expired sessions"""
|
||||
raise NotImplementedError
|
||||
|
||||
def encode(self, session_dict):
|
||||
"""encodes session dict as a string"""
|
||||
pickled = pickle.dumps(session_dict)
|
||||
return base64.encodestring(pickled)
|
||||
|
||||
def decode(self, session_data):
|
||||
"""decodes the data to get back the session dict """
|
||||
pickled = base64.decodestring(session_data)
|
||||
return pickle.loads(pickled)
|
||||
|
||||
class DiskStore(Store):
|
||||
"""
|
||||
Store for saving a session on disk.
|
||||
|
||||
>>> import tempfile
|
||||
>>> root = tempfile.mkdtemp()
|
||||
>>> s = DiskStore(root)
|
||||
>>> s['a'] = 'foo'
|
||||
>>> s['a']
|
||||
'foo'
|
||||
>>> time.sleep(0.01)
|
||||
>>> s.cleanup(0.01)
|
||||
>>> s['a']
|
||||
Traceback (most recent call last):
|
||||
...
|
||||
KeyError: 'a'
|
||||
"""
|
||||
def __init__(self, root):
|
||||
# if the storage root doesn't exists, create it.
|
||||
if not os.path.exists(root):
|
||||
os.makedirs(
|
||||
os.path.abspath(root)
|
||||
)
|
||||
self.root = root
|
||||
|
||||
def _get_path(self, key):
|
||||
if os.path.sep in key:
|
||||
raise ValueError, "Bad key: %s" % repr(key)
|
||||
return os.path.join(self.root, key)
|
||||
|
||||
def __contains__(self, key):
|
||||
path = self._get_path(key)
|
||||
return os.path.exists(path)
|
||||
|
||||
def __getitem__(self, key):
|
||||
path = self._get_path(key)
|
||||
if os.path.exists(path):
|
||||
pickled = open(path).read()
|
||||
return self.decode(pickled)
|
||||
else:
|
||||
raise KeyError, key
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
path = self._get_path(key)
|
||||
pickled = self.encode(value)
|
||||
try:
|
||||
f = open(path, 'w')
|
||||
try:
|
||||
f.write(pickled)
|
||||
finally:
|
||||
f.close()
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
def __delitem__(self, key):
|
||||
path = self._get_path(key)
|
||||
if os.path.exists(path):
|
||||
os.remove(path)
|
||||
|
||||
def cleanup(self, timeout):
|
||||
now = time.time()
|
||||
for f in os.listdir(self.root):
|
||||
path = self._get_path(f)
|
||||
atime = os.stat(path).st_atime
|
||||
if now - atime > timeout :
|
||||
os.remove(path)
|
||||
|
||||
class DBStore(Store):
|
||||
"""Store for saving a session in database
|
||||
Needs a table with the following columns:
|
||||
|
||||
session_id CHAR(128) UNIQUE NOT NULL,
|
||||
atime DATETIME NOT NULL default current_timestamp,
|
||||
data TEXT
|
||||
"""
|
||||
def __init__(self, db, table_name):
|
||||
self.db = db
|
||||
self.table = table_name
|
||||
|
||||
def __contains__(self, key):
|
||||
data = self.db.select(self.table, where="session_id=$key", vars=locals())
|
||||
return bool(list(data))
|
||||
|
||||
def __getitem__(self, key):
|
||||
now = datetime.datetime.now()
|
||||
try:
|
||||
s = self.db.select(self.table, where="session_id=$key", vars=locals())[0]
|
||||
self.db.update(self.table, where="session_id=$key", atime=now, vars=locals())
|
||||
except IndexError:
|
||||
raise KeyError
|
||||
else:
|
||||
return self.decode(s.data)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
pickled = self.encode(value)
|
||||
now = datetime.datetime.now()
|
||||
if key in self:
|
||||
self.db.update(self.table, where="session_id=$key", data=pickled, vars=locals())
|
||||
else:
|
||||
self.db.insert(self.table, False, session_id=key, data=pickled )
|
||||
|
||||
def __delitem__(self, key):
|
||||
self.db.delete(self.table, where="session_id=$key", vars=locals())
|
||||
|
||||
def cleanup(self, timeout):
|
||||
timeout = datetime.timedelta(timeout/(24.0*60*60)) #timedelta takes numdays as arg
|
||||
last_allowed_time = datetime.datetime.now() - timeout
|
||||
self.db.delete(self.table, where="$last_allowed_time > atime", vars=locals())
|
||||
|
||||
class ShelfStore:
|
||||
"""Store for saving session using `shelve` module.
|
||||
|
||||
import shelve
|
||||
store = ShelfStore(shelve.open('session.shelf'))
|
||||
|
||||
XXX: is shelve thread-safe?
|
||||
"""
|
||||
def __init__(self, shelf):
|
||||
self.shelf = shelf
|
||||
|
||||
def __contains__(self, key):
|
||||
return key in self.shelf
|
||||
|
||||
def __getitem__(self, key):
|
||||
atime, v = self.shelf[key]
|
||||
self[key] = v # update atime
|
||||
return v
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.shelf[key] = time.time(), value
|
||||
|
||||
def __delitem__(self, key):
|
||||
try:
|
||||
del self.shelf[key]
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def cleanup(self, timeout):
|
||||
now = time.time()
|
||||
for k in self.shelf.keys():
|
||||
atime, v = self.shelf[k]
|
||||
if now - atime > timeout :
|
||||
del self[k]
|
||||
|
||||
if __name__ == '__main__' :
|
||||
import doctest
|
||||
doctest.testmod()
|
||||
|
|
3030
web/template.py
3030
web/template.py
File diff suppressed because it is too large
Load Diff
102
web/test.py
102
web/test.py
|
@ -1,51 +1,51 @@
|
|||
"""test utilities
|
||||
(part of web.py)
|
||||
"""
|
||||
import unittest
|
||||
import sys, os
|
||||
import web
|
||||
|
||||
TestCase = unittest.TestCase
|
||||
TestSuite = unittest.TestSuite
|
||||
|
||||
def load_modules(names):
|
||||
return [__import__(name, None, None, "x") for name in names]
|
||||
|
||||
def module_suite(module, classnames=None):
|
||||
"""Makes a suite from a module."""
|
||||
if classnames:
|
||||
return unittest.TestLoader().loadTestsFromNames(classnames, module)
|
||||
elif hasattr(module, 'suite'):
|
||||
return module.suite()
|
||||
else:
|
||||
return unittest.TestLoader().loadTestsFromModule(module)
|
||||
|
||||
def doctest_suite(module_names):
|
||||
"""Makes a test suite from doctests."""
|
||||
import doctest
|
||||
suite = TestSuite()
|
||||
for mod in load_modules(module_names):
|
||||
suite.addTest(doctest.DocTestSuite(mod))
|
||||
return suite
|
||||
|
||||
def suite(module_names):
|
||||
"""Creates a suite from multiple modules."""
|
||||
suite = TestSuite()
|
||||
for mod in load_modules(module_names):
|
||||
suite.addTest(module_suite(mod))
|
||||
return suite
|
||||
|
||||
def runTests(suite):
|
||||
runner = unittest.TextTestRunner()
|
||||
return runner.run(suite)
|
||||
|
||||
def main(suite=None):
|
||||
if not suite:
|
||||
main_module = __import__('__main__')
|
||||
# allow command line switches
|
||||
args = [a for a in sys.argv[1:] if not a.startswith('-')]
|
||||
suite = module_suite(main_module, args or None)
|
||||
|
||||
result = runTests(suite)
|
||||
sys.exit(not result.wasSuccessful())
|
||||
|
||||
"""test utilities
|
||||
(part of web.py)
|
||||
"""
|
||||
import unittest
|
||||
import sys, os
|
||||
import web
|
||||
|
||||
TestCase = unittest.TestCase
|
||||
TestSuite = unittest.TestSuite
|
||||
|
||||
def load_modules(names):
|
||||
return [__import__(name, None, None, "x") for name in names]
|
||||
|
||||
def module_suite(module, classnames=None):
|
||||
"""Makes a suite from a module."""
|
||||
if classnames:
|
||||
return unittest.TestLoader().loadTestsFromNames(classnames, module)
|
||||
elif hasattr(module, 'suite'):
|
||||
return module.suite()
|
||||
else:
|
||||
return unittest.TestLoader().loadTestsFromModule(module)
|
||||
|
||||
def doctest_suite(module_names):
|
||||
"""Makes a test suite from doctests."""
|
||||
import doctest
|
||||
suite = TestSuite()
|
||||
for mod in load_modules(module_names):
|
||||
suite.addTest(doctest.DocTestSuite(mod))
|
||||
return suite
|
||||
|
||||
def suite(module_names):
|
||||
"""Creates a suite from multiple modules."""
|
||||
suite = TestSuite()
|
||||
for mod in load_modules(module_names):
|
||||
suite.addTest(module_suite(mod))
|
||||
return suite
|
||||
|
||||
def runTests(suite):
|
||||
runner = unittest.TextTestRunner()
|
||||
return runner.run(suite)
|
||||
|
||||
def main(suite=None):
|
||||
if not suite:
|
||||
main_module = __import__('__main__')
|
||||
# allow command line switches
|
||||
args = [a for a in sys.argv[1:] if not a.startswith('-')]
|
||||
suite = module_suite(main_module, args or None)
|
||||
|
||||
result = runTests(suite)
|
||||
sys.exit(not result.wasSuccessful())
|
||||
|
||||
|
|
3052
web/utils.py
3052
web/utils.py
File diff suppressed because it is too large
Load Diff
1048
web/webapi.py
1048
web/webapi.py
File diff suppressed because it is too large
Load Diff
230
web/webopenid.py
230
web/webopenid.py
|
@ -1,115 +1,115 @@
|
|||
"""openid.py: an openid library for web.py
|
||||
|
||||
Notes:
|
||||
|
||||
- This will create a file called .openid_secret_key in the
|
||||
current directory with your secret key in it. If someone
|
||||
has access to this file they can log in as any user. And
|
||||
if the app can't find this file for any reason (e.g. you
|
||||
moved the app somewhere else) then each currently logged
|
||||
in user will get logged out.
|
||||
|
||||
- State must be maintained through the entire auth process
|
||||
-- this means that if you have multiple web.py processes
|
||||
serving one set of URLs or if you restart your app often
|
||||
then log ins will fail. You have to replace sessions and
|
||||
store for things to work.
|
||||
|
||||
- We set cookies starting with "openid_".
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import hmac
|
||||
import __init__ as web
|
||||
import openid.consumer.consumer
|
||||
import openid.store.memstore
|
||||
|
||||
sessions = {}
|
||||
store = openid.store.memstore.MemoryStore()
|
||||
|
||||
def _secret():
|
||||
try:
|
||||
secret = file('.openid_secret_key').read()
|
||||
except IOError:
|
||||
# file doesn't exist
|
||||
secret = os.urandom(20)
|
||||
file('.openid_secret_key', 'w').write(secret)
|
||||
return secret
|
||||
|
||||
def _hmac(identity_url):
|
||||
return hmac.new(_secret(), identity_url).hexdigest()
|
||||
|
||||
def _random_session():
|
||||
n = random.random()
|
||||
while n in sessions:
|
||||
n = random.random()
|
||||
n = str(n)
|
||||
return n
|
||||
|
||||
def status():
|
||||
oid_hash = web.cookies().get('openid_identity_hash', '').split(',', 1)
|
||||
if len(oid_hash) > 1:
|
||||
oid_hash, identity_url = oid_hash
|
||||
if oid_hash == _hmac(identity_url):
|
||||
return identity_url
|
||||
return None
|
||||
|
||||
def form(openid_loc):
|
||||
oid = status()
|
||||
if oid:
|
||||
return '''
|
||||
<form method="post" action="%s">
|
||||
<img src="http://openid.net/login-bg.gif" alt="OpenID" />
|
||||
<strong>%s</strong>
|
||||
<input type="hidden" name="action" value="logout" />
|
||||
<input type="hidden" name="return_to" value="%s" />
|
||||
<button type="submit">log out</button>
|
||||
</form>''' % (openid_loc, oid, web.ctx.fullpath)
|
||||
else:
|
||||
return '''
|
||||
<form method="post" action="%s">
|
||||
<input type="text" name="openid" value=""
|
||||
style="background: url(http://openid.net/login-bg.gif) no-repeat; padding-left: 18px; background-position: 0 50%%;" />
|
||||
<input type="hidden" name="return_to" value="%s" />
|
||||
<button type="submit">log in</button>
|
||||
</form>''' % (openid_loc, web.ctx.fullpath)
|
||||
|
||||
def logout():
|
||||
web.setcookie('openid_identity_hash', '', expires=-1)
|
||||
|
||||
class host:
|
||||
def POST(self):
|
||||
# unlike the usual scheme of things, the POST is actually called
|
||||
# first here
|
||||
i = web.input(return_to='/')
|
||||
if i.get('action') == 'logout':
|
||||
logout()
|
||||
return web.redirect(i.return_to)
|
||||
|
||||
i = web.input('openid', return_to='/')
|
||||
|
||||
n = _random_session()
|
||||
sessions[n] = {'webpy_return_to': i.return_to}
|
||||
|
||||
c = openid.consumer.consumer.Consumer(sessions[n], store)
|
||||
a = c.begin(i.openid)
|
||||
f = a.redirectURL(web.ctx.home, web.ctx.home + web.ctx.fullpath)
|
||||
|
||||
web.setcookie('openid_session_id', n)
|
||||
return web.redirect(f)
|
||||
|
||||
def GET(self):
|
||||
n = web.cookies('openid_session_id').openid_session_id
|
||||
web.setcookie('openid_session_id', '', expires=-1)
|
||||
return_to = sessions[n]['webpy_return_to']
|
||||
|
||||
c = openid.consumer.consumer.Consumer(sessions[n], store)
|
||||
a = c.complete(web.input(), web.ctx.home + web.ctx.fullpath)
|
||||
|
||||
if a.status.lower() == 'success':
|
||||
web.setcookie('openid_identity_hash', _hmac(a.identity_url) + ',' + a.identity_url)
|
||||
|
||||
del sessions[n]
|
||||
return web.redirect(return_to)
|
||||
"""openid.py: an openid library for web.py
|
||||
|
||||
Notes:
|
||||
|
||||
- This will create a file called .openid_secret_key in the
|
||||
current directory with your secret key in it. If someone
|
||||
has access to this file they can log in as any user. And
|
||||
if the app can't find this file for any reason (e.g. you
|
||||
moved the app somewhere else) then each currently logged
|
||||
in user will get logged out.
|
||||
|
||||
- State must be maintained through the entire auth process
|
||||
-- this means that if you have multiple web.py processes
|
||||
serving one set of URLs or if you restart your app often
|
||||
then log ins will fail. You have to replace sessions and
|
||||
store for things to work.
|
||||
|
||||
- We set cookies starting with "openid_".
|
||||
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import hmac
|
||||
import __init__ as web
|
||||
import openid.consumer.consumer
|
||||
import openid.store.memstore
|
||||
|
||||
sessions = {}
|
||||
store = openid.store.memstore.MemoryStore()
|
||||
|
||||
def _secret():
|
||||
try:
|
||||
secret = file('.openid_secret_key').read()
|
||||
except IOError:
|
||||
# file doesn't exist
|
||||
secret = os.urandom(20)
|
||||
file('.openid_secret_key', 'w').write(secret)
|
||||
return secret
|
||||
|
||||
def _hmac(identity_url):
|
||||
return hmac.new(_secret(), identity_url).hexdigest()
|
||||
|
||||
def _random_session():
|
||||
n = random.random()
|
||||
while n in sessions:
|
||||
n = random.random()
|
||||
n = str(n)
|
||||
return n
|
||||
|
||||
def status():
|
||||
oid_hash = web.cookies().get('openid_identity_hash', '').split(',', 1)
|
||||
if len(oid_hash) > 1:
|
||||
oid_hash, identity_url = oid_hash
|
||||
if oid_hash == _hmac(identity_url):
|
||||
return identity_url
|
||||
return None
|
||||
|
||||
def form(openid_loc):
|
||||
oid = status()
|
||||
if oid:
|
||||
return '''
|
||||
<form method="post" action="%s">
|
||||
<img src="http://openid.net/login-bg.gif" alt="OpenID" />
|
||||
<strong>%s</strong>
|
||||
<input type="hidden" name="action" value="logout" />
|
||||
<input type="hidden" name="return_to" value="%s" />
|
||||
<button type="submit">log out</button>
|
||||
</form>''' % (openid_loc, oid, web.ctx.fullpath)
|
||||
else:
|
||||
return '''
|
||||
<form method="post" action="%s">
|
||||
<input type="text" name="openid" value=""
|
||||
style="background: url(http://openid.net/login-bg.gif) no-repeat; padding-left: 18px; background-position: 0 50%%;" />
|
||||
<input type="hidden" name="return_to" value="%s" />
|
||||
<button type="submit">log in</button>
|
||||
</form>''' % (openid_loc, web.ctx.fullpath)
|
||||
|
||||
def logout():
|
||||
web.setcookie('openid_identity_hash', '', expires=-1)
|
||||
|
||||
class host:
|
||||
def POST(self):
|
||||
# unlike the usual scheme of things, the POST is actually called
|
||||
# first here
|
||||
i = web.input(return_to='/')
|
||||
if i.get('action') == 'logout':
|
||||
logout()
|
||||
return web.redirect(i.return_to)
|
||||
|
||||
i = web.input('openid', return_to='/')
|
||||
|
||||
n = _random_session()
|
||||
sessions[n] = {'webpy_return_to': i.return_to}
|
||||
|
||||
c = openid.consumer.consumer.Consumer(sessions[n], store)
|
||||
a = c.begin(i.openid)
|
||||
f = a.redirectURL(web.ctx.home, web.ctx.home + web.ctx.fullpath)
|
||||
|
||||
web.setcookie('openid_session_id', n)
|
||||
return web.redirect(f)
|
||||
|
||||
def GET(self):
|
||||
n = web.cookies('openid_session_id').openid_session_id
|
||||
web.setcookie('openid_session_id', '', expires=-1)
|
||||
return_to = sessions[n]['webpy_return_to']
|
||||
|
||||
c = openid.consumer.consumer.Consumer(sessions[n], store)
|
||||
a = c.complete(web.input(), web.ctx.home + web.ctx.fullpath)
|
||||
|
||||
if a.status.lower() == 'success':
|
||||
web.setcookie('openid_identity_hash', _hmac(a.identity_url) + ',' + a.identity_url)
|
||||
|
||||
del sessions[n]
|
||||
return web.redirect(return_to)
|
||||
|
|
140
web/wsgi.py
140
web/wsgi.py
|
@ -1,70 +1,70 @@
|
|||
"""
|
||||
WSGI Utilities
|
||||
(from web.py)
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
|
||||
import http
|
||||
import webapi as web
|
||||
from utils import listget
|
||||
from net import validaddr, validip
|
||||
import httpserver
|
||||
|
||||
def runfcgi(func, addr=('localhost', 8000)):
|
||||
"""Runs a WSGI function as a FastCGI server."""
|
||||
import flup.server.fcgi as flups
|
||||
return flups.WSGIServer(func, multiplexed=True, bindAddress=addr, debug=False).run()
|
||||
|
||||
def runscgi(func, addr=('localhost', 4000)):
|
||||
"""Runs a WSGI function as an SCGI server."""
|
||||
import flup.server.scgi as flups
|
||||
return flups.WSGIServer(func, bindAddress=addr, debug=False).run()
|
||||
|
||||
def runwsgi(func):
|
||||
"""
|
||||
Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server,
|
||||
as appropriate based on context and `sys.argv`.
|
||||
"""
|
||||
|
||||
if os.environ.has_key('SERVER_SOFTWARE'): # cgi
|
||||
os.environ['FCGI_FORCE_CGI'] = 'Y'
|
||||
|
||||
if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi
|
||||
or os.environ.has_key('SERVER_SOFTWARE')):
|
||||
return runfcgi(func, None)
|
||||
|
||||
if 'fcgi' in sys.argv or 'fastcgi' in sys.argv:
|
||||
args = sys.argv[1:]
|
||||
if 'fastcgi' in args: args.remove('fastcgi')
|
||||
elif 'fcgi' in args: args.remove('fcgi')
|
||||
if args:
|
||||
return runfcgi(func, validaddr(args[0]))
|
||||
else:
|
||||
return runfcgi(func, None)
|
||||
|
||||
if 'scgi' in sys.argv:
|
||||
args = sys.argv[1:]
|
||||
args.remove('scgi')
|
||||
if args:
|
||||
return runscgi(func, validaddr(args[0]))
|
||||
else:
|
||||
return runscgi(func)
|
||||
|
||||
return httpserver.runsimple(func, validip(listget(sys.argv, 1, '')))
|
||||
|
||||
def _is_dev_mode():
|
||||
# Some embedded python interpreters won't have sys.arv
|
||||
# For details, see https://github.com/webpy/webpy/issues/87
|
||||
argv = getattr(sys, "argv", [])
|
||||
|
||||
# quick hack to check if the program is running in dev mode.
|
||||
if os.environ.has_key('SERVER_SOFTWARE') \
|
||||
or os.environ.has_key('PHP_FCGI_CHILDREN') \
|
||||
or 'fcgi' in argv or 'fastcgi' in argv \
|
||||
or 'mod_wsgi' in argv:
|
||||
return False
|
||||
return True
|
||||
|
||||
# When running the builtin-server, enable debug mode if not already set.
|
||||
web.config.setdefault('debug', _is_dev_mode())
|
||||
"""
|
||||
WSGI Utilities
|
||||
(from web.py)
|
||||
"""
|
||||
|
||||
import os, sys
|
||||
|
||||
import http
|
||||
import webapi as web
|
||||
from utils import listget
|
||||
from net import validaddr, validip
|
||||
import httpserver
|
||||
|
||||
def runfcgi(func, addr=('localhost', 8000)):
|
||||
"""Runs a WSGI function as a FastCGI server."""
|
||||
import flup.server.fcgi as flups
|
||||
return flups.WSGIServer(func, multiplexed=True, bindAddress=addr, debug=False).run()
|
||||
|
||||
def runscgi(func, addr=('localhost', 4000)):
|
||||
"""Runs a WSGI function as an SCGI server."""
|
||||
import flup.server.scgi as flups
|
||||
return flups.WSGIServer(func, bindAddress=addr, debug=False).run()
|
||||
|
||||
def runwsgi(func):
|
||||
"""
|
||||
Runs a WSGI-compatible `func` using FCGI, SCGI, or a simple web server,
|
||||
as appropriate based on context and `sys.argv`.
|
||||
"""
|
||||
|
||||
if os.environ.has_key('SERVER_SOFTWARE'): # cgi
|
||||
os.environ['FCGI_FORCE_CGI'] = 'Y'
|
||||
|
||||
if (os.environ.has_key('PHP_FCGI_CHILDREN') #lighttpd fastcgi
|
||||
or os.environ.has_key('SERVER_SOFTWARE')):
|
||||
return runfcgi(func, None)
|
||||
|
||||
if 'fcgi' in sys.argv or 'fastcgi' in sys.argv:
|
||||
args = sys.argv[1:]
|
||||
if 'fastcgi' in args: args.remove('fastcgi')
|
||||
elif 'fcgi' in args: args.remove('fcgi')
|
||||
if args:
|
||||
return runfcgi(func, validaddr(args[0]))
|
||||
else:
|
||||
return runfcgi(func, None)
|
||||
|
||||
if 'scgi' in sys.argv:
|
||||
args = sys.argv[1:]
|
||||
args.remove('scgi')
|
||||
if args:
|
||||
return runscgi(func, validaddr(args[0]))
|
||||
else:
|
||||
return runscgi(func)
|
||||
|
||||
return httpserver.runsimple(func, validip(listget(sys.argv, 1, '')))
|
||||
|
||||
def _is_dev_mode():
|
||||
# Some embedded python interpreters won't have sys.arv
|
||||
# For details, see https://github.com/webpy/webpy/issues/87
|
||||
argv = getattr(sys, "argv", [])
|
||||
|
||||
# quick hack to check if the program is running in dev mode.
|
||||
if os.environ.has_key('SERVER_SOFTWARE') \
|
||||
or os.environ.has_key('PHP_FCGI_CHILDREN') \
|
||||
or 'fcgi' in argv or 'fastcgi' in argv \
|
||||
or 'mod_wsgi' in argv:
|
||||
return False
|
||||
return True
|
||||
|
||||
# When running the builtin-server, enable debug mode if not already set.
|
||||
web.config.setdefault('debug', _is_dev_mode())
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1,72 +1,72 @@
|
|||
"""A library for integrating Python's builtin ``ssl`` library with CherryPy.
|
||||
|
||||
The ssl module must be importable for SSL functionality.
|
||||
|
||||
To use this module, set ``CherryPyWSGIServer.ssl_adapter`` to an instance of
|
||||
``BuiltinSSLAdapter``.
|
||||
"""
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
ssl = None
|
||||
|
||||
from cherrypy import wsgiserver
|
||||
|
||||
|
||||
class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
||||
"""A wrapper for integrating Python's builtin ssl module with CherryPy."""
|
||||
|
||||
certificate = None
|
||||
"""The filename of the server SSL certificate."""
|
||||
|
||||
private_key = None
|
||||
"""The filename of the server's private key file."""
|
||||
|
||||
def __init__(self, certificate, private_key, certificate_chain=None):
|
||||
if ssl is None:
|
||||
raise ImportError("You must install the ssl module to use HTTPS.")
|
||||
self.certificate = certificate
|
||||
self.private_key = private_key
|
||||
self.certificate_chain = certificate_chain
|
||||
|
||||
def bind(self, sock):
|
||||
"""Wrap and return the given socket."""
|
||||
return sock
|
||||
|
||||
def wrap(self, sock):
|
||||
"""Wrap and return the given socket, plus WSGI environ entries."""
|
||||
try:
|
||||
s = ssl.wrap_socket(sock, do_handshake_on_connect=True,
|
||||
server_side=True, certfile=self.certificate,
|
||||
keyfile=self.private_key, ssl_version=ssl.PROTOCOL_SSLv23)
|
||||
except ssl.SSLError, e:
|
||||
if e.errno == ssl.SSL_ERROR_EOF:
|
||||
# This is almost certainly due to the cherrypy engine
|
||||
# 'pinging' the socket to assert it's connectable;
|
||||
# the 'ping' isn't SSL.
|
||||
return None, {}
|
||||
elif e.errno == ssl.SSL_ERROR_SSL:
|
||||
if e.args[1].endswith('http request'):
|
||||
# The client is speaking HTTP to an HTTPS server.
|
||||
raise wsgiserver.NoSSLError
|
||||
raise
|
||||
return s, self.get_environ(s)
|
||||
|
||||
# TODO: fill this out more with mod ssl env
|
||||
def get_environ(self, sock):
|
||||
"""Create WSGI environ entries to be merged into each request."""
|
||||
cipher = sock.cipher()
|
||||
ssl_environ = {
|
||||
"wsgi.url_scheme": "https",
|
||||
"HTTPS": "on",
|
||||
'SSL_PROTOCOL': cipher[1],
|
||||
'SSL_CIPHER': cipher[0]
|
||||
## SSL_VERSION_INTERFACE string The mod_ssl program version
|
||||
## SSL_VERSION_LIBRARY string The OpenSSL program version
|
||||
}
|
||||
return ssl_environ
|
||||
|
||||
def makefile(self, sock, mode='r', bufsize=-1):
|
||||
return wsgiserver.CP_fileobject(sock, mode, bufsize)
|
||||
|
||||
"""A library for integrating Python's builtin ``ssl`` library with CherryPy.
|
||||
|
||||
The ssl module must be importable for SSL functionality.
|
||||
|
||||
To use this module, set ``CherryPyWSGIServer.ssl_adapter`` to an instance of
|
||||
``BuiltinSSLAdapter``.
|
||||
"""
|
||||
|
||||
try:
|
||||
import ssl
|
||||
except ImportError:
|
||||
ssl = None
|
||||
|
||||
from cherrypy import wsgiserver
|
||||
|
||||
|
||||
class BuiltinSSLAdapter(wsgiserver.SSLAdapter):
|
||||
"""A wrapper for integrating Python's builtin ssl module with CherryPy."""
|
||||
|
||||
certificate = None
|
||||
"""The filename of the server SSL certificate."""
|
||||
|
||||
private_key = None
|
||||
"""The filename of the server's private key file."""
|
||||
|
||||
def __init__(self, certificate, private_key, certificate_chain=None):
|
||||
if ssl is None:
|
||||
raise ImportError("You must install the ssl module to use HTTPS.")
|
||||
self.certificate = certificate
|
||||
self.private_key = private_key
|
||||
self.certificate_chain = certificate_chain
|
||||
|
||||
def bind(self, sock):
|
||||
"""Wrap and return the given socket."""
|
||||
return sock
|
||||
|
||||
def wrap(self, sock):
|
||||
"""Wrap and return the given socket, plus WSGI environ entries."""
|
||||
try:
|
||||
s = ssl.wrap_socket(sock, do_handshake_on_connect=True,
|
||||
server_side=True, certfile=self.certificate,
|
||||
keyfile=self.private_key, ssl_version=ssl.PROTOCOL_SSLv23)
|
||||
except ssl.SSLError, e:
|
||||
if e.errno == ssl.SSL_ERROR_EOF:
|
||||
# This is almost certainly due to the cherrypy engine
|
||||
# 'pinging' the socket to assert it's connectable;
|
||||
# the 'ping' isn't SSL.
|
||||
return None, {}
|
||||
elif e.errno == ssl.SSL_ERROR_SSL:
|
||||
if e.args[1].endswith('http request'):
|
||||
# The client is speaking HTTP to an HTTPS server.
|
||||
raise wsgiserver.NoSSLError
|
||||
raise
|
||||
return s, self.get_environ(s)
|
||||
|
||||
# TODO: fill this out more with mod ssl env
|
||||
def get_environ(self, sock):
|
||||
"""Create WSGI environ entries to be merged into each request."""
|
||||
cipher = sock.cipher()
|
||||
ssl_environ = {
|
||||
"wsgi.url_scheme": "https",
|
||||
"HTTPS": "on",
|
||||
'SSL_PROTOCOL': cipher[1],
|
||||
'SSL_CIPHER': cipher[0]
|
||||
## SSL_VERSION_INTERFACE string The mod_ssl program version
|
||||
## SSL_VERSION_LIBRARY string The OpenSSL program version
|
||||
}
|
||||
return ssl_environ
|
||||
|
||||
def makefile(self, sock, mode='r', bufsize=-1):
|
||||
return wsgiserver.CP_fileobject(sock, mode, bufsize)
|
||||
|
||||
|
|
|
@ -1,256 +1,256 @@
|
|||
"""A library for integrating pyOpenSSL with CherryPy.
|
||||
|
||||
The OpenSSL module must be importable for SSL functionality.
|
||||
You can obtain it from http://pyopenssl.sourceforge.net/
|
||||
|
||||
To use this module, set CherryPyWSGIServer.ssl_adapter to an instance of
|
||||
SSLAdapter. There are two ways to use SSL:
|
||||
|
||||
Method One
|
||||
----------
|
||||
|
||||
* ``ssl_adapter.context``: an instance of SSL.Context.
|
||||
|
||||
If this is not None, it is assumed to be an SSL.Context instance,
|
||||
and will be passed to SSL.Connection on bind(). The developer is
|
||||
responsible for forming a valid Context object. This approach is
|
||||
to be preferred for more flexibility, e.g. if the cert and key are
|
||||
streams instead of files, or need decryption, or SSL.SSLv3_METHOD
|
||||
is desired instead of the default SSL.SSLv23_METHOD, etc. Consult
|
||||
the pyOpenSSL documentation for complete options.
|
||||
|
||||
Method Two (shortcut)
|
||||
---------------------
|
||||
|
||||
* ``ssl_adapter.certificate``: the filename of the server SSL certificate.
|
||||
* ``ssl_adapter.private_key``: the filename of the server's private key file.
|
||||
|
||||
Both are None by default. If ssl_adapter.context is None, but .private_key
|
||||
and .certificate are both given and valid, they will be read, and the
|
||||
context will be automatically created from them.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from cherrypy import wsgiserver
|
||||
|
||||
try:
|
||||
from OpenSSL import SSL
|
||||
from OpenSSL import crypto
|
||||
except ImportError:
|
||||
SSL = None
|
||||
|
||||
|
||||
class SSL_fileobject(wsgiserver.CP_fileobject):
|
||||
"""SSL file object attached to a socket object."""
|
||||
|
||||
ssl_timeout = 3
|
||||
ssl_retry = .01
|
||||
|
||||
def _safe_call(self, is_reader, call, *args, **kwargs):
|
||||
"""Wrap the given call with SSL error-trapping.
|
||||
|
||||
is_reader: if False EOF errors will be raised. If True, EOF errors
|
||||
will return "" (to emulate normal sockets).
|
||||
"""
|
||||
start = time.time()
|
||||
while True:
|
||||
try:
|
||||
return call(*args, **kwargs)
|
||||
except SSL.WantReadError:
|
||||
# Sleep and try again. This is dangerous, because it means
|
||||
# the rest of the stack has no way of differentiating
|
||||
# between a "new handshake" error and "client dropped".
|
||||
# Note this isn't an endless loop: there's a timeout below.
|
||||
time.sleep(self.ssl_retry)
|
||||
except SSL.WantWriteError:
|
||||
time.sleep(self.ssl_retry)
|
||||
except SSL.SysCallError, e:
|
||||
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
||||
return ""
|
||||
|
||||
errnum = e.args[0]
|
||||
if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
|
||||
return ""
|
||||
raise socket.error(errnum)
|
||||
except SSL.Error, e:
|
||||
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
||||
return ""
|
||||
|
||||
thirdarg = None
|
||||
try:
|
||||
thirdarg = e.args[0][0][2]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if thirdarg == 'http request':
|
||||
# The client is talking HTTP to an HTTPS server.
|
||||
raise wsgiserver.NoSSLError()
|
||||
|
||||
raise wsgiserver.FatalSSLAlert(*e.args)
|
||||
except:
|
||||
raise
|
||||
|
||||
if time.time() - start > self.ssl_timeout:
|
||||
raise socket.timeout("timed out")
|
||||
|
||||
def recv(self, *args, **kwargs):
|
||||
buf = []
|
||||
r = super(SSL_fileobject, self).recv
|
||||
while True:
|
||||
data = self._safe_call(True, r, *args, **kwargs)
|
||||
buf.append(data)
|
||||
p = self._sock.pending()
|
||||
if not p:
|
||||
return "".join(buf)
|
||||
|
||||
def sendall(self, *args, **kwargs):
|
||||
return self._safe_call(False, super(SSL_fileobject, self).sendall,
|
||||
*args, **kwargs)
|
||||
|
||||
def send(self, *args, **kwargs):
|
||||
return self._safe_call(False, super(SSL_fileobject, self).send,
|
||||
*args, **kwargs)
|
||||
|
||||
|
||||
class SSLConnection:
|
||||
"""A thread-safe wrapper for an SSL.Connection.
|
||||
|
||||
``*args``: the arguments to create the wrapped ``SSL.Connection(*args)``.
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
self._ssl_conn = SSL.Connection(*args)
|
||||
self._lock = threading.RLock()
|
||||
|
||||
for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
|
||||
'renegotiate', 'bind', 'listen', 'connect', 'accept',
|
||||
'setblocking', 'fileno', 'close', 'get_cipher_list',
|
||||
'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
|
||||
'makefile', 'get_app_data', 'set_app_data', 'state_string',
|
||||
'sock_shutdown', 'get_peer_certificate', 'want_read',
|
||||
'want_write', 'set_connect_state', 'set_accept_state',
|
||||
'connect_ex', 'sendall', 'settimeout', 'gettimeout'):
|
||||
exec("""def %s(self, *args):
|
||||
self._lock.acquire()
|
||||
try:
|
||||
return self._ssl_conn.%s(*args)
|
||||
finally:
|
||||
self._lock.release()
|
||||
""" % (f, f))
|
||||
|
||||
def shutdown(self, *args):
|
||||
self._lock.acquire()
|
||||
try:
|
||||
# pyOpenSSL.socket.shutdown takes no args
|
||||
return self._ssl_conn.shutdown()
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
|
||||
class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
|
||||
"""A wrapper for integrating pyOpenSSL with CherryPy."""
|
||||
|
||||
context = None
|
||||
"""An instance of SSL.Context."""
|
||||
|
||||
certificate = None
|
||||
"""The filename of the server SSL certificate."""
|
||||
|
||||
private_key = None
|
||||
"""The filename of the server's private key file."""
|
||||
|
||||
certificate_chain = None
|
||||
"""Optional. The filename of CA's intermediate certificate bundle.
|
||||
|
||||
This is needed for cheaper "chained root" SSL certificates, and should be
|
||||
left as None if not required."""
|
||||
|
||||
def __init__(self, certificate, private_key, certificate_chain=None):
|
||||
if SSL is None:
|
||||
raise ImportError("You must install pyOpenSSL to use HTTPS.")
|
||||
|
||||
self.context = None
|
||||
self.certificate = certificate
|
||||
self.private_key = private_key
|
||||
self.certificate_chain = certificate_chain
|
||||
self._environ = None
|
||||
|
||||
def bind(self, sock):
|
||||
"""Wrap and return the given socket."""
|
||||
if self.context is None:
|
||||
self.context = self.get_context()
|
||||
conn = SSLConnection(self.context, sock)
|
||||
self._environ = self.get_environ()
|
||||
return conn
|
||||
|
||||
def wrap(self, sock):
|
||||
"""Wrap and return the given socket, plus WSGI environ entries."""
|
||||
return sock, self._environ.copy()
|
||||
|
||||
def get_context(self):
|
||||
"""Return an SSL.Context from self attributes."""
|
||||
# See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
|
||||
c = SSL.Context(SSL.SSLv23_METHOD)
|
||||
c.use_privatekey_file(self.private_key)
|
||||
if self.certificate_chain:
|
||||
c.load_verify_locations(self.certificate_chain)
|
||||
c.use_certificate_file(self.certificate)
|
||||
return c
|
||||
|
||||
def get_environ(self):
|
||||
"""Return WSGI environ entries to be merged into each request."""
|
||||
ssl_environ = {
|
||||
"HTTPS": "on",
|
||||
# pyOpenSSL doesn't provide access to any of these AFAICT
|
||||
## 'SSL_PROTOCOL': 'SSLv2',
|
||||
## SSL_CIPHER string The cipher specification name
|
||||
## SSL_VERSION_INTERFACE string The mod_ssl program version
|
||||
## SSL_VERSION_LIBRARY string The OpenSSL program version
|
||||
}
|
||||
|
||||
if self.certificate:
|
||||
# Server certificate attributes
|
||||
cert = open(self.certificate, 'rb').read()
|
||||
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
|
||||
ssl_environ.update({
|
||||
'SSL_SERVER_M_VERSION': cert.get_version(),
|
||||
'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
|
||||
## 'SSL_SERVER_V_START': Validity of server's certificate (start time),
|
||||
## 'SSL_SERVER_V_END': Validity of server's certificate (end time),
|
||||
})
|
||||
|
||||
for prefix, dn in [("I", cert.get_issuer()),
|
||||
("S", cert.get_subject())]:
|
||||
# X509Name objects don't seem to have a way to get the
|
||||
# complete DN string. Use str() and slice it instead,
|
||||
# because str(dn) == "<X509Name object '/C=US/ST=...'>"
|
||||
dnstr = str(dn)[18:-2]
|
||||
|
||||
wsgikey = 'SSL_SERVER_%s_DN' % prefix
|
||||
ssl_environ[wsgikey] = dnstr
|
||||
|
||||
# The DN should be of the form: /k1=v1/k2=v2, but we must allow
|
||||
# for any value to contain slashes itself (in a URL).
|
||||
while dnstr:
|
||||
pos = dnstr.rfind("=")
|
||||
dnstr, value = dnstr[:pos], dnstr[pos + 1:]
|
||||
pos = dnstr.rfind("/")
|
||||
dnstr, key = dnstr[:pos], dnstr[pos + 1:]
|
||||
if key and value:
|
||||
wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
|
||||
ssl_environ[wsgikey] = value
|
||||
|
||||
return ssl_environ
|
||||
|
||||
def makefile(self, sock, mode='r', bufsize=-1):
|
||||
if SSL and isinstance(sock, SSL.ConnectionType):
|
||||
timeout = sock.gettimeout()
|
||||
f = SSL_fileobject(sock, mode, bufsize)
|
||||
f.ssl_timeout = timeout
|
||||
return f
|
||||
else:
|
||||
return wsgiserver.CP_fileobject(sock, mode, bufsize)
|
||||
|
||||
"""A library for integrating pyOpenSSL with CherryPy.
|
||||
|
||||
The OpenSSL module must be importable for SSL functionality.
|
||||
You can obtain it from http://pyopenssl.sourceforge.net/
|
||||
|
||||
To use this module, set CherryPyWSGIServer.ssl_adapter to an instance of
|
||||
SSLAdapter. There are two ways to use SSL:
|
||||
|
||||
Method One
|
||||
----------
|
||||
|
||||
* ``ssl_adapter.context``: an instance of SSL.Context.
|
||||
|
||||
If this is not None, it is assumed to be an SSL.Context instance,
|
||||
and will be passed to SSL.Connection on bind(). The developer is
|
||||
responsible for forming a valid Context object. This approach is
|
||||
to be preferred for more flexibility, e.g. if the cert and key are
|
||||
streams instead of files, or need decryption, or SSL.SSLv3_METHOD
|
||||
is desired instead of the default SSL.SSLv23_METHOD, etc. Consult
|
||||
the pyOpenSSL documentation for complete options.
|
||||
|
||||
Method Two (shortcut)
|
||||
---------------------
|
||||
|
||||
* ``ssl_adapter.certificate``: the filename of the server SSL certificate.
|
||||
* ``ssl_adapter.private_key``: the filename of the server's private key file.
|
||||
|
||||
Both are None by default. If ssl_adapter.context is None, but .private_key
|
||||
and .certificate are both given and valid, they will be read, and the
|
||||
context will be automatically created from them.
|
||||
"""
|
||||
|
||||
import socket
|
||||
import threading
|
||||
import time
|
||||
|
||||
from cherrypy import wsgiserver
|
||||
|
||||
try:
|
||||
from OpenSSL import SSL
|
||||
from OpenSSL import crypto
|
||||
except ImportError:
|
||||
SSL = None
|
||||
|
||||
|
||||
class SSL_fileobject(wsgiserver.CP_fileobject):
|
||||
"""SSL file object attached to a socket object."""
|
||||
|
||||
ssl_timeout = 3
|
||||
ssl_retry = .01
|
||||
|
||||
def _safe_call(self, is_reader, call, *args, **kwargs):
|
||||
"""Wrap the given call with SSL error-trapping.
|
||||
|
||||
is_reader: if False EOF errors will be raised. If True, EOF errors
|
||||
will return "" (to emulate normal sockets).
|
||||
"""
|
||||
start = time.time()
|
||||
while True:
|
||||
try:
|
||||
return call(*args, **kwargs)
|
||||
except SSL.WantReadError:
|
||||
# Sleep and try again. This is dangerous, because it means
|
||||
# the rest of the stack has no way of differentiating
|
||||
# between a "new handshake" error and "client dropped".
|
||||
# Note this isn't an endless loop: there's a timeout below.
|
||||
time.sleep(self.ssl_retry)
|
||||
except SSL.WantWriteError:
|
||||
time.sleep(self.ssl_retry)
|
||||
except SSL.SysCallError, e:
|
||||
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
||||
return ""
|
||||
|
||||
errnum = e.args[0]
|
||||
if is_reader and errnum in wsgiserver.socket_errors_to_ignore:
|
||||
return ""
|
||||
raise socket.error(errnum)
|
||||
except SSL.Error, e:
|
||||
if is_reader and e.args == (-1, 'Unexpected EOF'):
|
||||
return ""
|
||||
|
||||
thirdarg = None
|
||||
try:
|
||||
thirdarg = e.args[0][0][2]
|
||||
except IndexError:
|
||||
pass
|
||||
|
||||
if thirdarg == 'http request':
|
||||
# The client is talking HTTP to an HTTPS server.
|
||||
raise wsgiserver.NoSSLError()
|
||||
|
||||
raise wsgiserver.FatalSSLAlert(*e.args)
|
||||
except:
|
||||
raise
|
||||
|
||||
if time.time() - start > self.ssl_timeout:
|
||||
raise socket.timeout("timed out")
|
||||
|
||||
def recv(self, *args, **kwargs):
|
||||
buf = []
|
||||
r = super(SSL_fileobject, self).recv
|
||||
while True:
|
||||
data = self._safe_call(True, r, *args, **kwargs)
|
||||
buf.append(data)
|
||||
p = self._sock.pending()
|
||||
if not p:
|
||||
return "".join(buf)
|
||||
|
||||
def sendall(self, *args, **kwargs):
|
||||
return self._safe_call(False, super(SSL_fileobject, self).sendall,
|
||||
*args, **kwargs)
|
||||
|
||||
def send(self, *args, **kwargs):
|
||||
return self._safe_call(False, super(SSL_fileobject, self).send,
|
||||
*args, **kwargs)
|
||||
|
||||
|
||||
class SSLConnection:
|
||||
"""A thread-safe wrapper for an SSL.Connection.
|
||||
|
||||
``*args``: the arguments to create the wrapped ``SSL.Connection(*args)``.
|
||||
"""
|
||||
|
||||
def __init__(self, *args):
|
||||
self._ssl_conn = SSL.Connection(*args)
|
||||
self._lock = threading.RLock()
|
||||
|
||||
for f in ('get_context', 'pending', 'send', 'write', 'recv', 'read',
|
||||
'renegotiate', 'bind', 'listen', 'connect', 'accept',
|
||||
'setblocking', 'fileno', 'close', 'get_cipher_list',
|
||||
'getpeername', 'getsockname', 'getsockopt', 'setsockopt',
|
||||
'makefile', 'get_app_data', 'set_app_data', 'state_string',
|
||||
'sock_shutdown', 'get_peer_certificate', 'want_read',
|
||||
'want_write', 'set_connect_state', 'set_accept_state',
|
||||
'connect_ex', 'sendall', 'settimeout', 'gettimeout'):
|
||||
exec("""def %s(self, *args):
|
||||
self._lock.acquire()
|
||||
try:
|
||||
return self._ssl_conn.%s(*args)
|
||||
finally:
|
||||
self._lock.release()
|
||||
""" % (f, f))
|
||||
|
||||
def shutdown(self, *args):
|
||||
self._lock.acquire()
|
||||
try:
|
||||
# pyOpenSSL.socket.shutdown takes no args
|
||||
return self._ssl_conn.shutdown()
|
||||
finally:
|
||||
self._lock.release()
|
||||
|
||||
|
||||
class pyOpenSSLAdapter(wsgiserver.SSLAdapter):
|
||||
"""A wrapper for integrating pyOpenSSL with CherryPy."""
|
||||
|
||||
context = None
|
||||
"""An instance of SSL.Context."""
|
||||
|
||||
certificate = None
|
||||
"""The filename of the server SSL certificate."""
|
||||
|
||||
private_key = None
|
||||
"""The filename of the server's private key file."""
|
||||
|
||||
certificate_chain = None
|
||||
"""Optional. The filename of CA's intermediate certificate bundle.
|
||||
|
||||
This is needed for cheaper "chained root" SSL certificates, and should be
|
||||
left as None if not required."""
|
||||
|
||||
def __init__(self, certificate, private_key, certificate_chain=None):
|
||||
if SSL is None:
|
||||
raise ImportError("You must install pyOpenSSL to use HTTPS.")
|
||||
|
||||
self.context = None
|
||||
self.certificate = certificate
|
||||
self.private_key = private_key
|
||||
self.certificate_chain = certificate_chain
|
||||
self._environ = None
|
||||
|
||||
def bind(self, sock):
|
||||
"""Wrap and return the given socket."""
|
||||
if self.context is None:
|
||||
self.context = self.get_context()
|
||||
conn = SSLConnection(self.context, sock)
|
||||
self._environ = self.get_environ()
|
||||
return conn
|
||||
|
||||
def wrap(self, sock):
|
||||
"""Wrap and return the given socket, plus WSGI environ entries."""
|
||||
return sock, self._environ.copy()
|
||||
|
||||
def get_context(self):
|
||||
"""Return an SSL.Context from self attributes."""
|
||||
# See http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/442473
|
||||
c = SSL.Context(SSL.SSLv23_METHOD)
|
||||
c.use_privatekey_file(self.private_key)
|
||||
if self.certificate_chain:
|
||||
c.load_verify_locations(self.certificate_chain)
|
||||
c.use_certificate_file(self.certificate)
|
||||
return c
|
||||
|
||||
def get_environ(self):
|
||||
"""Return WSGI environ entries to be merged into each request."""
|
||||
ssl_environ = {
|
||||
"HTTPS": "on",
|
||||
# pyOpenSSL doesn't provide access to any of these AFAICT
|
||||
## 'SSL_PROTOCOL': 'SSLv2',
|
||||
## SSL_CIPHER string The cipher specification name
|
||||
## SSL_VERSION_INTERFACE string The mod_ssl program version
|
||||
## SSL_VERSION_LIBRARY string The OpenSSL program version
|
||||
}
|
||||
|
||||
if self.certificate:
|
||||
# Server certificate attributes
|
||||
cert = open(self.certificate, 'rb').read()
|
||||
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert)
|
||||
ssl_environ.update({
|
||||
'SSL_SERVER_M_VERSION': cert.get_version(),
|
||||
'SSL_SERVER_M_SERIAL': cert.get_serial_number(),
|
||||
## 'SSL_SERVER_V_START': Validity of server's certificate (start time),
|
||||
## 'SSL_SERVER_V_END': Validity of server's certificate (end time),
|
||||
})
|
||||
|
||||
for prefix, dn in [("I", cert.get_issuer()),
|
||||
("S", cert.get_subject())]:
|
||||
# X509Name objects don't seem to have a way to get the
|
||||
# complete DN string. Use str() and slice it instead,
|
||||
# because str(dn) == "<X509Name object '/C=US/ST=...'>"
|
||||
dnstr = str(dn)[18:-2]
|
||||
|
||||
wsgikey = 'SSL_SERVER_%s_DN' % prefix
|
||||
ssl_environ[wsgikey] = dnstr
|
||||
|
||||
# The DN should be of the form: /k1=v1/k2=v2, but we must allow
|
||||
# for any value to contain slashes itself (in a URL).
|
||||
while dnstr:
|
||||
pos = dnstr.rfind("=")
|
||||
dnstr, value = dnstr[:pos], dnstr[pos + 1:]
|
||||
pos = dnstr.rfind("/")
|
||||
dnstr, key = dnstr[:pos], dnstr[pos + 1:]
|
||||
if key and value:
|
||||
wsgikey = 'SSL_SERVER_%s_DN_%s' % (prefix, key)
|
||||
ssl_environ[wsgikey] = value
|
||||
|
||||
return ssl_environ
|
||||
|
||||
def makefile(self, sock, mode='r', bufsize=-1):
|
||||
if SSL and isinstance(sock, SSL.ConnectionType):
|
||||
timeout = sock.gettimeout()
|
||||
f = SSL_fileobject(sock, mode, bufsize)
|
||||
f.ssl_timeout = timeout
|
||||
return f
|
||||
else:
|
||||
return wsgiserver.CP_fileobject(sock, mode, bufsize)
|
||||
|
||||
|
|
Loading…
Reference in New Issue