Skip to content

Use forward slashes in ZIP files; rewrite archive tests; refine error msg#1534

Merged
svenstaro merged 15 commits intosvenstaro:masterfrom
pzhlkj6612:fix/archive-zip-windows-path-backslashes
Feb 16, 2026
Merged

Use forward slashes in ZIP files; rewrite archive tests; refine error msg#1534
svenstaro merged 15 commits intosvenstaro:masterfrom
pzhlkj6612:fix/archive-zip-windows-path-backslashes

Conversation

@pzhlkj6612
Copy link
Copy Markdown
Contributor

Hello!

I'm running miniserve on Windows. I found that my files in produced ZIP files cannot be correctly extracted on macOS, but Windows Explorer didn't complain about it. After reading a post 1, I thought I can fix it. Now here's the PR.

I'm new to Rust. Please review my code and help me correct anything wrong.

My test. Run it on Git Bash (untested on WSL):

Bash shell script
#!/bin/bash

set -eu

# positional args
PORT=${1:-8080}

command -v miniserve 2>/dev/null || (echo 'miniserve not found' && exit 1)

playground="$(TMPDIR="$PWD" mktemp -d --suffix="-$(TZ='UTC' date '+%Y%m%dT%H%M%SZ')")"
wwwroot_dir="$playground/wwwroot"
downloaded_dir="$playground/download"
downloaded_file="$downloaded_dir/download.zip"

mkdir -p "$wwwroot_dir" "$downloaded_dir"

for name in 'out' 'out_dir/mid' 'out_dir/mid_dir/in'; do
    mkdir -p "$wwwroot_dir/${name}_dir"
    touch "$wwwroot_dir/${name}_file"
    dd if=/dev/urandom of="$wwwroot_dir/${name}_file" count=10 status=none
done

miniserve "$wwwroot_dir" --port $PORT --interfaces 127.0.0.1 --enable-zip &
pid=$!

curl -s -L "http://127.0.0.1:$PORT/?download=zip" -o "$downloaded_file"

kill $pid

unzip -l "$downloaded_file"

The output before this PR:

  Length      Date    Time    Name
---------  ---------- -----   ----
        0  1980-01-01 00:00   wwwroot\out_dir/
     5120  1980-01-01 00:00   wwwroot\out_file
        0  1980-01-01 00:00   wwwroot\out_dir\mid_dir/
     5120  1980-01-01 00:00   wwwroot\out_dir\mid_file
        0  1980-01-01 00:00   wwwroot\out_dir\mid_dir\in_dir/
     5120  1980-01-01 00:00   wwwroot\out_dir\mid_dir\in_file
---------                     -------
    15360                     6 files

The output for now:

  Length      Date    Time    Name
---------  ---------- -----   ----
        0  1980-01-01 00:00   wwwroot/out_dir/
     5120  1980-01-01 00:00   wwwroot/out_file
        0  1980-01-01 00:00   wwwroot/out_dir/mid_dir/
     5120  1980-01-01 00:00   wwwroot/out_dir/mid_file
        0  1980-01-01 00:00   wwwroot/out_dir/mid_dir/in_dir/
     5120  1980-01-01 00:00   wwwroot/out_dir/mid_dir/in_file
---------                     -------
    15360                     6 files

Before applying this fix, third-party archive utilities, for example PeaZip and 7-Zip, can't understand those backslashes:

Screenshot of the incorrect directory layout in 3rd-party archive utility PeaZip and 7-Zip

However, Windows Explorer always shows the correct directory hierarchy despite this fix:

Screenshot of a multi-layer directory layout in the sidebar of Windows Explorer

.

Footnotes

  1. windows - Zip files expand with backslashes on Linux, no subdirectories - Super User - https://superuser.com/q/1382839

@svenstaro
Copy link
Copy Markdown
Owner

Is this actually a problem in the zip crate or how we use it? It doesn't seem correct to me to try to fix it downstream. However, maybe we're holding it wrong. Could you read into the zip crate and see how they deal with this? Surely it's a common problem.

@pzhlkj6612 pzhlkj6612 marked this pull request as draft November 28, 2025 09:28
@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

Could you read into the zip crate and see how they deal with this?

In progress.

@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

Is this actually a problem in the zip crate or how we use it?

This is actually a problem in the zip (namely, zip-rs/zip2) crate.

It doesn't seem correct to me to try to fix it downstream.

I agree. However, waiting for the fix from upstream will be slow, while using my dirty fix will solve it right away.

... Could you read into the zip crate and see how they deal with this? Surely it's a common problem.

Unfortunately, the zip crate write the path to ZIP files as-is for now. And I've tried their "write_dir" example 1 and found out they write the path in the same way we do.

There are some issues about path handling in that crate:

  • zip-rs/zip2#242: Zip package created on Windows will generate messy up files after unpack on MacOS (issue closed but who knows the solution)
  • zip-rs/zip2#272: Path of directories on Windows wouldn't be recognized as directories path on Unix platform

There is also a comment about using 3rd-party library to handle paths instead of std::path in another thread (zip-rs/zip2#376).

Footnotes

  1. https://github.com/zip-rs/zip2/blob/v6.0.0/examples/write_dir.rs#L88

@pzhlkj6612 pzhlkj6612 marked this pull request as ready for review November 28, 2025 11:58
@svenstaro
Copy link
Copy Markdown
Owner

Hey, I'd love to get this merged. Sorry for not giving this any attention these last few weeks. Could you please add a test for Windows so we know it won't be broken again in the future?

@pzhlkj6612 pzhlkj6612 marked this pull request as draft January 7, 2026 11:32
@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

No problem. Let me add necessary tests.

@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

Ah-oh, workflows have not been approved. Let's wait for the Windows CI to fail, then I'll commit to revert the latest commit and you can merge it.

@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

9c39959

Please, the last two steps.

@svenstaro
Copy link
Copy Markdown
Owner

It looks like this currently only contains the tests but not the fixes.

@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

It looks like this currently only contains the tests but not the fixes.

Yep. That was the second to last step - let the test fail on Windows only.

https://github.com/svenstaro/miniserve/actions/runs/20843902950/job/59964978872

I made it. Now we have the fix: 3b59a69 .

- sort imports
- clean dependency
- refine comments and English
- use long parameter

Co-authored-by: Sven-Hendrik Haase <[email protected]>
@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

thread 'show_nested_readme_contents::case_7' (2572) panicked at tests\readme.rs:33:5:
assertion failed: parsed_dom.find(Attr("id", "readme-filename")).next().unwrap().text() ==
    filename
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Timing issue? It runs fine before.

@svenstaro
Copy link
Copy Markdown
Owner

svenstaro commented Jan 10, 2026

thread 'show_nested_readme_contents::case_7' (2572) panicked at tests\readme.rs:33:5:
assertion failed: parsed_dom.find(Attr("id", "readme-filename")).next().unwrap().text() ==
    filename
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

Timing issue? It runs fine before.

Hm yeah, probably a timing issue. :/ You're very welcome to take a look at that if you want.

@svenstaro
Copy link
Copy Markdown
Owner

Hey, I'd love to get this into the next release of miniserve. Do you think you'll have some time to finish this up soon? Looks like there's only some testing stuff remaining. I can also help you finish this up if you like.

- Universal definitions.
- Reuse HTTP client.
- Docstring.
@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

@svenstaro: sorry for being late. Please see a076fd3 . This is a rewrite.

@pzhlkj6612 pzhlkj6612 marked this pull request as ready for review January 21, 2026 12:35
@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

failures:

---- uploading_files_with_invalid_sha_func_is_prevented::case_3_sha128_hash stdout ----
[miniserve stdout] miniserve v0.32.0
[miniserve stdout] Wed, 21 Jan 2026 22:54:46 +0000 [INFO] starting 3 workers
[miniserve stdout] Bound to [::]:50404, 0.0.0.0:50404
[miniserve stdout] Serving path /private/var/folders/kg/7q73ww8s3llgyl61c9z_j5g40000gn/T/.tmpS6jASu
[miniserve stdout] Available at (non-exhaustive list):
[miniserve stdout]     http://127.0.0.1:50404/
[miniserve stdout]     http://192.168.64.23:50404/
[miniserve stdout]     http://[::1]:50404
[miniserve stdout] 
[miniserve stdout] Wed, 21 Jan 2026 22:54:46 +0000 [INFO] Actix runtime found; starting in Actix runtime
[miniserve stdout] Wed, 21 Jan 2026 22:54:46 +0000 [INFO] starting service: "actix-web-service-[::]:50404", workers: 3, listening on: [::]:50404
[miniserve stdout] Wed, 21 Jan 2026 22:54:46 +0000 [INFO] starting service: "actix-web-service-0.0.0.0:50404", workers: 3, listening on: 0.0.0.0:50404
[miniserve stdout] Wed, 21 Jan 2026 22:54:46 +0000 [INFO] ::1 "GET / HTTP/1.1" 200 33177 "-" "-" 0.001141
Error: rve stderr] Wed, 21 Jan 2026 22:54:46 +0000 [ERROR] Invalid HTTP request
Error: rve stderr] Wed, 21 Jan 2026 22:54:46 +0000 [ERROR] caused by: Invalid header value found for 'X-File-Hash-Function'. Supported values are SHA256 or SHA512. Found SHA128.
[miniserve stdout] Wed, 21 Jan 2026 22:54:46 +0000 [INFO] ::1 "POST /upload?path=/ HTTP/1.1" 400 139 "-" "-" 0.000178
Error: reqwest::Error { kind: Body, url: "http://localhost:50404/upload?path=/", source: SendError { kind: Disconnected } }

Bruh, it works on my machine ™.

@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

Hi, @svenstaro ! Does the current revision look good?

src/archive.rs Outdated

// Workaround for Windows path in ZIP files:
// Always use forward slashes for all paths to avoid literal backslashes in saved entry path.
// TODO: remove this when the "zip" cargo has the ability to process a directory as a whole.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

According to zip-rs/zip2#376 and the release notes of zip v7.2.0, this is now handled by upstream. At least I see they now have tests handling this. Could you check out whether that now does the job? It's a bit odd though that they have that issue still open.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a06e31e. It seems to work. I'll test it soon.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

a06e31e. It seems to work. I'll test it soon.

It still does not work on macOS (#1534 (comment)). Reverted by 9f8b0de and a new comment has been added.

@pzhlkj6612 pzhlkj6612 marked this pull request as draft February 8, 2026 03:00
Copy link
Copy Markdown
Owner

@svenstaro svenstaro left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiny nit but apart from that I really like these tests. I was at first a bit skeptical about the ArchiveKind thing as it adds some complexity but I think here it actually makes sense in the context of the archives_links_and_downloads test.

@svenstaro
Copy link
Copy Markdown
Owner

Happy to merge after you figure out whether upstream's fix works for us. :)

@svenstaro
Copy link
Copy Markdown
Owner

This is the last PR before releasing v0.33.0 :)

pzhlkj6612 and others added 3 commits February 11, 2026 08:23
The 2nd parameter of "assert!()" has "format!()" integrated.

Co-authored-by: Sven-Hendrik Haase <[email protected]>
Our dependency "zip" has fixed a bug about setting local platform in the ZIP since v7.2.0: zip-rs/zip2@9e9badd, so we don't need to fix it here.

This effectively reverts the main part of commits:

- The fix: ad8d3d9 (the origin) or 3b59a69 (the re-applied one)
- The test: 90c5234
@pzhlkj6612 pzhlkj6612 changed the title Fix Windows path in ZIP files by using forward slashes only Rewrite all tests for archives; add filename to error msg from archiving Feb 11, 2026
@svenstaro
Copy link
Copy Markdown
Owner

Is this still draft or shall I review it?

@pzhlkj6612
Copy link
Copy Markdown
Contributor Author

pzhlkj6612 commented Feb 11, 2026

Sorry for the delay. I didn't have a macOS with me several hours ago.

Unfortunately, on macOS, Finder does not treat those backslashes as path separators even though we have already correctly set the field after switching to the newest "zip" package:

Screenshot of wrong structure of a ZIP file on macOS
$ zipinfo -v .../wwwroot.zip | grep 'file system'
  file system or operating system of origin:      MS-DOS, OS/2 or NT FAT
  minimum file system compatibility required:     MS-DOS, OS/2 or NT FAT
  ...

BEFORE:

$ zipinfo -v .../wwwroot.zip | grep 'file system'
  file system or operating system of origin:      Unix
  minimum file system compatibility required:     MS-DOS, OS/2 or NT FAT
  ...

However, both PeaZip and 7-Zip agree with us:

Screenshot of correct structure of a ZIP file in PeaZip and 7-Zip on Windows

Path parsing in ZIP looks implementation dependent. Since we have this:

File name: the name of the file including an optional relative path. All slashes in the path should be forward slashes '/'.

from: The structure of a PKZip file - https://users.cs.jmu.edu/buchhofp/forensics/formats/pkzip.html

I think using forward slashes is necessary and more safe. Let me re-revert the code. Sorry for the noise.

Btw, the "zip" package works as expected: it names the saved item exactly what the user specified.

@svenstaro
Copy link
Copy Markdown
Owner

svenstaro commented Feb 11, 2026

Some really peculiar behavior from zip here. Thanks for looking into this!

macOS: rememeber, no forward slashes.

This reverts commit a06e31e.
@pzhlkj6612 pzhlkj6612 marked this pull request as ready for review February 11, 2026 14:02
@pzhlkj6612 pzhlkj6612 changed the title Rewrite all tests for archives; add filename to error msg from archiving Use forward slashes in ZIP files; rewrite archive tests; refine error msg Feb 11, 2026
Comment on lines +44 to +53
fn fetch_index_document(
reqwest_client: &Client,
server: &TestServer,
expected: StatusCode,
) -> Result<Document, Error> {
let resp = reqwest_client.get(server.url()).send()?;
assert_eq!(resp.status(), expected);

Ok(Document::from_read(resp)?)
}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would probably make sense to make this a universal utility and then use it everywhere? We have sooo many Document::from_reads in all the tests that this might be a great follow-up to tidy up some test code if you're up for it. At any rate, this should probably go into tests/utils/mod.rs for now.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, let's do that in a follow-up. I wanna get this one merged at long last.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

oops, I'm coding for this. Thank you for reviewing my PR. I think this cleanup will be made in following PRs after the upcoming new verison.

oops

Co-authored-by: Sven-Hendrik Haase <[email protected]>
@svenstaro svenstaro merged commit e068024 into svenstaro:master Feb 16, 2026
18 checks passed
svenstaro added a commit that referenced this pull request Feb 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants