Merge pull request #1387 from safing/feature/windows-icons

Add windows icon support in core and more
This commit is contained in:
Daniel Hovie 2023-12-19 15:42:18 +01:00 committed by GitHub
commit ceaf1546d2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
37 changed files with 536 additions and 310 deletions

View file

@ -1,72 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
#
# ******** NOTE ********
# We have attempted to detect the languages in your repository. Please check
# the `language` matrix defined below to confirm you have the correct set of
# supported CodeQL languages.
#
name: "CodeQL"
on:
push:
branches: [ "develop", master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ "develop" ]
schedule:
- cron: '43 14 * * 4'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: false
matrix:
language: [ 'go' ]
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby' ]
# Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# Details on CodeQL's query packs refer to : https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs
# queries: security-extended,security-and-quality
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
# If the Autobuild fails above, remove it and uncomment the following three lines.
# modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance.
# - run: |
# echo "Run, Build Application using script"
# ./location_of_script_within_repo/buildscript.sh
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

31
go.mod
View file

@ -4,6 +4,9 @@ go 1.21.1
toolchain go1.21.2
// TODO: Remove when https://github.com/tc-hib/winres/pull/4 is merged or changes are otherwise integrated.
replace github.com/tc-hib/winres => github.com/dhaavi/winres v0.2.2
require (
github.com/Xuanwo/go-locale v1.1.0
github.com/agext/levenshtein v1.2.3
@ -11,6 +14,7 @@ require (
github.com/coreos/go-iptables v0.7.0
github.com/florianl/go-conntrack v0.4.0
github.com/florianl/go-nfqueue v1.3.1
github.com/fogleman/gg v1.3.0
github.com/ghodss/yaml v1.0.0
github.com/godbus/dbus/v5 v5.1.0
github.com/google/gopacket v1.1.19
@ -18,11 +22,12 @@ require (
github.com/hashicorp/go-multierror v1.1.1
github.com/hashicorp/go-version v1.6.0
github.com/jackc/puddle/v2 v2.2.1
github.com/mat/besticon v3.12.0+incompatible
github.com/miekg/dns v1.1.57
github.com/mitchellh/go-server-timing v1.0.1
github.com/oschwald/maxminddb-golang v1.12.0
github.com/safing/jess v0.3.2
github.com/safing/portbase v0.18.6
github.com/safing/portbase v0.18.7-0.20231218160927-7631b9d28afe
github.com/safing/portmaster-android/go v0.0.0-20230830120134-3226ceac3bec
github.com/safing/spn v0.7.4
github.com/shirou/gopsutil v3.21.11+incompatible
@ -30,15 +35,16 @@ require (
github.com/spkg/zipfs v0.7.1
github.com/stretchr/testify v1.8.4
github.com/tannerryan/ring v1.1.2
github.com/tc-hib/winres v0.2.1
github.com/tevino/abool v1.2.0
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26
github.com/vincent-petithory/dataurl v1.0.0
golang.org/x/exp v0.0.0-20231127185646-65229373498e
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611
golang.org/x/net v0.19.0
golang.org/x/sync v0.5.0
golang.org/x/sys v0.15.0
gopkg.in/yaml.v3 v3.0.1
zombiezen.com/go/sqlite v0.13.1
zombiezen.com/go/sqlite v1.0.0
)
require (
@ -49,7 +55,7 @@ require (
github.com/armon/go-radix v1.0.0 // indirect
github.com/awalterschulze/gographviz v2.0.3+incompatible // indirect
github.com/bluele/gcache v0.0.2 // indirect
github.com/danieljoos/wincred v1.2.0 // indirect
github.com/danieljoos/wincred v1.2.1 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dustin/go-humanize v1.0.1 // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
@ -57,10 +63,11 @@ require (
github.com/fxamacker/cbor/v2 v2.5.0 // indirect
github.com/go-ole/go-ole v1.3.0 // indirect
github.com/gofrs/uuid v4.4.0+incompatible // indirect
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f // indirect
github.com/google/btree v1.1.2 // indirect
github.com/google/go-cmp v0.6.0 // indirect
github.com/google/uuid v1.4.0 // indirect
github.com/google/uuid v1.5.0 // indirect
github.com/gorilla/mux v1.8.1 // indirect
github.com/gorilla/websocket v1.5.1 // indirect
github.com/hashicorp/errwrap v1.1.0 // indirect
@ -72,6 +79,7 @@ require (
github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect
github.com/mr-tron/base58 v1.2.0 // indirect
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
@ -84,8 +92,8 @@ require (
github.com/tidwall/match v1.1.1 // indirect
github.com/tidwall/pretty v1.2.1 // indirect
github.com/tidwall/sjson v1.2.5 // indirect
github.com/tklauser/go-sysconf v0.3.12 // indirect
github.com/tklauser/numcpus v0.6.1 // indirect
github.com/tklauser/go-sysconf v0.3.13 // indirect
github.com/tklauser/numcpus v0.7.0 // indirect
github.com/valyala/fastrand v1.1.0 // indirect
github.com/valyala/histogram v1.2.0 // indirect
github.com/vmihailenco/msgpack/v5 v5.4.1 // indirect
@ -95,14 +103,15 @@ require (
github.com/zalando/go-keyring v0.2.3 // indirect
go.etcd.io/bbolt v1.3.8 // indirect
golang.org/x/crypto v0.16.0 // indirect
golang.org/x/image v0.14.0 // indirect
golang.org/x/mod v0.14.0 // indirect
golang.org/x/text v0.14.0 // indirect
golang.org/x/time v0.5.0 // indirect
golang.org/x/tools v0.16.0 // indirect
golang.org/x/tools v0.16.1 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
gvisor.dev/gvisor v0.0.0-20231130223849-479d60c2258b // indirect
modernc.org/libc v1.34.11 // indirect
gvisor.dev/gvisor v0.0.0-20231215062520-cd4a40b584a5 // indirect
modernc.org/libc v1.37.5 // indirect
modernc.org/mathutil v1.6.0 // indirect
modernc.org/memory v1.7.2 // indirect
modernc.org/sqlite v1.27.0 // indirect
modernc.org/sqlite v1.28.0 // indirect
)

56
go.sum
View file

@ -33,8 +33,8 @@ github.com/cilium/ebpf v0.12.3/go.mod h1:TctK1ivibvI3znr66ljgi4hqOT8EYQjz1KWBfb1
github.com/coreos/go-iptables v0.7.0 h1:XWM3V+MPRr5/q51NuWSgU0fqMad64Zyxs8ZUoMsamr8=
github.com/coreos/go-iptables v0.7.0/go.mod h1:Qe8Bv2Xik5FyTXwgIbLAnv2sWSBmvWdFETJConOQ//Q=
github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/danieljoos/wincred v1.2.0 h1:ozqKHaLK0W/ii4KVbbvluM91W2H3Sh0BncbUNPS7jLE=
github.com/danieljoos/wincred v1.2.0/go.mod h1:FzQLLMKBFdvu+osBrnFODiv32YGwCfx0SkRa/eYHgec=
github.com/danieljoos/wincred v1.2.1 h1:dl9cBrupW8+r5250DYkYxocLeZ1Y4vB1kxgtjxw8GQs=
github.com/danieljoos/wincred v1.2.1/go.mod h1:uGaFL9fDn3OLTvzCGulzE+SzjEe5NGlh5FdCcyfPwps=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
@ -42,6 +42,8 @@ github.com/dgraph-io/badger v1.6.2 h1:mNw0qs90GVgGGWylh0umH5iag1j6n/PeJtNvL6KY/x
github.com/dgraph-io/badger v1.6.2/go.mod h1:JW2yswe3V058sS0kZ2h/AXeDSqFjxnZcRrVH//y2UQE=
github.com/dgraph-io/ristretto v0.1.1 h1:6CWw5tJNgpegArSHpNHJKldNeq03FQCwYvfMVWajOK8=
github.com/dgraph-io/ristretto v0.1.1/go.mod h1:S1GPSBCYCIhmVNfcth17y2zZtQT6wzkzgwUve0VDWWA=
github.com/dhaavi/winres v0.2.2 h1:SUago7FwhgLSMyDdeuV6enBZ+ZQSl0KwcnbWzvlfBls=
github.com/dhaavi/winres v0.2.2/go.mod h1:1NTs+/DtKP1BplIL1+XQSoq4X1PUfLczexS7gf3x9T4=
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
github.com/felixge/httpsnoop v1.0.0/go.mod h1:3+D9sFq0ahK/JeJPhCBUV1xlf4/eIYrUQaxulT0VzX8=
@ -51,6 +53,8 @@ github.com/florianl/go-conntrack v0.4.0 h1:TlYkxytdwgVayfU0cKwkHurQA0Rd1ZSEBRckR
github.com/florianl/go-conntrack v0.4.0/go.mod h1:iPDx4oIats2T7X7Jm3PFyRCJM1GfZhJaSHOWROYOrE8=
github.com/florianl/go-nfqueue v1.3.1 h1:khQ9fYCrjbu5CF8dZF55G2RTIEIQRI0Aj5k3msJR6Gw=
github.com/florianl/go-nfqueue v1.3.1/go.mod h1:aHWbgkhryJxF5XxYvJ3oRZpdD4JP74Zu/hP1zuhja+M=
github.com/fogleman/gg v1.3.0 h1:/7zJX8F6AaYQc57WQCyN9cAIz+4bCJGO9B+dyW29am8=
github.com/fogleman/gg v1.3.0/go.mod h1:R/bRT+9gY/C5z7JzPU0zXsXHKM4/ayA+zqcVNZzPa1k=
github.com/frankban/quicktest v1.11.3/go.mod h1:wRf/ReqHper53s+kmmSZizM8NamnL3IM0I9ntUbOk+k=
github.com/frankban/quicktest v1.14.5 h1:dfYrrRyLtiqT9GyKXgdh+k4inNeTvmGbuSgZ3lx3GhA=
github.com/frankban/quicktest v1.14.5/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
@ -70,6 +74,8 @@ github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/gofrs/uuid v4.4.0+incompatible h1:3qXRTX8/NbyulANqlc0lchS1gqAVxRgsuW1YrTJupqA=
github.com/gofrs/uuid v4.4.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
github.com/golang/gddo v0.0.0-20180823221919-9d8ff1c67be5/go.mod h1:xEhNfoBDX1hzLm2Nf80qUvZ2sVwoMZ8d6IE2SrsQfh4=
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f h1:16RtHeWGkJMc80Etb8RPCcKevXGldr57+LOyZt8zOlg=
github.com/golang/gddo v0.0.0-20210115222349-20d68f94ee1f/go.mod h1:ijRvpgDJDI262hYq/IQVYgf8hd8IHUs93Ol0kvMBAx4=
@ -98,8 +104,8 @@ github.com/google/gopacket v1.1.19 h1:ves8RnFZPGiFnTS0uPQStjwru6uO6h+nlr9j6fL7kF
github.com/google/gopacket v1.1.19/go.mod h1:iJ8V8n6KS+z2U1A8pUwu8bW5SyEMkXJB8Yo/Vo+TKTo=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4=
github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ=
github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4=
github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU=
github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/googleapis/gax-go v2.0.0+incompatible/go.mod h1:SFVmujtThgffbyetf+mdk2eWhX2bMyUtNHzFKcPA9HY=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8=
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
@ -145,6 +151,8 @@ github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/magiconair/properties v1.7.4-0.20170902060319-8d7837e64d3c/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mat/besticon v3.12.0+incompatible h1:1KTD6wisfjfnX+fk9Kx/6VEZL+MAW1LhCkL9Q47H9Bg=
github.com/mat/besticon v3.12.0+incompatible/go.mod h1:mA1auQYHt6CW5e7L9HJLmqVQC8SzNk2gVwouO0AbiEU=
github.com/mattn/go-colorable v0.0.10-0.20170816031813-ad5389df28cd/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.2/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
@ -183,6 +191,8 @@ github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zx
github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw=
github.com/mr-tron/base58 v1.2.0 h1:T/HDJBh4ZCPbU39/+c3rRvE0uKBQlU27+QI8LJ4t64o=
github.com/mr-tron/base58 v1.2.0/go.mod h1:BinMc/sQntlIE1frQmRFPUoPA1Zkr8VRgBdjWI2mNwc=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ=
github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8=
github.com/oschwald/maxminddb-golang v1.12.0 h1:9FnTOD0YOhP7DGxGsq4glzpGy5+w7pq50AS6wALUMYs=
github.com/oschwald/maxminddb-golang v1.12.0/go.mod h1:q0Nob5lTCqyQ8WT6FYgS1L7PXKVVbgiymefNwIjPzgY=
github.com/pelletier/go-toml v1.0.1-0.20170904195809-1d6b12b7cb29/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic=
@ -201,6 +211,8 @@ github.com/safing/jess v0.3.2 h1:d21+sautJlfTKcwgm6/TohUf0F8DL27Lug2XHBOBqvY=
github.com/safing/jess v0.3.2/go.mod h1:N1Qiw9YBDHSVilocPEWHoGIEi+DvtmGds76rUhAyctU=
github.com/safing/portbase v0.18.6 h1:uMZOG4C0K61QJE7I4fI+55r1I/eV42TJdI1xA02U1yo=
github.com/safing/portbase v0.18.6/go.mod h1:qhhLjrr5iEGU9r7RZ6hJdtulOeycJ0d0jq95ZxGJ9Hs=
github.com/safing/portbase v0.18.7-0.20231218160927-7631b9d28afe h1:JphzTgtBrWwdS4i8g8TJByv8fV1zDvsdh0irf5bx2uQ=
github.com/safing/portbase v0.18.7-0.20231218160927-7631b9d28afe/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.4 h1:wAE17yWOgL/lEwluRXRj1gM08bhyg1f7GQHKuP3aNSw=
@ -250,10 +262,10 @@ github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
github.com/tidwall/sjson v1.2.5 h1:kLy8mja+1c9jlljvWTlSazM7cKDRfJuR/bOJhcY5NcY=
github.com/tidwall/sjson v1.2.5/go.mod h1:Fvgq9kS/6ociJEDnK0Fk1cpYF4FIW6ZF7LAe+6jwd28=
github.com/tklauser/go-sysconf v0.3.12 h1:0QaGUFOdQaIVdPgfITYzaTegZvdCjmYO52cSFAEVmqU=
github.com/tklauser/go-sysconf v0.3.12/go.mod h1:Ho14jnntGE1fpdOqQEEaiKRpvIavV0hSfmBq8nJbHYI=
github.com/tklauser/numcpus v0.6.1 h1:ng9scYS7az0Bk4OZLvrNXNSAO2Pxr1XXRAPyjhIx+Fk=
github.com/tklauser/numcpus v0.6.1/go.mod h1:1XfjsgE2zo8GVw7POkMbHENHzVg3GzmoZ9fESEdAacY=
github.com/tklauser/go-sysconf v0.3.13 h1:GBUpcahXSpR2xN01jhkNAbTLRk2Yzgggk8IM08lq3r4=
github.com/tklauser/go-sysconf v0.3.13/go.mod h1:zwleP4Q4OehZHGn4CYZDipCgg9usW5IJePewFCGVEa0=
github.com/tklauser/numcpus v0.7.0 h1:yjuerZP127QG9m5Zh/mSO4wqurYil27tHrqwRoRjpr4=
github.com/tklauser/numcpus v0.7.0/go.mod h1:bb6dMVcj8A42tSE7i32fsIUCbQNllK5iDguyOZRUzAY=
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26 h1:UFHFmFfixpmfRBcxuu+LA9l8MdURWVdVNUHxO5n1d2w=
github.com/umahmood/haversine v0.0.0-20151105152445-808ab04add26/go.mod h1:IGhd0qMDsUa9acVjsbsT7bu3ktadtGOHI79+idTew/M=
github.com/valyala/fastrand v1.1.0 h1:f+5HkLW4rsgzdNoleUOB69hyT9IlD2ZQh9GyDMfb5G8=
@ -282,8 +294,10 @@ golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8U
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.16.0 h1:mMMrFzRSCF0GvB7Ne27XVtVAaXLrPmgPC7/v0tkwHaY=
golang.org/x/crypto v0.16.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4=
golang.org/x/exp v0.0.0-20231127185646-65229373498e h1:Gvh4YaCaXNs6dKTlfgismwWZKyjVZXwOPfIyUaqU3No=
golang.org/x/exp v0.0.0-20231127185646-65229373498e/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611 h1:qCEDpW1G+vcj3Y7Fy52pEM1AWm3abj8WimGYejI3SC4=
golang.org/x/exp v0.0.0-20231214170342-aacd6d4b4611/go.mod h1:iRJReGqOEeBhDZGkGbynYwcHlctCvnjTYIamk7uXpHI=
golang.org/x/image v0.14.0 h1:tNgSxAFe3jC4uYqvZdTr84SZoM1KfwdC9SKIFrLjFn4=
golang.org/x/image v0.14.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE=
golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY=
golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg=
golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
@ -353,8 +367,6 @@ golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.15.0 h1:h48lPFYpsTvQJZF4EKyI4aLHaev3CxivZmv7yZig9pc=
golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
@ -375,8 +387,8 @@ golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtn
golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28=
golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0=
golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo=
golang.org/x/tools v0.16.0 h1:GO788SKMRunPIBCXiQyo2AaexLstOrVhuAL5YwsckQM=
golang.org/x/tools v0.16.0/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/tools v0.16.1 h1:TLyB3WofjdOEepBHAU20JdNC1Zbg87elYofWYAY5oZA=
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
@ -396,17 +408,17 @@ gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gvisor.dev/gvisor v0.0.0-20231130223849-479d60c2258b h1:fXhcWD4N2isj89cdDWZ6WttbixEyEaKisztUbShdhkw=
gvisor.dev/gvisor v0.0.0-20231130223849-479d60c2258b/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
gvisor.dev/gvisor v0.0.0-20231215062520-cd4a40b584a5 h1:/tJBYxR5QOOgZxdn4Ug6ikzua1Brenl+ZEUZBjfuRJU=
gvisor.dev/gvisor v0.0.0-20231215062520-cd4a40b584a5/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk=
honnef.co/go/tools v0.2.1/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
honnef.co/go/tools v0.2.2/go.mod h1:lPVVZ2BS5TfnjLyizF7o7hv7j9/L+8cZY2hLyjP9cGY=
modernc.org/libc v1.34.11 h1:hQDcIUlSG4QAOkXCIQKkaAOV5ptXvkOx4ddbXzgW2JU=
modernc.org/libc v1.34.11/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/libc v1.37.5 h1:IDpTjKpyMYFenZ7DkOr6Jswhn79yxsXeWHi/4DaFDBA=
modernc.org/libc v1.37.5/go.mod h1:YAXkAZ8ktnkCKaN9sw/UDeUVkGYJ/YquGO4FTi5nmHE=
modernc.org/mathutil v1.6.0 h1:fRe9+AmYlaej+64JsEEhoWuAYBkOtQiMEU7n/XgfYi4=
modernc.org/mathutil v1.6.0/go.mod h1:Ui5Q9q1TR2gFm0AQRqQUaBWFLAhQpCwNcuhBOSedWPo=
modernc.org/memory v1.7.2 h1:Klh90S215mmH8c9gO98QxQFsY+W451E8AnzjoE2ee1E=
modernc.org/memory v1.7.2/go.mod h1:NO4NVCQy0N7ln+T9ngWqOQfi7ley4vpwvARR+Hjw95E=
modernc.org/sqlite v1.27.0 h1:MpKAHoyYB7xqcwnUwkuD+npwEa0fojF0B5QRbN+auJ8=
modernc.org/sqlite v1.27.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
zombiezen.com/go/sqlite v0.13.1 h1:qDzxyWWmMtSSEH5qxamqBFmqA2BLSSbtODi3ojaE02o=
zombiezen.com/go/sqlite v0.13.1/go.mod h1:Ht/5Rg3Ae2hoyh1I7gbWtWAl89CNocfqeb/aAMTkJr4=
modernc.org/sqlite v1.28.0 h1:Zx+LyDDmXczNnEQdvPuEfcFVA2ZPyaD7UCZDjef3BHQ=
modernc.org/sqlite v1.28.0/go.mod h1:Qxpazz0zH8Z1xCFyi5GSL3FzbtZ3fvbjmywNogldEW0=
zombiezen.com/go/sqlite v1.0.0 h1:D2EvOZqumJBy+6t+0uNTTXnepUpB/pKG45op/UziI1o=
zombiezen.com/go/sqlite v1.0.0/go.mod h1:Yx7FJ77tr7Ucwi5solhXAxpflyxk/BHNXArZ/JvDm60=

View file

@ -7,17 +7,11 @@ import (
"io/fs"
"os"
"strconv"
"time"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/network/socket"
)
var (
baseWaitTime = 3 * time.Millisecond
lookupRetries = 3
)
// GetPID returns the already existing pid of the given socket info or searches for it.
// This also acts as a getter for socket.Info.PID, as locking for that occurs here.
func GetPID(socketInfo socket.Info) (pid int) {
@ -43,60 +37,27 @@ func GetPID(socketInfo socket.Info) (pid int) {
func findPID(uid, inode int) (pid int) {
socketName := "socket:[" + strconv.Itoa(inode) + "]"
for i := 0; i <= lookupRetries; i++ {
var pidsUpdated bool
// Always update pid table (it has a call limiter anyway)
updatePids()
// Get all pids for the given uid.
pids, ok := getPidsByUser(uid)
if !ok {
// If we cannot find the uid in the map, update it.
updatePids()
pidsUpdated = true
pids, ok = getPidsByUser(uid)
}
// Get all pids for the given uid.
pids, ok := getPidsByUser(uid)
if !ok {
return socket.UndefinedProcessID
}
// If we have found PIDs, search them.
if ok {
// Look through the PIDs in reverse order, because higher/newer PIDs will be more likely to
// be searched for.
for i := len(pids) - 1; i >= 0; i-- {
if findSocketFromPid(pids[i], socketName) {
return pids[i]
}
}
}
// If we still cannot find our socket, and haven't yet updated the PID map,
// do this and then check again immediately.
if !pidsUpdated {
updatePids()
pids, ok = getPidsByUser(uid)
if ok {
// Look through the PIDs in reverse order, because higher/newer PIDs will be more likely to
// be searched for.
for i := len(pids) - 1; i >= 0; i-- {
if findSocketFromPid(pids[i], socketName) {
return pids[i]
}
}
}
}
// We have updated the PID map, but still cannot find anything.
// So, there is nothing we can do other than to wait a little for the kernel to
// populate the information.
// Wait after each try, except for the last iteration
if i < lookupRetries {
// Wait in back-off fashion - with 3ms baseWaitTime: 3, 6, 9 - 18ms in total.
time.Sleep(time.Duration(i+1) * baseWaitTime)
// Look through the PIDs in reverse order, because higher/newer PIDs will be more likely to
// be searched for.
for j := len(pids) - 1; j >= 0; j-- {
if pidHasSocket(pids[j], socketName) {
return pids[j]
}
}
return socket.UndefinedProcessID
}
func findSocketFromPid(pid int, socketName string) bool {
func pidHasSocket(pid int, socketName string) bool {
socketBase := "/proc/" + strconv.Itoa(pid) + "/fd"
entries := readDirNames(socketBase)
if len(entries) == 0 {

View file

@ -28,6 +28,11 @@ var (
ErrPIDNotFound = errors.New("could not find pid for socket inode")
)
const (
lookupTries = 5
fastLookupTries = 2
)
// Lookup looks for the given connection in the system state tables and returns the PID of the associated process and whether the connection is inbound.
func Lookup(pktInfo *packet.Info, fast bool) (pid int, inbound bool, err error) {
// auto-detect version

View file

@ -10,11 +10,6 @@ import (
"github.com/safing/portmaster/network/socket"
)
var (
lookupTries = 20 // With a max wait of 5ms, this amounts to up to 100ms.
fastLookupTries = 2
)
func init() {
// This increases performance on unsupported system.
// It's not critical at all and does not break anything if it fails.

View file

@ -13,16 +13,14 @@ var (
getUDP4Table = proc.GetUDP4Table
getUDP6Table = proc.GetUDP6Table
lookupTries = 20 // With a max wait of 5ms, this amounts to up to 100ms.
fastLookupTries = 2
baseWaitTime = 3 * time.Millisecond
checkPIDTries = 5
checkPIDBaseWaitTime = 5 * time.Millisecond
)
// CheckPID checks the if socket info already has a PID and if not, tries to find it.
// Depending on the OS, this might be a no-op.
func CheckPID(socketInfo socket.Info, connInbound bool) (pid int, inbound bool, err error) {
for i := 1; i <= lookupTries; i++ {
for i := 1; i <= checkPIDTries; i++ {
// look for PID
pid = proc.GetPID(socketInfo)
if pid != socket.UndefinedProcessID {
@ -31,10 +29,10 @@ func CheckPID(socketInfo socket.Info, connInbound bool) (pid int, inbound bool,
}
// every time, except for the last iteration
if i < lookupTries {
if i < checkPIDTries {
// we found no PID, we could have been too fast, give the kernel some time to think
// back off timer: with 3ms baseWaitTime: 3, 6, 9, 12, 15, 18, 21ms - 84ms in total
time.Sleep(time.Duration(i+1) * baseWaitTime)
// back off timer: with 5ms baseWaitTime: 5, 10, 15, 20, 25 - 75ms in total
time.Sleep(time.Duration(i) * checkPIDBaseWaitTime)
}
}

View file

@ -10,13 +10,6 @@ var (
getTCP6Table = iphelper.GetTCP6Table
getUDP4Table = iphelper.GetUDP4Table
getUDP6Table = iphelper.GetUDP6Table
// With a max wait of 5ms, this amounts to up to 25ms,
// excluding potential data fetching time.
// Measured on Windows: ~150ms
lookupTries = 5
fastLookupTries = 2
)
// CheckPID checks the if socket info already has a PID and if not, tries to find it.

View file

@ -8,9 +8,9 @@ import (
"strings"
"github.com/safing/portbase/log"
"github.com/safing/portbase/utils/osdetail"
"github.com/safing/portmaster/process"
"github.com/safing/portmaster/profile"
"github.com/safing/portmaster/profile/binmeta"
)
func init() {
@ -124,7 +124,7 @@ func (h *AppImageHandler) CreateProfile(p *process.Process) *profile.Profile {
if tag, ok := p.GetTag(appImagePathTagKey); ok {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: osdetail.GenerateBinaryNameFromPath(p.Path),
Name: binmeta.GenerateBinaryNameFromPath(p.Path),
PresentationPath: p.Path,
UsePresentationPath: true,
Fingerprints: []profile.Fingerprint{
@ -141,7 +141,7 @@ func (h *AppImageHandler) CreateProfile(p *process.Process) *profile.Profile {
if tag, ok := p.GetTag(appImageMountIDTagKey); ok {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: osdetail.GenerateBinaryNameFromPath(p.Path),
Name: binmeta.GenerateBinaryNameFromPath(p.Path),
PresentationPath: p.Path,
UsePresentationPath: true,
Fingerprints: []profile.Fingerprint{

View file

@ -3,9 +3,9 @@ package tags
import (
"strings"
"github.com/safing/portbase/utils/osdetail"
"github.com/safing/portmaster/process"
"github.com/safing/portmaster/profile"
"github.com/safing/portmaster/profile/binmeta"
)
func init() {
@ -69,7 +69,7 @@ func (h *flatpakHandler) CreateProfile(p *process.Process) *profile.Profile {
if tag, ok := p.GetTag(flatpakIDTagKey); ok {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: osdetail.GenerateBinaryNameFromPath(p.Path),
Name: binmeta.GenerateBinaryNameFromPath(p.Path),
PresentationPath: p.Path,
UsePresentationPath: true,
Fingerprints: []profile.Fingerprint{

View file

@ -12,9 +12,9 @@ import (
"github.com/google/shlex"
"github.com/safing/portbase/utils/osdetail"
"github.com/safing/portmaster/process"
"github.com/safing/portmaster/profile"
"github.com/safing/portmaster/profile/binmeta"
)
func init() {
@ -161,7 +161,7 @@ func (h *InterpHandler) CreateProfile(p *process.Process) *profile.Profile {
for _, ext := range it.Extensions {
scriptName, _ = strings.CutSuffix(scriptName, ext)
}
scriptName = osdetail.GenerateBinaryNameFromPath(scriptName)
scriptName = binmeta.GenerateBinaryNameFromPath(scriptName)
return profile.New(&profile.Profile{
Source: profile.SourceLocal,

View file

@ -3,9 +3,9 @@ package tags
import (
"strings"
"github.com/safing/portbase/utils/osdetail"
"github.com/safing/portmaster/process"
"github.com/safing/portmaster/profile"
"github.com/safing/portmaster/profile/binmeta"
)
func init() {
@ -117,7 +117,7 @@ func (h *SnapHandler) CreateProfile(p *process.Process) *profile.Profile {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: osdetail.GenerateBinaryNameFromPath(tag.Value),
Name: binmeta.GenerateBinaryNameFromPath(tag.Value),
PresentationPath: p.Path,
UsePresentationPath: hasVersion,
Fingerprints: []profile.Fingerprint{

View file

@ -9,7 +9,7 @@ import (
"github.com/safing/portbase/utils/osdetail"
"github.com/safing/portmaster/process"
"github.com/safing/portmaster/profile"
"github.com/safing/portmaster/profile/icons"
"github.com/safing/portmaster/profile/binmeta"
)
func init() {
@ -36,7 +36,7 @@ func (h *SVCHostTagHandler) Name() string {
// of this handler.
func (h *SVCHostTagHandler) TagDescriptions() []process.TagDescription {
return []process.TagDescription{
process.TagDescription{
{
ID: svchostTagKey,
Name: "SvcHost Service Name",
Description: "Name of a service running in svchost.exe as reported by Windows.",
@ -86,10 +86,10 @@ func (h *SVCHostTagHandler) CreateProfile(p *process.Process) *profile.Profile {
// Create new profile based on tag.
newProfile := profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: "Windows Service: " + osdetail.GenerateBinaryNameFromPath(tag.Value),
Name: "Windows Service: " + binmeta.GenerateBinaryNameFromPath(tag.Value),
UsePresentationPath: false,
Fingerprints: []profile.Fingerprint{
profile.Fingerprint{
{
Type: profile.FingerprintTypeTagID,
Key: tag.Key,
Operation: profile.FingerprintOperationEqualsID,
@ -99,9 +99,9 @@ func (h *SVCHostTagHandler) CreateProfile(p *process.Process) *profile.Profile {
})
// Load default icon for windows service.
icon, err := icons.LoadAndSaveIcon(context.TODO(), `C:\Windows\System32\@WLOGO_48x48.png`)
icon, err := binmeta.LoadAndSaveIcon(context.TODO(), `C:\Windows\System32\@WLOGO_48x48.png`)
if err == nil {
newProfile.Icons = []icons.Icon{*icon}
newProfile.Icons = []binmeta.Icon{*icon}
}
return newProfile

View file

@ -6,9 +6,9 @@ import (
"github.com/safing/portbase/log"
"github.com/safing/portbase/utils"
"github.com/safing/portbase/utils/osdetail"
"github.com/safing/portmaster/process"
"github.com/safing/portmaster/profile"
"github.com/safing/portmaster/profile/binmeta"
)
func init() {
@ -101,7 +101,7 @@ func (h *WinStoreHandler) CreateProfile(p *process.Process) *profile.Profile {
if tag, ok := p.GetTag(winStoreAppNameTagKey); ok {
return profile.New(&profile.Profile{
Source: profile.SourceLocal,
Name: osdetail.GenerateBinaryNameFromPath(tag.Value),
Name: binmeta.GenerateBinaryNameFromPath(tag.Value),
PresentationPath: p.Path,
UsePresentationPath: true,
Fingerprints: []profile.Fingerprint{

View file

@ -10,7 +10,7 @@ import (
"github.com/safing/portbase/api"
"github.com/safing/portbase/formats/dsd"
"github.com/safing/portbase/utils"
"github.com/safing/portmaster/profile/icons"
"github.com/safing/portmaster/profile/binmeta"
)
func registerAPIEndpoints() error {
@ -99,7 +99,7 @@ func handleGetProfileIcon(ar *api.Request) (data []byte, err error) {
ext := filepath.Ext(name)
// Get profile icon.
data, err = icons.GetProfileIcon(name)
data, err = binmeta.GetProfileIcon(name)
if err != nil {
return nil, err
}
@ -153,7 +153,7 @@ func handleUpdateProfileIcon(ar *api.Request) (any, error) {
}
// Update profile icon.
filename, err := icons.UpdateProfileIcon(ar.InputData, ext)
filename, err := binmeta.UpdateProfileIcon(ar.InputData, ext)
if err != nil {
return nil, err
}

View file

@ -0,0 +1,32 @@
package binmeta
import (
"bytes"
"fmt"
"image"
_ "image/png" // Register png support for image package
"github.com/fogleman/gg"
_ "github.com/mat/besticon/ico" // Register ico support for image package
)
// ConvertICOtoPNG converts a an .ico to a .png image.
func ConvertICOtoPNG(ico []byte) (png []byte, err error) {
// Decode the ICO.
icon, _, err := image.Decode(bytes.NewReader(ico))
if err != nil {
return nil, fmt.Errorf("failed to decode ICO: %w", err)
}
// Convert to raw image.
img := gg.NewContextForImage(icon)
// Convert to PNG.
imgBuf := &bytes.Buffer{}
err = img.EncodePNG(imgBuf)
if err != nil {
return nil, fmt.Errorf("failed to encode PNG: %w", err)
}
return imgBuf.Bytes(), nil
}

View file

@ -0,0 +1,10 @@
//go:build !linux && !windows
package binmeta
import "context"
// GetIconAndName returns zero values for unsupported platforms.
func GetIconAndName(ctx context.Context, binPath string, homeDir string) (icon *Icon, name string, err error) {
return nil, "", nil
}

View file

@ -1,4 +1,4 @@
package icons
package binmeta
import (
"context"
@ -9,36 +9,46 @@ import (
"strings"
)
// FindIcon finds an icon for the given binary name.
// Providing the home directory of the user running the process of that binary can help find an icon.
func FindIcon(ctx context.Context, binName string, homeDir string) (*Icon, error) {
// GetIconAndName returns an icon and name of the given binary path.
// Providing the home directory of the user running the process of that binary can improve results.
// Even if an error is returned, the other return values are valid, if set.
func GetIconAndName(ctx context.Context, binPath string, homeDir string) (icon *Icon, name string, err error) {
// Derive name from binary.
name = GenerateBinaryNameFromPath(binPath)
// Search for icon.
iconPath, err := search(binName, homeDir)
iconPath, err := searchForIcon(binPath, homeDir)
if iconPath == "" {
if err != nil {
return nil, fmt.Errorf("failed to find icon for %s: %w", binName, err)
return nil, name, fmt.Errorf("failed to find icon for %s: %w", binPath, err)
}
return nil, nil
return nil, name, nil
}
return LoadAndSaveIcon(ctx, iconPath)
// Save icon to internal storage.
icon, err = LoadAndSaveIcon(ctx, iconPath)
if err != nil {
return nil, name, fmt.Errorf("failed to store icon for %s: %w", binPath, err)
}
return icon, name, nil
}
func search(binName string, homeDir string) (iconPath string, err error) {
binName = strings.ToLower(binName)
func searchForIcon(binPath string, homeDir string) (iconPath string, err error) {
binPath = strings.ToLower(binPath)
// Search for icon path.
for _, iconLoc := range iconLocations {
basePath := iconLoc.GetPath(binName, homeDir)
basePath := iconLoc.GetPath(binPath, homeDir)
if basePath == "" {
continue
}
switch iconLoc.Type {
case FlatDir:
iconPath, err = searchDirectory(basePath, binName)
iconPath, err = searchDirectory(basePath, binPath)
case XDGIcons:
iconPath, err = searchXDGIconStructure(basePath, binName)
iconPath, err = searchXDGIconStructure(basePath, binPath)
}
if iconPath != "" {
@ -48,10 +58,10 @@ func search(binName string, homeDir string) (iconPath string, err error) {
return
}
func searchXDGIconStructure(baseDirectory string, binName string) (iconPath string, err error) {
func searchXDGIconStructure(baseDirectory string, binPath string) (iconPath string, err error) {
for _, xdgIconDir := range xdgIconPaths {
directory := filepath.Join(baseDirectory, xdgIconDir)
iconPath, err = searchDirectory(directory, binName)
iconPath, err = searchDirectory(directory, binPath)
if iconPath != "" {
return
}
@ -59,7 +69,7 @@ func searchXDGIconStructure(baseDirectory string, binName string) (iconPath stri
return
}
func searchDirectory(directory string, binName string) (iconPath string, err error) {
func searchDirectory(directory string, binPath string) (iconPath string, err error) {
entries, err := os.ReadDir(directory)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
@ -82,13 +92,13 @@ func searchDirectory(directory string, binName string) (iconPath string, err err
iconName := strings.ToLower(entry.Name())
iconName = strings.TrimSuffix(iconName, filepath.Ext(iconName))
switch {
case len(iconName) < len(binName):
case len(iconName) < len(binPath):
// Continue to next.
case iconName == binName:
case iconName == binPath:
// Exact match, return immediately.
return filepath.Join(directory, entry.Name()), nil
case strings.HasPrefix(iconName, binName):
excessChars := len(iconName) - len(binName)
case strings.HasPrefix(iconName, binPath):
excessChars := len(iconName) - len(binPath)
if bestMatch == "" || excessChars < bestMatchExcessChars {
bestMatch = entry.Name()
bestMatchExcessChars = excessChars

View file

@ -1,4 +1,4 @@
package icons
package binmeta
import (
"os"
@ -19,7 +19,7 @@ func TestFindIcon(t *testing.T) {
func testFindIcon(t *testing.T, binName string, homeDir string) {
t.Helper()
iconPath, err := search(binName, homeDir)
iconPath, err := searchForIcon(binName, homeDir)
if err != nil {
t.Error(err)
return

View file

@ -0,0 +1,115 @@
package binmeta
import (
"bytes"
"context"
"errors"
"fmt"
"os"
"github.com/tc-hib/winres"
"github.com/tc-hib/winres/version"
)
// GetIconAndName returns an icon and name of the given binary path.
// Providing the home directory of the user running the process of that binary can improve results.
// Even if an error is returned, the other return values are valid, if set.
func GetIconAndName(ctx context.Context, binPath string, homeDir string) (icon *Icon, name string, err error) {
// Get name and png from exe.
png, name, err := getIconAndNamefromRSS(ctx, binPath)
// Fall back to name generation if name is not set.
if name == "" {
name = GenerateBinaryNameFromPath(binPath)
}
// Handle previous error.
if err != nil {
return nil, name, err
}
// Update profile icon and return icon object.
filename, err := UpdateProfileIcon(png, "png")
if err != nil {
return nil, name, fmt.Errorf("failed to store icon: %w", err)
}
return &Icon{
Type: IconTypeAPI,
Value: filename,
}, name, nil
}
func getIconAndNamefromRSS(ctx context.Context, binPath string) (png []byte, name string, err error) {
// Open .exe file.
exeFile, err := os.Open(binPath)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
return nil, "", nil
}
return nil, "", fmt.Errorf("failed to open exe %s to get icon: %w", binPath, err)
}
defer exeFile.Close() //nolint:errcheck
// Load .exe resources.
rss, err := winres.LoadFromEXE(exeFile)
if err != nil {
return nil, "", fmt.Errorf("failed to get rss: %w", err)
}
// DEBUG: Print all available resources:
// rss.Walk(func(typeID, resID winres.Identifier, langID uint16, data []byte) bool {
// fmt.Printf("typeID=%d resID=%d langID=%d\n", typeID, resID, langID)
// return true
// })
// Get first icon.
var (
icon *winres.Icon
iconErr error
)
rss.WalkType(winres.RT_GROUP_ICON, func(resID winres.Identifier, langID uint16, _ []byte) bool {
icon, iconErr = rss.GetIconTranslation(resID, langID)
return iconErr != nil
})
if iconErr != nil {
return nil, "", fmt.Errorf("failed to get icon: %w", err)
}
// Convert icon.
icoBuf := &bytes.Buffer{}
err = icon.SaveICO(icoBuf)
if err != nil {
return nil, "", fmt.Errorf("failed to save ico: %w", err)
}
png, err = ConvertICOtoPNG(icoBuf.Bytes())
if err != nil {
return nil, "", fmt.Errorf("failed to convert ico to png: %w", err)
}
// Get name from version record.
var (
versionInfo *version.Info
versionInfoErr error
)
rss.WalkType(winres.RT_VERSION, func(resID winres.Identifier, langID uint16, data []byte) bool {
versionInfo, versionInfoErr = version.FromBytes(data)
switch {
case versionInfoErr != nil:
return true
case versionInfo == nil:
return true
}
// Get metadata table and main language.
table := versionInfo.Table().GetMainTranslation()
if table == nil {
return true
}
name = table[version.ProductName]
return name == ""
})
name = cleanFileDescription(name)
return png, name, nil
}

View file

@ -0,0 +1,27 @@
package binmeta
import (
"context"
"os"
"testing"
)
func TestFindIcon(t *testing.T) {
if testing.Short() {
t.Skip("test meant for compiling and running on desktop")
}
t.Parallel()
binName := os.Args[len(os.Args)-1]
t.Logf("getting name and icon for %s", binName)
png, name, err := getIconAndNamefromRSS(context.Background(), binName)
if err != nil {
t.Fatal(err)
}
t.Logf("name: %s", name)
err = os.WriteFile("icon.png", png, 0o0600)
if err != nil {
t.Fatal(err)
}
}

View file

@ -1,4 +1,4 @@
package icons
package binmeta
import (
"errors"
@ -42,8 +42,8 @@ func (t IconType) sortOrder() int {
}
}
// SortAndCompact sorts and compacts a list of icons.
func SortAndCompact(icons []Icon) []Icon {
// SortAndCompactIcons sorts and compacts a list of icons.
func SortAndCompactIcons(icons []Icon) []Icon {
// Sort.
slices.SortFunc[[]Icon, Icon](icons, func(a, b Icon) int {
aOrder := a.Type.sortOrder()

View file

@ -1,4 +1,4 @@
package icons
package binmeta
import (
"context"

View file

@ -1,4 +1,4 @@
package icons
package binmeta
import (
"fmt"

121
profile/binmeta/name.go Normal file
View file

@ -0,0 +1,121 @@
package binmeta
import (
"path/filepath"
"regexp"
"strings"
)
var (
segmentsSplitter = regexp.MustCompile("[^A-Za-z0-9]*[A-Z]?[a-z0-9]*")
nameOnly = regexp.MustCompile("^[A-Za-z0-9]+$")
delimitersAtStart = regexp.MustCompile("^[^A-Za-z0-9]+")
delimitersOnly = regexp.MustCompile("^[^A-Za-z0-9]+$")
removeQuotes = strings.NewReplacer(`"`, ``, `'`, ``)
)
// GenerateBinaryNameFromPath generates a more human readable binary name from
// the given path. This function is used as fallback in the GetBinaryName
// functions.
func GenerateBinaryNameFromPath(path string) string {
// Get file name from path.
_, fileName := filepath.Split(path)
// Split up into segments.
segments := segmentsSplitter.FindAllString(fileName, -1)
// Remove last segment if it's an extension.
if len(segments) >= 2 {
switch strings.ToLower(segments[len(segments)-1]) {
case
".exe", // Windows Executable
".msi", // Windows Installer
".bat", // Windows Batch File
".cmd", // Windows Command Script
".ps1", // Windows Powershell Cmdlet
".run", // Linux Executable
".appimage", // Linux AppImage
".app", // MacOS Executable
".action", // MacOS Automator Action
".out": // Generic Compiled Executable
segments = segments[:len(segments)-1]
}
}
// Debugging snippet:
// fmt.Printf("segments: %s\n", segments)
// Go through segments and collect name parts.
nameParts := make([]string, 0, len(segments))
var fragments string
for _, segment := range segments {
// Group very short segments.
if len(delimitersAtStart.ReplaceAllString(segment, "")) <= 2 {
fragments += segment
continue
} else if fragments != "" {
nameParts = append(nameParts, fragments)
fragments = ""
}
// Add segment to name.
nameParts = append(nameParts, segment)
}
// Add last fragment.
if fragments != "" {
nameParts = append(nameParts, fragments)
}
// Debugging snippet:
// fmt.Printf("parts: %s\n", nameParts)
// Post-process name parts
for i := range nameParts {
// Remove any leading delimiters.
nameParts[i] = delimitersAtStart.ReplaceAllString(nameParts[i], "")
// Title-case name-only parts.
if nameOnly.MatchString(nameParts[i]) {
nameParts[i] = strings.Title(nameParts[i]) //nolint:staticcheck
}
}
// Debugging snippet:
// fmt.Printf("final: %s\n", nameParts)
return strings.Join(nameParts, " ")
}
func cleanFileDescription(fileDescr string) string {
fields := strings.Fields(fileDescr)
// Clean out and `"` and `'`.
for i := range fields {
fields[i] = removeQuotes.Replace(fields[i])
}
// If there is a 1 or 2 character delimiter field, only use fields before it.
endIndex := len(fields)
for i, field := range fields {
// Ignore the first field as well as fields with more than two characters.
if i >= 1 && len(field) <= 2 && !nameOnly.MatchString(field) {
endIndex = i
break
}
}
// Concatenate name
binName := strings.Join(fields[:endIndex], " ")
// If there are multiple sentences, only use the first.
if strings.Contains(binName, ". ") {
binName = strings.SplitN(binName, ". ", 2)[0]
}
// If does not have any characters or numbers, return an empty string.
if delimitersOnly.MatchString(binName) {
return ""
}
return strings.TrimSpace(binName)
}

View file

@ -0,0 +1,48 @@
package binmeta
import (
"testing"
"github.com/stretchr/testify/assert"
)
func TestGenerateBinaryNameFromPath(t *testing.T) {
t.Parallel()
assert.Equal(t, "Nslookup", GenerateBinaryNameFromPath("nslookup.exe"))
assert.Equal(t, "System Settings", GenerateBinaryNameFromPath("SystemSettings.exe"))
assert.Equal(t, "One Drive Setup", GenerateBinaryNameFromPath("OneDriveSetup.exe"))
assert.Equal(t, "Msedge", GenerateBinaryNameFromPath("msedge.exe"))
assert.Equal(t, "SIH Client", GenerateBinaryNameFromPath("SIHClient.exe"))
assert.Equal(t, "Openvpn Gui", GenerateBinaryNameFromPath("openvpn-gui.exe"))
assert.Equal(t, "Portmaster Core v0-1-2", GenerateBinaryNameFromPath("portmaster-core_v0-1-2.exe"))
assert.Equal(t, "Win Store App", GenerateBinaryNameFromPath("WinStore.App.exe"))
assert.Equal(t, "Test Script", GenerateBinaryNameFromPath(".test-script"))
assert.Equal(t, "Browser Broker", GenerateBinaryNameFromPath("browser_broker.exe"))
assert.Equal(t, "Virtual Box VM", GenerateBinaryNameFromPath("VirtualBoxVM"))
assert.Equal(t, "Io Elementary Appcenter", GenerateBinaryNameFromPath("io.elementary.appcenter"))
assert.Equal(t, "Microsoft Windows Store", GenerateBinaryNameFromPath("Microsoft.WindowsStore"))
}
func TestCleanFileDescription(t *testing.T) {
t.Parallel()
assert.Equal(t, "Product Name", cleanFileDescription("Product Name"))
assert.Equal(t, "Product Name", cleanFileDescription("Product Name. Does this and that."))
assert.Equal(t, "Product Name", cleanFileDescription("Product Name - Does this and that."))
assert.Equal(t, "Product Name", cleanFileDescription("Product Name / Does this and that."))
assert.Equal(t, "Product Name", cleanFileDescription("Product Name :: Does this and that."))
assert.Equal(t, "/ Product Name", cleanFileDescription("/ Product Name"))
assert.Equal(t, "Product", cleanFileDescription("Product / Name"))
assert.Equal(t, "Software 2", cleanFileDescription("Software 2"))
assert.Equal(t, "Launcher for Software 2", cleanFileDescription("Launcher for 'Software 2'"))
assert.Equal(t, "", cleanFileDescription(". / Name"))
assert.Equal(t, "", cleanFileDescription(". "))
assert.Equal(t, "", cleanFileDescription("."))
assert.Equal(t, "N/A", cleanFileDescription("N/A"))
assert.Equal(t,
"Product Name a Does this and that.",
cleanFileDescription("Product Name a Does this and that."),
)
}

View file

@ -5,11 +5,12 @@ import (
"fmt"
"regexp"
"strconv"
"strings"
"github.com/safing/portmaster/intel"
)
var asnRegex = regexp.MustCompile("^(AS)?[0-9]+$")
var asnRegex = regexp.MustCompile("^AS[0-9]+$")
// EndpointASN matches ASNs.
type EndpointASN struct {
@ -48,9 +49,10 @@ func (ep *EndpointASN) String() string {
func parseTypeASN(fields []string) (Endpoint, error) {
if asnRegex.MatchString(fields[1]) {
asn, err := strconv.ParseUint(fields[1][2:], 10, 64)
asnString := strings.TrimPrefix(fields[1], "AS")
asn, err := strconv.ParseUint(asnString, 10, 64)
if err != nil {
return nil, fmt.Errorf("failed to parse AS number %s", fields[1])
return nil, fmt.Errorf("failed to parse AS number %s", asnString)
}
ep := &EndpointASN{

View file

@ -107,6 +107,12 @@ func parseTypeDomain(fields []string) (Endpoint, error) {
domain += "."
}
// Check if this looks like an IP address.
// At least the TLDs has characters.
if looksLikeAnIP.MatchString(domain) {
return nil, nil
}
// Fix domain case.
domain = strings.ToLower(domain)
needValidFQDN := true

View file

@ -59,6 +59,9 @@ func TestEndpointParsing(t *testing.T) {
testParsing(t, "+ * UDP/1234")
testParsing(t, "+ * TCP/HTTP")
testParsing(t, "+ * TCP/80-443")
// TODO: Test fails:
// testParsing(t, "+ 1234")
}
func testParsing(t *testing.T, value string) {
@ -69,6 +72,7 @@ func testParsing(t *testing.T, value string) {
t.Error(err)
return
}
// t.Logf("%T: %+v", ep, ep)
if value != ep.String() {
t.Errorf(`stringified endpoint mismatch: original was "%s", parsed is "%s"`, value, ep.String())
}

View file

@ -1,10 +0,0 @@
//go:build !linux
package icons
import "context"
// FindIcon returns nil, nil for unsupported platforms.
func FindIcon(ctx context.Context, binName string, homeDir string) (*Icon, error) {
return nil, nil
}

View file

@ -7,7 +7,7 @@ import (
"time"
"github.com/safing/portbase/database/record"
"github.com/safing/portmaster/profile/icons"
"github.com/safing/portmaster/profile/binmeta"
)
// MergeProfiles merges multiple profiles into a new one.
@ -30,7 +30,6 @@ func MergeProfiles(name string, primary *Profile, secondaries ...*Profile) (newP
Description: primary.Description,
Homepage: primary.Homepage,
UsePresentationPath: false, // Disable presentation path.
SecurityLevel: primary.SecurityLevel,
Config: primary.Config,
Created: nowUnix,
}
@ -53,12 +52,12 @@ func MergeProfiles(name string, primary *Profile, secondaries ...*Profile) (newP
}
// Collect all icons.
newProfile.Icons = make([]icons.Icon, 0, len(secondaries)+1) // Guess the needed space.
newProfile.Icons = make([]binmeta.Icon, 0, len(secondaries)+1) // Guess the needed space.
newProfile.Icons = append(newProfile.Icons, primary.Icons...)
for _, sp := range secondaries {
newProfile.Icons = append(newProfile.Icons, sp.Icons...)
}
newProfile.Icons = icons.SortAndCompact(newProfile.Icons)
newProfile.Icons = binmeta.SortAndCompactIcons(newProfile.Icons)
// Collect all fingerprints.
newProfile.Fingerprints = make([]Fingerprint, 0, len(primary.Fingerprints)+len(secondaries)) // Guess the needed space.

View file

@ -11,7 +11,7 @@ import (
"github.com/safing/portbase/database/migration"
"github.com/safing/portbase/database/query"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/profile/icons"
"github.com/safing/portmaster/profile/binmeta"
)
func registerMigrations() error {
@ -103,7 +103,7 @@ func migrateIcons(ctx context.Context, _, to *version.Version, db *database.Inte
}
// Migrate to icon list.
profile.Icons = []icons.Icon{{
profile.Icons = []binmeta.Icon{{
Type: profile.IconType,
Value: profile.Icon,
}}

View file

@ -11,7 +11,7 @@ import (
"github.com/safing/portbase/log"
"github.com/safing/portbase/modules"
_ "github.com/safing/portmaster/core/base"
"github.com/safing/portmaster/profile/icons"
"github.com/safing/portmaster/profile/binmeta"
"github.com/safing/portmaster/updates"
)
@ -53,7 +53,7 @@ func prep() error {
if err := iconsDir.Ensure(); err != nil {
return fmt.Errorf("failed to create/check icons directory: %w", err)
}
icons.ProfileIconStoragePath = iconsDir.Path
binmeta.ProfileIconStoragePath = iconsDir.Path
return nil
}

View file

@ -135,8 +135,6 @@ func NewLayeredProfile(localProfile *Profile) *LayeredProfile {
// TODO: Load additional profiles.
lp.updateCaches()
lp.CreateMeta()
lp.SetKey(runtime.DefaultRegistry.DatabaseName() + ":" + revisionProviderPrefix + localProfile.ScopedID())
@ -292,9 +290,6 @@ func (lp *LayeredProfile) Update(md MatchingData, createProfileCallback func() *
// get global config validity flag
lp.globalValidityFlag.Refresh()
// update cached data fields
lp.updateCaches()
// bump revision counter
lp.increaseRevisionCounter(false)
}
@ -302,17 +297,6 @@ func (lp *LayeredProfile) Update(md MatchingData, createProfileCallback func() *
return lp.RevisionCounter
}
func (lp *LayeredProfile) updateCaches() {
// update security level
var newLevel uint8
for _, layer := range lp.layers {
if newLevel < layer.SecurityLevel {
newLevel = layer.SecurityLevel
}
}
atomic.StoreUint32(lp.securityLevel, uint32(newLevel))
}
// SecurityLevel returns the highest security level of all layered profiles. This function is atomic and does not require any locking.
func (lp *LayeredProfile) SecurityLevel() uint8 {
return uint8(atomic.LoadUint32(lp.securityLevel))

View file

@ -15,10 +15,9 @@ import (
"github.com/safing/portbase/database/record"
"github.com/safing/portbase/log"
"github.com/safing/portbase/utils"
"github.com/safing/portbase/utils/osdetail"
"github.com/safing/portmaster/intel/filterlists"
"github.com/safing/portmaster/profile/binmeta"
"github.com/safing/portmaster/profile/endpoints"
"github.com/safing/portmaster/profile/icons"
)
// ProfileSource is the source of the profile.
@ -69,9 +68,9 @@ type Profile struct { //nolint:maligned // not worth the effort
// See IconType for more information.
Icon string
// Deprecated: IconType describes the type of the Icon property.
IconType icons.IconType
IconType binmeta.IconType
// Icons holds a list of icons to represent the application.
Icons []icons.Icon
Icons []binmeta.Icon
// Deprecated: LinkedPath used to point to the executableis this
// profile was created for.
@ -88,12 +87,6 @@ type Profile struct { //nolint:maligned // not worth the effort
UsePresentationPath bool
// Fingerprints holds process matching information.
Fingerprints []Fingerprint
// SecurityLevel is the mininum security level to apply to
// connections made with this profile.
// Note(ppacher): we may deprecate this one as it can easily
// be "simulated" by adjusting the settings
// directly.
SecurityLevel uint8
// Config holds profile specific setttings. It's a nested
// object with keys defining the settings database path. All keys
// until the actual settings value (which is everything that is not
@ -476,7 +469,7 @@ func (profile *Profile) updateMetadata(binaryPath string) (changed bool) {
// Set Name if unset.
if profile.Name == "" && profile.PresentationPath != "" {
// Generate a default profile name from path.
profile.Name = osdetail.GenerateBinaryNameFromPath(profile.PresentationPath)
profile.Name = binmeta.GenerateBinaryNameFromPath(profile.PresentationPath)
changed = true
}
@ -514,38 +507,16 @@ func (profile *Profile) updateMetadataFromSystem(ctx context.Context, md Matchin
return fmt.Errorf("tried to update metadata for non-local or non-path profile %s", profile.ScopedID())
}
// Get binary name from PresentationPath.
newName, err := osdetail.GetBinaryNameFromSystem(profile.PresentationPath)
// Get home from ENV.
var home string
if env := md.Env(); env != nil {
home = env["HOME"]
}
// Get binary icon and name.
newIcon, newName, err := binmeta.GetIconAndName(ctx, profile.PresentationPath, home)
if err != nil {
switch {
case errors.Is(err, osdetail.ErrNotSupported):
case errors.Is(err, osdetail.ErrNotFound):
case errors.Is(err, osdetail.ErrEmptyOutput):
default:
log.Warningf("profile: error while getting binary name for %s: %s", profile.PresentationPath, err)
}
return nil
}
// Check if the new name is valid.
if strings.TrimSpace(newName) == "" {
return nil
}
// Get icon if path matches presentation path.
var newIcon *icons.Icon
if profile.PresentationPath == md.Path() {
// Get home from ENV.
var home string
if env := md.Env(); env != nil {
home = env["HOME"]
}
var err error
newIcon, err = icons.FindIcon(ctx, profile.PresentationPath, home)
if err != nil {
log.Warningf("profile: failed to find icon for %s: %s", profile.PresentationPath, err)
newIcon = nil
}
log.Warningf("profile: failed to get binary icon/name for %s: %s", profile.PresentationPath, err)
}
// Apply new data to profile.
@ -555,7 +526,7 @@ func (profile *Profile) updateMetadataFromSystem(ctx context.Context, md Matchin
defer profile.Unlock()
// Apply new name if it changed.
if profile.Name != newName {
if newName != "" && profile.Name != newName {
profile.Name = newName
changed = true
}
@ -563,10 +534,10 @@ func (profile *Profile) updateMetadataFromSystem(ctx context.Context, md Matchin
// Apply new icon if found.
if newIcon != nil {
if len(profile.Icons) == 0 {
profile.Icons = []icons.Icon{*newIcon}
profile.Icons = []binmeta.Icon{*newIcon}
} else {
profile.Icons = append(profile.Icons, *newIcon)
profile.Icons = icons.SortAndCompact(profile.Icons)
profile.Icons = binmeta.SortAndCompactIcons(profile.Icons)
}
}
}()

View file

@ -14,7 +14,7 @@ import (
"github.com/safing/portbase/config"
"github.com/safing/portbase/log"
"github.com/safing/portmaster/profile"
"github.com/safing/portmaster/profile/icons"
"github.com/safing/portmaster/profile/binmeta"
)
// ProfileExport holds an export of a profile.
@ -415,12 +415,12 @@ func ImportProfile(r *ProfileImportRequest, requiredProfileSource profile.Profil
if err != nil {
return nil, fmt.Errorf("%w: icon data is invalid: %w", ErrImportFailed, err)
}
filename, err := icons.UpdateProfileIcon(du.Data, du.MediaType.Subtype)
filename, err := binmeta.UpdateProfileIcon(du.Data, du.MediaType.Subtype)
if err != nil {
return nil, fmt.Errorf("%w: icon is invalid: %w", ErrImportFailed, err)
}
p.Icons = []icons.Icon{{
Type: icons.IconTypeAPI,
p.Icons = []binmeta.Icon{{
Type: binmeta.IconTypeAPI,
Value: filename,
}}
}

View file

@ -254,6 +254,12 @@ func ImportSettings(r *SettingsImportRequest) (*ImportResult, error) {
)
}
// Save new config to disk.
err := config.SaveConfig()
if err != nil {
return nil, fmt.Errorf("failed to save config: %w", err)
}
result.RestartRequired = restartRequired
return result, nil
}