PageRenderTime 28ms CodeModel.GetById 15ms app.highlight 10ms RepoModel.GetById 1ms app.codeStats 0ms

/r2/r2/controllers/error.py

https://github.com/stevewilber/reddit
Python | 223 lines | 186 code | 6 blank | 31 comment | 7 complexity | e27c893d04017ca42f969cc071d0e558 MD5 | raw file
  1# The contents of this file are subject to the Common Public Attribution
  2# License Version 1.0. (the "License"); you may not use this file except in
  3# compliance with the License. You may obtain a copy of the License at
  4# http://code.reddit.com/LICENSE. The License is based on the Mozilla Public
  5# License Version 1.1, but Sections 14 and 15 have been added to cover use of
  6# software over a computer network and provide for limited attribution for the
  7# Original Developer. In addition, Exhibit A has been modified to be consistent
  8# with Exhibit B.
  9#
 10# Software distributed under the License is distributed on an "AS IS" basis,
 11# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 12# the specific language governing rights and limitations under the License.
 13#
 14# The Original Code is reddit.
 15#
 16# The Original Developer is the Initial Developer.  The Initial Developer of
 17# the Original Code is reddit Inc.
 18#
 19# All portions of the code written by reddit are Copyright (c) 2006-2012 reddit
 20# Inc. All Rights Reserved.
 21###############################################################################
 22
 23import os.path
 24
 25import pylons
 26import paste.fileapp
 27from paste.httpexceptions import HTTPFound, HTTPMovedPermanently
 28from pylons.middleware import error_document_template, media_path
 29from pylons import c, request, g
 30from pylons.i18n import _
 31import random as rand
 32from r2.controllers.errors import ErrorSet
 33from r2.lib.filters import safemarkdown, unsafe
 34
 35import json
 36
 37try:
 38    # place all r2 specific imports in here.  If there is a code error, it'll get caught and
 39    # the stack trace won't be presented to the user in production
 40    from reddit_base import RedditController, Cookies
 41    from r2.models.subreddit import DefaultSR, Subreddit
 42    from r2.models.link import Link
 43    from r2.lib import pages
 44    from r2.lib.strings import strings, rand_strings
 45    from r2.lib.template_helpers import static
 46except Exception, e:
 47    if g.debug:
 48        # if debug mode, let the error filter up to pylons to be handled
 49        raise e
 50    else:
 51        # production environment: protect the code integrity!
 52        print "HuffmanEncodingError: make sure your python compiles before deploying, stupid!"
 53        # kill this app
 54        import os
 55        os._exit(1)
 56
 57NUM_FAILIENS = 3
 58
 59redditbroke =  \
 60'''<html>
 61  <head>
 62    <title>reddit broke!</title>
 63  </head>
 64  <body>
 65    <div style="margin: auto; text-align: center">
 66      <p>
 67        <a href="/">
 68          <img border="0" src="%s" alt="you broke reddit" />
 69        </a>
 70      </p>
 71      <p>
 72        %s
 73      </p>
 74  </body>
 75</html>
 76'''
 77
 78class ErrorController(RedditController):
 79    """Generates error documents as and when they are required.
 80
 81    The ErrorDocuments middleware forwards to ErrorController when error
 82    related status codes are returned from the application.
 83
 84    This behaviour can be altered by changing the parameters to the
 85    ErrorDocuments middleware in your config/middleware.py file.
 86    """
 87    allowed_render_styles = ('html', 'xml', 'js', 'embed', '', "compact", 'api')
 88    # List of admins to blame (skip the first admin, "reddit")
 89    # If list is empty, just blame "an admin"
 90    admins = g.admins[1:] or ["an admin"]
 91    def __before__(self):
 92        try:
 93            c.error_page = True
 94            RedditController.__before__(self)
 95        except (HTTPMovedPermanently, HTTPFound):
 96            # ignore an attempt to redirect from an error page
 97            pass
 98        except:
 99            handle_awful_failure("Error occurred in ErrorController.__before__")
