Minor changes required for running tests
[ipypbx] / src / ipypbx / http.py
index a049e42..3e76ed9 100644 (file)
@@ -23,18 +23,32 @@ class FreeswitchConfigServer(QtNetwork.QTcpServer):
     """
     TCP server that receives config requests from freeswitch.
     """
-    def __init__(self, window):
-        super(FreeswitchConfigServer, self).__init__(window)
+    configNotFound = '''
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<document type="freeswitch/xml">
+  <section name="result">
+    <result status="not found" />
+  </section>
+</document>
+    '''
+    responseTemplate = '''HTTP/1.1 200 OK
+Content-Type: text/xml; charset=utf-8
+Content-Length: %i
+
+%s'''
+  
+    def __init__(self, database, parent):
+        super(FreeswitchConfigServer, self).__init__(parent)
 
         self.host = None
         self.port = None
         self.connection_id = None
         self.is_running = False
         self.generators = [
-            GenClass(window, self) for GenClass in (
+            GenClass(database, self) for GenClass in (
                 SofiaConfGenerator,)]
         
-        self.httpRequestParser = HttpRequestParser(self)
+        self.httpRequestParser = HttpRequestParser()
         
     def setSocketData(self, host, port, connection_id):
         """
@@ -94,6 +108,19 @@ class FreeswitchConfigServer(QtNetwork.QTcpServer):
         for line in str(self.socket.readAll()).split('\r\n'):
             self.httpRequestParser.handle(line)
 
+        for generator in self.generators:
+            if generator.canHandle(self.httpRequestParser.data):
+                self.state += 1
+                self.result = etree.tostring(generator.generateConfig(
+                    self.httpRequestParser.headers))
+                break
+
+        response = self.httpRequestParser.result or self.configNotFound
+        http_response = self.responseTemplate % (len(response), response)
+        self.socket.write(http_response)
+        self.httpRequestParser.reset()        
+        self.socket.close()
+
 
 class HttpParseError(Exception):
     """
@@ -105,12 +132,11 @@ class HttpRequestParser(object):
     """
     A simple state machine for parsing HTTP requests.
     """
-    HTTP_NONE, HTTP_REQUEST, HTTP_HEADERS, HTTP_EMPTY, HTTP_MESSAGE, \
-        HTTP_DONE = range(6)
-    HTTP_STATES = ['NONE', 'REQUEST', 'HEADERS', 'EMPTY', 'MESSAGE', 'DONE']
+    HTTP_NONE, HTTP_REQUEST, HTTP_HEADERS, HTTP_EMPTY, HTTP_BODY, HTTP_DONE = \
+        range(6)
+    HTTP_STATES = ['NONE', 'REQUEST', 'HEADERS', 'EMPTY', 'BODY', 'DONE']
     
-    def __init__(self, parent):
-        self.parent = parent
+    def __init__(self):
         self.reset()
 
     def reset(self):
@@ -123,6 +149,7 @@ class HttpRequestParser(object):
         self.http_version = None
         self.headers = {}
         self.data = {}
+        self.result = None
         
         # Set initial state.
         self.state = self.HTTP_NONE        
@@ -149,8 +176,11 @@ class HttpRequestParser(object):
         """
         Retrieve HTTP method, request path and HTTP version from request.
         """
-        self.method, self.request_path, self.http_version = line.split(' ')
-        self.state += 1
+        try:
+            self.method, self.request_path, self.http_version = line.split(' ')
+            self.state += 1
+        except ValueError:
+            pass
 
     def handleHeaders(self, line):
         """
@@ -169,24 +199,18 @@ class HttpRequestParser(object):
         """
         self.state += 1
 
-    def handleMessage(self, line):
+    def handleBody(self, line):
         """
         Append to message body.
         """
-        self.data = dict(pair.split('=', 2) for pair in line.split('&'))
-
-        for k, v in self.data.items():
-            print k, '=>', v
-        print
-
-        for generator in self.parent.generators:
-            if generator.canHandle(self.data):
-                self.state += 1
-                print generator.generateConfig(self.headers)
-        else:
-            print 'No generator found'
+        if self.method != 'POST':
+            raise HttpParseError('Only POST request are supported')
             
+        self.data = dict(pair.split('=', 2) for pair in line.split('&'))
 
+    def handleDone(self, line):
+        raise HttpParseError("Can't read past request end")
+    
 
 class FreeswitchConfigGenerator(object):
     """
@@ -194,24 +218,31 @@ class FreeswitchConfigGenerator(object):
     """
     
     param_match = {}
-    section_name = None
 
-    def __init__(self, model, parent):
-        self.model = model
+    def __init__(self, database, parent):
+        self.database = database
         self.parent = parent
 
+#    def database(self):
+#        """
+#        Return database instance.
+#        """
+#        return self.model.controllers['connection'].model.database()
+
     def canHandle(self, params):
+        """
+        Check if this generator can handle a request from freeswitch.
+        """
         for key, value in self.param_match.iteritems():
-            print key, value, params.get(key, None)
             if params.get(key, None) != value:
                 return False
         else:
             return True
 
     def baseElements(self):
-        root_elt = etree.Element('document')
+        root_elt = etree.Element('document', type='freeswitch/xml')
         section_elt = etree.SubElement(
-            root_elt, 'section', name=self.section_name)
+            root_elt, 'section', name=self.param_match['section'])
         return root_elt, section_elt
     baseElements = property(baseElements)
 
