diff --git a/.env-dev b/.env-dev
new file mode 100644
index 0000000..2286005
--- /dev/null
+++ b/.env-dev
@@ -0,0 +1,11 @@
+ACME_API=https://acme.mock.directory
+ACME_ACCEPT_TERMS=true
+PAGES_DOMAIN=localhost.mock.directory
+RAW_DOMAIN=raw.localhost.mock.directory
+PAGES_BRANCHES=pages,master,main
+GITEA_ROOT=https://codeberg.org
+PORT=4430
+HTTP_PORT=8880
+ENABLE_HTTP_SERVER=true
+LOG_LEVEL=trace
+ACME_ACCOUNT_CONFIG=integration/acme-account.json
diff --git a/.gitea/ISSUE_TEMPLATE/config.yml b/.gitea/ISSUE_TEMPLATE/config.yml
new file mode 100644
index 0000000..5a9cce6
--- /dev/null
+++ b/.gitea/ISSUE_TEMPLATE/config.yml
@@ -0,0 +1,5 @@
+blank_issues_enabled: true
+contact_links:
+ - name: Codeberg Pages Usage Support
+ url: https://codeberg.org/Codeberg/Community/issues/
+ about: If you need help with configuring Codeberg Pages on codeberg.org, please go here.
diff --git a/.vscode/launch.json b/.vscode/launch.json
new file mode 100644
index 0000000..d2cc8d1
--- /dev/null
+++ b/.vscode/launch.json
@@ -0,0 +1,26 @@
+{
+ // Use IntelliSense to learn about possible attributes.
+ // Hover to view descriptions of existing attributes.
+ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+ "version": "0.2.0",
+ "configurations": [
+ {
+ "name": "Launch PagesServer",
+ "type": "go",
+ "request": "launch",
+ "mode": "auto",
+ "program": "${workspaceFolder}/main.go",
+ "args": ["sqlite", "sqlite_unlock_notify", "netgo"],
+ "envFile": "${workspaceFolder}/.env-dev"
+ },
+ {
+ "name": "Launch PagesServer integration test",
+ "type": "go",
+ "request": "launch",
+ "mode": "auto",
+ "program": "${workspaceFolder}/integration/main_test.go",
+ "args": ["codeberg.org/codeberg/pages/integration/..."],
+ "buildFlags": ["-tags", "'integration sqlite sqlite_unlock_notify netgo'"]
+ }
+ ]
+}
diff --git a/.woodpecker.yml b/.woodpecker.yml
index 14172ab..c0a7d6b 100644
--- a/.woodpecker.yml
+++ b/.woodpecker.yml
@@ -1,7 +1,10 @@
+when:
+ branch: main
+
steps:
# use vendor to cache dependencies
vendor:
- image: golang:1.20
+ image: golang:1.21
commands:
- go mod vendor
diff --git a/FEATURES.md b/FEATURES.md
index 7560a1d..3d2f394 100644
--- a/FEATURES.md
+++ b/FEATURES.md
@@ -2,13 +2,19 @@
## Custom domains
-...
+Custom domains can be used by creating a `.domains` file with the domain name, e.g.:
+
+```text
+codeberg.page
+```
+
+You also have to set some DNS records, see the [Codeberg Documentation](https://docs.codeberg.org/codeberg-pages/using-custom-domain/).
## Redirects
Redirects can be created with a `_redirects` file with the following format:
-```
+```text
# Comment
from to [status]
```
@@ -30,7 +36,7 @@ from to [status]
Redirects all paths to `/index.html` for single-page apps.
-```
+```text
/* /index.html 200
```
@@ -38,7 +44,7 @@ Redirects all paths to `/index.html` for single-page apps.
Redirects every path under `/articles` to `/posts` while keeping the path.
-```
+```text
/articles/* /posts/:splat 302
```
diff --git a/Justfile b/Justfile
index 0b8f814..0bf38a3 100644
--- a/Justfile
+++ b/Justfile
@@ -4,14 +4,9 @@ TAGS := 'sqlite sqlite_unlock_notify netgo'
dev:
#!/usr/bin/env bash
set -euxo pipefail
- export ACME_API=https://acme.mock.directory
- export ACME_ACCEPT_TERMS=true
- export PAGES_DOMAIN=localhost.mock.directory
- export RAW_DOMAIN=raw.localhost.mock.directory
- export PORT=4430
- export HTTP_PORT=8880
- export ENABLE_HTTP_SERVER=true
- export LOG_LEVEL=trace
+ set -a # automatically export all variables
+ source .env-dev
+ set +a
go run -tags '{{TAGS}}' .
build:
@@ -42,10 +37,10 @@ tool-gofumpt:
fi
test:
- go test -race -cover -tags '{{TAGS}}' codeberg.org/codeberg/pages/server/... codeberg.org/codeberg/pages/html/
+ go test -race -cover -tags '{{TAGS}}' codeberg.org/codeberg/pages/config/ codeberg.org/codeberg/pages/html/ codeberg.org/codeberg/pages/server/...
test-run TEST:
- go test -race -tags '{{TAGS}}' -run "^{{TEST}}$" codeberg.org/codeberg/pages/server/... codeberg.org/codeberg/pages/html/
+ go test -race -tags '{{TAGS}}' -run "^{{TEST}}$" codeberg.org/codeberg/pages/config/ codeberg.org/codeberg/pages/html/ codeberg.org/codeberg/pages/server/...
integration:
go test -race -tags 'integration {{TAGS}}' codeberg.org/codeberg/pages/integration/...
diff --git a/README.md b/README.md
index 1cb6967..f47a196 100644
--- a/README.md
+++ b/README.md
@@ -14,8 +14,7 @@ It is suitable to be deployed by other Gitea instances, too, to offer static pag
**End user documentation** can mainly be found at the [Wiki](https://codeberg.org/Codeberg/pages-server/wiki/Overview)
and the [Codeberg Documentation](https://docs.codeberg.org/codeberg-pages/).
-
-
+
## Quickstart
@@ -61,18 +60,18 @@ but if you want to run it on a shared IP address (and not a standalone),
you'll need to configure your reverse proxy not to terminate the TLS connections,
but forward the requests on the IP level to the Pages Server.
-You can check out a proof of concept in the `haproxy-sni` folder,
-and especially have a look at [this section of the haproxy.cfg](https://codeberg.org/Codeberg/pages-server/src/branch/main/haproxy-sni/haproxy.cfg#L38).
+You can check out a proof of concept in the `examples/haproxy-sni` folder,
+and especially have a look at [this section of the haproxy.cfg](https://codeberg.org/Codeberg/pages-server/src/branch/main/examples/haproxy-sni/haproxy.cfg#L38).
-### Environment
+### Environment Variables
- `HOST` & `PORT` (default: `[::]` & `443`): listen address.
- `PAGES_DOMAIN` (default: `codeberg.page`): main domain for pages.
-- `RAW_DOMAIN` (default: `raw.codeberg.page`): domain for raw resources.
+- `RAW_DOMAIN` (default: `raw.codeberg.page`): domain for raw resources (must be subdomain of `PAGES_DOMAIN`).
- `GITEA_ROOT` (default: `https://codeberg.org`): root of the upstream Gitea instance.
- `GITEA_API_TOKEN` (default: empty): API token for the Gitea instance to access non-public (e.g. limited) repos.
-- `RAW_INFO_PAGE` (default: https://docs.codeberg.org/pages/raw-content/): info page for raw resources, shown if no resource is provided.
-- `ACME_API` (default: https://acme-v02.api.letsencrypt.org/directory): set this to https://acme.mock.director to use invalid certificates without any verification (great for debugging).
+- `RAW_INFO_PAGE` (default: ): info page for raw resources, shown if no resource is provided.
+- `ACME_API` (default: ): set this to to use invalid certificates without any verification (great for debugging).
ZeroSSL might be better in the future as it doesn't have rate limits and doesn't clash with the official Codeberg certificates (which are using Let's Encrypt), but I couldn't get it to work yet.
- `ACME_EMAIL` (default: `noreply@example.email`): Set the email sent to the ACME API server to receive, for example, renewal reminders.
- `ACME_EAB_KID` & `ACME_EAB_HMAC` (default: don't use EAB): EAB credentials, for example for ZeroSSL.
@@ -80,10 +79,10 @@ and especially have a look at [this section of the haproxy.cfg](https://codeberg
- `ACME_USE_RATE_LIMITS` (default: true): Set this to false to disable rate limits, e.g. with ZeroSSL.
- `ENABLE_HTTP_SERVER` (default: false): Set this to true to enable the HTTP-01 challenge and redirect all other HTTP requests to HTTPS. Currently only works with port 80.
- `DNS_PROVIDER` (default: use self-signed certificate): Code of the ACME DNS provider for the main domain wildcard.
- See https://go-acme.github.io/lego/dns/ for available values & additional environment variables.
+ See for available values & additional environment variables.
+- `NO_DNS_01` (default: `false`): Disable the use of ACME DNS. This means that the wildcard certificate is self-signed and all domains and subdomains will have a distinct certificate. Because this may lead to a rate limit from the ACME provider, this option is not recommended for Gitea/Forgejo instances with open registrations or a great number of users/orgs.
- `LOG_LEVEL` (default: warn): Set this to specify the level of logging.
-
## Contributing to the development
The Codeberg team is very open to your contribution.
@@ -91,9 +90,13 @@ Since we are working nicely in a team, it might be hard at times to get started
(still check out the issues, we always aim to have some things to get you started).
If you have any questions, want to work on a feature or could imagine collaborating with us for some time,
-feel free to ping us in an issue or in a general Matrix chatgroup.
+feel free to ping us in an issue or in a general [Matrix chat room](#chat-for-admins--devs).
-You can also contact the maintainers of this project:
+You can also contact the maintainer(s) of this project:
+
+- [crapStone](https://codeberg.org/crapStone) [(Matrix)](https://matrix.to/#/@crapstone:obermui.de)
+
+Previous maintainers:
- [momar](https://codeberg.org/momar) [(Matrix)](https://matrix.to/#/@moritz:wuks.space)
- [6543](https://codeberg.org/6543) [(Matrix)](https://matrix.to/#/@marddl:obermui.de)
@@ -111,11 +114,12 @@ Thank you very much.
### Test Server
-Make sure you have [golang](https://go.dev) v1.20 or newer and [just](https://just.systems/man/en/) installed.
+Make sure you have [golang](https://go.dev) v1.21 or newer and [just](https://just.systems/man/en/) installed.
run `just dev`
-now this pages should work:
- - https://cb_pages_tests.localhost.mock.directory:4430/images/827679288a.jpg
- - https://momar.localhost.mock.directory:4430/ci-testing/
- - https://momar.localhost.mock.directory:4430/pag/@master/
- - https://mock-pages.codeberg-test.org:4430/README.md
+now these pages should work:
+
+-
+-
+-
+-
diff --git a/cmd/certs.go b/cli/certs.go
similarity index 92%
rename from cmd/certs.go
rename to cli/certs.go
index 6012b6e..76df7f0 100644
--- a/cmd/certs.go
+++ b/cli/certs.go
@@ -1,4 +1,4 @@
-package cmd
+package cli
import (
"fmt"
@@ -26,7 +26,7 @@ var Certs = &cli.Command{
}
func listCerts(ctx *cli.Context) error {
- certDB, closeFn, err := openCertDB(ctx)
+ certDB, closeFn, err := OpenCertDB(ctx)
if err != nil {
return err
}
@@ -53,7 +53,7 @@ func removeCert(ctx *cli.Context) error {
domains := ctx.Args().Slice()
- certDB, closeFn, err := openCertDB(ctx)
+ certDB, closeFn, err := OpenCertDB(ctx)
if err != nil {
return err
}
diff --git a/cmd/flags.go b/cli/flags.go
similarity index 79%
rename from cmd/flags.go
rename to cli/flags.go
index a71dd35..52e1c1c 100644
--- a/cmd/flags.go
+++ b/cli/flags.go
@@ -1,4 +1,4 @@
-package cmd
+package cli
import (
"github.com/urfave/cli/v2"
@@ -29,26 +29,35 @@ var (
Name: "gitea-root",
Usage: "specifies the root URL of the Gitea instance, without a trailing slash.",
EnvVars: []string{"GITEA_ROOT"},
- Value: "https://codeberg.org",
},
// GiteaApiToken specifies an api token for the Gitea instance
&cli.StringFlag{
Name: "gitea-api-token",
Usage: "specifies an api token for the Gitea instance",
EnvVars: []string{"GITEA_API_TOKEN"},
- Value: "",
},
&cli.BoolFlag{
Name: "enable-lfs-support",
Usage: "enable lfs support, require gitea >= v1.17.0 as backend",
EnvVars: []string{"ENABLE_LFS_SUPPORT"},
- Value: true,
+ Value: false,
},
&cli.BoolFlag{
Name: "enable-symlink-support",
Usage: "follow symlinks if enabled, require gitea >= v1.18.0 as backend",
EnvVars: []string{"ENABLE_SYMLINK_SUPPORT"},
- Value: true,
+ Value: false,
+ },
+ &cli.StringFlag{
+ Name: "default-mime-type",
+ Usage: "specifies the default mime type for files that don't have a specific mime type.",
+ EnvVars: []string{"DEFAULT_MIME_TYPE"},
+ Value: "application/octet-stream",
+ },
+ &cli.StringSliceFlag{
+ Name: "forbidden-mime-types",
+ Usage: "specifies the forbidden mime types. Use this flag multiple times for multiple mime types.",
+ EnvVars: []string{"FORBIDDEN_MIME_TYPES"},
},
// ###########################
@@ -61,7 +70,6 @@ var (
Name: "pages-domain",
Usage: "specifies the main domain (starting with a dot) for which subdomains shall be served as static pages",
EnvVars: []string{"PAGES_DOMAIN"},
- Value: "codeberg.page",
},
// RawDomain specifies the domain from which raw repository content shall be served in the following format:
// https://{RawDomain}/{owner}/{repo}[/{branch|tag|commit}/{version}]/{filepath...}
@@ -70,14 +78,6 @@ var (
Name: "raw-domain",
Usage: "specifies the domain from which raw repository content shall be served, not set disable raw content hosting",
EnvVars: []string{"RAW_DOMAIN"},
- Value: "raw.codeberg.page",
- },
- // RawInfoPage will be shown (with a redirect) when trying to access RawDomain directly (or without owner/repo/path).
- &cli.StringFlag{
- Name: "raw-info-page",
- Usage: "will be shown (with a redirect) when trying to access $RAW_DOMAIN directly (or without owner/repo/path)",
- EnvVars: []string{"RAW_INFO_PAGE"},
- Value: "https://docs.codeberg.org/codeberg-pages/raw-content/",
},
// #########################
@@ -105,19 +105,38 @@ var (
Name: "enable-http-server",
Usage: "start a http server to redirect to https and respond to http acme challenges",
EnvVars: []string{"ENABLE_HTTP_SERVER"},
+ Value: false,
},
+ // Default branches to fetch assets from
+ &cli.StringSliceFlag{
+ Name: "pages-branch",
+ Usage: "define a branch to fetch assets from. Use this flag multiple times for multiple branches.",
+ EnvVars: []string{"PAGES_BRANCHES"},
+ Value: cli.NewStringSlice("pages"),
+ },
+
+ &cli.StringSliceFlag{
+ Name: "allowed-cors-domains",
+ Usage: "specify allowed CORS domains. Use this flag multiple times for multiple domains.",
+ EnvVars: []string{"ALLOWED_CORS_DOMAINS"},
+ },
+ &cli.StringSliceFlag{
+ Name: "blacklisted-paths",
+ Usage: "return an error on these url paths.Use this flag multiple times for multiple paths.",
+ EnvVars: []string{"BLACKLISTED_PATHS"},
+ },
+
&cli.StringFlag{
Name: "log-level",
Value: "warn",
Usage: "specify at which log level should be logged. Possible options: info, warn, error, fatal",
EnvVars: []string{"LOG_LEVEL"},
},
- // Default branches to fetch assets from
- &cli.StringSliceFlag{
- Name: "pages-branch",
- Usage: "define a branch to fetch assets from",
- EnvVars: []string{"PAGES_BRANCHES"},
- Value: cli.NewStringSlice("pages"),
+ &cli.StringFlag{
+ Name: "config-file",
+ Usage: "specify the location of the config file",
+ Aliases: []string{"config"},
+ EnvVars: []string{"CONFIG_FILE"},
},
// ############################
@@ -159,6 +178,11 @@ var (
Usage: "Use DNS-Challenge for main domain. Read more at: https://go-acme.github.io/lego/dns/",
EnvVars: []string{"DNS_PROVIDER"},
},
+ &cli.BoolFlag{
+ Name: "no-dns-01",
+ Usage: "Always use individual certificates instead of a DNS-01 wild card certificate",
+ EnvVars: []string{"NO_DNS_01"},
+ },
&cli.StringFlag{
Name: "acme-account-config",
Usage: "json file of acme account",
diff --git a/cli/setup.go b/cli/setup.go
new file mode 100644
index 0000000..6bbff40
--- /dev/null
+++ b/cli/setup.go
@@ -0,0 +1,39 @@
+package cli
+
+import (
+ "fmt"
+
+ "github.com/rs/zerolog/log"
+ "github.com/urfave/cli/v2"
+
+ "codeberg.org/codeberg/pages/server/database"
+ "codeberg.org/codeberg/pages/server/version"
+)
+
+func CreatePagesApp() *cli.App {
+ app := cli.NewApp()
+ app.Name = "pages-server"
+ app.Version = version.Version
+ app.Usage = "pages server"
+ app.Flags = ServerFlags
+ app.Commands = []*cli.Command{
+ Certs,
+ }
+
+ return app
+}
+
+func OpenCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err error) {
+ certDB, err = database.NewXormDB(ctx.String("db-type"), ctx.String("db-conn"))
+ if err != nil {
+ return nil, nil, fmt.Errorf("could not connect to database: %w", err)
+ }
+
+ closeFn = func() {
+ if err := certDB.Close(); err != nil {
+ log.Error().Err(err)
+ }
+ }
+
+ return certDB, closeFn, nil
+}
diff --git a/cmd/main.go b/cmd/main.go
deleted file mode 100644
index 84915c9..0000000
--- a/cmd/main.go
+++ /dev/null
@@ -1,152 +0,0 @@
-package cmd
-
-import (
- "context"
- "crypto/tls"
- "fmt"
- "net"
- "net/http"
- "os"
- "strings"
- "time"
-
- "github.com/rs/zerolog"
- "github.com/rs/zerolog/log"
- "github.com/urfave/cli/v2"
-
- "codeberg.org/codeberg/pages/server/cache"
- "codeberg.org/codeberg/pages/server/certificates"
- "codeberg.org/codeberg/pages/server/gitea"
- "codeberg.org/codeberg/pages/server/handler"
-)
-
-// AllowedCorsDomains lists the domains for which Cross-Origin Resource Sharing is allowed.
-// TODO: make it a flag
-var AllowedCorsDomains = []string{
- "fonts.codeberg.org",
- "design.codeberg.org",
-}
-
-// BlacklistedPaths specifies forbidden path prefixes for all Codeberg Pages.
-// TODO: Make it a flag too
-var BlacklistedPaths = []string{
- "/.well-known/acme-challenge/",
-}
-
-// Serve sets up and starts the web server.
-func Serve(ctx *cli.Context) error {
- // Initialize the logger.
- logLevel, err := zerolog.ParseLevel(ctx.String("log-level"))
- if err != nil {
- return err
- }
- log.Logger = zerolog.New(zerolog.ConsoleWriter{Out: os.Stderr}).With().Timestamp().Logger().Level(logLevel)
-
- giteaRoot := ctx.String("gitea-root")
- giteaAPIToken := ctx.String("gitea-api-token")
- rawDomain := ctx.String("raw-domain")
- defaultBranches := ctx.StringSlice("pages-branch")
- mainDomainSuffix := ctx.String("pages-domain")
- rawInfoPage := ctx.String("raw-info-page")
- listeningHost := ctx.String("host")
- listeningSSLPort := ctx.Uint("port")
- listeningSSLAddress := fmt.Sprintf("%s:%d", listeningHost, listeningSSLPort)
- listeningHTTPAddress := fmt.Sprintf("%s:%d", listeningHost, ctx.Uint("http-port"))
- enableHTTPServer := ctx.Bool("enable-http-server")
-
- allowedCorsDomains := AllowedCorsDomains
- if rawDomain != "" {
- allowedCorsDomains = append(allowedCorsDomains, rawDomain)
- }
-
- // Make sure MainDomain has a trailing dot
- if !strings.HasPrefix(mainDomainSuffix, ".") {
- mainDomainSuffix = "." + mainDomainSuffix
- }
-
- if len(defaultBranches) == 0 {
- return fmt.Errorf("no default branches set (PAGES_BRANCHES)")
- }
-
- // Init ssl cert database
- certDB, closeFn, err := openCertDB(ctx)
- if err != nil {
- return err
- }
- defer closeFn()
-
- keyCache := cache.NewKeyValueCache()
- challengeCache := cache.NewKeyValueCache()
- // canonicalDomainCache stores canonical domains
- canonicalDomainCache := cache.NewKeyValueCache()
- // dnsLookupCache stores DNS lookups for custom domains
- dnsLookupCache := cache.NewKeyValueCache()
- // redirectsCache stores redirects in _redirects files
- redirectsCache := cache.NewKeyValueCache()
- // clientResponseCache stores responses from the Gitea server
- clientResponseCache := cache.NewKeyValueCache()
-
- giteaClient, err := gitea.NewClient(giteaRoot, giteaAPIToken, clientResponseCache, ctx.Bool("enable-symlink-support"), ctx.Bool("enable-lfs-support"))
- if err != nil {
- return fmt.Errorf("could not create new gitea client: %v", err)
- }
-
- acmeClient, err := createAcmeClient(ctx, enableHTTPServer, challengeCache)
- if err != nil {
- return err
- }
-
- if err := certificates.SetupMainDomainCertificates(mainDomainSuffix, acmeClient, certDB); err != nil {
- return err
- }
-
- // Create listener for SSL connections
- log.Info().Msgf("Create TCP listener for SSL on %s", listeningSSLAddress)
- listener, err := net.Listen("tcp", listeningSSLAddress)
- if err != nil {
- return fmt.Errorf("couldn't create listener: %v", err)
- }
-
- // Setup listener for SSL connections
- listener = tls.NewListener(listener, certificates.TLSConfig(mainDomainSuffix,
- giteaClient,
- acmeClient,
- defaultBranches[0],
- keyCache, challengeCache, dnsLookupCache, canonicalDomainCache,
- certDB))
-
- interval := 12 * time.Hour
- certMaintainCtx, cancelCertMaintain := context.WithCancel(context.Background())
- defer cancelCertMaintain()
- go certificates.MaintainCertDB(certMaintainCtx, interval, acmeClient, mainDomainSuffix, certDB)
-
- if enableHTTPServer {
- // Create handler for http->https redirect and http acme challenges
- httpHandler := certificates.SetupHTTPACMEChallengeServer(challengeCache, listeningSSLPort)
-
- // Create listener for http and start listening
- go func() {
- log.Info().Msgf("Start HTTP server listening on %s", listeningHTTPAddress)
- err := http.ListenAndServe(listeningHTTPAddress, httpHandler)
- if err != nil {
- log.Panic().Err(err).Msg("Couldn't start HTTP fastServer")
- }
- }()
- }
-
- // Create ssl handler based on settings
- sslHandler := handler.Handler(mainDomainSuffix, rawDomain,
- giteaClient,
- rawInfoPage,
- BlacklistedPaths, allowedCorsDomains,
- defaultBranches,
- dnsLookupCache, canonicalDomainCache, redirectsCache)
-
- // Start the ssl listener
- log.Info().Msgf("Start SSL server using TCP listener on %s", listener.Addr())
- if err := http.Serve(listener, sslHandler); err != nil {
- log.Panic().Err(err).Msg("Couldn't start fastServer")
- }
-
- return nil
-}
diff --git a/cmd/setup.go b/cmd/setup.go
deleted file mode 100644
index cde4bc9..0000000
--- a/cmd/setup.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package cmd
-
-import (
- "errors"
- "fmt"
-
- "github.com/rs/zerolog/log"
- "github.com/urfave/cli/v2"
-
- "codeberg.org/codeberg/pages/server/cache"
- "codeberg.org/codeberg/pages/server/certificates"
- "codeberg.org/codeberg/pages/server/database"
-)
-
-var ErrAcmeMissConfig = errors.New("ACME client has wrong config")
-
-func openCertDB(ctx *cli.Context) (certDB database.CertDB, closeFn func(), err error) {
- certDB, err = database.NewXormDB(ctx.String("db-type"), ctx.String("db-conn"))
- if err != nil {
- return nil, nil, fmt.Errorf("could not connect to database: %w", err)
- }
-
- closeFn = func() {
- if err := certDB.Close(); err != nil {
- log.Error().Err(err)
- }
- }
-
- return certDB, closeFn, nil
-}
-
-func createAcmeClient(ctx *cli.Context, enableHTTPServer bool, challengeCache cache.SetGetKey) (*certificates.AcmeClient, error) {
- acmeAPI := ctx.String("acme-api-endpoint")
- acmeMail := ctx.String("acme-email")
- acmeEabHmac := ctx.String("acme-eab-hmac")
- acmeEabKID := ctx.String("acme-eab-kid")
- acmeAcceptTerms := ctx.Bool("acme-accept-terms")
- dnsProvider := ctx.String("dns-provider")
- acmeUseRateLimits := ctx.Bool("acme-use-rate-limits")
- acmeAccountConf := ctx.String("acme-account-config")
-
- // check config
- if (!acmeAcceptTerms || dnsProvider == "") && acmeAPI != "https://acme.mock.directory" {
- return nil, fmt.Errorf("%w: you must set $ACME_ACCEPT_TERMS and $DNS_PROVIDER, unless $ACME_API is set to https://acme.mock.directory", ErrAcmeMissConfig)
- }
- if acmeEabHmac != "" && acmeEabKID == "" {
- return nil, fmt.Errorf("%w: ACME_EAB_HMAC also needs ACME_EAB_KID to be set", ErrAcmeMissConfig)
- } else if acmeEabHmac == "" && acmeEabKID != "" {
- return nil, fmt.Errorf("%w: ACME_EAB_KID also needs ACME_EAB_HMAC to be set", ErrAcmeMissConfig)
- }
-
- return certificates.NewAcmeClient(
- acmeAccountConf,
- acmeAPI,
- acmeMail,
- acmeEabHmac,
- acmeEabKID,
- dnsProvider,
- acmeAcceptTerms,
- enableHTTPServer,
- acmeUseRateLimits,
- challengeCache,
- )
-}
diff --git a/config/assets/test_config.toml b/config/assets/test_config.toml
new file mode 100644
index 0000000..6a2f0d0
--- /dev/null
+++ b/config/assets/test_config.toml
@@ -0,0 +1,33 @@
+logLevel = 'trace'
+
+[server]
+host = '127.0.0.1'
+port = 443
+httpPort = 80
+httpServerEnabled = true
+mainDomain = 'codeberg.page'
+rawDomain = 'raw.codeberg.page'
+allowedCorsDomains = ['fonts.codeberg.org', 'design.codeberg.org']
+blacklistedPaths = ['do/not/use']
+
+[gitea]
+root = 'codeberg.org'
+token = 'XXXXXXXX'
+lfsEnabled = true
+followSymlinks = true
+defaultMimeType = "application/wasm"
+forbiddenMimeTypes = ["text/html"]
+
+[database]
+type = 'sqlite'
+conn = 'certs.sqlite'
+
+[ACME]
+email = 'a@b.c'
+apiEndpoint = 'https://example.com'
+acceptTerms = false
+useRateLimits = true
+eab_hmac = 'asdf'
+eab_kid = 'qwer'
+dnsProvider = 'cloudflare.com'
+accountConfigFile = 'nope'
diff --git a/config/config.go b/config/config.go
new file mode 100644
index 0000000..0146e0f
--- /dev/null
+++ b/config/config.go
@@ -0,0 +1,47 @@
+package config
+
+type Config struct {
+ LogLevel string `default:"warn"`
+ Server ServerConfig
+ Gitea GiteaConfig
+ Database DatabaseConfig
+ ACME ACMEConfig
+}
+
+type ServerConfig struct {
+ Host string `default:"[::]"`
+ Port uint16 `default:"443"`
+ HttpPort uint16 `default:"80"`
+ HttpServerEnabled bool `default:"true"`
+ MainDomain string
+ RawDomain string
+ PagesBranches []string
+ AllowedCorsDomains []string
+ BlacklistedPaths []string
+}
+
+type GiteaConfig struct {
+ Root string
+ Token string
+ LFSEnabled bool `default:"false"`
+ FollowSymlinks bool `default:"false"`
+ DefaultMimeType string `default:"application/octet-stream"`
+ ForbiddenMimeTypes []string
+}
+
+type DatabaseConfig struct {
+ Type string `default:"sqlite3"`
+ Conn string `default:"certs.sqlite"`
+}
+
+type ACMEConfig struct {
+ Email string
+ APIEndpoint string `default:"https://acme-v02.api.letsencrypt.org/directory"`
+ AcceptTerms bool `default:"false"`
+ UseRateLimits bool `default:"true"`
+ EAB_HMAC string
+ EAB_KID string
+ DNSProvider string
+ NoDNS01 bool `default:"false"`
+ AccountConfigFile string `default:"acme-account.json"`
+}
diff --git a/config/setup.go b/config/setup.go
new file mode 100644
index 0000000..6a2aa62
--- /dev/null
+++ b/config/setup.go
@@ -0,0 +1,150 @@
+package config
+
+import (
+ "os"
+ "path"
+
+ "github.com/creasty/defaults"
+ "github.com/pelletier/go-toml/v2"
+ "github.com/rs/zerolog/log"
+ "github.com/urfave/cli/v2"
+)
+
+var ALWAYS_BLACKLISTED_PATHS = []string{
+ "/.well-known/acme-challenge/",
+}
+
+func NewDefaultConfig() Config {
+ config := Config{}
+ if err := defaults.Set(&config); err != nil {
+ panic(err)
+ }
+
+ // defaults does not support setting arrays from strings
+ config.Server.PagesBranches = []string{"main", "master", "pages"}
+
+ return config
+}
+
+func ReadConfig(ctx *cli.Context) (*Config, error) {
+ config := NewDefaultConfig()
+ // if config is not given as argument return empty config
+ if !ctx.IsSet("config-file") {
+ return &config, nil
+ }
+
+ configFile := path.Clean(ctx.String("config-file"))
+
+ log.Debug().Str("config-file", configFile).Msg("reading config file")
+ content, err := os.ReadFile(configFile)
+ if err != nil {
+ return nil, err
+ }
+
+ err = toml.Unmarshal(content, &config)
+ return &config, err
+}
+
+func MergeConfig(ctx *cli.Context, config *Config) {
+ if ctx.IsSet("log-level") {
+ config.LogLevel = ctx.String("log-level")
+ }
+
+ mergeServerConfig(ctx, &config.Server)
+ mergeGiteaConfig(ctx, &config.Gitea)
+ mergeDatabaseConfig(ctx, &config.Database)
+ mergeACMEConfig(ctx, &config.ACME)
+}
+
+func mergeServerConfig(ctx *cli.Context, config *ServerConfig) {
+ if ctx.IsSet("host") {
+ config.Host = ctx.String("host")
+ }
+ if ctx.IsSet("port") {
+ config.Port = uint16(ctx.Uint("port"))
+ }
+ if ctx.IsSet("http-port") {
+ config.HttpPort = uint16(ctx.Uint("http-port"))
+ }
+ if ctx.IsSet("enable-http-server") {
+ config.HttpServerEnabled = ctx.Bool("enable-http-server")
+ }
+ if ctx.IsSet("pages-domain") {
+ config.MainDomain = ctx.String("pages-domain")
+ }
+ if ctx.IsSet("raw-domain") {
+ config.RawDomain = ctx.String("raw-domain")
+ }
+ if ctx.IsSet("pages-branch") {
+ config.PagesBranches = ctx.StringSlice("pages-branch")
+ }
+ if ctx.IsSet("allowed-cors-domains") {
+ config.AllowedCorsDomains = ctx.StringSlice("allowed-cors-domains")
+ }
+ if ctx.IsSet("blacklisted-paths") {
+ config.BlacklistedPaths = ctx.StringSlice("blacklisted-paths")
+ }
+
+ // add the paths that should always be blacklisted
+ config.BlacklistedPaths = append(config.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
+}
+
+func mergeGiteaConfig(ctx *cli.Context, config *GiteaConfig) {
+ if ctx.IsSet("gitea-root") {
+ config.Root = ctx.String("gitea-root")
+ }
+ if ctx.IsSet("gitea-api-token") {
+ config.Token = ctx.String("gitea-api-token")
+ }
+ if ctx.IsSet("enable-lfs-support") {
+ config.LFSEnabled = ctx.Bool("enable-lfs-support")
+ }
+ if ctx.IsSet("enable-symlink-support") {
+ config.FollowSymlinks = ctx.Bool("enable-symlink-support")
+ }
+ if ctx.IsSet("default-mime-type") {
+ config.DefaultMimeType = ctx.String("default-mime-type")
+ }
+ if ctx.IsSet("forbidden-mime-types") {
+ config.ForbiddenMimeTypes = ctx.StringSlice("forbidden-mime-types")
+ }
+}
+
+func mergeDatabaseConfig(ctx *cli.Context, config *DatabaseConfig) {
+ if ctx.IsSet("db-type") {
+ config.Type = ctx.String("db-type")
+ }
+ if ctx.IsSet("db-conn") {
+ config.Conn = ctx.String("db-conn")
+ }
+}
+
+func mergeACMEConfig(ctx *cli.Context, config *ACMEConfig) {
+ if ctx.IsSet("acme-email") {
+ config.Email = ctx.String("acme-email")
+ }
+ if ctx.IsSet("acme-api-endpoint") {
+ config.APIEndpoint = ctx.String("acme-api-endpoint")
+ }
+ if ctx.IsSet("acme-accept-terms") {
+ config.AcceptTerms = ctx.Bool("acme-accept-terms")
+ }
+ if ctx.IsSet("acme-use-rate-limits") {
+ config.UseRateLimits = ctx.Bool("acme-use-rate-limits")
+ }
+ if ctx.IsSet("acme-eab-hmac") {
+ config.EAB_HMAC = ctx.String("acme-eab-hmac")
+ }
+ if ctx.IsSet("acme-eab-kid") {
+ config.EAB_KID = ctx.String("acme-eab-kid")
+ }
+ if ctx.IsSet("dns-provider") {
+ config.DNSProvider = ctx.String("dns-provider")
+ }
+ if ctx.IsSet("no-dns-01") {
+ config.NoDNS01 = ctx.Bool("no-dns-01")
+ }
+ if ctx.IsSet("acme-account-config") {
+ config.AccountConfigFile = ctx.String("acme-account-config")
+ }
+}
diff --git a/config/setup_test.go b/config/setup_test.go
new file mode 100644
index 0000000..1a32740
--- /dev/null
+++ b/config/setup_test.go
@@ -0,0 +1,603 @@
+package config
+
+import (
+ "context"
+ "os"
+ "testing"
+
+ "github.com/pelletier/go-toml/v2"
+ "github.com/stretchr/testify/assert"
+ "github.com/urfave/cli/v2"
+
+ cmd "codeberg.org/codeberg/pages/cli"
+)
+
+func runApp(t *testing.T, fn func(*cli.Context) error, args []string) {
+ app := cmd.CreatePagesApp()
+ app.Action = fn
+
+ appCtx, appCancel := context.WithCancel(context.Background())
+ defer appCancel()
+
+ // os.Args always contains the binary name
+ args = append([]string{"testing"}, args...)
+
+ err := app.RunContext(appCtx, args)
+ assert.NoError(t, err)
+}
+
+// fixArrayFromCtx fixes the number of "changed" strings in a string slice according to the number of values in the context.
+// This is a workaround because the cli library has a bug where the number of values in the context gets bigger the more tests are run.
+func fixArrayFromCtx(ctx *cli.Context, key string, expected []string) []string {
+ if ctx.IsSet(key) {
+ ctxSlice := ctx.StringSlice(key)
+
+ if len(ctxSlice) > 1 {
+ for i := 1; i < len(ctxSlice); i++ {
+ expected = append([]string{"changed"}, expected...)
+ }
+ }
+ }
+
+ return expected
+}
+
+func readTestConfig() (*Config, error) {
+ content, err := os.ReadFile("assets/test_config.toml")
+ if err != nil {
+ return nil, err
+ }
+
+ expectedConfig := NewDefaultConfig()
+ err = toml.Unmarshal(content, &expectedConfig)
+ if err != nil {
+ return nil, err
+ }
+
+ return &expectedConfig, nil
+}
+
+func TestReadConfigShouldReturnEmptyConfigWhenConfigArgEmpty(t *testing.T) {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg, err := ReadConfig(ctx)
+ expected := NewDefaultConfig()
+ assert.Equal(t, &expected, cfg)
+
+ return err
+ },
+ []string{},
+ )
+}
+
+func TestReadConfigShouldReturnConfigFromFileWhenConfigArgPresent(t *testing.T) {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg, err := ReadConfig(ctx)
+ if err != nil {
+ return err
+ }
+
+ expectedConfig, err := readTestConfig()
+ if err != nil {
+ return err
+ }
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ []string{"--config-file", "assets/test_config.toml"},
+ )
+}
+
+func TestValuesReadFromConfigFileShouldBeOverwrittenByArgs(t *testing.T) {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg, err := ReadConfig(ctx)
+ if err != nil {
+ return err
+ }
+
+ MergeConfig(ctx, cfg)
+
+ expectedConfig, err := readTestConfig()
+ if err != nil {
+ return err
+ }
+
+ expectedConfig.LogLevel = "debug"
+ expectedConfig.Gitea.Root = "not-codeberg.org"
+ expectedConfig.ACME.AcceptTerms = true
+ expectedConfig.Server.Host = "172.17.0.2"
+ expectedConfig.Server.BlacklistedPaths = append(expectedConfig.Server.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ []string{
+ "--config-file", "assets/test_config.toml",
+ "--log-level", "debug",
+ "--gitea-root", "not-codeberg.org",
+ "--acme-accept-terms",
+ "--host", "172.17.0.2",
+ },
+ )
+}
+
+func TestMergeConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg := &Config{
+ LogLevel: "original",
+ Server: ServerConfig{
+ Host: "original",
+ Port: 8080,
+ HttpPort: 80,
+ HttpServerEnabled: false,
+ MainDomain: "original",
+ RawDomain: "original",
+ PagesBranches: []string{"original"},
+ AllowedCorsDomains: []string{"original"},
+ BlacklistedPaths: []string{"original"},
+ },
+ Gitea: GiteaConfig{
+ Root: "original",
+ Token: "original",
+ LFSEnabled: false,
+ FollowSymlinks: false,
+ DefaultMimeType: "original",
+ ForbiddenMimeTypes: []string{"original"},
+ },
+ Database: DatabaseConfig{
+ Type: "original",
+ Conn: "original",
+ },
+ ACME: ACMEConfig{
+ Email: "original",
+ APIEndpoint: "original",
+ AcceptTerms: false,
+ UseRateLimits: false,
+ EAB_HMAC: "original",
+ EAB_KID: "original",
+ DNSProvider: "original",
+ NoDNS01: false,
+ AccountConfigFile: "original",
+ },
+ }
+
+ MergeConfig(ctx, cfg)
+
+ expectedConfig := &Config{
+ LogLevel: "changed",
+ Server: ServerConfig{
+ Host: "changed",
+ Port: 8443,
+ HttpPort: 443,
+ HttpServerEnabled: true,
+ MainDomain: "changed",
+ RawDomain: "changed",
+ PagesBranches: []string{"changed"},
+ AllowedCorsDomains: []string{"changed"},
+ BlacklistedPaths: append([]string{"changed"}, ALWAYS_BLACKLISTED_PATHS...),
+ },
+ Gitea: GiteaConfig{
+ Root: "changed",
+ Token: "changed",
+ LFSEnabled: true,
+ FollowSymlinks: true,
+ DefaultMimeType: "changed",
+ ForbiddenMimeTypes: []string{"changed"},
+ },
+ Database: DatabaseConfig{
+ Type: "changed",
+ Conn: "changed",
+ },
+ ACME: ACMEConfig{
+ Email: "changed",
+ APIEndpoint: "changed",
+ AcceptTerms: true,
+ UseRateLimits: true,
+ EAB_HMAC: "changed",
+ EAB_KID: "changed",
+ DNSProvider: "changed",
+ NoDNS01: true,
+ AccountConfigFile: "changed",
+ },
+ }
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ []string{
+ "--log-level", "changed",
+ // Server
+ "--pages-domain", "changed",
+ "--raw-domain", "changed",
+ "--allowed-cors-domains", "changed",
+ "--blacklisted-paths", "changed",
+ "--pages-branch", "changed",
+ "--host", "changed",
+ "--port", "8443",
+ "--http-port", "443",
+ "--enable-http-server",
+ // Gitea
+ "--gitea-root", "changed",
+ "--gitea-api-token", "changed",
+ "--enable-lfs-support",
+ "--enable-symlink-support",
+ "--default-mime-type", "changed",
+ "--forbidden-mime-types", "changed",
+ // Database
+ "--db-type", "changed",
+ "--db-conn", "changed",
+ // ACME
+ "--acme-email", "changed",
+ "--acme-api-endpoint", "changed",
+ "--acme-accept-terms",
+ "--acme-use-rate-limits",
+ "--acme-eab-hmac", "changed",
+ "--acme-eab-kid", "changed",
+ "--dns-provider", "changed",
+ "--no-dns-01",
+ "--acme-account-config", "changed",
+ },
+ )
+}
+
+func TestMergeServerConfigShouldAddDefaultBlacklistedPathsToBlacklistedPaths(t *testing.T) {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg := &ServerConfig{}
+ mergeServerConfig(ctx, cfg)
+
+ expected := ALWAYS_BLACKLISTED_PATHS
+ assert.Equal(t, expected, cfg.BlacklistedPaths)
+
+ return nil
+ },
+ []string{},
+ )
+}
+
+func TestMergeServerConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+ for range []uint8{0, 1} {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg := &ServerConfig{
+ Host: "original",
+ Port: 8080,
+ HttpPort: 80,
+ HttpServerEnabled: false,
+ MainDomain: "original",
+ RawDomain: "original",
+ AllowedCorsDomains: []string{"original"},
+ BlacklistedPaths: []string{"original"},
+ }
+
+ mergeServerConfig(ctx, cfg)
+
+ expectedConfig := &ServerConfig{
+ Host: "changed",
+ Port: 8443,
+ HttpPort: 443,
+ HttpServerEnabled: true,
+ MainDomain: "changed",
+ RawDomain: "changed",
+ AllowedCorsDomains: fixArrayFromCtx(ctx, "allowed-cors-domains", []string{"changed"}),
+ BlacklistedPaths: fixArrayFromCtx(ctx, "blacklisted-paths", append([]string{"changed"}, ALWAYS_BLACKLISTED_PATHS...)),
+ }
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ []string{
+ "--pages-domain", "changed",
+ "--raw-domain", "changed",
+ "--allowed-cors-domains", "changed",
+ "--blacklisted-paths", "changed",
+ "--host", "changed",
+ "--port", "8443",
+ "--http-port", "443",
+ "--enable-http-server",
+ },
+ )
+ }
+}
+
+func TestMergeServerConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
+ type testValuePair struct {
+ args []string
+ callback func(*ServerConfig)
+ }
+ testValuePairs := []testValuePair{
+ {args: []string{"--host", "changed"}, callback: func(sc *ServerConfig) { sc.Host = "changed" }},
+ {args: []string{"--port", "8443"}, callback: func(sc *ServerConfig) { sc.Port = 8443 }},
+ {args: []string{"--http-port", "443"}, callback: func(sc *ServerConfig) { sc.HttpPort = 443 }},
+ {args: []string{"--enable-http-server"}, callback: func(sc *ServerConfig) { sc.HttpServerEnabled = true }},
+ {args: []string{"--pages-domain", "changed"}, callback: func(sc *ServerConfig) { sc.MainDomain = "changed" }},
+ {args: []string{"--raw-domain", "changed"}, callback: func(sc *ServerConfig) { sc.RawDomain = "changed" }},
+ {args: []string{"--pages-branch", "changed"}, callback: func(sc *ServerConfig) { sc.PagesBranches = []string{"changed"} }},
+ {args: []string{"--allowed-cors-domains", "changed"}, callback: func(sc *ServerConfig) { sc.AllowedCorsDomains = []string{"changed"} }},
+ {args: []string{"--blacklisted-paths", "changed"}, callback: func(sc *ServerConfig) { sc.BlacklistedPaths = []string{"changed"} }},
+ }
+
+ for _, pair := range testValuePairs {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg := ServerConfig{
+ Host: "original",
+ Port: 8080,
+ HttpPort: 80,
+ HttpServerEnabled: false,
+ MainDomain: "original",
+ RawDomain: "original",
+ PagesBranches: []string{"original"},
+ AllowedCorsDomains: []string{"original"},
+ BlacklistedPaths: []string{"original"},
+ }
+
+ expectedConfig := cfg
+ pair.callback(&expectedConfig)
+ expectedConfig.BlacklistedPaths = append(expectedConfig.BlacklistedPaths, ALWAYS_BLACKLISTED_PATHS...)
+
+ expectedConfig.PagesBranches = fixArrayFromCtx(ctx, "pages-branch", expectedConfig.PagesBranches)
+ expectedConfig.AllowedCorsDomains = fixArrayFromCtx(ctx, "allowed-cors-domains", expectedConfig.AllowedCorsDomains)
+ expectedConfig.BlacklistedPaths = fixArrayFromCtx(ctx, "blacklisted-paths", expectedConfig.BlacklistedPaths)
+
+ mergeServerConfig(ctx, &cfg)
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ pair.args,
+ )
+ }
+}
+
+func TestMergeGiteaConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg := &GiteaConfig{
+ Root: "original",
+ Token: "original",
+ LFSEnabled: false,
+ FollowSymlinks: false,
+ DefaultMimeType: "original",
+ ForbiddenMimeTypes: []string{"original"},
+ }
+
+ mergeGiteaConfig(ctx, cfg)
+
+ expectedConfig := &GiteaConfig{
+ Root: "changed",
+ Token: "changed",
+ LFSEnabled: true,
+ FollowSymlinks: true,
+ DefaultMimeType: "changed",
+ ForbiddenMimeTypes: fixArrayFromCtx(ctx, "forbidden-mime-types", []string{"changed"}),
+ }
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ []string{
+ "--gitea-root", "changed",
+ "--gitea-api-token", "changed",
+ "--enable-lfs-support",
+ "--enable-symlink-support",
+ "--default-mime-type", "changed",
+ "--forbidden-mime-types", "changed",
+ },
+ )
+}
+
+func TestMergeGiteaConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
+ type testValuePair struct {
+ args []string
+ callback func(*GiteaConfig)
+ }
+ testValuePairs := []testValuePair{
+ {args: []string{"--gitea-root", "changed"}, callback: func(gc *GiteaConfig) { gc.Root = "changed" }},
+ {args: []string{"--gitea-api-token", "changed"}, callback: func(gc *GiteaConfig) { gc.Token = "changed" }},
+ {args: []string{"--enable-lfs-support"}, callback: func(gc *GiteaConfig) { gc.LFSEnabled = true }},
+ {args: []string{"--enable-symlink-support"}, callback: func(gc *GiteaConfig) { gc.FollowSymlinks = true }},
+ {args: []string{"--default-mime-type", "changed"}, callback: func(gc *GiteaConfig) { gc.DefaultMimeType = "changed" }},
+ {args: []string{"--forbidden-mime-types", "changed"}, callback: func(gc *GiteaConfig) { gc.ForbiddenMimeTypes = []string{"changed"} }},
+ }
+
+ for _, pair := range testValuePairs {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg := GiteaConfig{
+ Root: "original",
+ Token: "original",
+ LFSEnabled: false,
+ FollowSymlinks: false,
+ DefaultMimeType: "original",
+ ForbiddenMimeTypes: []string{"original"},
+ }
+
+ expectedConfig := cfg
+ pair.callback(&expectedConfig)
+
+ mergeGiteaConfig(ctx, &cfg)
+
+ expectedConfig.ForbiddenMimeTypes = fixArrayFromCtx(ctx, "forbidden-mime-types", expectedConfig.ForbiddenMimeTypes)
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ pair.args,
+ )
+ }
+}
+
+func TestMergeDatabaseConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg := &DatabaseConfig{
+ Type: "original",
+ Conn: "original",
+ }
+
+ mergeDatabaseConfig(ctx, cfg)
+
+ expectedConfig := &DatabaseConfig{
+ Type: "changed",
+ Conn: "changed",
+ }
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ []string{
+ "--db-type", "changed",
+ "--db-conn", "changed",
+ },
+ )
+}
+
+func TestMergeDatabaseConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
+ type testValuePair struct {
+ args []string
+ callback func(*DatabaseConfig)
+ }
+ testValuePairs := []testValuePair{
+ {args: []string{"--db-type", "changed"}, callback: func(gc *DatabaseConfig) { gc.Type = "changed" }},
+ {args: []string{"--db-conn", "changed"}, callback: func(gc *DatabaseConfig) { gc.Conn = "changed" }},
+ }
+
+ for _, pair := range testValuePairs {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg := DatabaseConfig{
+ Type: "original",
+ Conn: "original",
+ }
+
+ expectedConfig := cfg
+ pair.callback(&expectedConfig)
+
+ mergeDatabaseConfig(ctx, &cfg)
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ pair.args,
+ )
+ }
+}
+
+func TestMergeACMEConfigShouldReplaceAllExistingValuesGivenAllArgsExist(t *testing.T) {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg := &ACMEConfig{
+ Email: "original",
+ APIEndpoint: "original",
+ AcceptTerms: false,
+ UseRateLimits: false,
+ EAB_HMAC: "original",
+ EAB_KID: "original",
+ DNSProvider: "original",
+ NoDNS01: false,
+ AccountConfigFile: "original",
+ }
+
+ mergeACMEConfig(ctx, cfg)
+
+ expectedConfig := &ACMEConfig{
+ Email: "changed",
+ APIEndpoint: "changed",
+ AcceptTerms: true,
+ UseRateLimits: true,
+ EAB_HMAC: "changed",
+ EAB_KID: "changed",
+ DNSProvider: "changed",
+ NoDNS01: true,
+ AccountConfigFile: "changed",
+ }
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ []string{
+ "--acme-email", "changed",
+ "--acme-api-endpoint", "changed",
+ "--acme-accept-terms",
+ "--acme-use-rate-limits",
+ "--acme-eab-hmac", "changed",
+ "--acme-eab-kid", "changed",
+ "--dns-provider", "changed",
+ "--no-dns-01",
+ "--acme-account-config", "changed",
+ },
+ )
+}
+
+func TestMergeACMEConfigShouldReplaceOnlyOneValueExistingValueGivenOnlyOneArgExists(t *testing.T) {
+ type testValuePair struct {
+ args []string
+ callback func(*ACMEConfig)
+ }
+ testValuePairs := []testValuePair{
+ {args: []string{"--acme-email", "changed"}, callback: func(gc *ACMEConfig) { gc.Email = "changed" }},
+ {args: []string{"--acme-api-endpoint", "changed"}, callback: func(gc *ACMEConfig) { gc.APIEndpoint = "changed" }},
+ {args: []string{"--acme-accept-terms"}, callback: func(gc *ACMEConfig) { gc.AcceptTerms = true }},
+ {args: []string{"--acme-use-rate-limits"}, callback: func(gc *ACMEConfig) { gc.UseRateLimits = true }},
+ {args: []string{"--acme-eab-hmac", "changed"}, callback: func(gc *ACMEConfig) { gc.EAB_HMAC = "changed" }},
+ {args: []string{"--acme-eab-kid", "changed"}, callback: func(gc *ACMEConfig) { gc.EAB_KID = "changed" }},
+ {args: []string{"--dns-provider", "changed"}, callback: func(gc *ACMEConfig) { gc.DNSProvider = "changed" }},
+ {args: []string{"--no-dns-01"}, callback: func(gc *ACMEConfig) { gc.NoDNS01 = true }},
+ {args: []string{"--acme-account-config", "changed"}, callback: func(gc *ACMEConfig) { gc.AccountConfigFile = "changed" }},
+ }
+
+ for _, pair := range testValuePairs {
+ runApp(
+ t,
+ func(ctx *cli.Context) error {
+ cfg := ACMEConfig{
+ Email: "original",
+ APIEndpoint: "original",
+ AcceptTerms: false,
+ UseRateLimits: false,
+ EAB_HMAC: "original",
+ EAB_KID: "original",
+ DNSProvider: "original",
+ AccountConfigFile: "original",
+ }
+
+ expectedConfig := cfg
+ pair.callback(&expectedConfig)
+
+ mergeACMEConfig(ctx, &cfg)
+
+ assert.Equal(t, expectedConfig, cfg)
+
+ return nil
+ },
+ pair.args,
+ )
+ }
+}
diff --git a/example_config.toml b/example_config.toml
new file mode 100644
index 0000000..30e77c4
--- /dev/null
+++ b/example_config.toml
@@ -0,0 +1,32 @@
+logLevel = 'debug'
+
+[server]
+host = '[::]'
+port = 443
+httpPort = 80
+httpServerEnabled = true
+mainDomain = 'codeberg.page'
+rawDomain = 'raw.codeberg.page'
+pagesBranches = ["pages"]
+allowedCorsDomains = []
+blacklistedPaths = []
+
+[gitea]
+root = 'https://codeberg.org'
+token = 'ASDF1234'
+lfsEnabled = true
+followSymlinks = true
+
+[database]
+type = 'sqlite'
+conn = 'certs.sqlite'
+
+[ACME]
+email = 'noreply@example.email'
+apiEndpoint = 'https://acme-v02.api.letsencrypt.org/directory'
+acceptTerms = false
+useRateLimits = false
+eab_hmac = ''
+eab_kid = ''
+dnsProvider = ''
+accountConfigFile = 'acme-account.json'
diff --git a/haproxy-sni/.gitignore b/examples/haproxy-sni/.gitignore
similarity index 100%
rename from haproxy-sni/.gitignore
rename to examples/haproxy-sni/.gitignore
diff --git a/haproxy-sni/README.md b/examples/haproxy-sni/README.md
similarity index 100%
rename from haproxy-sni/README.md
rename to examples/haproxy-sni/README.md
diff --git a/haproxy-sni/dhparam.pem b/examples/haproxy-sni/dhparam.pem
similarity index 100%
rename from haproxy-sni/dhparam.pem
rename to examples/haproxy-sni/dhparam.pem
diff --git a/haproxy-sni/docker-compose.yml b/examples/haproxy-sni/docker-compose.yml
similarity index 100%
rename from haproxy-sni/docker-compose.yml
rename to examples/haproxy-sni/docker-compose.yml
diff --git a/haproxy-sni/gitea-www/index.html b/examples/haproxy-sni/gitea-www/index.html
similarity index 100%
rename from haproxy-sni/gitea-www/index.html
rename to examples/haproxy-sni/gitea-www/index.html
diff --git a/haproxy-sni/gitea.Caddyfile b/examples/haproxy-sni/gitea.Caddyfile
similarity index 100%
rename from haproxy-sni/gitea.Caddyfile
rename to examples/haproxy-sni/gitea.Caddyfile
diff --git a/haproxy-sni/haproxy-certificates/codeberg.org.pem b/examples/haproxy-sni/haproxy-certificates/codeberg.org.pem
similarity index 100%
rename from haproxy-sni/haproxy-certificates/codeberg.org.pem
rename to examples/haproxy-sni/haproxy-certificates/codeberg.org.pem
diff --git a/haproxy-sni/haproxy-certificates/codeberg.org.pem.key b/examples/haproxy-sni/haproxy-certificates/codeberg.org.pem.key
similarity index 100%
rename from haproxy-sni/haproxy-certificates/codeberg.org.pem.key
rename to examples/haproxy-sni/haproxy-certificates/codeberg.org.pem.key
diff --git a/haproxy-sni/haproxy.cfg b/examples/haproxy-sni/haproxy.cfg
similarity index 100%
rename from haproxy-sni/haproxy.cfg
rename to examples/haproxy-sni/haproxy.cfg
diff --git a/haproxy-sni/pages-www/index.html b/examples/haproxy-sni/pages-www/index.html
similarity index 100%
rename from haproxy-sni/pages-www/index.html
rename to examples/haproxy-sni/pages-www/index.html
diff --git a/haproxy-sni/pages.Caddyfile b/examples/haproxy-sni/pages.Caddyfile
similarity index 100%
rename from haproxy-sni/pages.Caddyfile
rename to examples/haproxy-sni/pages.Caddyfile
diff --git a/haproxy-sni/test.sh b/examples/haproxy-sni/test.sh
similarity index 100%
rename from haproxy-sni/test.sh
rename to examples/haproxy-sni/test.sh
diff --git a/go.mod b/go.mod
index bfde7f7..47e1e71 100644
--- a/go.mod
+++ b/go.mod
@@ -1,18 +1,22 @@
module codeberg.org/codeberg/pages
-go 1.20
+go 1.21
+
+toolchain go1.21.4
require (
- code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa
+ code.gitea.io/sdk/gitea v0.16.1-0.20231115014337-e23e8aa3004f
github.com/OrlovEvgeny/go-mcache v0.0.0-20200121124330-1a8195b34f3a
github.com/go-acme/lego/v4 v4.5.3
github.com/go-sql-driver/mysql v1.6.0
github.com/joho/godotenv v1.4.0
github.com/lib/pq v1.10.7
github.com/mattn/go-sqlite3 v1.14.16
+ github.com/microcosm-cc/bluemonday v1.0.26
+ github.com/pelletier/go-toml/v2 v2.1.0
github.com/reugn/equalizer v0.0.0-20210216135016-a959c509d7ad
github.com/rs/zerolog v1.27.0
- github.com/stretchr/testify v1.7.0
+ github.com/stretchr/testify v1.8.4
github.com/urfave/cli/v2 v2.3.0
golang.org/x/exp v0.0.0-20230213192124-5e25df0256eb
xorm.io/xorm v1.3.2
@@ -35,11 +39,13 @@ require (
github.com/akamai/AkamaiOPEN-edgegrid-golang v1.1.1 // indirect
github.com/aliyun/alibaba-cloud-sdk-go v1.61.1183 // indirect
github.com/aws/aws-sdk-go v1.39.0 // indirect
+ github.com/aymerick/douceur v0.2.0 // indirect
github.com/boombuler/barcode v1.0.1-0.20190219062509-6c824513bacc // indirect
github.com/cenkalti/backoff/v4 v4.1.1 // indirect
github.com/cloudflare/cloudflare-go v0.20.0 // indirect
github.com/cpu/goacmedns v0.1.1 // indirect
github.com/cpuguy83/go-md2man/v2 v2.0.0 // indirect
+ github.com/creasty/defaults v1.7.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/deepmap/oapi-codegen v1.6.1 // indirect
@@ -61,6 +67,7 @@ require (
github.com/googleapis/gax-go/v2 v2.0.5 // indirect
github.com/gophercloud/gophercloud v0.16.0 // indirect
github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae // indirect
+ github.com/gorilla/css v1.0.0 // indirect
github.com/hashicorp/go-cleanhttp v0.5.1 // indirect
github.com/hashicorp/go-retryablehttp v0.7.0 // indirect
github.com/hashicorp/go-version v1.6.0 // indirect
@@ -108,18 +115,18 @@ require (
github.com/softlayer/softlayer-go v1.0.3 // indirect
github.com/softlayer/xmlrpc v0.0.0-20200409220501-5f089df7cb7e // indirect
github.com/spf13/cast v1.3.1 // indirect
- github.com/stretchr/objx v0.3.0 // indirect
+ github.com/stretchr/objx v0.5.0 // indirect
github.com/syndtr/goleveldb v1.0.0 // indirect
github.com/transip/gotransip/v6 v6.6.1 // indirect
github.com/vinyldns/go-vinyldns v0.0.0-20200917153823-148a5f6b8f14 // indirect
github.com/vultr/govultr/v2 v2.7.1 // indirect
go.opencensus.io v0.22.3 // indirect
go.uber.org/ratelimit v0.0.0-20180316092928-c15da0234277 // indirect
- golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa // indirect
- golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
+ golang.org/x/crypto v0.14.0 // indirect
+ golang.org/x/net v0.17.0 // indirect
golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d // indirect
- golang.org/x/sys v0.1.0 // indirect
- golang.org/x/text v0.3.6 // indirect
+ golang.org/x/sys v0.13.0 // indirect
+ golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.0.0-20210611083556-38a9dc6acbc6 // indirect
google.golang.org/api v0.20.0 // indirect
google.golang.org/appengine v1.6.5 // indirect
@@ -130,6 +137,6 @@ require (
gopkg.in/ns1/ns1-go.v2 v2.6.2 // indirect
gopkg.in/square/go-jose.v2 v2.6.0 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
- gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
+ gopkg.in/yaml.v3 v3.0.1 // indirect
xorm.io/builder v0.3.12 // indirect
)
diff --git a/go.sum b/go.sum
index b10305c..1a10599 100644
--- a/go.sum
+++ b/go.sum
@@ -22,8 +22,8 @@ cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIA
cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw=
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
-code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa h1:OVwgYrY6vr6gWZvgnmevFhtL0GVA4HKaFOhD+joPoNk=
-code.gitea.io/sdk/gitea v0.15.1-0.20220729105105-cc14c63cccfa/go.mod h1:aRmrQC3CAHdJAU1LQt0C9zqzqI8tUB/5oQtNE746aYE=
+code.gitea.io/sdk/gitea v0.16.1-0.20231115014337-e23e8aa3004f h1:nMmwDgUIAWj9XQjzHz5unC3ZMfhhwHRk6rnuwLzdu1o=
+code.gitea.io/sdk/gitea v0.16.1-0.20231115014337-e23e8aa3004f/go.mod h1:ndkDk99BnfiUCCYEUhpNzi0lpmApXlwRFqClBlOlEBg=
dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a h1:lSA0F4e9A2NcQSqGqTOXqu2aRi/XEQxDCBwM8yJtE6s=
gitea.com/xorm/sqlfiddle v0.0.0-20180821085327-62ce714f951a/go.mod h1:EXuID2Zs0pAQhH8yz+DNjUbjppKQzKFAn28TMYPB6IU=
@@ -88,6 +88,8 @@ github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN
github.com/aws/aws-sdk-go v1.39.0 h1:74BBwkEmiqBbi2CGflEh34l0YNtIibTjZsibGarkNjo=
github.com/aws/aws-sdk-go v1.39.0/go.mod h1:hcU610XS61/+aQV88ixoOzUoG7v3b31pl2zKMmprdro=
github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g=
+github.com/aymerick/douceur v0.2.0 h1:Mv+mAeH1Q+n9Fr+oyamOlAkUNPWPlA8PPGR0QAaYuPk=
+github.com/aymerick/douceur v0.2.0/go.mod h1:wlT5vV2O3h55X9m7iVYN0TBM0NH/MmbLnd30/FjWUq4=
github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q=
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
@@ -129,6 +131,8 @@ github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:ma
github.com/cpuguy83/go-md2man/v2 v2.0.0 h1:EoUDS0afbrsXAZ9YQ9jdu/mZ2sXgT1/2yyNng4PGlyM=
github.com/cpuguy83/go-md2man/v2 v2.0.0/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY=
+github.com/creasty/defaults v1.7.0 h1:eNdqZvc5B509z18lD8yc212CAqJNvfT1Jq6L8WowdBA=
+github.com/creasty/defaults v1.7.0/go.mod h1:iGzKe6pbEHnpMPtfDXZEr0NVxWnPTjb1bbDy08fPzYM=
github.com/cyberdelia/templates v0.0.0-20141128023046-ca7fffd4298c/go.mod h1:GyV+0YP4qX0UQ7r2MoYZ+AvYDp12OF5yg4q8rGnyNh4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -249,8 +253,9 @@ github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw
github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.3/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
-github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
+github.com/google/go-cmp v0.5.8 h1:e6P7q2lk1O+qJJb4BtCQXlK8vWEO8V1ZeuEdJNOqZyg=
+github.com/google/go-cmp v0.5.8/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-github/v32 v32.1.0/go.mod h1:rIEpZD9CTDQwDK9GDrtMTycQNA4JU3qBsCizh3q2WCI=
github.com/google/go-querystring v1.0.0/go.mod h1:odCYkC5MyYFN7vkCjXpyrEuKhc/BUO6wN/zVPAxq5ck=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
@@ -278,6 +283,8 @@ github.com/gophercloud/utils v0.0.0-20210216074907-f6de111f2eae/go.mod h1:wx8HMD
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
github.com/gorilla/context v1.1.1/go.mod h1:kBGZzfjB9CEq2AlWe17Uuf7NDRt0dE0s8S51q0aT7Yg=
+github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY=
+github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c=
github.com/gorilla/mux v1.6.2/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs=
github.com/gorilla/mux v1.8.0/go.mod h1:DVbg23sWSpFRCP0SfiEN6jmj59UnW/n46BH5rLB71So=
@@ -477,6 +484,8 @@ github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S
github.com/mattn/go-tty v0.0.0-20180219170247-931426f7535a/go.mod h1:XPvLUNfbS4fJH25nqRHfWLMa1ONC8Amw+mIA639KxkE=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
+github.com/microcosm-cc/bluemonday v1.0.26 h1:xbqSvqzQMeEHCqMi64VAs4d8uy6Mequs3rQ0k/Khz58=
+github.com/microcosm-cc/bluemonday v1.0.26/go.mod h1:JyzOCs9gkyQyjs+6h10UEVSe02CGwkhd72Xdqh78TWs=
github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
github.com/miekg/dns v1.1.43 h1:JKfpVSCB84vrAmHzyrsxB5NAr5kLoMXZArPSw7Qlgyg=
github.com/miekg/dns v1.1.43/go.mod h1:+evo5L0630/F6ca/Z9+GAqzhjGyn8/c+TBaOyfEl0V4=
@@ -563,6 +572,8 @@ github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTK
github.com/pborman/uuid v1.2.0/go.mod h1:X/NO0urCmaxf9VXbdlT7C2Yzkj2IKimNn4k+gtPdI/k=
github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
github.com/pelletier/go-toml v1.8.1/go.mod h1:T2/BmBdy8dvIRq1a/8aqjN41wvWlN4lrapLU/GW4pbc=
+github.com/pelletier/go-toml/v2 v2.1.0 h1:FnwAJ4oYMvbT/34k9zzHuZNrhlz48GB3/s6at6/MHO4=
+github.com/pelletier/go-toml/v2 v2.1.0/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc=
github.com/performancecopilot/speed v3.0.0+incompatible/go.mod h1:/CLtqpZ5gBg1M9iaPbIdPPGyKcA8hKdoy6hAWba7Yac=
github.com/pierrec/lz4 v1.0.2-0.20190131084431-473cd7ce01a1/go.mod h1:3/3N9NVKO0jef7pBehbT1qWhCMrIgbYNnFAZCqQ5LRc=
github.com/pierrec/lz4 v2.0.5+incompatible/go.mod h1:pdkljMzZIN41W+lC3N2tnIh5sFi+IEE17M5jbnwPHcY=
@@ -673,14 +684,19 @@ github.com/streadway/handy v0.0.0-20190108123426-d5acb3125c2a/go.mod h1:qNTQ5P5J
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
-github.com/stretchr/objx v0.3.0 h1:NGXK3lHquSN08v5vWalVI/L8XU9hdzE/G6xsrze47As=
github.com/stretchr/objx v0.3.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE=
+github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
+github.com/stretchr/objx v0.5.0 h1:1zr/of2m5FGMsad5YfcqgdqdWrIhu+EBEJRhR1U7z/c=
+github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
+github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
+github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/subosito/gotenv v1.2.0/go.mod h1:N0PQaV/YGNqwC0u51sEeR/aUtSLEXKX9iv69rRypqCw=
github.com/syndtr/goleveldb v1.0.0 h1:fBdIW9lB4Iz0n9khmH8w27SJ3QEJ7+IgjPEwGSZiFdE=
github.com/syndtr/goleveldb v1.0.0/go.mod h1:ZVVdQEZoIme9iO1Ch2Jdy24qqXrMMOU6lpPAyBWyWuQ=
@@ -756,8 +772,8 @@ golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220525230936-793ad666bf5e/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
-golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa h1:zuSxTR4o9y82ebqCUJYNGJbGPo6sKVl54f/TVDObg1c=
-golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
+golang.org/x/crypto v0.14.0 h1:wBqGXzWJW6m1XrIKlAH0Hs1JJ7+9KBwnIO8v66Q9cHc=
+golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
@@ -789,8 +805,9 @@ golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
-golang.org/x/mod v0.3.0 h1:RM4zey1++hCTbCVQfnWeKs9/IEsaBLA8vTkd0WVtmH4=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
+golang.org/x/mod v0.8.0 h1:LUYupSeNrTNCGzR/hVBk2NHZO4hXcVaW1k4Qx7rjPx8=
+golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -826,8 +843,9 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781/go.mod h1:OJAsFXCWl8Ukc7SiCT/9KSuxbyM7479/AVlXFRxuMCk=
golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
@@ -899,13 +917,12 @@ golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20210902050250-f475640dd07b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211007075335-d3039528d8ac/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10 h1:WIoqL4EROvwiPdUtaip4VcDdpZ4kha7wBWZrbVKCIZg=
-golang.org/x/sys v0.0.0-20220728004956-3c1f35247d10/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.1.0 h1:kunALQeHf1/185U1i0GOB/fy1IPRDDpuoOOqRReG57U=
-golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
-golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
+golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -913,8 +930,9 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20180412165947-fbb02b2291d2/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
@@ -964,14 +982,14 @@ golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapK
golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw=
golang.org/x/tools v0.0.0-20200410194907-79a7a3126eef/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE=
golang.org/x/tools v0.0.0-20201124115921-2c860bdd6e78/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
-golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e h1:4nW4NLDYnU28ojHaHO8OVxFHk/aQ33U01a9cjED+pzE=
golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA=
+golang.org/x/tools v0.6.0 h1:BOw41kyTf3PuCW1pVQf8+Cyg8pMlkYB1oo9iJ6D/lKM=
+golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
-golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/api v0.3.1/go.mod h1:6wY9I6uQWHQ8EM57III9mq/AjF+i8G65rmVagqKMtkk=
google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE=
@@ -1070,8 +1088,9 @@ gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
-gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
+gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
honnef.co/go/tools v0.0.0-20180728063816-88497007e858/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/html/404.html b/html/404.html
deleted file mode 100644
index 7c721b5..0000000
--- a/html/404.html
+++ /dev/null
@@ -1,37 +0,0 @@
-
-
-
-
-
- %status%
-
-
-
-
-
-
-
-
-
-
- Page not found!
-
-
- Sorry, but this page couldn't be found or is inaccessible (%status%).
- We hope this isn't a problem on our end ;) - Make sure to check the troubleshooting section in the Docs!
-
- Sorry, but this page couldn't be served.
- We got an "%message%"
- We hope this isn't a problem on our end ;) - Make sure to check the troubleshooting section in the Docs!
-
-
-
- Static pages made easy - Codeberg Pages
-
-
-
diff --git a/html/error_test.go b/html/error_test.go
deleted file mode 100644
index f5da08c..0000000
--- a/html/error_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-package html
-
-import (
- "net/http"
- "strings"
- "testing"
-)
-
-func TestValidMessage(t *testing.T) {
- testString := "requested blacklisted path"
- statusCode := http.StatusForbidden
-
- expected := strings.ReplaceAll(
- strings.ReplaceAll(ErrorPage, "%message%", testString),
- "%status%",
- http.StatusText(statusCode))
- actual := generateResponse(testString, statusCode)
-
- if expected != actual {
- t.Errorf("generated response did not match: expected: '%s', got: '%s'", expected, actual)
- }
-}
-
-func TestMessageWithHtml(t *testing.T) {
- testString := `abc
+func ReturnErrorPage(ctx *context.Context, msg string, statusCode int) {
+ ctx.RespWriter.Header().Set("Content-Type", "text/html; charset=utf-8")
+ ctx.RespWriter.WriteHeader(statusCode)
+
+ templateContext := TemplateContext{
+ StatusCode: statusCode,
+ StatusText: http.StatusText(statusCode),
+ Message: sanitizer.Sanitize(msg),
+ }
+
+ err := errorTemplate.Execute(ctx.RespWriter, templateContext)
+ if err != nil {
+ log.Err(err).Str("message", msg).Int("status", statusCode).Msg("could not write response")
+ }
+}
+
+func createBlueMondayPolicy() *bluemonday.Policy {
+ p := bluemonday.NewPolicy()
+
+ p.AllowElements("code")
+
+ return p
+}
diff --git a/html/html_test.go b/html/html_test.go
new file mode 100644
index 0000000..b395bb2
--- /dev/null
+++ b/html/html_test.go
@@ -0,0 +1,54 @@
+package html
+
+import (
+ "testing"
+
+ "github.com/stretchr/testify/assert"
+)
+
+func TestSanitizerSimpleString(t *testing.T) {
+ str := "simple text message without any html elements"
+
+ assert.Equal(t, str, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithCodeTag(t *testing.T) {
+ str := "simple text message with html tag"
+
+ assert.Equal(t, str, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithCodeTagWithAttribute(t *testing.T) {
+ str := "simple text message with html tag"
+ expected := "simple text message with html tag"
+
+ assert.Equal(t, expected, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithATag(t *testing.T) {
+ str := "simple text message with a link to another page"
+ expected := "simple text message with a link to another page"
+
+ assert.Equal(t, expected, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithATagAndHref(t *testing.T) {
+ str := "simple text message with a link to another page"
+ expected := "simple text message with a link to another page"
+
+ assert.Equal(t, expected, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithImgTag(t *testing.T) {
+ str := "simple text message with a "
+ expected := "simple text message with a "
+
+ assert.Equal(t, expected, sanitizer.Sanitize(str))
+}
+
+func TestSanitizerStringWithImgTagAndOnerrorAttribute(t *testing.T) {
+ str := "simple text message with a "
+ expected := "simple text message with a "
+
+ assert.Equal(t, expected, sanitizer.Sanitize(str))
+}
diff --git a/html/templates/error.html b/html/templates/error.html
new file mode 100644
index 0000000..6094a26
--- /dev/null
+++ b/html/templates/error.html
@@ -0,0 +1,69 @@
+
+
+
+
+
+ {{.StatusText}}
+
+
+
+
+
+
+
+
+