~ubuntu-branches/ubuntu/precise/pymsn/precise

« back to all changes in this revision

Viewing changes to pymsn/transport.py

  • Committer: Bazaar Package Importer
  • Author(s): Laurent Bigonville, Sjoerd Simons, Laurent Bigonville, Jonny Lamb
  • Date: 2008-01-17 18:23:14 UTC
  • mfrom: (1.1.2 upstream)
  • Revision ID: james.westby@ubuntu.com-20080117182314-lwymmpnk2ut3rvr1
Tags: 0.3.1-0ubuntu1
[ Sjoerd Simons ]
* debian/rules: remove dh_python, it's no longer needed

[ Laurent Bigonville ]
* New upstream release (0.3.1)
* debian/control:
  - Add myself as an Uploaders
  - Add python:Provides for binary package
  - Add python-ctypes and python-crypto to build-deps/deps
* debian/rules: remove binary-install rule
* Add watch file
* remove pycompat file, not needed anymore
* Modify Maintainer value to match the DebianMaintainerField
  specification.

[ Jonny Lamb ]
* Added python-adns to build-deps/deps.
* Added python-pyopenssl to build-deps/deps.
* Updated copyright.
* Upped Standards-Version to 3.7.3.
* Added "XS-Dm-Upload-Allowed: yes" under the request of Sjoerd Simons.
* Added myself to Uploaders.
* Added Homepage to control.
* Added Vcs-Bzr to control.

Show diffs side-by-side

added added

removed removed

Lines of Context:
20
20
# along with this program; if not, write to the Free Software
21
21
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
22
 
23
 
"""Transport
24
 
Protocol transport module.
25
 
 
26
 
This module contains classes used by the library to connect to the msn server
27
 
it includes for example, the direct connection transport as well as an http
28
 
polling transport ideal in firewalled environment."""
29
 
 
30
 
__version__ = "$Id$"
 
23
"""Network Transport Layer
 
24
 
 
25
This module provides an abstraction of the transport to be used to communicate
 
26
with the MSN servers, actually MSN servers can communicate either through
 
27
direct connection using TCP/1863 or using TCP/80 by tunelling the protocol
 
28
inside HTTP POST requests.
 
29
 
 
30
The classes of this module are structured as follow:
 
31
G{classtree BaseTransport}"""
 
32
 
 
33
import gnet
 
34
import gnet.protocol
 
35
import msnp
31
36
 
32
37
import logging
33
 
import base64
34
38
import gobject
35
39
 
36
 
import gio
37
 
import structure
38
 
 
39
 
from consts import ServerType
40
 
 
41
 
logger = logging.getLogger('Connection')
 
40
__all__=['ServerType', 'DirectConnection']
 
41
 
 
42
logger = logging.getLogger('Transport')
 
43
 
 
44
class ServerType(object):
 
45
    """"""
 
46
    SWITCHBOARD = 'SB'
 
47
    NOTIFICATION = 'NS'
 
48
 
 
49
TransportError = gnet.IoError
42
50
 
43
51
class BaseTransport(gobject.GObject):
44
 
    """Abstract Base Class that modelize a connection to an MSN service"""
 
52
    """Abstract Base Class that modelize a connection to the MSN service, this
 
53
    abstraction is used to build various transports that expose the same
 
54
    interface, basically a transport is created using its constructor then it
 
55
    simply emits signals when network events (or even abstracted network events)
 
56
    occur, for example a Transport that successfully connected to the MSN
 
57
    service will emit a connection-success signal, and when that transport
 
58
    received a meaningful message it would emit a command-received signal.
 
59
        
 
60
        @ivar server: the server being used to connect to
 
61
        @type server: tuple(host, port)
 
62
        
 
63
        @ivar server_type: the server that we are connecting to, either
 
64
            Notification or switchboard.
 
65
        @type server_type: L{ServerType}
 
66
 
 
67
        @ivar proxies: proxies that we can use to connect
 
68
        @type proxies: dict(type => L{gnet.proxy.ProxyInfos})
 
69
        
 
70
        @ivar transaction_id: the current transaction ID
 
71
        @type transaction_id: integer
 
72
 
 
73
 
 
74
        @cvar connection-failure: signal emitted when the connection fails
 
75
        @type connection-failure: ()
 
76
 
 
77
        @cvar connection-success: signal emitted when the connection succeed
 
78
        @type connection-success: ()
 
79
 
 
80
        @cvar connection-reset: signal emitted when the connection is being
 
81
        reset
 
82
        @type connection-reset: ()
 
83
 
 
84
        @cvar connection-lost: signal emitted when the connection was lost
 
85
        @type connection-lost: ()
 
86
 
 
87
        @cvar command-received: signal emitted when a command is received
 
88
        @type command-received: FIXME
 
89
 
 
90
        @cvar command-sent: signal emitted when a command was successfully
 
91
            transmitted to the server
 
92
        @type command-sent: FIXME
 
93
 
 
94
        @undocumented: __gsignals__"""
45
95
    
46
96
    __gsignals__ = {
47
97
            "connection-failure" : (gobject.SIGNAL_RUN_FIRST,
48
98
                gobject.TYPE_NONE,
49
 
                ()),
 
99
                (object,)),
50
100
 
51
101
            "connection-success" : (gobject.SIGNAL_RUN_FIRST,
52
102
                gobject.TYPE_NONE,
58
108
 
59
109
            "connection-lost" : (gobject.SIGNAL_RUN_FIRST,
60
110
                gobject.TYPE_NONE,
61
 
                ()),
 
111
                (object,)),
62
112
 
63
113
            "command-received": (gobject.SIGNAL_RUN_FIRST,
64
114
                gobject.TYPE_NONE,
77
127
 
78
128
            @param server_type: the server that we are connecting to, either
79
129
                Notification or switchboard.
80
 
            @type server_type: L{consts.ServerType}
 
130
            @type server_type: L{ServerType}
81
131
 
82
132
            @param proxies: proxies that we can use to connect
83
 
            @type proxies: {type: string => L{gio.network.ProxyInfos}}"""
 
133
            @type proxies: {type: string => L{gnet.network.ProxyInfos}}"""
84
134
        gobject.GObject.__init__(self)
85
135
        self.server = server
86
136
        self.server_type = server_type
87
137
        self.proxies = proxies
88
138
        self._transaction_id = 0
89
 
    
90
 
    def __get_transaction_id(self):
 
139
   
 
140
    @property
 
141
    def transaction_id(self):
91
142
        return self._transaction_id
92
 
    transaction_id = property(__get_transaction_id,
93
 
            doc="return the current transaction id")
94
143
 
95
144
    # Connection
96
145
    def establish_connection(self):
106
155
 
107
156
            @param server: when set, reset the connection and
108
157
                connect to this new server
109
 
            @type server: (host: string, port: integer)"""
 
158
            @type server: tuple(host, port)"""
110
159
        raise NotImplementedError
111
160
 
112
161
    # Command Sending
113
 
    def send_command(self, command, increment=True, callback=None, cb_args=()):
 
162
    def send_command(self, command, increment=True, callback=None, *cb_args):
114
163
        """
115
 
        Sends a L{structure.Command} to the server.
 
164
        Sends a L{msnp.Command} to the server.
116
165
 
117
166
            @param command: command to send
118
 
            @type command: L{structure.Command}
 
167
            @type command: L{msnp.Command}
119
168
 
120
169
            @param increment: if False, the transaction ID is not incremented
121
170
            @type increment: bool
125
174
            @type callback: callable
126
175
 
127
176
            @param cb_args: callback arguments
128
 
            @type cb_args: tuple
 
177
            @type cb_args: Any, ...
129
178
        """
130
179
        raise NotImplementedError
131
180
 
132
 
    def send_command_ex(self, command, arguments=None, payload=None,
133
 
            transaction_id=-1, increment=True, callback=None, cb_args=()):
 
181
    def send_command_ex(self, command, arguments=(), payload=None, 
 
182
            increment=True, callback=None, *cb_args):
134
183
        """
135
184
        Builds a command object then send it to the server.
136
185
        
154
203
            @param cb_args: callback arguments
155
204
            @type cb_args: tuple
156
205
        """
157
 
        if transaction_id is not None and transaction_id < 0:
158
 
            transaction_id = self._transaction_id
159
 
        cmd = structure.Command()
160
 
        cmd.build(command, transaction_id, arguments, payload)
161
 
        self.send_command(cmd, increment, callback, cb_args)
 
206
        cmd = msnp.Command()
 
207
        cmd.build(command, self._transaction_id, payload, *arguments)
 
208
        self.send_command(cmd, increment, callback, *cb_args)
 
209
        return cmd
 
210
 
 
211
    def enable_ping(self):
 
212
        pass
162
213
 
163
214
    def _increment_transaction_id(self):
164
215
        """Increments the Transaction ID then return it.
166
217
            @rtype: integer"""
167
218
        self._transaction_id += 1
168
219
        return self._transaction_id
169
 
 
170
220
gobject.type_register(BaseTransport)
171
221
 
172
222
 
173
223
class DirectConnection(BaseTransport):
174
 
    """Implements a direct connection to the net without any proxy""" 
 
224
    """Implements a direct connection to the net using TCP/1863""" 
175
225
 
176
226
    def __init__(self, server, server_type=ServerType.NOTIFICATION, proxies={}):
177
227
        BaseTransport.__init__(self, server, server_type, proxies)
178
228
        
179
 
        transport = gio.network.TCPClient(server[0], server[1])
 
229
        transport = gnet.io.TCPClient(server[0], server[1])
180
230
        transport.connect("notify::status", self.__on_status_change)
181
 
        transport.connect("error", lambda t, msg: self.emit("connection-failure"))
 
231
        transport.connect("error", self.__on_error)
182
232
 
183
 
        receiver = gio.ChunkReceiver(transport)
 
233
        receiver = gnet.parser.DelimiterParser(transport)
184
234
        receiver.connect("received", self.__on_received)
185
235
 
186
236
        self._receiver = receiver
188
238
        self._transport = transport
189
239
        self.__pending_chunk = None
190
240
        self.__resetting = False
 
241
        self.__error = False
 
242
        self.__png_timeout = None
191
243
        
192
244
    __init__.__doc__ = BaseTransport.__init__.__doc__
193
245
 
199
251
 
200
252
    def lose_connection(self):
201
253
        self._transport.close()
 
254
        if self.__png_timeout is not None:
 
255
            gobject.source_remove(self.__png_timeout)
 
256
            self.__png_timeout = None
202
257
 
203
258
    def reset_connection(self, server=None):
204
259
        if server:
206
261
            self._transport.set_property("port", server[1])
207
262
            self.server = server
208
263
        self.__resetting = True
 
264
        if self.__png_timeout is not None:
 
265
            gobject.source_remove(self.__png_timeout)
 
266
            self.__png_timeout = None
209
267
        self._transport.close()
210
268
        self._transport.open()
211
269
 
212
 
    def send_command(self, command, increment=True, callback=None, cb_args=()):
 
270
    def send_command(self, command, increment=True, callback=None, *cb_args):
213
271
        logger.debug('>>> ' + repr(command))
214
272
        our_cb_args = (command, callback, cb_args)
215
 
        self._transport.send(str(command), self.__on_command_sent, our_cb_args)
 
273
        self._transport.send(str(command), self.__on_command_sent, *our_cb_args)
216
274
        if increment:
217
275
            self._increment_transaction_id()
218
276
 
 
277
    def enable_ping(self):
 
278
        cmd = msnp.Command()
 
279
        cmd.build("PNG", None)
 
280
        self.send_command(cmd, False)
 
281
        self.__png_timeout = None
 
282
        return False
 
283
 
219
284
    def __on_command_sent(self, command, user_callback, user_cb_args):
220
285
        self.emit("command-sent", command)
221
 
 
222
286
        if user_callback:
223
287
            user_callback(*user_cb_args)
224
288
 
 
289
    def __handle_ping_reply(self, command):
 
290
        timeout = int(command.arguments[0])
 
291
        self.__png_timeout = gobject.timeout_add(timeout * 1000, self.enable_ping)
 
292
 
225
293
    ### callbacks
226
294
    def __on_status_change(self, transport, param):
227
295
        status = transport.get_property("status")
228
 
        if status == gio.STATUS_OPEN:
 
296
        if status == gnet.IoStatus.OPEN:
229
297
            if self.__resetting:
230
298
                self.emit("connection-reset")
231
299
                self.__resetting = False
232
300
            self.emit("connection-success")
233
 
        elif status == gio.STATUS_CLOSED:
234
 
            if not self.__resetting:
235
 
                self.emit("connection-lost")
 
301
        elif status == gnet.IoStatus.CLOSED:
 
302
            if not self.__resetting and not self.__error:
 
303
                self.emit("connection-lost", None)
 
304
            self.__error = False
 
305
    
 
306
    def __on_error(self, transport, reason):
 
307
        status = transport.get_property("status")
 
308
        self.__error = True
 
309
        if status == gnet.IoStatus.OPEN:
 
310
            self.emit("connection-lost", reason)
 
311
        else:
 
312
            self.emit("connection-failure", reason)
236
313
 
237
314
    def __on_received(self, receiver, chunk):
238
 
        cmd = structure.Command()
 
315
        cmd = msnp.Command()
239
316
        if self.__pending_chunk:
240
317
            chunk = self.__pending_chunk + "\r\n" + chunk
241
318
            cmd.parse(chunk)
243
320
            self._receiver.delimiter = "\r\n"
244
321
        else:
245
322
            cmd.parse(chunk)
246
 
            if cmd.name in structure.Command.PAYLOAD_COMMANDS:
247
 
                payload_len = int(cmd.arguments[-1])
 
323
            if cmd.name in msnp.Command.INCOMING_PAYLOAD or \
 
324
                    (cmd.is_error() and (cmd.arguments is not None) and len(cmd.arguments) > 0):
 
325
                try:
 
326
                    payload_len = int(cmd.arguments[-1])
 
327
                except:
 
328
                    payload_len = 0
248
329
                if payload_len > 0:
249
330
                    self.__pending_chunk = chunk
250
331
                    self._receiver.delimiter = payload_len
251
332
                    return
252
 
                
253
333
        logger.debug('<<< ' + repr(cmd))
254
 
        self.emit("command-received", cmd)
255
 
 
 
334
        if cmd.name == 'QNG':
 
335
            self.__handle_ping_reply(cmd)
 
336
        else:
 
337
            self.emit("command-received", cmd)
256
338
gobject.type_register(DirectConnection)
257
339
 
258
340
 
259
 
class HTTPPollTransportStatus(object):
260
 
    """Transport status."""
261
 
    
262
 
    OPEN = 0
263
 
    """The transport is open."""
264
 
    OPENING_REQUESTED = 1
265
 
    """The transport is being opened due to user request."""
266
 
    OPENING_AUTO = 2
267
 
    """The transport is being opened due to auto reconnection process."""
268
 
    LOST = 3
269
 
    """The transport lost his connection."""
270
 
    CLOSING = 4
271
 
    """The transport is being closed."""
272
 
    CLOSED = 5
273
 
    """The transport is closed."""
274
 
 
275
 
class HTTPPollTransactionStatus(object):
276
 
    """Data transfert status"""
277
 
    NONE = 0
278
 
    SENDING = 1
279
 
    RECEIVING = 2
280
 
    IGNORING = 3 # to deal with HTTP/1.1 100 Continue
281
 
 
282
341
class HTTPPollConnection(BaseTransport):
283
 
    """Implements an http connection to the net"""
284
 
 
285
 
    POLL_DELAY = 3000
286
 
 
 
342
    """Implements an HTTP polling transport, basically it encapsulates the MSNP
 
343
    commands into an HTTP request, and receive responses by polling a specific
 
344
    url"""
287
345
    def __init__(self, server, server_type=ServerType.NOTIFICATION, proxies={}):
288
 
        self._target_host = server[0]
 
346
        self._target_server = server
289
347
        server = ("gateway.messenger.hotmail.com", 80)
290
348
        BaseTransport.__init__(self, server, server_type, proxies)
291
 
        
292
 
        if 'http' in self.proxies:
293
 
            transport = gio.network.TCPClient(proxies['http'].host, proxies['http'].port)
294
 
        else:
295
 
            transport = gio.network.TCPClient(server[0], server[1])
296
 
        transport.connect("notify::status", self.__on_status_change)
297
 
        transport.connect("error", lambda t, msg: self.emit("connection-failure"))
298
 
    
299
 
        receiver = gio.ChunkReceiver(transport)
300
 
        receiver.connect("received", self.__on_received)
301
 
        
302
 
        self._receiver = receiver
303
 
        self._receiver.delimiter = "\r\n"
304
 
        self._transport = transport
305
 
 
 
349
        self._setup_transport()
 
350
        
306
351
        self._command_queue = []
307
 
 
308
 
        self._transport_status = HTTPPollTransportStatus.CLOSED
309
 
        self._transaction_status = HTTPPollTransactionStatus.NONE
310
 
        self._session_open = False          # used to send Action=open
311
 
        
312
 
        self.__clear_response_handler()
313
 
 
314
 
        self._poll_id = None
315
 
        self._disable_poll = True
316
 
        
317
 
    __init__.__doc__ = BaseTransport.__init__.__doc__
318
 
 
319
 
    ### public commands
 
352
        self._waiting_for_response = False # are we waiting for a response
 
353
        self._session_id = None
 
354
        self.__error = False
 
355
 
 
356
    def _setup_transport(self):
 
357
        server = self.server
 
358
        proxies = self.proxies
 
359
        if 'http' in proxies:
 
360
            transport = gnet.protocol.HTTP(server[0], server[1], proxies['http'])
 
361
        else:
 
362
            transport = gnet.protocol.HTTP(server[0], server[1])
 
363
        transport.connect("response-received", self.__on_received)
 
364
        transport.connect("request-sent", self.__on_sent)
 
365
        transport.connect("error", self.__on_error)
 
366
        self._transport = transport
 
367
 
320
368
    def establish_connection(self):
321
369
        logger.debug('<-> Connecting to %s:%d' % self.server)
322
 
        self._transport.open()
 
370
        self._polling_source_id = gobject.timeout_add(5000, self._poll)
 
371
        self.emit("connection-success")
323
372
 
324
373
    def lose_connection(self):
325
 
        self._transport_status = HTTPPollTransportStatus.CLOSING
326
 
        self._session_open = False
327
 
        self._transaction_status = HTTPPollTransactionStatus.NONE
328
 
        self._transport.close()
 
374
        gobject.source_remove(self._polling_source_id)
 
375
        del self._polling_source_id
 
376
        if not self.__error:
 
377
            self.emit("connection-lost", None)
 
378
        self.__error = False
329
379
 
330
 
    def reset_connection(self, server=None): #TODO: HTTPPollTransportStatus.RESETING ?
 
380
    def reset_connection(self, server=None):
331
381
        if server:
332
 
            self._transport.set_property("host", server[0])
333
 
            self._transport.set_property("port", server[1])
334
 
            self.server = server
335
 
        self._transport.close()
336
 
        self._session_open = False
337
 
        self._transaction_status = HTTPPollTransactionStatus.NONE
338
 
        self._transport.open()
339
 
 
340
 
    def send_command(self, command, increment=True, callback=None, cb_args=()):
341
 
        self.__queue_command(command, callback, cb_args)
 
382
            self._target_server = server
 
383
        self.emit("connection-reset")
 
384
 
 
385
    def send_command(self, command, increment=True, callback=None, *cb_args):
 
386
        self._command_queue.append((command, increment, callback, cb_args))
 
387
        self._send_command()
 
388
 
 
389
    def _send_command(self):
 
390
        if len(self._command_queue) == 0 or self._waiting_for_response:
 
391
            return
 
392
        command, increment = self._command_queue[0][0:2]
 
393
        resource = "/gateway/gateway.dll"
 
394
        headers = {
 
395
            "Accept": "*/*",
 
396
            "Accept-Language": "en-us",
 
397
            #"User-Agent": "MSMSGS",
 
398
            "Connection": "Keep-Alive",
 
399
            "Pragma": "no-cache",
 
400
            "Content-Type": "application/x-msn-messenger",
 
401
            "Proxy-Connection": "Keep-Alive"
 
402
        }
 
403
        
 
404
        str_command = str(command)
 
405
        if self._session_id is None:            
 
406
            resource += "?Action=open&Server=%s&IP=%s" % (self.server_type,
 
407
                    self._target_server[0])
 
408
        elif command == None:# Polling the server for queued messages
 
409
            resource += "?Action=poll&SessionID=%s" % self._session_id 
 
410
            str_command = ""
 
411
        else:
 
412
            resource += "?SessionID=%s" % self._session_id
 
413
 
 
414
        self._transport.request(resource, headers, str_command, "POST")
 
415
        self._waiting_for_response = True
 
416
        
 
417
        if command is not None:
 
418
            logger.debug('>>> ' + repr(command))
 
419
        
342
420
        if increment:
343
421
            self._increment_transaction_id()
344
 
 
345
 
    def __queue_command(self, command, callback, cb_args):
346
 
        self._command_queue.append((command, callback, cb_args))
347
 
        self.__process_command_queue()
348
 
 
349
 
    def __pop_command(self):
350
 
        return self._command_queue.pop(0)
 
422
        
 
423
    def _poll(self):
 
424
        if not self._waiting_for_response:
 
425
            self.send_command(None)
 
426
        return True
351
427
    
352
 
    def __send_command(self, command, callback=None, cb_args=()):
353
 
        logger.debug('>>> ' + repr(command))
354
 
        host = self.server[0]
355
 
        strcmd = str(command)
356
 
        
357
 
        if not self._session_open:
358
 
            params = "Action=open&Server=%s&IP=%s" % (self.server_type, self._target_host)
359
 
            self._session_open = True
360
 
        elif command == None: # Polling the server for queued messages
361
 
            assert(self._transaction_status == HTTPPollTransactionStatus.NONE)
362
 
            params = "Action=poll&SessionID=%s" % self.session_id
363
 
            strcmd = ""
364
 
        else: # new command
365
 
            assert(self._transaction_status == HTTPPollTransactionStatus.NONE)
366
 
            params = "SessionID=%s" % self.session_id
367
 
 
368
 
        action = "POST http://%s/gateway/gateway.dll?%s HTTP/1.1" % (host, params)
369
 
        headers = []
370
 
 
371
 
        headers.append("Accept: */*")
372
 
        headers.append("Accept-Language: en-us")
373
 
        headers.append("User-Agent: MSMSGS")
374
 
        headers.append("Host: " + host)
375
 
        headers.append("Proxy-Connection: Keep-Alive")
376
 
        headers.append("Connection: Keep-Alive")
377
 
        headers.append("Pragma: no-cache")
378
 
        headers.append("Content-Type: application/x-msn-messenger")
379
 
        headers.append("Content-Length: %d" % len(strcmd))
380
 
        if 'http' in self.proxies and self.proxies['http'].user:
381
 
                auth = base64.encodestring(self.proxies['http'].user + ':' + self.proxies['http'].password)
382
 
                headers.append("Proxy-authorization: Basic " + auth) 
383
 
 
384
 
        http_envelope = action + "\r\n" + "\r\n".join(headers) + "\r\n"
385
 
 
386
 
        self._transaction_status = HTTPPollTransactionStatus.SENDING
387
 
        command_sent_cb_args = (command, callback, cb_args)
388
 
        self._transport.send(http_envelope + "\r\n" + strcmd,
389
 
                self.__on_command_sent, command_sent_cb_args)
390
 
        blob = http_envelope + "\r\n" + strcmd
391
 
        
392
 
        #for line in blob.split('\r\n'):
393
 
        #    print '\t>>> ', line
394
 
        #print '----------------------------------------------------------'
395
 
 
396
 
    def __process_command_queue(self):
397
 
        s = HTTPPollTransportStatus
398
 
        if self._transport_status == s.OPEN:
399
 
            if self._transaction_status != HTTPPollTransactionStatus.NONE:
400
 
                return
401
 
 
402
 
            if len(self._command_queue) > 0:
403
 
                cmd = self.__pop_command()
404
 
                self.__send_command(*cmd)
405
 
            elif not self._disable_poll:
406
 
                self.__send_command(None) # Poll (WARN: don't use self.__poll())
407
 
        elif self._transport_status == s.CLOSED or \
408
 
                self._transport_status == s.LOST:
409
 
            self.establish_connection()
410
 
 
411
 
    def __poll(self):
 
428
    def __on_error(self, transport, reason):
 
429
        self.__error = True
 
430
        self.emit("connection-lost", reason)
 
431
        
 
432
    def __on_received(self, transport, http_response):
 
433
        if http_response.status == 403:
 
434
            self.emit("connection-lost", TransportError.PROXY_FORBIDDEN)
 
435
            self.lose_connection()
 
436
        if 'X-MSN-Messenger' in http_response.headers:
 
437
            data = http_response.headers['X-MSN-Messenger'].split(";")
 
438
            for elem in data:
 
439
                key, value =  [p.strip() for p in elem.split('=', 1)]
 
440
                if key == 'SessionID':
 
441
                    self._session_id = value
 
442
                elif key == 'GW-IP':
 
443
                    self.server = (value, self.server[1])
 
444
                    self._setup_transport()
 
445
                elif key == 'Session'and value == 'close':
 
446
                    #self.lose_connection()
 
447
                    pass
 
448
        
 
449
        self._waiting_for_response = False
 
450
 
 
451
        commands = http_response.body
 
452
        while len(commands) != 0:
 
453
            commands = self.__extract_command(commands)
 
454
        
 
455
        self._send_command()
 
456
 
 
457
    def __on_sent(self, transport, http_request):
412
458
        if len(self._command_queue) == 0:
413
 
            self.__process_command_queue()
414
 
        return True
415
 
 
416
 
    ### callbacks
417
 
    def __on_command_sent(self, command, user_callback, user_cb_args):
418
 
        self.emit("command-sent", command)
419
 
        if user_callback:
420
 
            user_callback(*user_cb_args)
421
 
 
422
 
    def __on_status_change(self, transport, param):
423
 
        status = transport.get_property("status")
424
 
        s = HTTPPollTransportStatus
425
 
        if status == gio.STATUS_OPEN:
426
 
            if self._transport_status == s.OPENING_REQUESTED:
427
 
                self._transport_status = s.OPEN
428
 
                self.emit("connection-success")
429
 
                self._poll_id = gobject.timeout_add(self.POLL_DELAY, self.__poll)
430
 
            elif self._transport_status == s.OPENING_AUTO:
431
 
                self._transport_status = s.OPEN
432
 
                self.__process_command_queue()
433
 
 
434
 
        elif status == gio.STATUS_OPENING:
435
 
            if self._transport_status == s.CLOSED:
436
 
                self._transport_status = s.OPENING_REQUESTED
437
 
            elif self._transport_status == s.LOST:
438
 
                self._transport_status = s.OPENING_AUTO
439
 
 
440
 
        elif status == gio.STATUS_CLOSED:
441
 
            if self._transport_status == s.OPEN:
442
 
                self._transport_status = s.LOST
443
 
            elif self._transport_status == s.CLOSING:
444
 
                self._transport_status = s.CLOSED
445
 
                self.emit("connection-lost")
446
 
                self._disable_poll = True
447
 
                gobject.source_remove(self._poll_id)
448
 
 
449
 
    def __on_received(self, receiver, chunk):
450
 
        #print '\t<<< ', chunk
451
 
        if self._transaction_status == HTTPPollTransactionStatus.SENDING and \
452
 
                chunk[:4] == 'HTTP': # HTTP status line
453
 
            chunk = chunk.split()
454
 
            if chunk[1] == '100':
455
 
                self._transaction_status = HTTPPollTransactionStatus.IGNORING
456
 
            elif chunk[1] == '200':
457
 
                self._transaction_status = HTTPPollTransactionStatus.RECEIVING
458
 
            else:
459
 
                self.emit("connection-failure")
460
 
                self.lose_connection()
461
 
        elif not self.http_body_pending: # headers
462
 
            if len(chunk) != 0:
463
 
                header, value =  [p.strip() for p in chunk.split(':', 1)]
464
 
                if header == 'Content-Length':
465
 
                    self.content_length = int(value)
466
 
                elif header == 'X-MSN-Messenger':
467
 
                    for elem in value.split(';'):
468
 
                        key, val =  [p.strip() for p in elem.split('=', 1)]
469
 
                        if key == 'SessionID':
470
 
                            self.session_id = val
471
 
                        elif key == 'GW-IP':
472
 
                            self.server = (val, self.server[1])
473
 
                            #if 'http' not in self.proxies: # TODO: shall we reset the connection ?
474
 
                            #    self._transport.set_property("host", val)
475
 
                        elif key == 'Session'and val == 'close':
476
 
                            self.emit("connection-lost")
477
 
                            self.lose_connection()
478
 
            else: # empty line separating headers from body
479
 
                if self._transaction_status == HTTPPollTransactionStatus.IGNORING:
480
 
                    self._transaction_status = HTTPPollTransactionStatus.SENDING
481
 
                else:
482
 
                    if self.content_length > 0:
483
 
                        self.http_body_pending = True
484
 
                        self._receiver.delimiter = self.content_length
485
 
                    else:
486
 
                        self.__clear_response_handler()
487
 
        else: # HTTP body
488
 
            self.data = chunk
489
 
            if self.content_length == 0:
490
 
                # The message was an empty response to a poll,
491
 
                # there is nothing to retrieve from the server
492
 
                pass
493
 
            elif len(self.data) != 0:
494
 
                while len(self.data) != 0:
495
 
                    self.data = self.__extract_command(self.data)
496
 
                    
497
 
            # We got a complete response from the server, we can
498
 
            # send the next command or reconnect to the server
499
 
            # if necessary
500
 
            self.__clear_response_handler()
501
 
            self.__process_command_queue()
 
459
            return
 
460
        command, increment, callback, cb_args = self._command_queue.pop(0)
 
461
        if command is not None:
 
462
            if callback:
 
463
                callback(*cb_args)
 
464
            self.emit("command-sent", command)
502
465
 
503
466
    def __extract_command(self, data):
504
467
        first, rest = data.split('\r\n', 1)
505
 
        cmd = structure.Command()
 
468
        cmd = msnp.Command()
506
469
        cmd.parse(first.strip())
507
 
        if cmd.name == 'USR' and cmd.arguments[0] == 'OK':
508
 
            self._disable_poll = False
509
 
        if cmd.name in structure.Command.PAYLOAD_COMMANDS:
510
 
            payload_len = int(cmd.arguments[-1])
 
470
        if cmd.name in msnp.Command.INCOMING_PAYLOAD or \
 
471
                (cmd.is_error() and (cmd.arguments is not None) and len(cmd.arguments) > 0):
 
472
            try:
 
473
                payload_len = int(cmd.arguments[-1])
 
474
            except:
 
475
                payload_len = 0
511
476
            if payload_len > 0:
512
477
                cmd.payload = rest[:payload_len].strip()
513
478
            logger.debug('<<< ' + repr(cmd))
518
483
            self.emit("command-received", cmd)
519
484
            return rest
520
485
 
521
 
    def __clear_response_handler(self):
522
 
        self.http_body_pending = False
523
 
        self.data = ''
524
 
        self.content_length = 0
525
 
        self._receiver.delimiter = '\r\n'
526
 
        self._transaction_status = HTTPPollTransactionStatus.NONE
527
 
 
528
 
gobject.type_register(HTTPPollConnection)
 
486
 
 
487