Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

API endpoint to serve update resources #1343

Merged
merged 5 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 1 addition & 7 deletions core/base/module.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)
Expand Down Expand Up @@ -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
Expand Down
7 changes: 7 additions & 0 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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
}

Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -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=
Expand Down
76 changes: 3 additions & 73 deletions ui/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
)

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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 changes: 122 additions & 2 deletions updates/api.go
Original file line number Diff line number Diff line change
@@ -1,17 +1,26 @@
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 (
apiPathCheckForUpdates = "updates/check"
)

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{{
Expand Down Expand Up @@ -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