Repository: static-web-server/static-web-server (language: rust)

Issue:
Internal server error while serving a directory listing that contains softlinks
**Describe the bug**
500 error when serving a directory listing that contains a softlink

**To Reproduce**
Create a directory and a softlink inside it. Try to list it. 

**Expected behavior**
Do not return 500 error.

**Logs/Screenshots**

```log
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.619932Z  INFO static_web_server::logger: logging level: trace
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.619967Z DEBUG static_web_server::server: initializing tokio runtime with multi thread scheduler self.worker_threads=1
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.620094Z TRACE mio::poll: registering event source with poller: token=Token(2147483649), interests=READABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.622140Z TRACE static_web_server::server: starting web server
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.622175Z  INFO static_web_server::server: server bound to tcp socket [::]:80
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629506Z  INFO static_web_server::server: runtime worker threads: 1
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629517Z  INFO static_web_server::server: runtime max blocking threads: 512
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629522Z  INFO static_web_server::server: security headers: enabled=false
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629527Z  INFO static_web_server::server: auto compression: enabled=true
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629532Z  INFO static_web_server::server: compression static: enabled=false
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629537Z  INFO static_web_server::server: directory listing: enabled=true
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629541Z  INFO static_web_server::server: directory listing order code: 6
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629546Z  INFO static_web_server::server: directory listing format: Json
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629551Z  INFO static_web_server::server: cache control headers: enabled=true
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629578Z  INFO static_web_server::cors: enabled=true, allow_methods=[GET,HEAD,OPTIONS], allow_origins=*, allow_headers=[origin,content-type], expose_headers=[origin,content-type]
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629600Z  INFO static_web_server::server: basic authentication: enabled=false
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629605Z  INFO static_web_server::server: log remote address: enabled=false
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629609Z  INFO static_web_server::server: redirect trailing slash: enabled=true
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629613Z  INFO static_web_server::server: ignore hidden files: enabled=false
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629618Z  INFO static_web_server::server: grace period before graceful shutdown: 0s
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629635Z TRACE mio::poll: registering event source with poller: token=Token(0), interests=READABLE | WRITABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629649Z TRACE mio::poll: registering event source with poller: token=Token(1), interests=READABLE | WRITABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629699Z TRACE mio::poll: registering event source with poller: token=Token(2), interests=READABLE | WRITABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629736Z  INFO Server::start_server{addr_str="[::]:80" threads=1}: static_web_server::server: close time.busy=0.00ns time.idle=746ns
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629773Z  INFO static_web_server::server: listening on http://[::]:80
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629778Z  INFO static_web_server::server: press ctrl+c to shut down the server
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.649712Z TRACE mio::poll: registering event source with poller: token=Token(3), interests=READABLE | WRITABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.651185Z TRACE hyper::proto::h1::conn: Conn::read_head
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.652531Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: Init, writing: Init, keep_alive: Busy }        
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.653153Z TRACE hyper::proto::h1::conn: Conn::read_head
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.653600Z TRACE hyper::proto::h1::io: received 443 bytes
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.653677Z TRACE parse_headers: hyper::proto::h1::role: Request.parse bytes=443
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.653716Z TRACE parse_headers: hyper::proto::h1::role: Request.parse Complete(443)
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.654712Z TRACE parse_headers: hyper::proto::h1::role: close time.busy=1.03ms time.idle=2.92µs
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.654769Z DEBUG hyper::proto::h1::io: parsed 7 headers
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.654782Z DEBUG hyper::proto::h1::conn: incoming body is empty
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.654826Z  INFO static_web_server::handler: incoming request: method=GET uri=/archive/
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.655408Z DEBUG static_web_server::handler: cors state: NotCors
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.655464Z TRACE static_web_server::static_files: dir: base="./public", route="archive/"
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.655483Z TRACE static_web_server::static_files: getting metadata for file ./public/archive
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.658015Z TRACE static_web_server::static_files: file found: "./public/archive"
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.658055Z DEBUG static_web_server::static_files: dir: appending an index.html to the directory path
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.661282Z DEBUG static_web_server::static_files: file not found: "./public/archive/index.html" Os { code: 2, kind: NotFound, message: "No such file or directory" }
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.661319Z DEBUG static_web_server::static_files: file: appending .html to the path
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.663696Z DEBUG static_web_server::static_files: file not found: "./public/archive.html" Os { code: 2, kind: NotFound, message: 
"No such file or directory" }
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.663901Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: KeepAlive, writing: Init, keep_alive: Busy }   
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.673045Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: KeepAlive, writing: Init, keep_alive: Busy }   
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.673814Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: KeepAlive, writing: Init, keep_alive: Busy }   
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.677853Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: KeepAlive, writing: Init, keep_alive: Busy }   
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678304Z ERROR static_web_server::directory_listing: error during directory entries reading (path="./public/archive"): No such 
file or directory (os error 2)
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678316Z  WARN static_web_server::error_page: method=GET uri=/archive/ status=500 error="Internal Server Error"
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678345Z TRACE encode_headers: hyper::proto::h1::role: Server::encode status=500, body=Some(Known(130)), req_method=Some(GET)
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678357Z TRACE encode_headers: hyper::proto::h1::role: close time.busy=12.9µs time.idle=752ns
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678366Z TRACE hyper::proto::h1::io: buffer.queue self.len=158 buf.len=130
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678413Z DEBUG hyper::proto::h1::io: flushed 288 bytes
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678418Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: Init, writing: Init, keep_alive: Idle }        
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.706178Z TRACE mio::poll: registering event source with poller: token=Token(4), interests=READABLE | WRITABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.706245Z TRACE hyper::proto::h1::conn: Conn::read_head
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.706254Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: Init, writing: Init, keep_alive: Busy }        
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.706988Z TRACE hyper::proto::h1::conn: Conn::read_head
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707010Z TRACE hyper::proto::h1::io: received 474 bytes
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707020Z TRACE parse_headers: hyper::proto::h1::role: Request.parse bytes=474
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707029Z TRACE parse_headers: hyper::proto::h1::role: Request.parse Complete(434)
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707044Z TRACE parse_headers: hyper::proto::h1::role: close time.busy=24.2µs time.idle=1.09µs
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707055Z DEBUG hyper::proto::h1::io: parsed 12 headers
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707060Z DEBUG hyper::proto::h1::conn: incoming body is content-length (40 bytes)
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707084Z TRACE hyper::proto::h1::decode: decode; state=Length(40)
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707089Z DEBUG hyper::proto::h1::conn: incoming body completed
```

