summaryrefslogtreecommitdiff
path: root/internal/middleware/contentsecuritypolicy.go
diff options
context:
space:
mode:
Diffstat (limited to 'internal/middleware/contentsecuritypolicy.go')
-rw-r--r--internal/middleware/contentsecuritypolicy.go144
1 files changed, 144 insertions, 0 deletions
diff --git a/internal/middleware/contentsecuritypolicy.go b/internal/middleware/contentsecuritypolicy.go
new file mode 100644
index 000000000..5984a75c3
--- /dev/null
+++ b/internal/middleware/contentsecuritypolicy.go
@@ -0,0 +1,144 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package middleware
+
+import (
+ "strings"
+
+ "codeberg.org/gruf/go-debug"
+ "github.com/gin-gonic/gin"
+)
+
+func ContentSecurityPolicy(extraURIs ...string) gin.HandlerFunc {
+ csp := BuildContentSecurityPolicy(extraURIs...)
+
+ return func(c *gin.Context) {
+ // Inform the browser we only load
+ // CSS/JS/media using the given policy.
+ c.Header("Content-Security-Policy", csp)
+ }
+}
+
+func BuildContentSecurityPolicy(extraURIs ...string) string {
+ const (
+ defaultSrc = "default-src"
+ objectSrc = "object-src"
+ imgSrc = "img-src"
+ mediaSrc = "media-src"
+
+ self = "'self'"
+ none = "'none'"
+ blob = "blob:"
+ )
+
+ // CSP values keyed by directive.
+ values := make(map[string][]string, 4)
+
+ /*
+ default-src
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/default-src
+ */
+
+ if !debug.DEBUG {
+ // Restrictive 'self' policy
+ values[defaultSrc] = []string{self}
+ } else {
+ // If debug is enabled, allow
+ // serving things from localhost
+ // as well (regardless of port).
+ values[defaultSrc] = []string{
+ self,
+ "localhost:*",
+ "ws://localhost:*",
+ }
+ }
+
+ /*
+ object-src
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/object-src
+ */
+
+ // Disallow object-src as recommended.
+ values[objectSrc] = []string{none}
+
+ /*
+ img-src
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/img-src
+ */
+
+ // Restrictive 'self' policy,
+ // include extraURIs, and 'blob:'
+ // for previewing uploaded images
+ // (header, avi, emojis) in settings.
+ values[imgSrc] = append(
+ []string{self, blob},
+ extraURIs...,
+ )
+
+ /*
+ media-src
+ https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/media-src
+ */
+
+ // Restrictive 'self' policy,
+ // include extraURIs.
+ values[mediaSrc] = append(
+ []string{self},
+ extraURIs...,
+ )
+
+ /*
+ Assemble policy directives.
+ */
+
+ // Iterate through an ordered slice rather than
+ // iterating through the map, since we want these
+ // policyDirectives in a determinate order.
+ policyDirectives := make([]string, 4)
+ for i, directive := range []string{
+ defaultSrc,
+ objectSrc,
+ imgSrc,
+ mediaSrc,
+ } {
+ // Each policy directive should look like:
+ // `[directive] [value1] [value2] [etc]`
+
+ // Get assembled values
+ // for this directive.
+ values := values[directive]
+
+ // Prepend values with
+ // the directive name.
+ directiveValues := append(
+ []string{directive},
+ values...,
+ )
+
+ // Space-separate them.
+ policyDirective := strings.Join(directiveValues, " ")
+
+ // Done.
+ policyDirectives[i] = policyDirective
+ }
+
+ // Content-security-policy looks like this:
+ // `Content-Security-Policy: <policy-directive>; <policy-directive>`
+ // So join each policy directive appropriately.
+ return strings.Join(policyDirectives, "; ")
+}