PageRenderTime 38ms CodeModel.GetById 12ms RepoModel.GetById 0ms app.codeStats 0ms

/r2/r2/controllers/toolbar.py

https://github.com/stevewilber/reddit
Python | 275 lines | 219 code | 4 blank | 52 comment | 0 complexity | f05d11430e5c1db1adef229cc530c080 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, Apache-2.0
  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 reddit_base import RedditController
  23. from r2.lib.pages import *
  24. from r2.models import *
  25. from r2.lib.pages.things import wrap_links
  26. from r2.lib.menus import CommentSortMenu
  27. from r2.lib.filters import spaceCompress, safemarkdown
  28. from r2.lib.memoize import memoize
  29. from r2.lib.template_helpers import add_sr
  30. from r2.lib import utils
  31. from validator import *
  32. from pylons import c, Response
  33. from r2.models.admintools import is_shamed_domain
  34. import string
  35. # strips /r/foo/, /s/, or both
  36. strip_sr = re.compile('\A/r/[a-zA-Z0-9_-]+')
  37. strip_s_path = re.compile('\A/s/')
  38. leading_slash = re.compile('\A/+')
  39. has_protocol = re.compile('\A[a-zA-Z_-]+:')
  40. allowed_protocol = re.compile('\Ahttps?:')
  41. need_insert_slash = re.compile('\Ahttps?:/[^/]')
  42. def demangle_url(path):
  43. # there's often some URL mangling done by the stack above us, so
  44. # let's clean up the URL before looking it up
  45. path = strip_sr.sub('', path)
  46. path = strip_s_path.sub('', path)
  47. path = leading_slash.sub("", path)
  48. if has_protocol.match(path):
  49. if not allowed_protocol.match(path):
  50. return None
  51. else:
  52. path = 'http://%s' % path
  53. if need_insert_slash.match(path):
  54. path = string.replace(path, '/', '//', 1)
  55. path = utils.sanitize_url(path)
  56. return path
  57. def force_html():
  58. """Because we can take URIs like /s/http://.../foo.png, and we can
  59. guarantee that the toolbar will never be used with a non-HTML
  60. render style, we don't want to interpret the extension from the
  61. target URL. So here we rewrite Middleware's interpretation of
  62. the extension to force it to be HTML
  63. """
  64. c.render_style = 'html'
  65. c.extension = None
  66. c.content_type = 'text/html; charset=UTF-8'
  67. def auto_expand_panel(link):
  68. if not link.num_comments or link.is_self:
  69. return False
  70. else:
  71. return c.user.pref_frame_commentspanel
  72. class ToolbarController(RedditController):
  73. allow_stylesheets = True
  74. @validate(link1 = VByName('id'),
  75. link2 = VLink('id', redirect = False))
  76. def GET_goto(self, link1, link2):
  77. """Support old /goto?id= urls. deprecated"""
  78. link = link2 if link2 else link1
  79. if link:
  80. return self.redirect(add_sr("/tb/" + link._id36))
  81. return self.abort404()
  82. @validate(link = VLink('id'))
  83. def GET_tb(self, link):
  84. '''/tb/$id36, show a given link with the toolbar
  85. If the user doesn't have the toolbar enabled, redirect to comments
  86. page.
  87. '''
  88. from r2.lib.media import thumbnail_url
  89. if not link:
  90. return self.abort404()
  91. elif link.is_self:
  92. return self.redirect(link.url)
  93. elif not (c.user_is_loggedin and c.user.pref_frame):
  94. return self.redirect(link.make_permalink_slow(force_domain=True))
  95. # if the domain is shame-banned, bail out.
  96. if is_shamed_domain(link.url, request.ip)[0]:
  97. self.abort404()
  98. if not link.subreddit_slow.can_view(c.user):
  99. self.abort403()
  100. if link.has_thumbnail:
  101. thumbnail = thumbnail_url(link)
  102. else:
  103. thumbnail = None
  104. res = Frame(title = link.title,
  105. url = link.url,
  106. thumbnail = thumbnail,
  107. fullname = link._fullname)
  108. return spaceCompress(res.render())
  109. def GET_s(self, rest):
  110. """/s/http://..., show a given URL with the toolbar. if it's
  111. submitted, redirect to /tb/$id36"""
  112. force_html()
  113. path = demangle_url(request.fullpath)
  114. if not path:
  115. # it was malformed
  116. self.abort404()
  117. # if the domain is shame-banned, bail out.
  118. if is_shamed_domain(path, request.ip)[0]:
  119. self.abort404()
  120. link = utils.link_from_url(path, multiple = False)
  121. if c.cname and not c.authorized_cname:
  122. # In this case, we make some bad guesses caused by the
  123. # cname frame on unauthorised cnames.
  124. # 1. User types http://foo.com/http://myurl?cheese=brie
  125. # (where foo.com is an unauthorised cname)
  126. # 2. We generate a frame that points to
  127. # http://www.reddit.com/r/foo/http://myurl?cnameframe=0.12345&cheese=brie
  128. # 3. Because we accept everything after the /r/foo/, and
  129. # we've now parsed, modified, and reconstituted that
  130. # URL to add cnameframe, we really can't make any good
  131. # assumptions about what we've done to a potentially
  132. # already broken URL, and we can't assume that we've
  133. # rebuilt it in the way that it was originally
  134. # submitted (if it was)
  135. # We could try to work around this with more guesses (by
  136. # having demangle_url try to remove that param, hoping
  137. # that it's not already a malformed URL, and that we
  138. # haven't re-ordered the GET params, removed
  139. # double-slashes, etc), but for now, we'll just refuse to
  140. # do this operation
  141. return self.abort404()
  142. if link:
  143. # we were able to find it, let's send them to the
  144. # link-id-based URL so that their URL is reusable
  145. return self.redirect(add_sr("/tb/" + link._id36))
  146. title = utils.domain(path)
  147. res = Frame(title = title, url = path)
  148. # we don't want clients to think that this URL is actually a
  149. # valid URL for search-indexing or the like
  150. c.response = Response()
  151. c.response.status_code = 404
  152. request.environ['usable_error_content'] = spaceCompress(res.render())
  153. return c.response
  154. @validate(link = VLink('id'))
  155. def GET_comments(self, link):
  156. if not link:
  157. self.abort404()
  158. if not link.subreddit_slow.can_view(c.user):
  159. abort(403, 'forbidden')
  160. links = list(wrap_links(link))
  161. if not links:
  162. # they aren't allowed to see this link
  163. return self.abort(403, 'forbidden')
  164. link = links[0]
  165. wrapper = make_wrapper(render_class = StarkComment,
  166. target = "_top")
  167. b = TopCommentBuilder(link, CommentSortMenu.operator('confidence'),
  168. wrap = wrapper)
  169. listing = NestedListing(b, num = 10, # TODO: add config var
  170. parent_name = link._fullname)
  171. raw_bar = strings.comments_panel_text % dict(
  172. fd_link=link.permalink)
  173. md_bar = safemarkdown(raw_bar, target="_top")
  174. res = RedditMin(content=CommentsPanel(link=link,
  175. listing=listing.listing(),
  176. expanded=auto_expand_panel(link),
  177. infobar=md_bar))
  178. return res.render()
  179. @validate(link = VByName('id'),
  180. url = nop('url'))
  181. def GET_toolbar(self, link, url):
  182. """The visible toolbar, with voting buttons and all"""
  183. if not link:
  184. link = utils.link_from_url(url, multiple = False)
  185. if link:
  186. link = list(wrap_links(link, wrapper = FrameToolbar))
  187. if link:
  188. res = link[0]
  189. elif url:
  190. url = demangle_url(url)
  191. if not url: # also check for validity
  192. return self.abort404()
  193. res = FrameToolbar(link = None,
  194. title = None,
  195. url = url,
  196. expanded = False)
  197. else:
  198. return self.abort404()
  199. return spaceCompress(res.render())
  200. @validate(link = VByName('id'))
  201. def GET_inner(self, link):
  202. """The intermediate frame that displays the comments side-bar
  203. on one side and the link on the other"""
  204. if not link:
  205. return self.abort404()
  206. res = InnerToolbarFrame(link = link, expanded = auto_expand_panel(link))
  207. return spaceCompress(res.render())
  208. @validate(link = VLink('linkoid'))
  209. def GET_linkoid(self, link):
  210. if not link:
  211. return self.abort404()
  212. return self.redirect(add_sr("/tb/" + link._id36))
  213. slash_fixer = re.compile('(/s/https?:)/+')
  214. @validate(urloid = nop('urloid'))
  215. def GET_urloid(self, urloid):
  216. # they got here from "/http://..."
  217. path = demangle_url(request.fullpath)
  218. if not path:
  219. # malformed URL
  220. self.abort404()
  221. redir_path = add_sr("/s/" + path)
  222. force_html()
  223. # Redirect to http://reddit.com/s/http://google.com
  224. # rather than http://reddit.com/s/http:/google.com
  225. redir_path = self.slash_fixer.sub(r'\1///', redir_path, 1)
  226. # ^^^
  227. # 3=2 when it comes to self.redirect()
  228. return self.redirect(redir_path)