Specification notes#
This library implements two specifications that mostly agree:
- WebSub, a W3C Recommendation, the default
- PubSubHubbub 0.4,
its predecessor, behind
WithMode(websub.ModePuSH04)
The public API is identical either way. Every difference is a field in the
table in mode.go and is looked up rather than branched on at a call site,
so the deltas stay auditable against the spec text in one place.
Where they differ#
| Behavior | WebSub | PubSubHubbub 0.4 |
|---|---|---|
| Lease duration | Required. Hubs must enforce expiration and must not issue perpetual leases | Optional. An omitted lease means the subscription never expires |
| Discovery | Link header, then HTML <link>, then Atom, then RSS, as a required ordered chain |
Link header primarily, the rest best effort |
| Verification | Asynchronous GET challenge, always | Same mechanism, but synchronous verification is tolerated in some deployments |
Two of those have visible consequences:
- A hub in
ModeWebSubrefuseshub.lease_seconds=0with a 400. The same request inModePuSH04is accepted as a permanent subscription. - Discovery in
ModeWebSubsniffs a document whose media type is missing or unrecognized, because the ordered chain is required.ModePuSH04only parses what the response declares.
Where the specs are silent#
Places the text does not settle, and what this library does.
Publisher to hub notification#
The specific mechanism for the publisher to inform the hub is left unspecified. For example, some existing public hubs ask publishers to send a POST request with the keys
hub.mode="publish"andhub.url=(the URL of the resource that was updated).
Publisher.Publish sends exactly that, plus hub.topic with the same
value, because a number of hubs read that name instead. Sending both costs
one form field.
For content posted directly rather than pinged, the topic travels in a
rel=self Link header, which is how a hub identifies content everywhere
else in the protocol.
Response to invalid signed content#
The spec says a subscriber must ignore content whose signature does not match, and separately that a callback must return 2xx to indicate success. It does not say what to return for content you are ignoring.
This library returns 2xx and discards. See design decisions for why.
Which hub to use when several are advertised#
If more than one hub URL is specified, it is expected that the publisher notifies each hub, so the subscriber may subscribe to one or more of them.
Discovery.Hubs returns all of them in the order found, and
Discovery.Hub() returns the first. Subscriber.Subscribe uses the first.
Subscribing at several for redundancy is a caller decision, since it means
handling duplicate deliveries.
Publisher.Publish notifies every configured hub, and one failing does not
stop the others.
Requirements worth calling out#
These are easy to miss and are each covered by a conformance case.
hub.topicmust be theselfURL found during discovery, which is frequently not the URL discovery started from. A subscriber that echoes back the URL it fetched will silently subscribe to the wrong topic on any site that redirects or canonicalizes.- The verification challenge must be echoed with a safe media type. It
is attacker influenced text reflected into a response, so a callback that
serves it as HTML is a reflected XSS. This library sends
text/plain; charset=utf-8andX-Content-Type-Options: nosniff. - A subscriber must answer 404 to a verification request for a topic, mode, or callback it does not recognize. Answering 2xx confirms a subscription it never asked for.
- A hub must let subscribers re-subscribe to an active subscription, and each request overrides the previous state rather than duplicating or failing.
- Content distribution must carry both
rel=hubandrel=selfLink headers, and the content type of the topic rather than one the hub chose. - The secret is capped at 200 bytes and should only be sent over an HTTPS callback. This library enforces the cap and logs a warning for the callback, since the spec says should rather than must.
Signature algorithms#
sha1, sha256, sha384, and sha512 are all accepted on verification.
Hubs sign with sha256 unless told otherwise. sha1 exists for older
subscribers and is never chosen by default.
The header format is method=signature, with the signature in lowercase
hex. Verification is constant time, and the parsing is fuzzed, because a
forged X-Hub-Signature accepted through a parsing bug is the worst failure
this library can have.