app.py (120 lines of code) (raw):

import json import re from urllib.parse import ParseResult, urlencode, urlparse, urlunparse from flask import Flask, abort, make_response, redirect, request REDIRECT_MAP = {} def load_redirect_map(path="foundation.mozilla.org_wagtail_redirects.json"): """ Load the foundation.mozilla.org redirects into memory. Not necessary to offload to some external resource like Redis yet. """ global REDIRECT_MAP try: with open(path) as f: REDIRECT_MAP = json.load(f) except FileNotFoundError: REDIRECT_MAP = {} load_redirect_map() def get_keyvalue_redirect(path, query_string, redirect_map, debug=False): """ Checks the in-memory key/value redirect map and returns a redirect response if a match is found. Returns None otherwise. """ full_path = "/" + path # Normalize: try as-is and strip trailing slash candidates = [full_path] if full_path.endswith("/"): candidates.append(full_path.rstrip("/")) else: candidates.append(full_path + "/") # Also try full_path + query if query_string: candidates = [f"{p}?{query_string}" for p in candidates] + candidates # Try each candidate for candidate in candidates: redirect_entry = redirect_map.get(candidate) if redirect_entry: redirect_url = redirect_entry["redirect_to"] # If candidate didn't include query string, but the request did, add it if "?" not in candidate and query_string: separator = "&" if "?" in redirect_url else "?" redirect_url = f"{redirect_url}{separator}{query_string}" status_code = 301 if redirect_entry.get("is_permanent") else 302 if debug: print(f"[kv redirect] {candidate} → {redirect_url} ({status_code})") return redirect(redirect_url, code=status_code) return None def create_app(test_config=None): app = Flask(__name__, static_folder=None) if test_config is None: app.config.from_object("config.Config") else: app.config.update(test_config) redirect_rules = app.config["REDIRECT_RULES"] force_ssl = app.config["FORCE_SSL"] debug = app.config["DEBUG"] @app.before_request def enforce_ssl(): if not force_ssl: return None proto = request.headers.get("X-Forwarded-Proto", None) if proto == "https": return None url = request.url.replace("http://", "https://", 1) return redirect(url, code=301) @app.route("/robots.txt") def send_robots_txt(): response = make_response("User-agent: *\n") response.headers["Content-Type"] = "text/plain; charset=utf-8" return response def handle_donate_mozilla_org(path): """ Strips language codes from the URL path, redirecting to '/donate/' or its approved subpaths if specified. """ # Regex pattern to identify language codes (EX: /en-US/, /fr/) language_code_regex = re.compile(r"^[a-z]{2}(-[A-Z]{2}|-[A-Z][a-z])?/?$") # The default donate path donate_path = "/donate/" # Donate subpaths that exist on foundation.mozilla.org donate_subpaths = ["faq", "help", "ways-to-give"] if path: # Strip language codes from the path and reconstruct it, path_segments = path.strip("/").split("/") filtered_segments = [segment for segment in path_segments if not language_code_regex.match(segment)] cleaned_path = "/".join(filtered_segments) if cleaned_path in donate_subpaths: donate_path += cleaned_path return donate_path @app.route("/", defaults={"path": ""}) @app.route("/<path:path>") def redirector(path): x_forwarded_host = request.headers.get("X-Forwarded-Host", None) if x_forwarded_host: host = x_forwarded_host else: host = request.headers.get("Host", None) if debug: print("received request from {}".format(host)) # Special handling for donate.mozilla.org requests if "donate.mozilla.org" in host: path = handle_donate_mozilla_org(path) # Prevent redirect for resources such as JS, CSS and images and return HTTP 410 Gone if path.endswith((".js", ".css", ".png", ".svg", ".ico", ".txt")): return abort(410) # Use key/value redirects to short-circuit foundation.mozilla.org's redirect rule only # Note foundation.mozilla.org for testing until domain switch is live. if "foundation.mozilla.org" in host: keyvalue_response = get_keyvalue_redirect(path, request.query_string.decode("utf-8"), REDIRECT_MAP, debug) if keyvalue_response: return keyvalue_response if host in redirect_rules: redirect_target, redirect_code, preserves = redirect_rules[host] preserve_path, preserve_query = preserves redirect_path = "" redirect_query = "" target_url = urlparse(redirect_target) if preserve_path: redirect_path = path else: redirect_path = target_url.path if preserve_query: redirect_query = urlencode(request.args, doseq=True) else: redirect_query = target_url.query redirect_parse = ParseResult( scheme=target_url.scheme, netloc=target_url.netloc, path=redirect_path, query=redirect_query, params="", fragment="", ) final_redirect = urlunparse(redirect_parse) if debug: print("redirecting to {} with a {}".format(final_redirect, redirect_code.value)) return redirect(final_redirect, code=redirect_code.value) return abort(400) @app.after_request def response_headers(response): response.headers["Server"] = "MoFo Redirector" return response return app if __name__ == "__main__": create_app().run()