Home | History | Annotate | Download | only in web-page-replay
      1 #!/usr/bin/env python
      2 # Copyright 2013 Google Inc. All Rights Reserved.
      3 #
      4 # Licensed under the Apache License, Version 2.0 (the "License");
      5 # you may not use this file except in compliance with the License.
      6 # You may obtain a copy of the License at
      7 #
      8 #      http://www.apache.org/licenses/LICENSE-2.0
      9 #
     10 # Unless required by applicable law or agreed to in writing, software
     11 # distributed under the License is distributed on an "AS IS" BASIS,
     12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13 # See the License for the specific language governing permissions and
     14 # limitations under the License.
     15 
     16 import datetime
     17 import httparchive
     18 import mock
     19 import script_injector
     20 import unittest
     21 
     22 
     23 LONG_COMMENT = '<!--' + 'comment,' * 200 + '-->'
     24 COMMENT_OR_NOT = ('', LONG_COMMENT)
     25 SCRIPT_TO_INJECT = 'var flag = 0;'
     26 EXPECTED_SCRIPT = '<script>' + SCRIPT_TO_INJECT + '</script>'
     27 TEXT_HTML = 'text/html'
     28 TEXT_CSS = 'text/css'
     29 APPLICATION = 'application/javascript'
     30 SEPARATOR = httparchive.ArchivedHttpResponse.CHUNK_EDIT_SEPARATOR
     31 SEPARATORS_OR_NOT = ('', SEPARATOR, SEPARATOR*3)
     32 
     33 TEMPLATE_HEAD = """\
     34 {boundary_at_start}\
     35 <!doc{boundary_in_doctype}type html>{boundary_after_doctype}\
     36 <ht{boundary_in_html}ml>{boundary_after_html}\
     37 <he{boundary_in_head}ad>{injection}{boundary_after_head}\
     38 </head></html>\
     39 """
     40 TEMPLATE_HTML = """\
     41 {boundary_at_start}\
     42 <!doc{boundary_in_doctype}type html>{boundary_after_doctype}\
     43 <ht{boundary_in_html}ml>{injection}{boundary_after_html}\
     44 </html>\
     45 """
     46 TEMPLATE_DOCTYPE = """\
     47 {boundary_at_start}\
     48 <!doc{boundary_in_doctype}type html>{injection}{boundary_after_doctype}\
     49 <body></body>\
     50 """
     51 TEMPLATE_RAW = """\
     52 {boundary_at_start}\
     53 {injection}<body></body>\
     54 """
     55 NORMAL_TEMPLATES = (TEMPLATE_HEAD, TEMPLATE_HTML,
     56                     TEMPLATE_DOCTYPE, TEMPLATE_RAW)
     57 TEMPLATE_COMMENT = """\
     58 {comment_before_doctype}<!doctype html>{comment_after_doctype}\
     59 <html>{comment_after_html}<head>{injection}</head></html>\
     60 """
     61 
     62 
     63 def _wrap_inject_script(source, application, script_to_inject):
     64   text_chunks = source.split(SEPARATOR)
     65   text_chunks, just_injected = script_injector.InjectScript(
     66       text_chunks, application, script_to_inject)
     67   result = SEPARATOR.join(text_chunks)
     68   return result, just_injected
     69 
     70 
     71 class ScriptInjectorTest(unittest.TestCase):
     72 
     73   def _assert_no_injection(self, source, application):
     74     new_source, just_injected = _wrap_inject_script(
     75         source, application, SCRIPT_TO_INJECT)
     76     self.assertEqual(new_source, source)
     77     self.assertFalse(just_injected)
     78 
     79   def _assert_successful_injection(self, template):
     80     source, just_injected = _wrap_inject_script(
     81         template.format(injection=''), TEXT_HTML, SCRIPT_TO_INJECT)
     82     self.assertEqual(source, template.format(injection=EXPECTED_SCRIPT))
     83     self.assertTrue(just_injected)
     84 
     85   def test_unsupported_content_type(self):
     86     self._assert_no_injection('abc', TEXT_CSS)
     87     self._assert_no_injection('abc', APPLICATION)
     88 
     89   def test_empty_content_as_already_injected(self):
     90     self._assert_no_injection('', TEXT_HTML)
     91 
     92   def test_non_html_content_with_html_content_type(self):
     93     self._assert_no_injection('{"test": 1"}', TEXT_HTML)
     94 
     95   def test_already_injected(self):
     96     parameters = {'injection': SCRIPT_TO_INJECT}
     97     for template in NORMAL_TEMPLATES:
     98       for parameters['boundary_at_start'] in SEPARATORS_OR_NOT:
     99         for parameters['boundary_in_doctype'] in SEPARATORS_OR_NOT:
    100           for parameters['boundary_after_doctype'] in SEPARATORS_OR_NOT:
    101             for parameters['boundary_in_html'] in SEPARATORS_OR_NOT:
    102               for parameters['boundary_after_html'] in SEPARATORS_OR_NOT:
    103                 for parameters['boundary_in_head'] in SEPARATORS_OR_NOT:
    104                   for parameters['boundary_after_head'] in SEPARATORS_OR_NOT:
    105                     source = template.format(**parameters)
    106                     self._assert_no_injection(source, TEXT_HTML)
    107 
    108   def test_normal(self):
    109     parameters = {'injection': '{injection}'}
    110     for template in NORMAL_TEMPLATES:
    111       for parameters['boundary_at_start'] in SEPARATORS_OR_NOT:
    112         for parameters['boundary_in_doctype'] in SEPARATORS_OR_NOT:
    113           for parameters['boundary_after_doctype'] in SEPARATORS_OR_NOT:
    114             for parameters['boundary_in_html'] in SEPARATORS_OR_NOT:
    115               for parameters['boundary_after_html'] in SEPARATORS_OR_NOT:
    116                 for parameters['boundary_in_head'] in SEPARATORS_OR_NOT:
    117                   for parameters['boundary_after_head'] in SEPARATORS_OR_NOT:
    118                     template = template.format(**parameters)
    119                     self._assert_successful_injection(template)
    120 
    121   def test_comments(self):
    122     parameters = {'injection': '{injection}'}
    123     for parameters['comment_before_doctype'] in COMMENT_OR_NOT:
    124       for parameters['comment_after_doctype'] in COMMENT_OR_NOT:
    125         for parameters['comment_after_html'] in COMMENT_OR_NOT:
    126           template = TEMPLATE_COMMENT.format(**parameters)
    127           self._assert_successful_injection(template)
    128 
    129   @mock.patch('script_injector.os.path.exists', return_value=True)
    130   @mock.patch('script_injector.open',
    131               mock.mock_open(read_data='var time_seed = 123;'))
    132   def test_injection_function(self, _):
    133     injector = script_injector.GetScriptInjector('to_inject.js')
    134     self.assertEqual('var time_seed=123;',
    135                      injector(datetime.datetime.utcnow()))
    136 
    137   @mock.patch('script_injector.os.path.exists', return_value=True)
    138   @mock.patch('script_injector.open',
    139               mock.mock_open(
    140                   read_data='var time_seed = {{WPR_TIME_SEED_TIMESTAMP}};'))
    141   def test_time_seed_replacement(self, _):
    142     date = datetime.datetime(2016, 11, 17)
    143     injector = script_injector.GetScriptInjector('date.js')
    144     self.assertEqual('var time_seed=1479340800000;', injector(date))
    145 
    146 
    147 if __name__ == '__main__':
    148   unittest.main()
    149