**Environment and Specs**
 - static-web-server: [2.15.0]
 - OS: [Windows 10]
 - Docker: [ 20.10.14, build a224086]
 - Client: [Version 111.0.5563.147]

**Additional context**
A directory to be server is mounted to the host directory, which is a mount as well (via Fuse)

Internal server error while serving a directory listing that contains softlinks
**Describe the bug**
500 error when serving a directory listing that contains a softlink

**To Reproduce**
Create a directory and a softlink inside it. Try to list it. 

**Expected behavior**
Do not return 500 error.

**Logs/Screenshots**

```log
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.619932Z  INFO static_web_server::logger: logging level: trace
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.619967Z DEBUG static_web_server::server: initializing tokio runtime with multi thread scheduler self.worker_threads=1
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.620094Z TRACE mio::poll: registering event source with poller: token=Token(2147483649), interests=READABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.622140Z TRACE static_web_server::server: starting web server
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.622175Z  INFO static_web_server::server: server bound to tcp socket [::]:80
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629506Z  INFO static_web_server::server: runtime worker threads: 1
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629517Z  INFO static_web_server::server: runtime max blocking threads: 512
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629522Z  INFO static_web_server::server: security headers: enabled=false
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629527Z  INFO static_web_server::server: auto compression: enabled=true
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629532Z  INFO static_web_server::server: compression static: enabled=false
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629537Z  INFO static_web_server::server: directory listing: enabled=true
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629541Z  INFO static_web_server::server: directory listing order code: 6
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629546Z  INFO static_web_server::server: directory listing format: Json
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629551Z  INFO static_web_server::server: cache control headers: enabled=true
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629578Z  INFO static_web_server::cors: enabled=true, allow_methods=[GET,HEAD,OPTIONS], allow_origins=*, allow_headers=[origin,content-type], expose_headers=[origin,content-type]
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629600Z  INFO static_web_server::server: basic authentication: enabled=false
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629605Z  INFO static_web_server::server: log remote address: enabled=false
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629609Z  INFO static_web_server::server: redirect trailing slash: enabled=true
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629613Z  INFO static_web_server::server: ignore hidden files: enabled=false
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629618Z  INFO static_web_server::server: grace period before graceful shutdown: 0s
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629635Z TRACE mio::poll: registering event source with poller: token=Token(0), interests=READABLE | WRITABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629649Z TRACE mio::poll: registering event source with poller: token=Token(1), interests=READABLE | WRITABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629699Z TRACE mio::poll: registering event source with poller: token=Token(2), interests=READABLE | WRITABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629736Z  INFO Server::start_server{addr_str="[::]:80" threads=1}: static_web_server::server: close time.busy=0.00ns time.idle=746ns
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629773Z  INFO static_web_server::server: listening on http://[::]:80
static-web-server-static-web-server-1  | 2023-04-05T06:26:17.629778Z  INFO static_web_server::server: press ctrl+c to shut down the server
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.649712Z TRACE mio::poll: registering event source with poller: token=Token(3), interests=READABLE | WRITABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.651185Z TRACE hyper::proto::h1::conn: Conn::read_head
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.652531Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: Init, writing: Init, keep_alive: Busy }        
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.653153Z TRACE hyper::proto::h1::conn: Conn::read_head
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.653600Z TRACE hyper::proto::h1::io: received 443 bytes
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.653677Z TRACE parse_headers: hyper::proto::h1::role: Request.parse bytes=443
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.653716Z TRACE parse_headers: hyper::proto::h1::role: Request.parse Complete(443)
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.654712Z TRACE parse_headers: hyper::proto::h1::role: close time.busy=1.03ms time.idle=2.92µs
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.654769Z DEBUG hyper::proto::h1::io: parsed 7 headers
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.654782Z DEBUG hyper::proto::h1::conn: incoming body is empty
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.654826Z  INFO static_web_server::handler: incoming request: method=GET uri=/archive/
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.655408Z DEBUG static_web_server::handler: cors state: NotCors
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.655464Z TRACE static_web_server::static_files: dir: base="./public", route="archive/"
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.655483Z TRACE static_web_server::static_files: getting metadata for file ./public/archive
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.658015Z TRACE static_web_server::static_files: file found: "./public/archive"
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.658055Z DEBUG static_web_server::static_files: dir: appending an index.html to the directory path
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.661282Z DEBUG static_web_server::static_files: file not found: "./public/archive/index.html" Os { code: 2, kind: NotFound, message: "No such file or directory" }
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.661319Z DEBUG static_web_server::static_files: file: appending .html to the path
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.663696Z DEBUG static_web_server::static_files: file not found: "./public/archive.html" Os { code: 2, kind: NotFound, message: 
"No such file or directory" }
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.663901Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: KeepAlive, writing: Init, keep_alive: Busy }   
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.673045Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: KeepAlive, writing: Init, keep_alive: Busy }   
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.673814Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: KeepAlive, writing: Init, keep_alive: Busy }   
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.677853Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: KeepAlive, writing: Init, keep_alive: Busy }   
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678304Z ERROR static_web_server::directory_listing: error during directory entries reading (path="./public/archive"): No such 
file or directory (os error 2)
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678316Z  WARN static_web_server::error_page: method=GET uri=/archive/ status=500 error="Internal Server Error"
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678345Z TRACE encode_headers: hyper::proto::h1::role: Server::encode status=500, body=Some(Known(130)), req_method=Some(GET)
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678357Z TRACE encode_headers: hyper::proto::h1::role: close time.busy=12.9µs time.idle=752ns
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678366Z TRACE hyper::proto::h1::io: buffer.queue self.len=158 buf.len=130
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678413Z DEBUG hyper::proto::h1::io: flushed 288 bytes
static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678418Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: Init, writing: Init, keep_alive: Idle }        
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.706178Z TRACE mio::poll: registering event source with poller: token=Token(4), interests=READABLE | WRITABLE
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.706245Z TRACE hyper::proto::h1::conn: Conn::read_head
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.706254Z TRACE hyper::proto::h1::conn: flushed({role=server}): State { reading: Init, writing: Init, keep_alive: Busy }        
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.706988Z TRACE hyper::proto::h1::conn: Conn::read_head
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707010Z TRACE hyper::proto::h1::io: received 474 bytes
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707020Z TRACE parse_headers: hyper::proto::h1::role: Request.parse bytes=474
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707029Z TRACE parse_headers: hyper::proto::h1::role: Request.parse Complete(434)
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707044Z TRACE parse_headers: hyper::proto::h1::role: close time.busy=24.2µs time.idle=1.09µs
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707055Z DEBUG hyper::proto::h1::io: parsed 12 headers
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707060Z DEBUG hyper::proto::h1::conn: incoming body is content-length (40 bytes)
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707084Z TRACE hyper::proto::h1::decode: decode; state=Length(40)
static-web-server-static-web-server-1  | 2023-04-05T06:26:26.707089Z DEBUG hyper::proto::h1::conn: incoming body completed
```

**Environment and Specs**
 - static-web-server: [2.15.0]
 - OS: [Windows 10]
 - Docker: [ 20.10.14, build a224086]
 - Client: [Version 111.0.5563.147]

**Additional context**
A directory to be server is mounted to the host directory, which is a mount as well (via Fuse)



Discussion / hints:
>static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678304Z ERROR static_web_server::directory_listing: error during directory entries reading (path="./public/archive"): No such file or directory (os error 2)

Just to make sure what server's behavior you would like.
If you want to list a directory that contains a file/directory symlink that points to something, and that something either does not exist or has permission restrictions, what are you expecting from the server to be returned?

- a. 500
- b. just log the error and skip that symlink from the list

b option seems to be nicer. But it would be nice to follow symlinks as well
while serving files.

On Wed, Apr 5, 2023, 12:15 Jose Quintana ***@***.***> wrote:

> static-web-server-static-web-server-1 | 2023-04-05T06:26:23.678304Z ERROR
> static_web_server::directory_listing: error during directory entries
> reading (path="./public/archive"): No such file or directory (os error 2)
>
> Just to make sure what server's behavior you would like.
> If you want to list a directory that contains a file/directory symlink
> that points to something, and that something either does not exist or has
> permission restrictions, what are you expecting from the server to be
> returned?
>
>    - a. 500
>    - b. just log the error and skip that symlink from the list
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/static-web-server/static-web-server/issues/191#issuecomment-1497097370>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABBI253I5W4RFDRQPIYMOCTW7USYTANCNFSM6AAAAAAWTUOASY>
> .
> You are receiving this because you authored the thread.Message ID:
> ***@***.***>
>

>static-web-server-static-web-server-1  | 2023-04-05T06:26:23.678304Z ERROR static_web_server::directory_listing: error during directory entries reading (path="./public/archive"): No such file or directory (os error 2)

Just to make sure what server's behavior you would like.
If you want to list a directory that contains a file/directory symlink that points to something, and that something either does not exist or has permission restrictions, what are you expecting from the server to be returned?

- a. 500
- b. just log the error and skip that symlink from the list

b option seems to be nicer. But it would be nice to follow symlinks as well
while serving files.

On Wed, Apr 5, 2023, 12:15 Jose Quintana ***@***.***> wrote:

> static-web-server-static-web-server-1 | 2023-04-05T06:26:23.678304Z ERROR
> static_web_server::directory_listing: error during directory entries
> reading (path="./public/archive"): No such file or directory (os error 2)
>
> Just to make sure what server's behavior you would like.
> If you want to list a directory that contains a file/directory symlink
> that points to something, and that something either does not exist or has
> permission restrictions, what are you expecting from the server to be
> returned?
>
>    - a. 500
>    - b. just log the error and skip that symlink from the list
>
> —
> Reply to this email directly, view it on GitHub
> <https://github.com/static-web-server/static-web-server/issues/191#issuecomment-1497097370>,
> or unsubscribe
> <https://github.com/notifications/unsubscribe-auth/ABBI253I5W4RFDRQPIYMOCTW7USYTANCNFSM6AAAAAAWTUOASY>
> .
> You are receiving this because you authored the thread.Message ID:
> ***@***.***>
>


Fix patch:
diff --git a/src/directory_listing.rs b/src/directory_listing.rs
index 73886972..b7787f5a 100644
--- a/src/directory_listing.rs
+++ b/src/directory_listing.rs
@@ -18,7 +18,7 @@ use std::path::Path;
 use std::time::{SystemTime, UNIX_EPOCH};
 use structopt::clap::arg_enum;
 
