# 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' Change 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 = """



%(iframe_html)s """ IFRAME_FRAGMENT = """ """ 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()