Rssdit is an open source project powered by Assembla

Assembla offers free public and private SVN/Git repositories and project hosting with bug/issue tracking and collaboration tools.

Commit 6bcef0037bdb2d139a15065c3f31e872bae79df3

User picture
  • Commiter: ketralnis
  • Author: ketralnis
  • Parent: 1a107601fa
  • In branches: master
  • 2008-08-26 07:15 (over 5 years ago)

1. Allow a reddit to have a cname, like www.proggit.com, that renders
the listing for that reddit

2. Allow a reddit to have a custom CSS stylesheet that appears to
visitors

3. Allow a reddit to upload a custom reddit alien logo

Files Affected

 
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
14
.hgignore
14
.hgignore
15
lighttpd.**
15
lighttpd.**
16
development.ini
16
development.ini
 
 
17
development_*.ini
17
production.ini
18
production.ini
18
r2/r2/public/static/frame.js
19
r2/r2/public/static/frame.js
19
r2/r2/public/static/reddit.js
20
r2/r2/public/static/reddit.js
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
64
stylesheet = reddit.css
64
stylesheet = reddit.css
65
stylesheet_rtl = reddit_rtl.css
65
stylesheet_rtl = reddit_rtl.css
66
 
66
 
 
 
67
allowed_css_linked_domains = my.domain.com, my.otherdomain.com
 
 
68
 
67
login_cookie = reddit_session
69
login_cookie = reddit_session
68
domain = localhost
70
domain = localhost
69
default_sr = localhost
71
default_sr = localhost
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
173
    def __call__(self, environ, start_response):
173
    def __call__(self, environ, start_response):
174
        # get base domain as defined in INI file
174
        # get base domain as defined in INI file
175
        base_domain = config['global_conf']['domain']
175
        base_domain = config['global_conf']['domain']
176
 
176
        try:
177
        sub_domains = environ['HTTP_HOST']
177
            sub_domains, request_port  = environ['HTTP_HOST'].split(':')
178
        if not sub_domains.endswith(base_domain):
178
            environ['request_port'] = int(request_port)
179
            #if the domain doesn't end with base_domain, don't do anything
179
        except ValueError:
180
            return self.app(environ, start_response)
180
            sub_domains  = environ['HTTP_HOST'].split(':')[0]
 
 
181
            
 
 
182
        #If the domain doesn't end with base_domain, assume
 
 
183
        #this is a cname, and redirect to the frame controller.
 
 
184
        #Ignore localhost so paster shell still works.
 
 
185
        #If this is an error, don't redirect
 
 
186
        if (not sub_domains.endswith(base_domain) 
 
 
187
            and (not sub_domains == 'localhost')):
 
 
188
            environ['sub_domain'] = sub_domains
 
 
189
            if (not environ.get('extension') 
 
 
190
                and (not environ['PATH_INFO'].startswith('/error'))):
 
 
191
                environ['original_path'] = environ['PATH_INFO']
 
 
192
                environ['PATH_INFO'] = '/frame'
 
 
193
                return self.app(environ, start_response)
181
 
194
 
182
        sub_domains = sub_domains[:-len(base_domain)].strip('.')
195
        sub_domains = sub_domains[:-len(base_domain)].strip('.')
183
        sub_domains = sub_domains.split('.')
196
        sub_domains = sub_domains.split('.')
...
 
...
 
294
                pass
307
                pass
295
        return r
308
        return r
296
 
309
 
 
 
310
class LimitUploadSize(object):
 
 
311
    def __init__(self, app, max_size=1024*500):
 
 
312
        self.app = app
 
 
313
        self.max_size = max_size
 
 
314
 
 
 
315
    def __call__(self, environ, start_response):
 
 
316
        cl_key = 'CONTENT_LENGTH'
 
 
317
        if environ['REQUEST_METHOD'] == 'POST':
 
 
318
            if ((cl_key not in environ)
 
 
319
                or int(environ[cl_key]) > self.max_size):
 
 
320
                r = Response()
 
 
321
                r.status_code = 500
 
 
322
                r.content = 'request too big'
 
 
323
                return r(environ, start_response)
 
 
324
 
 
 
325
        return self.app(environ, start_response)
297
 
326
 
298
#god this shit is disorganized and confusing
327
#god this shit is disorganized and confusing
299
class RedditApp(PylonsBaseWSGIApp):
328
class RedditApp(PylonsBaseWSGIApp):
...
 
...
 
335
 
364
 
336
    # CUSTOM MIDDLEWARE HERE (filtered by the error handling middlewares)
365
    # CUSTOM MIDDLEWARE HERE (filtered by the error handling middlewares)
337
 
366
 
 
 
367
    app = LimitUploadSize(app)
338
    app = ProfilingMiddleware(app)
368
    app = ProfilingMiddleware(app)
339
    app = SourceViewMiddleware(app)
369
    app = SourceViewMiddleware(app)
340
 
370
 
341
    app = DomainMiddleware(app)
 
 
342
    app = SubredditMiddleware(app)
371
    app = SubredditMiddleware(app)
 
 
372
    app = DomainMiddleware(app)
343
    app = ExtensionMiddleware(app)
373
    app = ExtensionMiddleware(app)
344
 
374
 
 
 
375
 
345
    log_path = global_conf.get('log_path')
376
    log_path = global_conf.get('log_path')
346
    if log_path:
377
    if log_path:
347
        process_iden = global_conf.get('scgi_port', 'default')
378
        process_iden = global_conf.get('scgi_port', 'default')
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
94
 
94
 
95
    mc('/mail/optout', controller='front', action = 'optout')
95
    mc('/mail/optout', controller='front', action = 'optout')
96
    mc('/mail/optin',  controller='front', action = 'optin')
96
    mc('/mail/optin',  controller='front', action = 'optin')
 
 
97
    mc('/stylesheet', controller = 'front', action = 'stylesheet')
 
 
98
    mc('/frame', controller='front', action = 'frame')
97
 
99
 
98
    mc('/', controller='hot', action='listing')
100
    mc('/', controller='hot', action='listing')
99
 
101
 
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
27
from validator import *
27
from validator import *
28
 
28
 
29
from r2.models import *
29
from r2.models import *
 
 
30
from r2.models.subreddit import Default as DefaultSR
30
import r2.models.thing_changes as tc
31
import r2.models.thing_changes as tc
31
 
32
 
32
from r2.lib.utils import get_title, sanitize_url, timeuntil, set_last_modified
33
from r2.lib.utils import get_title, sanitize_url, timeuntil, set_last_modified
33
from r2.lib.wrapped import Wrapped
34
from r2.lib.wrapped import Wrapped
34
from r2.lib.pages import FriendList, ContributorList, ModList, \
35
from r2.lib.pages import FriendList, ContributorList, ModList, \
35
    BannedList, BoringPage, FormPage, NewLink
36
    BannedList, BoringPage, FormPage, NewLink, CssError, UploadedImage
36
 
37
 
37
from r2.lib.menus import CommentSortMenu
38
from r2.lib.menus import CommentSortMenu
38
from r2.lib.translation import Translator
39
from r2.lib.translation import Translator
...
 
...
 
40
from r2.lib.captcha import get_iden
41
from r2.lib.captcha import get_iden
41
from r2.lib import emailer
42
from r2.lib import emailer
42
from r2.lib.strings import strings
43
from r2.lib.strings import strings
 
 
44
from r2.lib.memoize import clear_memo
 
 
45
from r2.lib.filters import _force_unicode
43
from r2.lib.db import queries
46
from r2.lib.db import queries
44
from r2.config import cache
47
from r2.config import cache
45
 
 
 
46
from simplejson import dumps
 
 
47
 
 
 
48
from r2.lib.jsonresponse import JsonResponse, Json
48
from r2.lib.jsonresponse import JsonResponse, Json
49
from r2.lib.jsontemplates import api_type
49
from r2.lib.jsontemplates import api_type
 
 
50
from r2.lib import cssfilter
 
 
51
 
 
 
52
from simplejson import dumps
50
 
53
 
51
from datetime import datetime, timedelta
54
from datetime import datetime, timedelta
 
 
55
from md5 import md5
52
from r2.lib.organic import update_pos
56
from r2.lib.organic import update_pos
53
 
57
 
54
def link_listing_by_url(url, count = None):
58
def link_listing_by_url(url, count = None):
...
 
...
 
64
    builder = IDBuilder(names, num = 25)
68
    builder = IDBuilder(names, num = 25)
65
    listing = LinkListing(builder).listing()
69
    listing = LinkListing(builder).listing()
66
    return listing
70
    return listing
67
            
71
 
68
 
72
 
69
class ApiController(RedditController):
73
class ApiController(RedditController):
70
    def response_func(self, **kw):
74
    def response_func(self, **kw):
...
 
...
 
74
        try:    
78
        try:    
75
            l = Link._by_url(url, sr)
79
            l = Link._by_url(url, sr)
76
            if message:
80
            if message:
77
                return l.permalink + '?already_submitted=true'
81
                return l.already_submitted_link()
78
            else:
82
            else:
79
                return l.permalink
83
                return l.make_permalink_slow()
80
        except NotFound:
84
        except NotFound:
81
            pass
85
            pass
82
 
86
 
...
 
...
 
272
        # well, nothing left to do but submit it
276
        # well, nothing left to do but submit it
273
        l = Link._submit(request.post.title, url, c.user, sr, ip, spam)
277
        l = Link._submit(request.post.title, url, c.user, sr, ip, spam)
274
        if url.lower() == 'self':
278
        if url.lower() == 'self':
275
            l.url = l.permalink
279
            l.url = l.make_permalink_slow()
276
            l.is_self = True
280
            l.is_self = True
277
            l._commit()
281
            l._commit()
278
        Vote.vote(c.user, l, True, ip, spam)
282
        Vote.vote(c.user, l, True, ip, spam)
...
 
...
 
292
 
296
 
293
        # flag search indexer that something has changed
297
        # flag search indexer that something has changed
294
        tc.changed(l)
298
        tc.changed(l)
 
 
299
 
 
 
300
        # make_permalink is designed for links that can be set to _top
 
 
301
        # here, we need to generate an ajax redirect as if we were not on a
 
 
302
        # cname.
 
 
303
        cname = c.cname
 
 
304
        c.cname = False
 
 
305
        path = l.make_permalink_slow()
 
 
306
        c.cname = cname
295
 
307
 
296
        res._redirect(l.permalink)
308
        res._redirect(path)
297
 
309
 
298
 
310
 
299
    def _login(self, res, user, dest='', rem = None):
311
    def _login(self, res, user, dest='', rem = None):
...
 
...
 
722
    @Json
734
    @Json