-use crate::{exts::http::MethodExt, Result};
+use crate::{exts::http::MethodExt, Context, Result};
 
 arg_enum! {
     #[derive(Debug, Serialize, Deserialize, Clone)]
@@ -65,11 +65,7 @@ pub fn auto_index<'a>(
             {
                 Ok(resp) => Ok(resp),
                 Err(err) => {
-                    tracing::error!(
-                        "error during directory entries reading (path={:?}): {} ",
-                        parent.display(),
-                        err
-                    );
+                    tracing::error!("error after try to read directory entries: {:?}", err);
                     Err(StatusCode::INTERNAL_SERVER_ERROR)
                 }
             }
@@ -77,17 +73,25 @@ pub fn auto_index<'a>(
         Err(err) => {
             let status = match err.kind() {
                 io::ErrorKind::NotFound => {
-                    tracing::debug!("entry file not found: {:?}", filepath.display());
+                    tracing::debug!(
+                        "entry file not found (path: {}): {:?}",
+                        filepath.display(),
+                        err
+                    );
                     StatusCode::NOT_FOUND
                 }
                 io::ErrorKind::PermissionDenied => {
-                    tracing::warn!("entry file permission denied: {:?}", filepath.display());
+                    tracing::error!(
+                        "entry file permission denied (path: {}): {:?}",
+                        filepath.display(),
+                        err
+                    );
                     StatusCode::FORBIDDEN
                 }
                 _ => {
                     tracing::error!(
-                        "directory entries error (filepath={:?}): {} ",
-                        filepath.display(),
+                        "unable to read parent directory (parent={}): {:?}",
+                        parent.display(),
                         err
                     );
                     StatusCode::INTERNAL_SERVER_ERROR
@@ -135,13 +139,36 @@ async fn read_dir_entries(
     let mut files_count: usize = 0;
     let mut file_entries: Vec<FileEntry> = vec![];
 
-    while let Some(dir_entry) = dir_reader.next_entry().await? {
-        let meta = dir_entry.metadata().await?;
+    while let Some(dir_entry) = dir_reader
+        .next_entry()
+        .await
+        .with_context(|| "unable to read directory entry")?
+    {
+        let meta = match dir_entry.metadata().await {
+            Ok(m) => m,
+            Err(err) => {
+                tracing::error!(
+                    "unable to resolve metadata for file or directory entry (skipped): {:?}",
+                    err
+                );
+                continue;
+            }
+        };
 
-        let name = dir_entry
+        let name = match dir_entry
             .file_name()
             .into_string()
-            .map_err(|err| anyhow::anyhow!(err.into_string().unwrap_or_default()))?;
+            .map_err(|err| anyhow::anyhow!(err.into_string().unwrap_or_default()))
+        {
+            Ok(s) => s,
+            Err(err) => {
+                tracing::error!(
+                    "unable to resolve name for file or directory entry (skipped): {:?}",
+                    err
+                );
+                continue;
+            }
+        };
 
         // Check and ignore the current hidden file/directory (dotfile) if feature enabled
         if ignore_hidden_files && name.starts_with('.') {
@@ -152,15 +179,41 @@ async fn read_dir_entries(
         let mut filesize = 0_u64;
 
         if meta.is_dir() {
-            name_encoded += "/";
+            name_encoded.push('/');
             dirs_count += 1;
         } else if meta.is_file() {
             filesize = meta.len();
             files_count += 1;
         } else if meta.file_type().is_symlink() {
-            let m = tokio::fs::symlink_metadata(dir_entry.path().canonicalize()?).await?;
-            if m.is_dir() {
-                name_encoded += "/";
+            // NOTE: we resolve the symlink path below to just know if is a directory or not.
+            // Hwever, we are still showing the symlink name but not the resolved name.
+
+            let symlink = dir_entry.path();
+            let symlink = match symlink.canonicalize() {
+                Ok(v) => v,
+                Err(err) => {
+                    tracing::error!(
+                        "unable to resolve `{}` symlink path (skipped): {:?}",
+                        symlink.display(),
+                        err
+                    );
+                    continue;
+                }
+            };
+
+            let symlink_meta = match tokio::fs::symlink_metadata(&symlink).await {
+                Ok(v) => v,
+                Err(err) => {
+                    tracing::error!(
+                        "unable to resolve metadata for `{}` symlink (skipped): {:?}",
+                        symlink.display(),
+                        err
+                    );
+                    continue;
+                }
+            };
+            if symlink_meta.is_dir() {
+                name_encoded.push('/');
                 dirs_count += 1;
             } else {
                 filesize = meta.len();
@@ -183,7 +236,17 @@ async fn read_dir_entries(
             let parent_dir = base_path.parent().unwrap_or(base_path);
             let mut base_dir = base_path;
             if base_path != parent_dir {
-                base_dir = base_path.strip_prefix(parent_dir)?;
+                base_dir = match base_path.strip_prefix(parent_dir) {
+                    Ok(v) => v,
+                    Err(err) => {
+                        tracing::error!(
+                            "unable to strip parent path prefix for `{}` (skipped): {:?}",
+                            base_path.display(),
+                            err
+                        );
+                        continue;
+                    }
+                };
             }
 
             let mut base_str = String::new();
@@ -202,7 +265,7 @@ async fn read_dir_entries(
         let modified = match parse_last_modified(meta.modified()?) {
             Ok(local_dt) => Some(local_dt),
             Err(err) => {
-                tracing::error!("error determining file last modified: {:?}", err);
+                tracing::error!("error determining the file's last modified: {:?}", err);
                 None
             }
         };
@@ -225,7 +288,7 @@ async fn read_dir_entries(
                     match sort.1.parse::<u8>() {
                         Ok(code) => order_code = code,
                         Err(err) => {
-                            tracing::debug!(
+                            tracing::error!(
                                 "sorting: query value error when converting to u8: {:?}",
                                 err
                             );


Test patch: