Package mms :: Module mms_pdu
[hide private]
[frames] | no frames]

Source Code for Module mms.mms_pdu

  1  #!/usr/bin/env python 
  2  # 
  3  # This library is free software, distributed under the terms of 
  4  # the GNU Lesser General Public License Version 2. 
  5  # See the COPYING file included in this archive 
  6  # 
  7  # The docstrings in this module contain epytext markup; API documentation 
  8  # may be created by processing this file with epydoc: http://epydoc.sf.net 
  9   
 10  """ MMS Data Unit structure encoding and decoding classes """ 
 11   
 12  import os, array 
 13  import wsp_pdu 
 14  import message 
 15  from iterator import PreviewIterator 
 16   
17 -class MMSEncodingAssignments:
18 fieldNames = {0x01 : ('Bcc', 'EncodedStringValue'), 19 0x02 : ('Cc', 'EncodedStringValue'), 20 0x03 : ('Content-Location', 'UriValue'), 21 0x04 : ('Content-Type','ContentTypeValue'), 22 0x05 : ('Date', 'DateValue'), 23 0x06 : ('Delivery-Report', 'BooleanValue'), 24 0x07 : ('Delivery-Time', None), 25 0x08 : ('Expiry', 'ExpiryValue'), 26 0x09 : ('From', 'FromValue'), 27 0x0a : ('Message-Class', 'MessageClassValue'), 28 0x0b : ('Message-ID', 'TextString'), 29 0x0c : ('Message-Type', 'MessageTypeValue'), 30 0x0d : ('MMS-Version', 'VersionValue'), 31 0x0e : ('Message-Size', 'LongInteger'), 32 0x0f : ('Priority', 'PriorityValue'), 33 0x10 : ('Read-Reply', 'BooleanValue'), 34 0x11 : ('Report-Allowed', 'BooleanValue'), 35 0x12 : ('Response-Status', 'ResponseStatusValue'), 36 0x13 : ('Response-Text', 'EncodedStringValue'), 37 0x14 : ('Sender-Visibility', 'SenderVisibilityValue'), 38 0x15 : ('Status', 'StatusValue'), 39 0x16 : ('Subject', 'EncodedStringValue'), 40 0x17 : ('To', 'EncodedStringValue'), 41 0x18 : ('Transaction-Id', 'TextString')}
42 43
44 -class MMSDecoder(wsp_pdu.Decoder):
45 """ A decoder for MMS messages """
46 - def __init__(self, filename=None):
47 """ @param filename: If specified, decode the content of the MMS 48 message file with this name 49 @type filename: str 50 """ 51 self._mmsData = array.array('B') 52 self._mmsMessage = message.MMSMessage() 53 self._parts = []
54
55 - def decodeFile(self, filename):
56 """ Load the data contained in the specified file, and decode it. 57 58 @param filename: The name of the MMS message file to open 59 @type filename: str 60 61 @raises OSError: The filename is invalid 62 63 @return: The decoded MMS data 64 @rtype: MMSMessage 65 """ 66 nBytes = os.stat(filename)[6] 67 data = array.array('B') 68 f = open(filename, 'rb') 69 data.fromfile(f, nBytes) 70 f.close() 71 return self.decodeData(data)
72
73 - def decodeData(self, data):
74 """ Decode the specified MMS message data 75 76 @param data: The MMS message data to decode 77 @type data: array.array('B') 78 79 @return: The decoded MMS data 80 @rtype: MMSMessage 81 """ 82 self._mmsMessage = message.MMSMessage() 83 self._mmsData = data 84 bodyIter = self.decodeMessageHeader() 85 self.decodeMessageBody(bodyIter) 86 return self._mmsMessage
87
88 - def decodeMessageHeader(self):
89 """ Decodes the (full) MMS header data 90 91 @note: This B{must} be called before C{_decodeBody()}, as it sets 92 certain internal variables relating to data lengths, etc. 93 """ 94 dataIter = PreviewIterator(self._mmsData) 95 96 # First 3 headers (in order 97 ############################ 98 # - X-Mms-Message-Type 99 # - X-Mms-Transaction-ID 100 # - X-Mms-Version 101 # TODO: reimplement strictness - currently we allow these 3 headers 102 # to be mixed with any of the other headers (this allows the 103 # decoding of "broken" MMSs, but is technically incorrect 104 105 # Misc headers 106 ############## 107 # The next few headers will not be in a specific order, except for 108 # "Content-Type", which should be the last header 109 # According to [4], MMS header field names will be short integers 110 contentTypeFound = False 111 while contentTypeFound == False: 112 header, value = self.decodeHeader(dataIter) 113 if header == MMSEncodingAssignments.fieldNames[0x04][0]: 114 contentTypeFound = True 115 else: 116 self._mmsMessage.headers[header] = value 117 #print '%s: %s' % (header, str(value)) 118 119 cType = value[0] 120 #print '%s: %s' % (header, cType) 121 params = value[1] 122 #for parameter in params: 123 # print ' %s: %s' % (parameter, str(params[parameter])) 124 125 self._mmsMessage.headers[header] = (cType, params) 126 return dataIter
127 128
129 - def decodeMessageBody(self, dataIter):
130 """ Decodes the MMS message body 131 132 @param dataIter: an iterator over the sequence of bytes of the MMS 133 body 134 @type dataIter: iter 135 """ 136 ######### MMS body: headers ########### 137 # Get the number of data parts in the MMS body 138 nEntries = self.decodeUintvar(dataIter) 139 #print 'Number of data entries (parts) in MMS body:', nEntries 140 141 ########## MMS body: entries ########## 142 # For every data "part", we have to read the following sequence: 143 # <length of content-type + other possible headers>, 144 # <length of data>, 145 # <content-type + other possible headers>, 146 # <data> 147 for partNum in range(nEntries): 148 #print '\nPart %d:\n------' % partNum 149 headersLen = self.decodeUintvar(dataIter) 150 dataLen = self.decodeUintvar(dataIter) 151 152 # Prepare to read content-type + other possible headers 153 ctFieldBytes = [] 154 for i in range(headersLen): 155 ctFieldBytes.append(dataIter.next()) 156 # ctIter = iter(ctFieldBytes) 157 ctIter = PreviewIterator(ctFieldBytes) 158 # Get content type 159 contentType, ctParameters = self.decodeContentTypeValue(ctIter) 160 headers = {'Content-Type' : (contentType, ctParameters)} 161 #print 'Content-Type:', contentType 162 #for param in ctParameters: 163 # print ' %s: %s' % (param, str(ctParameters[param])) 164 165 # Now read other possible headers until <headersLen> bytes have been read 166 while True: 167 try: 168 hdr, value = self.decodeHeader(ctIter) 169 headers[hdr] = value 170 #print '%s: %s' % (otherHeader, otherValue) 171 except StopIteration: 172 break 173 #print 'Data length:', dataLen, 'bytes' 174 175 # Data (note: this is not null-terminated) 176 data = array.array('B') 177 for i in range(dataLen): 178 data.append(dataIter.next()) 179 180 part = message.DataPart() 181 part.setData(data, contentType) 182 part.contentTypeParameters = ctParameters 183 part.headers = headers 184 self._mmsMessage.addDataPart(part)
185 186 #extension = 'dump' 187 #if contentType == 'image/jpeg': 188 # extension = 'jpg' 189 #if contentType == 'image/gif': 190 # extension = 'gif' 191 #elif contentType == 'audio/wav': 192 # extension = 'wav' 193 #elif contentType == 'audio/midi': 194 # extension = 'mid' 195 #elif contentType == 'text/plain': 196 # extension = 'txt' 197 #elif contentType == 'application/smil': 198 # extension = 'smil' 199 200 #f = open('part%d.%s' % (partNum, extension), 'wb') 201 #data.tofile(f) 202 #f.close() 203 204 205 @staticmethod
206 - def decodeHeader(byteIter):
207 """ Decodes a header entry from an MMS message, starting at the byte 208 pointed to by C{byteIter.next()} 209 210 From [4], section 7.1: 211 C{Header = MMS-header | Application-header} 212 213 @raise DecodeError: This uses C{decodeMMSHeader()} and 214 C{decodeApplicationHeader()}, and will raise this 215 exception under the same circumstances as 216 C{decodeApplicationHeader()}. C{byteIter} will 217 not be modified in this case. 218 219 @note: The return type of the "header value" depends on the header 220 itself; it is thus up to the function calling this to determine 221 what that type is (or at least compensate for possibly 222 different return value types). 223 224 @return: The decoded header entry from the MMS, in the format: 225 (<str:header name>, <str/int/float:header value>) 226 @rtype: tuple 227 """ 228 header = '' 229 value = '' 230 try: 231 header, value = MMSDecoder.decodeMMSHeader(byteIter) 232 except wsp_pdu.DecodeError: 233 header, value = wsp_pdu.Decoder.decodeHeader(byteIter) #MMSDecoder.decodeApplicationHeader(byteIter) 234 return (header, value)
235 236 @staticmethod
237 - def decodeMMSHeader(byteIter):
238 """ From [4], section 7.1: 239 MMS-header = MMS-field-name MMS-value 240 MMS-field-name = Short-integer 241 MMS-value = Bcc-value | Cc-value | Content-location-value | Content-type-value | etc 242 243 This method takes into account the assigned number values for MMS 244 field names, as specified in [4], section 7.3, table 8. 245 246 @raise wsp_pdu.DecodeError: The MMS field name could not be parsed. 247 C{byteIter} will not be modified in this case. 248 249 @return: The decoded MMS header, in the format: 250 (<str:MMS-field-name>, <str:MMS-value>) 251 @rtype: tuple 252 """ 253 # Get the MMS-field-name 254 mmsFieldName = '' 255 byte = wsp_pdu.Decoder.decodeShortIntegerFromByte(byteIter.preview()) 256 #byte = wsp_pdu.Decoder.decodeShortInteger(byteIter) 257 if byte in MMSEncodingAssignments.fieldNames: 258 byteIter.next() 259 mmsFieldName = MMSEncodingAssignments.fieldNames[byte][0] 260 # byteIter.next() 261 else: 262 byteIter.resetPreview() 263 raise wsp_pdu.DecodeError, 'Invalid MMS Header: could not decode MMS field name' 264 # Now get the MMS-value 265 mmsValue = '' 266 try: 267 exec 'mmsValue = MMSDecoder.decode%s(byteIter)' % MMSEncodingAssignments.fieldNames[byte][1] 268 except wsp_pdu.DecodeError, msg: 269 raise wsp_pdu.DecodeError, 'Invalid MMS Header: Could not decode MMS-value: %s' % msg 270 except: 271 print 'A fatal error occurred, probably due to an unimplemented decoding operation. Tried to decode header: %s' % mmsFieldName 272 raise 273 return (mmsFieldName, mmsValue)
274 275 @staticmethod
276 - def decodeEncodedStringValue(byteIter):
277 """ From [4], section 7.2.9: 278 C{Encoded-string-value = Text-string | Value-length Char-set Text-string} 279 The Char-set values are registered by IANA as MIBEnum value. 280 281 @note: This function is not fully implemented, in that it does not 282 have proper support for the Char-set values; it basically just 283 reads over that sequence of bytes, and ignores it (see code for 284 details) - any help with this will be greatly appreciated. 285 286 @return: The decoded text string 287 @rtype: str 288 """ 289 decodedString = '' 290 try: 291 # First try "Value-length Char-set Text-string" 292 valueLength = wsp_pdu.Decoder.decodeValueLength(byteIter) 293 #TODO: *probably* have to include proper support for charsets... 294 try: 295 charSetValue = wsp_pdu.Decoder.decodeWellKnownCharset(byteIter) 296 except wsp_pdu.DecodeError, msg: 297 raise Exception, 'EncodedStringValue decoding error: Could not decode Char-set value; %s' % msg 298 decodedString = wsp_pdu.Decoder.decodeTextString(byteIter) 299 except wsp_pdu.DecodeError: 300 # Fall back on just "Text-string" 301 decodedString = wsp_pdu.Decoder.decodeTextString(byteIter) 302 return decodedString
303 304 #TODO: maybe change this to boolean values 305 @staticmethod
306 - def decodeBooleanValue(byteIter):
307 """ From [4], section 7.2.6:: 308 Delivery-report-value = Yes | No 309 Yes = <Octet 128> 310 No = <Octet 129> 311 312 A lot of other yes/no fields use this encoding (read-reply, 313 report-allowed, etc) 314 315 @raise wsp_pdu.DecodeError: The boolean value could not be parsed. 316 C{byteIter} will not be modified in this case. 317 318 @return: The value for the field: 'Yes' or 'No' 319 @rtype: str 320 """ 321 value = '' 322 # byteIter, localIter = itertools.tee(byteIter) 323 # byte = localIter.next() 324 byte = byteIter.preview() 325 if byte not in (128, 129): 326 byteIter.resetPreview() 327 raise wsp_pdu.DecodeError, 'Error parsing boolean value for byte: %s' % hex(byte) 328 else: 329 byte = byteIter.next() 330 if byte == 128: 331 value = 'Yes' 332 elif byte == 129: 333 value = 'No' 334 return value
335 336 @staticmethod
337 - def decodeFromValue(byteIter):
338 """ From [4], section 7.2.11: 339 From-value = Value-length (Address-present-token Encoded-string-value | Insert-address-token ) 340 Address-present-token = <Octet 128> 341 Insert-address-token = <Octet 129> 342 343 @return: The "From" address value 344 @rtype: str 345 """ 346 fromValue = '' 347 valueLength = wsp_pdu.Decoder.decodeValueLength(byteIter) 348 # See what token we have 349 byte = byteIter.next() 350 if byte == 129: # Insert-address-token 351 fromValue = '<not inserted>' 352 else: 353 fromValue = MMSDecoder.decodeEncodedStringValue(byteIter) 354 return fromValue
355 356 @staticmethod
357 - def decodeMessageClassValue(byteIter):
358 """ From [4], section 7.2.12: 359 Message-class-value = Class-identifier | Token-text 360 Class-identifier = Personal | Advertisement | Informational | Auto 361 Personal = <Octet 128> 362 Advertisement = <Octet 129> 363 Informational = <Octet 130> 364 Auto = <Octet 131> 365 The token-text is an extension method to the message class. 366 367 @return: The decoded message class 368 @rtype: str 369 """ 370 classIdentifiers = {128 : 'Personal', 371 129 : 'Advertisement', 372 130 : 'Informational', 373 131 : 'Auto'} 374 msgClass = '' 375 # byteIter, localIter = itertools.tee(byteIter) 376 # byte = localIter.next() 377 byte = byteIter.preview() 378 if byte in classIdentifiers: 379 byteIter.next() 380 msgClass = classIdentifiers[byte] 381 else: 382 byteIter.resetPreview() 383 msgClass = wsp_pdu.Decoder.decodeTokenText(byteIter) 384 return msgClass
385 386 @staticmethod
387 - def decodeMessageTypeValue(byteIter):
388 """ Defined in [4], section 7.2.14. 389 390 @return: The decoded message type, or '<unknown>' 391 @rtype: str 392 """ 393 messageTypes = {0x80 : 'm-send-req', 394 0x81 : 'm-send-conf', 395 0x82 : 'm-notification-ind', 396 0x83 : 'm-notifyresp-ind', 397 0x84 : 'm-retrieve-conf', 398 0x85 : 'm-acknowledge-ind', 399 0x86 : 'm-delivery-ind'} 400 byte = byteIter.preview() 401 if byte in messageTypes: 402 byteIter.next() 403 return messageTypes[byte] 404 else: 405 byteIter.resetPreview() 406 return '<unknown>'
407 408 @staticmethod
409 - def decodePriorityValue(byteIter):
410 """ Defined in [4], section 7.2.17 411 412 @raise wsp_pdu.DecodeError: The priority value could not be decoded; 413 C{byteIter} is not modified in this case. 414 415 @return: The decoded priority value 416 @rtype: str 417 """ 418 priorities = {128 : 'Low', 419 129 : 'Normal', 420 130 : 'High'} 421 # byteIter, localIter = itertools.tee(byteIter) 422 byte = byteIter.preview() 423 if byte in priorities: 424 byte = byteIter.next() 425 return priorities[byte] 426 else: 427 byteIter.resetPreview() 428 raise wsp_pdu.DecodeError, 'Error parsing Priority value for byte:',byte
429 430 @staticmethod
431 - def decodeSenderVisibilityValue(byteIter):
432 """ Defined in [4], section 7.2.22:: 433 Sender-visibility-value = Hide | Show 434 Hide = <Octet 128> 435 Show = <Octet 129> 436 437 @raise wsp_pdu.DecodeError: The sender visibility value could not be 438 parsed. 439 C{byteIter} will not be modified in this case. 440 441 @return: The sender visibility: 'Hide' or 'Show' 442 @rtype: str 443 """ 444 value = '' 445 # byteIter, localIter = itertools.tee(byteIter) 446 # byte = localIter.next() 447 byte = byteIter.preview() 448 if byte not in (128, 129): 449 byteIter.resetPreview() 450 raise wsp_pdu.DecodeError, 'Error parsing sender visibility value for byte: %s' % hex(byte) 451 else: 452 byte = byteIter.next() 453 if byte == 128: 454 value = 'Hide' 455 elif byte == 129: 456 value = 'Show' 457 return value
458 459 @staticmethod
460 - def decodeResponseStatusValue(byteIter):
461 """ Defined in [4], section 7.2.20 462 463 Used to decode the "Response Status" MMS header. 464 465 @raise wsp_pdu.DecodeError: The sender visibility value could not be 466 parsed. 467 C{byteIter} will not be modified in this case. 468 469 @return: The decoded Response-status-value 470 @rtype: str 471 """ 472 responseStatusValues = {0x80 : 'Ok', 473 0x81 : 'Error-unspecified', 474 0x82 : 'Error-service-denied', 475 0x83 : 'Error-message-format-corrupt', 476 0x84 : 'Error-sending-address-unresolved', 477 0x85 : 'Error-message-not-found', 478 0x86 : 'Error-network-problem', 479 0x87 : 'Error-content-not-accepted', 480 0x88 : 'Error-unsupported-message'} 481 byte = byteIter.preview() 482 if byte in responseStatusValues: 483 byteIter.next() 484 return responseStatusValues[byte] 485 else: 486 byteIter.next() 487 # Return an unspecified error if the response is not recognized 488 return responseStatusValues[0x81]
489 490 @staticmethod
491 - def decodeStatusValue(byteIter):
492 """ Defined in [4], section 7.2.23 493 494 Used to decode the "Status" MMS header. 495 496 @raise wsp_pdu.DecodeError: The sender visibility value could not be 497 parsed. 498 C{byteIter} will not be modified in this case. 499 500 @return: The decoded Status-value 501 @rtype: str 502 """ 503 504 statusValues = {0x80 : 'Expired', 505 0x81 : 'Retrieved', 506 0x82 : 'Rejected', 507 0x83 : 'Deferred', 508 0x84 : 'Unrecognised'} 509 510 byte = byteIter.preview() 511 if byte in statusValues: 512 byteIter.next() 513 return statusValues[byte] 514 else: 515 byteIter.next() 516 # Return an unrecognised state if it couldn't be decoded 517 return statusValues[0x84]
518 519 520 @staticmethod
521 - def decodeExpiryValue(byteIter):
522 """ Defined in [4], section 7.2.10 523 524 Used to decode the "Expiry" MMS header. 525 526 From [4], section 7.2.10: 527 Expiry-value = Value-length (Absolute-token Date-value | Relative-token Delta-seconds-value) 528 Absolute-token = <Octet 128> 529 Relative-token = <Octet 129> 530 531 @raise wsp_pdu.DecodeError: The Expiry-value could not be decoded 532 533 @return: The decoded Expiry-value, either as a date, or as a delta-seconds value 534 @rtype: str or int 535 """ 536 valueLength = MMSDecoder.decodeValueLength(byteIter) 537 token = byteIter.next() 538 539 if token == 0x80: # Absolute-token 540 data = MMSDecoder.decodeDateValue(byteIter) 541 elif token == 0x81: # Relative-token 542 data = MMSDecoder.decodeDeltaSecondsValue(byteIter) 543 else: 544 raise wsp_pdu.DecodeError, 'Unrecognized token value: %s' % hex(token) 545 return data
546 547
548 -class MMSEncoder(wsp_pdu.Encoder):
549 - def __init__(self):
550 self._mmsMessage = message.MMSMessage()
551
552 - def encode(self, mmsMessage):
553 """ Encodes the specified MMS message 554 555 @param mmsMessage: The MMS message to encode 556 @type mmsMessage: MMSMessage 557 558 @return: The binary-encoded MMS data, as a sequence of bytes 559 @rtype: array.array('B') 560 """ 561 self._mmsMessage = mmsMessage 562 msgData = self.encodeMessageHeader() 563 msgData.extend(self.encodeMessageBody()) 564 return msgData
565
566 - def encodeMessageHeader(self):
567 """ Binary-encodes the MMS header data. 568 569 @note: The encoding used for the MMS header is specified in [4]. 570 All "constant" encoded values found/used in this method 571 are also defined in [4]. For a good example, see [2]. 572 573 @return: the MMS PDU header, as an array of bytes 574 @rtype: array.array('B') 575 """ 576 # See [4], chapter 8 for info on how to use these 577 fromTypes = {'Address-present-token' : 0x80, 578 'Insert-address-token' : 0x81} 579 580 contentTypes = {'application/vnd.wap.multipart.related' : 0xb3} 581 582 # Create an array of 8-bit values 583 messageHeader = array.array('B') 584 585 headersToEncode = self._mmsMessage.headers 586 587 # If the user added any of these to the message manually (X- prefix), rather use those 588 for hdr in ('X-Mms-Message-Type', 'X-Mms-Transaction-Id', 'X-Mms-Version'): 589 if hdr in headersToEncode: 590 if hdr == 'X-Mms-Version': 591 cleanHeader = 'MMS-Version' 592 else: 593 cleanHeader = hdr.replace('X-Mms-', '', 1) 594 headersToEncode[cleanHeader] = headersToEncode[hdr] 595 del headersToEncode[hdr] 596 597 # First 3 headers (in order), according to [4]: 598 ################################################ 599 # - X-Mms-Message-Type 600 # - X-Mms-Transaction-ID 601 # - X-Mms-Version 602 603 ### Start of Message-Type verification 604 if 'Message-Type' not in headersToEncode: 605 # Default to 'm-retrieve-conf'; we don't need a To/CC field for this 606 # (see WAP-209, section 6.3, table 5) 607 headersToEncode['Message-Type'] = 'm-retrieve-conf' 608 609 # See if the chosen message type is valid, given the message's other headers 610 # NOTE: we only distinguish between 'm-send-req' (requires a destination number) 611 # and 'm-retrieve-conf' (requires no destination number) 612 # - if "Message-Type" is something else, we assume the message creator 613 # knows what he/she is doing... 614 if headersToEncode['Message-Type'] == 'm-send-req': 615 foundDestAddress = False 616 for addressType in ('To', 'Cc', 'Bc'): 617 if addressType in headersToEncode: 618 foundDestAddress = True 619 break 620 if not foundDestAddress: 621 headersToEncode['Message-Type'] = 'm-retrieve-conf' 622 ### End of Message-Type verification 623 624 ### Start of Transaction-Id verification 625 if 'Transaction-Id' not in headersToEncode: 626 import random 627 headersToEncode['Transaction-Id'] = str(random.randint(1000, 9999)) 628 ### End of Transaction-Id verification 629 630 ### Start of MMS-Version verification 631 if 'MMS-Version' not in headersToEncode: 632 headersToEncode['MMS-Version'] = '1.0' 633 634 # Encode the first three headers, in correct order 635 for hdr in ('Message-Type', 'Transaction-Id', 'MMS-Version'): 636 messageHeader.extend(MMSEncoder.encodeHeader(hdr, headersToEncode[hdr])) 637 del headersToEncode[hdr] 638 639 # Encode all remaining MMS message headers, except "Content-Type" 640 # -- this needs to be added last, according [2] and [4] 641 for hdr in headersToEncode: 642 if hdr == 'Content-Type': 643 continue 644 messageHeader.extend(MMSEncoder.encodeHeader(hdr, headersToEncode[hdr])) 645 646 # Ok, now only "Content-type" should be left 647 ctType = headersToEncode['Content-Type'][0] 648 ctParameters = headersToEncode['Content-Type'][1] 649 messageHeader.extend(MMSEncoder.encodeMMSFieldName('Content-Type')) 650 messageHeader.extend(MMSEncoder.encodeContentTypeValue(ctType, ctParameters)) 651 652 return messageHeader
653
654 - def encodeMessageBody(self):
655 """ Binary-encodes the MMS body data. 656 657 @note: The MMS body is of type C{application/vnd.wap.multipart} 658 (C{mixed} or C{related}). 659 As such, its structure is divided into a header, and the data entries/parts:: 660 661 [ header ][ entries ] 662 ^^^^^^^^^^^^^^^^^^^^^ 663 MMS Body 664 665 The MMS Body header consists of one entry[5]:: 666 name type purpose 667 ------- ------- ----------- 668 nEntries Uintvar number of entries in the multipart entity 669 670 The MMS body's multipart entries structure:: 671 name type purpose 672 ------- ----- ----------- 673 HeadersLen Uintvar length of the ContentType and 674 Headers fields combined 675 DataLen Uintvar length of the Data field 676 ContentType Multiple octets the content type of the data 677 Headers (<HeadersLen> 678 - length of 679 <ContentType>) octets the part's headers 680 Data <DataLen> octets the part's data 681 682 @note: The MMS body's header should not be confused with the actual 683 MMS header, as returned by C{_encodeHeader()}. 684 685 @note: The encoding used for the MMS body is specified in [5], section 8.5. 686 It is only referenced in [4], however [2] provides a good example of 687 how this ties in with the MMS header encoding. 688 689 @return: The binary-encoded MMS PDU body, as an array of bytes 690 @rtype: array.array('B') 691 """ 692 693 messageBody = array.array('B') 694 695 #TODO: enable encoding of MMSs without SMIL file 696 ########## MMS body: header ########## 697 # Parts: SMIL file + <number of data elements in each slide> 698 nEntries = 1 699 for page in self._mmsMessage._pages: 700 nEntries += page.numberOfParts() 701 for dataPart in self._mmsMessage._dataParts: 702 nEntries += 1 703 704 messageBody.extend(self.encodeUintvar(nEntries)) 705 706 ########## MMS body: entries ########## 707 # For every data "part", we have to add the following sequence: 708 # <length of content-type + other possible headers>, 709 # <length of data>, 710 # <content-type + other possible headers>, 711 # <data>. 712 713 # Gather the data parts, adding the MMS message's SMIL file 714 smilPart = message.DataPart() 715 smil = self._mmsMessage.smil() 716 smilPart.setData(smil, 'application/smil') 717 #TODO: make this dynamic.... 718 smilPart.headers['Content-ID'] = '<0000>' 719 parts = [smilPart] 720 for slide in self._mmsMessage._pages: 721 for partTuple in (slide.image, slide.audio, slide.text): 722 if partTuple != None: 723 parts.append(partTuple[0]) 724 725 for part in parts: 726 partContentType = self.encodeContentTypeValue(part.headers['Content-Type'][0], part.headers['Content-Type'][1]) 727 728 encodedPartHeaders = [] 729 for hdr in part.headers: 730 if hdr == 'Content-Type': 731 continue 732 encodedPartHeaders.extend(wsp_pdu.Encoder.encodeHeader(hdr, part.headers[hdr])) 733 734 # HeadersLen entry (length of the ContentType and Headers fields combined) 735 headersLen = len(partContentType) + len(encodedPartHeaders) 736 messageBody.extend(self.encodeUintvar(headersLen)) 737 # DataLen entry (length of the Data field) 738 messageBody.extend(self.encodeUintvar(len(part))) 739 # ContentType entry 740 messageBody.extend(partContentType) 741 # Headers 742 messageBody.extend(encodedPartHeaders) 743 # Data (note: we do not null-terminate this) 744 for char in part.data: 745 messageBody.append(ord(char)) 746 return messageBody
747 748 749 @staticmethod
750 - def encodeHeader(headerFieldName, headerValue):
751 """ Encodes a header entry for an MMS message 752 753 From [4], section 7.1: 754 C{Header = MMS-header | Application-header} 755 C{MMS-header = MMS-field-name MMS-value} 756 C{MMS-field-name = Short-integer} 757 C{MMS-value = Bcc-value | Cc-value | Content-location-value | Content-type-value | etc} 758 759 @raise DecodeError: This uses C{decodeMMSHeader()} and 760 C{decodeApplicationHeader()}, and will raise this 761 exception under the same circumstances as 762 C{decodeApplicationHeader()}. C{byteIter} will 763 not be modified in this case. 764 765 @note: The return type of the "header value" depends on the header 766 itself; it is thus up to the function calling this to determine 767 what that type is (or at least compensate for possibly 768 different return value types). 769 770 @return: The decoded header entry from the MMS, in the format: 771 (<str:header name>, <str/int/float:header value>) 772 @rtype: tuple 773 """ 774 encodedHeader = [] 775 # First try encoding the header as a "MMS-header"... 776 for assignedNumber in MMSEncodingAssignments.fieldNames: 777 if MMSEncodingAssignments.fieldNames[assignedNumber][0] == headerFieldName: 778 encodedHeader.extend(wsp_pdu.Encoder.encodeShortInteger(assignedNumber)) 779 # Now encode the value 780 expectedType = MMSEncodingAssignments.fieldNames[assignedNumber][1] 781 try: 782 exec 'encodedHeader.extend(MMSEncoder.encode%s(headerValue))' % expectedType 783 except wsp_pdu.EncodeError, msg: 784 raise wsp_pdu.EncodeError, 'Error encoding parameter value: %s' % msg 785 except: 786 print 'A fatal error occurred, probably due to an unimplemented encoding operation' 787 raise 788 break 789 # See if the "MMS-header" encoding worked 790 if len(encodedHeader) == 0: 791 # ...it didn't. Use "Application-header" encoding 792 encodedHeaderName = wsp_pdu.Encoder.encodeTokenText(headerFieldName) 793 encodedHeader.extend(encodedHeaderName) 794 # Now add the value 795 encodedHeader.extend(wsp_pdu.Encoder.encodeTextString(headerValue)) 796 return encodedHeader
797 798 @staticmethod
799 - def encodeMMSFieldName(fieldName):
800 """ Encodes an MMS header field name, using the "assigned values" for 801 well-known MMS headers as specified in [4]. 802 803 From [4], section 7.1: 804 C{MMS-field-name = Short-integer} 805 806 @raise EncodeError: The specified header field name is not a 807 well-known MMS header. 808 809 @param fieldName: The header field name to encode 810 @type fieldName: str 811 812 @return: The encoded header field name, as a sequence of bytes 813 @rtype: list 814 """ 815 encodedMMSFieldName = [] 816 for assignedNumber in MMSEncodingAssignments.fieldNames: 817 if MMSEncodingAssignments.fieldNames[assignedNumber][0] == fieldName: 818 encodedMMSFieldName.extend(wsp_pdu.Encoder.encodeShortInteger(assignedNumber)) 819 break 820 if len(encodedMMSFieldName) == 0: 821 raise wsp_pdu.EncodeError, 'The specified header field name is not a well-known MMS header field name' 822 return encodedMMSFieldName
823 824 @staticmethod
825 - def encodeFromValue(fromValue=''):
826 """ From [4], section 7.2.11: 827 From-value = Value-length (Address-present-token Encoded-string-value | Insert-address-token ) 828 Address-present-token = <Octet 128> 829 Insert-address-token = <Octet 129> 830 831 @param fromValue: The "originator" of the MMS message. This may be an 832 empty string, in which case a token will be encoded 833 informing the MMSC to insert the address of the 834 device that sent this message (default). 835 @type fromValue: str 836 837 @return: The encoded "From" address value, as a sequence of bytes 838 @rtype: list 839 """ 840 encodedFromValue = [] 841 if len(fromValue) == 0: 842 valueLength = wsp_pdu.Encoder.encodeValueLength(1) 843 encodedFromValue.extend(valueLength) 844 encodedFromValue.append(129) # Insert-address-token 845 else: 846 encodedAddress = MMSEncoder.encodeEncodedStringValue(fromValue) 847 length = len(encodedAddress) + 1 # the "+1" is for the Address-present-token 848 valueLength = wsp_pdu.Encoder.encodeValueLength(length) 849 encodedFromValue.extend(valueLength) 850 encodedFromValue.append(128) # Address-present-token 851 encodedFromValue.extend(encodedAddress) 852 return encodedFromValue
853 854 @staticmethod
855 - def encodeEncodedStringValue(stringValue):
856 """ From [4], section 7.2.9: 857 C{Encoded-string-value = Text-string | Value-length Char-set Text-string} 858 The Char-set values are registered by IANA as MIBEnum value. 859 860 @param stringValue: The text string to encode 861 @type stringValue: str 862 863 @note: This function is currently a simple wrappper to 864 C{encodeTextString()} 865 866 @return: The encoded string value, as a sequence of bytes 867 @rtype: list 868 """ 869 return wsp_pdu.Encoder.encodeTextString(stringValue)
870 871 @staticmethod
872 - def encodeMessageTypeValue(messageType):
873 """ Defined in [4], section 7.2.14. 874 875 @note: Unknown message types are discarded; thus they will be encoded 876 as 0x80 ("m-send-req") by this function 877 878 @param messageType: The MMS message type to encode 879 @type messageType: str 880 881 @return: The encoded message type, as a sequence of bytes 882 @rtype: list 883 """ 884 messageTypes = {'m-send-req' : 0x80, 885 'm-send-conf' : 0x81, 886 'm-notification-ind' : 0x81, 887 'm-notifyresp-ind' : 0x83, 888 'm-retrieve-conf' : 0x84, 889 'm-acknowledge-ind' : 0x85, 890 'm-delivery-ind' : 0x86} 891 if messageType in messageTypes: 892 return [messageTypes[messageType]] 893 else: 894 return [0x80]
895