723
    @validate(VUser(),
735
    @validate(VUser(),
724
              VModhash(),
736
              VModhash(),
 
 
737
              stylesheet_contents = nop('stylesheet_contents'),
 
 
738
              op = VOneOf('op',['save','preview']))
 
 
739
    def POST_subreddit_stylesheet(self, res, stylesheet_contents = '', op='save'):
 
 
740
        if not c.site.can_change_stylesheet(c.user):
 
 
741
            return self.abort(403,'forbidden')
 
 
742
 
 
 
743
        if g.css_killswitch:
 
 
744
            return self.abort(403,'forbidden')
 
 
745
 
 
 
746
        parsed, report = cssfilter.validate_css(stylesheet_contents)
 
 
747
 
 
 
748
        if report.errors:
 
 
749
            error_items = [ CssError(x).render(style='html')
 
 
750
                            for x in sorted(report.errors) ]
 
 
751
 
 
 
752
            res._update('status', innerHTML = _('validation errors'))
 
 
753
            res._update('validation-errors', innerHTML = ''.join(error_items))
 
 
754
            res._show('error-header')
 
 
755
        else:
 
 
756
            res._hide('error-header')
 
 
757
            res._update('status', innerHTML = '')
 
 
758
            res._update('validation-errors', innerHTML = '')
 
 
759
 
 
 
760
        if not report.errors and op == 'save':
 
 
761
            stylesheet_contents_user   = stylesheet_contents
 
 
762
            stylesheet_contents_parsed = parsed.cssText if parsed else ''
 
 
763
 
 
 
764
            c.site.stylesheet_contents      = stylesheet_contents_parsed
 
 
765
            c.site.stylesheet_contents_user = stylesheet_contents_user
 
 
766
 
 
 
767
            c.site.stylesheet_hash = md5(stylesheet_contents_parsed).hexdigest()
 
 
768
 
 
 
769
            set_last_modified(c.site,'stylesheet_contents')
 
 
770
            tc.changed(c.site)
 
 
771
            c.site._commit()
 
 
772
 
 
 
773
            res._update('status', innerHTML = 'saved')
 
 
774
            res._call('applyStylesheetFromTextbox("stylesheet_contents");')
 
 
775
            res._update('validation-errors', innerHTML = '')
 
 
776
 
 
 
777
        elif op == 'preview':
 
 
778
            # try to find a link to use, otherwise give up and
 
 
779
            # return
 
 
780
            links = cssfilter.find_preview_links(c.site)
 
 
781
            if not links:
 
 
782
                # we're probably not going to be able to find any
 
 
783
                # comments, either; screw it
 
 
784
                return
 
 
785
 
 
 
786
            res._show('preview-table')
 
 
787
 
 
 
788
            # do a regular link
 
 
789
            cssfilter.rendered_link('preview_link_normal',
 
 
790
                                    res, links,
 
 
791
                                    media = 'off', compress=False)
 
 
792
            # now do one with media
 
 
793
            cssfilter.rendered_link('preview_link_media',
 
 
794
                                    res, links,
 
 
795
                                    media = 'on', compress=False)
 
 
796
            # do a compressed link
 
 
797
            cssfilter.rendered_link('preview_link_compressed',
 
 
798
                                    res, links,
 
 
799
                                    media = 'off', compress=True)
 
 
800
            # and do a comment
 
 
801
            comments = cssfilter.find_preview_comments(c.site)
 
 
802
            if not comments:
 
 
803
                return
 
 
804
            cssfilter.rendered_comment('preview_comment',res,comments)
 
 
805
 
 
 
806
    @validate(VUser(),
 
 
807
              VModhash(),
 
 
808
              VRatelimit(rate_user = True,
 
 
809
                         rate_ip = True,
 
 
810
                         prefix = 'upload_reddit_img_'),
 
 
811
              file = VLength('file',length=1024*500),
 
 
812
              op = VOneOf('op',['upload','delete']))
 
 
813
    def POST_upload_header_img(self, file, op):
 
 
814
        if not c.site.can_change_stylesheet(c.user):
 
 
815
            return self.abort403()
 
 
816
 
 
 
817
        if g.css_killswitch:
 
 
818
            return self.abort(403,'forbidden')
 
 
819
 
 
 
820
        if op == 'upload':
 
 
821
            try:
 
 
822
                cleaned = cssfilter.clean_image(file,'PNG')
 
 
823
                new_url = cssfilter.save_header_image(c.site, cleaned)
 
 
824
            except cssfilter.BadImage:
 
 
825
                return UploadedImage(_('bad image'),c.site.header).render()
 
 
826
 
 
 
827
            c.site.header = new_url
 
 
828
            c.site._commit()
 
 
829
 
 
 
830
            return UploadedImage(_('saved'),new_url,'upload').render()
 
 
831
        elif op == 'delete':
 
 
832
            c.site.header = None
 
 
833
            c.site._commit()
 
 
834
 
 
 
835
            return UploadedImage(_('deleted'),DefaultSR.header,'delete').render()
 
 
836
 
 
 
837
    @Json
 
 
838
    @validate(VUser(),
 
 
839
              VModhash(),
725
              VRatelimit(rate_user = True,
840
              VRatelimit(rate_user = True,
726
                         rate_ip = True,
841
                         rate_ip = True,
727
                         prefix = 'create_reddit_'),
842
                         prefix = 'create_reddit_'),
728
              name = VSubredditName("name"),
843
              name = VSubredditName("name"),
729
              title = VSubredditTitle("title"),
844
              title = VSubredditTitle("title"),
 
 
845
              domain = VCnameDomain("domain"),
730
              description = VSubredditDesc("description"),
846
              description = VSubredditDesc("description"),
731
              firsttext = nop("firsttext"),
847
              firsttext = nop("firsttext"),
732
              header = nop("headerfile"),
848
              header = nop("headerfile"),
...
 
...
 
740
              type = VOneOf('type', ('public', 'private', 'restricted'))
856
              type = VOneOf('type', ('public', 'private', 'restricted'))
741
              )
857
              )
742
    def POST_site_admin(self, res, name ='', sr = None, **kw):
858
    def POST_site_admin(self, res, name ='', sr = None, **kw):
743
        res._update('status', innerHTML = '')
 
 
744
        redir = False
859
        redir = False
745
        kw = dict((k, v) for k, v in kw.iteritems()
860
        kw = dict((k, v) for k, v in kw.iteritems()
746
                  if v is not None
861
                  if v is not None
747
                  and k in ('name', 'title', 'description', 'firsttext',
862
                  and k in ('name', 'title', 'domain', 'description', 'firsttext',
748
                            'static_path', 'ad_file', 'over_18', 'show_media',
863
                            'static_path', 'ad_file', 'over_18', 'show_media',
749
                            'type', 'header', 'lang', 'stylesheet'))
864
                            'type', 'header', 'lang', 'stylesheet'))
750
 
865
 
...
 
...
 
753
            time = timeuntil(datetime.now(g.tz) + timedelta(seconds=600))
868
            time = timeuntil(datetime.now(g.tz) + timedelta(seconds=600))
754
            c.errors.add(errors.RATELIMIT, {'time': time})
869
            c.errors.add(errors.RATELIMIT, {'time': time})
755
 
870
 
 
 
871
        domain = kw['domain']
 
 
872
        cname_sr = domain and Subreddit._by_domain(domain)
 
 
873
        if cname_sr and (not sr or sr != cname_sr):
 
 
874
                kw['domain'] = None
 
 
875
                c.errors.add(errors.USED_CNAME)
 
 
876
 
756
        if not sr and res._chk_error(errors.RATELIMIT):
877
        if not sr and res._chk_error(errors.RATELIMIT):
757
            pass
878
            pass
758
        elif not sr and res._chk_errors((errors.SUBREDDIT_EXISTS,
879
        elif not sr and res._chk_errors((errors.SUBREDDIT_EXISTS,
...
 
...
 
764
            res._focus('title')
885
            res._focus('title')
765
        elif res._chk_error(errors.INVALID_SUBREDDIT_TYPE):
886
        elif res._chk_error(errors.INVALID_SUBREDDIT_TYPE):
766
            pass
887
            pass
 
 
888
        elif res._chk_errors((errors.BAD_CNAME, errors.USED_CNAME)):
 
 
889
            res._hide('example_domain')
 
 
890
            res._focus('domain')
767
        elif res._chk_error(errors.DESC_TOO_LONG):
891
        elif res._chk_error(errors.DESC_TOO_LONG):
768
            res._focus('description')
892
            res._focus('description')
769
 
893
 
...
 
...
 
784
 
908
 
785
        if not res.error:
909
        if not res.error:
786
            #assume sr existed, or was just built
910
            #assume sr existed, or was just built
 
 
911
            clear_memo('subreddit._by_domain', Subreddit, _force_unicode(sr.domain))
787
            for k, v in kw.iteritems():
912
            for k, v in kw.iteritems():
788
                setattr(sr, k, v)
913
                setattr(sr, k, v)
789
            sr._commit()
914
            sr._commit()
...
 
...
 
791
            # flag search indexer that something has changed
916
            # flag search indexer that something has changed
792
            tc.changed(sr)
917
            tc.changed(sr)
793
 
918
 
 
 
919
            res._update('status', innerHTML = _('saved'))
 
 
920
 
 
 
921
 
794
        if redir:
922
        if redir:
795
            res._redirect(redir)
923
            res._redirect(redir)
796
 
924
 
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
98
        try:
98
        try:
99
            return RedditController.__call__(self, environ, start_response)
99
            return RedditController.__call__(self, environ, start_response)
100
        except:
100
        except:
101
            c.response.content = "something really awful just happened"
101
            if g.debug:
102
            return c.response
102
                # if we're in debug mode, let this hit Pylons so we
 
 
103
                # get a stack trace
 
 
104
                raise
 
 
105
            else:
 
 
106
                c.response.content = "something really awful just happened"
 
 
107
                return c.response
103
 
108
 
104
 
109
 
105
    def send403(self):
110
    def send403(self):
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
57
        ('DRACONIAN', _('you must accept the terms first')),
57
        ('DRACONIAN', _('you must accept the terms first')),
58
        ('BANNED_IP', "IP banned"),
58
        ('BANNED_IP', "IP banned"),
59
        ('BANNED_DOMAIN', "Domain banned"),
59
        ('BANNED_DOMAIN', "Domain banned"),
 
 
60
        ('BAD_CNAME', "that domain isn't going to work"),
 
 
61
        ('USED_CNAME', "that cname is already in use"),
60
        ('INVALID_SUBREDDIT_TYPE', _('that option is not valid')),
62
        ('INVALID_SUBREDDIT_TYPE', _('that option is not valid')),
61
        ('DESC_TOO_LONG', _('description is too long')),
63
        ('DESC_TOO_LONG', _('description is too long')),
62
        ('CHEATER', 'what do you think you\'re doing there?'),
64
        ('CHEATER', 'what do you think you\'re doing there?'),
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
154
 
154
 
155
        # if permalink page, add that message first to the content
155
        # if permalink page, add that message first to the content
156
        if comment:
156
        if comment:
157
            displayPane.append(PermalinkMessage(article.permalink))
157
            displayPane.append(PermalinkMessage(article.make_permalink_slow()))
158
 
158
 
159
        # insert reply box only for logged in user
159
        # insert reply box only for logged in user
160
        if c.user_is_loggedin and article.subreddit_slow.can_comment(c.user):
160
        if c.user_is_loggedin and article.subreddit_slow.can_comment(c.user):
...
 
...
 
328
                       ).render()
328
                       ).render()
329
        return res
329
        return res
330
 
330
 
 
 
331
    def GET_stylesheet(self):
 
 
332
        if hasattr(c.site,'stylesheet_contents') and not g.css_killswitch:
 
 
333
            self.check_modified(c.site,'stylesheet_contents')
 
 
334
 
 
 
335
            c.response.content = c.site.stylesheet_contents
 
 
336
            c.response_content_type = 'text/css'
 
 
337
 
 
 
338
            return c.response
 
 
339
        else:
 
 
340
            return self.abort404()
 
 
341
 
331
    @base_listing
342
    @base_listing
332
    @validate(location = nop('location'))
343
    @validate(location = nop('location'))
333
    def GET_editreddit(self, location, num, after, reverse, count):
344
    def GET_editreddit(self, location, num, after, reverse, count):
334
        """Edit reddit form. """
345
        """Edit reddit form."""
335
        if isinstance(c.site, FakeSubreddit):
346
        if isinstance(c.site, FakeSubreddit):
336
            return self.abort404()
347
            return self.abort404()
337
 
348
 
...
 
...
 
346
            pane = BannedList(editable = is_moderator)
357
            pane = BannedList(editable = is_moderator)
347
        elif location == 'contributors' and c.site.type != 'public':
358
        elif location == 'contributors' and c.site.type != 'public':
348
            pane = ContributorList(editable = is_moderator)
359
            pane = ContributorList(editable = is_moderator)
 
 
360
        elif (location == 'stylesheet'
 
 
361
              and c.site.can_change_stylesheet(c.user)
 
 
362
              and not g.css_killswitch):
 
 
363
            if hasattr(c.site,'stylesheet_contents_user') and c.site.stylesheet_contents_user:
 
 
364
                stylesheet_contents = c.site.stylesheet_contents_user
 
 
365
            elif hasattr(c.site,'stylesheet_contents') and c.site.stylesheet_contents:
 
 
366
                stylesheet_contents = c.site.stylesheet_contents
 
 
367
            else:
 
 
368
                stylesheet_contents = ''
 
 
369
            pane = SubredditStylesheet(site = c.site,
 
 
370
                                       stylesheet_contents = stylesheet_contents)
349
        elif is_moderator and location == 'spam':
371
        elif is_moderator and location == 'spam':
350
            links = Link._query(Link.c._spam == True)
372
            links = Link._query(Link.c._spam == True)
351
            comments = Comment._query(Comment.c._spam == True)
373
            comments = Comment._query(Comment.c._spam == True)
...
 
...
 
486
        """wipe login cookie and redirect to referer."""
508
        """wipe login cookie and redirect to referer."""
487
        self.logout()
509
        self.logout()
488
        dest = request.referer or '/'
510
        dest = request.referer or '/'
 
 
511
        if c.cname:
 
 
512
            dest = '/?cnameframe=1'
 
 
513
        if not dest.startswith("http://"):
 
 
514
            return self.redirect(c.site.path + dest)
489
        return self.redirect(dest)
515
        return self.redirect(dest)
490
 
516
 
491
 
517
 
...
 
...
 
591
        ApiController.POST_optin."""
617
        ApiController.POST_optin."""
592
        return self._render_opt_in_out(msg_hash, False)
618
        return self._render_opt_in_out(msg_hash, False)
593
 
619
 
 
 
620
    def GET_frame(self):
 
 
621
        """used for cname support.  makes a frame and
 
 
622
        puts the proper url as the frame source"""
 
 
623
        sub_domain = request.environ.get('sub_domain')
 
 
624
        original_path = request.environ.get('original_path')
 
 
625
        sr = Subreddit._by_domain(sub_domain)
 
 
626
        if sub_domain and sr and original_path:
 
 
627
            return Cnameframe(original_path, sr.name, sr.title, sub_domain).render()
 
 
628
        else:
 
 
629
            return self.abort404()
 
 
630
 
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
350
    @staticmethod
350
    @staticmethod
351
    def builder_wrapper(thing):
351
    def builder_wrapper(thing):
352
        if isinstance(thing, Comment):
352
        if isinstance(thing, Comment):
353
            p = thing.permalink
353
            p = thing.make_permalink_slow()
354
            f = thing._fullname
354
            f = thing._fullname
355
            thing.__class__ = Message
355
            thing.__class__ = Message
356
            w = Wrapped(thing)
356
            w = Wrapped(thing)
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
101
              pref_min_comment_score = VInt('min_comment_score', -100, 100),
101
              pref_min_comment_score = VInt('min_comment_score', -100, 100),
102
              pref_num_comments = VInt('num_comments', 1, g.max_comments,
102
              pref_num_comments = VInt('num_comments', 1, g.max_comments,
103
                                       default = g.num_comments),
103
                                       default = g.num_comments),
 
 
104
              pref_show_stylesheets = VBoolean('show_stylesheets'),
104
              all_langs = nop('all-langs', default = 'all'))
105
              all_langs = nop('all-langs', default = 'all'))
105
    def POST_options(self, all_langs, pref_lang, **kw):
106
    def POST_options(self, all_langs, pref_lang, **kw):
106
        self.set_options(all_langs, pref_lang, **kw)
107
        self.set_options(all_langs, pref_lang, **kw)
107
        return self.redirect("/prefs?done=true")
108
        q_string = {'done': 'true'}
 
 
109
        if c.cname:
 
 
110
            q_string['cnameframe'] = '1'
 
 
111
        return self.redirect((request.referer or "/prefs") + query_string(q_string))
108
 
112
 
109
    def GET_over18(self):
113
    def GET_over18(self):
110
        return BoringPage(_("over 18?"),
114
        return BoringPage(_("over 18?"),
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
34
from r2.models import *
34
from r2.models import *
35
from errors import ErrorSet
35
from errors import ErrorSet
36
from validator import *
36
from validator import *
37
from r2.lib.template_helpers import reddit_link
37
from r2.lib.template_helpers import add_sr
38
from r2.lib.jsontemplates import api_type
38
from r2.lib.jsontemplates import api_type
39
 
39
 
40
from copy import copy
40
from copy import copy
...
 
...
 
105
    uname = c.user.name if c.user_is_loggedin else ""
105
    uname = c.user.name if c.user_is_loggedin else ""
106
    c.response.set_cookie(uname + '_' + name,
106
    c.response.set_cookie(uname + '_' + name,
107
                          value = val,
107
                          value = val,
108
                          domain = c.domain)
108
                          domain = g.domain)
109
 
109
 
110
def read_click_cookie():
110
def read_click_cookie():
111
    if c.user_is_loggedin:
111
    if c.user_is_loggedin:
...
 
...
 
129
        if not request.cookies.get("reddit_first"):
129
        if not request.cookies.get("reddit_first"):
130
            c.response.set_cookie("reddit_first", "first",
130
            c.response.set_cookie("reddit_first", "first",
131
                                  expires = NEVER,
131
                                  expires = NEVER,
132
                                  domain = c.domain)
132
                                  domain = g.domain)
133
            return True
133
            return True
134
    return False
134
    return False
135
 
135
 
...
 
...
 
146
    sr_name=request.environ.get("subreddit", request.params.get('r'))
146
    sr_name=request.environ.get("subreddit", request.params.get('r'))
147
 
147
 
148
    if not sr_name or sr_name == Default.name:
148
    if not sr_name or sr_name == Default.name:
149
        c.site = Default
149
        sub_domain = request.environ.get('sub_domain')
 
 
150
        sr = Subreddit._by_domain(sub_domain) if sub_domain else None
 
 
151
        c.site = sr or Default
150
    elif sr_name == 'r':
152
    elif sr_name == 'r':
151
        c.site = Sub
153
        c.site = Sub
152
    else:
154
    else:
...
 
...
 
191
        c.response_wrappers.append(utils.to_js)
193
        c.response_wrappers.append(utils.to_js)
192
    elif extension == 'mobile':
194
    elif extension == 'mobile':
193
        c.render_style = 'mobile'
195
        c.render_style = 'mobile'
 
 
196
    #Insert new extentions above this line
 
 
197
    elif extension not in ('', 'html'):
 
 
198
        dest = "http://%s%s" % (request.host, request.path)
 
 
199
        if request.get:
 
 
200
            dest += utils.query_string(request.get)
 
 
201
        redirect_to(dest)
194
 
202
 
195
def get_browser_langs():
203
def get_browser_langs():
196
    browser_langs = []
204
    browser_langs = []
...
 
...
 
252
    else:
260
    else:
253
        c.content_langs = c.user.pref_content_langs
261
        c.content_langs = c.user.pref_content_langs
254
 
262
 
 
 
263
def set_cnameframe():
 
 
264
    if (bool(request.params.get('cnameframe')) 
 
 
265
        or request.host.split(":")[0] != g.domain):
 
 
266
        c.cname = True
 
 
267
 
255
def ratelimit_agents():
268
def ratelimit_agents():
256
    user_agent = request.user_agent
269
    user_agent = request.user_agent
257
    for s in g.agents:
270
    for s in g.agents:
...
 
...
 
308
        key = ''.join((str(c.lang),
321
        key = ''.join((str(c.lang),
309
                       str(c.content_langs),
322
                       str(c.content_langs),
310
                       request.host,
323
                       request.host,
 
 
324
                       c.cname, 
311
                       request.fullpath,
325
                       request.fullpath,
312
                       str(c.firsttime),
326
                       str(c.firsttime),
313
                       str(c.over18)))
327
                       str(c.over18)))
...
 
...
 
367
        set_content_type()
381
        set_content_type()
368
        set_iface_lang()
382
        set_iface_lang()
369
        set_content_lang()
383
        set_content_lang()
 
 
384
        set_cnameframe()
370
 
385
 
371
        # check if the user has access to this subreddit
386
        # check if the user has access to this subreddit
372
        if not c.site.can_view(c.user):
387
        if not c.site.can_view(c.user):
373
            abort(403, "forbidden")
388
            abort(403, "forbidden")
374
 
389
 
375
        #check over 18
390
        #check over 18
376
        if c.site.over_18 and not c.over18:
391
        if c.site.over_18 and not c.over18 and not request.path == "/frame":
377
            d = dict(dest=reddit_link(request.path, url = True) + utils.query_string(request.GET))
392
            d = dict(dest=add_sr(request.path) + utils.query_string(request.GET))
378
            return redirect_to("/over18" + utils.query_string(d))
393
            return redirect_to("/over18" + utils.query_string(d))
379
 
394
 
380
        #check content cache
395
        #check content cache
...
 
...
 
425
    def check_modified(self, thing, action):
440
    def check_modified(self, thing, action):
426
        if c.user_is_loggedin:
441
        if c.user_is_loggedin:
427
            return
442
            return
428
 
443
        
429
        date = utils.is_modified_since(thing, action, request.if_modified_since)
444
        date = utils.is_modified_since(thing, action, request.if_modified_since)
430
        if date is True:
445
        if date is True:
431
            abort(304, 'not modified')
446
            abort(304, 'not modified')
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
21
################################################################################
21
################################################################################
22
from pylons.controllers.util import redirect_to
22
from pylons.controllers.util import redirect_to
23
from r2.lib.base import BaseController
23
from r2.lib.base import BaseController
 
 
24
from pylons import c
24
 
25
 
25
class RedirectController(BaseController):
26
class RedirectController(BaseController):
26
    def GET_redirect(self, dest):
27
    def GET_redirect(self, dest):
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
26
from r2.lib.filters import unkeep_space, websafe
26
from r2.lib.filters import unkeep_space, websafe
27
from r2.lib.db.operators import asc, desc
27
from r2.lib.db.operators import asc, desc
28
from r2.config import cache
28
from r2.config import cache
29
from r2.lib.template_helpers import reddit_link
29
from r2.lib.template_helpers import add_sr
30
from r2.lib.jsonresponse import json_respond
30
from r2.lib.jsonresponse import json_respond
31
 
31
 
32
from r2.models import *
32
from r2.models import *
...
 
...
 
77
                return fn(self, *a, **kw)
77
                return fn(self, *a, **kw)
78
 
78
 
79
            except UserRequiredException:
79
            except UserRequiredException:
80
                d = dict(dest=reddit_link(request.path, url = True)
80
                d = dict(dest=add_sr(request.path) + 
81
                         + utils.query_string(request.GET))
81
                         utils.query_string(request.GET))
 
 
82
                if c.cname:
 
 
83
                    d['cnameframe'] = 1
82
                path = "/login"
84
                path = "/login"
83
                if request.environ.get('extension'):
85
                if request.environ.get('extension'):
84
                    path += ".%s" % request.environ['extension']
86
                    path += ".%s" % request.environ['extension']
...
 
...
 
633
 
635
 
634
        if reason.startswith('redirect_'):
636
        if reason.startswith('redirect_'):
635
            dest = reason[9:]
637
            dest = reason[9:]
 
 
638
            if (not dest.startswith(c.site.path) and 
 
 
639
                not dest.startswith("http:")):
 
 
640
                dest = (c.site.path + dest).replace('//', '/')
636
            return ('redirect', dest)
641
            return ('redirect', dest)
637
        if reason.startswith('vote_'):
642
        if reason.startswith('vote_'):
638
            fullname = reason[5:]
643
            fullname = reason[5:]
639
            t = Thing._by_fullname(fullname, data=True)
644
            t = Thing._by_fullname(fullname, data=True)
640
            return ('redirect', t.permalink)
645
            return ('redirect', t.make_permalink_slow())
641
        elif reason.startswith('share_'):
646
        elif reason.startswith('share_'):
642
            fullname = reason[6:]
647
            fullname = reason[6:]
643
            t = Thing._by_fullname(fullname, data=True)
648
            t = Thing._by_fullname(fullname, data=True)
644
            return ('redirect', t.permalink)
649
            return ('redirect', t.make_permalink_slow())
645
        elif reason.startswith('reply_'):
650
        elif reason.startswith('reply_'):
646
            fullname = reason[6:]
651
            fullname = reason[6:]
647
            t = Thing._by_fullname(fullname, data=True)
652
            t = Thing._by_fullname(fullname, data=True)
648
            return ('redirect', t.permalink)
653
            return ('redirect', t.make_permalink_slow())
649
        elif reason.startswith('sr_change_'):
654
        elif reason.startswith('sr_change_'):
650
            sr_list = reason[10:].split(',')
655
            sr_list = reason[10:].split(',')
651
            fullnames = dict(i.split(':') for i in sr_list)
656
            fullnames = dict(i.split(':') for i in sr_list)
...
 
...
 
695
            # return single email if one is expected, list otherwise
700
            # return single email if one is expected, list otherwise
696
            return list(emails)[0] if self.num == 1 else emails
701
            return list(emails)[0] if self.num == 1 else emails
697
 
702
 
 
 
703
 
 
 
704
class VCnameDomain(Validator):
 
 
705
    domain_re  = re.compile(r'.+\..+')
 
 
706
 
 
 
707
    def run(self, domain):
 
 
708
        if (domain
 
 
709
            and (not self.domain_re.match(domain)
 
 
710
                 or domain.endswith('.reddit.com'))):
 
 
711
            c.errors.add(errors.BAD_CNAME)
 
 
712
        return domain or ''
 
 
713
 
698
# NOTE: make sure *never* to have res check these are present
714
# NOTE: make sure *never* to have res check these are present
699
# otherwise, the response could contain reference to these errors...!
715
# otherwise, the response could contain reference to these errors...!
700
class ValidIP(Validator):
716
class ValidIP(Validator):
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
49
                  'uncompressedJS',
49
                  'uncompressedJS',
50
                  'enable_doquery',
50
                  'enable_doquery',
51
                  'use_query_cache',
51
                  'use_query_cache',
52
                  'write_query_queue']
52
                  'write_query_queue',
 
 
53
                  'css_killswitch']
