diff --git a/r2/r2/config/middleware.py b/r2/r2/config/middleware.py index 5a0798bb0..32ad7258e 100644 --- a/r2/r2/config/middleware.py +++ b/r2/r2/config/middleware.py @@ -6,16 +6,16 @@ # 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-2008 # CondeNet, Inc. All Rights Reserved. ################################################################################ @@ -42,28 +42,33 @@ import sys, tempfile, urllib, re, os, sha #from pylons.middleware import error_mapper -def error_mapper(code, message, environ, global_conf=None, **kw): - if environ.get('pylons.error_call'): - return None - else: - environ['pylons.error_call'] = True - - if global_conf is None: - global_conf = {} - codes = [401, 403, 404, 503] - if not asbool(global_conf.get('debug')): - codes.append(500) - if code in codes: - # StatusBasedForward expects a relative URL (no SCRIPT_NAME) - url = '/error/document/?%s' % (urllib.urlencode({'message': message, - 'code': code})) - return url +def error_mapper(code, message, environ, global_conf=None, **kw): + + if environ.get('pylons.error_call'): + return None + else: + environ['pylons.error_call'] = True + + if global_conf is None: + global_conf = {} + codes = [401, 403, 404, 503] + if not asbool(global_conf.get('debug')): + codes.append(500) + if code in codes: + # StatusBasedForward expects a relative URL (no SCRIPT_NAME) + d = dict(code = code, message = message) + if environ.get('REDDIT_CNAME'): + d['cnameframe'] = 1 + if environ.get('REDDIT_NAME'): + d['srname'] = environ.get('REDDIT_NAME') + url = '/error/document/?%s' % (urllib.urlencode(d)) + return url class DebugMiddleware(object): def __init__(self, app, keyword): self.app = app self.keyword = keyword - + def __call__(self, environ, start_response): def foo(*a, **kw): self.res = self.app(environ, start_response) @@ -99,12 +104,12 @@ class ProfilingMiddleware(DebugMiddleware): file = line = func = None try: - profile.runctx('execution_func()', + profile.runctx('execution_func()', globals(), locals(), tmpfile.name) out = StringIO() stats = Stats(tmpfile.name, stream=out) stats.sort_stats('time', 'calls') - + def parse_table(t, ncol): table = [] for s in t: @@ -112,7 +117,7 @@ class ProfilingMiddleware(DebugMiddleware): if len(t) > 1: table += [t[:ncol-1] + [' '.join(t[ncol-1:])]] return table - + def cmp(n): def _cmp(x, y): return 0 if x[n] == y[n] else 1 if x[n] < y[n] else -1 @@ -133,7 +138,7 @@ class ProfilingMiddleware(DebugMiddleware): stats.print_callees(query) stats.print_callers(query) statdata = out.getvalue() - + data = statdata.split(query) callee = data[2].split('->')[1].split('Ordered by')[0] callee = parse_table(callee.split('\n'), 4) @@ -166,7 +171,7 @@ class SourceViewMiddleware(DebugMiddleware): class DomainMiddleware(object): lang_re = re.compile(r"^\w\w(-\w\w)?$") - + def __init__(self, app): self.app = app @@ -180,19 +185,26 @@ class DomainMiddleware(object): sub_domains = environ['HTTP_HOST'].split(':')[0] except KeyError: sub_domains = "localhost" - + #If the domain doesn't end with base_domain, assume #this is a cname, and redirect to the frame controller. #Ignore localhost so paster shell still works. #If this is an error, don't redirect - if (not sub_domains.endswith(base_domain) + + if (not sub_domains.endswith(base_domain) and (not sub_domains == 'localhost')): environ['sub_domain'] = sub_domains - if (not environ.get('extension') - and (not environ['PATH_INFO'].startswith('/error'))): - environ['original_path'] = environ['PATH_INFO'] - environ['PATH_INFO'] = '/frame' - return self.app(environ, start_response) + if not environ.get('extension'): + if environ['PATH_INFO'].startswith('/frame'): + return self.app(environ, start_response) + elif ("redditSession" in environ.get('HTTP_COOKIE', '') + and environ['REQUEST_METHOD'] != 'POST' + and not environ['PATH_INFO'].startswith('/error')): + environ['original_path'] = environ['PATH_INFO'] + environ['PATH_INFO'] = '/frame' + return self.app(environ, start_response) + else: + environ['frameless_cname'] = True sub_domains = sub_domains[:-len(base_domain)].strip('.') sub_domains = sub_domains.split('.') @@ -223,13 +235,13 @@ class DomainMiddleware(object): r.headers['location'] = redir r.content = "" return r(environ, start_response) - + return self.app(environ, start_response) class SubredditMiddleware(object): sr_pattern = re.compile(r'^/r/([^/]+)') - + def __init__(self, app): self.app = app @@ -243,7 +255,7 @@ class SubredditMiddleware(object): environ['subreddit'] = 'r' return self.app(environ, start_response) -class ExtensionMiddleware(object): +class ExtensionMiddleware(object): ext_pattern = re.compile(r'\.([^/]+)$') def __init__(self, app): @@ -283,7 +295,7 @@ class RewriteMiddleware(object): environ['FULLPATH'] += '?' + qs return self.app(environ, start_response) - + class RequestLogMiddleware(object): def __init__(self, log_path, process_iden, app): self.log_path = log_path @@ -323,7 +335,7 @@ class LimitUploadSize(object): r.status_code = 500 r.content = 'request too big' return r(environ, start_response) - + return self.app(environ, start_response) #god this shit is disorganized and confusing diff --git a/r2/r2/config/routing.py b/r2/r2/config/routing.py index 5ac86b30f..497b9ca7f 100644 --- a/r2/r2/config/routing.py +++ b/r2/r2/config/routing.py @@ -111,7 +111,7 @@ def make_map(global_conf={}, app_conf={}): mc('/message/:where', controller='message', action='listing') mc('/:action', controller='front', - requirements=dict(action="password|random")) + requirements=dict(action="password|random|framebuster")) mc('/:action', controller='embed', requirements=dict(action="help|blog")) mc('/help/:anything', controller='embed', action='help') @@ -125,7 +125,7 @@ def make_map(global_conf={}, app_conf={}): action='resetpassword') mc('/post/:action', controller='post', - requirements=dict(action="options|over18|unlogged_options|optout|optin")) + requirements=dict(action="options|over18|unlogged_options|optout|optin|login|reg")) mc('/api/:action', controller='api') mc('/d/:what', controller='api', action='bookmarklet') @@ -149,7 +149,9 @@ def make_map(global_conf={}, app_conf={}): # error pages. It should likely stay at the top # to ensure that the error page is # displayed properly. - mc('error/:action/:id', controller='error') - + mc('/error/document/:id', controller='error', action="document") + + mc("/*url", controller='front', action='catchall') + return map diff --git a/r2/r2/controllers/api.py b/r2/r2/controllers/api.py index 83027201a..f8cc0132b 100644 --- a/r2/r2/controllers/api.py +++ b/r2/r2/controllers/api.py @@ -30,7 +30,7 @@ from r2.models import * from r2.models.subreddit import Default as DefaultSR import r2.models.thing_changes as tc -from r2.lib.utils import get_title, sanitize_url, timeuntil, set_last_modified +from r2.lib.utils import get_title, sanitize_url, timeuntil, set_last_modified, query_string from r2.lib.wrapped import Wrapped from r2.lib.pages import FriendList, ContributorList, ModList, \ BannedList, BoringPage, FormPage, NewLink, CssError, UploadedImage @@ -74,6 +74,10 @@ class ApiController(RedditController): def response_func(self, **kw): return self.sendstring(dumps(kw)) + @Json + def ajax_login_redirect(self, res, dest): + res._redirect("/login" + query_string(dict(dest=dest))) + def link_exists(self, url, sr, message = False): try: l = Link._by_url(url, sr) @@ -233,7 +237,7 @@ class ApiController(RedditController): ) def POST_submit(self, res, url, title, save, sr, ip): res._update('status', innerHTML = '') - if url: + if isinstance(url, str): res._update('url', value=url) should_ratelimit = sr.should_ratelimit(c.user, 'link') @@ -246,7 +250,7 @@ class ApiController(RedditController): if res._chk_errors((errors.NO_URL, errors.BAD_URL)): res._focus('url') elif res._chk_error(errors.ALREADY_SUB): - link = Link._by_url(url, sr) + link = url[0] res._redirect(link.already_submitted_link) #ratelimiter elif res._chk_error(errors.RATELIMIT): @@ -514,7 +518,6 @@ class ApiController(RedditController): res._update('status', innerHTML = _("see? you don't really want to leave")) - @Json @validate(VUser(), VModhash(), @@ -1041,39 +1044,39 @@ class ApiController(RedditController): res._send_things(a) - def GET_bookmarklet(self, what): + @validate(uh = nop('uh'), + action = VOneOf('what', ('like', 'dislike', 'save')), + links = VUrl(['u'])) + def GET_bookmarklet(self, action, uh, links): '''Controller for the functionality of the bookmarklets (not the distribution page)''' - action = '' - for type in ['like', 'dislike', 'save']: - if what.startswith(type): - action = type - break - - url = sanitize_url(request.get.u) - uh = request.get.get('uh', "") - try: - links = Link._by_url(url,None) - except: - links = [] + # the redirect handler will clobber the extension if not told otherwise + c.extension = "png" - Subreddit.load_subreddits(links, return_dict = False) - user = c.user if c.user_is_loggedin else None - links = [l for l in links if l.subreddit_slow.can_view(user)] - - if links and not c.user_is_loggedin: + if not c.user_is_loggedin: return self.redirect("/static/css_login.png") - elif links and c.user_is_loggedin: - if not c.user.valid_hash(uh): - return self.redirect("/static/css_update.png") - elif action in ['like', 'dislike']: - #vote up all of the links - for link in links: - Vote.vote(c.user, link, action == 'like', request.ip) - elif action == 'save': - link = max(links, key = lambda x: x._score) - link._save(c.user) - return self.redirect("/static/css_%sd.png" % action) + # check the modhash (or force them to get new bookmarlets) + elif not c.user.valid_hash(uh): + return self.redirect("/static/css_update.png") + # unlike most cases, if not already submitted, error. + elif errors.ALREADY_SUB in c.errors: + # preserve the subreddit if not Default + sr = c.site if not isinstance(c.site, FakeSubreddit) else None + + # check permissions on those links to make sure votes will count + Subreddit.load_subreddits(links, return_dict = False) + user = c.user if c.user_is_loggedin else None + links = [l for l in links if l.subreddit_slow.can_view(user)] + + if links: + if action in ['like', 'dislike']: + #vote up all of the links + for link in links: + Vote.vote(c.user, link, action == 'like', request.ip) + elif action == 'save': + link = max(links, key = lambda x: x._score) + link._save(c.user) + return self.redirect("/static/css_%sd.png" % action) return self.redirect("/static/css_submit.png") @@ -1223,3 +1226,5 @@ class ApiController(RedditController): if getattr(c.user, pref): setattr(c.user, "pref_" + ui_elem, False) c.user._commit() + + diff --git a/r2/r2/controllers/error.py b/r2/r2/controllers/error.py index 426c30f41..afd93b998 100644 --- a/r2/r2/controllers/error.py +++ b/r2/r2/controllers/error.py @@ -31,7 +31,7 @@ try: # place all r2 specific imports in here. If there is a code error, it'll get caught and # the stack trace won't be presented to the user in production from reddit_base import RedditController - from r2.models.subreddit import Default + from r2.models.subreddit import Default, Subreddit from r2.lib import pages from r2.lib.strings import rand_strings except Exception, e: @@ -110,14 +110,13 @@ class ErrorController(RedditController): def send403(self): c.response.status_code = 403 c.site = Default - title = _("forbidden (%(domain)s)") % dict(domain=c.domain) + title = _("forbidden (%(domain)s)") % dict(domain=g.domain) return pages.BoringPage(title, loginbox=False, show_sidebar = False, content=pages.ErrorPage()).render() def send404(self): c.response.status_code = 404 - if c.site._spam and not c.user_is_admin: msg = _("this reddit has been banned.") res = pages.BoringPage(msg, loginbox = False, @@ -125,7 +124,6 @@ class ErrorController(RedditController): content = pages.ErrorPage(message = msg)) return res.render() else: - c.site = Default ch=rand.choice(['a','b','c','d','e']) res = pages.BoringPage(_("page not found"), loginbox=False, @@ -136,6 +134,9 @@ class ErrorController(RedditController): def GET_document(self): try: code = request.GET.get('code', '') + srname = request.GET.get('srname', '') + if srname: + c.site = Subreddit._by_name(srname) if code == '403': return self.send403() elif code == '500': diff --git a/r2/r2/controllers/errors.py b/r2/r2/controllers/errors.py index 8949d71c2..86b6b6c68 100644 --- a/r2/r2/controllers/errors.py +++ b/r2/r2/controllers/errors.py @@ -96,10 +96,20 @@ class ErrorSet(object): def __getitem__(self, name): return self.errors[name] + + def __repr__(self): + return "" % list(self) + + def __iter__(self): + for x in self.errors: + yield x + + def _add(self, error_name, msg, msg_params = {}): + self.errors[error_name] = Error(error_name, msg, msg_params) def add(self, error_name, msg_params = {}): msg = error_list[error_name] - self.errors[error_name] = Error(error_name, msg, msg_params) + self._add(error_name, msg, msg_params = msg_params) def remove(self, error_name): if self.errors.has_key(error_name): diff --git a/r2/r2/controllers/front.py b/r2/r2/controllers/front.py index 75fdd9042..a27fcd4eb 100644 --- a/r2/r2/controllers/front.py +++ b/r2/r2/controllers/front.py @@ -27,7 +27,8 @@ from r2 import config from r2.models import * from r2.lib.pages import * from r2.lib.menus import * -from r2.lib.utils import to36, sanitize_url, check_cheating, title_to_url, query_string +from r2.lib.utils import to36, sanitize_url, check_cheating, title_to_url, query_string, UrlParser +from r2.lib.template_helpers import get_domain from r2.lib.emailer import has_opted_out, Email from r2.lib.db.operators import desc from r2.lib.strings import strings @@ -63,8 +64,8 @@ class FrontController(RedditController): new_url = "/r/%s%s" % (c.site.name, new_url) if comment: new_url = new_url + "/%s" % comment._id36 - if request.environ.get('extension'): - new_url = new_url + "/.%s" % request.environ.get('extension') + if c.extension: + new_url = new_url + "/.%s" % c.extension new_url = new_url + query_string(request.get) @@ -94,7 +95,7 @@ class FrontController(RedditController): password.""" done = False if not key and request.referer: - referer_path = request.referer.split(c.domain)[-1] + referer_path = request.referer.split(g.domain)[-1] done = referer_path.startswith(request.fullpath) elif not user: return self.abort404() @@ -331,10 +332,8 @@ class FrontController(RedditController): def GET_stylesheet(self): if hasattr(c.site,'stylesheet_contents') and not g.css_killswitch: self.check_modified(c.site,'stylesheet_contents') - - c.response.content = c.site.stylesheet_contents c.response_content_type = 'text/css' - + c.response.content = c.site.stylesheet_contents return c.response else: return self.abort404() @@ -474,10 +473,11 @@ class FrontController(RedditController): my_reddits_link = "/search%s" % query_string({'q': query}) all_reddits_link = "%s/search%s" % (subreddit.All.path, query_string({'q': query})) - infotext = strings.searching_a_reddit % {'reddit_name': c.site.name, - 'reddit_link': c.site.path, - 'my_reddits_link': my_reddits_link, - 'all_reddits_link': all_reddits_link} + d = {'reddit_name': c.site.name, + 'reddit_link': "http://%s/"%get_domain(cname = c.cname), + 'my_reddits_link': my_reddits_link, + 'all_reddits_link': all_reddits_link} + infotext = strings.searching_a_reddit % d else: infotext = None @@ -524,10 +524,6 @@ class FrontController(RedditController): """wipe login cookie and redirect to referer.""" self.logout() dest = request.referer or '/' - if c.cname: - dest = '/?cnameframe=1' - if not dest.startswith("http://"): - return self.redirect(c.site.path + dest) return self.redirect(dest) @@ -639,8 +635,17 @@ class FrontController(RedditController): sub_domain = request.environ.get('sub_domain') original_path = request.environ.get('original_path') sr = Subreddit._by_domain(sub_domain) - if sub_domain and sr and original_path: - return Cnameframe(original_path, sr.name, sr.title, sub_domain).render() - else: - return self.abort404() + return Cnameframe(original_path, sr, sub_domain).render() + + def GET_framebuster(self): + if c.site.domain and c.user_is_loggedin: + u = UrlParser(c.site.path + "/frame") + u.put_in_frame() + c.cname = True + return self.redirect(u.unparse()) + + return "fail" + + def GET_catchall(self): + return self.abort404() diff --git a/r2/r2/controllers/i18n.py b/r2/r2/controllers/i18n.py index f35903912..a9efc4841 100644 --- a/r2/r2/controllers/i18n.py +++ b/r2/r2/controllers/i18n.py @@ -103,7 +103,7 @@ class I18nController(RedditController): del _translations[key] return self.redirect("http://%s.%s/" % - (lang, c.domain)) + (lang, g.domain)) return abort(404, 'page not found') @validate(VTranslationEnabled(), @@ -165,7 +165,7 @@ class I18nController(RedditController): del _translations[key] return self.redirect("http://%s/?lang=%s" % - (c.domain, lang)) + (g.domain, lang)) whereto = request.post.get('bttn_num', '') if whereto: diff --git a/r2/r2/controllers/post.py b/r2/r2/controllers/post.py index 6fd55738a..8f75895fc 100644 --- a/r2/r2/controllers/post.py +++ b/r2/r2/controllers/post.py @@ -21,7 +21,7 @@ ################################################################################ from r2.lib.pages import * from api import ApiController -from r2.lib.utils import Storage, query_string +from r2.lib.utils import Storage, query_string, UrlParser from r2.lib.emailer import opt_in, opt_out from pylons import request, c, g from validator import * @@ -105,10 +105,11 @@ class PostController(ApiController): all_langs = nop('all-langs', default = 'all')) def POST_options(self, all_langs, pref_lang, **kw): self.set_options(all_langs, pref_lang, **kw) - q_string = {'done': 'true'} + u = UrlParser(c.site.path + "prefs") + u.update_query(done = 'true') if c.cname: - q_string['cnameframe'] = '1' - return self.redirect((request.referer or "/prefs") + query_string(q_string)) + u.put_in_frame() + return self.redirect(u.unparse()) def GET_over18(self): return BoringPage(_("over 18?"), @@ -126,7 +127,7 @@ class PostController(ApiController): ip_hash = sha.new(request.ip).hexdigest() c.response.set_cookie('over18', value = ip_hash, - domain = c.domain) + domain = g.domain if not c.frameless_cname else None) return self.redirect(dest) else: return self.redirect('/') @@ -153,3 +154,43 @@ class PostController(ApiController): msg_hash = msg_hash)).render() + def POST_login(self, *a, **kw): + res = ApiController.POST_login(self, *a, **kw) + c.render_style = "html" + c.response_content_type = "" + + errors = list(c.errors) + if errors: + for e in errors: + if not e.endswith("_login"): + msg = c.errors[e].message + c.errors.remove(e) + c.errors._add(e + "_login", msg) + + dest = request.post.get('dest', request.referer or '/') + return LoginPage(user_login = request.post.get('user_login'), + dest = dest).render() + + return self.redirect(res.redirect) + + def POST_reg(self, *a, **kw): + res = ApiController.POST_register(self, *a, **kw) + c.render_style = "html" + c.response_content_type = "" + + errors = list(c.errors) + if errors: + for e in errors: + if not e.endswith("_reg"): + msg = c.errors[e].message + c.errors.remove(e) + c.errors._add(e + "_reg", msg) + + dest = request.post.get('dest', request.referer or '/') + return LoginPage(user_reg = request.post.get('user_reg'), + dest = dest).render() + + return self.redirect(res.redirect) + + def GET_login(self, *a, **kw): + return self.redirect('/login' + query_string(dict(dest="/"))) diff --git a/r2/r2/controllers/reddit_base.py b/r2/r2/controllers/reddit_base.py index e3e60290f..97cf01487 100644 --- a/r2/r2/controllers/reddit_base.py +++ b/r2/r2/controllers/reddit_base.py @@ -105,7 +105,7 @@ def set_user_cookie(name, val): uname = c.user.name if c.user_is_loggedin else "" c.response.set_cookie(uname + '_' + name, value = val, - domain = g.domain) + domain = g.domain if not c.frameless_cname else None) def read_click_cookie(): if c.user_is_loggedin: @@ -129,7 +129,7 @@ def firsttime(): if not request.cookies.get("reddit_first"): c.response.set_cookie("reddit_first", "first", expires = NEVER, - domain = g.domain) + domain = g.domain if not c.frameless_cname else None) return True return False @@ -166,39 +166,42 @@ def set_subreddit(): abort(404, "not found") def set_content_type(): - extension = request.environ.get('extension') or \ - request.environ.get('reddit-domain-extension') or \ - 'html' + c.extension = request.environ.get('extension') or \ + request.environ.get('reddit-domain-extension') or '' c.render_style = 'html' - if extension in ('rss', 'xml'): + if c.extension in ('rss', 'xml'): c.render_style = 'xml' c.response_content_type = 'text/xml; charset=UTF-8' - elif extension == 'js': + elif c.extension == 'js': c.render_style = 'js' c.response_content_type = 'text/javascript; charset=UTF-8' - elif extension.startswith('json') or extension == "api": + elif c.extension.startswith('json') or c.extension == "api": c.response_content_type = 'application/json; charset=UTF-8' c.response_access_control = 'allow <*>' - if extension == 'json-html': + if c.extension == 'json-html': c.render_style = api_type('html') else: c.render_style = api_type() - elif extension == 'wired': + elif c.extension == 'wired': c.render_style = 'wired' c.response_content_type = 'text/javascript; charset=UTF-8' c.response_wrappers.append(utils.to_js) - elif extension == 'embed': + elif c.extension == 'embed': c.render_style = 'htmllite' c.response_content_type = 'text/javascript; charset=UTF-8' c.response_wrappers.append(utils.to_js) - elif extension == 'mobile': - c.render_style = 'mobile' - #Insert new extentions above this line - elif extension not in ('', 'html'): - dest = "http://%s%s" % (request.host, request.path) - if request.get: - dest += utils.query_string(request.get) - redirect_to(dest) + elif c.extension == 'mobile': + c.render_style = 'mobile' + elif c.extension == 'png': + c.response_content_type = 'image/png' + c.render_style = 'png' + elif c.extension == 'css': + c.response_content_type = 'text/css' + c.render_style = 'css' + #Insert new extentions above this line + elif c.extension not in ('', 'html'): + # request.path already has the extension stripped off of it + redirect_to(request.path + utils.query_string(request.get)) def get_browser_langs(): browser_langs = [] @@ -261,9 +264,15 @@ def set_content_lang(): c.content_langs = c.user.pref_content_langs def set_cnameframe(): - if (bool(request.params.get('cnameframe')) + if (bool(request.params.get(utils.UrlParser.cname_get)) or not request.host.split(":")[0].endswith(g.domain)): c.cname = True + request.environ['REDDIT_CNAME'] = 1 + if request.params.has_key(utils.UrlParser.cname_get): + del request.params[utils.UrlParser.cname_get] + if request.get.has_key(utils.UrlParser.cname_get): + del request.get[utils.UrlParser.cname_get] + c.frameless_cname = request.environ.get('frameless_cname', False) def ratelimit_agents(): user_agent = request.user_agent @@ -322,7 +331,7 @@ class RedditController(BaseController): str(c.content_langs), request.host, str(c.cname), - request.fullpath, + str(request.fullpath), str(c.firsttime), str(c.over18))) return key @@ -334,14 +343,13 @@ class RedditController(BaseController): def login(user, admin = False, rem = False): c.response.set_cookie(g.login_cookie, value = user.make_cookie(admin = admin), - domain = c.domain, + domain = g.domain, expires = NEVER if rem else None) @staticmethod def logout(admin = False): - c.response.set_cookie(g.login_cookie, - value = '', - domain = c.domain) + c.response.set_cookie(g.login_cookie, value = '', + domain = g.domain) def pre(self): g.cache.caches = (LocalCache(),) + g.cache.caches[1:] @@ -349,10 +357,8 @@ class RedditController(BaseController): #check if user-agent needs a dose of rate-limiting ratelimit_agents() - c.domain = g.domain c.response_wrappers = [] c.errors = ErrorSet() - c.firsttime = firsttime() (c.user, maybe_admin) = \ valid_cookie(request.cookies.get(g.login_cookie)) @@ -382,15 +388,21 @@ class RedditController(BaseController): set_iface_lang() set_content_lang() set_cnameframe() + c.firsttime = firsttime() + + # set some environmental variables in case we hit an abort + if not isinstance(c.site, FakeSubreddit): + request.environ['REDDIT_NAME'] = c.site.name # check if the user has access to this subreddit if not c.site.can_view(c.user): abort(403, "forbidden") #check over 18 - if c.site.over_18 and not c.over18 and not request.path == "/frame": - d = dict(dest=add_sr(request.path) + utils.query_string(request.GET)) - return redirect_to("/over18" + utils.query_string(d)) + if (c.site.over_18 and not c.over18 and + request.path not in ("/frame", "/over18") + and c.render_style == 'html'): + return self.intermediate_redirect("/over18") #check content cache if not c.user_is_loggedin: @@ -440,7 +452,7 @@ class RedditController(BaseController): def check_modified(self, thing, action): if c.user_is_loggedin: return - + date = utils.is_modified_since(thing, action, request.if_modified_since) if date is True: abort(304, 'not modified') @@ -448,7 +460,7 @@ class RedditController(BaseController): c.response.headers['Last-Modified'] = utils.http_date_str(date) def abort404(self): - abort(404, 'not found') + abort(404, "not found") def sendpng(self, string): c.response_content_type = 'image/png' diff --git a/r2/r2/controllers/validator/validator.py b/r2/r2/controllers/validator/validator.py index 3edfcb40f..6ae3a94be 100644 --- a/r2/r2/controllers/validator/validator.py +++ b/r2/r2/controllers/validator/validator.py @@ -21,9 +21,9 @@ ################################################################################ from pylons import c, request, g from pylons.i18n import _ -from pylons.controllers.util import abort, redirect_to +from pylons.controllers.util import abort from r2.lib import utils, captcha -from r2.lib.filters import unkeep_space, websafe +from r2.lib.filters import unkeep_space, websafe, _force_unicode from r2.lib.db.operators import asc, desc from r2.config import cache from r2.lib.template_helpers import add_sr @@ -77,15 +77,10 @@ def validate(*simple_vals, **param_vals): return fn(self, *a, **kw) except UserRequiredException: - d = dict(dest=add_sr(request.path) + - utils.query_string(request.GET)) - if c.cname: - d['cnameframe'] = 1 - path = "/login" - if request.environ.get('extension'): - path += ".%s" % request.environ['extension'] - return redirect_to(path + utils.query_string(d)) - + if request.method == "POST" and hasattr(self, "ajax_login_redirect"): + # ajax failure, so redirect accordingly + return self.ajax_login_redirect("/") + return self.intermediate_redirect('/login') return newfn return val @@ -452,8 +447,11 @@ class VUrl(VRequired): def __init__(self, item, *a, **kw): VRequired.__init__(self, item, errors.NO_URL, *a, **kw) - def run(self, url, sr): - sr = Subreddit._by_name(sr) + def run(self, url, sr = None): + if sr is None and not isinstance(c.site, FakeSubreddit): + sr = c.site + else: + sr = Subreddit._by_name(sr) if sr else None if not url: return self.error(errors.NO_URL) @@ -464,7 +462,7 @@ class VUrl(VRequired): try: l = Link._by_url(url, sr) self.error(errors.ALREADY_SUB) - return l.url + return utils.tup(l) except NotFound: return url return self.error(errors.BAD_URL) diff --git a/r2/r2/lib/base.py b/r2/r2/lib/base.py index bdbae8366..b5f5e3a61 100644 --- a/r2/r2/lib/base.py +++ b/r2/r2/lib/base.py @@ -33,6 +33,7 @@ from urllib import quote #TODO hack import logging +from r2.lib.utils import UrlParser, query_string logging.getLogger('scgi-wsgi').setLevel(logging.CRITICAL) class BaseController(WSGIController): @@ -69,6 +70,7 @@ class BaseController(WSGIController): request.path = environ.get('PATH_INFO') request.user_agent = environ.get('HTTP_USER_AGENT') request.fullpath = environ.get('FULLPATH', request.path) + request.port = environ.get('request_port') if_modified_since = environ.get('HTTP_IF_MODIFIED_SINCE') if if_modified_since: @@ -92,14 +94,57 @@ class BaseController(WSGIController): def pre(self): pass def post(self): pass - @staticmethod - def redirect(dest, code = 302): - dest = _force_unicode(dest).encode('utf8') - if c.cname and "?cnameframe=1" not in dest: - dest += "?cnameframe=1" + + @classmethod + def format_output_url(cls, url, **kw): + """ + Helper method used during redirect to ensure that the redirect + url (assisted by frame busting code or javasctipt) will point + to the correct domain and not have any extra dangling get + parameters. The extensions are also made to match and the + resulting url is utf8 encoded. + + Node: for development purposes, also checks that the port + matches the request port + """ + u = UrlParser(url) + + # make sure to pass the port along if not 80 + if not kw.has_key('port'): + kw['port'] = request.port + + # disentagle the cname (for urls that would have cnameframe=1 in them) + u.mk_cname(**kw) + + # make sure the extensions agree with the current page + u.set_extension(c.extension) + + # unparse and encode it un utf8 + return _force_unicode(u.unparse()).encode('utf8') + + + @classmethod + def intermediate_redirect(cls, form_path): + """ + Generates a /login or /over18 redirect from the current + fullpath, after having properly reformated the path via + format_output_url. The reformatted original url is encoded + and added as the "dest" parameter of the new url. + """ + from r2.lib.template_helpers import add_sr + dest = cls.format_output_url(request.fullpath) + path = add_sr(form_path + query_string({"dest": dest})) + return cls.redirect(path) + + @classmethod + def redirect(cls, dest, code = 302): + """ + Reformats the new Location (dest) using format_output_url and + sends the user to that location with the provided HTTP code. + """ + dest = cls.format_output_url(dest) c.response.headers['Location'] = dest c.response.status_code = code - return c.response def sendjs(self,js, callback="document.write", escape=True): @@ -119,7 +164,7 @@ class EmbedHandler(urllib2.BaseHandler, urllib2.HTTPHandler, def http_redirect(self, req, fp, code, msg, hdrs): codes = [301, 302, 303, 307] map = dict((x, self.redirect(x)) for x in codes) - to = hdrs['Location'].replace('reddit.infogami.com', c.domain) + to = hdrs['Location'].replace('reddit.infogami.com', g.domain) map[code](to) raise StopIteration diff --git a/r2/r2/lib/emailer.py b/r2/r2/lib/emailer.py index e83a0ae03..6b88588b7 100644 --- a/r2/r2/lib/emailer.py +++ b/r2/r2/lib/emailer.py @@ -50,7 +50,7 @@ def simple_email(to, fr, subj, body): def password_email(user): key = passhash(random.randint(0, 1000), user.email) - passlink = 'http://' + c.domain + '/resetpassword/' + key + passlink = 'http://' + g.domain + '/resetpassword/' + key cache.set("reset_%s" %key, user._id, time=1800) simple_email(user.email, 'reddit@reddit.com', 'reddit.com password reset', diff --git a/r2/r2/lib/jsonresponse.py b/r2/r2/lib/jsonresponse.py index ea3b48038..d45861bf1 100644 --- a/r2/r2/lib/jsonresponse.py +++ b/r2/r2/lib/jsonresponse.py @@ -25,6 +25,7 @@ from r2.lib.wrapped import Wrapped from r2.lib.filters import websafe_json from r2.lib.template_helpers import replace_render from r2.lib.jsontemplates import get_api_subtype +from r2.lib.base import BaseController import simplejson def json_respond(x): @@ -81,12 +82,11 @@ class JsonResponse(): self.blur = f def _redirect(self, red): - from pylons import c - if c.cname and "?cnameframe=1" not in red: - self.redirect = red + "?cnameframe=1" - else: - self.redirect = red - + from pylons import c, request + if c.cname: + red = BaseController.format_output_url(red, subreddit = c.site, + require_frame = False) + self.redirect = red def _update(self, name, **kw): k = kw.copy() diff --git a/r2/r2/lib/pages/pages.py b/r2/r2/lib/pages/pages.py index 265d174cf..6b5cf6020 100644 --- a/r2/r2/lib/pages/pages.py +++ b/r2/r2/lib/pages/pages.py @@ -26,14 +26,14 @@ from r2.lib.jsonresponse import json_respond from r2.lib.jsontemplates import is_api from pylons.i18n import _ from pylons import c, request, g -from pylons.controllers.util import abort, redirect_to +from pylons.controllers.util import abort from r2.lib.captcha import get_iden from r2.lib.filters import spaceCompress, _force_unicode from r2.lib.menus import NavButton, NamedButton, NavMenu, PageNameNav, JsButton, menu from r2.lib.strings import plurals, rand_strings, strings -from r2.lib.utils import title_to_url, query_string -from r2.lib.template_helpers import add_sr +from r2.lib.utils import title_to_url, query_string, UrlParser +from r2.lib.template_helpers import add_sr, get_domain import sys def get_captcha(): @@ -121,6 +121,7 @@ class Reddit(Wrapped): if self.submit_box: ps.append(SideBox(_('Submit a link'), '/submit', 'submit', + sr_path = True, subtitles = [_('to anything interesting: news article, blog entry, video, picture...')], show_cover = True)) @@ -263,9 +264,9 @@ class SubredditInfoBar(Wrapped): class SideBox(Wrapped): """Generic sidebox used to generate the 'submit' and 'create a reddit' boxes.""" def __init__(self, title, link, css_class='', subtitles = [], - show_cover = False, nocname=False): + show_cover = False, nocname=False, sr_path = False): Wrapped.__init__(self, link = link, target = '_top', - title = title, css_class = css_class, + title = title, css_class = css_class, sr_path = sr_path, subtitles = subtitles, show_cover = show_cover, nocname=nocname) @@ -361,8 +362,10 @@ class LoginPage(BoringPage): BoringPage.__init__(self, _("login or register"), **context) def content(self): - return Login(dest = self.dest) - + kw = {} + for x in ('user_login', 'user_reg'): + kw[x] = getattr(self, x) if hasattr(self, x) else '' + return Login(dest = self.dest, **kw) class Login(Wrapped): """The two-unit login and register form.""" @@ -788,7 +791,11 @@ class Frame(Wrapped): class FrameToolbar(Wrapped): """The reddit voting toolbar used together with Frame.""" - pass + extension_handling = False + def __init__(self, link = None, **kw): + self.title = link.title + Wrapped.__init__(self, link = link, *kw) + class NewLink(Wrapped): @@ -847,6 +854,7 @@ class ButtonEmbed(Wrapped): class Button(Wrapped): """the voting buttons, embedded with the ButtonEmbed wrapper, shown on /buttons""" + extension_handling = False def __init__(self, link = None, likes = None, button = None, css=None, url = None, title = '', score_fmt = None): @@ -1069,13 +1077,15 @@ class DetailsPage(LinkInfoPage): class Cnameframe(Wrapped): """The frame page.""" - def __init__(self, original_path, sr_name, sr_title, sub_domain): + def __init__(self, original_path, subreddit, sub_domain): Wrapped.__init__(self, original_path=original_path) - self.title = "%s - %s" % (sr_title, sub_domain) - port = request.environ.get('request_port') - request.get['cnameframe'] = 1 - path = original_path + query_string(request.get) - if port > 0: - self.frame_target = "http://%s:%d/r/%s%s" % (c.domain, port, sr_name, path) + if sub_domain and subreddit and original_path: + self.title = "%s - %s" % (subreddit.title, sub_domain) + u = UrlParser(subreddit.path + original_path) + u.hostname = get_domain(cname = False, subreddit = False) + u.update_query(**request.get.copy()) + u.put_in_frame() + self.frame_target = u.unparse() else: - self.frame_target = "http://%s/r/%s%s" % (c.domain, sr_name, path) + self.title = "" + self.frame_target = None diff --git a/r2/r2/lib/template_helpers.py b/r2/r2/lib/template_helpers.py index 6d0927bd0..29e1d1cab 100644 --- a/r2/r2/lib/template_helpers.py +++ b/r2/r2/lib/template_helpers.py @@ -21,29 +21,15 @@ ################################################################################ from r2.models import * from filters import unsafe, websafe -from r2.lib.utils import vote_hash +from r2.lib.utils import vote_hash, UrlParser from mako.filters import url_escape import simplejson import os.path from copy import copy -from urlparse import urlparse, urlunparse from pylons import i18n, g, c -def contextualize(func): - def _contextualize(context, *a, **kw): - return func(*a, **kw) - return _contextualize - -def print_context(context): - print context.keys() - return '' - -def print_me(context, t): - print t - return '' - def static(file): # stip of "/static/" if already present fname = os.path.basename(file).split('?')[0] @@ -151,49 +137,93 @@ def replace_render(listing, item, style = None, display = True): rendered_item = replace_fn(u"$display", "" if display else "style='display:none'") return rendered_item -def dockletStr(context, type, browser): - domain = c.domain - if c.cname and c.site.domain: +def get_domain(cname = False, subreddit = True, no_www = False): + """ + returns the domain on the current subreddit, possibly including + the subreddit part of the path, suitable for insertion after an + "http://" and before a fullpath (i.e., something including the + first '/') in a template. The domain is updated to include the + current port (request.port). The effect of the arguments is: + + * no_www: if the domain ends up being g.domain, the default + behavior is to prepend "www." to the front of it (for akamai). + This flag will optionally disable it. + + * cname: whether to respect the value of c.cname and return + c.site.domain rather than g.domain as the host name. + + * subreddit: if a cname is not used in the resulting path, flags + whether or not to append to the domain the subreddit path (sans + the trailing path). + + """ + domain = g.domain + if not no_www: + domain = "www." + g.domain + if cname and c.cname and c.site.domain: domain = c.site.domain + if request.port: + domain += ":" + str(request.port) + if (not c.cname or not cname) and subreddit: + domain += c.site.path.rstrip('/') + return domain + +def dockletStr(context, type, browser): + domain = get_domain() + + # while site_domain will hold the (possibly) cnamed version + site_domain = get_domain(True) if type == "serendipity!": - return "http://"+domain+"/random" + return "http://"+site_domain+"/random" elif type == "reddit": - return "javascript:location.href='http://"+domain+"/submit?url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)" + return "javascript:location.href='http://"+site_domain+"/submit?url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)" else: - f = "fixed" - if browser == "ie": f = "absolute" - return "javascript:function b(){var u=encodeURIComponent(location.href);var i=document.getElementById('redstat')||document.createElement('a');var s=i.style;s.position='%s';s.top='0';s.left='0';s.zIndex='10002';i.id='redstat';i.href='http://%s/submit?url='+u+'&title='+encodeURIComponent(document.title);var q=i.firstChild||document.createElement('img');q.src='http://%s/d/%s'+Math.random()+'?uh=%s&u='+u;i.appendChild(q);document.body.appendChild(i)};b()" % \ - (f, domain, domain, type, - c.modhash if c.user else '') + return (("javascript:function b(){var u=encodeURIComponent(location.href);" + "var i=document.getElementById('redstat')||document.createElement('a');" + "var s=i.style;s.position='%(position)s';s.top='0';s.left='0';" + "s.zIndex='10002';i.id='redstat';" + "i.href='http://%(site_domain)s/submit?url='+u+'&title='+" + "encodeURIComponent(document.title);" + "var q=i.firstChild||document.createElement('img');" + "q.src='http://%(domain)s/d/%(type)s.png?v='+Math.random()+'&uh=%(modhash)s&u='+u;" + "i.appendChild(q);document.body.appendChild(i)};b()") % + dict(position = "absolute" if browser == "ie" else "fixed", + domain = domain, site_domain = site_domain, type = type, + modhash = c.modhash if c.user else '')) -def add_sr(path, sr_path = True, nocname=False): - """Given a link, returns that link with the subreddit added. - Also adds the domain for cname requests.""" - (scheme, netloc, path, params, query, fragment) = urlparse(path) - if sr_path: - #noslash fixes /reddits/ - noslash = c.site.path.rstrip('/') - #if it's a relative path, don't include the sitename - if (path.startswith('/') and not path.startswith(noslash) - and not path.startswith('/r/')): - if not c.cname: - path = c.site.path + path[1:] +def add_sr(path, sr_path = True, nocname=False, force_hostname = False): + """ + Given a path (which may be a full-fledged url or a relative path), + parses the path and updates it to include the subreddit path + according to the rules set by its arguments: - if not netloc and c.cname and not nocname: - netloc = getattr(c.site, 'domain', None) + * force_hostname: if True, force the url's hotname to be updated + even if it is already set in the path, and subject to the + c.cname/nocname combination. If false, the path will still + have its domain updated if no hostname is specified in the url. + + * nocname: when updating the hostname, overrides the value of + c.cname to set the hotname to g.domain. The default behavior + is to set the hostname consistent with c.cname. - if netloc: - port = request.environ.get('request_port') - if port > 0: - netloc = "%s:%d" % (netloc, port) - - if c.render_style == 'mobile' and not path.endswith('.mobile'): - path += '.mobile' + * sr_path: if a cname is not used for the domain, updates the + path to include c.site.path. + """ + u = UrlParser(path) + if sr_path and (nocname or not c.cname): + u.path_add_subreddit(c.site) - return urlunparse((scheme, netloc, path, params, query, fragment)) + if not u.hostname or force_hostname: + u.hostname = get_domain(cname = (c.cname and not nocname), + subreddit = False) + + if c.render_style == 'mobile': + u.set_extension('mobile') + + return u.unparse() def join_urls(*urls): """joins a series of urls together without doubles slashes""" diff --git a/r2/r2/lib/utils/utils.py b/r2/r2/lib/utils/utils.py index a9a0ee92d..7ab1e5920 100644 --- a/r2/r2/lib/utils/utils.py +++ b/r2/r2/lib/utils/utils.py @@ -25,11 +25,12 @@ from threading import local, Thread import Queue from copy import deepcopy import cPickle as pickle -import re, datetime, math, random, string, sha +import re, datetime, math, random, string, sha, os from datetime import datetime, timedelta from pylons.i18n import ungettext, _ from r2.lib.filters import _force_unicode +from mako.filters import url_escape, url_unescape iters = (list, tuple, set) @@ -93,19 +94,6 @@ class Storage(dict): storage = Storage -import inspect -class cold_storage(Storage): - def __getattr__(self, key): - try: - res = self[key] - if inspect.isfunction(res) and \ - inspect.getargspec(res)[:3] == ([], None, None): - res = res() - self[key] = res - return res - except KeyError, k: - raise AttributeError, k - def storify(mapping, *requireds, **defaults): """ Creates a `storage` object from dictionary `mapping`, raising `KeyError` if @@ -433,7 +421,6 @@ def to_base(q, alphabet): def to36(q): return to_base(q, '0123456789abcdefghijklmnopqrstuvwxyz') -from mako.filters import url_escape def query_string(dict): pairs = [] for k,v in dict.iteritems(): @@ -449,6 +436,239 @@ def query_string(dict): else: return '' +class UrlParser(object): + """ + Wrapper for urlparse and urlunparse for making changes to urls. + All attributes present on the tuple-like object returned by + urlparse are present on this class, and are setable, with the + exception of netloc, which is instead treated via a getter method + as a concatenation of hostname and port. + + Unlike urlparse, this class allows the query parameters to be + converted to a dictionary via the query_dict method (and + correspondingly updated vi update_query). The extension of the + path can also be set and queried. + + The class also contains reddit-specific functions for setting, + checking, and getting a path's subreddit. It also can convert + paths between in-frame and out of frame cname'd forms. + + """ + + __slots__ = ['scheme', 'path', 'params', 'query', + 'fragment', 'username', 'password', 'hostname', + 'port', '_url_updates', '_orig_url', '_query_dict'] + + valid_schemes = ('http', 'https', 'ftp', 'mailto') + cname_get = "cnameframe" + + def __init__(self, url): + u = urlparse(url) + for s in self.__slots__: + if hasattr(u, s): + setattr(self, s, getattr(u, s)) + self._url_updates = {} + self._orig_url = url + self._query_dict = None + + def update_query(self, **updates): + """ + Can be used instead of self.query_dict.update() to add/change + query params in situations where the original contents are not + required. + """ + self._url_updates.update(updates) + + @property + def query_dict(self): + """ + Parses they params attribute of the original urlparse and + generates a dictionary where both the keys and values have + been url_unescape'd. Any updates or changes to the resulting + dict will be reflected in the updated query params + """ + if self._query_dict is None: + def _split(param): + p = param.split('=') + return (url_unescape(p[0]), + url_unescape('='.join(p[1:]))) + self._query_dict = dict(_split(p) for p in self.query.split('&') + if p) + + return self._query_dict + + def path_extension(self): + """ + Fetches the current extension of the path. + """ + return self.path.split('/')[-1].split('.')[-1] + + def set_extension(self, extension): + """ + Changes the extension of the path to the provided value (the + "." should not be included in the extension as a "." is + provided) + """ + pieces = self.path.split('/') + dirs = pieces[:-1] + base = pieces[-1].split('.') + base = '.'.join(base[:-1] if len(base) > 1 else base) + if extension: + base += '.' + extension + dirs.append(base) + self.path = '/'.join(dirs) + return self + + + def unparse(self): + """ + Converts the url back to a string, applying all updates made + to the feilds thereof. + + Note: if a host name has been added and none was present + before, will enforce scheme -> "http" unless otherwise + specified. Double-slashes are removed from the resultant + path, and the query string is reconstructed only if the + query_dict has been modified/updated. + """ + # only parse the query params if there is an update dict + q = self.query + if self._url_updates or self._query_dict is not None: + q = self._query_dict or self.query_dict + q.update(self._url_updates) + q = query_string(q).lstrip('?') + + # make sure the port is not doubly specified + if self.port and ":" in self.hostname: + self.hostname = self.hostname.split(':')[0] + + # if there is a netloc, there had better be a scheme + if self.netloc and not self.scheme: + self.scheme = "http" + + return urlunparse((self.scheme, self.netloc, + self.path.replace('//', '/'), + self.params, q, self.fragment)) + + def path_has_subreddit(self): + """ + utility method for checking if the path starts with a + subreddit specifier (namely /r/ or /reddits/). + """ + return (self.path.startswith('/r/') or + self.path.startswith('/reddits/')) + + def get_subreddit(self): + """checks if the current url refers to a subreddit and returns + that subreddit object. The cases here are: + + * the hostname is unset or is g.domain, in which case it + looks for /r/XXXX or /reddits. The default in this case + is Default. + * the hostname is a cname to a known subreddit. + + On failure to find a subreddit, returns None. + """ + from pylons import g + from r2.models import Subreddit, Sub, NotFound, Default + try: + if not self.hostname or self.hostname.startswith(g.domain): + if self.path.startswith('/r/'): + return Subreddit._by_name(self.path.split('/')[2]) + elif self.path.startswith('/reddits/'): + return Sub + else: + return Default + elif self.hostname: + return Subreddit._by_domain(self.hostname) + except NotFound: + pass + return None + + def is_reddit_url(self, subreddit = None): + """utility method for seeing if the url is associated with + reddit as we don't necessarily want to mangle non-reddit + domains + + returns true only if hostname is nonexistant, a subdomain of + g.domain, or a subdomain of the provided subreddit's cname. + """ + from pylons import g + return (not self.hostname or + self.hostname.endswith(g.domain) or + (subreddit and subreddit.domain and + self.hostname.endswith(subreddit.domain))) + + def path_add_subreddit(self, subreddit): + """ + Adds the subreddit's path to the path if another subreddit's + prefix is not already present. + """ + if not self.path_has_subreddit(): + self.path = (subreddit.path + self.path) + return self + + @property + def netloc(self): + """ + Getter method which returns the hostname:port, or empty string + if no hostname is present. + """ + if not self.hostname: + return "" + elif self.port: + return self.hostname + ":" + str(self.port) + return self.hostname + + def mk_cname(self, require_frame = True, subreddit = None, port = None): + """ + Converts a ?cnameframe url into the corresponding cnamed + domain if applicable. Useful for frame-busting on redirect. + """ + + # make sure the url is indeed in a frame + if require_frame and not self.query_dict.has_key(self.cname_get): + return self + + # fetch the subreddit and make sure it + subreddit = subreddit or self.get_subreddit() + if subreddit and subreddit.domain: + + # no guarantee there was a scheme + self.scheme = self.scheme or "http" + + # update the domain (preserving the port) + self.hostname = subreddit.domain + self.port = self.port or port + + # and remove any cnameframe GET parameters + if self.query_dict.has_key(self.cname_get): + del self._query_dict[self.cname_get] + + # remove the subreddit reference + self.path = lstrips(self.path, subreddit.path) + if not self.path.startswith('/'): + self.path = '/' + self.path + + return self + + def is_in_frame(self): + """ + Checks if the url is in a frame by determining if + cls.cname_get is present. + """ + return self.query_dict.has_key(self.cname_get) + + def put_in_frame(self): + """ + Adds the cls.cname_get get parameter to the query string. + """ + self.update_query(**{self.cname_get:random.random()}) + + def __repr__(self): + return "" % repr(self.unparse()) + + def to_js(content, callback="document.write", escape=True): before = after = '' if callback: diff --git a/r2/r2/models/link.py b/r2/r2/models/link.py index e2838b879..a9775c52c 100644 --- a/r2/r2/models/link.py +++ b/r2/r2/models/link.py @@ -213,12 +213,14 @@ class Link(Thing, Printable): s = ''.join(s) return s - def make_permalink(self, sr): + def make_permalink(self, sr, force_domain = False): + from r2.lib.template_helpers import get_domain p = "comments/%s/%s/" % (self._id36, title_to_url(self.title)) if not c.cname: res = "/r/%s/%s" % (sr.name, p) - elif sr != c.site: - res = "http://%s/r/%s/%s" % (g.domain, sr.name, p) + elif sr != c.site or force_domain: + res = "http://%s/r/%s/%s" % (get_domain(cname = c.cname, + subreddit = False), sr.name, p) else: res = "/%s" % p return res @@ -262,6 +264,9 @@ class Link(Thing, Printable): item.num = None item.score_fmt = Score.number_only item.permalink = item.make_permalink(item.subreddit) + if item.is_self: + item.url = item.make_permalink(item.subreddit, force_domain = True) + if c.user_is_loggedin: incr_counts(wrapped) diff --git a/r2/r2/public/static/reddit.css b/r2/r2/public/static/reddit.css index 94c1d052c..aa3005abc 100644 --- a/r2/r2/public/static/reddit.css +++ b/r2/r2/public/static/reddit.css @@ -1187,6 +1187,21 @@ a.star { text-decoration: none; color: #ff8b60 } padding: 1em; } +/* the toolbar */ +.toolbar { height: 30px; border-bottom: 1px solid black} +.toolbar td { vertical-align: center; } +.toolbar .arrow { + background-position: center left; + padding-left: 18px; + display: inline; +} +.toolbar span { + margin-left: 20px; +} +.toolbar #frame-right { text-align: right } +.toolbar #frame-right a { margin-left: 10px; } + +/* default form styles */ .pretty-form { font-size: larger; vertical-align: top; diff --git a/r2/r2/public/static/reddit_piece.js b/r2/r2/public/static/reddit_piece.js index 0110c68f0..ed1c30af8 100644 --- a/r2/r2/public/static/reddit_piece.js +++ b/r2/r2/public/static/reddit_piece.js @@ -50,9 +50,21 @@ function hover_open_menu(menu) { } } +var global_cookies_allowed = true; + function init() { - updateClicks(); - /*updateMods();*/ + if(cur_domain != ajax_domain) { + global_cookies_allowed = false; + } + else if(cnameframe && !logged) { + var m = Math.random() + ''; + createCookie('test', m); + global_cookies_allowed = (readCookie('test') == m); + } + if (global_cookies_allowed) { + updateClicks(); + /*updateMods();*/ + } stc = $("siteTable_comments"); } @@ -120,18 +132,21 @@ function untoggle(execute, parent, oldtext, type) { function chklogin(form) { - var op = field(form.op); - var status = $("status_" + op); - if (status) { - status.innerHTML = _global_submitting_tag; - }; - if (op == 'login' || op == 'login-main') { - post_form(form, 'login'); + if(global_cookies_allowed) { + var op = field(form.op); + var status = $("status_" + op); + if (status) { + status.innerHTML = _global_submitting_tag; + }; + if (op == 'login' || op == 'login-main') { + post_form(form, 'login'); + } + else { + post_form(form, 'register'); + } + return false; } - else { - post_form(form, 'register'); - } - return false; + return true; } function toggle(a_tag, op) { diff --git a/r2/r2/public/static/utils.js b/r2/r2/public/static/utils.js index 3fe83bd69..acf8e6ec4 100644 --- a/r2/r2/public/static/utils.js +++ b/r2/r2/public/static/utils.js @@ -359,7 +359,7 @@ function handleResponse(action) { } // first thing to check is if a redirect has been requested if(res_obj.redirect) { - window.location = res_obj.redirect; + window.location = unsafe(res_obj.redirect); return; } // next check for errors diff --git a/r2/r2/templates/ads.html b/r2/r2/templates/ads.html index 9f8e28f1e..c9b1d496c 100644 --- a/r2/r2/templates/ads.html +++ b/r2/r2/templates/ads.html @@ -1,4 +1,4 @@ -## "The contents of this file are subject to the Common Public Attribution +## 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 @@ -19,6 +19,15 @@ ## All portions of the code written by CondeNet are Copyright (c) 2006-2008 ## CondeNet, Inc. All Rights Reserved. ################################################################################ +<%! + from r2.lib.template_helpers import get_domain, static + import random + %> - + +## diff --git a/r2/r2/templates/base.html b/r2/r2/templates/base.html index b73cd8c7d..512d70f81 100644 --- a/r2/r2/templates/base.html +++ b/r2/r2/templates/base.html @@ -21,52 +21,58 @@ ################################################################################ <%! - from r2.lib.template_helpers import static + from r2.lib.template_helpers import static, get_domain from r2.models import Link, Comment, Subreddit %> - -${self.Title()} + + ${self.Title()} + + + + + ${self.robots()} + + ##these are globals, so they should be run first + + + ${self.javascript()} + ${self.stylesheet()} + + ##things here may depend on globals, or included js, so we run them last + + - - - -${self.robots()} - -##these are globals, so they should be run first - - -${self.javascript()} -${self.stylesheet()} - -##things here may depend on globals, or included js, so we run them last - - -${self.head()} - - -${next.body()} + ${self.head()} + + + ${self.bodyHTML()} + +<%def name="bodyHTML()"> + + <%def name="Title()"> <% try: diff --git a/r2/r2/templates/base.xml b/r2/r2/templates/base.xml index 56d107caf..22a4fa645 100644 --- a/r2/r2/templates/base.xml +++ b/r2/r2/templates/base.xml @@ -21,13 +21,14 @@ ################################################################################ <%! from r2.models.subreddit import DefaultSR + from r2.lib.template_helpers import get_domain, add_sr %> ${self.Title()} - http://${c.domain}${c.site.path} + ${add_sr("/", force_hostname = True)} ${c.site.description or ''} <% @@ -36,9 +37,9 @@ else: header_img = c.site.header %> - ${header_img} + http://${get_domain(subreddit = False, cname = c.cname)}${header_img} ${self.Title()} - http://${c.domain}${c.site.path} + ${add_sr("/", force_hostname = True)} ${next.body()} diff --git a/r2/r2/templates/button.html b/r2/r2/templates/button.html index 239761d10..0a2322932 100644 --- a/r2/r2/templates/button.html +++ b/r2/r2/templates/button.html @@ -1,4 +1,4 @@ -## "The contents of this file are subject to the Common Public Attribution +## 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 @@ -19,27 +19,17 @@ ## All portions of the code written by CondeNet are Copyright (c) 2006-2008 ## CondeNet, Inc. All Rights Reserved. ################################################################################ - -<%inherit file="base.html"/> +<%! + from r2.lib.template_helpers import get_domain, static + from r2.lib.utils import query_string + %> +<%inherit file="reddit.html"/> <%namespace module="r2.lib.template_helpers" import="generateurl"/> <%namespace file="printable.html" import="arrow, score" /> -<%def name="stylesheet()"> - <% from r2.lib.template_helpers import static %> - - %if c.site.stylesheet: - - %endif - - %if thing.css: - - %endif - - <%def name="javascript()"> - <% from r2.lib.template_helpers import static %> - - -<%def name="drawrequiredtext(type)" filter="h"> +<%def name="drawrequired(type)" buffered="True"> <%def name="drawoptional(url, title)"> -${"" % url}
-${"" % title} +${"" % url}
+${"" % title}
@@ -54,19 +52,20 @@ ${"" % title} reddit, include the reddit_title line. if you don't include that\ line, then the user will have to specify a title when they\ submit.")}

