From 680f5e740bb9592b8728ab71f43551240a3a6159 Mon Sep 17 00:00:00 2001 From: Patrick D'appollonio <930925+patrickdappollonio@users.noreply.github.com> Date: Thu, 17 Apr 2025 17:50:51 -0700 Subject: [PATCH 1/4] Add support for Custom CSS. --- app.go | 1 + internal/server/filter.go | 4 +++- internal/server/handlers.go | 1 + internal/server/server.go | 25 +++++++++++++++++++++++++ internal/server/startup.go | 4 ++++ internal/server/templates/app.tmpl | 4 +++- internal/server/validation.go | 24 ++++++++++++++++++++++++ 7 files changed, 61 insertions(+), 2 deletions(-) diff --git a/app.go b/app.go index e90075d..42956bb 100644 --- a/app.go +++ b/app.go @@ -124,6 +124,7 @@ func run() error { flags.StringVar(&srv.CustomNotFoundPage, "custom-404", "", "custom \"page not found\" to serve") flags.IntVar(&srv.CustomNotFoundStatusCode, "custom-404-code", 0, "custtom status code for pages not found") flags.BoolVar(&srv.HideFilesInMarkdown, "hide-files-in-markdown", false, "hide file and directory listing in markdown rendering") + flags.StringVar(&srv.CustomCSS, "custom-css-file", "", "path within the served files to a custom CSS file") //nolint:wrapcheck // no need to wrap this error return rootCmd.Execute() diff --git a/internal/server/filter.go b/internal/server/filter.go index e5ef80c..b07eb02 100644 --- a/internal/server/filter.go +++ b/internal/server/filter.go @@ -1,6 +1,8 @@ package server -import "strings" +import ( + "strings" +) // forbiddenMatches is a list of filenames that are forbidden to be served. // This list is used to prevent sensitive files from being diff --git a/internal/server/handlers.go b/internal/server/handlers.go index c6c1550..0b7fc94 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -171,6 +171,7 @@ func (s *Server) walk(requestedPath string, w http.ResponseWriter, r *http.Reque "HideLinks": s.HideLinks, "MarkdownContent": markdownContent.String(), "MarkdownBeforeDir": s.MarkdownBeforeDir, + "CustomCSS": s.getCustomCSSURL(), } if err := s.templates.ExecuteTemplate(w, "app.tmpl", content); err != nil { diff --git a/internal/server/server.go b/internal/server/server.go index 427831d..f567eef 100644 --- a/internal/server/server.go +++ b/internal/server/server.go @@ -3,6 +3,8 @@ package server import ( "html/template" "io" + "path" + "strings" "github.com/patrickdappollonio/http-server/internal/redirects" ) @@ -49,6 +51,9 @@ type Server struct { JWTSigningKey string `flagName:"jwt-key" validate:"omitempty,excluded_with=Username,excluded_with=Password"` ValidateTimedJWT bool + // Custom CSS settings + CustomCSS string `flagName:"custom-css-file" validate:"omitempty,file"` + // Viper config settings ConfigFilePrefix string @@ -71,3 +76,23 @@ func (s *Server) IsBasicAuthEnabled() bool { func (s *Server) SetVersion(version string) { s.version = version } + +// Get path to custom CSS for rendering on the web and ensuring +// path prefix is set if needed +func (s *Server) getCustomCSSURL() string { + if s.CustomCSS == "" { + return "" + } + + css := s.CustomCSS + + if s.PathPrefix != "" { + css = path.Join(s.PathPrefix, s.CustomCSS) + } + + if !strings.HasPrefix(css, "/") { + css = "/" + css + } + + return css +} diff --git a/internal/server/startup.go b/internal/server/startup.go index ae9f744..a8f8cee 100644 --- a/internal/server/startup.go +++ b/internal/server/startup.go @@ -46,6 +46,10 @@ func (s *Server) PrintStartup() { fmt.Fprintln(s.LogOutput, startupPrefix, "CORS headers enabled: adding \"Access-Control-Allow-Origin=*\" header") } + if s.CustomCSS != "" { + fmt.Fprintln(s.LogOutput, startupPrefix, "Using custom CSS file:", s.CustomCSS) + } + if s.IsBasicAuthEnabled() { fmt.Fprintln(s.LogOutput, startupPrefix, "Basic authentication enabled with username:", s.Username) } diff --git a/internal/server/templates/app.tmpl b/internal/server/templates/app.tmpl index fd74207..6923692 100644 --- a/internal/server/templates/app.tmpl +++ b/internal/server/templates/app.tmpl @@ -11,7 +11,9 @@ - {{ if not .DisableMarkdown }}{{ end }} + {{- if not .DisableMarkdown }}{{ end }} + + {{- if .CustomCSS }}{{ end }} diff --git a/internal/server/validation.go b/internal/server/validation.go index 6fd7c94..e5c1516 100644 --- a/internal/server/validation.go +++ b/internal/server/validation.go @@ -5,8 +5,10 @@ import ( "fmt" "net/http" "os" + "path/filepath" "reflect" "regexp" + "strings" "github.com/go-playground/validator/v10" "github.com/patrickdappollonio/http-server/internal/utils" @@ -64,6 +66,14 @@ func (s *Server) Validate() error { return errors.New("etag max size is required: set it with --etag-max-size") } + // Validate that if custom CSS is set, that it lives in the path where + // we're serving files + if s.CustomCSS != "" { + if !validateIsFileInPath(s.Path, s.CustomCSS) { + return fmt.Errorf("css file path %q is outside the server's path %q: it must be served from the server itself", s.CustomCSS, s.Path) + } + } + size, err := utils.ParseSize(s.ETagMaxSize) if err != nil { return fmt.Errorf("unable to parse ETag max size: %w", err) @@ -108,6 +118,20 @@ func validateIsPathPrefix(field validator.FieldLevel) bool { return reIsPathPrefix.MatchString(field.Field().String()) } +func validateIsFileInPath(basepath, file string) bool { + absbasepath, err := filepath.Abs(basepath) + if err != nil { + return false + } + + absfile, err := filepath.Abs(file) + if err != nil { + return false + } + + return strings.HasPrefix(absfile, absbasepath) +} + func (s *Server) printWarningf(format string, args ...interface{}) { if s.LogOutput != nil { fmt.Fprintf(s.LogOutput, warnPrefix+format+"\n", args...) From a291669dcb609248b2180ed5663d548937b27cd5 Mon Sep 17 00:00:00 2001 From: Patrick D'appollonio <930925+patrickdappollonio@users.noreply.github.com> Date: Thu, 17 Apr 2025 18:29:21 -0700 Subject: [PATCH 2/4] Fix README descriptions. --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index cc3c36d..74271f9 100644 --- a/README.md +++ b/README.md @@ -66,6 +66,7 @@ Flags: --cors enable CORS support by setting the "Access-Control-Allow-Origin" header to "*" --custom-404 string custom "page not found" to serve --custom-404-code int custtom status code for pages not found + --custom-css-file string path within the served files to a custom CSS file --disable-cache-buster disable the cache buster for assets from the directory listing feature --disable-directory-listing disable the directory listing feature and return 404s for directories without index --disable-etag disable etag header generation @@ -75,6 +76,7 @@ Flags: --etag-max-size string maximum size for etag header generation, where bigger size = more memory usage (default "5M") --gzip enable gzip compression for supported content-types -h, --help help for http-server + --hide-files-in-markdown hide file and directory listing in markdown rendering --hide-links hide the links to this project's source code visible in the header and footer --jwt-key string signing key for JWT authentication --markdown-before-dir render markdown content before the directory listing From 2873b0ee7dcb98f6c64cd0d2f299fc15c43c3c5f Mon Sep 17 00:00:00 2001 From: Patrick D'appollonio <930925+patrickdappollonio@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:02:44 -0400 Subject: [PATCH 3/4] Also add custom CSS on full markdown files. --- internal/server/handlers.go | 1 + 1 file changed, 1 insertion(+) diff --git a/internal/server/handlers.go b/internal/server/handlers.go index 6f2866a..2b7d146 100644 --- a/internal/server/handlers.go +++ b/internal/server/handlers.go @@ -113,6 +113,7 @@ func (s *Server) serveMarkdown(requestedPath string, w http.ResponseWriter, r *h "HideLinks": s.HideLinks, "MarkdownContent": markdownContent.String(), "MarkdownBeforeDir": s.MarkdownBeforeDir, + "CustomCSS": s.getCustomCSSURL(), } if err := s.templates.ExecuteTemplate(w, "app.tmpl", content); err != nil { From bc97a4082f97357c80915fede9b84d2df9926edb Mon Sep 17 00:00:00 2001 From: Patrick D'appollonio <930925+patrickdappollonio@users.noreply.github.com> Date: Tue, 22 Apr 2025 19:11:35 -0400 Subject: [PATCH 4/4] Bump versions. --- .github/workflows/testing-commit.yaml | 2 +- .golangci.yaml | 114 +++++++++++----------- .goreleaser.yml | 3 +- internal/mdrendering/goldmark_renderer.go | 2 + 4 files changed, 64 insertions(+), 57 deletions(-) diff --git a/.github/workflows/testing-commit.yaml b/.github/workflows/testing-commit.yaml index 068447d..9d3d7e1 100644 --- a/.github/workflows/testing-commit.yaml +++ b/.github/workflows/testing-commit.yaml @@ -16,7 +16,7 @@ jobs: with: go-version-file: go.mod - name: Run GolangCI-Lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v7 with: version: latest - name: Test application diff --git a/.golangci.yaml b/.golangci.yaml index a9b98ec..c852f31 100644 --- a/.golangci.yaml +++ b/.golangci.yaml @@ -1,21 +1,16 @@ +version: "2" run: - tests: false concurrency: 5 - timeout: 3m - + tests: false linters: - disable-all: true + default: none enable: - - gosimple - - govet - - ineffassign - - staticcheck - - unused - asasalint - asciicheck - bidichk - bodyclose - contextcheck + - copyloopvar - decorder - dogsled - dupl @@ -25,23 +20,21 @@ linters: - errname - errorlint - exhaustive - - copyloopvar - ginkgolinter - gocheckcompilerdirectives - gochecksumtype - gocritic - gocyclo - - gofmt - - gofumpt - goheader - - goimports - gomodguard - goprintffuncname - gosec - gosmopolitan + - govet - grouper - importas - inamedparam + - ineffassign - ireturn - loggercheck - makezero @@ -52,7 +45,6 @@ linters: - nilerr - nilnil - noctx - - nolintlint - nonamedreturns - nosprintfhostport - paralleltest @@ -67,8 +59,7 @@ linters: - sloglint - spancheck - sqlclosecheck - - stylecheck - - tenv + - staticcheck - testableexamples - testifylint - testpackage @@ -76,47 +67,60 @@ linters: - tparallel - unconvert - unparam + - unused - usestdlibvars - wastedassign - whitespace - wrapcheck - zerologlint - -linters-settings: - perfsprint: - int-conversion: false - err-error: false - errorf: true - sprintf1: true - strconcat: false - - ireturn: - allow: - - error - - http.Handler - - gosec: - confidence: medium - excludes: - - G401 # Use of weak cryptographic primitive: we're using sha1 for etag generation - - G505 # Blocklisted import crypto/sha1: we're using sha1 for etag generation - - stylecheck: - checks: - - "all" - - "-ST1003" # this is covered by a different linter - - gocyclo: - min-complexity: 60 - - staticcheck: - checks: - - "all" - - "-SA1019" # keeping some deprecated code for compatibility - - gocritic: - enable-all: true - disabled-checks: - - appendAssign - - unnamedResult - - badRegexp + settings: + gocritic: + enable-all: true + disabled-checks: + - appendAssign + - unnamedResult + - badRegexp + gocyclo: + min-complexity: 60 + gosec: + excludes: + - G401 + - G505 + confidence: medium + ireturn: + allow: + - error + - http.Handler + perfsprint: + int-conversion: false + err-error: false + errorf: true + sprintf1: true + strconcat: false + staticcheck: + checks: + - -SA1019 + - -ST1003 + - all + exclusions: + generated: lax + presets: + - comments + - common-false-positives + - legacy + - std-error-handling + paths: + - third_party$ + - builtin$ + - examples$ +formatters: + enable: + - gofmt + - gofumpt + - goimports + exclusions: + generated: lax + paths: + - third_party$ + - builtin$ + - examples$ diff --git a/.goreleaser.yml b/.goreleaser.yml index fd96a40..6a61f4e 100644 --- a/.goreleaser.yml +++ b/.goreleaser.yml @@ -26,7 +26,8 @@ archives: {{- else }}{{ .Arch }}{{ end }} format_overrides: - goos: windows - format: zip + formats: + - zip checksum: name_template: "checksums.txt" snapshot: diff --git a/internal/mdrendering/goldmark_renderer.go b/internal/mdrendering/goldmark_renderer.go index c862a1c..10a0a88 100644 --- a/internal/mdrendering/goldmark_renderer.go +++ b/internal/mdrendering/goldmark_renderer.go @@ -46,6 +46,8 @@ func (r *HTTPServerRendering) renderImageAlign(w util.BufWriter, source []byte, } w.WriteString(`" alt="`) + + //nolint:staticcheck // skipping temporarily until we decide on keeping goldmark w.Write(util.EscapeHTML(n.Text(source))) w.WriteString(`"`) if n.Title != nil {