mirror of
https://github.com/safing/portmaster
synced 2025-09-01 18:19:12 +00:00
Merge pull request #1343 from safing/feature/server-updates-resources
API endpoint to serve update resources
This commit is contained in:
commit
a386cf72f1
6 changed files with 136 additions and 83 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
2
go.mod
2
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
|
||||
|
|
2
go.sum
2
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=
|
||||
|
|
76
ui/serve.go
76
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
|
||||
}
|
||||
|
|
124
updates/api.go
124
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
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue