PageRenderTime 155ms CodeModel.GetById 71ms RepoModel.GetById 0ms app.codeStats 1ms

/r2/r2/controllers/validator/validator.py

https://github.com/stevewilber/reddit
Python | 1911 lines | 1822 code | 23 blank | 66 comment | 21 complexity | af511162ea14d7fe1e1dfa0cd03f59ee MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0

Large files files are truncated, but you can click here to view the full file

  1. # The contents of this file are subject to the Common Public Attribution
  2. # License Version 1.0. (the "License"); you may not use this file except in
  3. # compliance with the License. You may obtain a copy of the License at
  4. # http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
  5. # License Version 1.1, but Sections 14 and 15 have been added to cover use of
  6. # software over a computer network and provide for limited attribution for the
  7. # Original Developer. In addition, Exhibit A has been modified to be consistent
  8. # with Exhibit B.
  9. #
  10. # Software distributed under the License is distributed on an "AS IS" basis,
  11. # WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
  12. # the specific language governing rights and limitations under the License.
  13. #
  14. # The Original Code is reddit.
  15. #
  16. # The Original Developer is the Initial Developer. The Initial Developer of
  17. # the Original Code is reddit Inc.
  18. #
  19. # All portions of the code written by reddit are Copyright (c) 2006-2012 reddit
  20. # Inc. All Rights Reserved.
  21. ###############################################################################
  22. from pylons import c, g, request, response
  23. from pylons.i18n import _
  24. from pylons.controllers.util import abort
  25. from r2.config.extensions import api_type
  26. from r2.lib import utils, captcha, promote, totp
  27. from r2.lib.filters import unkeep_space, websafe, _force_unicode
  28. from r2.lib.filters import markdown_souptest
  29. from r2.lib.db import tdb_cassandra
  30. from r2.lib.db.operators import asc, desc
  31. from r2.lib.template_helpers import add_sr
  32. from r2.lib.jsonresponse import json_respond, JQueryResponse, JsonResponse
  33. from r2.lib.log import log_text
  34. from r2.models import *
  35. from r2.lib.authorize import Address, CreditCard
  36. from r2.lib.utils import constant_time_compare
  37. from r2.controllers.errors import errors, UserRequiredException
  38. from r2.controllers.errors import VerifiedUserRequiredException
  39. from r2.controllers.errors import GoldRequiredException
  40. from copy import copy
  41. from datetime import datetime, timedelta
  42. from curses.ascii import isprint
  43. import re, inspect
  44. import pycountry
  45. from itertools import chain
  46. def visible_promo(article):
  47. is_promo = getattr(article, "promoted", None) is not None
  48. is_author = (c.user_is_loggedin and
  49. c.user._id == article.author_id)
  50. # subreddit discovery links are visible even without a live campaign
  51. if article._fullname in g.live_config['sr_discovery_links']:
  52. return True
  53. # promos are visible only if comments are not disabled and the
  54. # user is either the author or the link is live/previously live.
  55. if is_promo:
  56. return (c.user_is_sponsor or
  57. is_author or
  58. (not article.disable_comments and
  59. article.promote_status >= PROMOTE_STATUS.promoted))
  60. # not a promo, therefore it is visible
  61. return True
  62. def can_view_link_comments(article):
  63. return (article.subreddit_slow.can_view(c.user) and
  64. visible_promo(article))
  65. def can_comment_link(article):
  66. return (article.subreddit_slow.can_comment(c.user) and
  67. visible_promo(article))
  68. class Validator(object):
  69. default_param = None
  70. def __init__(self, param=None, default=None, post=True, get=True, url=True,
  71. docs=None):
  72. if param:
  73. self.param = param
  74. else:
  75. self.param = self.default_param
  76. self.default = default
  77. self.post, self.get, self.url, self.docs = post, get, url, docs
  78. self.has_errors = False
  79. def set_error(self, error, msg_params={}, field=False, code=None):
  80. """
  81. Adds the provided error to c.errors and flags that it is come
  82. from the validator's param
  83. """
  84. if field is False:
  85. field = self.param
  86. c.errors.add(error, msg_params=msg_params, field=field, code=code)
  87. self.has_errors = True
  88. def param_docs(self):
  89. param_info = {}
  90. for param in filter(None, tup(self.param)):
  91. param_info[param] = None
  92. if self.docs:
  93. param_info.update(self.docs)
  94. return param_info
  95. def __call__(self, url):
  96. self.has_errors = False
  97. a = []
  98. if self.param:
  99. for p in utils.tup(self.param):
  100. if self.post and request.post.get(p):
  101. val = request.post[p]
  102. elif self.get and request.get.get(p):
  103. val = request.get[p]
  104. elif self.url and url.get(p):
  105. val = url[p]
  106. else:
  107. val = self.default
  108. a.append(val)
  109. try:
  110. return self.run(*a)
  111. except TypeError, e:
  112. if str(e).startswith('run() takes'):
  113. # Prepend our class name so we know *which* run()
  114. raise TypeError('%s.%s' % (type(self).__name__, str(e)))
  115. else:
  116. raise
  117. def build_arg_list(fn, env):
  118. """given a fn and and environment the builds a keyword argument list
  119. for fn"""
  120. kw = {}
  121. argspec = inspect.getargspec(fn)
  122. # if there is a **kw argument in the fn definition,
  123. # just pass along the environment
  124. if argspec[2]:
  125. kw = env
  126. #else for each entry in the arglist set the value from the environment
  127. else:
  128. #skip self
  129. argnames = argspec[0][1:]
  130. for name in argnames:
  131. if name in env:
  132. kw[name] = env[name]
  133. return kw
  134. def _make_validated_kw(fn, simple_vals, param_vals, env):
  135. for validator in simple_vals:
  136. validator(env)
  137. kw = build_arg_list(fn, env)
  138. for var, validator in param_vals.iteritems():
  139. kw[var] = validator(env)
  140. return kw
  141. def set_api_docs(fn, simple_vals, param_vals):
  142. doc = fn._api_doc = getattr(fn, '_api_doc', {})
  143. param_info = doc.get('parameters', {})
  144. for validator in chain(simple_vals, param_vals.itervalues()):
  145. param_info.update(validator.param_docs())
  146. doc['parameters'] = param_info
  147. make_validated_kw = _make_validated_kw
  148. def validate(*simple_vals, **param_vals):
  149. def val(fn):
  150. @utils.wraps_api(fn)
  151. def newfn(self, *a, **env):
  152. try:
  153. kw = _make_validated_kw(fn, simple_vals, param_vals, env)
  154. return fn(self, *a, **kw)
  155. except UserRequiredException:
  156. return self.intermediate_redirect('/login')
  157. except VerifiedUserRequiredException:
  158. return self.intermediate_redirect('/verify')
  159. set_api_docs(newfn, simple_vals, param_vals)
  160. return newfn
  161. return val
  162. def api_validate(response_type=None):
  163. """
  164. Factory for making validators for API calls, since API calls come
  165. in two flavors: responsive and unresponsive. The machinary
  166. associated with both is similar, and the error handling identical,
  167. so this function abstracts away the kw validation and creation of
  168. a Json-y responder object.
  169. """
  170. def wrap(response_function):
  171. def _api_validate(*simple_vals, **param_vals):
  172. def val(fn):
  173. @utils.wraps_api(fn)
  174. def newfn(self, *a, **env):
  175. renderstyle = request.params.get("renderstyle")
  176. if renderstyle:
  177. c.render_style = api_type(renderstyle)
  178. elif not c.extension:
  179. # if the request URL included an extension, don't
  180. # touch the render_style, since it was already set by
  181. # set_extension. if no extension was provided, default
  182. # to response_type.
  183. c.render_style = api_type(response_type)
  184. # generate a response object
  185. if response_type == "html" and not request.params.get('api_type') == "json":
  186. responder = JQueryResponse()
  187. else:
  188. responder = JsonResponse()
  189. c.response_content_type = responder.content_type
  190. try:
  191. kw = _make_validated_kw(fn, simple_vals, param_vals, env)
  192. return response_function(self, fn, responder,
  193. simple_vals, param_vals, *a, **kw)
  194. except UserRequiredException:
  195. responder.send_failure(errors.USER_REQUIRED)
  196. return self.api_wrapper(responder.make_response())
  197. except VerifiedUserRequiredException:
  198. responder.send_failure(errors.VERIFIED_USER_REQUIRED)
  199. return self.api_wrapper(responder.make_response())
  200. set_api_docs(newfn, simple_vals, param_vals)
  201. return newfn
  202. return val
  203. return _api_validate
  204. return wrap
  205. @api_validate("html")
  206. def noresponse(self, self_method, responder, simple_vals, param_vals, *a, **kw):
  207. self_method(self, *a, **kw)
  208. return self.api_wrapper({})
  209. @api_validate("html")
  210. def textresponse(self, self_method, responder, simple_vals, param_vals, *a, **kw):
  211. return self_method(self, *a, **kw)
  212. @api_validate()
  213. def json_validate(self, self_method, responder, simple_vals, param_vals, *a, **kw):
  214. if c.extension != 'json':
  215. abort(404)
  216. val = self_method(self, responder, *a, **kw)
  217. if val is None:
  218. val = responder.make_response()
  219. return self.api_wrapper(val)
  220. def _validatedForm(self, self_method, responder, simple_vals, param_vals,
  221. *a, **kw):
  222. # generate a form object
  223. form = responder(request.POST.get('id', "body"))
  224. # clear out the status line as a courtesy
  225. form.set_html(".status", "")
  226. # do the actual work
  227. val = self_method(self, form, responder, *a, **kw)
  228. # add data to the output on some errors
  229. for validator in simple_vals:
  230. if (isinstance(validator, VCaptcha) and
  231. form.has_errors('captcha', errors.BAD_CAPTCHA)):
  232. form.new_captcha()
  233. elif (isinstance(validator, VRatelimit) and
  234. form.has_errors('ratelimit', errors.RATELIMIT)):
  235. form.ratelimit(validator.seconds)
  236. if val:
  237. return val
  238. else:
  239. return self.api_wrapper(responder.make_response())
  240. @api_validate("html")
  241. def validatedForm(self, self_method, responder, simple_vals, param_vals,
  242. *a, **kw):
  243. return _validatedForm(self, self_method, responder, simple_vals, param_vals,
  244. *a, **kw)
  245. @api_validate("html")
  246. def validatedMultipartForm(self, self_method, responder, simple_vals,
  247. param_vals, *a, **kw):
  248. def wrapped_self_method(*a, **kw):
  249. val = self_method(*a, **kw)
  250. if val:
  251. return val
  252. else:
  253. return self.iframe_api_wrapper(responder.make_response())
  254. return _validatedForm(self, wrapped_self_method, responder, simple_vals,
  255. param_vals, *a, **kw)
  256. #### validators ####
  257. class nop(Validator):
  258. def run(self, x):
  259. return x
  260. class VLang(Validator):
  261. def run(self, lang):
  262. if lang in g.all_languages:
  263. return lang
  264. return g.lang
  265. class VRequired(Validator):
  266. def __init__(self, param, error, *a, **kw):
  267. Validator.__init__(self, param, *a, **kw)
  268. self._error = error
  269. def error(self, e = None):
  270. if not e: e = self._error
  271. if e:
  272. self.set_error(e)
  273. def run(self, item):
  274. if not item:
  275. self.error()
  276. else:
  277. return item
  278. class VThing(Validator):
  279. def __init__(self, param, thingclass, redirect = True, *a, **kw):
  280. Validator.__init__(self, param, *a, **kw)
  281. self.thingclass = thingclass
  282. self.redirect = redirect
  283. def run(self, thing_id):
  284. if thing_id:
  285. try:
  286. tid = int(thing_id, 36)
  287. thing = self.thingclass._byID(tid, True)
  288. if thing.__class__ != self.thingclass:
  289. raise TypeError("Expected %s, got %s" %
  290. (self.thingclass, thing.__class__))
  291. return thing
  292. except (NotFound, ValueError):
  293. if self.redirect:
  294. abort(404, 'page not found')
  295. else:
  296. return None
  297. class VLink(VThing):
  298. def __init__(self, param, redirect = True, *a, **kw):
  299. VThing.__init__(self, param, Link, redirect=redirect, *a, **kw)
  300. class VPromoCampaign(VThing):
  301. def __init__(self, param, redirect = True, *a, **kw):
  302. VThing.__init__(self, param, PromoCampaign, *a, **kw)
  303. class VCommentByID(VThing):
  304. def __init__(self, param, redirect = True, *a, **kw):
  305. VThing.__init__(self, param, Comment, redirect=redirect, *a, **kw)
  306. class VAd(VThing):
  307. def __init__(self, param, redirect = True, *a, **kw):
  308. VThing.__init__(self, param, Ad, redirect=redirect, *a, **kw)
  309. class VAdByCodename(Validator):
  310. def run(self, codename, required_fullname=None):
  311. if not codename:
  312. return self.set_error(errors.NO_TEXT)
  313. try:
  314. a = Ad._by_codename(codename)
  315. except NotFound:
  316. a = None
  317. if a and required_fullname and a._fullname != required_fullname:
  318. return self.set_error(errors.INVALID_OPTION)
  319. else:
  320. return a
  321. class VAward(VThing):
  322. def __init__(self, param, redirect = True, *a, **kw):
  323. VThing.__init__(self, param, Award, redirect=redirect, *a, **kw)
  324. class VAwardByCodename(Validator):
  325. def run(self, codename, required_fullname=None):
  326. if not codename:
  327. return self.set_error(errors.NO_TEXT)
  328. try:
  329. a = Award._by_codename(codename)
  330. except NotFound:
  331. a = None
  332. if a and required_fullname and a._fullname != required_fullname:
  333. return self.set_error(errors.INVALID_OPTION)
  334. else:
  335. return a
  336. class VTrophy(VThing):
  337. def __init__(self, param, redirect = True, *a, **kw):
  338. VThing.__init__(self, param, Trophy, redirect=redirect, *a, **kw)
  339. class VMessage(Validator):
  340. def run(self, message_id):
  341. if message_id:
  342. try:
  343. aid = int(message_id, 36)
  344. return Message._byID(aid, True)
  345. except (NotFound, ValueError):
  346. abort(404, 'page not found')
  347. class VCommentID(Validator):
  348. def run(self, cid):
  349. if cid:
  350. try:
  351. cid = int(cid, 36)
  352. return Comment._byID(cid, True)
  353. except (NotFound, ValueError):
  354. pass
  355. class VMessageID(Validator):
  356. def run(self, cid):
  357. if cid:
  358. try:
  359. cid = int(cid, 36)
  360. m = Message._byID(cid, True)
  361. if not m.can_view_slow():
  362. abort(403, 'forbidden')
  363. return m
  364. except (NotFound, ValueError):
  365. pass
  366. class VCount(Validator):
  367. def run(self, count):
  368. if count is None:
  369. count = 0
  370. try:
  371. return max(int(count), 0)
  372. except ValueError:
  373. return 0
  374. class VLimit(Validator):
  375. def __init__(self, param, default=25, max_limit=100, **kw):
  376. self.default_limit = default
  377. self.max_limit = max_limit
  378. Validator.__init__(self, param, **kw)
  379. def run(self, limit):
  380. default = c.user.pref_numsites
  381. if c.render_style in ("compact", api_type("compact")):
  382. default = self.default_limit # TODO: ini param?
  383. if limit is None:
  384. return default
  385. try:
  386. i = int(limit)
  387. except ValueError:
  388. return default
  389. return min(max(i, 1), self.max_limit)
  390. class VCssMeasure(Validator):
  391. measure = re.compile(r"\A\s*[\d\.]+\w{0,3}\s*\Z")
  392. def run(self, value):
  393. return value if value and self.measure.match(value) else ''
  394. subreddit_rx = re.compile(r"\A[A-Za-z0-9][A-Za-z0-9_]{2,20}\Z")
  395. def chksrname(x):
  396. #notice the space before reddit.com
  397. if x in ('friends', 'all', ' reddit.com'):
  398. return False
  399. try:
  400. return str(x) if x and subreddit_rx.match(x) else None
  401. except UnicodeEncodeError:
  402. return None
  403. class VLength(Validator):
  404. only_whitespace = re.compile(r"\A\s*\Z", re.UNICODE)
  405. def __init__(self, param, max_length,
  406. empty_error = errors.NO_TEXT,
  407. length_error = errors.TOO_LONG,
  408. **kw):
  409. Validator.__init__(self, param, **kw)
  410. self.max_length = max_length
  411. self.length_error = length_error
  412. self.empty_error = empty_error
  413. def run(self, text, text2 = ''):
  414. text = text or text2
  415. if self.empty_error and (not text or self.only_whitespace.match(text)):
  416. self.set_error(self.empty_error)
  417. elif len(text) > self.max_length:
  418. self.set_error(self.length_error, {'max_length': self.max_length})
  419. else:
  420. return text
  421. class VPrintable(VLength):
  422. def run(self, text, text2 = ''):
  423. text = VLength.run(self, text, text2)
  424. if text is None:
  425. return None
  426. try:
  427. if all(isprint(str(x)) for x in text):
  428. return str(text)
  429. except UnicodeEncodeError:
  430. pass
  431. self.set_error(errors.BAD_STRING)
  432. return None
  433. class VTitle(VLength):
  434. def __init__(self, param, max_length = 300, **kw):
  435. VLength.__init__(self, param, max_length, **kw)
  436. class VMarkdown(VLength):
  437. def __init__(self, param, max_length = 10000, **kw):
  438. VLength.__init__(self, param, max_length, **kw)
  439. def run(self, text, text2 = ''):
  440. text = text or text2
  441. VLength.run(self, text)
  442. try:
  443. markdown_souptest(text)
  444. return text
  445. except ValueError:
  446. import sys
  447. user = "???"
  448. if c.user_is_loggedin:
  449. user = c.user.name
  450. g.log.error("HAX by %s: %s" % (user, text))
  451. s = sys.exc_info()
  452. # reraise the original error with the original stack trace
  453. raise s[1], None, s[2]
  454. class VSelfText(VMarkdown):
  455. def set_max_length(self, val):
  456. self._max_length = val
  457. def get_max_length(self):
  458. if c.site.link_type == "self":
  459. return self._max_length * 4
  460. return self._max_length * 1.5
  461. max_length = property(get_max_length, set_max_length)
  462. class VSubredditName(VRequired):
  463. def __init__(self, item, *a, **kw):
  464. VRequired.__init__(self, item, errors.BAD_SR_NAME, *a, **kw)
  465. def run(self, name):
  466. name = chksrname(name)
  467. if not name:
  468. return self.error()
  469. else:
  470. try:
  471. a = Subreddit._by_name(name)
  472. return self.error(errors.SUBREDDIT_EXISTS)
  473. except NotFound:
  474. return name
  475. class VSubredditTitle(Validator):
  476. def run(self, title):
  477. if not title:
  478. self.set_error(errors.NO_TITLE)
  479. elif len(title) > 100:
  480. self.set_error(errors.TITLE_TOO_LONG)
  481. else:
  482. return title
  483. class VSubredditDesc(Validator):
  484. def run(self, description):
  485. if description and len(description) > 500:
  486. self.set_error(errors.DESC_TOO_LONG)
  487. return unkeep_space(description or '')
  488. class VAccountByName(VRequired):
  489. def __init__(self, param, error = errors.USER_DOESNT_EXIST, *a, **kw):
  490. VRequired.__init__(self, param, error, *a, **kw)
  491. def run(self, name):
  492. if name:
  493. try:
  494. return Account._by_name(name)
  495. except NotFound: pass
  496. return self.error()
  497. def fullname_regex(thing_cls = None, multiple = False):
  498. pattern = "[%s%s]" % (Relation._type_prefix, Thing._type_prefix)
  499. if thing_cls:
  500. pattern += utils.to36(thing_cls._type_id)
  501. else:
  502. pattern += r"[0-9a-z]+"
  503. pattern += r"_[0-9a-z]+"
  504. if multiple:
  505. pattern = r"(%s *,? *)+" % pattern
  506. return re.compile(r"\A" + pattern + r"\Z")
  507. class VByName(Validator):
  508. # Lookup tdb_sql.Thing or tdb_cassandra.Thing objects by fullname.
  509. splitter = re.compile('[ ,]+')
  510. def __init__(self, param, thing_cls=None, multiple=False, limit=None,
  511. error=errors.NO_THING_ID, backend='sql', **kw):
  512. # Limit param only applies when multiple is True
  513. if not multiple and limit is not None:
  514. raise TypeError('multiple must be True when limit is set')
  515. self.re = fullname_regex(thing_cls)
  516. self.multiple = multiple
  517. self.limit = limit
  518. self._error = error
  519. self.backend = backend
  520. Validator.__init__(self, param, **kw)
  521. def run(self, items):
  522. if self.backend == 'cassandra':
  523. # tdb_cassandra.Thing objects can't use the regex
  524. if items and self.multiple:
  525. items = [item for item in self.splitter.split(items)]
  526. if self.limit and len(items) > self.limit:
  527. return self.set_error(errors.TOO_MANY_THING_IDS)
  528. if items:
  529. try:
  530. return tdb_cassandra.Thing._by_fullname(items, return_dict=False)
  531. except NotFound:
  532. pass
  533. else:
  534. if items and self.multiple:
  535. items = [item for item in self.splitter.split(items)
  536. if item and self.re.match(item)]
  537. if self.limit and len(items) > self.limit:
  538. return self.set_error(errors.TOO_MANY_THING_IDS)
  539. if items and (self.multiple or self.re.match(items)):
  540. try:
  541. return Thing._by_fullname(items, return_dict=False,
  542. data=True)
  543. except NotFound:
  544. pass
  545. return self.set_error(self._error)
  546. def param_docs(self):
  547. return {
  548. self.param: _('an existing thing id')
  549. }
  550. class VByNameIfAuthor(VByName):
  551. def run(self, fullname):
  552. thing = VByName.run(self, fullname)
  553. if thing:
  554. if not thing._loaded: thing._load()
  555. if c.user_is_loggedin and thing.author_id == c.user._id:
  556. return thing
  557. return self.set_error(errors.NOT_AUTHOR)
  558. class VCaptcha(Validator):
  559. default_param = ('iden', 'captcha')
  560. def run(self, iden, solution):
  561. if c.user.needs_captcha():
  562. valid_captcha = captcha.valid_solution(iden, solution)
  563. if not valid_captcha:
  564. self.set_error(errors.BAD_CAPTCHA)
  565. g.stats.action_event_count("captcha", valid_captcha)
  566. class VUser(Validator):
  567. def run(self, password = None):
  568. if not c.user_is_loggedin:
  569. raise UserRequiredException
  570. if (password is not None) and not valid_password(c.user, password):
  571. self.set_error(errors.WRONG_PASSWORD)
  572. class VModhash(Validator):
  573. default_param = 'uh'
  574. def __init__(self, param=None, fatal=True, *a, **kw):
  575. Validator.__init__(self, param, *a, **kw)
  576. self.fatal = fatal
  577. def run(self, uh):
  578. pass
  579. def param_docs(self):
  580. return {
  581. self.param: _('a modhash')
  582. }
  583. class VVotehash(Validator):
  584. def run(self, vh, thing_name):
  585. return True
  586. class VAdmin(Validator):
  587. def run(self):
  588. if not c.user_is_admin:
  589. abort(404, "page not found")
  590. class VAdminOrAdminSecret(VAdmin):
  591. def run(self, secret):
  592. '''If validation succeeds, return True if the secret was used,
  593. False otherwise'''
  594. if secret and constant_time_compare(secret, g.ADMINSECRET):
  595. return True
  596. super(VAdminOrAdminSecret, self).run()
  597. return False
  598. class VVerifiedUser(VUser):
  599. def run(self):
  600. VUser.run(self)
  601. if not c.user.email_verified:
  602. raise VerifiedUserRequiredException
  603. class VGold(VUser):
  604. def run(self):
  605. VUser.run(self)
  606. if not c.user.gold:
  607. abort(403, 'forbidden')
  608. class VSponsorAdmin(VVerifiedUser):
  609. """
  610. Validator which checks c.user_is_sponsor
  611. """
  612. def user_test(self, thing):
  613. return (thing.author_id == c.user._id)
  614. def run(self, link_id = None):
  615. VVerifiedUser.run(self)
  616. if c.user_is_sponsor:
  617. return
  618. abort(403, 'forbidden')
  619. class VSponsor(VVerifiedUser):
  620. """
  621. Not intended to be used as a check for c.user_is_sponsor, but
  622. rather is the user allowed to use the sponsored link system.
  623. If a link or campaign is passed in, it also checks whether the user is
  624. allowed to edit that particular sponsored link.
  625. """
  626. def user_test(self, thing):
  627. return (thing.author_id == c.user._id)
  628. def run(self, link_id=None, campaign_id=None):
  629. assert not (link_id and campaign_id), 'Pass link or campaign, not both'
  630. VVerifiedUser.run(self)
  631. if c.user_is_sponsor:
  632. return
  633. elif campaign_id:
  634. pc = None
  635. try:
  636. if '_' in campaign_id:
  637. pc = PromoCampaign._by_fullname(campaign_id, data=True)
  638. else:
  639. pc = PromoCampaign._byID36(campaign_id, data=True)
  640. except (NotFound, ValueError):
  641. pass
  642. if pc:
  643. link_id = pc.link_id
  644. if link_id:
  645. try:
  646. if '_' in link_id:
  647. t = Link._by_fullname(link_id, True)
  648. else:
  649. aid = int(link_id, 36)
  650. t = Link._byID(aid, True)
  651. if self.user_test(t):
  652. return
  653. except (NotFound, ValueError):
  654. pass
  655. abort(403, 'forbidden')
  656. class VTrafficViewer(VSponsor):
  657. def user_test(self, thing):
  658. return (VSponsor.user_test(self, thing) or
  659. promote.is_traffic_viewer(thing, c.user))
  660. class VSrModerator(Validator):
  661. def run(self):
  662. if not (c.user_is_loggedin and c.site.is_moderator(c.user)
  663. or c.user_is_admin):
  664. abort(403, "forbidden")
  665. class VFlairManager(VSrModerator):
  666. """Validates that a user is permitted to manage flair for a subreddit.
  667. Currently this is the same as VSrModerator. It's a separate class to act as
  668. a placeholder if we ever need to give mods a way to delegate this aspect of
  669. subreddit administration."""
  670. pass
  671. class VCanDistinguish(VByName):
  672. def run(self, thing_name, how):
  673. if c.user_is_admin:
  674. return True
  675. elif c.user_is_loggedin:
  676. item = VByName.run(self, thing_name)
  677. if item.author_id == c.user._id:
  678. # will throw a legitimate 500 if this isn't a link or
  679. # comment, because this should only be used on links and
  680. # comments
  681. subreddit = item.subreddit_slow
  682. if how in ("yes", "no") and subreddit.can_distinguish(c.user):
  683. return True
  684. elif how in ("special", "no") and c.user_special_distinguish:
  685. return True
  686. abort(403,'forbidden')
  687. class VSrCanAlter(VByName):
  688. def run(self, thing_name):
  689. if c.user_is_admin:
  690. return True
  691. elif c.user_is_loggedin:
  692. item = VByName.run(self, thing_name)
  693. if item.author_id == c.user._id:
  694. return True
  695. else:
  696. # will throw a legitimate 500 if this isn't a link or
  697. # comment, because this should only be used on links and
  698. # comments
  699. subreddit = item.subreddit_slow
  700. if subreddit.can_distinguish(c.user):
  701. return True
  702. abort(403,'forbidden')
  703. class VSrCanBan(VByName):
  704. def run(self, thing_name):
  705. if c.user_is_admin:
  706. return True
  707. elif c.user_is_loggedin:
  708. item = VByName.run(self, thing_name)
  709. # will throw a legitimate 500 if this isn't a link or
  710. # comment, because this should only be used on links and
  711. # comments
  712. subreddit = item.subreddit_slow
  713. if subreddit.is_moderator(c.user):
  714. return True
  715. abort(403,'forbidden')
  716. class VSrSpecial(VByName):
  717. def run(self, thing_name):
  718. if c.user_is_admin:
  719. return True
  720. elif c.user_is_loggedin:
  721. item = VByName.run(self, thing_name)
  722. # will throw a legitimate 500 if this isn't a link or
  723. # comment, because this should only be used on links and
  724. # comments
  725. subreddit = item.subreddit_slow
  726. if subreddit.is_special(c.user):
  727. return True
  728. abort(403,'forbidden')
  729. class VSubmitParent(VByName):
  730. def run(self, fullname, fullname2):
  731. #for backwards compatability (with iphone app)
  732. fullname = fullname or fullname2
  733. if fullname:
  734. parent = VByName.run(self, fullname)
  735. if parent:
  736. if c.user_is_loggedin and parent.author_id in c.user.enemies:
  737. self.set_error(errors.USER_BLOCKED)
  738. if parent._deleted:
  739. if isinstance(parent, Link):
  740. self.set_error(errors.DELETED_LINK)
  741. else:
  742. self.set_error(errors.DELETED_COMMENT)
  743. if isinstance(parent, Message):
  744. return parent
  745. else:
  746. link = parent
  747. if isinstance(parent, Comment):
  748. link = Link._byID(parent.link_id, data=True)
  749. if link and c.user_is_loggedin and can_comment_link(link):
  750. return parent
  751. #else
  752. abort(403, "forbidden")
  753. def param_docs(self):
  754. return {
  755. self.param[0]: _('id of parent thing')
  756. }
  757. class VSubmitSR(Validator):
  758. def __init__(self, srname_param, linktype_param=None, promotion=False):
  759. self.require_linktype = False
  760. self.promotion = promotion
  761. if linktype_param:
  762. self.require_linktype = True
  763. Validator.__init__(self, (srname_param, linktype_param))
  764. else:
  765. Validator.__init__(self, srname_param)
  766. def run(self, sr_name, link_type = None):
  767. if not sr_name:
  768. self.set_error(errors.SUBREDDIT_REQUIRED)
  769. return None
  770. try:
  771. sr = Subreddit._by_name(str(sr_name).strip())
  772. except (NotFound, AttributeError, UnicodeEncodeError):
  773. self.set_error(errors.SUBREDDIT_NOEXIST)
  774. return
  775. if not c.user_is_loggedin or not sr.can_submit(c.user, self.promotion):
  776. self.set_error(errors.SUBREDDIT_NOTALLOWED)
  777. return
  778. if self.require_linktype:
  779. if link_type not in ('link', 'self'):
  780. self.set_error(errors.INVALID_OPTION)
  781. return
  782. elif link_type == 'link' and sr.link_type == 'self':
  783. self.set_error(errors.NO_LINKS)
  784. return
  785. elif link_type == 'self' and sr.link_type == 'link':
  786. self.set_error(errors.NO_SELFS)
  787. return
  788. return sr
  789. class VSubscribeSR(VByName):
  790. def __init__(self, srid_param, srname_param):
  791. VByName.__init__(self, (srid_param, srname_param))
  792. def run(self, sr_id, sr_name):
  793. if sr_id:
  794. return VByName.run(self, sr_id)
  795. elif not sr_name:
  796. return
  797. try:
  798. sr = Subreddit._by_name(str(sr_name).strip())
  799. except (NotFound, AttributeError, UnicodeEncodeError):
  800. self.set_error(errors.SUBREDDIT_NOEXIST)
  801. return
  802. return sr
  803. MIN_PASSWORD_LENGTH = 3
  804. class VPassword(Validator):
  805. def run(self, password, verify):
  806. if not (password and len(password) >= MIN_PASSWORD_LENGTH):
  807. self.set_error(errors.BAD_PASSWORD)
  808. elif verify != password:
  809. self.set_error(errors.BAD_PASSWORD_MATCH)
  810. else:
  811. return password.encode('utf8')
  812. user_rx = re.compile(r"\A[\w-]{3,20}\Z", re.UNICODE)
  813. def chkuser(x):
  814. if x is None:
  815. return None
  816. try:
  817. if any(ch.isspace() for ch in x):
  818. return None
  819. return str(x) if user_rx.match(x) else None
  820. except TypeError:
  821. return None
  822. except UnicodeEncodeError:
  823. return None
  824. class VUname(VRequired):
  825. def __init__(self, item, *a, **kw):
  826. VRequired.__init__(self, item, errors.BAD_USERNAME, *a, **kw)
  827. def run(self, user_name):
  828. user_name = chkuser(user_name)
  829. if not user_name:
  830. return self.error(errors.BAD_USERNAME)
  831. else:
  832. try:
  833. a = Account._by_name(user_name, True)
  834. if a._deleted:
  835. return self.error(errors.USERNAME_TAKEN_DEL)
  836. else:
  837. return self.error(errors.USERNAME_TAKEN)
  838. except NotFound:
  839. return user_name
  840. class VLogin(VRequired):
  841. def __init__(self, item, *a, **kw):
  842. VRequired.__init__(self, item, errors.WRONG_PASSWORD, *a, **kw)
  843. def run(self, user_name, password):
  844. user_name = chkuser(user_name)
  845. user = None
  846. if user_name:
  847. try:
  848. str(password)
  849. except UnicodeEncodeError:
  850. password = password.encode('utf8')
  851. user = valid_login(user_name, password)
  852. if not user:
  853. self.error()
  854. return False
  855. return user
  856. class VThrottledLogin(VLogin):
  857. def __init__(self, *args, **kwargs):
  858. VLogin.__init__(self, *args, **kwargs)
  859. self.vdelay = VDelay("login")
  860. self.vlength = VLength("user", max_length=100)
  861. def run(self, username, password):
  862. if username:
  863. username = username.strip()
  864. username = self.vlength.run(username)
  865. self.vdelay.run()
  866. if (errors.RATELIMIT, "vdelay") in c.errors:
  867. return False
  868. user = VLogin.run(self, username, password)
  869. if login_throttle(username, wrong_password=not user):
  870. VDelay.record_violation("login", seconds=1, growfast=True)
  871. c.errors.add(errors.WRONG_PASSWORD, field=self.param[1])
  872. else:
  873. return user
  874. class VSanitizedUrl(Validator):
  875. def run(self, url):
  876. return utils.sanitize_url(url)
  877. def param_docs(self):
  878. return {self.param: _("a valid URL")}
  879. class VUrl(VRequired):
  880. def __init__(self, item, allow_self = True, lookup = True, *a, **kw):
  881. self.allow_self = allow_self
  882. self.lookup = lookup
  883. VRequired.__init__(self, item, errors.NO_URL, *a, **kw)
  884. def run(self, url, sr = None, resubmit=False):
  885. if sr is None and not isinstance(c.site, FakeSubreddit):
  886. sr = c.site
  887. elif sr:
  888. try:
  889. sr = Subreddit._by_name(str(sr))
  890. except (NotFound, UnicodeEncodeError):
  891. self.set_error(errors.SUBREDDIT_NOEXIST)
  892. sr = None
  893. else:
  894. sr = None
  895. if not url:
  896. return self.error(errors.NO_URL)
  897. url = utils.sanitize_url(url)
  898. if not url:
  899. return self.error(errors.BAD_URL)
  900. if url == 'self':
  901. if self.allow_self:
  902. return url
  903. elif not self.lookup or resubmit:
  904. return url
  905. elif url:
  906. try:
  907. l = Link._by_url(url, sr)
  908. self.error(errors.ALREADY_SUB)
  909. return utils.tup(l)
  910. except NotFound:
  911. return url
  912. return self.error(errors.BAD_URL)
  913. def param_docs(self):
  914. if isinstance(self.param, (list, tuple)):
  915. param_names = self.param
  916. else:
  917. param_names = [self.param]
  918. params = {}
  919. try:
  920. params[param_names[0]] = _('a valid URL')
  921. params[param_names[1]] = _('a subreddit')
  922. params[param_names[2]] = _('boolean value')
  923. except IndexError:
  924. pass
  925. return params
  926. class VShamedDomain(Validator):
  927. def run(self, url):
  928. if not url:
  929. return
  930. is_shamed, domain, reason = is_shamed_domain(url, request.ip)
  931. if is_shamed:
  932. self.set_error(errors.DOMAIN_BANNED, dict(domain=domain,
  933. reason=reason))
  934. class VExistingUname(VRequired):
  935. def __init__(self, item, *a, **kw):
  936. VRequired.__init__(self, item, errors.NO_USER, *a, **kw)
  937. def run(self, name):
  938. if name and name.startswith('~') and c.user_is_admin:
  939. try:
  940. user_id = int(name[1:])
  941. return Account._byID(user_id, True)
  942. except (NotFound, ValueError):
  943. self.error(errors.USER_DOESNT_EXIST)
  944. # make sure the name satisfies our user name regexp before
  945. # bothering to look it up.
  946. name = chkuser(name)
  947. if name:
  948. try:
  949. return Account._by_name(name)
  950. except NotFound:
  951. self.error(errors.USER_DOESNT_EXIST)
  952. else:
  953. self.error()
  954. def param_docs(self):
  955. return {
  956. self.param: _('the name of an existing user')
  957. }
  958. class VMessageRecipient(VExistingUname):
  959. def run(self, name):
  960. if not name:
  961. return self.error()
  962. is_subreddit = False
  963. if name.startswith('/r/'):
  964. name = name[3:]
  965. is_subreddit = True
  966. elif name.startswith('#'):
  967. name = name[1:]
  968. is_subreddit = True
  969. if is_subreddit:
  970. try:
  971. s = Subreddit._by_name(name)
  972. if isinstance(s, FakeSubreddit):
  973. raise NotFound, "fake subreddit"
  974. if s._spam:
  975. raise NotFound, "banned community"
  976. return s
  977. except NotFound:
  978. self.set_error(errors.SUBREDDIT_NOEXIST)
  979. else:
  980. account = VExistingUname.run(self, name)
  981. if account and account._id in c.user.enemies:
  982. self.set_error(errors.USER_BLOCKED)
  983. else:
  984. return account
  985. class VUserWithEmail(VExistingUname):
  986. def run(self, name):
  987. user = VExistingUname.run(self, name)
  988. if not user or not hasattr(user, 'email') or not user.email:
  989. return self.error(errors.NO_EMAIL_FOR_USER)
  990. return user
  991. class VBoolean(Validator):
  992. def run(self, val):
  993. lv = str(val).lower()
  994. if lv == 'off' or lv == '' or lv[0] in ("f", "n"):
  995. return False
  996. return bool(val)
  997. def param_docs(self):
  998. return {
  999. self.param: _('boolean value')
  1000. }
  1001. class VNumber(Validator):
  1002. def __init__(self, param, min=None, max=None, coerce = True,
  1003. error=errors.BAD_NUMBER, num_default=None,
  1004. *a, **kw):
  1005. self.min = self.cast(min) if min is not None else None
  1006. self.max = self.cast(max) if max is not None else None
  1007. self.coerce = coerce
  1008. self.error = error
  1009. self.num_default = num_default
  1010. Validator.__init__(self, param, *a, **kw)
  1011. def cast(self, val):
  1012. raise NotImplementedError
  1013. def run(self, val):
  1014. if not val:
  1015. return self.num_default
  1016. try:
  1017. val = self.cast(val)
  1018. if self.min is not None and val < self.min:
  1019. if self.coerce:
  1020. val = self.min
  1021. else:
  1022. raise ValueError, ""
  1023. elif self.max is not None and val > self.max:
  1024. if self.coerce:
  1025. val = self.max
  1026. else:
  1027. raise ValueError, ""
  1028. return val
  1029. except ValueError:
  1030. if self.max is None and self.min is None:
  1031. range = ""
  1032. elif self.max is None:
  1033. range = _("%(min)d to any") % dict(min=self.min)
  1034. elif self.min is None:
  1035. range = _("any to %(max)d") % dict(max=self.max)
  1036. else:
  1037. range = _("%(min)d to %(max)d") % dict(min=self.min, max=self.max)
  1038. self.set_error(self.error, msg_params=dict(range=range))
  1039. class VInt(VNumber):
  1040. def cast(self, val):
  1041. return int(val)
  1042. class VFloat(VNumber):
  1043. def cast(self, val):
  1044. return float(val)
  1045. class VCssName(Validator):
  1046. """
  1047. returns a name iff it consists of alphanumeric characters and
  1048. possibly "-", and is below the length limit.
  1049. """
  1050. r_css_name = re.compile(r"\A[a-zA-Z0-9\-]{1,100}\Z")
  1051. def run(self, name):
  1052. if name:
  1053. if self.r_css_name.match(name):
  1054. return name
  1055. else:
  1056. self.set_error(errors.BAD_CSS_NAME)
  1057. return ''
  1058. class VMenu(Validator):
  1059. def __init__(self, param, menu_cls, remember = True, **kw):
  1060. self.nav = menu_cls
  1061. self.remember = remember
  1062. param = (menu_cls.name, param)
  1063. Validator.__init__(self, param, **kw)
  1064. def run(self, sort, where):
  1065. if self.remember:
  1066. pref = "%s_%s" % (where, self.nav.name)
  1067. user_prefs = copy(c.user.sort_options) if c.user else {}
  1068. user_pref = user_prefs.get(pref)
  1069. # check to see if a default param has been set
  1070. if not sort:
  1071. sort = user_pref
  1072. # validate the sort
  1073. if sort not in self.nav.options:
  1074. sort = self.nav.default
  1075. # commit the sort if changed and if this is a POST request
  1076. if (self.remember and c.user_is_loggedin and sort != user_pref
  1077. and request.method.upper() == 'POST'):
  1078. user_prefs[pref] = sort
  1079. c.user.sort_options = user_prefs
  1080. user = c.user
  1081. user._commit()
  1082. return sort
  1083. class VRatelimit(Validator):
  1084. def __init__(self, rate_user = False, rate_ip = False,
  1085. prefix = 'rate_', error = errors.RATELIMIT, *a, **kw):
  1086. self.rate_user = rate_user
  1087. self.rate_ip = rate_ip
  1088. self.prefix = prefix
  1089. self.error = error
  1090. self.seconds = None
  1091. Validator.__init__(self, *a, **kw)
  1092. def run (self):
  1093. from r2.models.admintools import admin_ratelimit
  1094. if g.disable_ratelimit:
  1095. return
  1096. if c.user_is_loggedin and not admin_ratelimit(c.user):
  1097. return
  1098. to_check = []
  1099. if self.rate_user and c.user_is_loggedin:
  1100. to_check.append('user' + str(c.user._id36))
  1101. if self.rate_ip:
  1102. to_check.append('ip' + str(request.ip))
  1103. r = g.cache.get_multi(to_check, self.prefix)
  1104. if r:
  1105. expire_time = max(r.values())
  1106. time = utils.timeuntil(expire_time)
  1107. g.log.debug("rate-limiting %s from %s" % (self.prefix, r.keys()))
  1108. # when errors have associated field parameters, we'll need
  1109. # to add that here
  1110. if self.error == errors.RATELIMIT:
  1111. from datetime import datetime
  1112. delta = expire_time - datetime.now(g.tz)
  1113. self.seconds = delta.total_seconds()
  1114. if self.seconds < 3: # Don't ratelimit within three seconds
  1115. return
  1116. self.set_error(errors.RATELIMIT, {'time': time},
  1117. field = 'ratelimit')
  1118. else:
  1119. self.set_error(self.error)
  1120. @classmethod
  1121. def ratelimit(self, rate_user = False, rate_ip = False, prefix = "rate_",
  1122. seconds = None):
  1123. to_set = {}
  1124. if seconds is None:
  1125. seconds = g.RATELIMIT*60
  1126. expire_time = datetime.now(g.tz) + timedelta(seconds = seconds)
  1127. if rate_user and c.user_is_loggedin:
  1128. to_set['user' + str(c.user._id36)] = expire_time
  1129. if rate_ip:
  1130. to_set['ip' + str(request.ip)] = expire_time
  1131. g.cache.set_multi(to_set, prefix = prefix, time = seconds)
  1132. class VDelay(Validator):
  1133. def __init__(self, category, *a, **kw):
  1134. self.category = category
  1135. Validator.__init__(self, *a, **kw)
  1136. def run (self):
  1137. if g.disable_ratelimit:
  1138. return
  1139. key = "VDelay-%s-%s" % (self.category, request.ip)
  1140. prev_violations = g.cache.get(key)
  1141. if prev_violations:
  1142. time = utils.timeuntil(prev_violations["expire_time"])
  1143. if prev_violations["expire_time"] > datetime.now(g.tz):
  1144. self.set_error(errors.RATELIMIT, {'time': time},
  1145. field='vdelay')
  1146. @classmethod
  1147. def record_violation(self, category, seconds = None, growfast=False):
  1148. if seconds is None:
  1149. seconds = g.RATELIMIT*60
  1150. key = "VDelay-%s-%s" % (category, request.ip)
  1151. prev_violations = g.memcache.get(key)
  1152. if prev_violations is None:
  1153. prev_violations = dict(count=0)
  1154. num_violations = prev_violations["count"]
  1155. if growfast:
  1156. multiplier = 3 ** num_violations
  1157. else:
  1158. multiplier = 1
  1159. max_duration = 8 * 3600
  1160. duration = min(seconds * multiplier, max_duration)
  1161. expire_time = (datetime.now(g.tz) +
  1162. timedelta(seconds = duration))
  1163. prev_violations["expire_time"] = expire_time
  1164. prev_violations["duration"] = duration
  1165. prev_violations["count"] += 1
  1166. with g.make_lock("record_violation", "lock-" + key, timeout=5, verbose=False):
  1167. existing = g.memcache.get(key)
  1168. if existing and existing["count"] > prev_violations["count"]:
  1169. g.log.warning("Tried to set %s to count=%d, but found existing=%d"
  1170. % (key, prev_violations["count"], existing["count"]))
  1171. else:
  1172. g.cache.set(key, prev_violations, max_duration)
  1173. class VCommentIDs(Validator):
  1174. #id_str is a comma separated list of id36's
  1175. def run(self, id_str):
  1176. if id_str:
  1177. cids = [int(i, 36) for i in id_str.split(',')]
  1178. comments = Comment._byID(cids, data=True, return_dict = False)
  1179. return comments
  1180. return []
  1181. class CachedUser(object):
  1182. def __init__(self, cache_prefix, user, key):
  1183. self.cache_prefix = cache_prefix
  1184. self.user = user
  1185. self.key = key
  1186. def clear(self):
  1187. if self.key and self.cache_prefix:
  1188. g.cache.delete(str(self.cache_prefix + "_" + self.key))
  1189. class VOneTimeToken(Validator):
  1190. def __init__(self, model, param, *args, **kwargs):
  1191. self.model = model
  1192. Validator.__init__(self, param, *args, **kwargs)
  1193. def run(self, key):
  1194. token = self.model.get_token(key)
  1195. if token:
  1196. return token
  1197. else:
  1198. self.set_error(errors.EXPIRED)
  1199. return None
  1200. class VOneOf(Validator):
  1201. def __init__(self, param, options = (), *a, **kw):
  1202. Validator.__init__(self, param, *a, **kw)
  1203. self.options = options
  1204. def run(self, val):
  1205. if self.options and val not in self.options:
  1206. self.set_error(errors.INVALID_OPTION)
  1207. return self.default
  1208. else:
  1209. return val
  1210. def param_docs(self):
  1211. return {
  1212. self.param: _('one of (%s)') % ', '.join(self.options)
  1213. }
  1214. class VImageType(Validator):
  1215. def run(self, img_type):
  1216. if not img_type in ('png', 'jpg'):
  1217. return 'png'
  1218. return img_type
  1219. class VSubredditSponsorship(VInt):
  1220. max = 1
  1221. min = 0
  1222. def run(self, val):
  1223. s = super(VSubredditSponsorship, self).run(val)
  1224. if s and not c.user_is_admin:
  1225. abort(403, "forbidden")
  1226. return s
  1227. class ValidEmails(Validator):
  1228. """Validates a list of email addresses passed in as a string and
  1229. delineated by whitespace, ',' or ';'. Also validates quantity of
  1230. provided emails. Returns a list of valid email addresses on
  1231. success"""
  1232. separator = re.compile(r'[^\s,;]+')
  1233. email_re = re.compile(r'.+@.+\..+')
  1234. def __init__(self, param, num = 20, **kw):
  1235. self.num = num
  1236. Validator.__init__(self, param = param, **kw)
  1237. def run(self, emails0):
  1238. emails = set(self.separator.findall(emails0) if emails0 else [])
  1239. failures = set(e for e in emails if not self.email_re.match(e))
  1240. emails = emails - failures
  1241. # make sure the number of addresses does not exceed the max
  1242. if self.num > 0 and len(emails) + len(failures) > self.num:
  1243. # special case for 1: there should be no delineators at all, so
  1244. # send back original string to the user
  1245. if self.num == 1:
  1246. self.set_error(errors.BAD_EMAILS,
  1247. {'emails': '"%s"' % emails0})
  1248. # else report the number expected
  1249. else:
  1250. self.set_error(errors.TOO_MANY_EMAILS,
  1251. {'num': self.num})
  1252. # correct number, but invalid formatting
  1253. elif failures:
  1254. self.set_error(errors.BAD_EMAILS,
  1255. {'emails': ', '.join(failures)})
  1256. # no emails
  1257. elif not emails:
  1258. self.set_error(errors.NO_EMAILS)
  1259. else:
  1260. # return single email if one is expected, list otherwise
  1261. return list(emails)[0] if self.num == 1 else emails
  1262. class VCnameDomain(Validator):
  1263. domain_re = re.compile(r'\A([\w\-_]+\.)+[\w]+\Z')
  1264. def run(self, domain):
  1265. if (domain
  1266. and (not self.domain_re.match(domain)
  1267. or domain.endswith('.' + g.domain)
  1268. or domain.endswith('.' + g.media_domain)
  1269. or len(domain) > 300)):

Large files files are truncated, but you can click here to view the full file