← Back to team overview

openlp-core team mailing list archive

[Merge] lp:~raoul-snyman/openlp/ssl into lp:openlp

 

Raoul Snyman has proposed merging lp:~raoul-snyman/openlp/ssl into lp:openlp.

Requested reviews:
  OpenLP Core (openlp-core)

For more details, see:
https://code.launchpad.net/~raoul-snyman/openlp/ssl/+merge/106245
-- 
https://code.launchpad.net/~raoul-snyman/openlp/ssl/+merge/106245
Your team OpenLP Core is requested to review the proposed merge of lp:~raoul-snyman/openlp/ssl into lp:openlp.
=== modified file 'openlp/plugins/remotes/lib/httpserver.py'
--- openlp/plugins/remotes/lib/httpserver.py	2012-04-20 19:36:10 +0000
+++ openlp/plugins/remotes/lib/httpserver.py	2012-05-17 19:11:20 +0000
@@ -117,9 +117,11 @@
 import re
 import urllib
 import urlparse
+import inspect
 
 from PyQt4 import QtCore, QtNetwork
 from mako.template import Template
+from PyQt4.QtNetwork import QSslSocket
 
 from openlp.core.lib import Receiver, PluginStatus, StringContent
 from openlp.core.utils import AppLocation, translate
@@ -144,7 +146,37 @@
             self.code = code
 
 
-class HttpServer(object):
+class SslServer(QtNetwork.QTcpServer):
+    """
+    SslServer is a class that implements an HTTPS server.
+    """
+    sslCertificate = None
+    sslPrivateKey = None
+    connections = []
+
+    def incomingConnection(self, socket_descriptor):
+        """
+        This method overrides the default one in :method:`incomingConnection`
+        to provide the SSL socket support needed for HTTPS.
+        """
+        log.debug(u'Incoming HTTPS connection')
+        if not SslServer.sslCertificate:
+            ssl_cert_data = QtCore.QByteArray(open("/home/raoul/openlp.crt", "rb").read())
+            SslServer.sslCertificate = QtNetwork.QSslCertificate(ssl_cert_data)
+        if not SslServer.sslPrivateKey:
+            ssl_key_data = QtCore.QByteArray(open("/home/raoul/openlp.key", "rb").read())
+            SslServer.sslPrivateKey = QtNetwork.QSslKey(ssl_key_data, QtNetwork.QSsl.Rsa)
+        server_socket = QSslSocket()
+        if server_socket.setSocketDescriptor(socket_descriptor):
+            server_socket.setPrivateKey(SslServer.sslPrivateKey)
+            server_socket.setLocalCertificate(SslServer.sslCertificate)
+            server_socket.setPeerVerifyMode(QtNetwork.QSslSocket.VerifyNone)
+            server_socket.startServerEncryption()
+            self.connections.append(server_socket)
+            self.addPendingConnection(server_socket)
+
+
+class HttpServer(QtCore.QObject):
     """
     Ability to control OpenLP via a web browser.
     """
@@ -152,6 +184,7 @@
         """
         Initialise the httpserver, and start the server.
         """
+        QtCore.QObject.__init__(self)
         log.debug(u'Initialise httpserver')
         self.plugin = plugin
         self.html_dir = os.path.join(
@@ -161,10 +194,11 @@
         self.current_item = None
         self.current_slide = None
         self.start_tcp()
+        self.start_ssl()
 
     def start_tcp(self):
         """
-        Start the http server, use the port in the settings default to 4316.
+        Start the HTTP server, use the port in the settings default to 4316.
         Listen out for slide and song changes so they can be broadcast to
         clients. Listen out for socket connections.
         """
@@ -175,18 +209,43 @@
         address = QtCore.QSettings().value(
             self.plugin.settingsSection + u'/ip address',
             QtCore.QVariant(u'0.0.0.0')).toString()
-        self.server = QtNetwork.QTcpServer()
-        self.server.listen(QtNetwork.QHostAddress(address), port)
+        self.http_server = QtNetwork.QTcpServer()
+        self.http_server.listen(QtNetwork.QHostAddress(address), port)
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'slidecontroller_live_changed'),
             self.slide_change)
         QtCore.QObject.connect(Receiver.get_receiver(),
             QtCore.SIGNAL(u'slidecontroller_live_started'),
             self.item_change)
