from jinja2 import Environment, FileSystemLoader, environmentfilter from flask import Flask, request, session, g, redirect, url_for, abort, render_template, flash, send_from_directory, safe_join from flaskext.babel import Babel from werkzeug.routing import BaseConverter from docutils.core import publish_parts import os.path import os import fileinput from random import randint try: import json except ImportError: import simplejson as json from helpers import LazyView, Pagination CURRENT_I2P_VERSION = '0.9.4' CANONICAL_DOMAIN = 'www.i2p2.de' TEMPLATE_DIR = os.path.join(os.path.dirname(__file__), 'pages') STATIC_DIR = os.path.join(os.path.dirname(__file__), 'static') BLOG_DIR = os.path.join(os.path.dirname(__file__), 'blog') MEETINGS_DIR = os.path.join(os.path.dirname(__file__), 'meetings/logs') BLOG_ENTRIES_PER_PAGE = 20 MEETINGS_PER_PAGE = 20 MIRRORS_FILE = os.path.join(TEMPLATE_DIR, 'downloads/mirrors') app = application = Flask('i2p2www', template_folder=TEMPLATE_DIR, static_url_path='/_static', static_folder=STATIC_DIR) app.debug = bool(os.environ.get('APP_DEBUG', 'False')) babel = Babel(app) ####################### # Custom URL converters class LangConverter(BaseConverter): def __init__(self, url_map): super(LangConverter, self).__init__(url_map) self.regex = '(?:[a-z]{2})' def to_python(self, value): return value def to_url(self, value): return value app.url_map.converters['lang'] = LangConverter ###### # URLs def url(url_rule, import_name, **options): view = LazyView('i2p2www.' + import_name) app.add_url_rule(url_rule, view_func=view, **options) url('/', 'views.main_index') url('//', 'views.site_show', defaults={'page': 'index'}) url('//', 'views.site_show') url('//blog/', 'blog.views.blog_index', defaults={'page': 1}) url('//blog/page/', 'blog.views.blog_index') url('//blog/entry/', 'blog.views.blog_entry') url('//feed/blog/rss', 'blog.views.blog_rss') url('//feed/blog/atom', 'blog.views.blog_atom') url('//meetings/', 'meetings.views.meetings_index', defaults={'page': 1}) url('//meetings/page/', 'meetings.views.meetings_index') url('//meetings/', 'meetings.views.meetings_show') url('//meetings/.log', 'meetings.views.meetings_show_log') url('//meetings/.rst', 'meetings.views.meetings_show_rst') url('//feed/meetings/atom', 'meetings.views.meetings_atom') url('/meeting', 'legacy.legacy_meeting') url('/meeting.html', 'legacy.legacy_meeting') url('/status---', 'legacy.legacy_status') url('/status---.html', 'legacy.legacy_status') url('/_', 'legacy.legacy_show') url('/_.html', 'legacy.legacy_show') url('//', 'legacy.legacy_show') url('/.html', 'legacy.legacy_show') ################# # Babel selectors @babel.localeselector def get_locale(): # If the language is already set from the url, use that if hasattr(g, 'lang'): return g.lang # otherwise try to guess the language from the user accept # header the browser transmits. The best match wins. return request.accept_languages.best_match(['en', 'es', 'zh', 'de', 'fr', 'it', 'nl', 'ru', 'sv', 'cs', 'ar']) ########################## # Hooks - helper functions def after_this_request(f): if not hasattr(g, 'after_request_callbacks'): g.after_request_callbacks = [] g.after_request_callbacks.append(f) return f ########################### # Hooks - url preprocessing @app.url_value_preprocessor def pull_lang(endpoint, values): if not values: return g.lang=values.pop('lang', None) @app.url_defaults def set_lang(endpoint, values): if not values: return if endpoint == 'static': # Static urls shouldn't have a lang flag # (causes complete reload on lang change) return if 'lang' in values: return if hasattr(g, 'lang'): values['lang'] = g.lang ######################## # Hooks - before request # Detect and store chosen theme @app.before_request def detect_theme(): theme = 'duck' if 'style' in request.cookies: theme = request.cookies['style'] if 'theme' in request.args.keys(): theme = request.args['theme'] if not os.path.isfile(safe_join(safe_join(STATIC_DIR, 'styles'), '%s.css' % theme)): theme = 'duck' g.theme = theme @after_this_request def remember_theme(resp): if g.theme == 'duck' and 'style' in request.cookies: resp.delete_cookie('style') elif g.theme != 'duck': resp.set_cookie('style', g.theme) return resp ####################### # Hooks - after request @app.after_request def call_after_request_callbacks(response): for callback in getattr(g, 'after_request_callbacks', ()): response = callback(response) return response ################## # Template filters @app.template_filter('restructuredtext') def restructuredtext(value): parts = publish_parts(source=value, writer_name="html") return parts['html_body'] #################### # Context processors @app.context_processor def utility_processor(): # Shorthand for getting a site url def get_site_url(path=None): lang = 'en' if hasattr(g, 'lang') and g.lang: lang = g.lang if path: return url_for('site_show', lang=lang, page=path) else: return url_for('site_show', lang=lang) # Provide the canonical link to the current page def get_canonical_link(): protocol = request.url.split('//')[0] return protocol + '//' + CANONICAL_DOMAIN + request.path # Convert an I2P url to an equivalent clearnet one i2ptoclear = { 'www.i2p2.i2p': 'www.i2p2.de', #'forum.i2p': 'forum.i2p2.de', 'trac.i2p2.i2p': 'trac.i2p2.de', 'mail.i2p': 'i2pmail.org', } def convert_url_to_clearnet(value): if not value.endswith('.i2p'): # The url being passed in isn't an I2P url, so just return it return value if request.headers.get('X-I2P-Desthash') and not request.headers.get('X-Forwarded-Server'): # The request is from within I2P, so use I2P url return value # The request is either directly from clearnet or through an inproxy try: # Return the known clearnet url corresponding to the I2P url return i2ptoclear[value] except KeyError: # The I2P site has no known clearnet address, so use an inproxy return value + '.to' # Convert a paginated URL to that of another page def url_for_other_page(page): args = request.view_args.copy() args['page'] = page return url_for(request.endpoint, **args) # Change the theme of the current page def change_theme(theme): args = request.view_args.copy() args['theme'] = theme return url_for(request.endpoint, **args) return dict(i2pconv=convert_url_to_clearnet, url_for_other_page=url_for_other_page, change_theme=change_theme, site_url=get_site_url, canonical=get_canonical_link) ################ # Error handlers @app.errorhandler(404) def page_not_found(error): return render_template('global/error_404.html'), 404 @app.errorhandler(500) def server_error(error): return render_template('global/error_500.html'), 500 ################### # Download handlers # Read in mirrors from file def read_mirrors(): file = open(MIRRORS_FILE, 'r') dat = file.read() file.close() lines=dat.split('\n') ret={} for line in lines: try: obj=json.loads(line) except ValueError: continue if 'protocol' not in obj: continue protocol=obj['protocol'] if protocol not in ret: ret[protocol]=[] ret[protocol].append(obj) return ret # List of downloads @app.route('//download') def downloads_list(): # TODO: read mirror list or list of available files return render_template('downloads/list.html') # Specific file downloader @app.route('//download/') def downloads_select(file): if (file == 'debian'): return render_template('downloads/debian.html') mirrors=read_mirrors() data = { 'version': CURRENT_I2P_VERSION, 'file': file, } obj=[] for protocol in mirrors.keys(): a={} a['name']=protocol a['mirrors']=mirrors[protocol] for mirror in a['mirrors']: mirror['url']=mirror['url'] % data obj.append(a) return render_template('downloads/select.html', mirrors=obj, file=file) @app.route('/download//any/', defaults={'mirror': None}) @app.route('/download///') def downloads_redirect(protocol, file, mirror): mirrors=read_mirrors() if not protocol in mirrors: abort(404) mirrors=mirrors[protocol] data = { 'version': CURRENT_I2P_VERSION, 'file': file, } if mirror: return redirect(mirrors[mirror]['url'] % data) return redirect(mirrors[randint(0, len(mirrors) - 1)]['url'] % data) ############ # Root files @app.route('/hosts.txt') def hosts(): return send_from_directory(STATIC_DIR, 'hosts.txt', mimetype='text/plain') @app.route('/robots.txt') def robots(): return send_from_directory(STATIC_DIR, 'robots.txt', mimetype='text/plain') @app.route('/favicon.ico') def favicon(): return send_from_directory(os.path.join(app.root_path, 'static'), 'favicon.ico', mimetype='image/vnd.microsoft.icon') if __name__ == '__main__': app.run(debug=True)