Skip to content

Commit 3a5ffba

Browse files
pietroalbiniJoshua Nelson
authored andcommitted
statics: allow loading subdirectories in the statics handler
1 parent c86cdea commit 3a5ffba

File tree

2 files changed

+27
-16
lines changed

2 files changed

+27
-16
lines changed

src/web/routes.rs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ pub(super) fn build_routes() -> Routes {
2929
PermanentRedirect("/-/static/opensearch.xml"),
3030
);
3131

32-
routes.static_resource("/-/static/:file", super::statics::static_handler);
32+
routes.static_resource("/-/static/:single", super::statics::static_handler);
33+
routes.static_resource("/-/static/*", super::statics::static_handler);
3334

3435
routes.internal_page("/", super::releases::home_page);
3536

src/web/statics.rs

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@ use iron::{
77
IronResult, Request, Response, Url,
88
};
99
use mime_guess::MimeGuess;
10-
use router::Router;
1110
use std::{ffi::OsStr, fs, path::Path};
1211

1312
const VENDORED_CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/vendored.css"));
@@ -16,10 +15,11 @@ const RUSTDOC_CSS: &str = include_str!(concat!(env!("OUT_DIR"), "/rustdoc.css"))
1615
const STATIC_SEARCH_PATHS: &[&str] = &["vendor/pure-css/css", "static"];
1716

1817
pub(crate) fn static_handler(req: &mut Request) -> IronResult<Response> {
19-
let router = extension!(req, Router);
20-
let file = cexpect!(req, router.find("file"));
18+
let mut file = req.url.path();
19+
file.drain(..2).for_each(std::mem::drop);
20+
let file = file.join("/");
2121

22-
Ok(match file {
22+
Ok(match file.as_str() {
2323
"vendored.css" => serve_resource(VENDORED_CSS, ContentType("text/css".parse().unwrap()))?,
2424
"style.css" => serve_resource(STYLE_CSS, ContentType("text/css".parse().unwrap()))?,
2525
"rustdoc.css" => serve_resource(RUSTDOC_CSS, ContentType("text/css".parse().unwrap()))?,
@@ -28,15 +28,21 @@ pub(crate) fn static_handler(req: &mut Request) -> IronResult<Response> {
2828
}
2929

3030
fn serve_file(req: &Request, file: &str) -> IronResult<Response> {
31-
// Filter out files that attempt to traverse directories
32-
if file.contains("..") || file.contains('/') || file.contains('\\') {
33-
return Err(Nope::ResourceNotFound.into());
34-
}
35-
3631
// Find the first path that actually exists
3732
let path = STATIC_SEARCH_PATHS
3833
.iter()
39-
.map(|p| Path::new(p).join(file))
34+
.filter_map(|root| {
35+
let path = Path::new(root).join(file);
36+
37+
// Prevent accessing static files outside the root. This could happen if the path
38+
// contains `/` or `..`. The check doesn't outright prevent those strings to be present
39+
// to allow accessing files in subdirectories.
40+
if path.starts_with(root) {
41+
Some(path)
42+
} else {
43+
None
44+
}
45+
})
4046
.find(|p| p.exists())
4147
.ok_or(Nope::ResourceNotFound)?;
4248
let contents = ctry!(req, fs::read(&path));
@@ -199,11 +205,15 @@ mod tests {
199205
wrapper(|env| {
200206
let web = env.frontend();
201207

202-
for path in STATIC_SEARCH_PATHS {
203-
for (file, path) in fs::read_dir(path)?
204-
.map(|e| e.unwrap())
205-
.map(|e| (e.file_name(), e.path()))
206-
{
208+
for root in STATIC_SEARCH_PATHS {
209+
for entry in walkdir::WalkDir::new(root) {
210+
let entry = entry?;
211+
if !entry.file_type().is_file() {
212+
continue;
213+
}
214+
let file = entry.path().strip_prefix(root).unwrap();
215+
let path = entry.path();
216+
207217
let url = format!("/-/static/{}", file.to_str().unwrap());
208218
let resp = web.get(&url).send()?;
209219

0 commit comments

Comments
 (0)