fix(services)!: reject trailing slash for file paths#678
Conversation
|
@jplatte this feels like probably the right default, but something that needs to ship across a breaking semver release due to the behavioral change, would you agree? One could call it a bugfix, but that seems a bit sketchy to me. (specific PR code aside, I will review this more closely) |
|
Yeah, seems likely enough that some users are currently (accidentally) relying on |
jlizen
left a comment
There was a problem hiding this comment.
Thanks for the contribution! Implementation could use a bit of tweaking, see comments.
minor: It might be worth applying similar validation to ServeVariant::SingleFile as well, no? Probably more confusing to have handling be inconsistent?
| // Might already at this point know a redirect or not found result should be | ||
| // returned which corresponds to a Some(output). Otherwise the path might be | ||
| // modified and proceed to the open file/metadata future. | ||
| let was_directory = is_dir(&path_to_file).await; |
There was a problem hiding this comment.
This will result in a redundant syscall since we anyway call is_dir inside maybe_redirect_or_append_path.
We could instead just check for the trailing slash AFTER that call, since if it doesn't have an early return, we know that we are dealing with file rather than a directory (perhaps because index.html was appended)
| ServeVariant::Directory { | ||
| append_index_html_on_directories, | ||
| } => { | ||
| // Might already at this point know a redirect or not found result should be |
There was a problem hiding this comment.
I would probably keep this comment, it is largely accurate still? Instead of we moved the slash check until after per this comment we would want a new comment explaining, "at this point we know we are are dealing with a file, so make sure it doesn't have a trailing slash"
| negotiated_encodings: Vec<(Encoding, QValue)>, | ||
| range_header: Option<String>, | ||
| buf_chunk_size: usize, | ||
| trailing_slash: bool, |
There was a problem hiding this comment.
I would probably just check the path directly inline where we need it rather than threading throughout.
| } | ||
|
|
||
| #[tokio::test] | ||
| async fn not_found_when_file_requested_with_trailing_slash() { |
There was a problem hiding this comment.
Can we also have a check where there is a trailing slash + file where there is a fallback configured?
Hi @jlizen, Thanks so much for the review. I've updated the PR with your feedback. I took out the redundant I also did the suggestion for I really appreciate you taking the time for the review and the helpful guidance. I learned a lot messing around with this. Let me know if there's anything else I should tweak. |
|
@jlizen are you sure about |
Yeah, after thinking more with it, I agree that this check doesn't belong with ServeFile. My knee-jerk previously was, "well, it is intrinsically confusing to serve a file from a directory-like URI". But, it is literally the contract of ServeFile to serve a given file regardless of path, which is already off the rail for "URI intuition", so I think the concern is misplaced. |
| Some(path_to_file) | ||
| } | ||
| ServeVariant::SingleFile { mime: _ } => Some(base_path.to_path_buf()), | ||
| ServeVariant::SingleFile { mime: _ } => { |
There was a problem hiding this comment.
Sorry for the churn on this, but let's rip out the SingleFile check as discussed here.
| // At this point we know we are dealing with a file mapping. | ||
| // A trailing slash is only valid if it's the root path "/" fetching an index file, otherwise it's invalid. | ||
| let uri_path = req.uri().path(); | ||
| if uri_path.ends_with('/') && uri_path != "/" { |
There was a problem hiding this comment.
This introduces a bug where /foo/ with append_index_html_on_directories: true will now 404 out.
The check should instead be something like:
if uri_path.ends_with('/') && !path_to_file.ends_with("index.html") { ...
Another option would be to return an enum from maybe_redirect_or_append_path:
enum PathResolution {
EarlyOutput(OpenFileOutput),
AppendedIndexHtml, // was a directory, index.html appended
NotADirectory, // not a directory, path unchanged
}
I have no strong preference for one versus the other, dealer's choice. The enum is cleaner in principal but probably a bit overengineered compared to the one-liner.
There was a problem hiding this comment.
Also, let's definitely have a test case with /foo/ that appends index.html and makes sure it is still served properly.
Let's throw in a test against / that appends index.html while we're at it.
Motivation
Hi. This is my first time contributing to global library and I wanted to help out with #654.
The goal here was to make sure that if someone tries to access a file with a trailing slash (like /index.html/), the server doesn't get confused and try to serve it as a file. It should only allow the trailing slash if it’s actually a directory.
Solution
serve_dir/mod.rs, I added a check to see if the requested URI ends with a /.open_filefunction.open_file.rs, I added a check: if there’s a trailing slash but the metadata says "this is a file," we returnFileNotFound.index.htmlinside real folders.tests.rsto make sure requesting/index.html/now correctly returns a 404.I'm very open to feedback if there’s a cleaner way to handle this or if I missed any project conventions, please let me know.