100
101    def __after__(self): 
102        try:
103            RedditController.__after__(self)
104        except:
105            handle_awful_failure("Error occurred in ErrorController.__after__")
106
107    def __call__(self, environ, start_response):
108        try:
109            return RedditController.__call__(self, environ, start_response)
110        except:
111            return handle_awful_failure("something really awful just happened.")
112
113
114    def send403(self):
115        c.response.status_code = 403
116        c.site = DefaultSR()
117        if 'usable_error_content' in request.environ:
118            return request.environ['usable_error_content']
119        else:
120            res = pages.RedditError(
121                title=_("forbidden (%(domain)s)") % dict(domain=g.domain),
122                message=_("you are not allowed to do that"),
123                explanation=request.GET.get('explanation'))
124            return res.render()
125
126    def send404(self):
127        c.response.status_code = 404
128        if 'usable_error_content' in request.environ:
129            return request.environ['usable_error_content']
130        return pages.RedditError(_("page not found"),
131                                 _("the page you requested does not exist")).render()
132
133    def send429(self):
134        c.response.status_code = 429
135
136        if 'retry_after' in request.environ:
137            c.response.headers['Retry-After'] = str(request.environ['retry_after'])
138            template_name = '/ratelimit_toofast.html'
139        else:
140            template_name = '/ratelimit_throttled.html'
141
142        loader = pylons.buffet.engines['mako']['engine']
143        template = loader.load_template(template_name)
144        return template.render(logo_url=static(g.default_header_url))
145
146    def send503(self):
147        c.response.status_code = 503
148        c.response.headers['Retry-After'] = request.environ['retry_after']
149        return request.environ['usable_error_content']
150
151    def GET_document(self):
152        try:
153            c.errors = c.errors or ErrorSet()
154            # clear cookies the old fashioned way 
155            c.cookies = Cookies()
156
157            code =  request.GET.get('code', '')
158            try:
159                code = int(code)
160            except ValueError:
161                code = 404
162            srname = request.GET.get('srname', '')
163            takedown = request.GET.get('takedown', "")
164            
165            if srname:
166                c.site = Subreddit._by_name(srname)
167            if c.render_style not in self.allowed_render_styles:
168                if code not in (204, 304):
169                     c.response.content = str(code)
170                c.response.status_code = code
171                return c.response
172            elif c.render_style == "api":
173                data = request.environ.get('extra_error_data', {'error': code})
174                c.response.content = json.dumps(data)
175                return c.response
176            elif takedown and code == 404:
177                link = Link._by_fullname(takedown)
178                return pages.TakedownPage(link).render()
179            elif code == 403:
180                return self.send403()
181            elif code == 429:
182                return self.send429()
183            elif code == 500:
184                randmin = {'admin': rand.choice(self.admins)}
185                failien_name = 'youbrokeit%d.png' % rand.randint(1, NUM_FAILIENS)
186                failien_url = static(failien_name)
187                return redditbroke % (failien_url, rand_strings.sadmessages % randmin)
188            elif code == 503:
189                return self.send503()
190            elif code == 304:
191                if request.GET.has_key('x-sup-id'):
192                    x_sup_id = request.GET.get('x-sup-id')
193                    if '\r\n' not in x_sup_id:
194                        c.response.headers['x-sup-id'] = x_sup_id
195                return c.response
196            elif c.site:
197                return self.send404()
198            else:
199                return "page not found"
200        except:
201            return handle_awful_failure("something really bad just happened.")
202
203    POST_document = GET_document
204
205def handle_awful_failure(fail_text):
206    """
207    Makes sure that no errors generated in the error handler percolate
208    up to the user unless debug is enabled.
209    """
210    if g.debug:
211        import sys
212        s = sys.exc_info()
213        # reraise the original error with the original stack trace
214        raise s[1], None, s[2]
215    try:
216        # log the traceback, and flag the "path" as the error location
217        import traceback
218        g.log.error("FULLPATH: %s" % fail_text)
219        g.log.error(traceback.format_exc())
220        return redditbroke % (rand.randint(1,NUM_FAILIENS), fail_text)
221    except:
222        # we are doomed.  Admit defeat
223        return "This is an error that should never occur.  You win."