anonymous group photoblog software
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

400 lines
12 KiB

  1. # misc.py: Temporary place for new functions
  2. import os
  3. import re
  4. import time
  5. import crypt
  6. import struct
  7. from subprocess import Popen, PIPE
  8. import util
  9. import crypto # part of wakarimasen
  10. import config, config_defaults
  11. import str_format
  12. import urllib
  13. from util import local
  14. def dot_to_dec(ip):
  15. try:
  16. parts = [int(x) for x in ip.split(".")]
  17. return struct.unpack('>L', struct.pack('>4B', *parts))[0]
  18. except ValueError:
  19. return ip
  20. def dec_to_dot(numip):
  21. try:
  22. # Parse 64-bit integer for compatibility with database written
  23. # with Perl's 'N' packing format.
  24. parts = struct.unpack('>8B', struct.pack('>Q', long(numip)))
  25. return '.'.join([str(x) for x in parts[4:8]])
  26. except ValueError:
  27. return numip
  28. def validate_ip(ip):
  29. try:
  30. if isinstance(dot_to_dec(ip), int):
  31. return True
  32. except:
  33. pass
  34. return False
  35. def is_whitelisted(numip):
  36. return False
  37. TRIP_RE = '^(.*?)((?<!&)#|%s)(.*)$'
  38. SECURE_TRIP_RE = '(?:%s)(?<!&#)(?:%s)*(.*)$'
  39. SALT_CLEAN_RE = re.compile('[^\.-z]')
  40. def process_tripcode(name, tripkey='!'):
  41. match = re.match(TRIP_RE % re.escape(tripkey), name)
  42. if not match:
  43. return (str_format.clean_string(str_format.decode_string(name)), '')
  44. trip = ''
  45. namepart, marker, trippart = match.groups()
  46. namepart = str_format.clean_string(str_format.decode_string(namepart))
  47. # do we want secure trips, and is there one?
  48. if config.SECRET:
  49. regexp = re.compile(SECURE_TRIP_RE.replace("%s", re.escape(marker)))
  50. smatch = regexp.match(trippart)
  51. if smatch:
  52. trippart = regexp.sub('', trippart)
  53. maxlen = 255 - len(config.SECRET)
  54. string = smatch.group(1)[:maxlen]
  55. trip = tripkey * 2 + hide_data(smatch.group(1), 6, "trip",
  56. config.SECRET, True)
  57. if not trippart: # return directly if there's no normal tripcode
  58. return (namepart, trip)
  59. # 2ch trips are processed as Shift_JIS whenever possible
  60. trippart = trippart.encode("shiftjis", "xmlcharrefreplace")
  61. trippar = str_format.clean_string(trippart)
  62. salt = (trippart + "H..")[1:3]
  63. salt = SALT_CLEAN_RE.sub('.', salt)
  64. for old, new in map(None, ':;<=>?@[\\]^_`', 'ABCDEFGabcdef'):
  65. salt = salt.replace(old, new)
  66. trip = tripkey + crypt.crypt(trippart, salt)[-10:] + trip
  67. return (namepart, trip)
  68. def make_key(key, secret, length):
  69. return crypto.rc4('\0' * length, key + secret)
  70. def hide_data(data, bytes, key, secret, base64=False):
  71. ret = crypto.rc4('\0' * bytes, make_key(key, secret, 32) + str(data))
  72. if base64:
  73. return ret.encode("base64").rstrip('\n')
  74. return ret
  75. def hide_critical_data(string, key):
  76. rc6 = crypto.RC6(key)
  77. ret = ''
  78. i = 0
  79. while i < len(string):
  80. ret += rc6.encrypt(string[i:i+15]).encode("base64")[:-1]
  81. i += 15
  82. return ret
  83. def compile_spam_checker(spam_files):
  84. # TODO caching this by timestamps would be nice
  85. regexps = []
  86. for file in spam_files:
  87. for line in open(file).readlines():
  88. line = re.sub("(^|\s+)#.*", "", line).strip()
  89. if not line:
  90. continue
  91. match = re.match("^/(.*)/([xism]*)$", line)
  92. if match:
  93. pattern, modifiers = match.groups()
  94. flags = sum([getattr(re, x.upper()) for x in modifiers])
  95. else:
  96. pattern = re.escape(line)
  97. flags = re.I
  98. regexps.append(re.compile(pattern, flags))
  99. def spam_checker(string):
  100. for regexp in regexps:
  101. if regexp.search(string) is not None:
  102. return True
  103. return False
  104. return spam_checker
  105. def spam_engine(trap_fields, spam_files):
  106. def spam_screen():
  107. raise util.SpamError()
  108. request = local.request
  109. for field in trap_fields:
  110. if request.values.get('request', None) is not None:
  111. spam_screen()
  112. spam_checker = compile_spam_checker(spam_files)
  113. fields = request.values.keys()
  114. fulltext = '\n'.join([str_format.decode_string(request.values[x])
  115. for x in fields])
  116. if spam_checker(fulltext):
  117. spam_screen()
  118. def is_trusted(trip):
  119. # needed only when captcha is enabled?
  120. pass
  121. def check_captcha(*args):
  122. # broken in wakaba+desuchan?
  123. pass
  124. def make_date(timestamp, style='futaba'):
  125. '''Generate a date string from a passed timestamp based on a requested
  126. style. The string formatting power of the time module's strftime()
  127. method is used here. The optional locale array from Wakaba is dropped
  128. in favor of using the local day and month abbreviations provided by the
  129. module. The format used can also be inputted directly into the style
  130. parameter.'''
  131. localtime = time.localtime(timestamp + config.TIME_OFFSET)
  132. gmt = time.gmtime(timestamp)
  133. time_str = ''
  134. if style.lower() == '2ch':
  135. time_str = time.strftime('%Y-%m-%d %H:%M', localtime)
  136. elif style.lower() == '2ch-gmt':
  137. time_str = time.strftime('%Y-%m-%d %H:%M GMT', gmt)
  138. elif style.lower() == 'futaba' or style == 0:
  139. time_str = time.strftime('%y/%m/%d(%a)%H:%M', localtime)
  140. elif style.lower() == 'futaba-gmt':
  141. time_str = time.strftime('%y/%m/%d(%a)%H:%M GMT', gmt)
  142. elif style.lower() == 'tiny':
  143. time_str = time.strftime('%m/%d %H:%M', localtime)
  144. elif style.lower() == 'cookie':
  145. time_str = time.strftime('%a, %d-%b-%Y %H:%M:%S GMT', gmt)
  146. elif style.lower() == 'http':
  147. time_str = time.strftime('%a, %d %b %Y %H:%M:%S GMT', gmt)
  148. elif style.lower() == 'month':
  149. time_str = time.strftime('%b %Y', gmt)
  150. elif style.lower() == 'us-en':
  151. time_str = time.strftime('%A, %B %d, %Y @ %h:%M %p', localtime)
  152. elif style.lower() == 'uk-en':
  153. time_str = time.strftime('%A, %d %B %Y @ %h:%M %p', localtime)
  154. elif style.lower() == 'ctime' or style.lower() == 'c':
  155. time_str = time.asctime(localtime)
  156. elif style.lower() == 'localtime':
  157. time_str = str(timestamp)
  158. elif style.lower() == '2ch-sep93': # Damn AOLers! Get offa mah lawn!
  159. # September 1, 1993 as a timestamp.
  160. SEP_1_1993 = 746884800L
  161. seconds_past = long(timestamp) - SEP_1_1993
  162. days_past = seconds_past / 86400L
  163. time_str = '1993-09-%u' % (days_past + 1)
  164. time_str = ' '.join([time_str, time.strftime('%H:%M', localtime)])
  165. else:
  166. # Let the style parameter default to a format string.
  167. time_str = time.strftime(style, timestamp)
  168. return time_str
  169. def make_cookies(**kwargs):
  170. expires = kwargs.pop('expires', time.time() + 14 * 24 * 3600)
  171. path = kwargs.pop('path', '/')
  172. httponly = kwargs.pop('httponly', False)
  173. expire_date = make_date(expires, "cookie")
  174. environ = local.environ
  175. cookies = environ['waka.cookies']
  176. for key, value in kwargs.iteritems():
  177. cookies[key] = urllib.quote(value.encode('unicode-escape'))
  178. cookies[key]['expires'] = expire_date
  179. cookies[key]['path'] = path
  180. if httponly:
  181. cookies[key]['httponly'] = True
  182. def get_script_name():
  183. return local.environ['SCRIPT_NAME']
  184. def get_secure_script_name():
  185. script_name = get_script_name()
  186. if config.USE_SECURE_ADMIN:
  187. return 'https://' + local.environ['SERVER_NAME'] + script_name
  188. return script_name
  189. def make_script_url(**kwargs):
  190. '''Builds an url pointing to the cgi script
  191. Optional params:
  192. - _secure: set to False to avoid enforcing https
  193. - _amp: set to True to html escape ampersands
  194. Everything else is included as url parameters'''
  195. secure = kwargs.pop('_secure', True)
  196. amp = kwargs.pop('_amp', False)
  197. base = get_secure_script_name() if secure else get_script_name()
  198. url = '%s?%s' % (base, urllib.urlencode(kwargs))
  199. if amp:
  200. url = url.replace('&', '&amp;')
  201. return url
  202. def get_filestorage_size(filestorage):
  203. filestorage.stream.seek(0, 2)
  204. size = filestorage.stream.tell()
  205. filestorage.stream.seek(0, 0)
  206. return size
  207. def analyze_image(file, name):
  208. types = [("jpg", analyze_jpeg), ("png", analyze_png), ("gif", analyze_gif)]
  209. for ext, analyze in types:
  210. res = analyze(file)
  211. if res:
  212. return (ext, res[0], res[1])
  213. # find file extension for unknown files
  214. ext = ''
  215. if name.find(".") != -1:
  216. ext = name.split(".")[-1].lower()
  217. return (ext, 0, 0)
  218. def analyze_jpeg(file):
  219. # TODO: requires testing
  220. try:
  221. buffer = file.read(2)
  222. if buffer != '\xff\xd8':
  223. return
  224. while True:
  225. while True:
  226. buffer = file.read(1)
  227. if not buffer:
  228. return
  229. if buffer == '\xff':
  230. break
  231. mark, size = struct.unpack(">BH", file.read(3))
  232. if mark in (0xda, 0xd9): # SOS/EOI
  233. break
  234. if size < 2:
  235. # MS GDI+ JPEG exploit uses short chunks
  236. raise util.WakaError("Possible virus in image")
  237. if mark >= 0xc0 and mark <= 0xc2: # SOF0..SOF2 - what the hell are the rest?
  238. bits, height, width = struct.unpack(">BHH", file.read(5))
  239. return (width, height)
  240. file.seek(size - 2, 1)
  241. except struct.error:
  242. return
  243. finally:
  244. file.seek(0)
  245. PNG_MAGIC = '\x89PNG\r\n\x1a\n'
  246. PNG_IHDR = 'IHDR'
  247. def analyze_png(file):
  248. buffer = file.read(24)
  249. file.seek(0)
  250. if len(buffer) != 24:
  251. return
  252. magic, length, ihdr, width, height = struct.unpack(">8sL4sLL", buffer)
  253. if magic != PNG_MAGIC and ihdr != PNG_IHDR:
  254. return
  255. return (width, height)
  256. GIF_MAGICS = ('GIF87a', 'GIF89a')
  257. def analyze_gif(file):
  258. buffer = file.read(10)
  259. file.seek(0)
  260. if len(buffer) != 10:
  261. return
  262. magic, width, height = struct.unpack('<6sHH', buffer)
  263. if magic not in GIF_MAGICS:
  264. return
  265. return (width, height)
  266. def make_thumbnail(filename, thumbnail, width, height, quality, convert):
  267. is_animated = False
  268. magickname = filename
  269. convert = convert or 'convert' # lol
  270. popen_array = [convert, '-resize', '%sx%s!' % (width, height),
  271. '-quality', str(quality)]
  272. if magickname.endswith(".gif"):
  273. identify = config.IDENTIFY_COMMAND
  274. gif_check = Popen([identify, '-format', '%n', magickname],
  275. stdout=PIPE).communicate()[0]
  276. try:
  277. if int(gif_check) > 1:
  278. magickname += '[0]'
  279. is_animated = True
  280. except ValueError:
  281. pass
  282. if is_animated:
  283. popen_array.extend([magickname, '-background', config.BG_ANIM_COLOR,
  284. '-gravity', 'Center',
  285. '-fill', config.FG_ANIM_COLOR,
  286. '-size', '100x15',
  287. 'label:Animated', '-append', thumbnail])
  288. height += 15
  289. else:
  290. popen_array.extend([magickname, thumbnail])
  291. process = Popen(popen_array)
  292. if process.wait() == 0 and os.path.exists(thumbnail) and \
  293. os.path.getsize(thumbnail) != 0:
  294. return width, height
  295. elif os.path.exists(thumbnail):
  296. os.unlink(thumbnail)
  297. return 0, 0
  298. # other methods supported by the original wakaba
  299. # but not by wakaba+desuchan aren't included here
  300. # because they suck.
  301. def get_cookie_from_request(request, key):
  302. return urllib.unquote(request.cookies.get(key, '')).decode('unicode-escape')
  303. def kwargs_from_params(request, params_arg=None, **params):
  304. '''Associate function to convert CGI request data with dictionary
  305. of parameter keys to a dictionary of keyword arguments for passing
  306. to an application function.
  307. Dictonary format: {'cookies': ['cookie_keys'],
  308. 'form': ['html_form_input_names'],
  309. 'file': ['file_keys']}
  310. Not all keys are necessary. Invalid keys are ignored.'''
  311. if params_arg:
  312. params = params_arg
  313. kwargs = {}
  314. if 'admin' in params.keys():
  315. kwargs['cookie'] = get_cookie_from_request(request, 'wakaadmin')
  316. if 'cookies' in params.keys():
  317. for param in params['cookies']:
  318. kwargs[param] = get_cookie_from_request(request, param)
  319. if 'form' in params.keys():
  320. for param in params['form']:
  321. kwargs[param] = request.values.get(param, '')
  322. if 'file' in params.keys():
  323. for param in params['file']:
  324. kwargs[param] = request.files.get(param, None)
  325. return kwargs