Skip to content

Commit 61bca32

Browse files
jshasyphar
authored andcommitted
Use header for canonical URLs on rustdoc pages
Since the header on rustdoc pages is partly user-controlled, the user can inadvertantly break parsing such that Google doesn't see the `<link>` tag. Putting it in the header ensures it is reliably parsed.
1 parent 773de6f commit 61bca32

File tree

2 files changed

+40
-18
lines changed

2 files changed

+40
-18
lines changed

src/web/rustdoc.rs

Lines changed: 40 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,11 @@ use crate::{
1414
};
1515
use anyhow::{anyhow, Context};
1616
use iron::{
17-
modifiers::Redirect, status, url::percent_encoding::percent_decode, Handler, IronResult,
18-
Request, Response, Url,
17+
headers::{Link, LinkValue, RelationType},
18+
modifiers::Redirect,
19+
status,
20+
url::percent_encoding::percent_decode,
21+
Handler, IronResult, Request, Response, Url,
1922
};
2023
use lol_html::errors::RewritingError;
2124
use once_cell::sync::Lazy;
@@ -243,6 +246,8 @@ impl RustdocPage {
243246
.expect("missing Metrics from the request extensions");
244247

245248
let is_latest_url = self.is_latest_url;
249+
let canonical_url = self.canonical_url.clone();
250+
246251
// Build the page of documentation
247252
let ctx = ctry!(req, tera::Context::from_serialize(self));
248253
let config = extension!(req, Config);
@@ -264,6 +269,11 @@ impl RustdocPage {
264269

265270
let mut response = Response::with((Status::Ok, html));
266271
response.headers.set(ContentType::html());
272+
let link_value = LinkValue::new(canonical_url)
273+
.push_rel(RelationType::ExtRelType("canonical".to_string()));
274+
275+
response.headers.set(Link::new(vec![link_value]));
276+
267277
response.extensions.insert::<CachePolicy>(if is_latest_url {
268278
CachePolicy::ForeverInCdn
269279
} else {
@@ -2310,31 +2320,45 @@ mod test {
23102320
assert!(web
23112321
.get("/dummy-dash/0.1.0/dummy_dash/")
23122322
.send()?
2313-
.text()?
2323+
.headers()
2324+
.get("link")
2325+
.unwrap()
2326+
.to_str()
2327+
.unwrap()
23142328
.contains("rel=\"canonical\""),);
23152329

23162330
assert!(web
23172331
.get("/dummy-docs/0.1.0/dummy_docs/")
23182332
.send()?
2319-
.text()?
2320-
.contains(
2321-
"<link rel=\"canonical\" href=\"https://docs.rs/dummy-docs/latest/dummy_docs/\" />"
2322-
),);
2333+
.headers()
2334+
.get("link")
2335+
.unwrap()
2336+
.to_str()
2337+
.unwrap()
2338+
.contains("<https://docs.rs/dummy-docs/latest/dummy_docs/>; rel=\"canonical\""),);
23232339

2324-
assert!(
2325-
web
2326-
.get("/dummy-nodocs/0.1.0/dummy_nodocs/")
2327-
.send()?
2328-
.text()?
2329-
.contains("<link rel=\"canonical\" href=\"https://docs.rs/dummy-nodocs/latest/dummy_nodocs/\" />"),
2330-
);
2340+
assert!(web
2341+
.get("/dummy-nodocs/0.1.0/dummy_nodocs/")
2342+
.send()?
2343+
.headers()
2344+
.get("link")
2345+
.unwrap()
2346+
.to_str()
2347+
.unwrap()
2348+
.contains(
2349+
"<https://docs.rs/dummy-nodocs/latest/dummy_nodocs/>; rel=\"canonical\""
2350+
),);
23312351

23322352
assert!(
23332353
web
23342354
.get("/dummy-nodocs/0.1.0/dummy_nodocs/struct.Foo.html")
23352355
.send()?
2336-
.text()?
2337-
.contains("<link rel=\"canonical\" href=\"https://docs.rs/dummy-nodocs/latest/dummy_nodocs/struct.Foo.html\" />"),
2356+
.headers()
2357+
.get("link")
2358+
.unwrap()
2359+
.to_str()
2360+
.unwrap()
2361+
.contains("<https://docs.rs/dummy-nodocs/latest/dummy_nodocs/struct.Foo.html>; rel=\"canonical\""),
23382362
);
23392363
Ok(())
23402364
})

templates/rustdoc/head.html

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,4 @@
33

44
<link rel="search" href="/-/static/opensearch.xml" type="application/opensearchdescription+xml" title="Docs.rs" />
55

6-
<link rel="canonical" href="{{canonical_url | safe}}" />
7-
86
<script type="text/javascript">{%- include "theme.js" -%}</script>

0 commit comments

Comments
 (0)