From a3fb51ba412367e6aeabaa741fd2cbcdd80df2f5 Mon Sep 17 00:00:00 2001 From: Brett Langdon Date: Wed, 24 Apr 2013 19:35:27 -0400 Subject: [PATCH] initial commit --- 1_simple/tracking_server.py | 47 +++++++++++++++++ 2_search/tracking_server.py | 51 ++++++++++++++++++ 3_cookies/tracking_server.py | 66 +++++++++++++++++++++++ 4_cachebuster/tracking_server.py | 91 ++++++++++++++++++++++++++++++++ README.md | 31 +++++++++++ 5 files changed, 286 insertions(+) create mode 100644 1_simple/tracking_server.py create mode 100644 2_search/tracking_server.py create mode 100644 3_cookies/tracking_server.py create mode 100644 4_cachebuster/tracking_server.py create mode 100644 README.md diff --git a/1_simple/tracking_server.py b/1_simple/tracking_server.py new file mode 100644 index 0000000..72e0980 --- /dev/null +++ b/1_simple/tracking_server.py @@ -0,0 +1,47 @@ +from wsgiref.simple_server import make_server + + +def tracking_server(environ, respond): + """Function used to handle all requests made to + this tracking server + """ + if environ['PATH_INFO'] == '/track.js': + return track_user(environ, respond) + elif environ['PATH_INFO'] == '/favicon.ico': + respond('204 NO CONTENT', []) + return [''] + else: + return html_content(environ, respond) + + +def track_user(environ, respond): + """Function used to handle the route: /track.js + This will print environ information about the user + to stdout and return back an empty string + """ + headers = [('Content-Type', 'application/javascript')] + respond('200 OK', headers) + prefixes = ['PATH_', 'HTTP', 'REQUEST', 'QUERY'] + for key, value in environ.iteritems(): + if any(key.startswith(prefix) for prefix in prefixes): + print '%s: %s' % (key, value) + return [''] + + +def html_content(environ, respond): + """Function used to handle any route that is not /track.js + This will return to the user a very basic html page that has + a script that to call /track.js + """ + headers = [('Content-Type', 'text/html')] + respond('200 OK', headers) + return ['

Welcome

\n'] + + +if __name__ == '__main__': + try: + httpd = make_server('', 8000, tracking_server) + print 'Tracking Server Listening on Port 8000...' + httpd.serve_forever() + except KeyboardInterrupt: + print 'Exiting...' diff --git a/2_search/tracking_server.py b/2_search/tracking_server.py new file mode 100644 index 0000000..825fabf --- /dev/null +++ b/2_search/tracking_server.py @@ -0,0 +1,51 @@ +from urllib import quote +from urlparse import parse_qs +from wsgiref.simple_server import make_server + + +def tracking_server(environ, respond): + """Function used to handle all requests made to + this tracking server + """ + if environ['PATH_INFO'] == '/track.js': + return track_user(environ, respond) + elif environ['PATH_INFO'] == '/favicon.ico': + respond('204 NO CONTENT', []) + return [''] + else: + return html_content(environ, respond) + + +def track_user(environ, respond): + """Function used to handle the route: /track.js + This will also print what the user searched for + to stdout + """ + query = parse_qs(environ['QUERY_STRING']) + search = query.get('s', [''])[0] + print 'User Searched For: %s' % search + headers = [('Content-Type', 'application/javascript')] + respond('200 OK', headers) + return [''] + + +def html_content(environ, respond): + """Function used to handle any route that is not /track.js + This will return to the user a very basic html page that has + a script that to call /track.js?s= with the content + of the ?search= parameter to this request. + """ + query = parse_qs(environ['QUERY_STRING']) + search = quote(query.get('search', [''])[0]) + headers = [('Content-Type', 'text/html')] + respond('200 OK', headers) + return ['

Welcome

\n' % search] + + +if __name__ == '__main__': + try: + httpd = make_server('', 8000, tracking_server) + print 'Tracking Server Listening on Port 8000...' + httpd.serve_forever() + except KeyboardInterrupt: + print 'Exiting...' diff --git a/3_cookies/tracking_server.py b/3_cookies/tracking_server.py new file mode 100644 index 0000000..3535819 --- /dev/null +++ b/3_cookies/tracking_server.py @@ -0,0 +1,66 @@ +from Cookie import SimpleCookie +from urllib import quote +from urlparse import parse_qs +from uuid import uuid4 +from wsgiref.simple_server import make_server + + +def tracking_server(environ, respond): + """Function used to handle all requests made to + this tracking server + """ + if environ['PATH_INFO'] == '/track.js': + return track_user(environ, respond) + elif environ['PATH_INFO'] == '/favicon.ico': + respond('204 NO CONTENT', []) + return [''] + else: + return html_content(environ, respond) + + +def track_user(environ, respond): + """Function used to handle the route: /track.js + This will check to make sure that the user + has a cookie id=, if not then we will + generate a new uuid4 id for them and set the + cookie. + This will also print to stdout when we generate + a new cookie as well as what the user searched for. + """ + cookies = SimpleCookie() + cookies.load(environ.get('HTTP_COOKIE', '')) + + user_id = cookies.get('id') + if not user_id: + user_id = uuid4() + print 'User did not have id, giving: %s' % user_id + + query = parse_qs(environ['QUERY_STRING']) + search = query.get('s', [''])[0] + print 'User %s Searched For: %s' % (user_id, search) + headers = [('Content-Type', 'application/javascript'), + ('Set-Cookie', 'id=%s' % user_id)] + respond('200 OK', headers) + return [''] + + +def html_content(environ, respond): + """Function used to handle any route that is not /track.js + This will return to the user a very basic html page that has + a script that to call /track.js?s= with the content + of the ?search= parameter to this request. + """ + query = parse_qs(environ['QUERY_STRING']) + search = quote(query.get('search', [''])[0]) + headers = [('Content-Type', 'text/html')] + respond('200 OK', headers) + return ['

Welcome

\n' % search] + + +if __name__ == '__main__': + try: + httpd = make_server('', 8000, tracking_server) + print 'Tracking Server Listening on Port 8000...' + httpd.serve_forever() + except KeyboardInterrupt: + print 'Exiting...' diff --git a/4_cachebuster/tracking_server.py b/4_cachebuster/tracking_server.py new file mode 100644 index 0000000..1ac9b98 --- /dev/null +++ b/4_cachebuster/tracking_server.py @@ -0,0 +1,91 @@ +from Cookie import SimpleCookie +from urlparse import parse_qs +from uuid import uuid4 +from wsgiref.simple_server import make_server + + +def tracking_server(environ, respond): + """Function used to handle all requests made to + this tracking server + """ + if environ['PATH_INFO'] == '/track.js': + return track_user(environ, respond) + elif environ['PATH_INFO'] == '/buster.js': + return cache_buster(environ, respond) + elif environ['PATH_INFO'] == '/favicon.ico': + respond('204 NO CONTENT', []) + return [''] + else: + return html_content(environ, respond) + + +def track_user(environ, respond): + """Function used to handle the route: /track.js + This will check to make sure that the user + has a cookie id=, if not then we will + generate a new uuid4 id for them and set the + cookie. + This will also print to stdout when we generate + a new cookie as well as what the user searched for. + """ + cookies = SimpleCookie() + cookies.load(environ.get('HTTP_COOKIE', '')) + if not cookies.get('id'): + user_id = uuid4() + print 'User did not have id, giving: %s' % user_id + else: + user_id = cookies['id'].value + + query = parse_qs(environ['QUERY_STRING']) + search = query.get('s', [''])[0] + print 'User %s Searched For: %s' % (user_id, search) + headers = [('Content-Type', 'application/javascript'), + ('Set-Cookie', 'id=%s' % user_id)] + respond('200 OK', headers) + return [''] + + +def cache_buster(environ, respond): + """Function used to handle the /buster.js route + This will simply return our cache buster javascript + to the user, which adds a script tag to call /track.js + """ + headers = [('Content-Type', 'application/javascript')] + respond('200 OK', headers) + cb_js = """ + function getParameterByName(name){ + name = name.replace(/[\[]/, "\\\[").replace(/[\]]/, "\\\]"); + var regexS = "[\\?&]" + name + "=([^&#]*)"; + var regex = new RegExp(regexS); + var results = regex.exec(window.location.search); + if(results == null) + return ""; + else + return decodeURIComponent(results[1].replace(/\+/g, " ")); + } + + var now = new Date().getTime(); + var random = Math.random() * 99999999999; + var search = getParameterByName('search'); + document.write(''); + """ + return [cb_js] + + +def html_content(environ, respond): + """Function used to handle any route that is not /track.js + This will return to the user a very basic html page that has + a script that to call our /buster.js script + """ + headers = [('Content-Type', 'text/html')] + respond('200 OK', headers) + return ['

Welcome

\n'] + + +if __name__ == '__main__': + try: + httpd = make_server('', 8000, tracking_server) + print 'Tracking Server Listening on Port 8000...' + httpd.serve_forever() + except KeyboardInterrupt: + print 'Exiting...' diff --git a/README.md b/README.md new file mode 100644 index 0000000..47ae0cb --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +Tracking Server Examples +======================== + +This repository contains the code examples to follow along with the blog post [Third Party Tracking Pixels](http://brett.is/writing/about/third-party-tracking-pixels) +by [Brett Langdon](http://brett.is). + +Each example provided builds upon the previous examples code. + + +## License +The MIT License (MIT) + +Copyright (c) 2013 Brett Langdon + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE.