File: guestbook.py

package info (click to toggle)
pyamf 0.6.1%2Bdfsg-3
  • links: PTS, VCS
  • area: main
  • in suites: wheezy
  • size: 7,692 kB
  • sloc: python: 17,944; xml: 455; makefile: 116; sql: 38; java: 11; sh: 7
file content (192 lines) | stat: -rw-r--r-- 4,991 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# Copyright (c) The PyAMF Project.
# See LICENSE.txt for details.

"""
Guestbook remoting service.

@since: 0.3
"""


from datetime import datetime
from urlparse import urlparse
import re

try:
    from genshi.input import HTML
    from genshi.filters import HTMLSanitizer
except ImportError:
    import sys
    print >> sys.stderr, "Genshi is required for this example"
    raise

from twisted.internet import defer
from twisted.internet.task import LoopingCall

import pyamf 
from pyamf.flex import ArrayCollection, ObjectProxy
from pyamf.remoting.gateway import expose_request


EMAIL_RE = r"^.+\@(\[?)[a-zA-Z0-9\-\.]+\.([a-zA-Z]{2,3}|[0-9]{1,3})(\]?)$"

# This is MySQL specific, make sure that if you use a different database server
# this is updated to ensure sql injection attacks don't occur 
def sql_safe(value):
    if isinstance(value, basestring):
        return value.replace("'", "\\'")
    elif isinstance(type(value), (int, float)):
        return value

    raise TypeError, 'basestring, int or float expected' 


def is_valid_url(url):
    o = urlparse(url)

    # scheme
    if o[0] == '':
        return (False, 'Scheme required')

    if o[1] == '':
        return (False, 'Hostname required')

    return (True, None)


def is_valid_email(email):
    """
    A very basic email address format validator
    """
    if re.match(EMAIL_RE, email) != None:
        return True

    return False


def strip_message(message):
    markup = HTML(message) | HTMLSanitizer()

    return markup.render('xhtml')


def build_message(row):
    m = Message()

    m.name = row[0]
    m.url = row[1]
    #m.email = row[2]
    m.created = row[3]
    m.message = row[4]

    return m


class Message:
    pass

pyamf.register_class(Message, 'org.pyamf.examples.guestbook.Message')


class GuestBookService(object):
    def __init__(self, pool):
        self.conn_pool = pool
        LoopingCall(self._keepAlive).start(3600, False)
        
    def _keepAlive():
        print 'Running Keep Alive...'
        self.conn_pool.runOperation('SELECT 1')

    def getMessages(self):
        """
        Gets all approved messages.
        """
        def cb(rs):
            ret = [ObjectProxy(build_message(row)) for row in rs]

            return ArrayCollection(ret)

        def eb(failure):
            # TODO nick: logging
            return ArrayCollection()

        d = self.conn_pool.runQuery("SELECT name, url, email, created, message FROM " + \
            "message WHERE approved = 1 ORDER BY id DESC").addErrback(eb).addCallback(cb)

        return d

    def getMessageById(self, id):
        def cb(rs):
            return build_message(rs[0])

        return self.conn_pool.runQuery("SELECT name, url, email, created, message FROM " + \
            "message WHERE id = %d" % int(id)).addCallback(cb)

    @expose_request
    def addMessage(self, request, msg):
        """
        Adds a message to the guestbook

        @param request: The underlying HTTP request.
        @type msg: L{Message}
        """
        name = msg._amf_object.name
        url = msg._amf_object.url
        email = msg._amf_object.email
        message = msg._amf_object.message
 
        if not isinstance(name, basestring):
            name = str(name)

        if len(name) > 50:
            raise IOError, "Name exceeds maximum length (50 chars max)"

        if not isinstance(url, basestring):
            url = str(url)

        if len(url) > 255:
            raise IOError, "Website url exceeds maximum length (255 chars max)"

        if len(url) > 0:
            valid_url, reason = is_valid_url(url)

            if not valid_url:
                raise ValueError, "Website url not valid"

        if not isinstance(email, basestring):
            email = str(email)

        if not is_valid_email(email):
            raise ValueError, "Email address is not valid"

        if not isinstance(message, basestring):
            message = str(message)

        if len(message) == 0:
            raise ValueError, "Message is required"

        message = strip_message(message)
        response_deferred = defer.Deferred()

        def cb(rs):
            # rs contains the last inserted id of the message
            def cb2(msg):
                response_deferred.callback(msg)

            self.getMessageById(rs[0][0]).addCallback(cb2).addErrback(eb)

        def eb(failure):
            response_deferred.errback(failure)

        d = self.conn_pool.runQuery("INSERT INTO message (name, url, email, created, ip_address, message, approved)" + \
            " VALUES ('%s', '%s', '%s', NOW(), '%s', '%s', 1);" % (
                sql_safe(name),
                sql_safe(url),
                sql_safe(email),
                sql_safe(request.getClientIP()),
                sql_safe(message)
            )).addCallback(lambda x: self.conn_pool.runQuery("SELECT id FROM message ORDER BY id DESC LIMIT 0, 1"))

        d.addCallback(cb).addErrback(eb)

        return response_deferred