Mercurial > p > roundup > code
comparison roundup/cgi/client.py @ 6638:e1588ae185dc issue2550923_computed_property
merge from default branch. Fix travis.ci so CI builds don't error out
| author | John Rouillard <rouilj@ieee.org> |
|---|---|
| date | Thu, 21 Apr 2022 16:54:17 -0400 |
| parents | 91ab3e0ffcd0 |
| children | 33616bc80baf |
comparison
equal
deleted
inserted
replaced
| 6508:85db90cc1705 | 6638:e1588ae185dc |
|---|---|
| 29 from roundup.cgi import templating, cgitb, TranslationService | 29 from roundup.cgi import templating, cgitb, TranslationService |
| 30 from roundup.cgi import actions | 30 from roundup.cgi import actions |
| 31 from roundup.exceptions import LoginError, Reject, RejectRaw, \ | 31 from roundup.exceptions import LoginError, Reject, RejectRaw, \ |
| 32 Unauthorised, UsageError | 32 Unauthorised, UsageError |
| 33 from roundup.cgi.exceptions import ( | 33 from roundup.cgi.exceptions import ( |
| 34 FormError, NotFound, NotModified, Redirect, SendFile, SendStaticFile, | 34 FormError, IndexerQueryError, NotFound, NotModified, Redirect, |
| 35 DetectorError, SeriousError) | 35 SendFile, SendStaticFile, DetectorError, SeriousError) |
| 36 from roundup.cgi.form_parser import FormParser | 36 from roundup.cgi.form_parser import FormParser |
| 37 from roundup.mailer import Mailer, MessageSendError | 37 from roundup.mailer import Mailer, MessageSendError |
| 38 from roundup.cgi import accept_language | 38 from roundup.cgi import accept_language |
| 39 from roundup import xmlrpc | 39 from roundup import xmlrpc |
| 40 from roundup import rest | 40 from roundup import rest |
| 338 | 338 |
| 339 # Cache_Control[key] = Cache-Control header value | 339 # Cache_Control[key] = Cache-Control header value |
| 340 # Key can be explicitly file basename - value applied to just that file | 340 # Key can be explicitly file basename - value applied to just that file |
| 341 # takes precedence over mime type. | 341 # takes precedence over mime type. |
| 342 # Key can be mime type - all files of that mimetype will get the value | 342 # Key can be mime type - all files of that mimetype will get the value |
| 343 Cache_Control = {} | 343 Cache_Control = { |
| 344 'application/javascript': "public, max-age=1209600", # 2 weeks | |
| 345 'text/css': "public, max-age=4838400", # 8 weeks/2 months | |
| 346 } | |
| 344 | 347 |
| 345 # list of valid http compression (Content-Encoding) algorithms | 348 # list of valid http compression (Content-Encoding) algorithms |
| 346 # we have available | 349 # we have available |
| 347 compressors = [] | 350 compressors = [] |
| 348 try: | 351 try: |
| 643 output = handler.dispatch(self.env['REQUEST_METHOD'], | 646 output = handler.dispatch(self.env['REQUEST_METHOD'], |
| 644 self.path, self.form) | 647 self.path, self.form) |
| 645 | 648 |
| 646 # type header set by rest handler | 649 # type header set by rest handler |
| 647 # self.setHeader("Content-Type", "text/xml") | 650 # self.setHeader("Content-Type", "text/xml") |
| 648 self.setHeader("Content-Length", str(len(output))) | 651 if self.response_code == 204: # no body with 204 |
| 649 self.write(output) | 652 self.write("") |
| 653 else: | |
| 654 self.setHeader("Content-Length", str(len(output))) | |
| 655 self.write(output) | |
| 650 | 656 |
| 651 def add_ok_message(self, msg, escape=True): | 657 def add_ok_message(self, msg, escape=True): |
| 652 add_message(self._ok_message, msg, escape) | 658 add_message(self._ok_message, msg, escape) |
| 653 | 659 |
| 654 def add_error_message(self, msg, escape=True): | 660 def add_error_message(self, msg, escape=True): |
| 846 self.send_error_to_admin(e.subject, e.html, e.txt) | 852 self.send_error_to_admin(e.subject, e.html, e.txt) |
| 847 self.write_html(e.html) | 853 self.write_html(e.html) |
| 848 else: | 854 else: |
| 849 # in debug mode, only write error to screen. | 855 # in debug mode, only write error to screen. |
| 850 self.write_html(e.html) | 856 self.write_html(e.html) |
| 851 except: | 857 except Exception as e: |
| 852 # Something has gone badly wrong. Therefore, we should | 858 # Something has gone badly wrong. Therefore, we should |
| 853 # make sure that the response code indicates failure. | 859 # make sure that the response code indicates failure. |
| 854 if self.response_code == http_.client.OK: | 860 if self.response_code == http_.client.OK: |
| 855 self.response_code = http_.client.INTERNAL_SERVER_ERROR | 861 self.response_code = http_.client.INTERNAL_SERVER_ERROR |
| 856 # Help the administrator work out what went wrong. | 862 # Help the administrator work out what went wrong. |
| 1773 def _serve_file(self, lmt, mime_type, content=None, filename=None): | 1779 def _serve_file(self, lmt, mime_type, content=None, filename=None): |
| 1774 """ guts of serve_file() and serve_static_file() | 1780 """ guts of serve_file() and serve_static_file() |
| 1775 """ | 1781 """ |
| 1776 | 1782 |
| 1777 # spit out headers | 1783 # spit out headers |
| 1778 self.additional_headers['Content-Type'] = mime_type | |
| 1779 self.additional_headers['Last-Modified'] = email.utils.formatdate(lmt) | 1784 self.additional_headers['Last-Modified'] = email.utils.formatdate(lmt) |
| 1780 | 1785 |
| 1781 ims = None | 1786 ims = None |
| 1782 # see if there's an if-modified-since... | 1787 # see if there's an if-modified-since... |
| 1783 # used if this is run behind a non-caching http proxy | 1788 # used if this is run behind a non-caching http proxy |
| 1794 # set vary header as though we were returning 200 | 1799 # set vary header as though we were returning 200 |
| 1795 # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary | 1800 # https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary |
| 1796 self.setVary("Accept-Encoding") | 1801 self.setVary("Accept-Encoding") |
| 1797 raise NotModified | 1802 raise NotModified |
| 1798 | 1803 |
| 1804 # don't set until we are sure we are sending a response body. | |
| 1805 self.additional_headers['Content-Type'] = mime_type | |
| 1806 | |
| 1799 if filename: | 1807 if filename: |
| 1800 self.write_file(filename) | 1808 self.write_file(filename) |
| 1801 else: | 1809 else: |
| 1802 self.additional_headers['Content-Length'] = str(len(content)) | 1810 self.additional_headers['Content-Length'] = str(len(content)) |
| 1803 self.write(content) | 1811 self.write(content) |
| 1913 'ok_message': self._ok_message, | 1921 'ok_message': self._ok_message, |
| 1914 'error_message': self._error_message | 1922 'error_message': self._error_message |
| 1915 } | 1923 } |
| 1916 pt = self.instance.templates.load(tplname) | 1924 pt = self.instance.templates.load(tplname) |
| 1917 # let the template render figure stuff out | 1925 # let the template render figure stuff out |
| 1918 result = pt.render(self, None, None, **args) | 1926 try: |
| 1927 result = pt.render(self, None, None, **args) | |
| 1928 except IndexerQueryError as e: | |
| 1929 result = self.renderError(e.args[0]) | |
| 1930 | |
| 1919 self.additional_headers['Content-Type'] = pt.content_type | 1931 self.additional_headers['Content-Type'] = pt.content_type |
| 1920 if self.env.get('CGI_SHOW_TIMING', ''): | 1932 if self.env.get('CGI_SHOW_TIMING', ''): |
| 1921 if self.env['CGI_SHOW_TIMING'].upper() == 'COMMENT': | 1933 if self.env['CGI_SHOW_TIMING'].upper() == 'COMMENT': |
| 1922 timings = {'starttag': '<!-- ', 'endtag': ' -->'} | 1934 timings = {'starttag': '<!-- ', 'endtag': ' -->'} |
| 1923 else: | 1935 else: |
| 1960 if sys.version_info[0] > 2: | 1972 if sys.version_info[0] > 2: |
| 1961 raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) | 1973 raise exc_info[0](exc_info[1]).with_traceback(exc_info[2]) |
| 1962 else: | 1974 else: |
| 1963 exec('raise exc_info[0], exc_info[1], exc_info[2]') # nosec | 1975 exec('raise exc_info[0], exc_info[1], exc_info[2]') # nosec |
| 1964 | 1976 |
| 1977 def renderError(self, error, response_code=400, use_template=True): | |
| 1978 self.response_code = response_code | |
| 1979 | |
| 1980 # see if error message already logged add if not | |
| 1981 if error not in self._error_message: | |
| 1982 self.add_error_message(error, escape=True) | |
| 1983 | |
| 1984 # allow use of template for a specific code | |
| 1985 trial_templates = [] | |
| 1986 if use_template: | |
| 1987 if response_code == 400: | |
| 1988 trial_templates = [ "400" ] | |
| 1989 else: | |
| 1990 trial_templates = [ str(response_code), "400" ] | |
| 1991 | |
| 1992 tplname = None | |
| 1993 for rcode in trial_templates: | |
| 1994 try: | |
| 1995 tplname = self.selectTemplate(self.classname, rcode) | |
| 1996 break | |
| 1997 except templating.NoTemplate: | |
| 1998 pass | |
| 1999 | |
| 2000 if not tplname: | |
| 2001 # call string of serious error to get basic html | |
| 2002 # response. | |
| 2003 return str(SeriousError(error)) | |
| 2004 | |
| 2005 args = { | |
| 2006 'ok_message': self._ok_message, | |
| 2007 'error_message': self._error_message | |
| 2008 } | |
| 2009 | |
| 2010 try: | |
| 2011 pt = self.instance.templates.load(tplname) | |
| 2012 return pt.render(self, None, None, **args) | |
| 2013 except Exception: | |
| 2014 # report original error | |
| 2015 return str(SeriousError(error)) | |
| 2016 | |
| 1965 # these are the actions that are available | 2017 # these are the actions that are available |
| 1966 actions = ( | 2018 actions = ( |
| 1967 ('edit', actions.EditItemAction), | 2019 ('edit', actions.EditItemAction), |
| 1968 ('editcsv', actions.EditCSVAction), | 2020 ('editcsv', actions.EditCSVAction), |
| 1969 ('new', actions.NewItemAction), | 2021 ('new', actions.NewItemAction), |
| 2161 # we changed the data, change existing content-length header | 2213 # we changed the data, change existing content-length header |
| 2162 # and add Content-Encoding and Vary header. | 2214 # and add Content-Encoding and Vary header. |
| 2163 self.additional_headers['Content-Length'] = str(len(new_content)) | 2215 self.additional_headers['Content-Length'] = str(len(new_content)) |
| 2164 self.additional_headers['Content-Encoding'] = encoder | 2216 self.additional_headers['Content-Encoding'] = encoder |
| 2165 self.setVary('Accept-Encoding') | 2217 self.setVary('Accept-Encoding') |
| 2218 try: | |
| 2219 current_etag = self.additional_headers['ETag'] | |
| 2220 except KeyError: | |
| 2221 pass # etag not set for non-rest endpoints | |
| 2222 else: | |
| 2223 etag_end = current_etag.rindex('"') | |
| 2224 self.additional_headers['ETag'] = ( current_etag[:etag_end] + | |
| 2225 '-' + encoder + current_etag[etag_end:]) | |
| 2166 | 2226 |
| 2167 return new_content | 2227 return new_content |
| 2168 | 2228 |
| 2169 def write(self, content): | 2229 def write(self, content): |
| 2170 if not self.headers_done and self.env['REQUEST_METHOD'] != 'HEAD': | 2230 if not self.headers_done and self.env['REQUEST_METHOD'] != 'HEAD': |
| 2194 if not self.headers_done: | 2254 if not self.headers_done: |
| 2195 # at this point, we are sure about Content-Type | 2255 # at this point, we are sure about Content-Type |
| 2196 if 'Content-Type' not in self.additional_headers: | 2256 if 'Content-Type' not in self.additional_headers: |
| 2197 self.additional_headers['Content-Type'] = \ | 2257 self.additional_headers['Content-Type'] = \ |
| 2198 'text/html; charset=%s' % self.charset | 2258 'text/html; charset=%s' % self.charset |
| 2259 if 'Content-Length' not in self.additional_headers: | |
| 2260 self.additional_headers['Content-Length'] = str(len(content)) | |
| 2199 self.header() | 2261 self.header() |
| 2200 | 2262 |
| 2201 if self.env['REQUEST_METHOD'] == 'HEAD': | 2263 if self.env['REQUEST_METHOD'] == 'HEAD': |
| 2202 # client doesn't care about content | 2264 # client doesn't care about content |
| 2203 return | 2265 return |
| 2477 finally: | 2539 finally: |
| 2478 f.close() | 2540 f.close() |
| 2479 self.write(content) | 2541 self.write(content) |
| 2480 | 2542 |
| 2481 def setHeader(self, header, value): | 2543 def setHeader(self, header, value): |
| 2482 """Override a header to be returned to the user's browser. | 2544 """Override or delete a header to be returned to the user's browser. |
| 2483 """ | 2545 """ |
| 2484 self.additional_headers[header] = value | 2546 if value is None: |
| 2547 try: | |
| 2548 del(self.additional_headers[header]) | |
| 2549 except KeyError: | |
| 2550 pass | |
| 2551 else: | |
| 2552 self.additional_headers[header] = value | |
| 2485 | 2553 |
| 2486 def header(self, headers=None, response=None): | 2554 def header(self, headers=None, response=None): |
| 2487 """Put up the appropriate header. | 2555 """Put up the appropriate header. |
| 2488 """ | 2556 """ |
| 2489 if headers is None: | 2557 if headers is None: |
| 2494 # update with additional info | 2562 # update with additional info |
| 2495 headers.update(self.additional_headers) | 2563 headers.update(self.additional_headers) |
| 2496 | 2564 |
| 2497 if headers.get('Content-Type', 'text/html') == 'text/html': | 2565 if headers.get('Content-Type', 'text/html') == 'text/html': |
| 2498 headers['Content-Type'] = 'text/html; charset=utf-8' | 2566 headers['Content-Type'] = 'text/html; charset=utf-8' |
| 2567 | |
| 2568 if response in [ 204, 304]: # has no body so no content-type | |
| 2569 del(headers['Content-Type']) | |
| 2499 | 2570 |
| 2500 headers = list(headers.items()) | 2571 headers = list(headers.items()) |
| 2501 | 2572 |
| 2502 for ((path, name), (value, expire)) in self._cookies.items(): | 2573 for ((path, name), (value, expire)) in self._cookies.items(): |
| 2503 cookie = "%s=%s; Path=%s;"%(name, value, path) | 2574 cookie = "%s=%s; Path=%s;"%(name, value, path) |