53
 
54
 
54
    tuple_props = ['memcaches',
55
    tuple_props = ['memcaches',
55
                   'rec_cache',
56
                   'rec_cache',
...
 
...
 
57
                   'monitored_servers',
58
                   'monitored_servers',
58
                   'default_srs',
59
                   'default_srs',
59
                   'agents',
60
                   'agents',
 
 
61
                   'allowed_css_linked_domains',
60
                   'query_caches']
62
                   'query_caches']
61
 
63
 
62
    def __init__(self, global_conf, app_conf, paths, **extra):
64
    def __init__(self, global_conf, app_conf, paths, **extra):
...
 
...
 
157
        self.log.addHandler(logging.StreamHandler())
159
        self.log.addHandler(logging.StreamHandler())
158
        if self.debug:
160
        if self.debug:
159
            self.log.setLevel(logging.DEBUG)
161
            self.log.setLevel(logging.DEBUG)
160
            
162
 
 
 
163
        #read in our CSS so that it can become a default for subreddit
 
 
164
        #stylesheets
 
 
165
        stylesheet_path = os.path.join(paths.get('static_files'),
 
 
166
                                       self.static_path.lstrip('/'),
 
 
167
                                       self.stylesheet)
 
 
168
        with open(stylesheet_path) as s:
 
 
169
            self.default_stylesheet = s.read()
 
 
170
 
161
    def __del__(self):
171
    def __del__(self):
162
        """
172
        """
163
        Put any cleanup code to be run when the application finally exits 
173
        Put any cleanup code to be run when the application finally exits 
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
94
 
94
 
95
    @staticmethod
95
    @staticmethod
96
    def redirect(dest, code = 302):
96
    def redirect(dest, code = 302):
97
        c.response.headers['Location'] = _force_unicode(dest).encode('utf8')
97
        dest = _force_unicode(dest).encode('utf8')
 
 
98
        if c.cname and "?cnameframe=1" not in red:
 
 
99
            dest += "?cnameframe=1"
 
 
100
        c.response.headers['Location'] = dest
98
        c.response.status_code = code
101
        c.response.status_code = code
 
 
102
 
99
        return c.response
103
        return c.response
100
 
104
 
101
    def sendjs(self,js, callback="document.write", escape=True):
105
    def sendjs(self,js, callback="document.write", escape=True):
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
 
 
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 the
 
 
17
# Original Code is CondeNet, Inc.
 
 
18
# 
 
 
19
# All portions of the code written by CondeNet are Copyright (c) 2006-2008
 
 
20
# CondeNet, Inc. All Rights Reserved.
 
 
21
################################################################################
 
 
22
from __future__ import with_statement
 
 
23
 
 
 
24
from r2.models import *
 
 
25
from r2.lib.utils import sanitize_url, domain
 
 
26
from r2.lib.strings import string_dict
 
 
27
 
 
 
28
from pylons import g
 
 
29
from pylons.i18n import _
 
 
30
 
 
 
31
import re
 
 
32
 
 
 
33
import cssutils
 
 
34
from cssutils import CSSParser
 
 
35
from cssutils.css import CSSStyleRule
 
 
36
from cssutils.css import CSSValue, CSSValueList
 
 
37
from cssutils.css import CSSPrimitiveValue
 
 
38
from cssutils.css import cssproperties
 
 
39
from xml.dom import DOMException
 
 
40
 
 
 
41
msgs = string_dict['css_validator_messages']
 
 
42
 
 
 
43
custom_macros = {
 
 
44
    'num': r'[-]?\d+|[-]?\d*\.\d+',
 
 
45
    'percentage': r'{num}%',
 
 
46
    'length': r'0|{num}(em|ex|px|in|cm|mm|pt|pc)',
 
 
47
    'color': r'orangered|dimgray|lightgray|whitesmoke|pink',
 
 
48
}
 
 
49
 
 
 
50
custom_values = {
 
 
51
    '_height': r'{length}|{percentage}|auto|inherit',
 
 
52
    '_width': r'{length}|{percentage}|auto|inherit',
 
 
53
    '_overflow': r'visible|hidden|scroll|auto|inherit',
 
 
54
    'color': r'{color}',
 
 
55
    'background-color': r'{color}',
 
 
56
    'border-color': r'{color}',
 
 
57
    'background-position': r'(({percentage}|{length}){0,3})?\s*(top|center|left)?\s*(left|center|right)?',
 
 
58
    'opacity': r'{num}',
 
 
59
    'filter': r'alpha\(opacity={num}\)',
 
 
60
}
 
 
61
 
 
 
62
def _expand_macros(tokdict,macrodict):
 
 
63
    """ Expand macros in token dictionary """
 
 
64
    def macro_value(m):
 
 
65
        return '(?:%s)' % macrodict[m.groupdict()['macro']]
 
 
66
    for key, value in tokdict.items():
 
 
67
        while re.search(r'{[a-z][a-z0-9-]*}', value):
 
 
68
            value = re.sub(r'{(?P<macro>[a-z][a-z0-9-]*)}',
 
 
69
                           macro_value, value)
 
 
70
        tokdict[key] = value
 
 
71
    return tokdict
 
 
72
def _compile_regexes(tokdict):
 
 
73
    """ Compile all regular expressions into callable objects """
 
 
74
    for key, value in tokdict.items():
 
 
75
        tokdict[key] = re.compile('^(?:%s)$' % value, re.I).match
 
 
76
    return tokdict
 
 
77
_compile_regexes(_expand_macros(custom_values,custom_macros))
 
 
78
 
 
 
79
class ValidationReport(object):
 
 
80
    def __init__(self, original_text=''):
 
 
81
        self.errors        = []
 
 
82
        self.original_text = original_text.split('\n') if original_text else ''
 
 
83
 
 
 
84
    def __str__(self):
 
 
85
        "only for debugging"
 
 
86
        return "Report:\n" + '\n'.join(['\t' + str(x) for x in self.errors])
 
 
87
 
 
 
88
    def append(self,error):
 
 
89
        if hasattr(error,'line'):
 
 
90
            error.offending_line = self.original_text[error.line-1]
 
 
91
        self.errors.append(error)
 
 
92
 
 
 
93
class ValidationError(Exception):
 
 
94
    def __init__(self, message, obj = None, line = None):
 
 
95
        self.message  = message
 
 
96
        if obj is not None:
 
 
97
            self.obj  = obj
 
 
98
        # self.offending_line is the text of the actual line that
 
 
99
        #  caused the problem; it's set by the ValidationReport that
 
 
100
        #  owns this ValidationError
 
 
101
 
 
 
102
        if obj is not None and line is None and hasattr(self.obj,'_linetoken'):
 
 
103
            (_type1,_type2,self.line,_char) = obj._linetoken
 
 
104
        elif line is not None:
 
 
105
            self.line = line
 
 
106
 
 
 
107
    def __cmp__(self, other):
 
 
108
        if hasattr(self,'line') and not hasattr(other,'line'):
 
 
109
            return -1
 
 
110
        elif hasattr(other,'line') and not hasattr(self,'line'):
 
 
111
            return 1
 
 
112
        else:
 
 
113
            return cmp(self.line,other.line)
 
 
114
 
 
 
115
 
 
 
116
    def __str__(self):
 
 
117
        "only for debugging"
 
 
118
        line = (("(%d)" % self.line)
 
 
119
                if hasattr(self,'line') else '')
 
 
120
        obj = str(self.obj) if hasattr(self,'obj') else ''
 
 
121
        return "ValidationError%s: %s (%s)" % (line, self.message, obj)
 
 
122
 
 
 
123
local_urls = re.compile(r'^/static/[a-z./-]+$')
 
 
124
def valid_url(prop,value,report):
 
 
125
    url = value.getStringValue()
 
 
126
    if local_urls.match(url):
 
 
127
        pass
 
 
128
    elif domain(url) in g.allowed_css_linked_domains:
 
 
129
        pass
 
 
130
    else:
 
 
131
        report.append(ValidationError(msgs['broken_url']
 
 
132
                                      % dict(brokenurl = value.cssText),
 
 
133
                                      value))
 
 
134
    #elif sanitize_url(url) != url:
 
 
135
    #    report.append(ValidationError(msgs['broken_url']
 
 
136
    #                                  % dict(brokenurl = value.cssText),
 
 
137
    #                                  value))
 
 
138
 
 
 
139
 
 
 
140
def valid_value(prop,value,report):
 
 
141
    if not (value.valid and value.wellformed):
 
 
142
        if (value.wellformed
 
 
143
            and prop.name in cssproperties.cssvalues
 
 
144
            and cssproperties.cssvalues[prop.name](prop.value)):
 
 
145
            # it's actually valid. cssutils bug.
 
 
146
            pass
 
 
147
        elif (not value.valid
 
 
148
              and value.wellformed
 
 
149
              and prop.name in custom_values
 
 
150
              and custom_values[prop.name](prop.value)):
 
 
151
            # we're allowing it via our own custom validator
 
 
152
            value.valid = True
 
 
153
 
 
 
154
            # see if this suddenly validates the entire property
 
 
155
            prop.valid = True
 
 
156
            prop.cssValue.valid = True
 
 
157
            if prop.cssValue.cssValueType == CSSValue.CSS_VALUE_LIST:
 
 
158
                for i in range(prop.cssValue.length):
 
 
159
                    if not prop.cssValue.item(i).valid:
 
 
160
                        prop.cssValue.valid = False
 
 
161
                        prop.valid = False
 
 
162
                        break
 
 
163
    elif not (prop.name in cssproperties.cssvalues or prop.name in custom_values):
 
 
164
            error = (msgs['invalid_property']
 
 
165
                     % dict(cssprop = prop.name))
 
 
166
            report.append(ValidationError(error,value))
 
 
167
        else:
 
 
168
            error = (msgs['invalid_val_for_prop']
 
 
169
                     % dict(cssvalue = value.cssText,
 
 
170
                            cssprop  = prop.name))
 
 
171
            report.append(ValidationError(error,value))
 
 
172
 
 
 
173
    if value.primitiveType == CSSPrimitiveValue.CSS_URI:
 
 
174
        valid_url(prop,value,report)
 
 
175
 
 
 
176
error_message_extract_re = re.compile('.*\\[([0-9]+):[0-9]*:.*\\]$')
 
 
177
only_whitespace          = re.compile('^\s*$')
 
 
178
def validate_css(string):
 
 
179
    p = CSSParser(raiseExceptions = True)
 
 
180
 
 
 
181
    if not string or only_whitespace.match(string):
 
 
182
        return ('',ValidationReport())
 
 
183
 
 
 
184
    report = ValidationReport(string)
 
 
185
 
 
 
186
    # avoid a very expensive parse
 
 
187
    max_size_kb = 100;
 
 
188
    if len(string) > max_size_kb * 1024:
 
 
189
        report.append(ValidationError((msgs['too_big']
 
 
190
                                       % dict (max_size = max_size_kb))))
 
 
191
        return (string, report)
 
 
192
 
 
 
193
    try:
 
 
194
        parsed = p.parseString(string)
 
 
195
    except DOMException,e:
 
 
196
        # yuck; xml.dom.DOMException can't give us line-information
 
 
197
        # directly, so we have to parse its error message string to
 
 
198
        # get it
 
 
199
        line = None
 
 
200
        line_match = error_message_extract_re.match(e.message)
 
 
201
        if line_match:
 
 
202
            line = line_match.group(1)
 
 
203
            if line:
 
 
204
                line = int(line)
 
 
205
        error_message=  (msgs['syntax_error']
 
 
206
                         % dict(syntaxerror = e.message))
 
 
207
        report.append(ValidationError(error_message,e,line))
 
 
208
        return (None,report)
 
 
209
 
 
 
210
    for rule in parsed.cssRules:
 
 
211
        if rule.type == CSSStyleRule.IMPORT_RULE:
 
 
212
            report.append(ValidationError(msgs['no_imports'],rule))
 
 
213
        elif rule.type == CSSStyleRule.COMMENT:
 
 
214
            pass
 
 
215
        elif rule.type == CSSStyleRule.STYLE_RULE:
 
 
216
            style = rule.style
 
 
217
            for prop in style.getProperties():
 
 
218
 
 
 
219
                if prop.cssValue.cssValueType == CSSValue.CSS_VALUE_LIST:
 
 
220
                    for i in range(prop.cssValue.length):
 
 
221
                        valid_value(prop,prop.cssValue.item(i),report)
 
 
222
                    if not (prop.cssValue.valid and prop.cssValue.wellformed):
 
 
223
                        report.append(ValidationError(msgs['invalid_property_list']
 
 
224
                                                      % dict(proplist = prop.cssText),
 
 
225
                                                      prop.cssValue))
 
 
226
                elif prop.cssValue.cssValueType == CSSValue.CSS_PRIMITIVE_VALUE:
 
 
227
                    valid_value(prop,prop.cssValue,report)
 
 
228
 
 
 
229
                # cssutils bug: because valid values might be marked
 
 
230
                # as invalid, we can't trust cssutils to properly
 
 
231
                # label valid properties, so we're going to rely on
 
 
232
                # the value validation (which will fail if the
 
 
233
                # property is invalid anyway). If this bug is fixed,
 
 
234
                # we should uncomment this 'if'
 
 
235
 
 
 
236
                # a property is not valid if any of its values are
 
 
237
                # invalid, or if it is itself invalid. To get the
 
 
238
                # best-quality error messages, we only report on
 
 
239
                # whether the property is valid after we've checked
 
 
240
                # the values
 
 
241
                #if not (prop.valid and prop.wellformed):
 
 
242
                #    report.append(ValidationError(_('invalid property'),prop))
 
 
243
 
 
 
244
        else:
 
 
245
            report.append(ValidationError(msgs['unknown_rule_type']
 
 
246
                                          % dict(ruletype = rule.cssText),
 
 
247
                                          rule))
 
 
248
 
 
 
249
    return parsed,report
 
 
250
 
 
 
251
def builder_wrapper(thing):
 
 
252
    if c.user.pref_compress and isinstance(thing, Link):
 
 
253
        thing.__class__ = LinkCompressed
 
 
254
        thing.score_fmt = Score.points
 
 
255
    return Wrapped(thing)
 
 
256
 
 
 
257
def find_preview_comments(sr):
 
 
258
    comments = Comment._query(Comment.c.sr_id == c.site._id,
 
 
259
                              limit=25, data=True)
 
 
260
    comments = list(comments)
 
 
261
    if not comments:
 
 
262
        comments = Comment._query(limit=25, data=True)
 
 
263
        comments = list(comments)
 
 
264
 
 
 
265
    return comments
 
 
266
 
 
 
267
def find_preview_links(sr):
 
 
268
    from r2.lib.normalized_hot import get_hot
 
 
269
 
 
 
270
    # try to find a link to use, otherwise give up and return
 
 
271
    links = get_hot(c.site)
 
 
272
    if not links:
 
 
273
        sr = Subreddit._by_name(g.default_sr)
 
 
274
        if sr:
 
 
275
            links = get_hot(sr)
 
 
276
 
 
 
277
    return links
 
 
278
 
 
 
279
def rendered_link(id, res, links, media, compress):
 
 
280
    from pylons.controllers.util import abort
 
 
281
 
 
 
282
    try:
 
 
283
        render_style    = c.render_style
 
 
284
 
 
 
285
        c.render_style = 'html'
 
 
286
 
 
 
287
        with c.user.safe_set_attr:
 
 
288
            c.user.pref_compress = compress
 
 
289
            c.user.pref_media    = media
 
 
290
 
 
 
291
            b = IDBuilder([l._fullname for l in links],
 
 
292
                          num = 1, wrap = builder_wrapper)
 
 
293
            l = LinkListing(b, nextprev=False,
 
 
294
                            show_nums=True).listing().render(style='html')
 
 
295
            res._update(id, innerHTML=l)
 
 
296
 
 
 
297
    finally:
 
 
298
        c.render_style = render_style
 
 
299
 
 
 
300
def rendered_comment(id, res, comments):
 
 
301
    try:
 
 
302
        render_style    = c.render_style
 
 
303
 
 
 
304
        c.render_style = 'html'
 
 
305
 
 
 
306
        b = IDBuilder([x._fullname for x in comments],
 
 
307
                      num = 1)
 
 
308
        l = LinkListing(b, nextprev=False,
 
 
309
                        show_nums=False).listing().render(style='html')
 
 
310
        res._update('preview_comment', innerHTML=l)
 
 
311
 
 
 
312
    finally:
 
 
313
        c.render_style = render_style
 
 
314
 
 
 
315
class BadImage(Exception): pass
 
 
316
 
 
 
317
def clean_image(data,format):
 
 
318
    import Image
 
 
319
    from StringIO import StringIO
 
 
320
 
 
 
321
    try:
 
 
322
        in_file = StringIO(data)
 
 
323
        out_file = StringIO()
 
 
324
 
 
 
325
        im = Image.open(in_file)
 
 
326
        im = im.resize(im.size)
 
 
327
 
 
 
328
        im.save(out_file,format)
 
 
329
        ret = out_file.getvalue()
 
 
330
    except IOError,e:
 
 
331
        raise BadImage(e)
 
 
332
    finally:
 
 
333
        out_file.close()
 
 
334
        in_file.close()
 
 
335
 
 
 
336
    return ret
 
 
337
 
 
 
338
def save_header_image(sr, data):
 
 
339
    import tempfile
 
 
340
    from r2.lib import s3cp
 
 
341
    from md5 import md5
 
 
342
 
 
 
343
    hash = md5(data).hexdigest()
 
 
344
 
 
 
345
    try:
 
 
346
        f = tempfile.NamedTemporaryFile(suffix = '.png')
 
 
347
        f.write(data)
 
 
348
        f.flush()
 
 
349
 
 
 
350
        resource = g.s3_thumb_bucket + sr._fullname + '.png'
 
 
351
        s3cp.send_file(f.name, resource, 'image/png', 'public-read', None, False)
 
 
352
    finally:
 
 
353
        f.close()
 
 
354
 
 
 
355
    return 'http:/%s%s.png?v=%s' % (g.s3_thumb_bucket, sr._fullname, hash)
 
 
356
 
 
 
357
 
 
 
358
 
 
 
359
 
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
35
log_format = logging.Formatter('sql: %(message)s')
35
log_format = logging.Formatter('sql: %(message)s')
36
 
36
 
37
settings = storage()
37
settings = storage()
38
settings.DEBUG = g.debug
38
settings.DEBUG = False
39
settings.DB_CREATE_TABLES = True
39
settings.DB_CREATE_TABLES = True
40
settings.DB_APP_NAME = 'reddit'
40
settings.DB_APP_NAME = 'reddit'
41
 
41
 
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
114
        #wipe malicious javascript
114
        #wipe malicious javascript
115
        text = jscript_url.sub('', text)
115
        text = jscript_url.sub('', text)
116
        def href_handler(m):
116
        def href_handler(m):
117
            return '<a href="%s"' % m.group(1).replace('&amp;', '&')
117
            x = m.group(1).replace('&amp;', '&')
 
 
118
            if c.cname:
 
 
119
                return '<a target="_top" href="%s"' % x
 
 
120
            else:
 
 
121
                return '<a href="%s"' % x
118
        def code_handler(m):
122
        def code_handler(m):
119
            l = m.group(1)
123
            l = m.group(1)
120
            return '<code>%s</code>' % l.replace('&amp;','&')
124
            return '<code>%s</code>' % l.replace('&amp;','&')
...
 
...
 
125
 
129
 
126
 
130
 
127
def keep_space(text):
131
def keep_space(text):
128
    return unsafe(websafe(text).replace(' ', '&#32;').replace('\n', '&#10;').replace('\t', '&#09;'))
132
    text = websafe(text)
 
 
133
    for i in " \n\r\t":
 
 
134
        text=text.replace(i,'&#%02d;' % ord(i))
 
 
135
    return unsafe(text)
129
 
136
 
130
 
137
 
131
def unkeep_space(text):
138
def unkeep_space(text):
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
46
class JsonResponse():
46
class JsonResponse():
47
    # handled entried in the response object
47
    # handled entried in the response object
48
    __slots__ = ['update', 'blur', 'focus', 'object', 'hide', 'show',
48
    __slots__ = ['update', 'blur', 'focus', 'object', 'hide', 'show',
49
                 'captcha', 'success']
49
                 'captcha', 'success', 'call']
50
 
50
 
51
    def __init__(self):
51
    def __init__(self):
52
        self.update = []
52
        self.update = []
...
 
...
 
59
        self.error = None
59
        self.error = None
60
        self.success = None
60
        self.success = None
61
        self.redirect = None
61
        self.redirect = None
 
 
62
        self.call = []
 
 
63
 
 
 
64
    def _call(self, fn):
 
 
65
        self.call.append(fn)
62
 
66
 
63
    def _success(self):
67
    def _success(self):
64
        self.success = 1
68
        self.success = 1
...
 
...
 
77
        self.blur = f
81
        self.blur = f
78
 
82
 
79
    def _redirect(self, red):
83
    def _redirect(self, red):
80
        self.redirect = red
84
        from pylons import c
 
 
85
        if c.cname and "?cnameframe=1" not in red:
 
 
86
            self.redirect = red + "?cnameframe=1"
 
 
87
        else:
 
 
88
            self.redirect = red
81
 
89
 
82
 
90
 
83
    def _update(self, name, **kw):
91
    def _update(self, name, **kw):
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
238
    must also have its build() method called with the current path to
238
    must also have its build() method called with the current path to
239
    set self.path.  This step is done automatically if the button is
239
    set self.path.  This step is done automatically if the button is
240
    passed to a NavMenu instance upon its construction."""
240
    passed to a NavMenu instance upon its construction."""
241
    def __init__(self, title, dest, sr_path = True, opt = '', aliases = [],
241
    def __init__(self, title, dest, sr_path = True, 
242
                 style = "plain", **kw):
242
                 nocname=False, opt = '', aliases = [],
 
 
243
                 target = "", style = "plain", **kw):
243
 
244
 
244
        # keep original dest to check against c.location when rendering
245
        # keep original dest to check against c.location when rendering
245
        self.aliases = set(a.rstrip('/') for a in aliases)
246
        self.aliases = set(a.rstrip('/') for a in aliases)
246
        self.aliases.add(dest.rstrip('/'))
247
        self.aliases.add(dest.rstrip('/'))
247
        self.dest = dest
248
        self.dest = dest
248
 
249
 
249
        Styled.__init__(self, style = style, sr_path = sr_path,
250
        Styled.__init__(self, style = style, sr_path = sr_path, 
 
 
251
                        nocname = nocname, target = target, 
250
                        title = title, opt = opt, **kw)
252
                        title = title, opt = opt, **kw)
251
 
253
 
252
    def build(self, base_path = ''):
254
    def build(self, base_path = ''):
...
 
...
 
295
    'dest' defaults to the 'name' as well (unless specified
297
    'dest' defaults to the 'name' as well (unless specified
296
    separately)."""
298
    separately)."""
297
 
299
 
298
    def __init__(self, name, sr_path = True, dest = None, **kw):
300
    def __init__(self, name, sr_path = True, nocname=False, dest = None, **kw):
299
        self.name = name.strip('/')
301
        self.name = name.strip('/')
300
        NavButton.__init__(self, menu[self.name], name if dest is None else dest,
302
        NavButton.__init__(self, menu[self.name], name if dest is None else dest,
301
                           sr_path = sr_path, **kw)
303
                           sr_path = sr_path, nocname=nocname, **kw)
302
 
304
 
303
    def selected_title(self):
305
    def selected_title(self):
304
        """Overrides selected_title to use menu_selected dictionary"""
306
        """Overrides selected_title to use menu_selected dictionary"""
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
32
from r2.lib.filters import spaceCompress, _force_unicode
32
from r2.lib.filters import spaceCompress, _force_unicode
33
from r2.lib.menus import NavButton, NamedButton, NavMenu, PageNameNav, JsButton, menu
33
from r2.lib.menus import NavButton, NamedButton, NavMenu, PageNameNav, JsButton, menu
34
from r2.lib.strings import plurals, rand_strings, strings
34
from r2.lib.strings import plurals, rand_strings, strings
35
from r2.lib.utils import title_to_url
35
from r2.lib.utils import title_to_url, query_string
 
 
36
from r2.lib.template_helpers import add_sr
36
import sys
37
import sys
37
 
38
 
38
def get_captcha():
39
def get_captcha():
...
 
...
 
119
 
120
 
120
        if self.submit_box:
121
        if self.submit_box:
121
            ps.append(SideBox(_('Submit a link'),
122
            ps.append(SideBox(_('Submit a link'),
122
                              c.site.path + 'submit', 'submit',
123
                              '/submit', 'submit',
123
                              subtitles = [_('to anything interesting: news article, blog entry, video, picture...')],
124
                              subtitles = [_('to anything interesting: news article, blog entry, video, picture...')],
124
                              show_cover = True))
125
                              show_cover = True))
125
 
126
 
126
        if self.create_reddit_box:
127
        if self.create_reddit_box:
127
            ps.append(SideBox(_('Create your own reddit'),
128
           ps.append(SideBox(_('Create your own reddit'),
128
                              '/reddits/create', 'create',
129
                              '/reddits/create', 'create',
129
                              subtitles = rand_strings.get("create_reddit", 2),
130
                              subtitles = rand_strings.get("create_reddit", 2),
130
                              show_cover = True))
131
                              show_cover = True, nocname=True))
131
        return ps
132
        return ps
132
 
133
 
133
    def render(self, *a, **kw):
134
    def render(self, *a, **kw):
...
 
...
 
160
        if c.user_is_loggedin:
161
        if c.user_is_loggedin:
161
            if c.user.name in g.admins:
162
            if c.user.name in g.admins:
162
                if c.user_is_admin:
163
                if c.user_is_admin:
163
                   buttons += [NamedButton("adminoff", False)]
164
                   buttons += [NamedButton("adminoff", False, nocname=True,
 
 
165
                                           target = "_self")]
164
                else:
166
                else:
165
                   buttons += [NamedButton("adminon",  False)]
167
                   buttons += [NamedButton("adminon",  False, nocname=True,
 
 
168
                                           target = "_self")]
166
            buttons += [NamedButton("prefs", False,
169
            buttons += [NamedButton("prefs", False,
167
                                  css_class = "pref-lang")]
170
                                  css_class = "pref-lang")]
168
        else:
171
        else:
...
 
...
 
170
            buttons += [JsButton(g.lang_name.get(lang, lang),  
173
            buttons += [JsButton(g.lang_name.get(lang, lang),  
171
                                  onclick = "return showlang();",
174
                                  onclick = "return showlang();",
172
                                  css_class = "pref-lang")]
175
                                  css_class = "pref-lang")]
173
        buttons += [NamedButton("stats", False)]
176
        buttons += [NamedButton("stats", False, nocname=True)]
174
        buttons += [NamedButton("help", False),
177
        buttons += [NamedButton("help", False, nocname=True),
175
                    NamedButton("blog", False)]                    
178
                    NamedButton("blog", False, nocname=True)]                    
176
 
179
 
177
        if c.user_is_loggedin:
180
        if c.user_is_loggedin:
178
            buttons += [NamedButton("logout", False)]
181
            buttons += [NamedButton("logout", False, nocname=True,
 
 
182
                                    target = "_self")]
179
 
183
 
180
        return NavMenu(buttons, base_path = "/", type = "flatlist")
184
        return NavMenu(buttons, base_path = "/", type = "flatlist")
181
 
185
 
...
 
...
 
185
                   NamedButton("bookmarklets", False),
189
                   NamedButton("bookmarklets", False),
186
                   NamedButton("buttons",      False),
190
                   NamedButton("buttons",      False),
187
                   NamedButton("widget",       False),
191
                   NamedButton("widget",       False),
188
                   NamedButton("code",         False),
192
                   NamedButton("code",         False, nocname=True),
189
                   NamedButton("mobile",       False),
193
                   NamedButton("mobile",       False, nocname=True),
190
                   NamedButton("store",        False),
194
                   NamedButton("store",        False, nocname=True),
191
                   NamedButton("ad_inq",       False),
195
                   NamedButton("ad_inq",       False, nocname=True),
192
                   ]
196
                   ]
193
 
197
 
194
        return NavMenu(buttons, base_path = "/", type = "flatlist")
198
        return NavMenu(buttons, base_path = "/", type = "flatlist")
...
 
...
 
217
        toolbar = [NavMenu(main_buttons, type='tabmenu')]
221
        toolbar = [NavMenu(main_buttons, type='tabmenu')]
218
        if more_buttons:
222
        if more_buttons:
219
            toolbar.append(NavMenu(more_buttons, title=menu.more, type='tabdrop'))
223
            toolbar.append(NavMenu(more_buttons, title=menu.more, type='tabdrop'))