-        QtCore.QObject.connect(self.server,
+        QtCore.QObject.connect(self.http_server,
             QtCore.SIGNAL(u'newConnection()'), self.new_connection)
         log.debug(u'TCP listening on port %d' % port)
 
+    def start_ssl(self):
+        """
+        Start the HTTPS server, use the port in the settings default to 4317.
+        Listen out for slide and song changes so they can be broadcast to
+        clients. Listen out for socket connections.
+        """
+        log.debug(u'Start SSL server')
+        port = QtCore.QSettings().value(
+            self.plugin.settingsSection + u'/ssl port',
+            QtCore.QVariant(4317)).toInt()[0]
+        address = QtCore.QSettings().value(
+            self.plugin.settingsSection + u'/ip address',
+            QtCore.QVariant(u'0.0.0.0')).toString()
+        self.https_server = SslServer()
+        self.https_server.listen(QtNetwork.QHostAddress(address), port)
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'slidecontroller_live_changed'),
+            self.slide_change)
+        QtCore.QObject.connect(Receiver.get_receiver(),
+            QtCore.SIGNAL(u'slidecontroller_live_started'),
+            self.item_change)
+        QtCore.QObject.connect(self.https_server,
+            QtCore.SIGNAL(u'newConnection()'), self.new_connection)
+        log.debug(u'SSL listening on port %d' % port)
+
     def slide_change(self, row):
         """
         Slide change listener. Store the item and tell the clients.
@@ -205,7 +264,8 @@
         communication.
         """
         log.debug(u'new http connection')
-        socket = self.server.nextPendingConnection()
+        server = self.sender()
+        socket = server.nextPendingConnection()
         if socket:
             self.connections.append(HttpConnection(self, socket))
 
@@ -222,7 +282,8 @@
         Close down the http server.
         """
         log.debug(u'close http server')
-        self.server.close()
+        self.http_server.close()
+        self.https_server.close()
 
 
 class HttpConnection(object):
@@ -252,10 +313,14 @@
             (r'^/api/(.*)/live$', self.go_live),
             (r'^/api/(.*)/add$', self.add_to_service)
         ]
-        QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
-            self.ready_read)
-        QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'),
-            self.disconnected)
+        if isinstance(socket, QtNetwork.QSslSocket):
+            QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'encrypted()'),
+                self.encrypted)
+        else:
+            QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
+                self.ready_read)
+            QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'),
+                self.disconnected)
         self.translate()
 
     def _get_service_items(self):
@@ -309,6 +374,15 @@
             'options': translate('RemotePlugin.Mobile', 'Options')
         }
 
+    def encrypted(self):
+        """
+        Only setup these slots when the data is encrypted.
+        """
+        QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'readyRead()'),
+            self.ready_read)
+        QtCore.QObject.connect(self.socket, QtCore.SIGNAL(u'disconnected()'),
+            self.disconnected)
+
     def ready_read(self):
         """
         Data has been sent from the client. Respond to it
@@ -320,6 +394,7 @@
                 log.debug(u'received: ' + data)
             except UnicodeDecodeError:
                 # Malicious request containing non-ASCII characters.
+                #self.send_response(HttpResponse(code='400 Bad Request'))
                 self.close()
                 return
             words = data.split(' ')
@@ -342,6 +417,9 @@
             else:
                 self.send_response(HttpResponse(code='404 Not Found'))
             self.close()
+        else:
+            self.send_response(HttpResponse(code='400 Bad Request'))
+            self.close()
 
     def serve_file(self, filename=None):
         """
@@ -352,25 +430,27 @@
         Ultimately for i18n, this could first look for xx/file.html before
         falling back to file.html... where xx is the language, e.g. 'en'
         """
-        log.debug(u'serve file request %s' % filename)
+        log.debug(u'serve file request (original) %s' % filename)
         if not filename:
             filename = u'index.html'
         elif filename == u'stage':
             filename = u'stage.html'
+        log.debug(u'serve file request (updated) %s' % filename)
         path = os.path.normpath(os.path.join(self.parent.html_dir, filename))
         if not path.startswith(self.parent.html_dir):
+            log.debug(u'File not found, returning 404')
             return HttpResponse(code=u'404 Not Found')
         ext = os.path.splitext(filename)[1]
         html = None
         if ext == u'.html':
-            mimetype = u'text/html'
+            mimetype = u'text/html; charset=utf-8'
             variables = self.template_vars
             html = Template(filename=path, input_encoding=u'utf-8',
                 output_encoding=u'utf-8').render(**variables)
         elif ext == u'.css':
-            mimetype = u'text/css'
+            mimetype = u'text/css; charset=utf-8'
         elif ext == u'.js':
