|  | # Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
|  | # Use of this source code is governed by a BSD-style license that can be | 
|  | # found in the LICENSE file. | 
|  |  | 
|  | """Test server for generating nested iframes with different sites. | 
|  |  | 
|  | Very simple python server for creating a bunch of iframes. The page generation | 
|  | is randomized based on query parameters.  See the __init__ function of the | 
|  | Params class for a description of the parameters. | 
|  |  | 
|  | This server relies on gevent. On Ubuntu, install it via: | 
|  |  | 
|  | sudo apt-get install python-gevent | 
|  |  | 
|  | Run the server using | 
|  |  | 
|  | python iframe_server.py | 
|  |  | 
|  | To use the server, run chrome as follows: | 
|  |  | 
|  | google-chrome --host-resolver-rules='map *.invalid 127.0.0.1' | 
|  |  | 
|  | Change 127.0.0.1 to be the IP of the machine this server is running on. Then | 
|  | in this chrome instance, navigate to any domain in .invalid | 
|  | (eg., http://1.invalid:8090) to run this test. | 
|  |  | 
|  | """ | 
|  |  | 
|  | import colorsys | 
|  | import copy | 
|  | import random | 
|  | import urllib | 
|  | import urlparse | 
|  |  | 
|  | from gevent import pywsgi # pylint: disable=F0401 | 
|  |  | 
|  | MAIN_PAGE = """ | 
|  | <html> | 
|  | <head> | 
|  | <style> | 
|  | body { | 
|  | background-color: %(color)s; | 
|  | } | 
|  | </style> | 
|  | </head> | 
|  | <body> | 
|  | <center> | 
|  | <h1><a href="%(url)s">%(site)s</a></h1> | 
|  | <p><small>%(url)s</small> | 
|  | </center> | 
|  | <br /> | 
|  | %(iframe_html)s | 
|  | </body> | 
|  | </html> | 
|  | """ | 
|  |  | 
|  | IFRAME_FRAGMENT = """ | 
|  | <iframe src="%(src)s" width="%(width)s" height="%(height)s"> | 
|  | </iframe> | 
|  | """ | 
|  |  | 
|  | class Params(object): | 
|  | """Simple object for holding parameters""" | 
|  | def __init__(self, query_dict): | 
|  | # Basic params: | 
|  | #  nframes is how many frames per page. | 
|  | #  nsites is how many sites to random choose out of. | 
|  | #  depth is how deep to make the frame tree | 
|  | #  pattern specifies how the sites are layed out per depth. An empty string | 
|  | #      uses a random N = [0, nsites] each time to generate a N.invalid URL. | 
|  | #      Otherwise sepcify with single letters like 'ABCA' and frame | 
|  | #      A.invalid will embed B.invalid will embed C.invalid will embed A. | 
|  | #  jitter is the amount of randomness applied to nframes and nsites. | 
|  | #      Should be from [0,1]. 0.0 means no jitter. | 
|  | #  size_jitter is like jitter, but for width and height. | 
|  | self.nframes = int(query_dict.get('nframes', [4] )[0]) | 
|  | self.nsites = int(query_dict.get('nsites', [10] )[0]) | 
|  | self.depth = int(query_dict.get('depth', [1] )[0]) | 
|  | self.jitter = float(query_dict.get('jitter', [0] )[0]) | 
|  | self.size_jitter = float(query_dict.get('size_jitter', [0.5] )[0]) | 
|  | self.pattern = query_dict.get('pattern', [''] )[0] | 
|  | self.pattern_pos = int(query_dict.get('pattern_pos', [0] )[0]) | 
|  |  | 
|  | # Size parameters. Values are percentages. | 
|  | self.width = int(query_dict.get('width', [60])[0]) | 
|  | self.height = int(query_dict.get('height', [50])[0]) | 
|  |  | 
|  | # Pass the random seed so our pages are reproduceable. | 
|  | self.seed = int(query_dict.get('seed', | 
|  | [random.randint(0, 2147483647)])[0]) | 
|  |  | 
|  |  | 
|  | def get_site(urlpath): | 
|  | """Takes a urlparse object and finds its approximate site. | 
|  |  | 
|  | Site is defined as registered domain name + scheme. We approximate | 
|  | registered domain name by preserving the last 2 elements of the DNS | 
|  | name. This breaks for domains like co.uk. | 
|  | """ | 
|  | no_port = urlpath.netloc.split(':')[0] | 
|  | host_parts = no_port.split('.') | 
|  | site_host = '.'.join(host_parts[-2:]) | 
|  | return '%s://%s' % (urlpath.scheme, site_host) | 
|  |  | 
|  |  | 
|  | def generate_host(rand, params): | 
|  | """Generates the host to be used as an iframes source. | 
|  |  | 
|  | Uses the .invalid domain to ensure DNS will not resolve to any real | 
|  | address. | 
|  | """ | 
|  | if params.pattern: | 
|  | host = params.pattern[params.pattern_pos] | 
|  | params.pattern_pos = (params.pattern_pos + 1) % len(params.pattern) | 
|  | else: | 
|  | host = rand.randint(1, apply_jitter(rand, params.jitter, params.nsites)) | 
|  | return '%s.invalid' % host | 
|  |  | 
|  |  | 
|  | def apply_jitter(rand, jitter, n): | 
|  | """Reduce n by random amount from [0, jitter]. Ensures result is >=1.""" | 
|  | if jitter <= 0.001: | 
|  | return n | 
|  | v = n - int(n * rand.uniform(0, jitter)) | 
|  | if v: | 
|  | return v | 
|  | else: | 
|  | return 1 | 
|  |  | 
|  |  | 
|  | def get_color_for_site(site): | 
|  | """Generate a stable (and pretty-ish) color for a site.""" | 
|  | val = hash(site) | 
|  | # The constants below are arbitrary chosen emperically to look "pretty." | 
|  | # HSV is used because it is easier to control the color than RGB. | 
|  | # Reducing the H to 0.6 produces a good range of colors. Preserving | 
|  | # > 0.5 saturation and value means the colors won't be too washed out. | 
|  | h = (val % 100)/100.0 * 0.6 | 
|  | s = 1.0 - (int(val/100) % 100)/200. | 
|  | v = 1.0 - (int(val/10000) % 100)/200.0 | 
|  | (r, g, b) = colorsys.hsv_to_rgb(h, s, v) | 
|  | return 'rgb(%d, %d, %d)' % (int(r * 255), int(g * 255), int(b * 255)) | 
|  |  | 
|  |  | 
|  | def make_src(scheme, netloc, path, params): | 
|  | """Constructs the src url that will recreate the given params.""" | 
|  | if path == '/': | 
|  | path = '' | 
|  | return '%(scheme)s://%(netloc)s%(path)s?%(params)s' % { | 
|  | 'scheme': scheme, | 
|  | 'netloc': netloc, | 
|  | 'path': path, | 
|  | 'params': urllib.urlencode(params.__dict__), | 
|  | } | 
|  |  | 
|  |  | 
|  | def make_iframe_html(urlpath, params): | 
|  | """Produces the HTML fragment for the iframe.""" | 
|  | if (params.depth <= 0): | 
|  | return '' | 
|  | # Ensure a stable random number per iframe. | 
|  | rand = random.Random() | 
|  | rand.seed(params.seed) | 
|  |  | 
|  | netloc_paths = urlpath.netloc.split(':') | 
|  | netloc_paths[0] = generate_host(rand, params) | 
|  |  | 
|  | width = apply_jitter(rand, params.size_jitter, params.width) | 
|  | height = apply_jitter(rand, params.size_jitter, params.height) | 
|  | iframe_params = { | 
|  | 'src': make_src(urlpath.scheme, ':'.join(netloc_paths), | 
|  | urlpath.path, params), | 
|  | 'width': '%d%%' % width, | 
|  | 'height': '%d%%' % height, | 
|  | } | 
|  | return IFRAME_FRAGMENT % iframe_params | 
|  |  | 
|  |  | 
|  | def create_html(environ): | 
|  | """Creates the current HTML page. Also parses out query parameters.""" | 
|  | urlpath = urlparse.urlparse('%s://%s%s?%s' % ( | 
|  | environ['wsgi.url_scheme'], | 
|  | environ['HTTP_HOST'], | 
|  | environ['PATH_INFO'], | 
|  | environ['QUERY_STRING'])) | 
|  | site = get_site(urlpath) | 
|  | params = Params(urlparse.parse_qs(urlpath.query)) | 
|  |  | 
|  | rand = random.Random() | 
|  | rand.seed(params.seed) | 
|  |  | 
|  | iframe_htmls = [] | 
|  | for frame in xrange(0, apply_jitter(rand, params.jitter, params.nframes)): | 
|  | # Copy current parameters into iframe and make modifications | 
|  | # for the recursive generation. | 
|  | iframe_params = copy.copy(params) | 
|  | iframe_params.depth = params.depth - 1 | 
|  | # Base the new seed off the current seed, but have it skip enough that | 
|  | # different frame trees are unlikely to collide. Numbers and skips | 
|  | # not chosen in any scientific manner at all. | 
|  | iframe_params.seed = params.seed + (frame + 1) * ( | 
|  | 1000000 + params.depth + 333) | 
|  | iframe_htmls.append(make_iframe_html(urlpath, iframe_params)) | 
|  | template_params = dict(params.__dict__) | 
|  | template_params.update({ | 
|  | 'color': get_color_for_site(site), | 
|  | 'iframe_html': '\n'.join(iframe_htmls), | 
|  | 'site': site, | 
|  | 'url': make_src(urlpath.scheme, urlpath.netloc, urlpath.path, params), | 
|  | }) | 
|  | return MAIN_PAGE % template_params | 
|  |  | 
|  |  | 
|  | def application(environ, start_response): | 
|  | start_response('200 OK', [('Content-Type', 'text/html')]) | 
|  | if environ['PATH_INFO'] == '/favicon.ico': | 
|  | yield '' | 
|  | else: | 
|  | yield create_html(environ) | 
|  |  | 
|  |  | 
|  | server = pywsgi.WSGIServer(('', 8090), application) | 
|  |  | 
|  | server.serve_forever() |