def encode_header(header_field_name, header_value): """ Encodes a header entry for an MMS message The return type of the "header value" depends on the header itself; it is thus up to the function calling this to determine what that type is (or at least compensate for possibly different return value types). From [4], section 7.1:: Header = MMS-header | Application-header MMS-header = MMS-field-name MMS-value MMS-field-name = Short-integer MMS-value = Bcc-value | Cc-value | Content-location-value | Content-type-value | etc :raise DecodeError: This uses :func:`decode_mms_header` and :func:`decode_application_header`, and will raise this exception under the same circumstances as :func:`decode_application_header`. ``byte_iter`` will not be modified in this case. :return: The decoded header entry from the MMS, in the format: (<str:header name>, <str/int/float:header value>) :rtype: tuple """ encoded_header = [] # First try encoding the header as a "MMS-header"... for assigned_number in mms_field_names: header = mms_field_names[assigned_number][0] if header == header_field_name: encoded_header.extend( wsp_pdu.Encoder.encode_short_integer(assigned_number)) # Now encode the value expected_type = mms_field_names[assigned_number][1] try: ret = getattr(MMSEncoder, 'encode_%s' % expected_type)(header_value) encoded_header.extend(ret) except wsp_pdu.EncodeError as msg: raise wsp_pdu.EncodeError('Error encoding parameter ' 'value: %s' % msg) except: debug('A fatal error occurred, probably due to an ' 'unimplemented encoding operation') raise break # See if the "MMS-header" encoding worked if not len(encoded_header): # ...it didn't. Use "Application-header" encoding header_name = wsp_pdu.Encoder.encode_token_text(header_field_name) encoded_header.extend(header_name) # Now add the value encoded_header.extend( wsp_pdu.Encoder.encode_text_string(header_value)) return encoded_header
def to_pdu(self): """Returns a list of :class:`~messaging.pdu.Pdu` objects""" smsc_pdu = self._get_smsc_pdu() sms_submit_pdu = self._get_sms_submit_pdu() tpmessref_pdu = self._get_tpmessref_pdu() sms_phone_pdu = self._get_phone_pdu() tppid_pdu = self._get_tppid_pdu() sms_msg_pdu = self._get_msg_pdu() if len(sms_msg_pdu) == 1: pdu = smsc_pdu len_smsc = len(smsc_pdu) / 2 pdu += sms_submit_pdu pdu += tpmessref_pdu pdu += sms_phone_pdu pdu += tppid_pdu pdu += sms_msg_pdu[0] debug("smsc_pdu: %s" % smsc_pdu) debug("sms_submit_pdu: %s" % sms_submit_pdu) debug("tpmessref_pdu: %s" % tpmessref_pdu) debug("sms_phone_pdu: %s" % sms_phone_pdu) debug("tppid_pdu: %s" % tppid_pdu) debug("sms_msg_pdu: %s" % sms_msg_pdu) debug("-" * 20) debug("full_pdu: %s" % pdu) debug("full_text: %s" % self.text) debug("-" * 20) return [Pdu(pdu, len_smsc)] # multipart SMS sms_submit_pdu = self._get_sms_submit_pdu(udh=True) pdu_list = [] cnt = len(sms_msg_pdu) for i, sms_msg_pdu_item in enumerate(sms_msg_pdu): pdu = smsc_pdu len_smsc = len(smsc_pdu) / 2 pdu += sms_submit_pdu pdu += tpmessref_pdu pdu += sms_phone_pdu pdu += tppid_pdu pdu += sms_msg_pdu_item debug("smsc_pdu: %s" % smsc_pdu) debug("sms_submit_pdu: %s" % sms_submit_pdu) debug("tpmessref_pdu: %s" % tpmessref_pdu) debug("sms_phone_pdu: %s" % sms_phone_pdu) debug("tppid_pdu: %s" % tppid_pdu) debug("sms_msg_pdu: %s" % sms_msg_pdu_item) debug("-" * 20) debug("full_pdu: %s" % pdu) debug("full_text: %s" % self.text) debug("-" * 20) pdu_list.append(Pdu(pdu, len_smsc, cnt=cnt, seq=i + 1)) return pdu_list
def _decode_status_report_pdu(self, data): self.udh = UserDataHeader.from_status_report_ref(data.pop(0)) sndlen = data.pop(0) if sndlen % 2: sndlen += 1 sndlen = int(sndlen / 2.0) sndtype = data.pop(0) recipient = swap_number(encode_bytes(data[:sndlen])) if (sndtype >> 4) & 0x07 == consts.INTERNATIONAL: recipient = '+%s' % recipient data = data[sndlen:] date = swap(list(encode_bytes(data[:7]))) try: scts_str = "%s%s/%s%s/%s%s %s%s:%s%s:%s%s" % tuple(date[0:12]) self.date = datetime.strptime(scts_str, "%y/%m/%d %H:%M:%S") except (ValueError, TypeError): scts_str = '' debug('Could not decode scts: %s' % date) data = data[7:] date = swap(list(encode_bytes(data[:7]))) try: dt_str = "%s%s/%s%s/%s%s %s%s:%s%s:%s%s" % tuple(date[0:12]) dt = datetime.strptime(dt_str, "%y/%m/%d %H:%M:%S") except (ValueError, TypeError): dt_str = '' dt = None debug('Could not decode date: %s' % date) data = data[7:] msg_l = [recipient, scts_str] try: status = data.pop(0) except IndexError: # Yes it is entirely possible that a status report comes # with no status at all! I'm faking for now the values and # set it to SR-UNKNOWN as that's all we can do _status = None status = 0x1 sender = 'SR-UNKNOWN' msg_l.append(dt_str) else: _status = status if status == 0x00: msg_l.append(dt_str) else: msg_l.append('') if status == 0x00: sender = "SR-OK" elif status == 0x1: sender = "SR-UNKNOWN" elif status == 0x30: sender = "SR-STORED" else: sender = "SR-UNKNOWN" self.number = sender self.text = "|".join(msg_l) self.fmt = 0x08 # UCS2 self.type = 0x03 # status report self.sr = { 'recipient': recipient, 'scts': self.date, 'dt': dt, 'status': _status }
class MMSEncoder(wsp_pdu.Encoder): """MMS Encoder""" def __init__(self): self._mms_message = message.MMSMessage() def encode(self, mms_message): """ Encodes the specified MMS message ``mms_message`` :param mms_message: The MMS message to encode :type mms_message: MMSMessage :return: The binary-encoded MMS data, as a sequence of bytes :rtype: array.array('B') """ self._mms_message = mms_message msg_data = self.encode_message_header() msg_data.extend(self.encode_message_body()) return msg_data def encode_message_header(self): """ Binary-encodes the MMS header data. The encoding used for the MMS header is specified in [4]. All "constant" encoded values found/used in this method are also defined in [4]. For a good example, see [2]. :return: the MMS PDU header, as an array of bytes :rtype: array.array('B') """ # See [4], chapter 8 for info on how to use these # from_types = {'Address-present-token': 0x80, # 'Insert-address-token': 0x81} # content_types = {'application/vnd.wap.multipart.related': 0xb3} # Create an array of 8-bit values message_header = array.array('B') headers_to_encode = self._mms_message.headers # If the user added any of these to the message manually # (X- prefix) use those instead for hdr in ('X-Mms-Message-Type', 'X-Mms-Transaction-Id', 'X-Mms-Version'): if hdr in headers_to_encode: if hdr == 'X-Mms-Version': clean_header = 'MMS-Version' else: clean_header = hdr.replace('X-Mms-', '', 1) headers_to_encode[clean_header] = headers_to_encode[hdr] del headers_to_encode[hdr] # First 3 headers (in order), according to [4]: ################################################ # - X-Mms-Message-Type # - X-Mms-Transaction-ID # - X-Mms-Version ### Start of Message-Type verification if 'Message-Type' not in headers_to_encode: # Default to 'm-retrieve-conf'; we don't need a To/CC field for # this (see WAP-209, section 6.3, table 5) headers_to_encode['Message-Type'] = 'm-retrieve-conf' # See if the chosen message type is valid, given the message's # other headers. NOTE: we only distinguish between 'm-send-req' # (requires a destination number) and 'm-retrieve-conf' # (requires no destination number) - if "Message-Type" is # something else, we assume the message creator knows # what she is doing if headers_to_encode['Message-Type'] == 'm-send-req': found_dest_address = False for address_type in ('To', 'Cc', 'Bc'): if address_type in headers_to_encode: found_dest_address = True break if not found_dest_address: headers_to_encode['Message-Type'] = 'm-retrieve-conf' ### End of Message-Type verification ### Start of Transaction-Id verification if 'Transaction-Id' not in headers_to_encode: trans_id = str(random.randint(1000, 9999)) headers_to_encode['Transaction-Id'] = trans_id ### End of Transaction-Id verification ### Start of MMS-Version verification if 'MMS-Version' not in headers_to_encode: headers_to_encode['MMS-Version'] = '1.0' # Encode the first three headers, in correct order for hdr in ('Message-Type', 'Transaction-Id', 'MMS-Version'): message_header.extend( MMSEncoder.encode_header(hdr, headers_to_encode[hdr])) del headers_to_encode[hdr] # Encode all remaining MMS message headers, except "Content-Type" # -- this needs to be added last, according [2] and [4] for hdr in headers_to_encode: if hdr != 'Content-Type': message_header.extend( MMSEncoder.encode_header(hdr, headers_to_encode[hdr])) # Ok, now only "Content-type" should be left content_type, ct_parameters = headers_to_encode['Content-Type'] message_header.extend(MMSEncoder.encode_mms_field_name('Content-Type')) ret = MMSEncoder.encode_content_type_value(content_type, ct_parameters) message_header.extend(flatten_list(ret)) return message_header def encode_message_body(self): """ Binary-encodes the MMS body data The MMS body's header should not be confused with the actual MMS header, as returned by :func:`encode_header`. The encoding used for the MMS body is specified in [5], section 8.5. It is only referenced in [4], however [2] provides a good example of how this ties in with the MMS header encoding. The MMS body is of type `application/vnd.wap.multipart` ``mixed`` or ``related``. As such, its structure is divided into a header, and the data entries/parts:: [ header ][ entries ] ^^^^^^^^^^^^^^^^^^^^^ MMS Body The MMS Body header consists of one entry[5]:: name type purpose ------- ------- ----------- num_entries uint_var num of entries in the multipart entity The MMS body's multipart entries structure:: name type purpose ------- ----- ----------- HeadersLen uint_var length of the ContentType and Headers fields combined DataLen uint_var length of the Data field ContentType Multiple octets the content type of the data Headers (<HeadersLen> - length of <ContentType>) octets the part's headers Data <DataLen> octets the part's data :return: The binary-encoded MMS PDU body, as an array of bytes :rtype: array.array('B') """ message_body = array.array('B') #TODO: enable encoding of MMSs without SMIL file ########## MMS body: header ########## # Parts: SMIL file + <number of data elements in each slide> num_entries = 1 for page in self._mms_message._pages: num_entries += page.number_of_parts() for data_part in self._mms_message._data_parts: num_entries += 1 message_body.extend(self.encode_uint_var(num_entries)) ########## MMS body: entries ########## # For every data "part", we have to add the following sequence: # <length of content-type + other possible headers>, # <length of data>, # <content-type + other possible headers>, # <data>. # Gather the data parts, adding the MMS message's SMIL file smil_part = message.DataPart() smil = self._mms_message.smil() smil_part.set_data(smil, 'application/smil') #TODO: make this dynamic.... smil_part.headers['Content-ID'] = '<0000>' parts = [smil_part] for slide in self._mms_message._pages: for part_tuple in (slide.image, slide.audio, slide.text): if part_tuple is not None: parts.append(part_tuple[0]) for part in parts: name, val_type = part.headers['Content-Type'] part_content_type = self.encode_content_type_value(name, val_type) encoded_part_headers = [] for hdr in part.headers: if hdr == 'Content-Type': continue encoded_part_headers.extend( wsp_pdu.Encoder.encode_header(hdr, part.headers[hdr])) # HeadersLen entry (length of the ContentType and # Headers fields combined) headers_len = len(part_content_type) + len(encoded_part_headers) message_body.extend(self.encode_uint_var(headers_len)) # DataLen entry (length of the Data field) message_body.extend(self.encode_uint_var(len(part))) # ContentType entry message_body.extend(part_content_type) # Headers message_body.extend(encoded_part_headers) # Data (note: we do not null-terminate this) for char in part.data: message_body.append(ord(char)) return message_body @staticmethod def encode_header(header_field_name, header_value): """ Encodes a header entry for an MMS message The return type of the "header value" depends on the header itself; it is thus up to the function calling this to determine what that type is (or at least compensate for possibly different return value types). From [4], section 7.1:: Header = MMS-header | Application-header MMS-header = MMS-field-name MMS-value MMS-field-name = Short-integer MMS-value = Bcc-value | Cc-value | Content-location-value | Content-type-value | etc :raise DecodeError: This uses :func:`decode_mms_header` and :func:`decode_application_header`, and will raise this exception under the same circumstances as :func:`decode_application_header`. ``byte_iter`` will not be modified in this case. :return: The decoded header entry from the MMS, in the format: (<str:header name>, <str/int/float:header value>) :rtype: tuple """ encoded_header = [] # First try encoding the header as a "MMS-header"... for assigned_number in mms_field_names: header = mms_field_names[assigned_number][0] if header == header_field_name: encoded_header.extend( wsp_pdu.Encoder.encode_short_integer(assigned_number)) # Now encode the value expected_type = mms_field_names[assigned_number][1] try: ret = getattr(MMSEncoder, 'encode_%s' % expected_type)(header_value) encoded_header.extend(ret) except wsp_pdu.EncodeError, msg: raise wsp_pdu.EncodeError('Error encoding parameter ' 'value: %s' % msg) except: debug('A fatal error occurred, probably due to an ' 'unimplemented encoding operation') raise