-            mimetype = u'application/x-javascript'
+            mimetype = u'application/x-javascript; charset=utf-8'
         elif ext == u'.jpg':
             mimetype = u'image/jpeg'
         elif ext == u'.gif':
@@ -378,7 +458,7 @@
         elif ext == u'.png':
             mimetype = u'image/png'
         else:
-            mimetype = u'text/plain'
+            mimetype = u'text/plain; charset=utf-8'
         file_handle = None
         try:
             if html:
@@ -413,8 +493,8 @@
             u'display': self.parent.plugin.liveController.desktopScreen.\
                 isChecked()
         }
-        return HttpResponse(json.dumps({u'results': result}),
-            {u'Content-Type': u'application/json'})
+        return HttpResponse(json.dumps({u'results': result}).encode(u'utf-8'),
+            {u'Content-Type': u'application/json; charset=utf-8'})
 
     def display(self, action):
         """
@@ -424,8 +504,8 @@
             This is the action, either ``hide`` or ``show``.
         """
         Receiver.send_message(u'slidecontroller_toggle_display', action)
-        return HttpResponse(json.dumps({u'results': {u'success': True}}),
-            {u'Content-Type': u'application/json'})
+        return HttpResponse(json.dumps({u'results': {u'success': True}}).encode(u'utf-8'),
+            {u'Content-Type': u'application/json; charset=utf-8'})
 
     def alert(self):
         """
@@ -443,8 +523,8 @@
             success = True
         else:
             success = False
-        return HttpResponse(json.dumps({u'results': {u'success': success}}),
-            {u'Content-Type': u'application/json'})
+        return HttpResponse(json.dumps({u'results': {u'success': success}}).encode(u'utf-8'),
+            {u'Content-Type': u'application/json; charset=utf-8'})
 
     def controller(self, type, action):
         """
@@ -493,15 +573,15 @@
             else:
                 Receiver.send_message(event)
             json_data = {u'results': {u'success': True}}
-        return HttpResponse(json.dumps(json_data),
-            {u'Content-Type': u'application/json'})
+        return HttpResponse(json.dumps(json_data).encode(u'utf-8'),
+            {u'Content-Type': u'application/json; charset=utf-8'})
 
     def service(self, action):
         event = u'servicemanager_%s' % action
         if action == u'list':
             return HttpResponse(
-                json.dumps({u'results': {u'items': self._get_service_items()}}),
-                {u'Content-Type': u'application/json'})
+                json.dumps({u'results': {u'items': self._get_service_items()}}).encode(u'utf-8'),
+                {u'Content-Type': u'application/json; charset=utf-8'})
         else:
             event += u'_item'
         if self.url_params and self.url_params.get(u'data'):
@@ -512,8 +592,8 @@
             Receiver.send_message(event, data[u'request'][u'id'])
         else:
             Receiver.send_message(event)
-        return HttpResponse(json.dumps({u'results': {u'success': True}}),
-            {u'Content-Type': u'application/json'})
+        return HttpResponse(json.dumps({u'results': {u'success': True}}).encode(u'utf-8'),
+            {u'Content-Type': u'application/json; charset=utf-8'})
 
     def pluginInfo(self, action):
         """
@@ -531,8 +611,8 @@
                     searches.append([plugin.name, unicode(
                         plugin.textStrings[StringContent.Name][u'plural'])])
             return HttpResponse(
-                json.dumps({u'results': {u'items': searches}}),
-                {u'Content-Type': u'application/json'})
+                json.dumps({u'results': {u'items': searches}}).encode(u'utf-8'),
+                {u'Content-Type': u'application/json; charset=utf-8'})
 
     def search(self, type):
         """
@@ -553,8 +633,8 @@
         else:
             results = []
         return HttpResponse(
-            json.dumps({u'results': {u'items': results}}),
-            {u'Content-Type': u'application/json'})
+            json.dumps({u'results': {u'items': results}}).encode(u'utf-8'),
+            {u'Content-Type': u'application/json; charset=utf-8'})
 
     def go_live(self, type):
         """
@@ -588,8 +668,13 @@
         for header, value in response.headers.iteritems():
             http += '%s: %s\r\n' % (header, value)
         http += '\r\n'
-        self.socket.write(http)
-        self.socket.write(response.content)
+        self.socket.write(http.encode(u'utf-8'))
+        content = response.content
+        if isinstance(content, basestring):
+            content = unicode(content, u'utf-8')
+            content = content.encode('utf-8')
+        self.socket.write(content)
+        log.debug(u'Content: %s', unicode(content, u'utf-8'))
 
     def disconnected(self):
         """


Follow ups