fileserver: configure plain browse time format (#8084)
parent
7bb95a47b2
commit
644dbe695d
|
|
@ -0,0 +1,36 @@
|
|||
:80
|
||||
|
||||
file_server {
|
||||
browse {
|
||||
plain_time_format "2006-01-02 15:04:05 MST"
|
||||
}
|
||||
}
|
||||
----------
|
||||
{
|
||||
"apps": {
|
||||
"http": {
|
||||
"servers": {
|
||||
"srv0": {
|
||||
"listen": [
|
||||
":80"
|
||||
],
|
||||
"routes": [
|
||||
{
|
||||
"handle": [
|
||||
{
|
||||
"browse": {
|
||||
"plain_time_format": "2006-01-02 15:04:05 MST"
|
||||
},
|
||||
"handler": "file_server",
|
||||
"hide": [
|
||||
"./Caddyfile"
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
:80
|
||||
|
||||
file_server {
|
||||
browse {
|
||||
plain_time_format ""
|
||||
}
|
||||
}
|
||||
----------
|
||||
plain_time_format requires a non-empty time layout argument
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
:80
|
||||
|
||||
file_server {
|
||||
browse {
|
||||
plain_time_format 2006-01-02 15:04:05 MST
|
||||
}
|
||||
}
|
||||
----------
|
||||
plain_time_format accepts exactly one time layout argument; enclose layouts containing spaces in quotes
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
:80
|
||||
|
||||
file_server {
|
||||
browse {
|
||||
plain_time_format
|
||||
}
|
||||
}
|
||||
----------
|
||||
plain_time_format is missing a time layout argument
|
||||
|
|
@ -69,10 +69,17 @@ type Browse struct {
|
|||
|
||||
// FileLimit limits the number of up to n DirEntry values in directory order.
|
||||
FileLimit int `json:"file_limit,omitempty"`
|
||||
|
||||
// PlainTimeFormat specifies the Go time layout used for modification
|
||||
// times in plain-text directory listings. The default layout is
|
||||
// `January 2, 2006 at 15:04:05`. See
|
||||
// [Go's time layout documentation](https://pkg.go.dev/time#pkg-constants).
|
||||
PlainTimeFormat string `json:"plain_time_format,omitempty"`
|
||||
}
|
||||
|
||||
const (
|
||||
defaultDirEntryLimit = 10000
|
||||
defaultDirEntryLimit = 10000
|
||||
defaultPlainTimeFormat = "January 2, 2006 at 15:04:05"
|
||||
)
|
||||
|
||||
func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w http.ResponseWriter, r *http.Request, next caddyhttp.Handler) error {
|
||||
|
|
@ -167,11 +174,19 @@ func (fsrv *FileServer) serveBrowse(fileSystem fs.FS, root, dirPath string, w ht
|
|||
return caddyhttp.Error(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
// Use the configured time format if specified, otherwise use the default
|
||||
plainTimeFormat := defaultPlainTimeFormat
|
||||
if fsrv.Browse.PlainTimeFormat != "" {
|
||||
plainTimeFormat = fsrv.Browse.PlainTimeFormat
|
||||
}
|
||||
|
||||
// Actual files
|
||||
for _, item := range listing.Items {
|
||||
//nolint:gosec // not sure how this could be XSS unless you lose control of the file system (like aren't sanitizing) and client ignores Content-Type of text/plain
|
||||
if _, err := fmt.Fprintf(writer, "%s\t%s\t%s\n",
|
||||
item.Name, item.HumanSize(), item.HumanModTime("January 2, 2006 at 15:04:05"),
|
||||
item.Name,
|
||||
item.HumanSize(),
|
||||
item.HumanModTime(plainTimeFormat),
|
||||
); err != nil {
|
||||
return caddyhttp.Error(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,88 @@
|
|||
// Copyright 2015 Matthew Holt and The Caddy Authors
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
|
||||
package fileserver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/caddyserver/caddy/v2"
|
||||
"github.com/caddyserver/caddy/v2/modules/caddyhttp"
|
||||
)
|
||||
|
||||
func TestBrowsePlainTimeFormat(t *testing.T) {
|
||||
root := t.TempDir()
|
||||
filename := filepath.Join(root, "example.txt")
|
||||
if err := os.WriteFile(filename, nil, 0o600); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
modTime := time.Date(2024, time.January, 2, 3, 4, 5, 0, time.UTC)
|
||||
if err := os.Chtimes(filename, modTime, modTime); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
layout string
|
||||
wantModTime string
|
||||
}{
|
||||
{
|
||||
name: "default",
|
||||
wantModTime: "January 2, 2024 at 03:04:05",
|
||||
},
|
||||
{
|
||||
name: "custom",
|
||||
layout: "2006-01-02T15:04:05Z07:00",
|
||||
wantModTime: "2024-01-02T03:04:05Z",
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
fsrv := FileServer{
|
||||
Root: root,
|
||||
Browse: &Browse{
|
||||
PlainTimeFormat: tc.layout,
|
||||
},
|
||||
}
|
||||
ctx, _ := caddy.NewContext(caddy.Context{Context: context.Background()})
|
||||
if err := fsrv.Provision(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
req := httptest.NewRequest(http.MethodGet, "http://example.com/", nil)
|
||||
req.Header.Set("Accept", "text/plain")
|
||||
originalReq := *req
|
||||
reqCtx := context.WithValue(req.Context(), caddy.ReplacerCtxKey, caddy.NewReplacer())
|
||||
reqCtx = context.WithValue(reqCtx, caddyhttp.OriginalRequestCtxKey, originalReq)
|
||||
req = req.WithContext(reqCtx)
|
||||
|
||||
w := httptest.NewRecorder()
|
||||
if err := fsrv.ServeHTTP(w, req, nil); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if got := w.Header().Get("Content-Type"); got != "text/plain; charset=utf-8" {
|
||||
t.Fatalf("Content-Type = %q, want %q", got, "text/plain; charset=utf-8")
|
||||
}
|
||||
if !strings.Contains(w.Body.String(), tc.wantModTime) {
|
||||
t.Fatalf("response body does not contain modification time %q:\n%s", tc.wantModTime, w.Body.String())
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +56,12 @@ func parseCaddyfile(h httpcaddyfile.Helper) (caddyhttp.MiddlewareHandler, error)
|
|||
// root <path>
|
||||
// hide <files...>
|
||||
// index <files...>
|
||||
// browse [<template_file>]
|
||||
// browse [<template_file>] {
|
||||
// reveal_symlinks
|
||||
// sort <options...>
|
||||
// file_limit <limit>
|
||||
// plain_time_format <layout>
|
||||
// }
|
||||
// precompressed <formats...>
|
||||
// status <status>
|
||||
// disable_canonical_uris
|
||||
|
|
@ -147,6 +152,21 @@ func (fsrv *FileServer) UnmarshalCaddyfile(d *caddyfile.Dispenser) error {
|
|||
return d.Err("file_limit is already enabled")
|
||||
}
|
||||
fsrv.Browse.FileLimit = val
|
||||
case "plain_time_format":
|
||||
plainTimeFormat := d.RemainingArgs()
|
||||
if len(plainTimeFormat) == 0 {
|
||||
return d.Err("plain_time_format is missing a time layout argument")
|
||||
}
|
||||
if len(plainTimeFormat) > 1 {
|
||||
return d.Err("plain_time_format accepts exactly one time layout argument; enclose layouts containing spaces in quotes")
|
||||
}
|
||||
if plainTimeFormat[0] == "" {
|
||||
return d.Err("plain_time_format requires a non-empty time layout argument")
|
||||
}
|
||||
if fsrv.Browse.PlainTimeFormat != "" {
|
||||
return d.Err("plain_time_format is already set")
|
||||
}
|
||||
fsrv.Browse.PlainTimeFormat = plainTimeFormat[0]
|
||||
default:
|
||||
return d.Errf("unknown subdirective '%s'", d.Val())
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue