152 lines
5.1 KiB
Python
152 lines
5.1 KiB
Python
import tornado.ioloop
|
|
import tornado.web
|
|
import tornado.template
|
|
from urlparse import urlparse
|
|
import redis
|
|
import datetime
|
|
import string
|
|
import random
|
|
import time
|
|
|
|
|
|
|
|
#Admin Handler
|
|
class HashHandler(tornado.web.RedirectHandler):
|
|
def initialize(self, cache, admin_token):
|
|
self.cache = cache
|
|
self.admin_token = admin_token
|
|
self.life_of_hash = 60*60*6
|
|
|
|
def get(self):
|
|
#simple admin check. Best to move this endpoint to an internal IP for security on production
|
|
if self.get_argument("admin_token") != self.admin_token:
|
|
self.write({"error":1,"errorMsg":"Are you the admin?"})
|
|
return 1
|
|
|
|
#get the number of hashes they want. Default to 10.
|
|
num = int(self.get_argument("num",10))
|
|
if num > 1000000000000:
|
|
self.write({"error":1,"errorMsg":"No more than 100 at a time, please."})
|
|
return 1
|
|
|
|
|
|
expiration = int(time.mktime(time.gmtime())) + self.life_of_hash
|
|
tokens = []
|
|
for i in range(num):
|
|
#create token and add it to the database
|
|
token = self.generateRandomToken()
|
|
self.cache.zadd("linkshim:hashes", expiration, token)
|
|
tokens.append(token)
|
|
|
|
self.write({"error":0,"tokens": tokens,"expiration":expiration})
|
|
return 1
|
|
|
|
|
|
def generateRandomToken(self, size=25, chars=string.ascii_uppercase + string.ascii_lowercase + string.digits):
|
|
return "".join(random.choice(chars) for x in range(size))
|
|
|
|
|
|
|
|
|
|
#Handle requests to redirect
|
|
class RedirectHandler(tornado.web.RequestHandler):
|
|
def initialize(self, cache, templates_dir):
|
|
self.cache = cache
|
|
self.loader = tornado.template.Loader(templates_dir)
|
|
|
|
def get(self):
|
|
href = self.get_argument("href")
|
|
|
|
current_timestamp = int(time.mktime(time.gmtime()))
|
|
|
|
#add the url to today's set
|
|
today = datetime.date.today().strftime("%Y-%m-%d")
|
|
self.cache.zincrby("linkshim:outbound:" + today , href, 1)
|
|
|
|
#check domain for watch_list
|
|
domain = self.getDomain(href)
|
|
ismember = self.cache.sismember("linkshim:watchlist",domain)
|
|
|
|
#if it is in the watchlist, lets totally block that domain by displaying watchlist.html
|
|
if ismember:
|
|
self.writeConfirmationMessage("watchlist.html",href)
|
|
return 1
|
|
|
|
#get expiration of the hash provided
|
|
h = str(self.get_argument("h"))
|
|
expiration = self.cache.zscore("linkshim:hashes", h)
|
|
|
|
#if hash doesn't exist, or its expired, display the warning message
|
|
if expiration == None or expiration < time.mktime(time.gmtime()):
|
|
self.writeConfirmationMessage("warning.html", href)
|
|
return 1
|
|
|
|
#we're all good! [domain not marked as spam, and hash is valid]
|
|
self.smartRedirect(href)
|
|
return 1
|
|
|
|
#write a simple HTML page to stop user from being linked, or to warning them (you edit the HTML in watchlist.html or warning.html)
|
|
def writeConfirmationMessage(self, file, href):
|
|
html = self.loader.load(file).generate(href=href)
|
|
self.write(html)
|
|
return 1
|
|
|
|
|
|
#using this rather than a simple header redirect makes the referrer from this page, to protect privacy of the user
|
|
def smartRedirect(self, href):
|
|
#we set a refresh header just to be safe, but hopefully we won't use it.
|
|
self.set_header("Refresh","1")
|
|
self.set_header("URL", href)
|
|
|
|
#IE requires a special solution.
|
|
if self.isIE():
|
|
self.write("<a hef='" + href + "' id='a'></a><script>document.getElementById('a').click();</script>")
|
|
else:
|
|
self.write("<script>document.location.replace('" + href + "');</script>")
|
|
|
|
return 1
|
|
|
|
|
|
#IE sucks. Let's use a helper function to detect it
|
|
def isIE(self):
|
|
if "User-Agent" not in self.request.headers:
|
|
return False
|
|
|
|
user_agent = self.request.headers["User-Agent"]
|
|
if user_agent.find("MSIE") != -1:
|
|
return True
|
|
|
|
return False
|
|
|
|
#get raw domain name, stripped of subdomains and ports
|
|
def getDomain(self, href):
|
|
url_parts = urlparse(href)
|
|
domain = '.'.join(url_parts.netloc.split('.')[-2:])
|
|
index = domain.find(":")
|
|
if index != -1:
|
|
domain = domain[:index]
|
|
|
|
return domain
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
#connect to redis!
|
|
redis_cache = redis.StrictRedis(host='localhost', port=6379, db=0)
|
|
|
|
#change to random string. Strong authentication with IP checks, etc. should be required on production scripts.
|
|
admin_token = "JTCyFFO7OMWRxlnLCp6gp4fcJaLj2234tv3U0AabE7iQ"
|
|
listen_on_port = 8888
|
|
|
|
#absolute path that our templates are located in
|
|
templates_dir = "/var/www/linkshim/templates"
|
|
|
|
application = tornado.web.Application([
|
|
(r"/r", RedirectHandler, dict(cache=redis_cache, templates_dir=templates_dir)),
|
|
(r"/hash", HashHandler, dict(cache=redis_cache,admin_token=admin_token)),
|
|
])
|
|
application.listen(listen_on_port)
|
|
tornado.ioloop.IOLoop.instance().start()
|