Files
reddit/r2/r2/controllers/toolbar.py
ketralnis 7fff900bbe February 2011 Merge
* Upgrade discount to 1.6.8
* Start reading Votes, Saves, and Hides from Cassandra (while still dual-writing all three)
* Some XSS fixes
* Significant Gold buying and gifting improvements
  - Move /api/ipn to /ipn
* Allow non-US countries to buy sponsored links
* Increase embed.ly scope
* redd.it support
* Allow postgres port number to be specified in ini file (this changes the format of the .ini file)
* Upgrade Cassandra to 0.7
  - Change g.urlcache to LinksByURL
  - Translate storage-conf.xml to cassandra.yaml
  - TTL support (and enable on Hides)
  - Move permacache keyspace to inside reddit keyspace
* The stalecache: a local memcached that contains slightly old information to speed up some lookups
* Switch to patched Paste that is hopefully download.gz-proof
* Don't store votes on things > 30 days old
* Many many bugfixes/small features
2011-02-23 12:00:44 -08:00

258 lines
9.3 KiB
Python

# The contents of this file are subject to the Common Public Attribution
# License Version 1.0. (the "License"); you may not use this file except in
# compliance with the License. You may obtain a copy of the License at
# http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
# License Version 1.1, but Sections 14 and 15 have been added to cover use of
# software over a computer network and provide for limited attribution for the
# Original Developer. In addition, Exhibit A has been modified to be consistent
# with Exhibit B.
#
# Software distributed under the License is distributed on an "AS IS" basis,
# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
# the specific language governing rights and limitations under the License.
#
# The Original Code is Reddit.
#
# The Original Developer is the Initial Developer. The Initial Developer of the
# Original Code is CondeNet, Inc.
#
# All portions of the code written by CondeNet are Copyright (c) 2006-2010
# CondeNet, Inc. All Rights Reserved.
################################################################################
from reddit_base import RedditController
from r2.lib.pages import *
from r2.models import *
from r2.lib.pages.things import wrap_links
from r2.lib.menus import CommentSortMenu
from r2.lib.filters import spaceCompress, safemarkdown
from r2.lib.memoize import memoize
from r2.lib.template_helpers import add_sr
from r2.lib import utils
from validator import *
from pylons import c, Response
import string
# strips /r/foo/, /s/, or both
strip_sr = re.compile('\A/r/[a-zA-Z0-9_-]+')
strip_s_path = re.compile('\A/s/')
leading_slash = re.compile('\A/+')
has_protocol = re.compile('\A[a-zA-Z_-]+:')
allowed_protocol = re.compile('\Ahttps?:')
need_insert_slash = re.compile('\Ahttps?:/[^/]')
def demangle_url(path):
# there's often some URL mangling done by the stack above us, so
# let's clean up the URL before looking it up
path = strip_sr.sub('', path)
path = strip_s_path.sub('', path)
path = leading_slash.sub("", path)
if has_protocol.match(path):
if not allowed_protocol.match(path):
return None
else:
path = 'http://%s' % path
if need_insert_slash.match(path):
path = string.replace(path, '/', '//', 1)
path = utils.sanitize_url(path)
return path
def force_html():
"""Because we can take URIs like /s/http://.../foo.png, and we can
guarantee that the toolbar will never be used with a non-HTML
render style, we don't want to interpret the extension from the
target URL. So here we rewrite Middleware's interpretation of
the extension to force it to be HTML
"""
c.render_style = 'html'
c.extension = None
c.content_type = 'text/html; charset=UTF-8'
def auto_expand_panel(link):
if not link.num_comments or link.is_self:
return False
else:
return c.user.pref_frame_commentspanel
class ToolbarController(RedditController):
allow_stylesheets = True
@validate(link1 = VByName('id'),
link2 = VLink('id', redirect = False))
def GET_goto(self, link1, link2):
"""Support old /goto?id= urls. deprecated"""
link = link2 if link2 else link1
if link:
return self.redirect(add_sr("/tb/" + link._id36))
return self.abort404()
@validate(link = VLink('id'))
def GET_tb(self, link):
from r2.lib.media import thumbnail_url
"/tb/$id36, show a given link with the toolbar"
if not link:
return self.abort404()
elif link.is_self or not link.subreddit_slow.can_view(c.user):
return self.redirect(link.url)
if link.has_thumbnail:
thumbnail = thumbnail_url(link)
else:
thumbnail = None
res = Frame(title = link.title,
url = link.url,
thumbnail = thumbnail,
fullname = link._fullname)
return spaceCompress(res.render())
def GET_s(self, rest):
"""/s/http://..., show a given URL with the toolbar. if it's
submitted, redirect to /tb/$id36"""
force_html()
path = demangle_url(request.fullpath)
if not path:
# it was malformed
self.abort404()
link = utils.link_from_url(path, multiple = False)
if c.cname and not c.authorized_cname:
# In this case, we make some bad guesses caused by the
# cname frame on unauthorised cnames.
# 1. User types http://foo.com/http://myurl?cheese=brie
# (where foo.com is an unauthorised cname)
# 2. We generate a frame that points to
# http://www.reddit.com/r/foo/http://myurl?cnameframe=0.12345&cheese=brie
# 3. Because we accept everything after the /r/foo/, and
# we've now parsed, modified, and reconstituted that
# URL to add cnameframe, we really can't make any good
# assumptions about what we've done to a potentially
# already broken URL, and we can't assume that we've
# rebuilt it in the way that it was originally
# submitted (if it was)
# We could try to work around this with more guesses (by
# having demangle_url try to remove that param, hoping
# that it's not already a malformed URL, and that we
# haven't re-ordered the GET params, removed
# double-slashes, etc), but for now, we'll just refuse to
# do this operation
return self.abort404()
if link:
# we were able to find it, let's send them to the
# link-id-based URL so that their URL is reusable
return self.redirect(add_sr("/tb/" + link._id36))
title = utils.domain(path)
res = Frame(title = title, url = path)
# we don't want clients to think that this URL is actually a
# valid URL for search-indexing or the like
c.response = Response()
c.response.status_code = 404
request.environ['usable_error_content'] = spaceCompress(res.render())
return c.response
@validate(link = VLink('id'))
def GET_comments(self, link):
if not link:
self.abort404()
if not link.subreddit_slow.can_view(c.user):
abort(403, 'forbidden')
links = list(wrap_links(link))
if not links:
# they aren't allowed to see this link
return self.abort(403, 'forbidden')
link = links[0]
wrapper = make_wrapper(render_class = StarkComment,
target = "_top")
b = TopCommentBuilder(link, CommentSortMenu.operator('confidence'),
wrap = wrapper)
listing = NestedListing(b, num = 10, # TODO: add config var
parent_name = link._fullname)
raw_bar = strings.comments_panel_text % dict(
fd_link=link.permalink)
md_bar = safemarkdown(raw_bar, target="_top")
res = RedditMin(content=CommentsPanel(link=link,
listing=listing.listing(),
expanded=auto_expand_panel(link),
infobar=md_bar))
return res.render()
@validate(link = VByName('id'),
url = nop('url'))
def GET_toolbar(self, link, url):
"""The visible toolbar, with voting buttons and all"""
if not link:
link = utils.link_from_url(url, multiple = False)
if link:
link = list(wrap_links(link, wrapper = FrameToolbar))
if link:
res = link[0]
elif url:
url = demangle_url(url)
if not url: # also check for validity
return self.abort404()
res = FrameToolbar(link = None,
title = None,
url = url,
expanded = False)
else:
return self.abort404()
return spaceCompress(res.render())
@validate(link = VByName('id'))
def GET_inner(self, link):
"""The intermediate frame that displays the comments side-bar
on one side and the link on the other"""
if not link:
return self.abort404()
res = InnerToolbarFrame(link = link, expanded = auto_expand_panel(link))
return spaceCompress(res.render())
@validate(link = VLink('linkoid'))
def GET_linkoid(self, link):
if not link:
return self.abort404()
return self.redirect(add_sr("/tb/" + link._id36))
slash_fixer = re.compile('(/s/https?:)/+')
@validate(urloid = nop('urloid'))
def GET_urloid(self, urloid):
# they got here from "/http://..."
path = demangle_url(request.fullpath)
if not path:
# malformed URL
self.abort404()
redir_path = add_sr("/s/" + path)
force_html()
# Redirect to http://reddit.com/s/http://google.com
# rather than http://reddit.com/s/http:/google.com
redir_path = self.slash_fixer.sub(r'\1///', redir_path, 1)
# ^^^
# 3=2 when it comes to self.redirect()
return self.redirect(redir_path)