- - ${buttondemo(1)} - ${buttondemo(2)} - ${buttondemo(3)} + + + ${buttondemo(1)} + ${buttondemo(2)} + ${buttondemo(3)}
<%def name="buttondemo(type)">

${_("style %(number)s") % dict(number=type)}

- ${drawrequired(type)} + ${unsafe(drawrequired(type))}
   ${drawoptional('[URL]', '[TITLE]')}
- ${drawrequiredtext(type)} + ${drawrequired(type)}
diff --git a/r2/r2/templates/buttonembed.js b/r2/r2/templates/buttonembed.js index 6e04175c6..1da848330 100644 --- a/r2/r2/templates/buttonembed.js +++ b/r2/r2/templates/buttonembed.js @@ -1,4 +1,4 @@ -## "The contents of this file are subject to the Common Public Attribution +## 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 @@ -19,10 +19,16 @@ ## All portions of the code written by CondeNet are Copyright (c) 2006-2008 ## CondeNet, Inc. All Rights Reserved. ################################################################################ +<%! + from r2.lib.template_helpers import get_domain + %> -<% domain = "www.reddit.com" if c.domain == "reddit.com" else c.domain %> +<% + domain = get_domain() + arg = "cnameframe=1&" if c.cname else "" +%> (function() { -var write_string=' + %endif %if not c.user_is_loggedin: diff --git a/r2/r2/templates/searchbar.html b/r2/r2/templates/searchbar.html index 008f7ab70..a8b12f9e2 100644 --- a/r2/r2/templates/searchbar.html +++ b/r2/r2/templates/searchbar.html @@ -1,4 +1,4 @@ -## "The contents of this file are subject to the Common Public Attribution +## 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 diff --git a/r2/r2/templates/share.email b/r2/r2/templates/share.email index 4e62f8462..36e2dfe5c 100644 --- a/r2/r2/templates/share.email +++ b/r2/r2/templates/share.email @@ -20,7 +20,7 @@ ## CondeNet, Inc. All Rights Reserved. ################################################################################ %if not thing.body: -${thing.username} from http://${g.domain}/ has shared a link with you. +<% from r2.lib.template_helpers import get_domain%>${thing.username} from http://${get_domain(cname = c.cname, subreddit = False)}/ has shared a link with you. %else: ${thing.body} %endif @@ -28,7 +28,7 @@ ${thing.body} "${thing.link.title}" http://${g.domain}/goto?share=true&id=${thing.link._fullname} -<% from r2.lib.strings import strings, plurals %>${_("There are currently %(num_comments)s on this link. You can view them here:") % dict(num_comments = strings.number_label % (thing.link.num_comments, plurals.N_comments(thing.link.num_comments)))} +<% from r2.lib.strings import strings, plurals %>${ungettext("There is currently %(num_comments)s on this link. You can view it here:", "There are currently %(num_comments)s on this link. You can view them here:", thing.link.num_comments) % dict(num_comments = strings.number_label % (thing.link.num_comments, plurals.N_comments(thing.link.num_comments)))} <% from r2.lib.template_helpers import add_sr %>http://${g.domain}${add_sr(thing.link.make_permalink_slow())} diff --git a/r2/r2/templates/sharelink.html b/r2/r2/templates/sharelink.html index 5187e3887..30466d0f1 100644 --- a/r2/r2/templates/sharelink.html +++ b/r2/r2/templates/sharelink.html @@ -57,6 +57,7 @@ diff --git a/r2/r2/templates/sidebox.html b/r2/r2/templates/sidebox.html index c2a69c259..ab760339e 100644 --- a/r2/r2/templates/sidebox.html +++ b/r2/r2/templates/sidebox.html @@ -31,7 +31,7 @@ nocname = thing.nocname %> - ${plain_link(thing.title,thing.link, _sr_path = False, _class="morelink", onclick=onclick, nocname=nocname)} + ${plain_link(thing.title,thing.link, _sr_path = thing.sr_path, _class="morelink", onclick=onclick, nocname=nocname)} %if thing.subtitles:
%for subtitle in thing.subtitles: diff --git a/r2/r2/templates/utils.html b/r2/r2/templates/utils.html index 2e98d5dc8..74d633c71 100644 --- a/r2/r2/templates/utils.html +++ b/r2/r2/templates/utils.html @@ -21,7 +21,6 @@ ################################################################################ <%! - from r2.controllers.errors import errors from r2.lib.filters import spaceCompress, unsafe from r2.lib.template_helpers import add_sr from r2.lib.utils import cols @@ -71,11 +70,11 @@ ${first_defined(kw[1:])} <%def name="error_field(name, kind='p')"> -<${kind} id="${name}" class="error"> -%if errors.get(name) in c.errors: -${errors.get(name).message} -%endif - + <${kind} id="${name}" class="error"> + %if name in c.errors: + ${c.errors[name].message} + %endif + <%def name="success_field(success_str, kind='p', successful=False, hide='')"> diff --git a/r2/r2/templates/widgetdemopanel.html b/r2/r2/templates/widgetdemopanel.html index 1614668e3..9f1612c42 100644 --- a/r2/r2/templates/widgetdemopanel.html +++ b/r2/r2/templates/widgetdemopanel.html @@ -19,11 +19,13 @@ ## All portions of the code written by CondeNet are Copyright (c) 2006-2008 ## CondeNet, Inc. All Rights Reserved. ################################################################################ +<%! + from r2.lib.template_helpers import get_domain + %> + +<% domain = get_domain(True) %>