diff --git a/core/base/module.go b/core/base/module.go index cd802c7ee..10870d174 100644 --- a/core/base/module.go +++ b/core/base/module.go @@ -2,8 +2,7 @@ package base import ( _ "github.com/safing/portbase/config" - "github.com/safing/portbase/log" - "github.com/safing/portbase/metrics" + _ "github.com/safing/portbase/metrics" "github.com/safing/portbase/modules" _ "github.com/safing/portbase/rng" ) @@ -33,11 +32,6 @@ func start() error { return err } - // Set metrics storage key and load them from db. - if err := metrics.EnableMetricPersistence("core:metrics/storage"); err != nil { - log.Warningf("core: failed to load persisted metrics from db: %s", err) - } - registerLogCleaner() return nil diff --git a/core/core.go b/core/core.go index f217cb09b..a9462fdf7 100644 --- a/core/core.go +++ b/core/core.go @@ -5,6 +5,8 @@ import ( "fmt" "time" + "github.com/safing/portbase/log" + "github.com/safing/portbase/metrics" "github.com/safing/portbase/modules" "github.com/safing/portbase/modules/subsystems" _ "github.com/safing/portmaster/broadcasts" @@ -60,6 +62,11 @@ func prep() error { return err } + // Enable persistent metrics. + if err := metrics.EnableMetricPersistence("core:metrics/storage"); err != nil { + log.Warningf("core: failed to enable persisted metrics: %s", err) + } + return nil } diff --git a/go.mod b/go.mod index 7ad0fa48a..b1379e339 100644 --- a/go.mod +++ b/go.mod @@ -22,7 +22,7 @@ require ( github.com/mitchellh/go-server-timing v1.0.1 github.com/oschwald/maxminddb-golang v1.12.0 github.com/safing/jess v0.3.1 - github.com/safing/portbase v0.18.3 + github.com/safing/portbase v0.18.4 github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec github.com/safing/spn v0.7.2 github.com/shirou/gopsutil v3.21.11+incompatible diff --git a/go.sum b/go.sum index 033cf34de..3f5294af4 100644 --- a/go.sum +++ b/go.sum @@ -251,6 +251,8 @@ github.com/safing/portbase v0.15.2/go.mod h1:5bHi99fz7Hh/wOsZUOI631WF9ePSHk57c4f github.com/safing/portbase v0.16.2/go.mod h1:mzNCWqPbO7vIYbbK5PElGbudwd2vx4YPNawymL8Aro8= github.com/safing/portbase v0.18.3 h1:0eWv9r3in0MQEUaOyrd2LNhYKs+D6UZbys2fWTpO0+Y= github.com/safing/portbase v0.18.3/go.mod h1:qhhLjrr5iEGU9r7RZ6hJdtulOeycJ0d0jq95ZxGJ9Hs= +github.com/safing/portbase v0.18.4 h1:Dinjp7EMe/McPwg0OcgoXdcjvQdO3yP85mhJQ8z7vOU= +github.com/safing/portbase v0.18.4/go.mod h1:qhhLjrr5iEGU9r7RZ6hJdtulOeycJ0d0jq95ZxGJ9Hs= github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec h1:oSJY1seobofPwpMoJRkCgXnTwfiQWNfGMCPDfqgAEfg= github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec/go.mod h1:abwyAQrZGemWbSh/aCD9nnkp0SvFFf/mGWkAbOwPnFE= github.com/safing/spn v0.7.2 h1:FKxcGqWZaOr12Ddz+rSZi9KTu6H9N911FpVZJUh6eDc= diff --git a/ui/serve.go b/ui/serve.go index 811be7dfc..2fe7f7103 100644 --- a/ui/serve.go +++ b/ui/serve.go @@ -17,6 +17,7 @@ import ( "github.com/safing/portbase/log" "github.com/safing/portbase/modules" "github.com/safing/portbase/updater" + "github.com/safing/portbase/utils" "github.com/safing/portmaster/updates" ) @@ -146,10 +147,8 @@ func ServeFileFromArchive(w http.ResponseWriter, r *http.Request, archiveName st // set content type _, ok := w.Header()["Content-Type"] if !ok { - contentType := mimeTypeByExtension(filepath.Ext(path)) - if contentType != "" { - w.Header().Set("Content-Type", contentType) - } + contentType, _ := utils.MimeTypeByExtension(filepath.Ext(path)) + w.Header().Set("Content-Type", contentType) } w.WriteHeader(http.StatusOK) @@ -178,72 +177,3 @@ func redirectToDefault(w http.ResponseWriter, r *http.Request) { func redirAddSlash(w http.ResponseWriter, r *http.Request) { http.Redirect(w, r, r.RequestURI+"/", http.StatusPermanentRedirect) } - -// We now do our mimetypes ourselves, because, as far as we analyzed, a Windows -// update screwed us over here and broke all the mime typing. -// (April 2021) - -var ( - defaultMimeType = "application/octet-stream" - - mimeTypes = map[string]string{ - ".7z": "application/x-7z-compressed", - ".atom": "application/atom+xml", - ".css": "text/css; charset=utf-8", - ".csv": "text/csv; charset=utf-8", - ".deb": "application/x-debian-package", - ".epub": "application/epub+zip", - ".es": "application/ecmascript", - ".flv": "video/x-flv", - ".gif": "image/gif", - ".gz": "application/gzip", - ".htm": "text/html; charset=utf-8", - ".html": "text/html; charset=utf-8", - ".jpeg": "image/jpeg", - ".jpg": "image/jpeg", - ".js": "text/javascript; charset=utf-8", - ".json": "application/json; charset=utf-8", - ".m3u": "audio/mpegurl", - ".m4a": "audio/mpeg", - ".md": "text/markdown; charset=utf-8", - ".mjs": "text/javascript; charset=utf-8", - ".mov": "video/quicktime", - ".mp3": "audio/mpeg", - ".mp4": "video/mp4", - ".mpeg": "video/mpeg", - ".mpg": "video/mpeg", - ".ogg": "audio/ogg", - ".ogv": "video/ogg", - ".otf": "font/otf", - ".pdf": "application/pdf", - ".png": "image/png", - ".qt": "video/quicktime", - ".rar": "application/rar", - ".rtf": "application/rtf", - ".svg": "image/svg+xml", - ".tar": "application/x-tar", - ".tiff": "image/tiff", - ".ts": "video/MP2T", - ".ttc": "font/collection", - ".ttf": "font/ttf", - ".txt": "text/plain; charset=utf-8", - ".wasm": "application/wasm", - ".wav": "audio/x-wav", - ".webm": "video/webm", - ".webp": "image/webp", - ".woff": "font/woff", - ".woff2": "font/woff2", - ".xml": "text/xml; charset=utf-8", - ".xz": "application/x-xz", - ".zip": "application/zip", - } -) - -func mimeTypeByExtension(ext string) string { - mimeType, ok := mimeTypes[ext] - if ok { - return mimeType - } - - return defaultMimeType -} diff --git a/updates/api.go b/updates/api.go index 2a25b8bca..88513b3a6 100644 --- a/updates/api.go +++ b/updates/api.go @@ -1,9 +1,18 @@ package updates import ( + "bytes" + "io" "net/http" + "os" + "path/filepath" + "strings" + + "github.com/ghodss/yaml" "github.com/safing/portbase/api" + "github.com/safing/portbase/log" + "github.com/safing/portbase/utils" ) const ( @@ -11,7 +20,7 @@ const ( ) func registerAPIEndpoints() error { - return api.RegisterEndpoint(api.Endpoint{ + if err := api.RegisterEndpoint(api.Endpoint{ Name: "Check for Updates", Description: "Checks if new versions are available. If automatic updates are enabled, they are also downloaded and applied.", Parameters: []api.Parameter{{ @@ -39,5 +48,116 @@ func registerAPIEndpoints() error { } return "checking for updates...", nil }, - }) + }); err != nil { + return err + } + + if err := api.RegisterEndpoint(api.Endpoint{ + Name: "Get Resource", + Description: "Returns the requested resource from the udpate system", + Path: `updates/get/{identifier:[A-Za-z0-9/\.\-_]{1,255}}`, + Read: api.PermitUser, + ReadMethod: http.MethodGet, + BelongsTo: module, + HandlerFunc: func(w http.ResponseWriter, r *http.Request) { + // Get identifier from URL. + var identifier string + if ar := api.GetAPIRequest(r); ar != nil { + identifier = ar.URLVars["identifier"] + } + if identifier == "" { + http.Error(w, "no resource speicified", http.StatusBadRequest) + return + } + + // Get resource. + resource, err := registry.GetFile(identifier) + if err != nil { + http.Error(w, err.Error(), http.StatusNotFound) + return + } + + // Open file for reading. + file, err := os.Open(resource.Path()) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer file.Close() //nolint:errcheck,gosec + + // Assign file to reader + var reader io.Reader = file + + // Add version to header. + w.Header().Set("Resource-Version", resource.Version()) + + // Set Content-Type. + contentType, _ := utils.MimeTypeByExtension(filepath.Ext(resource.Path())) + w.Header().Set("Content-Type", contentType) + + // Check if the content type may be returned. + accept := r.Header.Get("Accept") + if accept != "" { + mimeTypes := strings.Split(accept, ",") + // First, clean mime types. + for i, mimeType := range mimeTypes { + mimeType = strings.TrimSpace(mimeType) + mimeType, _, _ = strings.Cut(mimeType, ";") + mimeTypes[i] = mimeType + } + // Second, check if we may return anything. + var acceptsAny bool + for _, mimeType := range mimeTypes { + switch mimeType { + case "*", "*/*": + acceptsAny = true + } + } + // Third, check if we can convert. + if !acceptsAny { + var converted bool + sourceType, _, _ := strings.Cut(contentType, ";") + findConvertiblePair: + for _, mimeType := range mimeTypes { + switch { + case sourceType == "application/yaml" && mimeType == "application/json": + yamlData, err := io.ReadAll(reader) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + jsonData, err := yaml.YAMLToJSON(yamlData) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + reader = bytes.NewReader(jsonData) + converted = true + break findConvertiblePair + } + } + + // If we could not convert to acceptable format, return an error. + if !converted { + http.Error(w, err.Error(), http.StatusNotAcceptable) + return + } + } + } + + // Write file. + w.WriteHeader(http.StatusOK) + if r.Method != http.MethodHead { + _, err = io.Copy(w, reader) + if err != nil { + log.Errorf("updates: failed to serve resource file: %s", err) + return + } + } + }, + }); err != nil { + return err + } + + return nil }