Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Move onload out of the app.

We need onload to be able to get a client-IP based
tracking validation hash, but it doesn't actually need
to do any database work to make those hashes. It also
fails a huge number of times per day. This moves the hash
calculation out to an external app with no db dependencies.
  • Loading branch information...
commit 93a65a6a3a6e5fd0f930bb8c0a47e2a7f8659cb0 1 parent 581cb4e
@spladug spladug authored
View
2  r2/example.ini
@@ -127,6 +127,8 @@ incr_caches = localhost:11211
rec_cache = 127.0.0.1:11211
## -- traffic tracking urls --
+# domain to send tracking requests (see scripts/tracker.py)
+tracking_domain = reddit.local
# image to render to track pageviews
tracker_url = /static/pixel.png
# images to render to track sponsored links
View
2  r2/r2/config/routing.py
@@ -220,7 +220,7 @@ def make_map(global_conf={}, app_conf={}):
mc('/api/:action', controller='promote',
requirements=dict(action="promote|unpromote|edit_promo|link_thumb|freebie|promote_note|update_pay|refund|traffic_viewer|rm_traffic_viewer|edit_campaign|delete_campaign|meta_promo|add_roadblock|rm_roadblock"))
mc('/api/:action', controller='apiminimal',
- requirements=dict(action="onload|new_captcha"))
+ requirements=dict(action="new_captcha"))
mc('/api/:action', controller='api')
mc("/button_info", controller="api", action="info", limit = 1)
View
33 r2/r2/controllers/api.py
@@ -78,37 +78,6 @@ class ApiminimalController(MinimalController):
Put API calls in here which don't rely on the user being logged in
"""
- @validatedForm(promoted = VByName('ids', thing_cls = Link,
- multiple = True),
- sponsorships = VByName('ids', thing_cls = Subreddit,
- multiple = True))
- def POST_onload(self, form, jquery, promoted, sponsorships, *a, **kw):
- suffix = ""
- if not isinstance(c.site, FakeSubreddit):
- suffix = "-" + c.site.name
- def add_tracker(dest, where, what):
- if not dest.startswith("javascript:"):
- dest = tracking.PromotedLinkClickInfo.gen_url(fullname =what + suffix,
- dest = dest,
- ip = request.ip)
- jquery.set_tracker(
- where,
- tracking.PromotedLinkInfo.gen_url(fullname=what + suffix,
- ip = request.ip), dest
- )
-
- if promoted:
- # make sure that they are really promoted
- promoted = [ l for l in promoted if l.promoted ]
- for l in promoted:
- add_tracker(l.url, l._fullname, l._fullname)
-
- if sponsorships:
- for s in sponsorships:
- if getattr(s, 'sponsorship_url', None):
- add_tracker(s.sponsorship_url, s._fullname,
- "%s_%s" % (s._fullname, s.sponsorship_name))
-
@validatedForm()
def POST_new_captcha(self, form, jquery, *a, **kw):
jquery("body").captcha(get_iden())
@@ -1279,7 +1248,7 @@ def POST_upload_sr_img(self, file, header, sponsor, name, form_id):
link_type = VOneOf('link_type', ('any', 'link', 'self')),
ip = ValidIP(),
sponsor_text =VLength('sponsorship-text', max_length = 500),
- sponsor_name =VLength('sponsorship-name', max_length = 500),
+ sponsor_name =VLength('sponsorship-name', max_length = 64),
sponsor_url = VLength('sponsorship-url', max_length = 500),
css_on_cname = VBoolean("css_on_cname"),
)
View
104 r2/r2/public/static/js/analytics.js
@@ -0,0 +1,104 @@
+r.analytics = {
+ trackers: {},
+
+ init: function() {
+ // these guys are relying on the custom 'onshow' from jquery.reddit.js
+ $(document).delegate(
+ '.promotedlink.promoted, .sponsorshipbox',
+ 'onshow',
+ $.proxy(this, 'fetchTrackersOrFirePixel')
+ )
+
+ $('.promotedlink.promoted:visible, .sponsorshipbox:visible').trigger('onshow')
+ },
+
+ fetchTrackingHashes: function(callback) {
+ var fullnames = []
+
+ $('.promotedlink.promoted, .sponsorshipbox')
+ .each(function() {
+ var thing = $(this),
+ fullname = thing.data('fullname'),
+ sponsorship = thing.data('sponsorship')
+
+ if (sponsorship)
+ fullname += '_' + sponsorship
+
+ if (!reddit.is_fake)
+ fullname += '-' + reddit.post_site
+
+ thing.data('trackingName', fullname)
+
+ if (!(fullname in r.analytics.trackers))
+ fullnames.push(fullname)
+ })
+
+ $.ajax({
+ url: 'http://' + reddit.tracking_domain + '/fetch-trackers',
+ type: 'get',
+ dataType: 'jsonp',
+ data: { 'ids': fullnames },
+ success: function(data) {
+ $.extend(r.analytics.trackers, data)
+ callback()
+ }
+ })
+ },
+
+ fetchTrackersOrFirePixel: function(e) {
+ var target = $(e.target),
+ fullname = target.data('fullname')
+
+ if (fullname in this.trackers) {
+ this.fireTrackingPixel(target)
+ } else {
+ this.fetchTrackingHashes($.proxy(this, 'fireTrackingPixel', target))
+ }
+ },
+
+ fireTrackingPixel: function(thing) {
+ if (thing.data('trackerFired'))
+ return
+
+ var fullname = thing.data('trackingName'),
+ hash = this.trackers[fullname]
+
+ var pixel = new Image()
+ pixel.src = reddit.adtracker_url + '?' + $.param({
+ 'id': fullname,
+ 'hash': hash,
+ 'r': Math.round(Math.random() * 2147483647) // cachebuster
+ })
+
+ // If IE7/8 thinks the text of a link looks like an email address
+ // (e.g. it has an @ in it), then setting the href replaces the
+ // text as well. We'll store the original text and replace it to
+ // hack around this. Distilled reproduction in: http://jsfiddle.net/JU2Vj/1/
+ var link = thing.find('a.title'),
+ old_html = link.html(),
+ dest = link.attr('href'),
+ click_url = reddit.clicktracker_url + '?' + $.param({
+ 'id': fullname,
+ 'hash': hash,
+ 'url': dest
+ })
+
+ save_href(link)
+ link.attr('href', click_url)
+
+ if (link.html() != old_html)
+ link.html(old_html)
+
+ // also do the thumbnail
+ var thumb = thing.find('a.thumbnail')
+ save_href(thumb)
+ thumb.attr('href', click_url)
+
+ thing.data('trackerFired', true)
+ }
+}
+
+$(function() {
+ // TODO: move this into r.base when it exists
+ r.analytics.init()
+})
View
6 r2/r2/public/static/js/jquery.reddit.js
@@ -579,12 +579,6 @@ $.fn.insert_table_rows = function(rows, index) {
return this;
};
-$.set_tracker = function(id, show_track, click_track) {
- /* hook for api to pass back tracker information */
- reddit.trackers[id] = {show: show_track, click: click_track};
- $.things(id).filter(":visible").show();
-};
-
$.fn.captcha = function(iden) {
/* */
View
38 r2/r2/public/static/js/reddit.js
@@ -603,44 +603,6 @@ function updateEventHandlers(thing) {
thing = $(thing);
var listing = thing.parent();
- $(thing).filter(".promotedlink, .sponsorshipbox")
- .bind("onshow", function() {
- var id = $(this).thing_id();
- if($.inArray(id, reddit.tofetch) != -1) {
- $.request("onload", {ids: reddit.tofetch.join(",")});
- reddit.tofetch = [];
- }
- var tracker = reddit.trackers[id];
- if($.defined(tracker)) {
- var title = $(this).find("a.title");
- var text;
- if ($.browser.msie) {
- /* bugfix for IE7-8; links with text that look like
- * a url of some sort (including the @ character)
- * have their text changed when href is set.
- * see http://jsfiddle.net/JU2Vj/1/ for a distilled
- * reproduction of the bug */
- text = title.html();
- }
-
- save_href($(this).find("a.title"))
- .attr("href", tracker.click).end();
- save_href($(this).find("a.thumbnail"))
- .attr("href", tracker.click).end();
- $(this).find("img.promote-pixel").attr("src", tracker.show);
-
- if ($.browser.msie) {
- if (text != title.html()) {
- title.html(text);
- }
- }
-
- delete reddit.trackers[id];
- }
- })
- /* pre-trigger new event if already shown */
- .filter(":visible").trigger("onshow");
-
/* click on a title.. */
$(thing).filter(".link")
.find("a.title, a.comments").mousedown(function() {
View
2  r2/r2/templates/printable.html
@@ -76,7 +76,7 @@
rowclass += " hidden"
%>
<div class="${self.thing_css_class(thing)} ${rowclass} ${unsafe(cls)} ${selflink}"
- ${thing.display} onclick="click_thing(this)">
+ ${thing.display} data-fullname="${thing._fullname}" onclick="click_thing(this)">
<p class="parent">
${self.ParentDiv()}
</p>
View
7 r2/r2/templates/promotedlink.html
@@ -71,12 +71,5 @@
${_('sponsored link')}
%endif
</p>
- %if promote.is_promoted(thing):
- <img class="promote-pixel" id="promote_oimg_${thing._fullname}"
- src="/static/pixel.png" alt=" " />
- <script type="text/javascript">
- reddit.tofetch.push("${thing._fullname}");
- </script>
- %endif
</%def>
View
9 r2/r2/templates/sponsorshipbox.html
@@ -22,20 +22,15 @@
%if c.site.sponsorship_url and c.site.sponsorship_img:
<%
+ from r2.models.subreddit import FakeSubreddit
name = c.site._fullname
%>
-<div class="thing sponsorshipbox id-${name}">
+<div class="thing sponsorshipbox id-${name}" data-fullname="${name}" data-sponsorship="${c.site.sponsorship_name}">
<span>${c.site.sponsorship_text}</span>
<div>
<a class="title" href="${c.site.sponsorship_url}">
<img alt="sponsor" src="${c.site.sponsorship_img}" />
</a>
</div>
- <img class="promote-pixel"
- id="promote_img_${name}"
- src="/static/pixel.png" alt=" " />
- <script type="text/javascript">
- reddit.tofetch.push("${name}");
- </script>
</div>
%endif
View
7 r2/r2/templates/utils.html
@@ -21,6 +21,7 @@
################################################################################
<%!
+ from r2.models import FakeSubreddit
from r2.lib.filters import spaceCompress, unsafe
from r2.lib.template_helpers import add_sr
from r2.lib.utils import cols, long_datetime, timesince
@@ -391,8 +392,10 @@
submitting: "${_('submitting...')}",
loading: "${_('loading...')}"
},
- tofetch: [],
- trackers: {}
+ is_fake: ${'true' if isinstance(c.site, FakeSubreddit) else 'false'},
+ tracking_domain: '${g.tracking_domain}',
+ adtracker_url: '${g.adtracker_url}',
+ clicktracker_url: '${g.clicktracker_url}'
};
</%def>
View
55 scripts/click_redirect.py
@@ -1,55 +0,0 @@
-#!/usr/bin/python
-"""
-A simple raw WSGI app to take click-tracking requests, verify
-the hash to make sure they're valid, and redirect the client
-accordingly.
-"""
-
-import time
-import hashlib
-import urlparse
-from ConfigParser import RawConfigParser
-from wsgiref.handlers import format_date_time
-
-config = RawConfigParser()
-config.read(['production.ini'])
-tracking_secret = config.get('DEFAULT', 'tracking_secret')
-
-
-def click_redirect(environ, start_response):
- if environ['REQUEST_METHOD'] != 'GET':
- start_response('405 Method Not Allowed', [])
- return
-
- if environ.get('PATH_INFO') != '/click':
- start_response('404 Not Found', [])
- return
-
- query = environ.get('QUERY_STRING', '')
- params = urlparse.parse_qs(query)
-
- try:
- destination = params['url'][0]
- ip = environ['REMOTE_ADDR']
- except KeyError:
- start_response('400 Bad Request', [])
- return
-
- try:
- hash = params['hash'][0]
- fullname = params['id'][0]
- expected_hash_text = ''.join((ip, fullname, tracking_secret))
- expected_hash = hashlib.sha1(expected_hash_text).hexdigest()
- assert hash == expected_hash
- except (KeyError, AssertionError):
- start_response('403 Forbidden', [])
- return
-
- now = format_date_time(time.time())
- start_response('301 Moved Permanently', [
- ('Location', destination),
- ('Date', now),
- ('Expires', now),
- ('Cache-Control', 'no-cache'),
- ('Pragma', 'no-cache'),
- ])
View
65 scripts/tracker.py
@@ -0,0 +1,65 @@
+#!/usr/bin/python
+
+import time
+import hashlib
+from ConfigParser import RawConfigParser
+from wsgiref.handlers import format_date_time
+
+from flask import Flask, request, json, make_response, abort, redirect
+
+application = Flask(__name__)
+
+# fullname can include the sr name and a codename, leave room for those
+MAX_FULLNAME_LENGTH = 128
+
+def jsonpify(callback_name, data):
+ data = callback_name + '(' + json.dumps(data) + ')'
+ response = make_response(data)
+ response.mimetype = 'text/javascript'
+ return response
+
+config = RawConfigParser()
+config.read(['production.ini'])
+tracking_secret = config.get('DEFAULT', 'tracking_secret')
+adtracker_url = config.get('DEFAULT', 'adtracker_url')
+
+@application.route('/fetch-trackers')
+def fetch_trackers():
+ ip = request.environ['REMOTE_ADDR']
+ jsonp_callback = request.args['callback']
+ ids = request.args.getlist('ids[]')
+
+ if len(ids) > 32:
+ abort(400)
+
+ hashed = {}
+ for fullname in ids:
+ if len(fullname) > MAX_FULLNAME_LENGTH:
+ continue
+ text = ''.join((ip, fullname, tracking_secret))
+ hashed[fullname] = hashlib.sha1(text).hexdigest()
+ return jsonpify(jsonp_callback, hashed)
+
+@application.route('/click')
+def click_redirect():
+ ip = request.environ['REMOTE_ADDR']
+ destination = request.args['url']
+ fullname = request.args['id']
+ observed_hash = request.args['hash']
+
+ expected_hash_text = ''.join((ip, fullname, tracking_secret))
+ expected_hash = hashlib.sha1(expected_hash_text).hexdigest()
+
+ if expected_hash != observed_hash:
+ abort(403)
+
+ now = format_date_time(time.time())
+ response = redirect(destination)
+ response.headers['Cache-control'] = 'no-cache'
+ response.headers['Pragma'] = 'no-cache'
+ response.headers['Date'] = now
+ response.headers['Expires'] = now
+ return response
+
+if __name__ == "__main__":
+ application.run()
Please sign in to comment.
Something went wrong with that request. Please try again.