URLs and link cleaning
Why it matters When cookies are blocked or partitioned, trackers carry your ID in the link itself, and every time you copy and share such a link you pass along the click ID with it. A link cleaner removes that decoration on every navigation without breaking the page. Browsers and blockers ship one; here you write yours, and the blocker module reuses the idea.
Think of a parcel label. The address is what the courier needs. Stickers added by everyone who handled it along the way ("came via the newsletter", "customer number 7f3a") are not. Cleaning a link means peeling off the stickers while leaving the address exactly as it was.
URL: a web address, in parts: https://shopa.example:443/p/tent?colour=green#reviews has a scheme (https), a host (shopa.example), a port (443), a path (/p/tent), a query (colour=green) and a fragment (reviews, which stays in the browser and is not sent to the server).
Query parameter: one name=value piece of the query, separated from the others by &.
Link decoration: parameters added to a link to carry an ID or campaign label from one site to another, such as utm_source, fbclid or gclid.
Two jobs: normalise, then strip
Normalise means writing the same address the same way every time. Scheme and host are not case sensitive, so HTTPS://SHOPA.Example becomes https://shopa.example. Port 443 is the default for HTTPS (80 for HTTP), so writing it adds nothing. An empty path is /. The path itself is case sensitive on many servers, so leave it alone.
Strip means removing tracking parameters only. STRIP_PARAMS in watched.leg2c_http lists the click-ID and campaign names (from ad platforms, email tools and social sites), and every name starting with utm_ goes too. Compare names in lower case and after decoding (utm%5Fsource is utm_source in disguise). Everything else stays exactly as written: q=red+tent must not become q=red%20tent, and order matters to some sites.
Percent-encoding: writing a character as % plus two hex digits, so it can travel safely in a URL: %5F is _, %26 is &. urllib.parse.unquote turns it back.
from urllib.parse import urlsplit
parts = urlsplit("https://Shop.Example:443/p?utm_source=x&id=5#top")
parts.scheme, parts.hostname, parts.port, parts.path, parts.query, parts.fragment
# ('https', 'shop.example', 443, '/p', 'utm_source=x&id=5', 'top')Why not rebuild the query with urlencode? Because it re-encodes values and can quietly change them. Splitting the raw query on & and keeping the untouched pieces is safer. A nested address such as ?to=https%3A%2F%2F...%3Futm_source%3Dx is one encoded value of a normal parameter; leave it alone here (unwrapping bounce links is a job for the next lesson and the blocker).
A common misconception
"Strip every parameter to be safe." That breaks search results (q=), page numbers (page=2) and, worst of all, password-reset and login links (token=). One of the 40 graded cases is exactly that reset link. Cleaners in browsers use a list for the same reason (checked October 2026).
Try it Copy a link from a newsletter email or a social media post into a text editor. Can you spot utm_ parameters or a long click ID? Remove them, open the shorter link, and check the page still works.
In other languages
JavaScript:
function cleanLink(link) {
const u = new URL(link); // lower-cases host, drops default port
if (!["http:", "https:"].includes(u.protocol)) return link;
const kept = u.search.slice(1).split("&").filter(piece => {
const name = decodeURIComponent(piece.split("=")[0]).toLowerCase();
return piece && !name.startsWith("utm_") && !STRIP.has(name);
});
u.search = kept.length ? "?" + kept.join("&") : "";
return u.toString();
}C#:
var u = new Uri(link);
var kept = u.Query.TrimStart('?').Split('&')
.Where(p => p != "" && !IsTracking(Uri.UnescapeDataString(p.Split('=')[0])));
var b = new UriBuilder(u) { Query = string.Join("&", kept) };Recap
- Normalise the parts that are not case sensitive (scheme, host), drop default ports, and turn an empty path into
/. - Strip only known tracking parameters, compared in lower case after decoding; keep every other piece exactly as written and in order.
- Keep the fragment, leave non-web links alone, and never strip tokens a page needs.
Write is_tracking_param(name): True when the name, percent-decoded with unquote and lower-cased, starts with utm_ or is in STRIP_PARAMS. Then write clean_link(url): leave non-http/https links unchanged; lower-case scheme and host; drop a default port; turn an empty path into /; split the query on &, drop empty pieces and tracking parameters, keep the rest exactly as written and in order; no ? when nothing is left; keep the #fragment. It is graded on the 40 cases in LINK_CASES plus random links.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.