]>
code.delx.au - pymsnt/blob - src/legacy/msn/msnft.py
5bb0eaa65e1fd6cda6a275966545818934acb615
1 # Copyright 2005 James Bunton <james@delx.cjb.net>
2 # Licensed for distribution under the GPL version 2, check COPYING for details
4 from twisted
.internet
import reactor
5 from twisted
.internet
.defer
import Deferred
6 from twisted
.protocols
.basic
import LineReceiver
7 from twisted
.internet
.protocol
import ClientFactory
8 from twisted
.python
import log
10 from msn
import checkParamLen
, MSNMessage
12 from debug
import LogEvent
, INFO
, WARN
, ERROR
17 MAXAUTHCOOKIE
= 2**32-1
20 class MSNFTReceive_Base
:
22 def __init__(self
, filename
, filesize
, userHandle
):
27 self
.filename
, self
.filesize
, self
.userHandle
= filename
, filesize
, userHandle
32 def accept(self
, yes
=True):
35 def writeTo(self
, obj
):
37 for data
in self
.buffer:
38 self
.consumer
.write(data
)
47 def write(self
, data
):
49 self
.consumer
.write(data
)
51 self
.buffer.append(data
)
59 def gotError(self
, ignored
=None):
69 lowPort
= int(config
.ftLowPort
)
70 highPort
= int(config
.ftHighPort
)
72 LogEvent(ERROR
, "", "Invalid values for ftLowPort & ftHighPort. Using 6891 & 6899 respectively")
75 self
.ports
= [lowPort
+x
for x
in xrange(highPort
-lowPort
)]
76 self
.portFree
= [True] * len(self
.ports
)
78 def requestPort(self
):
79 for i
in xrange(len(self
.ports
)):
81 self
.portFree
[i
] = False
82 LogEvent(INFO
, "", "Reserved a port")
84 LogEvent(INFO
, "", "Out of ports")
86 def freePort(self
, port
):
87 if self
.ports
.count(port
) > 0:
88 self
.portFree
[self
.ports
.index(port
)] = True;
91 msnports
= MSNFTP_Ports()
93 class MSNFTP_Receive(ClientFactory
, MSNFTReceive_Base
):
94 def __init__(self
, filename
, filesize
, userHandle
, iCookie
, connectivity
, switchboard
):
95 MSNFTReceive_Base
.__init
__(self
, filename
, filesize
, userHandle
)
96 self
.iCookie
= iCookie
97 self
.switchboard
= switchboard
98 self
.serverSocket
= None
100 self
.authCookie
= str(random
.randint(1, MAXAUTHCOOKIE
))
103 LogEvent(INFO
, self
.switchboard
.userHandle
)
106 if self
.serverSocket
:
107 self
.serverSocket
.stopListening()
108 if self
.timeout
and not self
.timeout
.called
:
109 self
.timeout
.cancel()
112 msnports
.freePort(self
.port
)
117 LogEvent(INFO
, self
.switchboard
.userHandle
)
119 def accept(self
, yes
=True):
120 LogEvent(INFO
, self
.switchboard
.userHandle
)
122 self
.port
= msnports
.requestPort()
126 LogEvent(INFO
, self
.switchboard
.userHandle
)
128 from msn
import MSNMessage
130 m
.setHeader('Content-Type', 'text/x-msmsgsinvite; charset=UTF-8')
132 m
.message
+= 'IP-Address: %s\r\n' % str(config
.ip
)
133 m
.message
+= 'Port: %s\r\n' % str(self
.port
)
134 m
.message
+= 'AuthCookie: %s\r\n' % self
.authCookie
135 m
.message
+= 'Sender-Connect: TRUE\r\n'
136 m
.message
+= 'Invitation-Command: ACCEPT\r\n'
137 m
.message
+= 'Invitation-Cookie: %s\r\n' % str(self
.iCookie
)
139 m
.message
+= 'Invitation-Command: CANCEL\r\n'
140 m
.message
+= 'Cancel-Code: REJECT\r\n'
141 m
.message
+= 'Launch-Application: FALSE\r\n'
142 m
.message
+= 'Request-Data: IP-Address:\r\n'
144 m
.ack
= m
.MESSAGE_ACK_NONE
145 self
.switchboard
.sendMessage(m
)
149 self
.serverSocket
= reactor
.listenTCP(self
.port
, self
)
150 self
.timeout
= reactor
.callLater(20, self
.gotError
)
153 def buildProtocol(self
, addr
):
154 LogEvent(INFO
, self
.switchboard
.userHandle
)
155 self
.serverSocket
.stopListening()
156 self
.serverSocket
= None
157 self
.timeout
.cancel()
159 self
.d
.callback(None)
161 return MSNFTP_FileReceive(self
.authCookie
, self
.switchboard
.userHandle
, self
)
164 class MSNFTP_FileReceive(LineReceiver
):
166 This class provides support for receiving files from contacts.
168 @ivar fileSize: the size of the receiving file. (you will have to set this)
169 @ivar connected: true if a connection has been established.
170 @ivar completed: true if the transfer is complete.
171 @ivar bytesReceived: number of bytes (of the file) received.
172 This does not include header data.
175 def __init__(self
, auth
, myUserHandle
, file, directory
="", overwrite
=0):
177 @param auth: auth string received in the file invitation.
178 @param myUserHandle: your userhandle.
179 @param file: A string or file object represnting the file
181 @param directory: optional parameter specifiying the directory.
182 Defaults to the current directory.
183 @param overwrite: if true and a file of the same name exists on
184 your system, it will be overwritten. (0 by default)
187 self
.myUserHandle
= myUserHandle
191 self
.directory
= directory
192 self
.bytesReceived
= 0
193 self
.overwrite
= overwrite
195 # used for handling current received state
196 self
.state
= 'CONNECTING'
197 self
.segmentLength
= 0
200 if isinstance(file, str):
201 path
= os
.path
.join(directory
, file)
202 if os
.path
.exists(path
) and not self
.overwrite
:
203 log
.msg('File already exists...')
204 raise IOError, "File Exists" # is this all we should do here?
205 self
.file = open(os
.path
.join(directory
, file), 'wb')
209 def connectionMade(self
):
211 self
.state
= 'INHEADER'
212 self
.sendLine('VER MSNFTP')
214 def connectionLost(self
, reason
):
218 def parseHeader(self
, header
):
219 """ parse the header of each 'message' to obtain the segment length """
221 if ord(header
[0]) != 0: # they requested that we close the connection
222 self
.transport
.loseConnection()
225 extra
, factor
= header
[1:]
227 # munged header, ending transfer
228 self
.transport
.loseConnection()
232 return factor
* 256 + extra
234 def sendLine(self
, line
):
235 log
.msg("SENDING LINE!!! " + line
)
236 LineReceiver
.sendLine(self
, line
)
238 def lineReceived(self
, line
):
239 temp
= line
.split(' ')
240 if len(temp
) == 1: params
= []
241 else: params
= temp
[1:]
243 log
.msg("GOT A LINE!!! " + line
)
244 handler
= getattr(self
, "handle_%s" % cmd
.upper(), None)
245 if handler
: handler(params
) # try/except
246 else: self
.handle_UNKNOWN(cmd
, params
)
248 def rawDataReceived(self
, data
):
249 bufferLen
= len(self
.buffer)
250 log
.msg("RAW DATA: " + data
)
251 if self
.state
== 'INHEADER':
253 self
.buffer += data
[:delim
]
254 if len(self
.buffer) == 3:
255 self
.segmentLength
= self
.parseHeader(self
.buffer)
256 if not self
.segmentLength
: return # hrm
258 self
.state
= 'INSEGMENT'
260 if len(extra
) > 0: self
.rawDataReceived(extra
)
263 elif self
.state
== 'INSEGMENT':
264 dataSeg
= data
[:(self
.segmentLength
-bufferLen
)]
265 self
.buffer += dataSeg
266 self
.bytesReceived
+= len(dataSeg
)
267 if len(self
.buffer) == self
.segmentLength
:
268 self
.gotSegment(self
.buffer)
270 if self
.bytesReceived
== self
.fileSize
:
274 self
.sendLine("BYE 16777989")
276 self
.state
= 'INHEADER'
277 extra
= data
[(self
.segmentLength
-bufferLen
):]
278 if len(extra
) > 0: self
.rawDataReceived(extra
)
281 def handle_VER(self
, params
):
282 checkParamLen(len(params
), 1, 'VER')
283 if params
[0].upper() == "MSNFTP":
284 self
.sendLine("USR %s %s" % (self
.myUserHandle
, self
.auth
))
286 log
.msg('they sent the wrong version, time to quit this transfer')
287 self
.transport
.loseConnection()
289 def handle_FIL(self
, params
):
290 checkParamLen(len(params
), 1, 'FIL')
292 self
.fileSize
= int(params
[0])
293 except ValueError: # they sent the wrong file size - probably want to log this
294 self
.transport
.loseConnection()
299 def handle_UNKNOWN(self
, cmd
, params
):
300 log
.msg('received unknown command (%s), params: %s' % (cmd
, params
))
302 def gotSegment(self
, data
):
303 """ called when a segment (block) of data arrives. """
304 self
.file.write(data
)
307 class MSNFTP_FileSend(LineReceiver
):
309 This class provides support for sending files to other contacts.
311 @ivar bytesSent: the number of bytes that have currently been sent.
312 @ivar completed: true if the send has completed.
313 @ivar connected: true if a connection has been established.
314 @ivar targetUser: the target user (contact).
315 @ivar segmentSize: the segment (block) size.
316 @ivar auth: the auth cookie (number) to use when sending the
320 def __init__(self
, file):
322 @param file: A string or file object represnting the file to send.
325 if isinstance(file, str) or isinstance(file, unicode):
326 self
.file = open(file, 'rb')
334 self
.targetUser
= None
335 self
.segmentSize
= 2045
336 self
.auth
= random
.randint(0, 2**30)
337 self
._pendingSend
= None # :(
339 def connectionMade(self
):
342 def connectionLost(self
, reason
):
343 if self
._pendingSend
:
344 self
._pendingSend
.cancel()
345 self
._pendingSend
= None
349 def lineReceived(self
, line
):
350 temp
= line
.split(' ')
351 if len(temp
) == 1: params
= []
352 else: params
= temp
[1:]
354 handler
= getattr(self
, "handle_%s" % cmd
.upper(), None)
355 if handler
: handler(params
)
356 else: self
.handle_UNKNOWN(cmd
, params
)
358 def handle_VER(self
, params
):
359 checkParamLen(len(params
), 1, 'VER')
360 if params
[0].upper() == "MSNFTP":
361 self
.sendLine("VER MSNFTP")
362 else: # they sent some weird version during negotiation, i'm quitting.
363 self
.transport
.loseConnection()
365 def handle_USR(self
, params
):
366 checkParamLen(len(params
), 2, 'USR')
367 self
.targetUser
= params
[0]
368 if self
.auth
== int(params
[1]):
369 self
.sendLine("FIL %s" % (self
.fileSize
))
370 else: # they failed the auth test, disconnecting.
371 self
.transport
.loseConnection()
373 def handle_TFR(self
, params
):
374 checkParamLen(len(params
), 0, 'TFR')
375 # they are ready for me to start sending
378 def handle_BYE(self
, params
):
379 self
.completed
= (self
.bytesSent
== self
.fileSize
)
380 self
.transport
.loseConnection()
382 def handle_CCL(self
, params
):
383 self
.completed
= (self
.bytesSent
== self
.fileSize
)
384 self
.transport
.loseConnection()
386 def handle_UNKNOWN(self
, cmd
, params
): log
.msg('received unknown command (%s), params: %s' % (cmd
, params
))
388 def makeHeader(self
, size
):
389 """ make the appropriate header given a specific segment size. """
390 quotient
, remainder
= divmod(size
, 256)
391 return chr(0) + chr(remainder
) + chr(quotient
)
394 """ send a segment of data """
395 if not self
.connected
:
396 self
._pendingSend
= None
397 return # may be buggy (if handle_CCL/BYE is called but self.connected is still 1)
398 data
= self
.file.read(self
.segmentSize
)
401 header
= self
.makeHeader(dataSize
)
402 self
.transport
.write(header
+ data
)
403 self
.bytesSent
+= dataSize
404 self
._pendingSend
= reactor
.callLater(0, self
.sendPart
)
406 self
._pendingSend
= None