225 lines
7.1 KiB
Python
225 lines
7.1 KiB
Python
|
# 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()
|