Home | History | Annotate | Download | only in oopif
      1 # Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """Test server for generating nested iframes with different sites.
      6 
      7 Very simple python server for creating a bunch of iframes. The page generation
      8 is randomized based on query parameters.  See the __init__ function of the
      9 Params class for a description of the parameters.
     10 
     11 This server relies on gevent. On Ubuntu, install it via:
     12 
     13   sudo apt-get install python-gevent
     14 
     15 Run the server using
     16 
     17   python iframe_server.py
     18 
     19 To use the server, run chrome as follows:
     20 
     21   google-chrome --host-resolver-rules='map *.invalid 127.0.0.1'
     22 
     23 Change 127.0.0.1 to be the IP of the machine this server is running on. Then
     24 in this chrome instance, navigate to any domain in .invalid
     25 (eg., http://1.invalid:8090) to run this test.
     26 
     27 """
     28 
     29 import colorsys
     30 import copy
     31 import random
     32 import urllib
     33 import urlparse
     34 
     35 from gevent import pywsgi # pylint: disable=F0401
     36 
     37 MAIN_PAGE = """
     38 <html>
     39   <head>
     40     <style>
     41       body {
     42         background-color: %(color)s;
     43       }
     44     </style>
     45   </head>
     46   <body>
     47     <center>
     48       <h1><a href="%(url)s">%(site)s</a></h1>
     49       <p><small>%(url)s</small>
     50     </center>
     51     <br />
     52     %(iframe_html)s
     53   </body>
     54 </html>
     55 """
     56 
     57 IFRAME_FRAGMENT = """
     58 <iframe src="%(src)s" width="%(width)s" height="%(height)s">
     59 </iframe>
     60 """
     61 
     62 class Params(object):
     63   """Simple object for holding parameters"""
     64   def __init__(self, query_dict):
     65     # Basic params:
     66     #  nframes is how many frames per page.
     67     #  nsites is how many sites to random choose out of.
     68     #  depth is how deep to make the frame tree
     69     #  pattern specifies how the sites are layed out per depth. An empty string
     70     #      uses a random N = [0, nsites] each time to generate a N.invalid URL.
     71     #      Otherwise sepcify with single letters like 'ABCA' and frame
     72     #      A.invalid will embed B.invalid will embed C.invalid will embed A.
     73     #  jitter is the amount of randomness applied to nframes and nsites.
     74     #      Should be from [0,1]. 0.0 means no jitter.
     75     #  size_jitter is like jitter, but for width and height.
     76     self.nframes = int(query_dict.get('nframes', [4] )[0])
     77     self.nsites = int(query_dict.get('nsites', [10] )[0])
     78     self.depth = int(query_dict.get('depth', [1] )[0])
     79     self.jitter = float(query_dict.get('jitter', [0] )[0])
     80     self.size_jitter = float(query_dict.get('size_jitter', [0.5] )[0])
     81     self.pattern = query_dict.get('pattern', [''] )[0]
     82     self.pattern_pos = int(query_dict.get('pattern_pos', [0] )[0])
     83 
     84     # Size parameters. Values are percentages.
     85     self.width = int(query_dict.get('width', [60])[0])
     86     self.height = int(query_dict.get('height', [50])[0])
     87 
     88     # Pass the random seed so our pages are reproduceable.
     89     self.seed = int(query_dict.get('seed',
     90                                    [random.randint(0, 2147483647)])[0])
     91 
     92 
     93 def get_site(urlpath):
     94   """Takes a urlparse object and finds its approximate site.
     95 
     96   Site is defined as registered domain name + scheme. We approximate
     97   registered domain name by preserving the last 2 elements of the DNS
     98   name. This breaks for domains like co.uk.
     99   """
    100   no_port = urlpath.netloc.split(':')[0]
    101   host_parts = no_port.split('.')
    102   site_host = '.'.join(host_parts[-2:])
    103   return '%s://%s' % (urlpath.scheme, site_host)
    104 
    105 
    106 def generate_host(rand, params):
    107   """Generates the host to be used as an iframes source.
    108 
    109   Uses the .invalid domain to ensure DNS will not resolve to any real
    110   address.
    111   """
    112   if params.pattern:
    113     host = params.pattern[params.pattern_pos]
    114     params.pattern_pos = (params.pattern_pos + 1) % len(params.pattern)
    115   else:
    116     host = rand.randint(1, apply_jitter(rand, params.jitter, params.nsites))
    117   return '%s.invalid' % host
    118 
    119 
    120 def apply_jitter(rand, jitter, n):
    121   """Reduce n by random amount from [0, jitter]. Ensures result is >=1."""
    122   if jitter <= 0.001:
    123     return n
    124   v = n - int(n * rand.uniform(0, jitter))
    125   if v:
    126     return v
    127   else:
    128     return 1
    129 
    130 
    131 def get_color_for_site(site):
    132   """Generate a stable (and pretty-ish) color for a site."""
    133   val = hash(site)
    134   # The constants below are arbitrary chosen emperically to look "pretty."
    135   # HSV is used because it is easier to control the color than RGB.
    136   # Reducing the H to 0.6 produces a good range of colors. Preserving
    137   # > 0.5 saturation and value means the colors won't be too washed out.
    138   h = (val % 100)/100.0 * 0.6
    139   s = 1.0 - (int(val/100) % 100)/200.
    140   v = 1.0 - (int(val/10000) % 100)/200.0
    141   (r, g, b) = colorsys.hsv_to_rgb(h, s, v)
    142   return 'rgb(%d, %d, %d)' % (int(r * 255), int(g * 255), int(b * 255))
    143 
    144 
    145 def make_src(scheme, netloc, path, params):
    146   """Constructs the src url that will recreate the given params."""
    147   if path == '/':
    148     path = ''
    149   return '%(scheme)s://%(netloc)s%(path)s?%(params)s' % {
    150       'scheme': scheme,
    151       'netloc': netloc,
    152       'path': path,
    153       'params': urllib.urlencode(params.__dict__),
    154       }
    155 
    156 
    157 def make_iframe_html(urlpath, params):
    158   """Produces the HTML fragment for the iframe."""
    159   if (params.depth <= 0):
    160     return ''
    161   # Ensure a stable random number per iframe.
    162   rand = random.Random()
    163   rand.seed(params.seed)
    164 
    165   netloc_paths = urlpath.netloc.split(':')
    166   netloc_paths[0] = generate_host(rand, params)
    167 
    168   width = apply_jitter(rand, params.size_jitter, params.width)
    169   height = apply_jitter(rand, params.size_jitter, params.height)
    170   iframe_params = {
    171       'src': make_src(urlpath.scheme, ':'.join(netloc_paths),
    172                       urlpath.path, params),
    173       'width': '%d%%' % width,
    174       'height': '%d%%' % height,
    175       }
    176   return IFRAME_FRAGMENT % iframe_params
    177 
    178 
    179 def create_html(environ):
    180   """Creates the current HTML page. Also parses out query parameters."""
    181   urlpath = urlparse.urlparse('%s://%s%s?%s' % (
    182       environ['wsgi.url_scheme'],
    183       environ['HTTP_HOST'],
    184       environ['PATH_INFO'],
    185       environ['QUERY_STRING']))
    186   site = get_site(urlpath)
    187   params = Params(urlparse.parse_qs(urlpath.query))
    188 
    189   rand = random.Random()
    190   rand.seed(params.seed)
    191 
    192   iframe_htmls = []
    193   for frame in xrange(0, apply_jitter(rand, params.jitter, params.nframes)):
    194     # Copy current parameters into iframe and make modifications
    195     # for the recursive generation.
    196     iframe_params = copy.copy(params)
    197     iframe_params.depth = params.depth - 1
    198     # Base the new seed off the current seed, but have it skip enough that
    199     # different frame trees are unlikely to collide. Numbers and skips
    200     # not chosen in any scientific manner at all.
    201     iframe_params.seed = params.seed + (frame + 1) * (
    202         1000000 + params.depth + 333)
    203     iframe_htmls.append(make_iframe_html(urlpath, iframe_params))
    204   template_params = dict(params.__dict__)
    205   template_params.update({
    206       'color': get_color_for_site(site),
    207       'iframe_html': '\n'.join(iframe_htmls),
    208       'site': site,
    209       'url': make_src(urlpath.scheme, urlpath.netloc, urlpath.path, params),
    210       })
    211   return MAIN_PAGE % template_params
    212 
    213 
    214 def application(environ, start_response):
    215   start_response('200 OK', [('Content-Type', 'text/html')])
    216   if environ['PATH_INFO'] == '/favicon.ico':
    217     yield ''
    218   else:
    219     yield create_html(environ)
    220 
    221 
    222 server = pywsgi.WSGIServer(('', 8090), application)
    223 
    224 server.serve_forever()
    225