summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rwxr-xr-xwww/cgi-bin/gnu-date-delta.py221
-rw-r--r--www/index.html22
2 files changed, 243 insertions, 0 deletions
diff --git a/www/cgi-bin/gnu-date-delta.py b/www/cgi-bin/gnu-date-delta.py
new file mode 100755
index 0000000..a7f3644
--- /dev/null
+++ b/www/cgi-bin/gnu-date-delta.py
@@ -0,0 +1,221 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import sys, os, cgi, re, locale, resource, time, signal
+from jinja2 import Template
+from subprocess import Popen, PIPE
+
+
+def http_error(http_code,http_status,msg):
+ """
+ Send an error code + status to the HTTP server,
+ and log the error on STDERR as well,
+ then terminate the script with exit-code 1.
+
+ http_code: int ( 40X = client errors, 50X = server errors)
+ http_status: text message
+ msg: any text
+
+ Message is always sent as plain-text content.
+ """
+ print ("Status: %d %s" % (http_code, http_status))
+ print ("Content-Type: text/plain")
+ print ("")
+ print (msg)
+ print ("returned HTTP error %d (%s): %s" % (http_code, http_status, msg),
+ file=sys.stderr)
+ sys.exit(1)
+
+def http_bad_request_error(msg):
+ """
+ Shortcut for sending client errors (HTTP 400/Bad Request)
+ """
+ http_error(400,"Bad Request",msg)
+
+
+def http_server_error(msg):
+ """
+ Shortcut for sending server errors (HTTP 500)
+ """
+ http_error(500,"Internal Server Error",msg)
+
+
+def set_resource_limits():
+ """
+ Self-imposed limits - reduce risk of abuse.
+ Adjust values as needed (per script/scenario)
+ """
+ max_cpu = 1 # max. seconds of cputime (not wall time)
+ max_fsize = 10000 # in bytes, per-process
+ max_nofile = 10 # num. of open descriptors, per process
+ max_data = 50000000 # in bytes, per-process (when using brk/sbrk)
+ max_vmem = 100000000 # in bytes, per-process (when mmap)
+ nice = 10 # nice level
+
+ try:
+ resource.setrlimit(resource.RLIMIT_CPU, (max_cpu,max_cpu))
+ resource.setrlimit(resource.RLIMIT_FSIZE, (max_fsize,max_fsize))
+ resource.setrlimit(resource.RLIMIT_NOFILE, (max_nofile,max_nofile))
+ resource.setrlimit(resource.RLIMIT_DATA, (max_data,max_data))
+
+ #use AS as alternative to VMEM if the attribute isn't defined.
+ # http://stackoverflow.com/a/30269998/5731870
+ # http://git.savannah.gnu.org/cgit/bash.git/tree/builtins/ulimit.def#n154
+ if hasattr(resource,'RLIMIT_VMEM'):
+ resource.setrlimit(resource.RLIMIT_VMEM,(max_vmem,max_vmem))
+ elif hasattr(resource,'RLIMIT_AS'):
+ resource.setrlimit(resource.RLIMIT_AS, (max_vmem,max_vmem))
+
+ os.nice(nice)
+ except resource.error as e:
+ http_server_error("failed to set resource limits (%s)" % (str(e)))
+ except OSError as e:
+ http_server_error("failed to renice (%s)" % (str(e)))
+
+
+def force_C_locale():
+ """
+ Force C/POSIX locale for this script, and all executed children.
+ """
+ locale.setlocale(locale.LC_ALL,'C')
+ os.environ["LC_ALL"] = 'C'
+ os.environ["LC_LANG"] = 'C'
+ os.environ["LANG"] = 'C'
+ os.environ["LANGUAGE"] = 'C'
+ os.environ["LC_CTYPE"] = 'C'
+
+ #TODO: consider this (with ignoring non-ascii output)
+ #sys.stdout = codecs.getwriter('ascii')(sys.stdout)
+
+
+class AlarmException(Exception):
+ pass
+
+def alarm_signal_handler(signum, frame):
+ raise AlarmException()
+
+def limit_script_walltime(seconds):
+ signal.signal(signal.SIGALRM, alarm_signal_handler)
+ signal.alarm(seconds)
+
+
+def valid_delta(d):
+ if not d:
+ return False
+ if len(str(d).strip())==0:
+ return False
+ t = re.compile("^[-a-zA-Z0-9: \.\+]+$")
+ if not t.match(d):
+ return False
+ return True
+
+
+
+def parse_cgi_params():
+ """
+ Extract CGI parameters, bail-out on any errors.
+ """
+ form = cgi.FieldStorage()
+
+ delta = form.getfirst('d',None)
+ if not valid_delta(delta):
+ http_bad_request_error("invalid delta value (%s)" % (str(delta)))
+
+ fmt = form.getfirst('f',"")
+ if not (fmt in ["",'8601','2822','3339']):
+ http_bad_request_error("invalid format value (%s)" % (str(fmt)))
+
+ return (delta,fmt)
+
+
+def run_gnu_date(delta,fmt=None):
+ cmd = ["date" ]
+ if fmt:
+ cmd.append(fmt)
+ cmd.append("-d")
+ cmd.append("now " + str(delta))
+
+ msg = ' '.join(cmd)
+ print ("executing: " + msg, file=sys.stderr)
+
+ try:
+ p = Popen(cmd,shell=False,stdout=PIPE,stderr=PIPE)
+ (out,err) = p.communicate()
+ ## NOTE:
+ ## returned values are not necessarily ASCII, or even valid text/utf/etc.
+ ## ALWAYS sanitize output.
+ ## Python (at least v2) has severe problems handling non-ascii
+ ## characters without extra processing.
+ return ( p.returncode==0, out, err )
+ except OSError as e:
+ http_server_error("failed to execute date: %s" % (str(e)))
+
+
+def main():
+ """
+ Main script: get CGI parameters, return HTML content.
+ """
+
+ (delta, fmt) = parse_cgi_params()
+
+ if not fmt or len(str(fmt).strip())==0:
+ fmt_string = "(default)"
+ fmt = None
+ elif fmt=='8601':
+ fmt_string = "iso-8601"
+ fmt = "--iso-8601=seconds"
+ elif fmt=='2822':
+ fmt_string = "rfc-2822"
+ fmt = "--rfc-2822"
+ elif fmt=='3339':
+ fmt_string = "rfc-3339"
+ fmt = "--rfc-3339=seconds"
+ else:
+ # should never happen
+ http_server_error("internal error: invalid fmt (%s)" % (str(fmt)))
+
+
+ (date_ok,date_result,date_error) = run_gnu_date(delta,fmt)
+
+ print ("Content-Type: text/html")
+ print ("")
+
+ html_tmpl="""
+ <html>
+ <body>
+ Hello from Python CGI Script (Program Execution Example)
+ <br/>
+ <br/>
+
+ Time Delta: <b>now (and) {{ delta }}</b>
+ <br/>
+
+ Output Format: {{ fmt_string }}
+ <br/>
+ <br/>
+
+ Result:
+ {% if date_ok %}
+ <b><code>{{date_result}}</code></b>
+ {% else %}
+ Failed to run date:
+ <b><code>{{ date_error }}</b></code>
+ {% endif %}
+ </body>
+ </html>
+ """
+ tmpl = Template(html_tmpl)
+ html = tmpl.render(delta=delta,fmt_string=fmt_string,
+ date_ok=date_ok, date_result=date_result,
+ date_error=date_error)
+ print (html)
+
+
+if __name__=="__main__":
+ limit_script_walltime(4)
+ set_resource_limits()
+ force_C_locale()
+ try:
+ main()
+ except AlarmException as e:
+ http_server_error("script timed-out")
diff --git a/www/index.html b/www/index.html
index 4855ac5..4630c71 100644
--- a/www/index.html
+++ b/www/index.html
@@ -33,8 +33,30 @@ also works with POST requests:
curl -F age=43 http://localhost:8888/cgi-bin/get-params.py
curl -F name=XXXX http://localhost:8888/cgi-bin/get-params.py
</pre>
+ </li>
+
+ <li>
+ <form method="GET" action="/cgi-bin/gnu-date-delta.py">
+ <b>Python scripts, runs an external command:</b>
+ <br/>
+ calculate delta from today: <input type="text" size="10" name="d" value="+ 3 days">
+ format: <select name="f">
+ <option value="">(default)</option>
+ <option value="8601">iso-8601</option>
+ <option value="2822">rfc-2822</option>
+ <option value="3339">rfc-3339</option>
+ </select>
+ <input type="submit" value="go">
+ </form>
+ From Command-line, try:
+<pre>
+ curl 'http://localhost:8888/cgi-bin/gnu-date-delta.py?d=2+years'
+also works with POST requests:
+ curl -F d="2 years - 3 days" http://localhost:8888/cgi-bin/gnu-date-delta.py
+</pre>
</li>
+
</ul>
</body>