-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathutil.py
281 lines (238 loc) · 7.91 KB
/
util.py
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
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
import MySQLdb
from flask import request, Markup
from wand.image import Image
import datetime
from collections import namedtuple
Option = namedtuple('Option', ['success', 'value'])
import re
import os
from contextlib import contextmanager
from conf import *
import cred
def conn2db():
conn = MySQLdb.connect(
host = cred.SQL_HOST,
user = cred.SQL_USER,
passwd = cred.SQL_PASS,
db = cred.SQL_DB,
cursorclass = MySQLdb.cursors.DictCursor
)
conn.autocommit(True)
return conn
def get_remote_IP():
trusted_proxies = {'127.0.0.1'}
route = request.access_route + [request.remote_addr]
return next(
(addr for addr in reversed(route) if addr not in trusted_proxies),
request.remote_addr
)
def store_file(f, cursor, OP=False):
f_size = os.fstat(f.fileno()).st_size
# Make sure the file is not too large
if f_size > MAX_FILE_SIZE:
return Option(
False,
'Filesize exceeds maximum (%dMB).' % (MAX_FILE_SIZE // pow(1024, 2))
)
if not is_allowed(f.filename):
return Option(False, 'Illegal file extension')
# Create a thmbnail
img = Image(blob=f.read())
width = img.width
height = img.height
if width > height:
img.transform(resize=str(MAX_IMG_WH if not OP else MAX_OP_IMG_WH) + 'x')
else:
img.transform(resize='x' + str(MAX_IMG_WH if not OP else MAX_OP_IMG_WH))
# Add the image and thumbnailto the file table
f.stream.seek(0,0)
res = '%sx%s' % (width, height)
cursor.execute(
'INSERT INTO files (NAME, SIZE, RES, FILE, THUMB) '
'VALUES (%s, %s, %s, %s, %s)',
# TODO: Might have to rewidn
(f.filename, f_size, res, f.read(), img.make_blob())
)
img.close()
return Option(True, cursor.lastrowid)
def fetch_file(id, cursor, thumb=False):
if thumb:
cursor.execute('SELECT THUMB FROM files WHERE ID = %s', (id,))
else:
cursor.execute('SELECT FILE FROM files WHERE ID = %s', (id,))
t = cursor.fetchone()
if not t:
return Option(False, 'File does not exist.')
else:
return Option(True, t['THUMB'] if thumb else t['FILE'])
def is_allowed(filename):
return '.' in filename and \
filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS
def purge_thread(id, cursor):
# TODO: make sure this works
# Delete post links from the thread
cursor.execute(
'DELETE FROM post_links WHERE LINKED_BY in '
'(SELECT ID FROM posts WHERE THREAD_ID = %s OR ID = %s)',
(id, id)
)
# Delete files from the thread
cursor.execute(
'DELETE FROM files WHERE ID IN '
'(SELECT FILE_ID FROM posts WHERE THREAD_ID = %s OR ID = %s)',
(id, id)
)
# Delete all posts in the thread
cursor.execute(
'DELETE FROM posts WHERE THREAD_ID = %s OR ID = %s',
(id, id)
)
# Delete the thread
cursor.execute('DELETE FROM threads WHERE ID = %s', (id,))
def purge_post(id, cursor):
if(cursor.execute('SELECT THREAD_ID FROM posts WHERE ID = %s', (id,)) == 0):
return False;
if cursor.fetchone()['THREAD_ID'] == SQL_CONST_OP:
purge_thread(id, cursor)
else:
cursor.execute(
'DELETE FROM post_links WHERE LINKED_BY in '
'(SELECT ID FROM posts WHERE ID = %s)',
(id, )
)
cursor.execute(
'DELETE FROM files WHERE ID IN '
'(SELECT FILE_ID FROM posts WHERE THREAD_ID = %s OR ID = %s)',
(id, id)
)
cursor.execute('DELETE FROM posts WHERE ID = %s', (id, ))
return True
def is_banned(ip, cursor):
cursor.execute('SELECT IP FROM banned WHERE IP = %s', (ip, ))
if cursor.fetchone():
return True
else:
return False
def fetch_thread_data(threads, cursor, IPs=True):
for thread in threads:
# Fetch OP
cursor.execute(
'SELECT ID, TIME, USERNAME, TEXT%s '
% (', IP' if IPs else ' ', ) +
' FROM posts WHERE ID = %s',
(thread['ID'], )
)
thread['OP'] = cursor.fetchone()
cursor.execute(
'SELECT LINKED_BY FROM post_links WHERE POST=%s',
(thread['ID'], )
)
thread['OP']['LINKS'] = map(lambda d: d['LINKED_BY'], cursor.fetchall())
# Fetch Replies
cursor.execute(
'SELECT ID, TIME, USERNAME, TEXT%s '
% (', IP' if IPs else ' ', ) +
' FROM posts WHERE THREAD_ID = %s',
(thread['ID'], )
)
thread['POSTS'] = cursor.fetchall()
# Fetch post links
for post in thread['POSTS']:
cursor.execute(
'SELECT LINKED_BY FROM post_links WHERE POST=%s',
(post['ID'], )
)
post['LINKS'] = map(lambda d: d['LINKED_BY'], cursor.fetchall())
# Get thread statistics
# TODO: Use COUNT?
thread['I_COUNT'] = cursor.execute(
'SELECT ID FROM posts WHERE FILE_ID IS NOT NULL AND THREAD_ID = %s',
(thread['ID'], )
) + 1
thread['R_COUNT'] = cursor.execute(
'SELECT ID FROM posts WHERE THREAD_ID = %s',
(thread['ID'], )
)
thread['P_COUNT'] = cursor.execute(
'SELECT DISTINCT IP FROM posts WHERE THREAD_ID=%s',
(thread['ID'], )
)
# Get file info
cursor.execute(
'SELECT ID, NAME, SIZE, RES FROM files '
'WHERE ID = (SELECT FILE_ID FROM posts WHERE ID = %s)',
(thread['OP']['ID'], )
)
thread['OP']['FILE'] = cursor.fetchone()
for post in thread['POSTS']:
cursor.execute(
'SELECT ID, NAME, SIZE, RES FROM files '
'WHERE ID = (SELECT FILE_ID FROM posts WHERE ID = %s)',
(post['ID'], )
)
t = cursor.fetchone()
if t:
post['FILE'] = t
def refine_text(s):
# Sanitize the string
s = re.sub(r'<.*?>', '', s).strip()
patterns = (
'(?P<eline>(\n)\s*\n*\s*(\n))',
)
subs = {
'eline': lambda mo : '%s%s' % (mo.group(2), mo.group(3)),
}
regex = re.compile('|'.join(patterns), re.MULTILINE)
# Match named group to its lambda
def reg_match(mo):
for k,v in mo.groupdict().iteritems():
if v:
return subs[k](mo)
return regex.sub(reg_match, s).replace('\n', '<br>')
def store_post(thread_id, file_id, cursor):
# Handle link quotes
# TODO: filter out broken links
text = refine_text(request.form['text'])
if not len(text):
return Option(False, 'Empty body.')
if len(text) > MAX_POST_LEN:
return Option(False, 'Post is too long.')
# Find post links
quoted = re.findall(r'>>(\d+)', text, flags=re.MULTILINE)
# Set mandatory fields
p_fields = 'TIME, TEXT, THREAD_ID, IP'
p_phldrs = '%s, %s, %s, %s'
p_values = (
datetime.datetime.now(),
text,
thread_id,
Markup.escape(get_remote_IP())
)
# Set optional fields
if file_id:
p_fields += ', FILE_ID'
p_phldrs += ', %s'
p_values += (file_id, )
if len(request.form['name']):
p_fields += ', USERNAME'
p_phldrs += ', %s'
p_values += (Markup(request.form['name'].strip()).striptags(), )
# Store the post in the post table
cursor.execute(
'INSERT INTO posts (%s) VALUES (%s)' % (p_fields, p_phldrs),
p_values
)
post_id = cursor.lastrowid
# Update posts that have been referenced
for quote in quoted:
cursor.execute(
'INSERT IGNORE INTO post_links values (%s, %s)',
(quote, post_id)
)
return Option(True, post_id)
def is_banned(ip, cursor):
cursor.execute('SELECT IP FROM banned WHERE IP = %s', (ip, ))
if cursor.fetchone():
return True
else:
return False