Merge branch 'v2.0' into task/refactor-spn
This commit is contained in:
74
base/utils/call_limiter2.go
Normal file
74
base/utils/call_limiter2.go
Normal file
@@ -0,0 +1,74 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// CallLimiter2 bundles concurrent calls and optionally limits how fast a function is called.
|
||||
type CallLimiter2 struct {
|
||||
pause time.Duration
|
||||
|
||||
slot atomic.Int64
|
||||
slotWait sync.RWMutex
|
||||
|
||||
executing atomic.Bool
|
||||
lastExec time.Time
|
||||
}
|
||||
|
||||
// NewCallLimiter2 returns a new call limiter.
|
||||
// Set minPause to zero to disable the minimum pause between calls.
|
||||
func NewCallLimiter2(minPause time.Duration) *CallLimiter2 {
|
||||
return &CallLimiter2{
|
||||
pause: minPause,
|
||||
}
|
||||
}
|
||||
|
||||
// Do executes the given function.
|
||||
// All concurrent calls to Do are bundled and return when f() finishes.
|
||||
// Waits until the minimum pause is over before executing f() again.
|
||||
func (l *CallLimiter2) Do(f func()) {
|
||||
// Get ticket number.
|
||||
slot := l.slot.Load()
|
||||
|
||||
// Check if we can execute.
|
||||
if l.executing.CompareAndSwap(false, true) {
|
||||
// Make others wait.
|
||||
l.slotWait.Lock()
|
||||
defer l.slotWait.Unlock()
|
||||
|
||||
// Execute and return.
|
||||
l.waitAndExec(f)
|
||||
return
|
||||
}
|
||||
|
||||
// Wait for slot to end and check if slot is done.
|
||||
for l.slot.Load() == slot {
|
||||
time.Sleep(100 * time.Microsecond)
|
||||
l.slotWait.RLock()
|
||||
l.slotWait.RUnlock() //nolint:staticcheck
|
||||
}
|
||||
}
|
||||
|
||||
func (l *CallLimiter2) waitAndExec(f func()) {
|
||||
defer func() {
|
||||
// Update last exec time.
|
||||
l.lastExec = time.Now().UTC()
|
||||
// Enable next execution first.
|
||||
l.executing.Store(false)
|
||||
// Move to next slot aftewards to prevent wait loops.
|
||||
l.slot.Add(1)
|
||||
}()
|
||||
|
||||
// Wait for the minimum duration between executions.
|
||||
if l.pause > 0 {
|
||||
sinceLastExec := time.Since(l.lastExec)
|
||||
if sinceLastExec < l.pause {
|
||||
time.Sleep(l.pause - sinceLastExec)
|
||||
}
|
||||
}
|
||||
|
||||
// Execute.
|
||||
f()
|
||||
}
|
||||
@@ -13,7 +13,7 @@ func TestCallLimiter(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
pause := 10 * time.Millisecond
|
||||
oa := NewCallLimiter(pause)
|
||||
oa := NewCallLimiter2(pause)
|
||||
executed := abool.New()
|
||||
var testWg sync.WaitGroup
|
||||
|
||||
@@ -41,14 +41,14 @@ func TestCallLimiter(t *testing.T) {
|
||||
executed.UnSet() // reset check
|
||||
}
|
||||
|
||||
// Wait for pause to reset.
|
||||
time.Sleep(pause)
|
||||
// Wait for 2x pause to reset.
|
||||
time.Sleep(2 * pause)
|
||||
|
||||
// Continuous use with re-execution.
|
||||
// Choose values so that about 10 executions are expected
|
||||
var execs uint32
|
||||
testWg.Add(200)
|
||||
for range 200 {
|
||||
testWg.Add(100)
|
||||
for range 100 {
|
||||
go func() {
|
||||
oa.Do(func() {
|
||||
atomic.AddUint32(&execs, 1)
|
||||
@@ -69,8 +69,8 @@ func TestCallLimiter(t *testing.T) {
|
||||
t.Errorf("unexpected high exec count: %d", execs)
|
||||
}
|
||||
|
||||
// Wait for pause to reset.
|
||||
time.Sleep(pause)
|
||||
// Wait for 2x pause to reset.
|
||||
time.Sleep(2 * pause)
|
||||
|
||||
// Check if the limiter correctly handles panics.
|
||||
testWg.Add(100)
|
||||
|
||||
@@ -9,6 +9,7 @@ import (
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/notifications"
|
||||
"github.com/safing/portmaster/service"
|
||||
"github.com/safing/portmaster/service/ui"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
@@ -71,3 +72,4 @@ type updateDummyInstance struct{}
|
||||
func (udi *updateDummyInstance) Restart() {}
|
||||
func (udi *updateDummyInstance) Shutdown() {}
|
||||
func (udi *updateDummyInstance) Notifications() *notifications.Notifications { return nil }
|
||||
func (udi *updateDummyInstance) UI() *ui.UI { return nil }
|
||||
|
||||
24
desktop/angular/package-lock.json
generated
24
desktop/angular/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "portmaster",
|
||||
"version": "0.8.11",
|
||||
"version": "2.0.1",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "portmaster",
|
||||
"version": "0.8.11",
|
||||
"version": "2.0.1",
|
||||
"dependencies": {
|
||||
"@angular/animations": "^16.0.1",
|
||||
"@angular/cdk": "^16.0.1",
|
||||
@@ -28,9 +28,11 @@
|
||||
"@tauri-apps/plugin-cli": ">=2.0.0",
|
||||
"@tauri-apps/plugin-clipboard-manager": ">=2.0.0",
|
||||
"@tauri-apps/plugin-dialog": ">=2.0.0",
|
||||
"@tauri-apps/plugin-http": ">=2.2.0",
|
||||
"@tauri-apps/plugin-notification": ">=2.0.0",
|
||||
"@tauri-apps/plugin-os": ">=2.0.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.1",
|
||||
"@tauri-apps/plugin-websocket": ">=2.2.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"d3": "^7.8.4",
|
||||
"data-urls": "^5.0.0",
|
||||
@@ -4844,6 +4846,15 @@
|
||||
"@tauri-apps/api": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-http": {
|
||||
"version": "2.4.2",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-http/-/plugin-http-2.4.2.tgz",
|
||||
"integrity": "sha512-deoafidYelei/fmd4AQoHa2aCA9N2DvnnQrF/91QNjE0xCCTuVpPhIQdVRgdHDhFehEal9uI14OTvERBpcfHrg==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-notification": {
|
||||
"version": "2.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-notification/-/plugin-notification-2.2.1.tgz",
|
||||
@@ -4871,6 +4882,15 @@
|
||||
"@tauri-apps/api": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tauri-apps/plugin-websocket": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/@tauri-apps/plugin-websocket/-/plugin-websocket-2.3.0.tgz",
|
||||
"integrity": "sha512-eAwRGe3tnqDeQYE0wq4g1PUKbam9tYvlC4uP/au12Y/z7MP4lrS4ylv+aoZ5Ly+hTlBdi7hDkhHomwF/UeBesA==",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"dependencies": {
|
||||
"@tauri-apps/api": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tootallnate/once": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
|
||||
|
||||
@@ -45,6 +45,8 @@
|
||||
"@tauri-apps/plugin-notification": ">=2.0.0",
|
||||
"@tauri-apps/plugin-os": ">=2.0.0",
|
||||
"@tauri-apps/plugin-shell": "^2.0.1",
|
||||
"@tauri-apps/plugin-http": ">=2.2.0",
|
||||
"@tauri-apps/plugin-websocket": ">=2.2.0",
|
||||
"autoprefixer": "^10.4.14",
|
||||
"d3": "^7.8.4",
|
||||
"data-urls": "^5.0.0",
|
||||
|
||||
1720
desktop/tauri/src-tauri/Cargo.lock
generated
1720
desktop/tauri/src-tauri/Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
@@ -25,6 +25,8 @@ tauri-plugin-single-instance = "2.2.1"
|
||||
tauri-plugin-notification = "2.2.1"
|
||||
tauri-plugin-log = "2.2.1"
|
||||
tauri-plugin-window-state = "2.2.1"
|
||||
tauri-plugin-http = "2.2.1"
|
||||
tauri-plugin-websocket = "2.2.1"
|
||||
|
||||
clap_lex = "0.7.2"
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -37,7 +37,7 @@
|
||||
],
|
||||
"definitions": {
|
||||
"Capability": {
|
||||
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
|
||||
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identifier",
|
||||
@@ -70,14 +70,14 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"windows": {
|
||||
"description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`",
|
||||
"description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"webviews": {
|
||||
"description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`",
|
||||
"description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
@@ -134,6 +134,123 @@
|
||||
"description": "Reference a permission or permission set by identifier and extends its scope.",
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n",
|
||||
"type": "string",
|
||||
"const": "http:default"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-cancel"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_read_body command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-read-body"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-send"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-cancel"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_read_body command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-read-body"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-send"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"allow": {
|
||||
"items": {
|
||||
"title": "HttpScopeEntry",
|
||||
"description": "HTTP scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"url": {
|
||||
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"items": {
|
||||
"title": "HttpScopeEntry",
|
||||
"description": "HTTP scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"url": {
|
||||
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "Identifier of the permission or permission set.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Identifier"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
@@ -465,11 +582,26 @@
|
||||
"type": "string",
|
||||
"const": "core:app:allow-default-window-icon"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:allow-fetch-data-store-identifiers"
|
||||
},
|
||||
{
|
||||
"description": "Enables the identifier command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:allow-identifier"
|
||||
},
|
||||
{
|
||||
"description": "Enables the name command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:allow-name"
|
||||
},
|
||||
{
|
||||
"description": "Enables the remove_data_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:allow-remove-data-store"
|
||||
},
|
||||
{
|
||||
"description": "Enables the set_app_theme command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -500,11 +632,26 @@
|
||||
"type": "string",
|
||||
"const": "core:app:deny-default-window-icon"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:deny-fetch-data-store-identifiers"
|
||||
},
|
||||
{
|
||||
"description": "Denies the identifier command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:deny-identifier"
|
||||
},
|
||||
{
|
||||
"description": "Denies the name command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:deny-name"
|
||||
},
|
||||
{
|
||||
"description": "Denies the remove_data_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:deny-remove-data-store"
|
||||
},
|
||||
{
|
||||
"description": "Denies the set_app_theme command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -1300,6 +1447,11 @@
|
||||
"type": "string",
|
||||
"const": "core:window:allow-internal-toggle-maximize"
|
||||
},
|
||||
{
|
||||
"description": "Enables the is_always_on_top command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:window:allow-is-always-on-top"
|
||||
},
|
||||
{
|
||||
"description": "Enables the is_closable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -1665,6 +1817,11 @@
|
||||
"type": "string",
|
||||
"const": "core:window:deny-internal-toggle-maximize"
|
||||
},
|
||||
{
|
||||
"description": "Denies the is_always_on_top command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:window:deny-is-always-on-top"
|
||||
},
|
||||
{
|
||||
"description": "Denies the is_closable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -2025,6 +2182,51 @@
|
||||
"type": "string",
|
||||
"const": "dialog:deny-save"
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n",
|
||||
"type": "string",
|
||||
"const": "http:default"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-cancel"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_read_body command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-read-body"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-send"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-cancel"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_read_body command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-read-body"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-send"
|
||||
},
|
||||
{
|
||||
"description": "Allows the log command",
|
||||
"type": "string",
|
||||
@@ -2345,6 +2547,31 @@
|
||||
"type": "string",
|
||||
"const": "shell:deny-stdin-write"
|
||||
},
|
||||
{
|
||||
"description": "Allows connecting and sending data to a WebSocket server",
|
||||
"type": "string",
|
||||
"const": "websocket:default"
|
||||
},
|
||||
{
|
||||
"description": "Enables the connect command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "websocket:allow-connect"
|
||||
},
|
||||
{
|
||||
"description": "Enables the send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "websocket:allow-send"
|
||||
},
|
||||
{
|
||||
"description": "Denies the connect command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "websocket:deny-connect"
|
||||
},
|
||||
{
|
||||
"description": "Denies the send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "websocket:deny-send"
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n",
|
||||
"type": "string",
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
],
|
||||
"definitions": {
|
||||
"Capability": {
|
||||
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows fine grained access to the Tauri core, application, or plugin commands. If a window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
|
||||
"description": "A grouping and boundary mechanism developers can use to isolate access to the IPC layer.\n\nIt controls application windows' and webviews' fine grained access to the Tauri core, application, or plugin commands. If a webview or its window is not matching any capability then it has no access to the IPC layer at all.\n\nThis can be done to create groups of windows, based on their required system access, which can reduce impact of frontend vulnerabilities in less privileged windows. Windows can be added to a capability by exact name (e.g. `main-window`) or glob patterns like `*` or `admin-*`. A Window can have none, one, or multiple associated capabilities.\n\n## Example\n\n```json { \"identifier\": \"main-user-files-write\", \"description\": \"This capability allows the `main` window on macOS and Windows access to `filesystem` write related commands and `dialog` commands to enable programatic access to files selected by the user.\", \"windows\": [ \"main\" ], \"permissions\": [ \"core:default\", \"dialog:open\", { \"identifier\": \"fs:allow-write-text-file\", \"allow\": [{ \"path\": \"$HOME/test.txt\" }] }, ], \"platforms\": [\"macOS\",\"windows\"] } ```",
|
||||
"type": "object",
|
||||
"required": [
|
||||
"identifier",
|
||||
@@ -70,14 +70,14 @@
|
||||
"type": "boolean"
|
||||
},
|
||||
"windows": {
|
||||
"description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nOn multiwebview windows, prefer [`Self::webviews`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`",
|
||||
"description": "List of windows that are affected by this capability. Can be a glob pattern.\n\nIf a window label matches any of the patterns in this list, the capability will be enabled on all the webviews of that window, regardless of the value of [`Self::webviews`].\n\nOn multiwebview windows, prefer specifying [`Self::webviews`] and omitting [`Self::windows`] for a fine grained access control.\n\n## Example\n\n`[\"main\"]`",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"webviews": {
|
||||
"description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThis is only required when using on multiwebview contexts, by default all child webviews of a window that matches [`Self::windows`] are linked.\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`",
|
||||
"description": "List of webviews that are affected by this capability. Can be a glob pattern.\n\nThe capability will be enabled on all the webviews whose label matches any of the patterns in this list, regardless of whether the webview's window label matches a pattern in [`Self::windows`].\n\n## Example\n\n`[\"sub-webview-one\", \"sub-webview-two\"]`",
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
@@ -134,6 +134,123 @@
|
||||
"description": "Reference a permission or permission set by identifier and extends its scope.",
|
||||
"type": "object",
|
||||
"allOf": [
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n",
|
||||
"type": "string",
|
||||
"const": "http:default"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-cancel"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_read_body command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-read-body"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-send"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-cancel"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_read_body command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-read-body"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-send"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"properties": {
|
||||
"allow": {
|
||||
"items": {
|
||||
"title": "HttpScopeEntry",
|
||||
"description": "HTTP scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"url": {
|
||||
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"deny": {
|
||||
"items": {
|
||||
"title": "HttpScopeEntry",
|
||||
"description": "HTTP scope entry.",
|
||||
"anyOf": [
|
||||
{
|
||||
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"required": [
|
||||
"url"
|
||||
],
|
||||
"properties": {
|
||||
"url": {
|
||||
"description": "A URL that can be accessed by the webview when using the HTTP APIs. Wildcards can be used following the URL pattern standard.\n\nSee [the URL Pattern spec](https://urlpattern.spec.whatwg.org/) for more information.\n\nExamples:\n\n- \"https://*\" : allows all HTTPS origin on port 443\n\n- \"https://*:*\" : allows all HTTPS origin on any port\n\n- \"https://*.github.com/tauri-apps/tauri\": allows any subdomain of \"github.com\" with the \"tauri-apps/api\" path\n\n- \"https://myapi.service.com/users/*\": allows access to any URLs that begins with \"https://myapi.service.com/users/\"",
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"identifier": {
|
||||
"description": "Identifier of the permission or permission set.",
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/definitions/Identifier"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
@@ -465,11 +582,26 @@
|
||||
"type": "string",
|
||||
"const": "core:app:allow-default-window-icon"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_data_store_identifiers command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:allow-fetch-data-store-identifiers"
|
||||
},
|
||||
{
|
||||
"description": "Enables the identifier command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:allow-identifier"
|
||||
},
|
||||
{
|
||||
"description": "Enables the name command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:allow-name"
|
||||
},
|
||||
{
|
||||
"description": "Enables the remove_data_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:allow-remove-data-store"
|
||||
},
|
||||
{
|
||||
"description": "Enables the set_app_theme command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -500,11 +632,26 @@
|
||||
"type": "string",
|
||||
"const": "core:app:deny-default-window-icon"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_data_store_identifiers command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:deny-fetch-data-store-identifiers"
|
||||
},
|
||||
{
|
||||
"description": "Denies the identifier command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:deny-identifier"
|
||||
},
|
||||
{
|
||||
"description": "Denies the name command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:deny-name"
|
||||
},
|
||||
{
|
||||
"description": "Denies the remove_data_store command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:app:deny-remove-data-store"
|
||||
},
|
||||
{
|
||||
"description": "Denies the set_app_theme command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -1300,6 +1447,11 @@
|
||||
"type": "string",
|
||||
"const": "core:window:allow-internal-toggle-maximize"
|
||||
},
|
||||
{
|
||||
"description": "Enables the is_always_on_top command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:window:allow-is-always-on-top"
|
||||
},
|
||||
{
|
||||
"description": "Enables the is_closable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -1665,6 +1817,11 @@
|
||||
"type": "string",
|
||||
"const": "core:window:deny-internal-toggle-maximize"
|
||||
},
|
||||
{
|
||||
"description": "Denies the is_always_on_top command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "core:window:deny-is-always-on-top"
|
||||
},
|
||||
{
|
||||
"description": "Denies the is_closable command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
@@ -2025,6 +2182,51 @@
|
||||
"type": "string",
|
||||
"const": "dialog:deny-save"
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\nfetch operations are available from the http plugin.\n\nThis enables all fetch operations but does not\nallow explicitly any origins to be fetched. This needs to\nbe manually configured before usage.\n\n#### Granted Permissions\n\nAll fetch operations are enabled.\n\n",
|
||||
"type": "string",
|
||||
"const": "http:default"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-cancel"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_read_body command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-read-body"
|
||||
},
|
||||
{
|
||||
"description": "Enables the fetch_send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:allow-fetch-send"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_cancel command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-cancel"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_read_body command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-read-body"
|
||||
},
|
||||
{
|
||||
"description": "Denies the fetch_send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "http:deny-fetch-send"
|
||||
},
|
||||
{
|
||||
"description": "Allows the log command",
|
||||
"type": "string",
|
||||
@@ -2345,6 +2547,31 @@
|
||||
"type": "string",
|
||||
"const": "shell:deny-stdin-write"
|
||||
},
|
||||
{
|
||||
"description": "Allows connecting and sending data to a WebSocket server",
|
||||
"type": "string",
|
||||
"const": "websocket:default"
|
||||
},
|
||||
{
|
||||
"description": "Enables the connect command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "websocket:allow-connect"
|
||||
},
|
||||
{
|
||||
"description": "Enables the send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "websocket:allow-send"
|
||||
},
|
||||
{
|
||||
"description": "Denies the connect command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "websocket:deny-connect"
|
||||
},
|
||||
{
|
||||
"description": "Denies the send command without any pre-configured scope.",
|
||||
"type": "string",
|
||||
"const": "websocket:deny-send"
|
||||
},
|
||||
{
|
||||
"description": "This permission set configures what kind of\noperations are available from the window state plugin.\n\n#### Granted Permissions\n\nAll operations are enabled by default.\n\n",
|
||||
"type": "string",
|
||||
|
||||
@@ -438,6 +438,15 @@ func (i *Instance) BinaryUpdates() *updates.Updater {
|
||||
return i.binaryUpdates
|
||||
}
|
||||
|
||||
// GetBinaryUpdateFile returns the file path of a binary update file.
|
||||
func (i *Instance) GetBinaryUpdateFile(name string) (path string, err error) {
|
||||
file, err := i.binaryUpdates.GetFile(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return file.Path(), nil
|
||||
}
|
||||
|
||||
// IntelUpdates returns the updates module.
|
||||
func (i *Instance) IntelUpdates() *updates.Updater {
|
||||
return i.intelUpdates
|
||||
|
||||
@@ -19,7 +19,7 @@ var (
|
||||
// pidsByUserLock is also used for locking the socketInfo.PID on all socket.*Info structs.
|
||||
pidsByUser = make(map[int][]int)
|
||||
pidsByUserLock sync.RWMutex
|
||||
fetchPidsByUser = utils.NewCallLimiter(10 * time.Millisecond)
|
||||
fetchPidsByUser = utils.NewCallLimiter2(10 * time.Millisecond)
|
||||
)
|
||||
|
||||
// getPidsByUser returns the cached PIDs for the given UID.
|
||||
|
||||
@@ -25,7 +25,7 @@ type tcpTable struct {
|
||||
// lastUpdateAt stores the time when the tables where last updated as unix nanoseconds.
|
||||
lastUpdateAt atomic.Int64
|
||||
|
||||
fetchLimiter *utils.CallLimiter
|
||||
fetchLimiter *utils.CallLimiter2
|
||||
fetchTable func() (connections []*socket.ConnectionInfo, listeners []*socket.BindInfo, err error)
|
||||
|
||||
dualStack *tcpTable
|
||||
@@ -34,13 +34,13 @@ type tcpTable struct {
|
||||
var (
|
||||
tcp6Table = &tcpTable{
|
||||
version: 6,
|
||||
fetchLimiter: utils.NewCallLimiter(minDurationBetweenTableUpdates),
|
||||
fetchLimiter: utils.NewCallLimiter2(minDurationBetweenTableUpdates),
|
||||
fetchTable: getTCP6Table,
|
||||
}
|
||||
|
||||
tcp4Table = &tcpTable{
|
||||
version: 4,
|
||||
fetchLimiter: utils.NewCallLimiter(minDurationBetweenTableUpdates),
|
||||
fetchLimiter: utils.NewCallLimiter2(minDurationBetweenTableUpdates),
|
||||
fetchTable: getTCP4Table,
|
||||
}
|
||||
)
|
||||
|
||||
@@ -24,7 +24,7 @@ type udpTable struct {
|
||||
// lastUpdateAt stores the time when the tables where last updated as unix nanoseconds.
|
||||
lastUpdateAt atomic.Int64
|
||||
|
||||
fetchLimiter *utils.CallLimiter
|
||||
fetchLimiter *utils.CallLimiter2
|
||||
fetchTable func() (binds []*socket.BindInfo, err error)
|
||||
|
||||
states map[string]map[string]*udpState
|
||||
@@ -52,14 +52,14 @@ const (
|
||||
var (
|
||||
udp6Table = &udpTable{
|
||||
version: 6,
|
||||
fetchLimiter: utils.NewCallLimiter(minDurationBetweenTableUpdates),
|
||||
fetchLimiter: utils.NewCallLimiter2(minDurationBetweenTableUpdates),
|
||||
fetchTable: getUDP6Table,
|
||||
states: make(map[string]map[string]*udpState),
|
||||
}
|
||||
|
||||
udp4Table = &udpTable{
|
||||
version: 4,
|
||||
fetchLimiter: utils.NewCallLimiter(minDurationBetweenTableUpdates),
|
||||
fetchLimiter: utils.NewCallLimiter2(minDurationBetweenTableUpdates),
|
||||
fetchTable: getUDP4Table,
|
||||
states: make(map[string]map[string]*udpState),
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package process
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/safing/portmaster/base/log"
|
||||
@@ -21,7 +22,12 @@ func (pm *ProcessModule) Manager() *mgr.Manager {
|
||||
}
|
||||
|
||||
func (pm *ProcessModule) Start() error {
|
||||
file, err := pm.instance.BinaryUpdates().GetFile("portmaster")
|
||||
identifier := "portmaster"
|
||||
if runtime.GOOS == "windows" {
|
||||
identifier += ".exe"
|
||||
}
|
||||
|
||||
file, err := pm.instance.BinaryUpdates().GetFile(identifier)
|
||||
if err != nil {
|
||||
log.Errorf("process: failed to get path of ui: %s", err)
|
||||
} else {
|
||||
|
||||
@@ -72,7 +72,7 @@ type Profile struct { //nolint:maligned // not worth the effort
|
||||
// Icons holds a list of icons to represent the application.
|
||||
Icons []binmeta.Icon
|
||||
|
||||
// Deprecated: LinkedPath used to point to the executableis this
|
||||
// Deprecated: LinkedPath used to point to the executables this
|
||||
// profile was created for.
|
||||
// Until removed, it will be added to the Fingerprints as an exact path match.
|
||||
LinkedPath string // constant
|
||||
|
||||
@@ -2,35 +2,21 @@ package ui
|
||||
|
||||
import (
|
||||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
)
|
||||
|
||||
func registerAPIEndpoints() error {
|
||||
func (ui *UI) registerAPIEndpoints() error {
|
||||
return api.RegisterEndpoint(api.Endpoint{
|
||||
Path: "ui/reload",
|
||||
Write: api.PermitUser,
|
||||
ActionFunc: reloadUI,
|
||||
ActionFunc: ui.reloadUI,
|
||||
Name: "Reload UI Assets",
|
||||
Description: "Removes all assets from the cache and reloads the current (possibly updated) version from disk when requested.",
|
||||
})
|
||||
}
|
||||
|
||||
func reloadUI(_ *api.Request) (msg string, err error) {
|
||||
appsLock.Lock()
|
||||
defer appsLock.Unlock()
|
||||
|
||||
func (ui *UI) reloadUI(_ *api.Request) (msg string, err error) {
|
||||
// Close all archives.
|
||||
for id, archiveFS := range apps {
|
||||
err := archiveFS.Close()
|
||||
if err != nil {
|
||||
log.Warningf("ui: failed to close archive %s: %s", id, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Reset index.
|
||||
for key := range apps {
|
||||
delete(apps, key)
|
||||
}
|
||||
ui.CloseArchives()
|
||||
|
||||
return "all ui archives successfully reloaded", nil
|
||||
}
|
||||
|
||||
@@ -1,27 +1,55 @@
|
||||
package ui
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
|
||||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
"github.com/spkg/zipfs"
|
||||
)
|
||||
|
||||
func prep() error {
|
||||
if err := registerAPIEndpoints(); err != nil {
|
||||
return err
|
||||
}
|
||||
// UI serves the user interface files.
|
||||
type UI struct {
|
||||
mgr *mgr.Manager
|
||||
instance instance
|
||||
|
||||
return registerRoutes()
|
||||
archives map[string]*zipfs.FileSystem
|
||||
archivesLock sync.RWMutex
|
||||
|
||||
upgradeLock atomic.Bool
|
||||
}
|
||||
|
||||
func start() error {
|
||||
// New returns a new UI module.
|
||||
func New(instance instance) (*UI, error) {
|
||||
m := mgr.New("UI")
|
||||
ui := &UI{
|
||||
mgr: m,
|
||||
instance: instance,
|
||||
|
||||
archives: make(map[string]*zipfs.FileSystem),
|
||||
}
|
||||
|
||||
if err := ui.registerAPIEndpoints(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := ui.registerRoutes(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return ui, nil
|
||||
}
|
||||
|
||||
func (ui *UI) Manager() *mgr.Manager {
|
||||
return ui.mgr
|
||||
}
|
||||
|
||||
// Start starts the module.
|
||||
func (ui *UI) Start() error {
|
||||
// Create a dummy directory to which processes change their working directory
|
||||
// to. Currently this includes the App and the Notifier. The aim is protect
|
||||
// all other directories and increase compatibility should any process want
|
||||
@@ -30,7 +58,7 @@ func start() error {
|
||||
// may seem dangerous, but proper permission on the parent directory provide
|
||||
// (some) protection.
|
||||
// Processes must _never_ read from this directory.
|
||||
execDir := filepath.Join(module.instance.DataDir(), "exec")
|
||||
execDir := filepath.Join(ui.instance.DataDir(), "exec")
|
||||
err := os.MkdirAll(execDir, 0o0777) //nolint:gosec // This is intentional.
|
||||
if err != nil {
|
||||
log.Warningf("ui: failed to create safe exec dir: %s", err)
|
||||
@@ -45,52 +73,67 @@ func start() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// UI serves the user interface files.
|
||||
type UI struct {
|
||||
mgr *mgr.Manager
|
||||
|
||||
instance instance
|
||||
}
|
||||
|
||||
func (ui *UI) Manager() *mgr.Manager {
|
||||
return ui.mgr
|
||||
}
|
||||
|
||||
// Start starts the module.
|
||||
func (ui *UI) Start() error {
|
||||
return start()
|
||||
}
|
||||
|
||||
// Stop stops the module.
|
||||
func (ui *UI) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
shimLoaded atomic.Bool
|
||||
module *UI
|
||||
)
|
||||
func (ui *UI) getArchive(name string) (archive *zipfs.FileSystem, ok bool) {
|
||||
ui.archivesLock.RLock()
|
||||
defer ui.archivesLock.RUnlock()
|
||||
|
||||
// New returns a new UI module.
|
||||
func New(instance instance) (*UI, error) {
|
||||
if !shimLoaded.CompareAndSwap(false, true) {
|
||||
return nil, errors.New("only one instance allowed")
|
||||
}
|
||||
m := mgr.New("UI")
|
||||
module = &UI{
|
||||
mgr: m,
|
||||
instance: instance,
|
||||
archive, ok = ui.archives[name]
|
||||
return
|
||||
}
|
||||
|
||||
func (ui *UI) setArchive(name string, archive *zipfs.FileSystem) {
|
||||
ui.archivesLock.Lock()
|
||||
defer ui.archivesLock.Unlock()
|
||||
|
||||
ui.archives[name] = archive
|
||||
}
|
||||
|
||||
// CloseArchives closes all open archives.
|
||||
func (ui *UI) CloseArchives() {
|
||||
if ui == nil {
|
||||
return
|
||||
}
|
||||
|
||||
if err := prep(); err != nil {
|
||||
return nil, err
|
||||
ui.archivesLock.Lock()
|
||||
defer ui.archivesLock.Unlock()
|
||||
|
||||
// Close archives.
|
||||
for _, archive := range ui.archives {
|
||||
if err := archive.Close(); err != nil {
|
||||
ui.mgr.Warn("failed to close ui archive", "err", err)
|
||||
}
|
||||
}
|
||||
|
||||
return module, nil
|
||||
// Reset map.
|
||||
clear(ui.archives)
|
||||
}
|
||||
|
||||
// EnableUpgradeLock enables the upgrade lock and closes all open archives.
|
||||
func (ui *UI) EnableUpgradeLock() {
|
||||
if ui == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ui.upgradeLock.Store(true)
|
||||
ui.CloseArchives()
|
||||
}
|
||||
|
||||
// DisableUpgradeLock disables the upgrade lock.
|
||||
func (ui *UI) DisableUpgradeLock() {
|
||||
if ui == nil {
|
||||
return
|
||||
}
|
||||
|
||||
ui.upgradeLock.Store(false)
|
||||
}
|
||||
|
||||
type instance interface {
|
||||
DataDir() string
|
||||
API() *api.API
|
||||
BinaryUpdates() *updates.Updater
|
||||
GetBinaryUpdateFile(name string) (path string, err error)
|
||||
}
|
||||
|
||||
@@ -9,26 +9,19 @@ import (
|
||||
"net/url"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"github.com/spkg/zipfs"
|
||||
|
||||
"github.com/safing/portmaster/base/api"
|
||||
"github.com/safing/portmaster/base/log"
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/service/updates"
|
||||
)
|
||||
|
||||
var (
|
||||
apps = make(map[string]*zipfs.FileSystem)
|
||||
appsLock sync.RWMutex
|
||||
)
|
||||
|
||||
func registerRoutes() error {
|
||||
func (ui *UI) registerRoutes() error {
|
||||
// Server assets.
|
||||
api.RegisterHandler(
|
||||
"/assets/{resPath:[a-zA-Z0-9/\\._-]+}",
|
||||
&archiveServer{defaultModuleName: "assets"},
|
||||
&archiveServer{ui: ui, defaultModuleName: "assets"},
|
||||
)
|
||||
|
||||
// Add slash to plain module namespaces.
|
||||
@@ -38,7 +31,7 @@ func registerRoutes() error {
|
||||
)
|
||||
|
||||
// Serve modules.
|
||||
srv := &archiveServer{}
|
||||
srv := &archiveServer{ui: ui}
|
||||
api.RegisterHandler("/ui/modules/{moduleName:[a-z]+}/", srv)
|
||||
api.RegisterHandler("/ui/modules/{moduleName:[a-z]+}/{resPath:[a-zA-Z0-9/\\._-]+}", srv)
|
||||
|
||||
@@ -52,6 +45,7 @@ func registerRoutes() error {
|
||||
}
|
||||
|
||||
type archiveServer struct {
|
||||
ui *UI
|
||||
defaultModuleName string
|
||||
}
|
||||
|
||||
@@ -82,39 +76,35 @@ func (bs *archiveServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
||||
resPath = "index.html"
|
||||
}
|
||||
|
||||
appsLock.RLock()
|
||||
archiveFS, ok := apps[moduleName]
|
||||
appsLock.RUnlock()
|
||||
archiveFS, ok := bs.ui.getArchive(moduleName)
|
||||
if ok {
|
||||
ServeFileFromArchive(w, r, moduleName, archiveFS, resPath)
|
||||
return
|
||||
}
|
||||
|
||||
// Check if the upgrade lock is enabled.
|
||||
if bs.ui.upgradeLock.Load() {
|
||||
http.Error(w, "Resources locked, upgrade in progress.", http.StatusLocked)
|
||||
return
|
||||
}
|
||||
|
||||
// get file from update system
|
||||
zipFile, err := module.instance.BinaryUpdates().GetFile(fmt.Sprintf("%s.zip", moduleName))
|
||||
zipFile, err := bs.ui.instance.GetBinaryUpdateFile(fmt.Sprintf("%s.zip", moduleName))
|
||||
if err != nil {
|
||||
if errors.Is(err, updates.ErrNotFound) {
|
||||
log.Tracef("ui: requested module %s does not exist", moduleName)
|
||||
http.Error(w, err.Error(), http.StatusNotFound)
|
||||
} else {
|
||||
log.Tracef("ui: error loading module %s: %s", moduleName, err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
}
|
||||
log.Tracef("ui: error loading module %s: %s", moduleName, err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
// Open archive from disk.
|
||||
archiveFS, err = zipfs.New(zipFile.Path())
|
||||
archiveFS, err = zipfs.New(zipFile)
|
||||
if err != nil {
|
||||
log.Tracef("ui: error prepping module %s: %s", moduleName, err)
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
|
||||
appsLock.Lock()
|
||||
apps[moduleName] = archiveFS
|
||||
appsLock.Unlock()
|
||||
|
||||
bs.ui.setArchive(moduleName, archiveFS)
|
||||
ServeFileFromArchive(w, r, moduleName, archiveFS, resPath)
|
||||
}
|
||||
|
||||
|
||||
@@ -20,6 +20,7 @@ import (
|
||||
"github.com/safing/portmaster/base/utils"
|
||||
"github.com/safing/portmaster/service/configure"
|
||||
"github.com/safing/portmaster/service/mgr"
|
||||
"github.com/safing/portmaster/service/ui"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -645,4 +646,5 @@ type instance interface {
|
||||
Restart()
|
||||
Shutdown()
|
||||
Notifications() *notifications.Notifications
|
||||
UI() *ui.UI
|
||||
}
|
||||
|
||||
@@ -24,6 +24,10 @@ func (u *Updater) upgrade(downloader *Downloader, ignoreVersion bool) error {
|
||||
}
|
||||
}
|
||||
|
||||
// Unload UI assets to be able to move files on Windows.
|
||||
u.instance.UI().EnableUpgradeLock()
|
||||
defer u.instance.UI().DisableUpgradeLock()
|
||||
|
||||
// Execute the upgrade.
|
||||
upgradeError := u.upgradeMoveFiles(downloader)
|
||||
if upgradeError == nil {
|
||||
|
||||
Reference in New Issue
Block a user