def process_request(self, request): request.t0 = time() meta = request.META msg = ('%-15s %s %s?%s' % (meta['REMOTE_ADDR'], request.method, meta['PATH_INFO'], meta['QUERY_STRING'])) log(msg, log='request', subsys='begin') return None
def process_response(self, request, response): spent = time() - request.t0 meta = request.META msg = ('%-15s %s %s?%s (%.4f seconds)' % (meta['REMOTE_ADDR'], request.method, meta['PATH_INFO'], meta['QUERY_STRING'], spent)) log(msg, log='request', subsys='end ') return response
def __init__(self, *args, **kwargs): if 'data' in kwargs: log('data: %r' % kwargs['data'], log='sms', subsys='mollie2-dlr', fail_silently=True) elif len(args) > 0: log('data: %r' % args[0], log='sms', subsys='mollie2-dlr', fail_silently=True) super(DeliveryReportForm, self).__init__(*args, **kwargs)
def __init__(self, *args, **kwargs): if 'data' in kwargs: log('data: %r' % kwargs['data'], log='sms', subsys='mollie2-in', fail_silently=True) elif len(args) > 0: log('data: %r' % args[0], log='sms', subsys='mollie2-in', fail_silently=True) super(IncomingTextMessageForm, self).__init__(*args, **kwargs)
def __init__(self, *args, **kwargs): if 'data' in kwargs: log('data: %r' % kwargs['data'], log='sms', subsys='mollie-in', fail_silently=True) elif len(args) > 0: log('data: %r' % args[0], log='sms', subsys='mollie-in', fail_silently=True) # Attempt to merge incoming messages by default. # NOTE: When autojoin is enabled, the joined messages are # separated by an u2060 WORD JOINER unicode character. That way # you can show the breaks if you want and the joining algorithm # uses it to calculate whether a new message should be joined at # all (i.e. not if the previous message was too short). self.autojoin_messages = kwargs.pop('autojoin_messages', True) super(IncomingTextMessageForm, self).__init__(*args, **kwargs)
def clean_message(self): message = self.cleaned_data['message'] # Mollie has fixed the odd escaping. Some providers however # replace the ESC with a SPACE for certain extended characters. # So, from some operators we get " /" when sending "\\" # (because it's sent as "\x1B/"). The EURO sign -- also an # extended character -- is decoded properly however. # As of 2011-02-05 we regularly get UTF16BE encoded messages. # At least from 204-04 numbers. Attempt to decode that. if len(message) and len(message) % 4 == 0 and all( i in '0123456789ABCDEF' for i in message): utf16 = [] for i in range(0, len(message), 2): utf16.append(chr(int(message[i:(i + 2)], 16))) try: decoded = ''.join(utf16).decode('UTF-16BE') except UnicodeDecodeError: pass else: # Double-check that the resulting string is not garbage # by looking for a percentage of expected ascii. ascii = [ i in 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789 ' for i in decoded ] ascii_pct = float(len([i for i in ascii if i])) / len(ascii) if ascii_pct >= 0.25: log('data: decoded %r to %r' % (message, decoded), log='sms', subsys='mollie-in', fail_silently=True) message = decoded # use the decoded message # Replace any \u2060 with \ufeff because we use 2060 internally. return message.replace(u'\u2060', u'\ufeff') # we use 2060 as concat delim
def _send_part(self, args): url = self.url + '?' + urllib.urlencode(args) log('data: %r' % args, log='sms', subsys='mollie-out', fail_silently=True) log('url: %r' % url, log='sms', subsys='mollie-out', fail_silently=True) try: # Do not forget the timeout here. Also we need to be wary of # a python (2.6/2.7) bug in ssl.py. See these and confirm # that it has been fixed locally. # http://bugs.python.org/issue5103 # http://svn.python.org/view?view=rev&revision=80453 # http://svn.python.org/view/python/branches/release26-maint/\ # Lib/ssl.py?r1=80453&r2=80452&pathrev=80453&diff_format=u response = urllib2.urlopen(url, timeout=20) responsexml = response.read() except urllib2.URLError as e: # (should catch more errors here?) log('result: %r' % (e.args, ), log='sms', subsys='mollie-out', fail_silently=True) mail_admins( 'SMS API fail: sms_mollie reference %s' % args['reference'], (u'Sending message failed with sms_mollie sms backend.\n\n' u'URL: %s\n\nData: %r\n\nErrors: %r\n') % (url.replace(args['md5_password'], '<hidden>'), args, e.args), fail_silently=True) # This backend is not exactly silent anyway, but silent # failing means that we don't need to catch exceptions. if not self.fail_silently: raise e return 'retry' log('result: %r' % responsexml, log='sms', subsys='mollie-out', fail_silently=True) # <?xml version="1.0"?> # <response> # <item type="sms"> # <recipients>0</recipients> # <success>false</success> # <resultcode>31</resultcode> # <resultmessage>Not enough credits to send message.</resultmessage> # </item> # </response> dom = parseString(responsexml) success = dom.getElementsByTagName('success')[0].childNodes[0].data if success != 'true': # recipients = int(dom.getElementsByTagName('recipients')[0] # .childNodes[0].data) resultcode = int( dom.getElementsByTagName('resultcode')[0].childNodes[0].data) resultmessage = (dom.getElementsByTagName('resultmessage') [0].childNodes[0].data) mail_admins( 'SMS API fail: sms_mollie reference %s' % args['reference'], (u'Sending message failed with sms_mollie sms backend.\n\n' u'URL: %s\n\nData: %r\n\nError: %s (%s)\n\nResponse: %s\n') % (url.replace(args['md5_password'], '<hidden>'), args, resultmessage, resultcode, responsexml), fail_silently=True) # This backend is not exactly silent anyway, but silent # failing means that we don't need to catch exceptions. if not self.fail_silently: raise ValueError('Gateway error', resultcode, resultmessage) if resultcode in (98, 99): # 98 "Gateway down" # 99 "Unknown error" (should be fixed mid-feb2011) return 'retry' # Here we can trap 47 "No premium SMS supported for this customer" # (which happens to not be in the documentation yet..). return 'nak' return 'pnd' # success.. status is pending
def decode_message(message, dcs=None, udh=[]): ''' Can return str or unicode, depending on the data. Mind it! message is a binary string, dcs is an integer between 0 and 255 (optional), udh is a list of integers in 0..255 (optional). Note that we need to take the UDH length and last bits into account when decoding binary septet-encoded messages -- if we were to implement that ;) ''' is_in_hex = (message and len(message) % 2 == 0 and all(i in '0123456789ABCDEF' for i in message)) if dcs is None: # As of 2011-02-05 we regularly get UCS-2-encoded messages. # If we don't have the newer DCS headers, use a heuristic to see # if this is HEX-encoded UCS-2. if not is_in_hex or not len(message) % 4 == 0: return message # not 2-byte hex ucs2 = [] for i in range(0, len(message), 2): ucs2.append(chr(int(message[i:(i + 2)], 16))) try: decoded = ''.join(ucs2).decode('utf-16be') except UnicodeDecodeError: # Decode error? Then it wasn't UTF-16 (might've been UCS-2 # though.. ah, whatever) return message # Double-check that the resulting string is not garbage # by looking for a percentage of expected ascii. ascii = [ i in ('ABCDEFGHIJKLMNOPQRSTUVWXYZ' 'abcdefghijklmnopqrstuvwxyz0123456789 ') for i in decoded ] ascii_pct = float(len([i for i in ascii if i])) / len(ascii) if ascii_pct < 0.25: return message log('data: decoded %r to %r' % (message, decoded), log='sms', subsys='mollie2', fail_silently=True) return decoded # Okay. As of 2011-03-17, we get more info: udh and dcs. If the # messagetype was "text", you shouldn't have called this. If it is # "binary", you've come to the right place. assert is_in_hex, ('Expected message to be HEX-encoded, value: %r' % (message, )) dcsdata = parse_dcs(dcs) log('data: parsed data coding scheme %r' % (dcsdata, ), log='sms', subsys='mollie2', fail_silently=True) assert not dcsdata.get('compressed', False), \ 'Unable to handle compressed messages, dcs: %r' % (dcs,) assert 'encoding' in dcsdata, \ 'Unable to continue without known encoding, dcs: %r' % (dcs,) # (1) Decode to binary string data = [] for i in range(0, len(message), 2): data.append(chr(int(message[i:(i + 2)], 16))) data = ''.join(data) # (2) Decode if necessary (mind the return value!) if dcsdata['encoding']: assert dcsdata['encoding'] != 'gsm-0338', \ 'Do we need to unpack7? What about UDH alignment?' data = data.decode(dcsdata['encoding']) return data # can be str or unicode
def _send(self, body, recipient_list, local_address, gateway=None, reference=None, premium_args=None): ''' Returns a tuple with the text message status and the number of text message bodies needed. ''' args = self.default_args.copy() args['recipients'] = u','.join(recipient_list).encode('utf-8') args['originator'] = local_address.encode('utf-8') if gateway is not None: args['gateway'] = unicode(gateway).encode('utf-8') if reference is not None: args['reference'] = unicode(reference).encode('utf-8') if premium_args: args.update(premium_args) # Ensure that we send no illegal characters and take the correct # message length into account. body_0338 = body.encode('gsm-0338', 'replace') length = len(body_0338) body_utf8 = body_0338.decode('gsm-0338').encode('utf-8') # The message args['message'] = body_utf8 # """Via de API is het mogelijk maximaal 1377 tekens per bericht te # gebruiken. Het bericht wordt opgesplitst in delen van 153 tekens, in # maximaal 9 SMS-berichten. (9x 153 tekens). # Let op! U betaalt per verzonden SMS-bericht. Bij een bericht met 300 # tekens worden 2 SMS-berichten verstuurd.""" if length <= 160: body_count = 1 else: assert 'tariff_cent' not in args, \ 'We do cannot do long paid SMS.' # premium # Mollie does accept custom UDH, so we could do long paid # SMS, except that legislations/ethics prohibits that. # Sending the UDH yourself involves filling the 'udh' # parameter with hexadecimal values, setting the type to # 'binary' and filling the message body with Windows-1252- # encoded data. (Note that UDH takes room from the body.) # See: http://en.wikipedia.org/wiki/Concatenated_SMS args['type'] = 'long' body_count = ((length - 1) / 153) + 1 # GET /xml/sms?username=user&replace_illegal_chars=true \ # &recipients=%2B31612345678&keyword=NU&md5_password \ # =123456abcdef123456abcdef123456ab&type=normal&charset=UTF-8 \ # &mid=ghi&member=false&shortcode=5665&originator=5665 \ # &message=testing..1..2..3 \ # &dlrurl=http%3A%2F%2Fexample.com%2Fapi%2Fsms%2Fdlr%2F \ # &tariff=025&gateway=2&reference=12 url = self.url + '?' + urllib.urlencode(args) log('data: %r' % args, log='sms', subsys='mollie2-out', fail_silently=True) log('url: %r' % url, log='sms', subsys='mollie2-out', fail_silently=True) try: # Do not forget the timeout here. Also we need to be wary of # a python bug in ssl.py. See these and confirm that it has # been fixed locally. # http://bugs.python.org/issue5103 # http://svn.python.org/view?view=rev&revision=80453 # http://svn.python.org/view/python/branches/release26-maint/ \ # Lib/ssl.py?r1=80453&r2=80452&pathrev=80453&diff_format=u response = urllib2.urlopen(url, timeout=20) responsexml = response.read() except urllib2.URLError as e: # (SSLError is a URLError too) log('result: %r' % (e.args, ), log='sms', subsys='mollie2-out', fail_silently=True) if not self.fail_silently: raise ValueError('Gateway communication error', *e.args) return 'retry', body_count # you should retry this log('result: %r' % responsexml, log='sms', subsys='mollie2-out', fail_silently=True) # <?xml version="1.0"?> # <response> # <item type="sms"> # <recipients>0</recipients> # <success>false</success> # <resultcode>31</resultcode> # <resultmessage>Not enough credits to send message.</resultmessage> # </item> # </response> dom = parseString(responsexml) success = dom.getElementsByTagName('success')[0].childNodes[0].data if success != 'true': # recipients = int(dom.getElementsByTagName('recipients')[0] # .childNodes[0].data) resultcode = int( dom.getElementsByTagName('resultcode')[0].childNodes[0].data) resultmessage = (dom.getElementsByTagName('resultmessage') [0].childNodes[0].data) if not self.fail_silently: # Error 47 may happen: the client will never be able to # pay for premium SMS. if resultcode == 47: raise DestinationError(('Destination will fail for ' 'paid SMS'), resultcode, resultmessage) # The other errors should not happen if we're supplying # the right parameters. raise ValueError('Gateway error', resultcode, resultmessage) # Even in fail_silently mode, we're still verbose as there # is a chance that this is a programming error. mail_admins( 'SMS API fail: sms_mollie2 reference %s' % args['reference'], (((u'Sending message failed with sms_mollie2 sms backend.\n\n' u'URL: %s\n\nData: %r\n\nError: %s (%s)\n\nResponse: %s\n') % (url, args, resultmessage, resultcode, responsexml)).replace( args['md5_password'], 'CENSORED')), fail_silently=True) if resultcode in (98, 99): # 98 "Gateway down" # 99 "Unknown error" (should be fixed mid-feb2011) return 'retry', body_count # Here we can trap 47 "No premium SMS supported for this customer" # (which happens to not be in the documentation yet..). return 'nak', body_count return 'pnd', body_count