Home | History | Annotate | Download | only in server2
      1 #!/usr/bin/env python
      2 # Copyright 2013 The Chromium Authors. All rights reserved.
      3 # Use of this source code is governed by a BSD-style license that can be
      4 # found in the LICENSE file.
      5 
      6 from HTMLParser import HTMLParser
      7 import unittest
      8 
      9 from fake_fetchers import ConfigureFakeFetchers
     10 from github_file_system_provider import GithubFileSystemProvider
     11 from host_file_system_provider import HostFileSystemProvider
     12 from patch_servlet import PatchServlet
     13 from render_servlet import RenderServlet
     14 from server_instance import ServerInstance
     15 from servlet import Request
     16 from test_branch_utility import TestBranchUtility
     17 from test_util import DisableLogging
     18 
     19 
     20 
     21 _ALLOWED_HOST = 'https://chrome-apps-doc.appspot.com'
     22 
     23 
     24 def _CheckURLsArePatched(content, patch_servlet_path):
     25   errors = []
     26   class LinkChecker(HTMLParser):
     27     def handle_starttag(self, tag, attrs):
     28       if tag != 'a':
     29         return
     30       tag_description = '<a %s .../>' % ' '.join('%s="%s"' % (key, val)
     31                                                  for key, val in attrs)
     32       attrs = dict(attrs)
     33       if ('href' in attrs and
     34            attrs['href'].startswith('/') and
     35            not attrs['href'].startswith('/%s/' % patch_servlet_path)):
     36         errors.append('%s has an unqualified href' % tag_description)
     37   LinkChecker().feed(content)
     38   return errors
     39 
     40 
     41 class _RenderServletDelegate(RenderServlet.Delegate):
     42   def CreateServerInstance(self):
     43     return ServerInstance.ForLocal()
     44 
     45 class _PatchServletDelegate(RenderServlet.Delegate):
     46   def CreateBranchUtility(self, object_store_creator):
     47     return TestBranchUtility.CreateWithCannedData()
     48 
     49   def CreateHostFileSystemProvider(self, object_store_creator, **optargs):
     50     return HostFileSystemProvider.ForLocal(object_store_creator, **optargs)
     51 
     52   def CreateGithubFileSystemProvider(self, object_store_creator):
     53     return GithubFileSystemProvider.ForEmpty()
     54 
     55 
     56 class PatchServletTest(unittest.TestCase):
     57   def setUp(self):
     58     ConfigureFakeFetchers()
     59 
     60   def _RenderWithPatch(self, path, issue):
     61     path_with_issue = '%s/%s' % (issue, path)
     62     return PatchServlet(Request.ForTest(path_with_issue, host=_ALLOWED_HOST),
     63                         _PatchServletDelegate()).Get()
     64 
     65   def _RenderWithoutPatch(self, path):
     66     return RenderServlet(Request.ForTest(path, host=_ALLOWED_HOST),
     67                          _RenderServletDelegate()).Get()
     68 
     69   def _RenderAndCheck(self, path, issue, expected_equal):
     70     '''Renders |path| with |issue| patched in and asserts that the result is
     71     the same as |expected_equal| modulo any links that get rewritten to
     72     "_patch/issue".
     73     '''
     74     patched_response = self._RenderWithPatch(path, issue)
     75     unpatched_response = self._RenderWithoutPatch(path)
     76     for header in ('Cache-Control', 'ETag'):
     77       patched_response.headers.pop(header, None)
     78       unpatched_response.headers.pop(header, None)
     79     unpatched_content = unpatched_response.content.ToString()
     80 
     81     # Check that all links in the patched content are qualified with
     82     # the patch URL, then strip them out for checking (in)equality.
     83     patched_content = patched_response.content.ToString()
     84     patch_servlet_path = '_patch/%s' % issue
     85     errors = _CheckURLsArePatched(patched_content, patch_servlet_path)
     86     self.assertFalse(errors,
     87         '%s\nFound errors:\n * %s' % (patched_content, '\n * '.join(errors)))
     88     patched_content = patched_content.replace('/%s' % patch_servlet_path, '')
     89 
     90     self.assertEqual(patched_response.status, unpatched_response.status)
     91     self.assertEqual(patched_response.headers, unpatched_response.headers)
     92     if expected_equal:
     93       self.assertEqual(patched_content, unpatched_content)
     94     else:
     95       self.assertNotEqual(patched_content, unpatched_content)
     96 
     97   def _RenderAndAssertEqual(self, path, issue):
     98     self._RenderAndCheck(path, issue, True)
     99 
    100   def _RenderAndAssertNotEqual(self, path, issue):
    101     self._RenderAndCheck(path, issue, False)
    102 
    103   @DisableLogging('warning')
    104   def _AssertNotFound(self, path, issue):
    105     response = self._RenderWithPatch(path, issue)
    106     self.assertEqual(response.status, 404,
    107         'Path %s with issue %s should have been removed for %s.' % (
    108             path, issue, response))
    109 
    110   def _AssertOk(self, path, issue):
    111     response = self._RenderWithPatch(path, issue)
    112     self.assertEqual(response.status, 200,
    113         'Failed to render path %s with issue %s.' % (path, issue))
    114     self.assertTrue(len(response.content.ToString()) > 0,
    115         'Rendered result for path %s with issue %s should not be empty.' %
    116         (path, issue))
    117 
    118   def _AssertRedirect(self, path, issue, redirect_path):
    119     response = self._RenderWithPatch(path, issue)
    120     self.assertEqual(302, response.status)
    121     self.assertEqual('/_patch/%s/%s' % (issue, redirect_path),
    122                      response.headers['Location'])
    123 
    124   def testRender(self):
    125     # '_patch' is not included in paths below because it's stripped by Handler.
    126     issue = '14096030'
    127 
    128     # TODO(kalman): Test with chrome_sidenav.json once the sidenav logic has
    129     # stabilised.
    130 
    131     # extensions/runtime.html is removed in the patch, should redirect to the
    132     # apps version.
    133     self._AssertRedirect('extensions/runtime', issue, 'apps/runtime')
    134 
    135     # apps/runtime.html is not removed.
    136     self._RenderAndAssertEqual('apps/runtime', issue)
    137 
    138     # test_foo.html is added in the patch.
    139     self._AssertOk('extensions/test_foo', issue)
    140 
    141     # Invalid issue number results in a 404.
    142     self._AssertNotFound('extensions/index', '11111')
    143 
    144   def testXssRedirect(self):
    145     def is_redirect(from_host, from_path, to_url):
    146       response = PatchServlet(Request.ForTest(from_path, host=from_host),
    147                               _PatchServletDelegate()).Get()
    148       redirect_url, _ = response.GetRedirect()
    149       if redirect_url is None:
    150         return (False, '%s/%s did not cause a redirect' % (
    151             from_host, from_path))
    152       if redirect_url != to_url:
    153         return (False, '%s/%s redirected to %s not %s' % (
    154             from_host, from_path, redirect_url, to_url))
    155       return (True, '%s/%s redirected to %s' % (
    156           from_host, from_path, redirect_url))
    157     self.assertTrue(*is_redirect('http://developer.chrome.com', '12345',
    158                                  '%s/_patch/12345' % _ALLOWED_HOST))
    159     self.assertTrue(*is_redirect('http://developers.google.com', '12345',
    160                                  '%s/_patch/12345' % _ALLOWED_HOST))
    161     self.assertFalse(*is_redirect('http://chrome-apps-doc.appspot.com', '12345',
    162                                   None))
    163     self.assertFalse(*is_redirect('http://some-other-app.appspot.com', '12345',
    164                                   None))
    165 
    166 if __name__ == '__main__':
    167   unittest.main()
    168