diff --git a/r2/example.ini b/r2/example.ini index 9344ac160..2757f99e9 100644 --- a/r2/example.ini +++ b/r2/example.ini @@ -352,6 +352,9 @@ static_secure_domain = # this is for hosts that don't do on-the-fly gzipping (e.g. s3) static_pre_gzipped = false static_secure_pre_gzipped = false +# which s3 bucket to place subreddit styles on (when empty, stylesheets will be served +# from the local database instead. +static_stylesheet_bucket = # -- translator UI -- # enable/disable access to the translation UI in /admin/i18n diff --git a/r2/r2/controllers/front.py b/r2/r2/controllers/front.py index cb1e2f5e1..716ec927c 100755 --- a/r2/r2/controllers/front.py +++ b/r2/r2/controllers/front.py @@ -22,6 +22,7 @@ from validator import * from pylons.i18n import _, ungettext +from pylons.controllers.util import redirect_to from reddit_base import RedditController, base_listing, paginated_listing, prevent_framing_and_css from r2 import config from r2.models import * @@ -379,17 +380,26 @@ class FrontController(RedditController): return res def GET_stylesheet(self): + if g.css_killswitch: + self.abort404() + # de-stale the subreddit object so we don't poison nginx's cache if not isinstance(c.site, FakeSubreddit): c.site = Subreddit._byID(c.site._id, data=True, stale=False) - if hasattr(c.site,'stylesheet_contents') and not g.css_killswitch: + if c.site.stylesheet_is_static: + # TODO: X-Private-Subreddit? + return redirect_to(c.site.stylesheet_url) + else: + stylesheet_contents = c.site.stylesheet_contents + + if stylesheet_contents: c.allow_loggedin_cache = True self.check_modified(c.site,'stylesheet_contents', private=False, max_age=7*24*60*60, must_revalidate=False) c.response_content_type = 'text/css' - c.response.content = c.site.stylesheet_contents + c.response.content = stylesheet_contents if c.site.type == 'private': c.response.headers['X-Private-Subreddit'] = 'private' return c.response diff --git a/r2/r2/models/subreddit.py b/r2/r2/models/subreddit.py index 2e1ed278d..a9a08733c 100644 --- a/r2/r2/models/subreddit.py +++ b/r2/r2/models/subreddit.py @@ -22,6 +22,9 @@ from __future__ import with_statement +import base64 +import hashlib + from pylons import c, g from pylons.i18n import _ @@ -40,12 +43,13 @@ from r2.lib.db import tdb_cassandra from r2.models.wiki import WikiPage from r2.lib.merge import ConflictException from r2.lib.cache import CL_ONE +from r2.lib.contrib.rcssmin import cssmin +from r2.lib import s3cp import math from r2.lib.utils import set_last_modified from r2.models.wiki import WikiPage -from md5 import md5 import os.path import random @@ -59,7 +63,7 @@ class Subreddit(Thing, Printable): stylesheet = None, stylesheet_rtl = None, stylesheet_contents = '', - stylesheet_hash = '0', + stylesheet_hash = '', firsttext = strings.firsttext, header = None, header_size = None, @@ -211,6 +215,30 @@ class Subreddit(Thing, Printable): def moderators(self): return self.moderator_ids() + @property + def stylesheet_is_static(self): + """Is the subreddit using the newer static file based stylesheets?""" + return g.static_stylesheet_bucket and len(self.stylesheet_hash) == 27 + + static_stylesheet_prefix = "subreddit-stylesheet/" + + @property + def static_stylesheet_name(self): + return "".join((self.static_stylesheet_prefix, + self.stylesheet_hash, + ".css")) + + @property + def stylesheet_url(self): + from r2.lib.template_helpers import static, get_domain + + if self.stylesheet_is_static: + return static(self.static_stylesheet_name) + else: + return "http://%s/stylesheet.css?v=%s" % (get_domain(cname=False, + subreddit=True), + self.stylesheet_hash) + @property def stylesheet_contents_user(self): try: @@ -340,10 +368,33 @@ class Subreddit(Thing, Printable): except tdb_cassandra.NotFound: wiki = WikiPage.create(self, 'config/stylesheet') wr = wiki.revise(content, previous=prev, author=author, reason=reason, force=force) - self.stylesheet_contents = parsed - self.stylesheet_hash = md5(parsed).hexdigest() - set_last_modified(self, 'stylesheet_contents') - c.site._commit() + + minified = cssmin(parsed) + if minified: + if g.static_stylesheet_bucket: + digest = hashlib.sha1(minified).digest() + self.stylesheet_hash = (base64.urlsafe_b64encode(digest) + .rstrip("=")) + + s3cp.send_file(g.static_stylesheet_bucket, + self.static_stylesheet_name, + minified, + content_type="text/css", + never_expire=True, + replace=False, + ) + + self.stylesheet_contents = "" + else: + self.stylesheet_hash = hashlib.md5(minified).hexdigest() + self.stylesheet_contents = minified + set_last_modified(self, 'stylesheet_contents') + else: + self.stylesheet_contents = "" + self.stylesheet_hash = "" + self.stylesheet_contents_user = "" # reads from wiki; ensure pg clean + self._commit() + ModAction.create(self, c.user, action='wikirevise', details='Updated subreddit stylesheet') return wr diff --git a/r2/r2/templates/reddit.html b/r2/r2/templates/reddit.html index f811d5adc..93818dd50 100644 --- a/r2/r2/templates/reddit.html +++ b/r2/r2/templates/reddit.html @@ -82,21 +82,11 @@ %endif %endif - %if c.can_apply_styles and c.allow_styles and c.site.stylesheet_contents: - <% inline_stylesheet = ( - len(c.site.stylesheet_contents) < 1024 - and '<' not in c.site.stylesheet_contents) %> - %if inline_stylesheet: - ## for very simple stylesheets, we can just include them inline - - %else: - - %endif + %if c.can_apply_styles and c.allow_styles and c.site.stylesheet_hash: + %endif %if getattr(thing, "additional_css", None):