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 |