| OLD | NEW |
| (Empty) |
| 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() | |
| OLD | NEW |