220
        if c.site != Default:
224
        if c.site != Default and not c.cname:
221
            toolbar.insert(0, PageNameNav('subreddit'))
225
            toolbar.insert(0, PageNameNav('subreddit'))
222
 
226
 
223
        return toolbar
227
        return toolbar
...
 
...
 
259
class SideBox(Wrapped):
263
class SideBox(Wrapped):
260
    """Generic sidebox used to generate the 'submit' and 'create a reddit' boxes."""
264
    """Generic sidebox used to generate the 'submit' and 'create a reddit' boxes."""
261
    def __init__(self, title, link, css_class='', subtitles = [],
265
    def __init__(self, title, link, css_class='', subtitles = [],
262
                 show_cover = False):
266
                 show_cover = False, nocname=False):
263
        Wrapped.__init__(self, link = link, 
267
        Wrapped.__init__(self, link = link, target = '_top',
264
                         title = title, css_class = css_class,
268
                         title = title, css_class = css_class,
265
                         subtitles = subtitles, show_cover = show_cover)
269
                         subtitles = subtitles, show_cover = show_cover, nocname=nocname)
266
 
270
 
267
 
271
 
268
class PrefsPage(Reddit):
272
class PrefsPage(Reddit):
...
 
...
 
429
 
433
 
430
        toolbar = [NavMenu(buttons, base_path = "", type="tabmenu")]
434
        toolbar = [NavMenu(buttons, base_path = "", type="tabmenu")]
431
 
435
 
432
        if c.site != Default:
436
        if c.site != Default and not c.cname:
433
            toolbar.insert(0, PageNameNav('subreddit'))
437
            toolbar.insert(0, PageNameNav('subreddit'))
434
 
438
 
435
        return toolbar
439
        return toolbar
...
 
...
 
463
        Reddit.__init__(self, title = title, *a, **kw)
467
        Reddit.__init__(self, title = title, *a, **kw)
464
 
468
 
465
    def build_toolbars(self):
469
    def build_toolbars(self):
466
        return [PageNameNav('subreddit')]
470
        if not c.cname:
 
 
471
            return [PageNameNav('subreddit')]
 
 
472
        else:
 
 
473
            return []
467
 
474
 
468
 
475
 
469
 
476
 
...
 
...
 
659
    def __init__(self, site = None, name = ''):
666
    def __init__(self, site = None, name = ''):
660
        Wrapped.__init__(self, site = site, name = name)
667
        Wrapped.__init__(self, site = site, name = name)
661
 
668
 
 
 
669
class SubredditStylesheet(Wrapped):
 
 
670
    """form for editing or creating subreddit stylesheets"""
 
 
671
    def __init__(self, site = None,
 
 
672
                 stylesheet_contents = ''):
 
 
673
        Wrapped.__init__(self, site = site,
 
 
674
                         stylesheet_contents = stylesheet_contents)
 
 
675
 
 
 
676
class CssError(Wrapped):
 
 
677
    """Rendered error returned to the stylesheet editing page via ajax"""
 
 
678
    def __init__(self, error):
 
 
679
        # error is an instance of cssutils.py:ValidationError
 
 
680
        Wrapped.__init__(self, error = error)
 
 
681
 
 
 
682
class UploadedImage(Wrapped):
 
 
683
    "The page rendered in the iframe during an upload of a header image"
 
 
684
    def __init__(self,status,img_src,op):
 
 
685
        Wrapped.__init__(self,
 
 
686
                         status=status, img_src=img_src, op=op)
662
 
687
 
663
class Password(Wrapped):
688
class Password(Wrapped):
664
    """Form encountered when 'recover password' is clicked in the LoginFormWide."""
689
    """Form encountered when 'recover password' is clicked in the LoginFormWide."""
...
 
...
 
1042
        from admin_pages import Details
1067
        from admin_pages import Details
1043
        return self.content_stack(self.link_listing, Details(link = self.link))
1068
        return self.content_stack(self.link_listing, Details(link = self.link))
1044
 
1069
 
 
 
1070
class Cnameframe(Wrapped):
 
 
1071
    """The frame page."""
 
 
1072
    def __init__(self, original_path, sr_name, sr_title, sub_domain):
 
 
1073
        Wrapped.__init__(self, original_path=original_path)
 
 
1074
        self.title = "%s - %s" % (sr_title, sub_domain)
 
 
1075
        port = request.environ.get('request_port')
 
 
1076
        request.get['cnameframe'] = 1
 
 
1077
        path = original_path + query_string(request.get)
 
 
1078
        if port > 0:
 
 
1079
            self.frame_target = "http://%s:%d/r/%s%s" % (c.domain, port, sr_name, path)
 
 
1080
        else:
 
 
1081
            self.frame_target = "http://%s/r/%s%s" % (c.domain, sr_name, path)
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
93
 
93
 
94
    searching_a_reddit = _('you\'re searching within the [%(reddit_name)s](%(reddit_link)s) reddit. '+
94
    searching_a_reddit = _('you\'re searching within the [%(reddit_name)s](%(reddit_link)s) reddit. '+
95
                           'you can search within [your subscribed reddits](%(my_reddits_link)s) ' +
95
                           'you can search within [your subscribed reddits](%(my_reddits_link)s) ' +
96
                           'or [all reddits](%(all_reddits_link)s)')
96
                           'or [all reddits](%(all_reddits_link)s)'),
 
 
97
 
 
 
98
    css_validator_messages = dict(
 
 
99
        broken_url = _('"%(brokenurl)s" is not a valid URL'),
 
 
100
        invalid_property = _('"%(cssprop)s" is not a valid CSS property'),
 
 
101
        invalid_val_for_prop = _('"%(cssvalue)s" is not a valid value for CSS property "%(cssprop)s"'),
 
 
102
        too_big = _('too big. keep it under %(max_size)dkb'),
 
 
103
        syntax_error = _('syntax error: "%(syntaxerror)s"'),
 
 
104
        no_imports = _('@imports are not allowed'),
 
 
105
        invalid_property_list = _('invalid CSS property list "%(proplist)s"'),
 
 
106
        unknown_rule_type = _('unknown CSS rule type "%(ruletype)s"')
 
 
107
    )
 
 
108
    
97
)
109
)
98
 
110
 
99
class StringHandler(object):
111
class StringHandler(object):
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
53
        return file + v
53
        return file + v
54
    return os.path.join(c.site.static_path, file) + v
54
    return os.path.join(c.site.static_path, file) + v
55
 
55
 
56
 
 
 
57
def generateurl(context, path, **kw):
56
def generateurl(context, path, **kw):
58
    if kw:
57
    if kw:
59
        return path + '?' + '&'.join(["%s=%s"%(k, url_escape(v)) \
58
        return path + '?' + '&'.join(["%s=%s"%(k, url_escape(v)) \
...
 
...
 
145
        rendered_item = replace_fn(u"$ListClass", listing._js_cls)
144
        rendered_item = replace_fn(u"$ListClass", listing._js_cls)
146
 
145
 
147
        #$votehash is only present when voting arrows are present
146
        #$votehash is only present when voting arrows are present
148
        if u'$votehash' in rendered_item:
147
        if c.user_is_loggedin and u'$votehash' in rendered_item:
149
            hash = vote_hash(c.user, item, listing.vote_hash_type)
148
            hash = vote_hash(c.user, item, listing.vote_hash_type)
150
            rendered_item = replace_fn(u'$votehash', hash)
149
            rendered_item = replace_fn(u'$votehash', hash)
151
 
150
 
152
    rendered_item = replace_fn(u"$display", "" if display else "style='display:none'")
151
    rendered_item = replace_fn(u"$display", "" if display else "style='display:none'")
153
    return rendered_item
152
    return rendered_item
154
 
153
 
155
from pylons import c as cur
 
 
156
def dockletStr(context, type, browser):
154
def dockletStr(context, type, browser):
 
 
155
    domain = c.domain
 
 
156
    if c.cname:
 
 
157
        domain = c.site.domain
 
 
158
 
157
    if type == "serendipity!":
159
    if type == "serendipity!":
158
        return "http://"+cur.domain+"/random"
160
        return "http://"+domain+"/random"
159
    elif type == "reddit":
161
    elif type == "reddit":
160
        return "javascript:location.href='http://"+cur.domain+"/submit?url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(document.title)"
162
        return
"javascript:location.href='http://"+domain+"/submit?url='+encodeURIComponent(location.href)+'&title='+encodeURIComponent(
document.title)"
161
    else:
163
    else:
162
        f = "fixed"
164
        f = "fixed"
163
        if browser == "ie": f = "absolute"
165
        if browser == "ie": f = "absolute"
164
        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?u
rl='+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.bod
y.appendChild(i)};b()" % \
166
        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?u
rl='+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.bod
y.appendChild(i)};b()" % \
165
            (f, cur.domain, cur.domain, type, 
167
            (f, domain, domain, type, 
166
             c.modhash if cur.user else '')
168
             c.modhash if c.user else '')
 
 
169
 
 
 
170
 
 
 
171
 
 
 
172
def add_sr(path, sr_path = True, nocname=False):
 
 
173
    """Given a link, returns that link with the subreddit added.
 
 
174
       Also adds the domain for cname requests."""
 
 
175
    (scheme, netloc, path, params, query, fragment) = urlparse(path)
 
 
176
    if sr_path:
 
 
177
        #noslash fixes /reddits/
 
 
178
        noslash = c.site.path.rstrip('/')
 
 
179
        #if it's a relative path, don't include the sitename
 
 
180
        if (path.startswith('/') and not path.startswith(noslash)
 
 
181
            and not path.startswith('/r/')):
 
 
182
            if not c.cname:
 
 
183
                path = c.site.path + path[1:]
167
 
184
 
 
 
185
    if not netloc and c.cname and not nocname:
 
 
186
        netloc = getattr(c.site, 'domain', None)
168
 
187
 
 
 
188
    if netloc:
 
 
189
        port = request.environ.get('request_port')
 
 
190
        if port > 0:
 
 
191
            netloc = "%s:%d" % (netloc, port)
 
 
192
 
 
 
193
    if c.render_style == 'mobile' and not path.endswith('.mobile'):
 
 
194
        path += '.mobile'
169
 
195
 
170
def reddit_link(path, url = False, get = False):
196
    return urlunparse((scheme, netloc, path, params, query, fragment))
171
    if url or get:
 
 
172
        (scheme, netloc, path, params, query, fragment) = urlparse(path)
 
 
173
        if url:
 
 
174
            #noslash fixes /reddits/
 
 
175
            noslash = c.site.path.rstrip('/')
 
 
176
            #if it's a relative path, don't include the sitename
 
 
177
            if path.startswith('/') and not path.startswith(noslash):
 
 
178
                path = c.site.path + path[1:]
 
 
179
        else:
 
 
180
            newparam = "r=" + url_escape(c.site.name)
 
 
181
            if query:
 
 
182
                query += "&" + newparam
 
 
183
            else:
 
 
184
                query = newparam
 
 
185
        return urlunparse((scheme, netloc, path, params, query, fragment))
 
 
186
    return path
 
 
187
 
197
 
188
def join_urls(*urls):
198
def join_urls(*urls):
189
    """joins a series of urls together without doubles slashes"""
199
    """joins a series of urls together without doubles slashes"""
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
11
    which means a 304 should be returned. Otherwise returns the date
11
    which means a 304 should be returned. Otherwise returns the date
12
    that should be sent as the last-modified header."""
12
    that should be sent as the last-modified header."""
13
    from pylons import g
13
    from pylons import g
14
    
14
 
15
    prop = 'last_' + action
15
    prop = 'last_' + action
16
    if not hasattr(thing, prop):
16
    if not hasattr(thing, prop):
17
        last_modified = make_last_modified()
17
        last_modified = make_last_modified()
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
51
                     pref_over_18 = False,
51
                     pref_over_18 = False,
52
                     pref_compress = False,
52
                     pref_compress = False,
53
                     pref_organic = True,
53
                     pref_organic = True,
 
 
54
                     pref_show_stylesheets = True,
54
                     reported = 0,
55
                     reported = 0,
55
                     report_made = 0,
56
                     report_made = 0,
56
                     report_correct = 0,
57
                     report_correct = 0,
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
79
 
79
 
80
    @property
80
    @property
81
    def already_submitted_link(self):
81
    def already_submitted_link(self):
82
        return self.permalink + '?already_submitted=true'
82
        return self.make_permalink_slow() + '?already_submitted=true'
83
 
83
 
84
    def resubmit_link(self, sr_url = False):
84
    def resubmit_link(self, sr_url = False):
85
        submit_url  = self.subreddit_slow.path if sr_url else '/'
85
        submit_url  = self.subreddit_slow.path if sr_url else '/'
...
 
...
 
193
                              c.user.pref_newwindow,
193
                              c.user.pref_newwindow,
194
                              c.user.pref_frame,
194
                              c.user.pref_frame,
195
                              c.user.pref_compress,
195
                              c.user.pref_compress,
 
 
196
                              c.user.pref_media,
196
                              request.host,
197
                              request.host,
 
 
198
                              c.cname, 
197
                              wrapped.author == c.user,
199
                              wrapped.author == c.user,
198
                              wrapped.likes,
200
                              wrapped.likes,
199
                              wrapped.saved,
201
                              wrapped.saved,
...
 
...
 
208
        s = ''.join(s)
210
        s = ''.join(s)
209
        return s
211
        return s
210
 
212
 
211
    @property
213
    def make_permalink(self, sr):
212
    def permalink(self):
214
        p = "comments/%s/%s/" % (self._id36, title_to_url(self.title))
213
        return "/comments/%s/%s/" % (self._id36, title_to_url(self.title))
215
        if not c.cname:
 
 
216
            res = "/r/%s/%s" % (sr.name, p)
 
 
217
        elif sr != c.site:
 
 
218
            res = "http://%s/r/%s/%s" % (g.domain, sr.name, p)
 
 
219
        else:
 
 
220
            res = "/%s" % p
 
 
221
        return res
214
 
222
 
 
 
223
    def make_permalink_slow(self):
 
 
224
        return self.make_permalink(self.subreddit_slow)
 
 
225
 
215
    @classmethod
226
    @classmethod
216
    def add_props(cls, user, wrapped):
227
    def add_props(cls, user, wrapped):
217
        from r2.lib.count import incr_counts
228
        from r2.lib.count import incr_counts
...
 
...
 
247
            item.clicked = bool(clicked.get((user, item, 'click')))
258
            item.clicked = bool(clicked.get((user, item, 'click')))
248
            item.num = None
259
            item.num = None
249
            item.score_fmt = Score.number_only
260
            item.score_fmt = Score.number_only
 
 
261
            item.permalink = item.make_permalink(item.subreddit)
250
 
262
 
251
        if c.user_is_loggedin:
263
        if c.user_is_loggedin:
252
            incr_counts(wrapped)
264
            incr_counts(wrapped)
...
 
...
 
333
                              bool(c.user_is_loggedin),
345
                              bool(c.user_is_loggedin),
334
                              c.focal_comment == self._id36,
346
                              c.focal_comment == self._id36,
335
                              request.host,
347
                              request.host,
 
 
348
                              c.cname, 
336
                              wrapped.author == c.user,
349
                              wrapped.author == c.user,
337
                              wrapped.likes,
350
                              wrapped.likes,
338
                              wrapped.friend,
351
                              wrapped.friend,
...
 
...
 
347
        s = ''.join(s)
360
        s = ''.join(s)
348
        return s
361
        return s
349
 
362
 
350
    @property
363
    def make_permalink(self, link, sr=None):
351
    def permalink(self):
364
        return link.make_permalink(sr) + self._id36
352
        if not self._loaded:
 
 
353
            self._load()
 
 
354
 
 
 
355
        try:
 
 
356
            l = Link._byID(self.link_id, True)
 
 
357
            return l.permalink + self._id36
 
 
358
        except NotFound:
 
 
359
            return ""
 
 
360
 
 
 
361
 
365
 
 
 
366
    def make_permalink_slow(self):
 
 
367
        l = Link._byID(self.link_id, data=True)
 
 
368
        return self.make_permalink(l, l.subreddit_slow)
 
 
369
 
362
    @classmethod
370
    @classmethod
363
    def add_props(cls, user, wrapped):
371
    def add_props(cls, user, wrapped):
364
        #fetch parent links
372
        #fetch parent links
365
        links = Link._byID(set(l.link_id for l in wrapped), True)
373
        links = Link._byID(set(l.link_id for l in wrapped), True)
 
 
374
 
366
 
375
 
367
        #get srs for comments that don't have them (old comments)
376
        #get srs for comments that don't have them (old comments)
368
        for cm in wrapped:
377
        for cm in wrapped:
...
 
...
 
378
        cids = dict((w._id, w) for w in wrapped)
387
        cids = dict((w._id, w) for w in wrapped)
379
 
388
 
380
        for item in wrapped:
389
        for item in wrapped:
 
 
390
            item.link = links.get(item.link_id)
 
 
391
            if not hasattr(item, 'subreddit'):
 
 
392
                item.subreddit = item.subreddit_slow
381
            if hasattr(item, 'parent_id'):
393
            if hasattr(item, 'parent_id'):
382
                if cids.has_key(item.parent_id):
394
                if cids.has_key(item.parent_id):
383
                    item.parent_permalink = '#' + utils.to36(item.parent_id)
395
                    item.parent_permalink = '#' + utils.to36(item.parent_id)
384
                else:
396
                else:
385
                    parent = Comment._byID(item.parent_id)
397
                    parent = Comment._byID(item.parent_id)
386
                    item.parent_permalink = parent.permalink
398
                    item.parent_permalink = parent.make_permalink(item.link, item.subreddit)
387
            else:
399
            else:
388
                item.parent_permalink = None
400
                item.parent_permalink = None
389
 
401
 
390
            item.can_reply = (item.sr_id in can_reply_srs)
402
            item.can_reply = (item.sr_id in can_reply_srs)
391
 
403
 
392
            if not hasattr(item, 'subreddit'):
 
 
393
                item.subreddit = item.subreddit_slow
 
 
394
 
404
 
395
            # not deleted on profile pages,
405
            # not deleted on profile pages,
396
            # deleted if spam and not author or admin
406
            # deleted if spam and not author or admin
...
 
...
 
406
                                  item.deleted or
416
                                  item.deleted or
407
                                  c.user_is_admin))
417
                                  c.user_is_admin))
408
 
418
 
409
            item.link = links.get(item.link_id)
 
 
410
            if not hasattr(item,'editted'):
419
            if not hasattr(item,'editted'):
411
                item.editted = False
420
                item.editted = False
412
            #will get updated in builder
421
            #will get updated in builder
413
            item.num_children = 0
422
            item.num_children = 0
414
            item.score_fmt = Score.points
423
            item.score_fmt = Score.points
 
 
424
            item.permalink = item.make_permalink(item.link, item.subreddit)
415
 
425
 
416
class MoreComments(object):
426
class MoreComments(object):
417
    show_spam = False
427
    show_spam = False
...
 
...
 
432
        if parent:
442
        if parent:
433
            self.parent_id = parent._id
443
            self.parent_id = parent._id
434
            self.parent_name = parent._fullname
444
            self.parent_name = parent._fullname
435
            self.parent_permalink = parent.permalink
445
            self.parent_permalink = parent.make_permalink(link)
436
        self.link_name = link._fullname
446
        self.link_name = link._fullname
437
        self.link_id = link._id
447
        self.link_id = link._id
438
        self.depth = depth
448
        self.depth = depth
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
21
################################################################################
21
################################################################################
22
from r2.models import *
22
from r2.models import *
23
 
23
 
24
def populate():
24
def populate(sr_name = 'reddit.com', start_account = 1, sr_title = "reddit.com: what's new online"):
25
    sr = Subreddit._new(name= 'reddit.com', title = "reddit.com: what's new online")
25
    sr = Subreddit._new(name= sr_name, title = sr_title)
26
    sr._commit()
26
    sr._commit()
27
    for i in range(1,5):
27
    for i in range(start_account,(start_account + 4)):
28
        name = 'test' + str(i)
28
        name = 'test' + str(i)
29
        password = name
29
        password = name
30
        user = register(name, password)
30
        user = register(name, password)
File was changed - ok, show the diff
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
39
</div>
39
</div>
40
 
40
 
41
<div id="adlink">
41
<div id="adlink">
42
<a target="_parent" href="#" onclick="rwt(this)">reddit this ad</a>
42
<a target="_top" href="#" onclick="rwt(this)">reddit this ad</a>
43
</div>
43
</div>
44
 
44
 
45
</body>
45
</body>
File was changed - ok, show the diff
File was changed - ok, show the diff
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
46
 
46
 
47
  var logged = ${c.user_is_loggedin and ("'%s'" % c.user.name) or "false"};
47
  var logged = ${c.user_is_loggedin and ("'%s'" % c.user.name) or "false"};
48
  var post_site = "${c.site.name}";
48
  var post_site = "${c.site.name}";
 
 
49
  var cnameframe  = ${'true' if c.cname else 'false'}; 
49
  var modhash = ${"'%s'" % c.modhash or "false"};
50
  var modhash = ${"'%s'" % c.modhash or "false"};
 
 
51
 
 
 
52
 
50
</script> 
53
</script> 
51
 
54
 
52
${self.javascript()}
55
${self.javascript()}
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
1
## "The contents of this file are subject to the Common Public Attribution
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
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
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
4
## http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
1a107601fa490ca69215facceebe3d0dc5e7f1f86bcef0037bdb2d139a15065c3f31e872bae79df3
17
## the Original Code is CondeNet, Inc.
17
## the Original Code is CondeNet, Inc.
18
## 
18
## 
19
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
19
## All portions of the code written by CondeNet are Copyright (c) 2006-2008
20
## CondeNet, Inc. All Rights Reserved.
20
## CondeNet, Inc. All Rights Reserved."
21
################################################################################
21
################################################################################
22
 
22
 
23
<%namespace file="utils.html" import="plain_link, text_with_links, img_link, separator"/>
23
<%namespace file="utils.html" import="plain_link, text_with_links, img_link, separator"/>
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff
File was changed - ok, show the diff