Beispiel #1
0
 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
Beispiel #2
0
 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
Beispiel #3
0
 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)
Beispiel #4
0
    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)
Beispiel #5
0
    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)
Beispiel #6
0
    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
Beispiel #7
0
    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
Beispiel #8
0
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
Beispiel #9
0
    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