safing-portmaster/service/profile/api.go
Daniel Hååvi 80664d1a27
Restructure modules ()
* Move portbase into monorepo

* Add new simple module mgr

* [WIP] Switch to new simple module mgr

* Add StateMgr and more worker variants

* [WIP] Switch more modules

* [WIP] Switch more modules

* [WIP] swtich more modules

* [WIP] switch all SPN modules

* [WIP] switch all service modules

* [WIP] Convert all workers to the new module system

* [WIP] add new task system to module manager

* [WIP] Add second take for scheduling workers

* [WIP] Add FIXME for bugs in new scheduler

* [WIP] Add minor improvements to scheduler

* [WIP] Add new worker scheduler

* [WIP] Fix more bug related to new module system

* [WIP] Fix start handing of the new module system

* [WIP] Improve startup process

* [WIP] Fix minor issues

* [WIP] Fix missing subsystem in settings

* [WIP] Initialize managers in constructor

* [WIP] Move module event initialization to constrictors

* [WIP] Fix setting for enabling and disabling the SPN module

* [WIP] Move API registeration into module construction

* [WIP] Update states mgr for all modules

* [WIP] Add CmdLine operation support

* Add state helper methods to module group and instance

* Add notification and module status handling to status package

* Fix starting issues

* Remove pilot widget and update security lock to new status data

* Remove debug logs

* Improve http server shutdown

* Add workaround for cleanly shutting down firewall+netquery

* Improve logging

* Add syncing states with notifications for new module system

* Improve starting, stopping, shutdown; resolve FIXMEs/TODOs

* [WIP] Fix most unit tests

* Review new module system and fix minor issues

* Push shutdown and restart events again via API

* Set sleep mode via interface

* Update example/template module

* [WIP] Fix spn/cabin unit test

* Remove deprecated UI elements

* Make log output more similar for the logging transition phase

* Switch spn hub and observer cmds to new module system

* Fix log sources

* Make worker mgr less error prone

* Fix tests and minor issues

* Fix observation hub

* Improve shutdown and restart handling

* Split up big connection.go source file

* Move varint and dsd packages to structures repo

* Improve expansion test

* Fix linter warnings

* Fix interception module on windows

* Fix linter errors

---------

Co-authored-by: Vladimir Stoilov <vladimir@safing.io>
2024-08-09 18:15:48 +03:00

166 lines
4 KiB
Go

package profile
import (
"errors"
"fmt"
"net/http"
"path/filepath"
"strings"
"github.com/safing/portmaster/base/api"
"github.com/safing/portmaster/base/utils"
"github.com/safing/portmaster/service/profile/binmeta"
"github.com/safing/structures/dsd"
)
func registerAPIEndpoints() error {
if err := api.RegisterEndpoint(api.Endpoint{
Name: "Merge profiles",
Description: "Merge multiple profiles into a new one.",
Path: "profile/merge",
Write: api.PermitUser,
StructFunc: handleMergeProfiles,
}); err != nil {
return err
}
if err := api.RegisterEndpoint(api.Endpoint{
Name: "Get Profile Icon",
Description: "Returns the requested profile icon.",
Path: "profile/icon/{id:[a-f0-9]*\\.[a-z]{3,4}}",
Read: api.PermitUser,
DataFunc: handleGetProfileIcon,
}); err != nil {
return err
}
if err := api.RegisterEndpoint(api.Endpoint{
Name: "Update Profile Icon",
Description: "Updates a profile icon.",
Path: "profile/icon",
Write: api.PermitUser,
StructFunc: handleUpdateProfileIcon,
}); err != nil {
return err
}
return nil
}
type mergeProfilesRequest struct {
Name string `json:"name"` // Name of the new merged profile.
To string `json:"to"` // Profile scoped ID.
From []string `json:"from"` // Profile scoped IDs.
}
type mergeprofilesResponse struct {
New string `json:"new"` // Profile scoped ID.
}
func handleMergeProfiles(ar *api.Request) (i interface{}, err error) {
request := &mergeProfilesRequest{}
_, err = dsd.MimeLoad(ar.InputData, ar.Header.Get("Content-Type"), request)
if err != nil {
return nil, fmt.Errorf("failed to parse request: %w", err)
}
// Get all profiles.
var (
primary *Profile
secondaries = make([]*Profile, 0, len(request.From))
)
if primary, err = getProfile(request.To); err != nil {
return nil, fmt.Errorf("failed to get profile %s: %w", request.To, err)
}
for _, from := range request.From {
sp, err := getProfile(from)
if err != nil {
return nil, fmt.Errorf("failed to get profile %s: %w", request.To, err)
}
secondaries = append(secondaries, sp)
}
newProfile, err := MergeProfiles(request.Name, primary, secondaries...)
if err != nil {
return nil, fmt.Errorf("failed to merge profiles: %w", err)
}
return &mergeprofilesResponse{
New: newProfile.ScopedID(),
}, nil
}
func handleGetProfileIcon(ar *api.Request) (data []byte, err error) {
name := ar.URLVars["id"]
ext := filepath.Ext(name)
// Get profile icon.
data, err = binmeta.GetProfileIcon(name)
switch {
case err == nil:
// Continue
case errors.Is(err, binmeta.ErrIconIgnored):
return nil, api.ErrorWithStatus(err, http.StatusNotFound)
default:
return nil, err
}
// Set content type for icon.
contentType, ok := utils.MimeTypeByExtension(ext)
if ok {
ar.ResponseHeader.Set("Content-Type", contentType)
}
return data, nil
}
type updateProfileIconResponse struct {
Filename string `json:"filename"`
}
//nolint:goconst
func handleUpdateProfileIcon(ar *api.Request) (any, error) {
// Check input.
if len(ar.InputData) == 0 {
return nil, api.ErrorWithStatus(errors.New("no content"), http.StatusBadRequest)
}
mimeType := ar.Header.Get("Content-Type")
if mimeType == "" {
return nil, api.ErrorWithStatus(errors.New("no content type"), http.StatusBadRequest)
}
// Derive image format from content type.
mimeType = strings.TrimSpace(mimeType)
mimeType = strings.ToLower(mimeType)
mimeType, _, _ = strings.Cut(mimeType, ";")
var ext string
switch mimeType {
case "image/gif":
ext = "gif"
case "image/jpeg":
ext = "jpg"
case "image/jpg":
ext = "jpg"
case "image/png":
ext = "png"
case "image/svg+xml":
ext = "svg"
case "image/tiff":
ext = "tiff"
case "image/webp":
ext = "webp"
default:
return "", api.ErrorWithStatus(errors.New("unsupported image format"), http.StatusBadRequest)
}
// Update profile icon.
filename, err := binmeta.UpdateProfileIcon(ar.InputData, ext)
if err != nil {
return nil, err
}
return &updateProfileIconResponse{
Filename: filename,
}, nil
}