Skip to content

Commit 36e0ad8

Browse files
committed
Use a different path for static files
1 parent 1d27020 commit 36e0ad8

File tree

5 files changed

+120
-12
lines changed

5 files changed

+120
-12
lines changed

src/docbuilder/rustwide_builder.rs

+4-2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ use crate::utils::{
1313
};
1414
use crate::{db::blacklist::is_blacklisted, utils::MetadataPackage};
1515
use crate::{Config, Context, Index, Metrics, Storage};
16+
use crate::RUSTDOC_STATIC_STORAGE_PREFIX;
1617
use anyhow::{anyhow, bail, Error};
1718
use docsrs_metadata::{Metadata, DEFAULT_TARGETS, HOST_TARGET};
1819
use failure::Error as FailureError;
@@ -225,7 +226,8 @@ impl RustwideBuilder {
225226
.prefix("essential-files")
226227
.tempdir()?;
227228
copy_dir_all(source, &dest)?;
228-
add_path_into_database(&self.storage, "", &dest)?;
229+
230+
add_path_into_database(&self.storage, RUSTDOC_STATIC_STORAGE_PREFIX, &dest)?;
229231

230232
set_config(
231233
&mut conn,
@@ -710,7 +712,7 @@ impl RustwideBuilder {
710712

711713
#[rustfmt::skip]
712714
const UNCONDITIONAL_ARGS: &[&str] = &[
713-
"--static-root-path", "/",
715+
"--static-root-path", "/-/rustdoc.static/",
714716
"--cap-lints", "warn",
715717
"--disable-per-crate-search",
716718
"--extern-html-root-takes-precedence",

src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -55,3 +55,6 @@ pub const BUILD_VERSION: &str = concat!(
5555
" ",
5656
include_str!(concat!(env!("OUT_DIR"), "/git_version"))
5757
);
58+
59+
/// Where rustdoc's static files are stored in S3.
60+
pub const RUSTDOC_STATIC_STORAGE_PREFIX: &str = "/rustdoc-static/";

src/web/mod.rs

+9-7
Original file line numberDiff line numberDiff line change
@@ -138,8 +138,10 @@ impl MainHandler {
138138
let inject_extensions = InjectExtensions::new(context, template_data)?;
139139

140140
let routes = routes::build_routes();
141-
let shared_resources =
142-
Self::chain(inject_extensions.clone(), rustdoc::SharedResourceHandler);
141+
let shared_resources = Self::chain(
142+
inject_extensions.clone(),
143+
rustdoc::LegacySharedResourceHandler,
144+
);
143145
let router_chain = Self::chain(inject_extensions.clone(), routes.iron_router());
144146

145147
Ok(MainHandler {
@@ -174,13 +176,13 @@ impl Handler for MainHandler {
174176
// removes the shared static files from individual builds to save space, the "../mainXXX.js"
175177
// path doesn't really exist in storage for any particular build. The appropriate main file
176178
// *does* exist in the storage root, which is where the shared static files are put for each
177-
// new rustdoc version. So here we give SharedResourceHandler a chance to handle all URLs
178-
// before they go to the router. SharedResourceHandler looks at the last component of the
179+
// new rustdoc version. So here we give LegacySharedResourceHandler a chance to handle all URLs
180+
// before they go to the router. LegacySharedResourceHandler looks at the last component of the
179181
// request path ("main-20181217-1.33.0-nightly-adbfec229.js") and tries to fetch it from
180182
// the storage root (if it's JS, CSS, etc). If the file doesn't exist, we fall through to
181183
// the normal router, which may wind up serving an invocation-specific file from the crate
182184
// itself. For instance, a request for "/crate/foo/search-index-XYZ.js" will get a 404 from
183-
// the SharedResourceHandler because "search-index-XYZ.js" doesn't exist in the storage root
185+
// the LegacySharedResourceHandler because "search-index-XYZ.js" doesn't exist in the storage root
184186
// (it's not a shared static file), but it will get a 200 from rustdoc_html_server_handler
185187
// because it exists in a specific crate.
186188
//
@@ -192,8 +194,8 @@ impl Handler for MainHandler {
192194
// https://docs.rs/this.path.does.not.exist/main-20181217-1.33.0-nightly-adbfec229.js
193195
//
194196
// If those 2018 crates get rebuilt, we won't have this problem anymore, and
195-
// SharedResourceHandler can receive dispatch from the router, as other handlers do. That
196-
// will also allow SharedResourceHandler to look up full paths in storage rather than just
197+
// LegacySharedResourceHandler can receive dispatch from the router, as other handlers do. That
198+
// will also allow LegacySharedResourceHandler to look up full paths in storage rather than just
197199
// the last component of the requested path.
198200
//
199201
// Also note: this approach means that a request for a given JS/CSS may serve from two

src/web/routes.rs

+42
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ pub(super) fn build_routes() -> Routes {
3131

3232
routes.static_resource("/-/static/:single", super::statics::static_handler);
3333
routes.static_resource("/-/static/*", super::statics::static_handler);
34+
routes.internal_page(
35+
"/-/rustdoc.static/:single",
36+
super::rustdoc::static_asset_handler,
37+
);
38+
routes.internal_page("/-/rustdoc.static/*", super::rustdoc::static_asset_handler);
3439
routes.internal_page("/-/storage-change-detection.html", {
3540
#[derive(Debug, serde::Serialize)]
3641
struct StorageChangeDetection {}
@@ -377,4 +382,41 @@ mod tests {
377382
Ok(())
378383
});
379384
}
385+
386+
#[test]
387+
fn serve_rustdoc_content_not_found() {
388+
wrapper(|env| {
389+
let response = env.frontend().get("/-/rustdoc-static/style.css").send()?;
390+
assert_eq!(response.status(), StatusCode::NOT_FOUND);
391+
assert_cache_control(&response, CachePolicy::NoCaching, &env.config());
392+
Ok(())
393+
})
394+
}
395+
396+
#[test]
397+
fn serve_rustdoc_content() {
398+
wrapper(|env| {
399+
let web = env.frontend();
400+
env.storage().store_one("style.css", *b"content")?;
401+
env.storage()
402+
.store_one("will_not/be_found.css", *b"something")?;
403+
404+
let response = web.get("/-/rustdoc-static/style.css").send()?;
405+
assert_cache_control(
406+
&response,
407+
CachePolicy::ForeverInCdnAndBrowser,
408+
&env.config(),
409+
);
410+
assert!(response.status().is_success());
411+
assert_eq!(response.text()?, "content");
412+
413+
assert_eq!(
414+
web.get("/-/rustdoc-static/will_not/be_found.css")
415+
.send()?
416+
.status(),
417+
StatusCode::NOT_FOUND
418+
);
419+
Ok(())
420+
})
421+
}
380422
}

src/web/rustdoc.rs

+62-3
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
//! rustdoc handler
22
33
use crate::{
4+
RUSTDOC_STATIC_STORAGE_PREFIX,
45
db::Pool,
56
repositories::RepositoryStatsUpdater,
6-
storage::rustdoc_archive_path,
7+
storage::{rustdoc_archive_path, PathNotFoundError},
78
utils,
89
web::{
910
cache::CachePolicy, crate_details::CrateDetails, csp::Csp, error::Nope, file::File,
@@ -766,14 +767,49 @@ pub fn download_handler(req: &mut Request) -> IronResult<Response> {
766767
)))
767768
}
768769

770+
/// Serves shared resources used by rustdoc-generated documentation.
771+
///
772+
/// This serves files from S3, and is pointed to by the `--static-root-path` flag to rustdoc.
773+
pub fn static_asset_handler(req: &mut Request) -> IronResult<Response> {
774+
let storage = extension!(req, Storage);
775+
let config = extension!(req, Config);
776+
777+
let filename = req.url.path()[2..].join("/");
778+
let storage_path = format!("{}{}", RUSTDOC_STATIC_STORAGE_PREFIX, filename);
779+
780+
// Prevent accessing static files outside the root. This could happen if the path
781+
// contains `/` or `..`. The check doesn't outright prevent those strings to be present
782+
// to allow accessing files in subdirectories.
783+
let canonical_path = std::fs::canonicalize(&storage_path).map_err(|_| Nope::ResourceNotFound)?;
784+
let canonical_root = std::fs::canonicalize(&storage_path).map_err(|_| Nope::ResourceNotFound)?;
785+
786+
if !canonical_path.starts_with(canonical_root) {
787+
return Err(Nope::ResourceNotFound.into());
788+
}
789+
790+
match File::from_path(storage, &storage_path, config) {
791+
Ok(file) => Ok(file.serve()),
792+
Err(err) if err.downcast_ref::<PathNotFoundError>().is_some() => {
793+
Err(Nope::ResourceNotFound.into())
794+
}
795+
Err(err) => {
796+
utils::report_error(&err);
797+
Err(Nope::InternalServerError.into())
798+
}
799+
}
800+
}
801+
769802
/// Serves shared web resources used by rustdoc-generated documentation.
770803
///
771804
/// This includes common `css` and `js` files that only change when the compiler is updated, but are
772805
/// otherwise the same for all crates documented with that compiler. Those have a custom handler to
773806
/// deduplicate them and save space.
774-
pub struct SharedResourceHandler;
807+
///
808+
/// This handler considers only the last component of the request path, and looks for a matching file
809+
/// in the Storage root.
810+
pub struct LegacySharedResourceHandler;
775811

776-
impl Handler for SharedResourceHandler {
812+
impl Handler for LegacySharedResourceHandler {
777813
fn handle(&self, req: &mut Request) -> IronResult<Response> {
778814
let path = req.url.path();
779815
let filename = path.last().unwrap(); // unwrap is fine: vector is non-empty
@@ -796,6 +832,29 @@ impl Handler for SharedResourceHandler {
796832
}
797833
}
798834

835+
/// Serves shared web resources used by rustdoc-generated documentation.
836+
///
837+
/// Rustdoc has certain JS, CSS, font and image files that are required for all
838+
/// documentation it generates, and these don't change often. We make one copy
839+
/// of these per rustdoc release and serve them from a common location.
840+
///
841+
/// This handler considers the whole path, and looks for a file at that path in
842+
/// the Storage.
843+
pub struct SharedResourceHandler;
844+
845+
impl Handler for SharedResourceHandler {
846+
fn handle(&self, req: &mut Request) -> IronResult<Response> {
847+
let storage = extension!(req, Storage);
848+
let config = extension!(req, Config);
849+
850+
let storage_path = format!("/{}", req.url.path().join("/"));
851+
match File::from_path(storage, &storage_path, config) {
852+
Ok(file) => Ok(file.serve()),
853+
Err(_) => Err(Nope::ResourceNotFound.into()),
854+
}
855+
}
856+
}
857+
799858
#[cfg(test)]
800859
mod test {
801860
use crate::{test::*, web::cache::CachePolicy, Config};

0 commit comments

Comments
 (0)