@@ -220,8 +251,12 @@ class FreeswitchConfigGenerator(object):
 
     @staticmethod
     def addParams(parent_elt, params):
+        """
+        Create params element based on data passed in a list.
+        """
         for name, value in params:
-            etree.SubElement(parent_elt, 'param', name=name, value=value)
+            etree.SubElement(
+                parent_elt, 'param', name=name, value=str(value))
             
         
 class SofiaConfGenerator(FreeswitchConfigGenerator):
@@ -229,7 +264,6 @@ class SofiaConfGenerator(FreeswitchConfigGenerator):
     Generates sofia.conf.xml config file.
     """
     param_match = {'section': 'configuration', 'key_value': 'sofia.conf'}
-    section_name = 'configuration'
     config_name = 'sofia.conf'
 
     def generateConfig(self, params):
@@ -240,39 +274,47 @@ class SofiaConfGenerator(FreeswitchConfigGenerator):
         configuration_elt = etree.SubElement(
             section_elt, 'configuration', name=self.config_name,
             description='%s config' % self.config_name)
-        settings_elt = etree.SubElement(configuration_elt, 'settings')
-        profiles_elt = etree.SubElement(settings_elt, 'profiles')
+        profiles_elt = etree.SubElement(configuration_elt, 'profiles')
 
-        database = self.model.controllers['connection'].model.database()
+        database = self.database
         
         # Create all profiles for current host.
         profiles_query = database.exec_(
-            'select id from ipypbxweb_sipprofile where connection_id = %i' %
-            self.parent.connection_id)
+            '''
+            select id, name, external_sip_ip, external_rtp_ip, sip_ip, rtp_ip,
+            sip_port, accept_blind_registration, authenticate_calls
+            from ipypbxweb_sipprofile where connection_id = %i
+            ''' % self.parent.connection_id)
         while profiles_query.next():
+            # Create profile element.
             profile_id, _ok = profiles_query.value(0).toInt()
-            profile_elt = etree.SubElement(profiles_elt, 'profile')
+            profile_elt = etree.SubElement(
+                profiles_elt, 'profile',
+                name=profiles_query.value(1).toString())
 
             # Create domains for current profile.
             domains_elt = etree.SubElement(profile_elt, 'domains')
-
             domains_query = database.exec_(
-                'select host_name from ipypbxweb_domain where profile_id = '
+                'select host_name from ipypbxweb_domain where sip_profile_id = '
                 '%i' % profile_id)
             while domains_query.next():
                 domain_elt = etree.SubElement(
-                    domains_elt, 'domain', name=domains_quey.value(0),
-                    alias='true', parse='true')
+                    domains_elt, 'domain',
+                    name=domains_query.value(0).toString(), alias='true',
+                    parse='true')
+
+
+            profile_sip_port, _ok = profiles_query.value(6).toInt()
 
             # Create settings for current profile.
             settings_elt = etree.SubElement(profile_elt, 'settings')
             params = (
                 ('dialplan', 'XML,enum'),
-                ('ext-sip-ip', profile.ext_sip_ip),
-                ('ext-rtp-ip', profile.ext_rtp_ip),
-                ('sip-ip', profile.sip_ip),
-                ('rtp-ip', profile.rtp_ip),
-                ('sip-port', profile.sip_port),
+                ('ext-sip-ip', profiles_query.value(2).toString()),
+                ('ext-rtp-ip', profiles_query.value(3).toString()),
+                ('sip-ip', profiles_query.value(4).toString()),
+                ('rtp-ip', profiles_query.value(5).toString()),
+                ('sip-port', profile_sip_port),
                 ('nonce-ttl', '60'),
                 ('rtp-timer-name', 'soft'),
                 ('codec-prefs', 'PCMU@20i'),
@@ -280,26 +322,118 @@ class SofiaConfGenerator(FreeswitchConfigGenerator):
                 ('rfc2833-pt', '1'),
                 ('dtmf-duration', '100'),
                 ('codec-ms', '20'),
-                ('accept-blind-reg', profile.accept_blind_registration),
-                ('auth-calls', profile.authenticate_calls))
-            self.add_params(settings_elt, params)
+                ('accept-blind-reg', profiles_query.value(7).toBool()),
+                ('auth-calls', profiles_query.value(8).toBool()))
+            self.addParams(settings_elt, params)
 
             # Create gateways for current profile.
-            gateways_elt = etree.SubElement(profile, 'gateways')
-            for gateway in self.parent.get_gateways_for_profile(profile):
-                gateway_elt = etree.SubElement(gateways_elt, 'gateway', name=gateway.name)
+            gateways_elt = etree.SubElement(profile_elt, 'gateways')
+            gateways_query = database.exec_(
+                '''
+                select name, username, realm, from_domain, password,
+                retry_in_seconds, expire_in_seconds, caller_id_in_from_field,
+                extension
+                from ipypbxweb_gateway where sip_profile_id = %i
+                '''  % profile_id)
+            while gateways_query.next():
+                # Create gateway element.
+                gateway_elt = etree.SubElement(
+                    gateways_elt, 'gateway', name=gateways_query.value(0).toString())
+                retry_seconds, _ok = gateways_query.value(5).toInt()
+                expire_seconds, _ok = gateways_query.value(6).toInt()
                 params = (
-                    ('username', gateway.username),
-                    ('realm', gateway.realm),
-                    ('from-domain', gateway.from_domain),
-                    ('password', gateway.password),
-                    ('retry-seconds', gateway.retry_seconds),
-                    ('expire-seconds', gateway.expire_seconds),
-                    ('caller-id-in-from', gateway.caller_id_in_from),
-                    ('extension', gateway.extension),
+                    ('username', gateways_query.value(1).toString()),
+                    ('realm', gateways_query.value(2).toString()),
+                    ('from-domain', gateways_query.value(3).toString()),
+                    ('password', gateways_query.value(4).toString()),
+                    ('retry-seconds', retry_seconds),
+                    ('expire-seconds', expire_seconds),
+                    ('caller-id-in-from', gateways_query.value(7).toBool()),
+                    ('extension', gateways_query.value(8).toString()),
                     # TODO: proxy, register
-                    ('expire-seconds', gateway.expire_seconds),
-                    ('retry-seconds', gateway.retry_seconds))
-                self.add_params(gateway_elt, params)
+                    )
+                self.addParams(gateway_elt, params)
 
         return root_elt    
+
+class DirectoryGenerator(FreeswitchConfigGenerator):
+    """
+    Generates user directory.
+    """
+    param_match = {'section': 'directory'}
+
+    def generateConfig(self, params):
+        #Get base elemenets.
+        root_elt, section_elt = self.baseELements
+
+        database = self.database
+
+        # Find profile id from params.
+        profile_query = database.exec_(
+            '''
+            select id from ipypbxweb_sipprofile
+            where name= '%s' and connection_id = %i limit 1
+            ''' % (params['profile'], self.parent.connection_id))
+
+        _ok = False
+        if profile_query.next():
+            profile_id, _ok = profile_query.value(0).toInt()
+
+        if not _ok:
+            # Matching SIP profile not found.
+            return
+        
+        # List all domains for this profile.        
+        domains_query = database.exec_(
+            '''
+            select id, host_name from ipypbxweb_domain
+            where sip_profile_id = %i
+            ''' % profile_id)
+
+        while domains_query.next():
+            domain_id, _ok = domains_query.value(0).toInt()
+
+            # Create domaim element.
+            domain_elt = etree.SubElement(
+                section_elt, 'domain', name=domains_query.value(1).toString())
+            
+            # TODO: add domain params section if we need it, i.e.:
+            #<params>
+            #     <param name="dial-string"
+            #            value="{presence_id=${dialed_user}@${dialed_domain}}$\
+            #                   {sofia_contact(${dialed_user}@${dialed_domain})}"/>
+            #</params>            
+
+            # For new we put all users into one group called default.
+            groups_elt = etree.SubElement(domain_elt, 'groups')
+            group_elt = etree.SubElement(groups_elt, 'group', name='default')
+
+            users_elt = etree.SubElement(group_elt, 'users')
+
+            users_query = database.exec_(
+                '''
+                select user_id, password from ipypbxweb_endpoint
+                where domain_id = %i
+                ''' % domain_id)
+
+            # Create user entries for all endpoints for this domain.
+            while users_query.next():
+                user_elt = etree.SubElement(
+                    users_elt, 'user', id=users_query.value(0).toString())
+
+                # Specify endpoint password.
+                params = (
+                    ('password', users_query.value(1).toString()),
+                    )
+                self.addParams(user_elt, params)
+
+        return root_elt
+
+
+class DialplanGenerator(FreeswitchConfigGenerator):
+    """
+    Generates XML dialplans.
+    """
+
+    param_match = {'section': 'dialplan'}
+