Sites, origins and "same site"
Why it matters Every privacy rule inside a browser starts with one question: "which site is this?" Cookies, partitions, SameSite, tracker lists and bounce detection all file things under a site. If an engine gets this answer wrong, a tracker can slip between two jars, or two strangers' blogs can end up sharing one. In this lesson you write the answer yourself.
Think of a block of flats. The street address (example) is shared by everyone, so it tells you nothing about who lives where. The flat number plus the street (shopa.example) is one household. Rooms inside the flat (cdn.shopa.example, www.shopa.example) all belong to that same household. A browser wants the household.
Three words you need
Host: the name part of a URL, such as cdn.shopa.example in https://cdn.shopa.example:8443/lib.js. It is made of labels separated by dots: cdn, shopa, example.
Origin: scheme, host and port together, such as https://cdn.shopa.example:8443. It is the strictest boundary: https://www.shopa.example and https://shopa.example are different origins.
Site: the part of the host one owner registered, also called the registrable domain: shopa.example for all of the hosts above. Privacy features such as partitioning and SameSite work on sites, because one owner controls every host under it anyway.
The public suffix list
"Take the last two labels" works for shopa.example, but fails for practice-shop.co.uk: the last two labels are co.uk, which thousands of unrelated companies share. So browsers carry a public suffix list, a long file of endings under which anyone can register a name.
Public suffix: an ending nobody owns as a whole, such as com, co.uk or github.io. The site is the public suffix plus one more label to its left.
The list also holds "private" suffixes added by hosting services. github.io is on it, so alice.github.io and bob.github.io are two different sites, even though one company runs the servers. Without that entry, Alice's blog could set a cookie that Bob's blog receives. In our practice world, pages.example plays the same role.
The rules, as the browser applies them:
- Lower-case the host, drop the port, and drop a trailing dot (
shopa.example.is a legal way of writingshopa.example). - Find the longest ending of the host that is on the list. For
a.b.practice-charity.org.uk, bothukandorg.ukare listed;org.ukwins. - The site is that ending plus one label to its left.
- If the host is a public suffix (such as plain
github.io), the site is the host itself. - If no listed ending matches at all, treat the last label as the suffix. So
dev.app.zzhas siteapp.zz.
Real lists also have a few wildcard and exception rules, and an IP address is its own site. Engines handle those too; you can leave them out here.
Schemeful same-site
Older browsers compared only the registrable domain, so http://shopa.example and https://shopa.example were "same site". Today's web standards define same-site as schemeful: the scheme is part of the site, so the plain-http page counts as a different, untrusted site. That matters because anyone on your café Wi-Fi can tamper with an http page, and it should not be able to borrow the https site's SameSite cookies.
| A | B | Same origin? | Same site? |
|---|---|---|---|
https://www.shopa.example | https://shopa.example | No | Yes |
https://alice.github.io | https://bob.github.io | No | No |
http://shopa.example | https://shopa.example | No | No (schemeful) |
https://shopa.example:8443 | https://shopa.example | No | Yes (ports do not count) |
The Python you need
Beyond variables, if, lists and functions, you need four small tools. Each is shown here with what it prints.
from urllib.parse import urlsplit # part of Python's standard library
parts = urlsplit("https://CDN.ShopA.example:8443/lib.js")
print(parts.scheme) # https
print(parts.hostname) # cdn.shopa.example (already lower case, port removed)
labels = "cdn.shopa.example".split(".") # cut a string at every dot
print(labels) # ['cdn', 'shopa', 'example']
print(labels[1:]) # ['shopa', 'example'] (a slice: from position 1 to the end)
print(".".join(labels[1:])) # shopa.example (glue a list back together with dots)
print("shopa.example.".rstrip(".")) # shopa.example (remove dots from the right end)
print("co.uk" in {"co.uk", "uk"}) # True (a set answers "is this in it?" quickly)
for i in range(3): # i takes the values 0, 1, 2
print(i)
A neat trick: if you try the endings from the longest (labels[0:]) to the shortest, the first one you find on the list is automatically the longest match.
Try it Before writing code, work three by hand: www.alice.pages.example, news.practice-paper.co.uk and zz. You should get alice.pages.example, practice-paper.co.uk and zz.
In other languages
Real projects use a maintained copy of the public suffix list rather than their own:
// JavaScript (the tldts package)
import { getDomain } from "tldts";
getDomain("https://cdn.shopa.co.uk/lib.js"); // "shopa.co.uk"
getDomain("alice.github.io", { allowPrivateDomains: true }); // "alice.github.io" (private suffixes are off by default)
new URL("https://CDN.ShopA.example:8443/").hostname; // "cdn.shopa.example"
// Go (golang.org/x/net/publicsuffix)
site, err := publicsuffix.EffectiveTLDPlusOne("alice.github.io") // "alice.github.io", nil
// Rust (the psl crate)
let site = psl::domain_str("www.shopa.co.uk"); // Some("shopa.co.uk")
// C# (built in: host only; use a public-suffix package such as Nager.PublicSuffix for the site)
var host = new Uri("https://CDN.ShopA.example:8443/").Host; // "cdn.shopa.example"A common misconception
"Same site means same origin, just looser wording." They are different boundaries. Origin is what JavaScript uses to keep pages apart (who may read whose data). Site is what privacy features use to decide what counts as one party (whose cookies travel, which jar an embed gets). www.shopa.example and shopa.example are different origins but one site.
Recap
- A site is the public suffix plus one label; the public suffix list says where suffixes end.
- The longest listed suffix wins, and private suffixes such as
github.iomake each user's sub-site its own site. - Schemeful same-site counts the scheme, so
httpandhttpsversions of a domain are different sites. - Origins keep pages apart; sites decide which party is which.
Write four functions, then use them on the 30 practice URLs.
host_from_url(url): the host ofurl, lower case, with no port and no trailing dot.site_for(url, suffixes): the site ofurl, using the setsuffixesas the public suffix list and the five rules from the lesson (longest listed suffix plus one label; a host that is itself a suffix is its own site; if nothing matches, the last label is the suffix).schemeful_site(url, suffixes): the scheme, then://, then the site, for example"https://shopa.example".is_same_site(a, b, suffixes):Truewhen the two URLs have the same schemeful site.
SITES to a list with the site of every URL in PRACTICE_URLS, in order, using PRACTICE_SUFFIXES. Write the logic yourself: do not use site_of or same_site from watched.
This lesson is locked
Lessons open one at a time. Finish the previous lesson to unlock this one.