# -*- coding: UTF-8 -*-
Version = '$Header$';
# things to improve:
# - better error handling
# - use namespace parser
from Tools.Import import my_import
from Components.Sources.Source import ObsoleteSource
from Components.Converter.Converter import Converter
from Components.Element import Element
from xml.sax import make_parser
from xml.sax.handler import ContentHandler, feature_namespaces
from xml.sax.saxutils import escape as escape_xml
from twisted.python import util
from urllib2 import quote
from time import time
#DO NOT REMOVE THIS IMPORT
#It IS used (dynamically)
from WebScreens import *
#DO NOT REMOVE THIS IMPORT
from __init__ import decrypt_block
from os import urandom
global screen_cache
screen_cache = {}
# The classes and Function in File handle all ScreenPage-based requests
# ScreenPages use enigma2 standard functionality to bring contents to a webfrontend
#
# Like Skins a ScreenPage can consist of several Elements and Converters
#===============================================================================
# OneTimeElement
#
# This is the Standard Element for Rendering a "standard" WebElement
#===============================================================================
class OneTimeElement(Element):
def __init__(self, id):
Element.__init__(self)
self.source_id = id
def handleCommand(self, args):
if ',' in self.source_id:
paramlist = self.source_id.split(",")
list = {}
for key in paramlist:
arg = args.get(key, ())
Len = len(arg)
if Len == 0:
list[key] = None
elif Len == 1:
list[key] = "".join(arg)
elif Len == 2:
list[key] = arg[0]
self.source.handleCommand(list)
else:
for c in args.get(self.source_id, ()):
self.source.handleCommand(c)
def render(self, request):
t = self.source.getHTML(self.source_id)
request.write(t)
def execBegin(self):
self.suspended = False
def execEnd(self):
self.suspended = True
def onShow(self):
pass
def onHide(self):
pass
def destroy(self):
pass
#===============================================================================
# MacroElement
#
# A MacroElement helps using OneTimeElements inside a (Simple)ListFiller Loop
#===============================================================================
class MacroElement(OneTimeElement):
def __init__(self, id, macro_dict, macro_name):
OneTimeElement.__init__(self, id)
self.macro_dict = macro_dict
self.macro_name = macro_name
def render(self, request):
self.macro_dict[self.macro_name] = self.source.getHTML(self.source_id)
#===============================================================================
# StreamingElement
#
# In difference to an OneTimeElement a StreamingElement sends an ongoing Stream
# of Data. The end of the Streaming is usually when the client disconnects
#===============================================================================
class StreamingElement(OneTimeElement):
def __init__(self, id):
OneTimeElement.__init__(self, id)
self.request = None
def changed(self, what):
if self.request:
self.render(self.request)
def setRequest(self, request):
self.request = request
#===============================================================================
# ListItem
#
# a to-be-filled list item
#===============================================================================
class ListItem:
def __init__(self, name, filternum):
self.name = name
self.filternum = filternum
#===============================================================================
# ListMacroItem
#
# MacroItem inside a (Simple)ListFiller
#===============================================================================
class ListMacroItem:
def __init__(self, macrodict, macroname):
self.macrodict = macrodict
self.macroname = macroname
#===============================================================================
# TextToHTML
#
# Returns the String as is
#===============================================================================
class TextToHTML(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getHTML(self, id):
return self.source.text.replace('\xc2\x86', '').replace('\xc2\x87', '').decode("utf-8", "ignore").encode("utf-8") # encode & etc. here!
#===============================================================================
# TextToXML
#
# Escapes the given Text to be XML conform
#===============================================================================
class TextToXML(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getHTML(self, id):
return escape_xml(self.source.text).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
#===============================================================================
# TextToURL
#
# Escapes the given Text so it can be used inside a URL
#===============================================================================
class TextToURL(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getHTML(self, id):
return self.source.text.replace(" ", "%20").replace("+", "%2b").replace("&", "%26").replace('\xc2\x86', '').replace('\xc2\x87', '').decode("utf-8", "ignore").encode("utf-8")
#===============================================================================
# ReturnEmptyXML
#
# Returns a XML only consisting of
#===============================================================================
class ReturnEmptyXML(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getHTML(self, id):
return ""
#===============================================================================
# Null
# Return simply NOTHING
# Useful if you only want to issue a command.
#===============================================================================
class Null(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getHTML(self, id):
return ""
#===============================================================================
# JavascriptUpdate
#
# Transforms a string into a javascript update pattern
#===============================================================================
class JavascriptUpdate(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getHTML(self, id):
# 3c5x9, added parent. , this is because the ie loads this in a iframe. an the set is in index.html.xml
# all other will replace this in JS
return '\n' % (id, self.source.text.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"').replace('\xb0', '°'))
#===============================================================================
# SimpleListFiller
#
# The performant 'one-dimensonial listfiller' engine (podlfe)
#===============================================================================
class SimpleListFiller(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
def getText(self):
l = self.source.simplelist
conv_args = self.converter_arguments
list = [ ]
for element in conv_args:
if isinstance(element, basestring):
list.append((element, None))
elif isinstance(element, ListItem):
list.append((element, element.filternum))
elif isinstance(element, ListMacroItem):
list.append(element.macrodict[element.macroname], None)
else:
raise Exception("neither string, ListItem nor ListMacroItem")
strlist = [ ]
append = strlist.append
for item in l:
if item is None:
item = ""
for (element, filternum) in list:
#filter out "non-displayable" Characters - at the very end, do it the hard way...
item = str(item).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
if not filternum:
append(element)
elif filternum == 2:
append(item.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
elif filternum == 3:
append(escape_xml(item))
elif filternum == 4:
append(item.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
elif filternum == 5:
append(quote(item))
elif filternum == 6:
time = parseint(item) or 0
t = localtime(time)
append("%02d:%02d" % (t.tm_hour, t.tm_min))
elif filternum == 7:
time = parseint(item) or 0
t = localtime(time)
append("%d min" % (time / 60))
else:
append(item)
# (this will be done in c++ later!)
return ''.join(strlist)
text = property(getText)
#===============================================================================
# the performant 'listfiller'-engine (plfe)
#===============================================================================
class ListFiller(Converter):
def __init__(self, arg):
Converter.__init__(self, arg)
# print "ListFiller-arg: ",arg
def getText(self):
l = self.source.list
lut = self.source.lut
conv_args = self.converter_arguments
# now build a ["string", 1, "string", 2]-styled list, with indices into the
# list to avoid lookup of item name for each entry
lutlist = [ ]
for element in conv_args:
if isinstance(element, basestring):
lutlist.append((element, None))
elif isinstance(element, ListItem):
lutlist.append((lut[element.name], element.filternum))
elif isinstance(element, ListMacroItem):
lutlist.append((element.macrodict[element.macroname], None))
else:
raise Exception("neither string, ListItem nor ListMacroItem")
# now, for the huge list, do:
strlist = [ ]
append = strlist.append
for item in l:
for (element, filternum) in lutlist:
#None becomes ""
curitem = ""
if filternum:
#filter out "non-displayable" Characters - at the very end, do it the hard way...
curitem = str(item[element]).replace('\xc2\x86', '').replace('\xc2\x87', '').replace("\x19", "").replace("\x1c", "").replace("\x1e", "").decode("utf-8", "ignore").encode("utf-8")
if curitem is None:
curitem = ""
else:
if element is None:
element = ""
if not filternum:
append(element)
elif filternum == 2:
append(curitem.replace("\\", "\\\\").replace("\n", "\\n").replace('"', '\\"'))
elif filternum == 3:
append( escape_xml( curitem ))
elif filternum == 4:
append(curitem.replace("%", "%25").replace("+", "%2B").replace('&', '%26').replace('?', '%3f').replace(' ', '+'))
elif filternum == 5:
append(quote(curitem))
elif filternum == 6:
from time import localtime
time = int(float(curitem)) or 0
t = localtime(time)
append("%02d:%02d" % (t.tm_hour, t.tm_min))
elif filternum == 7:
from time import localtime
time = int(float(curitem)) or 0
t = localtime(time)
append("%d min" % (time / 60))
else:
append(curitem)
# (this will be done in c++ later!)
return ''.join(strlist)
text = property(getText)
#===============================================================================
# webifHandler
#
# Handles the Content of a Web-Request
# It looks up the source, instantiates the Element and Calls the Converter
#===============================================================================
class webifHandler(ContentHandler):
def __init__(self, session, request):
self.res = [ ]
self.mode = 0
self.screen = None
self.session = session
self.screens = [ ]
self.request = request
self.macros = { }
def start_element(self, attrs):
scr = self.screen
wsource = attrs["source"]
path = wsource.split('.')
while len(path) > 1:
scr = self.screen.getRelatedScreen(path[0])
if scr is None:
print "[webif.py] Parent Screen not found!"
print wsource
path = path[1:]
source = scr.get(path[0])
if isinstance(source, ObsoleteSource):
# however, if we found an "obsolete source", issue warning, and resolve the real source.
print "WARNING: WEBIF '%s' USES OBSOLETE SOURCE '%s', USE '%s' INSTEAD!" % (name, wsource, source.new_source)
print "OBSOLETE SOURCE WILL BE REMOVED %s, PLEASE UPDATE!" % (source.removal_date)
if source.description:
print source.description
wsource = source.new_source
else:
pass
# otherwise, use that source.
self.source = source
self.source_id = str(attrs.get("id", wsource))
self.is_streaming = "streaming" in attrs
self.macro_name = attrs.get("macro") or None
def end_element(self):
# instatiate either a StreamingElement or a OneTimeElement, depending on what's required.
if not self.is_streaming:
if self.macro_name is None:
c = OneTimeElement(self.source_id)
else:
c = MacroElement(self.source_id, self.macros, self.macro_name)
else:
assert self.macro_name is None
c = StreamingElement(self.source_id)
c.connect(self.source)
self.res.append(c)
self.screen.renderer.append(c)
del self.source
def start_convert(self, attrs):
ctype = attrs["type"]
# TODO: we need something better here
if ctype[:4] == "web:": # for now
self.converter = eval(ctype[4:])
else:
try:
self.converter = my_import('.'.join(("Components", "Converter", ctype))).__dict__.get(ctype)
except ImportError:
self.converter = my_import('.'.join(("Plugins", "Extensions", "WebInterface", "WebComponents", "Converter", ctype))).__dict__.get(ctype)
self.sub = [ ]
def end_convert(self):
if len(self.sub) == 1:
self.sub = self.sub[0]
c = self.converter(self.sub)
c.connect(self.source)
self.source = c
del self.sub
def parse_item(self, attrs):
if "name" in attrs:
filter = {"": 1, "javascript_escape": 2, "xml": 3, "uri": 4, "urlencode": 5, "time": 6, "minutes": 7}[attrs.get("filter", "")]
self.sub.append(ListItem(attrs["name"], filter))
else:
assert "macro" in attrs, "e2:item must have a name= or macro= attribute!"
self.sub.append(ListMacroItem(self.macros, attrs["macro"]))
def startElement(self, name, attrs):
if name == "e2:screen":
if "external_module" in attrs:
exec "from " + attrs["external_module"] + " import *"
self.screen = eval(attrs["name"])(self.session, self.request) # fixme
self.screens.append(self.screen)
return
if name[:3] == "e2:":
self.mode += 1
tag = '<' + name + ''.join([' %s="%s"' % x for x in attrs.items()]) + '>'
#tag = tag.encode('utf-8')
if self.mode == 0:
self.res.append(tag)
elif self.mode == 1: # expect ""
assert name == "e2:element", "found %s instead of e2:element" % name
self.start_element(attrs)
elif self.mode == 2: # expect ""
if name[:3] == "e2:":
assert name == "e2:convert"
self.start_convert(attrs)
else:
self.sub.append(tag)
elif self.mode == 3:
assert name == "e2:item", "found %s instead of e2:item!" % name
self.parse_item(attrs)
def endElement(self, name):
if name == "e2:screen":
self.screen = None
return
tag = "" + name + ">"
if self.mode == 0:
self.res.append(tag)
elif self.mode == 2 and name[:3] != "e2:":
self.sub.append(tag)
elif self.mode == 2: # closed 'convert' -> sub
self.end_convert()
elif self.mode == 1: # closed 'element'
self.end_element()
if name[:3] == "e2:":
self.mode -= 1
def processingInstruction(self, target, data):
self.res.append('' + target + ' ' + data + '>')
def characters(self, ch):
ch = ch.encode('utf-8')
if self.mode == 0:
self.res.append(ch)
elif self.mode == 2:
self.sub.append(ch)
def startEntity(self, name):
self.res.append('&' + name + ';');
def execBegin(self):
for screen in self.screens:
screen.execBegin()
def cleanup(self):
print "screen cleanup!"
for screen in self.screens:
screen.execEnd()
screen.doClose()
self.screens = [ ]
#===============================================================================
# renderPage
#
# Creates the Handler for a Request and calls it
# Also ensures that the Handler is finished after the Request is done
#===============================================================================
def renderPage(request, path, session):
# read in the template, create required screens
# we don't have persistense yet.
# if we had, this first part would only be done once.
handler = webifHandler(session, request)
parser = make_parser()
parser.setFeature(feature_namespaces, 0)
parser.setContentHandler(handler)
parser.parse(open(util.sibpath(__file__, path)))
# by default, we have non-streaming pages
finish = True
# first, apply "commands" (aka. URL argument)
for x in handler.res:
if isinstance(x, Element):
x.handleCommand(request.args)
handler.execBegin()
# now, we have a list with static texts mixed
# with non-static Elements.
# flatten this list, write into the request.
for x in handler.res:
if isinstance(x, Element):
if isinstance(x, StreamingElement):
finish = False
x.setRequest(request)
x.render(request)
else:
request.write(str(x))
# if we met a "StreamingElement", there is at least one
# element which wants to output data more than once,
# i.e. on host-originated changes.
# in this case, don't finish yet, don't cleanup yet,
# but instead do that when the client disconnects.
if finish:
requestFinish(handler, request)
else:
def requestFinishDeferred(nothing, handler, request):
from twisted.internet import reactor
reactor.callLater(0, requestFinish, handler, request)
d = request.notifyFinish()
d.addBoth( requestFinishDeferred, handler, request )
#===============================================================================
# requestFinish
#
# This has to be/is called at the end of every ScreenPage-based Request
#===============================================================================
def requestFinish(handler, request):
handler.cleanup()
request.finish()
del handler
def validate_certificate(cert, key):
buf = decrypt_block(cert[8:], key)
if buf is None:
return None
return buf[36:107] + cert[139:196]
def get_random():
try:
xor = lambda a,b: ''.join(chr(ord(c)^ord(d)) for c,d in zip(a,b*100))
random = urandom(8)
x = str(time())[-8:]
result = xor(random, x)
return result
except:
return None