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.
 
 
 
 
 
 

323 lines
10 KiB

  1. import re
  2. import misc
  3. import strings
  4. import str_format
  5. from util import WakaError, local
  6. class ValidationError(WakaError):
  7. pass
  8. class WakaPost(object):
  9. '''A post object that uses __slots__ for no good reason'''
  10. __slots__ = [
  11. # columns copied directly from model.board
  12. 'num', 'parent', 'timestamp', 'lasthit', 'ip', 'date', 'name', 'trip',
  13. 'email', 'subject', 'password', 'comment', 'size', 'md5',
  14. 'width', 'height', 'thumbnail', 'tn_width', 'tn_height', 'lastedit',
  15. 'lastedit_ip', '_admin_post', 'stickied', 'locked',
  16. # extensions
  17. 'abbrev', 'nofile', 'req_file', 'filename', 'req_no_format',
  18. 'killtrip', 'postfix', 'ninja'
  19. ]
  20. def __init__(self, rowproxy=None, **kwargs):
  21. self.date = ''
  22. self.filename = ''
  23. self.thumbnail = ''
  24. self.md5 = ''
  25. self.comment = ''
  26. self.name = ''
  27. self.email = ''
  28. self.subject = ''
  29. self.trip = ''
  30. self.password = ''
  31. self.lastedit = ''
  32. self.postfix = ''
  33. self.lasthit = 0
  34. self.ip = 0
  35. self.lastedit_ip = 0
  36. self.num = 0
  37. self.size = 0
  38. self.width = 0
  39. self.height = 0
  40. self.tn_width = 0
  41. self.tn_height = 0
  42. self.parent = 0
  43. self.timestamp = 0
  44. self.abbrev = 0
  45. # tri-state: True, False, or None for unset
  46. self.stickied = None
  47. self.locked = None
  48. self.nofile = None
  49. self.req_no_format = None
  50. self.ninja = None
  51. self.killtrip = None
  52. self.req_file = None
  53. self.admin_post = False
  54. if rowproxy:
  55. self.update(items=rowproxy.items())
  56. else:
  57. self.update(**kwargs)
  58. def __repr__(self):
  59. parent = ''
  60. if self.parent:
  61. parent = ' in thread %s' % self.parent
  62. return '<Post >>%s%s>' % (self.num, parent)
  63. @property
  64. def noko(self):
  65. return ((self.subject.lower() == 'noko') or
  66. (self.email.lower() == 'noko'))
  67. @property
  68. def db_values(self):
  69. '''Return a kwargs dict of values to set in the db'''
  70. return dict(
  71. parent=self.parent,
  72. timestamp=self.timestamp,
  73. ip=self.ip,
  74. date=self.date,
  75. name=self.name,
  76. trip=self.trip,
  77. email=self.email,
  78. subject=self.subject,
  79. password=self.password,
  80. comment=self.comment,
  81. image=self.filename,
  82. size=self.size,
  83. md5=self.md5,
  84. width=self.width,
  85. height=self.height,
  86. thumbnail=self.thumbnail,
  87. tn_width=self.tn_width,
  88. tn_height=self.tn_height,
  89. admin_post=self.admin_post,
  90. stickied=self.stickied,
  91. locked=self.locked,
  92. lastedit_ip=self.lastedit_ip,
  93. lasthit=self.lasthit,
  94. lastedit=self.lastedit)
  95. @classmethod
  96. def from_request(cls, request):
  97. '''Creates a Post object based on a request
  98. This function does not do ANY kind of validation!'''
  99. self = cls()
  100. # direct fields - assigned to attributes
  101. # same name in the input as the database
  102. for key in ['num', 'parent', 'email', 'subject', 'comment',
  103. 'password', 'killtrip', 'postfix', 'ninja', 'nofile']:
  104. setattr(self, key, request.values.get(key, ''))
  105. # modify these a bit here
  106. self.name = request.values.get('field1', '')
  107. self.admin_post = (request.values.get('adminpost', '0') == '1' or
  108. request.values.get('adminedit', '0') == '1')
  109. self.stickied = request.values.get('sticky', '0') == '1'
  110. self.locked = request.values.get('lock', '0') == '1'
  111. self.req_no_format = request.values.get('no_format', '0') == '1'
  112. self.req_file = request.files.get('file', None)
  113. return self
  114. @classmethod
  115. def copy(cls, instance):
  116. '''Copies all the attributes of another instance into a new one'''
  117. return cls().merge(instance)
  118. def merge(self, other, which='all'):
  119. '''Merges only the attributes relevant to edited posts
  120. which: either 'all', 'request', or a list of keys'''
  121. keys = []
  122. if which == 'all':
  123. keys = WakaPost.__slots__
  124. elif which == 'request':
  125. keys = ['num', 'parent', 'email', 'subject', 'comment',
  126. 'password', 'killtrip', 'postfix', 'ninja', 'nofile',
  127. 'name', 'admin_post', 'stickied', 'locked',
  128. 'req_no_format', 'req_file']
  129. elif type(which) == list:
  130. keys = which
  131. for key in keys:
  132. setattr(self, key, getattr(other, key, ''))
  133. return self
  134. def update(self, items=None, **kwargs):
  135. for key, value in (items or kwargs.iteritems()):
  136. setattr(self, key, value)
  137. @property
  138. def admin_post(self):
  139. return self._admin_post
  140. @admin_post.setter
  141. def admin_post(self, value):
  142. # TODO: database migration / unfucking
  143. self._admin_post = (value in (True, 1, 'yes', 'True', '1'))
  144. @property
  145. def image(self):
  146. return self.filename
  147. @image.setter
  148. def image(self, value):
  149. self.filename = value
  150. def set_ip(self, numip, editing=None):
  151. '''Sets the ip or the lastedit ip'''
  152. if editing:
  153. self.lastedit_ip = numip
  154. else:
  155. self.ip = numip
  156. def set_date(self, editing, date_style):
  157. '''Sets the date or lastedit according to the timestamp'''
  158. if not editing:
  159. self.date = misc.make_date(self.timestamp, date_style)
  160. else:
  161. self.date = editing.date
  162. if not self.ninja:
  163. self.lastedit = misc.make_date(self.timestamp, date_style)
  164. def set_tripcode(self, tripkey):
  165. '''Splits the name by the tripcode'''
  166. self.name, temp = misc.process_tripcode(self.name, tripkey)
  167. self.trip = self.trip or temp
  168. def validate(self, editing, admin_mode, options):
  169. '''Validates the post contents, raises ValidationError'''
  170. if not admin_mode:
  171. if self.req_no_format or \
  172. (self.stickied and not self.parent) or self.locked:
  173. # the user is not allowed to do this
  174. raise ValidationError(strings.NOTALLOWED)
  175. file_ = self.req_file
  176. if self.parent:
  177. if file_ and not options['ALLOW_IMAGE_REPLIES']:
  178. raise ValidationError(strings.NOTALLOWED)
  179. if not file_ and not options['ALLOW_TEXT_REPLIES']:
  180. raise ValidationError(strings.NOTALLOWED)
  181. else:
  182. if file_ and not options['ALLOW_IMAGES']:
  183. raise ValidationError(strings.NOTALLOWED)
  184. if not file_ and self.nofile and options['ALLOW_TEXTONLY']:
  185. raise ValidationError(strings.NOTALLOWED)
  186. try:
  187. if len(self.parent) == 0:
  188. self.parent = 0
  189. elif len(self.parent) > 10:
  190. raise ValueError
  191. else:
  192. self.parent = int(self.parent)
  193. except ValueError:
  194. raise ValidationError(strings.UNUSUAL)
  195. # Check for weird characters
  196. has_crlf = lambda x: '\n' in x or '\r' in x
  197. if [True for x in (self.name, self.email, self.subject)
  198. if has_crlf(x)]:
  199. raise ValidationError(strings.UNUSUAL)
  200. # Check for excessive amounts of text
  201. if (len(self.name) > options['MAX_FIELD_LENGTH'] or
  202. len(self.email) > options['MAX_FIELD_LENGTH'] or
  203. len(self.subject) > options['MAX_FIELD_LENGTH'] or
  204. len(self.comment) > options['MAX_COMMENT_LENGTH']):
  205. raise ValidationError(strings.TOOLONG)
  206. # check to make sure the user selected a file, or clicked the checkbox
  207. if (not editing and not self.parent and
  208. not self.req_file and not self.nofile):
  209. raise ValidationError(strings.NOPIC)
  210. # check for empty reply or empty text-only post
  211. if not self.comment.strip() and not self.req_file:
  212. raise ValidationError(strings.NOTEXT)
  213. # get file size, and check for limitations.
  214. if self.req_file:
  215. self.size = misc.get_filestorage_size(self.req_file)
  216. if self.size > (options['MAX_KB'] * 1024):
  217. raise ValidationError(strings.TOOBIG)
  218. if self.size == 0:
  219. raise ValidationError(strings.TOOBIGORNONE)
  220. def clean_fields(self, editing, admin_mode, options):
  221. '''Modifies fields to clean them'''
  222. # kill the name if anonymous posting is being enforced
  223. if options['FORCED_ANON']:
  224. self.name = ''
  225. self.trip = ''
  226. if self.email.lower() == 'sage':
  227. self.email = 'sage'
  228. else:
  229. self.email = ''
  230. # fix up the email/link, if it is not a generic URI already.
  231. if self.email and not re.search(r"(?:^\w+:)|(?:\:\/\/)", self.email):
  232. self.email = "mailto:" + self.email
  233. # clean up the inputs
  234. self.subject = str_format.clean_string(
  235. str_format.decode_string(self.subject))
  236. # format comment
  237. if not self.req_no_format:
  238. self.comment = str_format.format_comment(str_format.clean_string(
  239. str_format.decode_string(self.comment)))
  240. # insert default values for empty fields
  241. if not (self.name or self.trip):
  242. self.name = options['S_ANONAME']
  243. self.subject = self.subject or options['S_ANOTITLE']
  244. self.comment = self.comment or options['S_ANOTEXT']
  245. def process_file(self, board, editing):
  246. '''Wrapper around board.process_file to use wakapost'''
  247. self.filename, self.md5, self.width, self.height, \
  248. self.thumbnail, self.tn_width, self.tn_height = \
  249. board.process_file(self.req_file, self.timestamp,
  250. self.parent, editing is not None)
  251. def make_post_cookies(self, options, url):
  252. '''Sets the name, email and password cookies'''
  253. c_name = self.name
  254. c_email = self.email
  255. c_password = self.password
  256. autopath = options['COOKIE_PATH']
  257. if autopath == 'current':
  258. path = url
  259. elif autopath == 'parent':
  260. path = local.environ['waka.rootpath']
  261. else:
  262. path = '/'
  263. misc.make_cookies(name=c_name, email=c_email, password=c_password,
  264. path=path) # yum !