Merge branch 'main' into 2fa

This commit is contained in:
tobi 2025-04-07 12:57:02 +02:00
commit c6c212fb81
271 changed files with 7226 additions and 11019 deletions

1
SECURITY.md Normal file
View file

@ -0,0 +1 @@
Please email security issues to: admin@gotosocial.org

View file

@ -113,6 +113,27 @@ nothanks.com,suspend,false,false,,false
JSON lists use content type `application/json`. JSON lists use content type `application/json`.
```json
[
{
"domain": "bumfaces.net",
"suspended_at": "2020-05-13T13:29:12.000Z",
"comment": "big jerks"
},
{
"domain": "peepee.poopoo",
"suspended_at": "2020-05-13T13:29:12.000Z",
"comment": "harassment"
},
{
"domain": "nothanks.com",
"suspended_at": "2020-05-13T13:29:12.000Z"
}
]
```
As an alternative to `"comment"`, `"public_comment"` will also work:
```json ```json
[ [
{ {

View file

@ -1099,13 +1099,22 @@ definitions:
domain: domain:
description: Domain represents a remote domain description: Domain represents a remote domain
properties: properties:
comment:
description: |-
If the domain is blocked, what's the publicly-stated reason for the block.
Alternative to `public_comment` to be used when serializing/deserializing via /api/v1/instance.
example: they smell
type: string
x-go-name: Comment
domain: domain:
description: The hostname of the domain. description: The hostname of the domain.
example: example.org example: example.org
type: string type: string
x-go-name: Domain x-go-name: Domain
public_comment: public_comment:
description: If the domain is blocked, what's the publicly-stated reason for the block. description: |-
If the domain is blocked, what's the publicly-stated reason for the block.
Alternative to `comment` to be used when serializing/deserializing NOT via /api/v1/instance.
example: they smell example: they smell
type: string type: string
x-go-name: PublicComment x-go-name: PublicComment
@ -1124,6 +1133,13 @@ definitions:
x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model x-go-package: github.com/superseriousbusiness/gotosocial/internal/api/model
domainPermission: domainPermission:
properties: properties:
comment:
description: |-
If the domain is blocked, what's the publicly-stated reason for the block.
Alternative to `public_comment` to be used when serializing/deserializing via /api/v1/instance.
example: they smell
type: string
x-go-name: Comment
created_at: created_at:
description: Time at which the permission entry was created (ISO 8601 Datetime). description: Time at which the permission entry was created (ISO 8601 Datetime).
example: "2021-07-30T09:20:25+00:00" example: "2021-07-30T09:20:25+00:00"
@ -1162,7 +1178,9 @@ definitions:
type: string type: string
x-go-name: PrivateComment x-go-name: PrivateComment
public_comment: public_comment:
description: If the domain is blocked, what's the publicly-stated reason for the block. description: |-
If the domain is blocked, what's the publicly-stated reason for the block.
Alternative to `comment` to be used when serializing/deserializing NOT via /api/v1/instance.
example: they smell example: they smell
type: string type: string
x-go-name: PublicComment x-go-name: PublicComment
@ -5828,6 +5846,53 @@ paths:
summary: View domain allow with the given ID. summary: View domain allow with the given ID.
tags: tags:
- admin - admin
put:
consumes:
- multipart/form-data
operationId: domainAllowUpdate
parameters:
- description: The id of the domain allow.
in: path
name: id
required: true
type: string
- description: Obfuscate the name of the domain when serving it publicly. Eg., `example.org` becomes something like `ex***e.org`.
in: formData
name: obfuscate
type: boolean
- description: Public comment about this domain allow. This will be displayed alongside the domain allow if you choose to share allows.
in: formData
name: public_comment
type: string
- description: Private comment about this domain allow. Will only be shown to other admins, so this is a useful way of internally keeping track of why a certain domain ended up allowed.
in: formData
name: private_comment
type: string
produces:
- application/json
responses:
"200":
description: The updated domain allow.
schema:
$ref: '#/definitions/domainPermission'
"400":
description: bad request
"401":
description: unauthorized
"403":
description: forbidden
"404":
description: not found
"406":
description: not acceptable
"500":
description: internal server error
security:
- OAuth2 Bearer:
- admin:write:domain_allows
summary: Update a single domain allow.
tags:
- admin
/api/v1/admin/domain_blocks: /api/v1/admin/domain_blocks:
get: get:
operationId: domainBlocksGet operationId: domainBlocksGet
@ -5995,6 +6060,53 @@ paths:
summary: View domain block with the given ID. summary: View domain block with the given ID.
tags: tags:
- admin - admin
put:
consumes:
- multipart/form-data
operationId: domainBlockUpdate
parameters:
- description: The id of the domain block.
in: path
name: id
required: true
type: string
- description: Obfuscate the name of the domain when serving it publicly. Eg., `example.org` becomes something like `ex***e.org`.
in: formData
name: obfuscate
type: boolean
- description: Public comment about this domain block. This will be displayed alongside the domain block if you choose to share blocks.
in: formData
name: public_comment
type: string
- description: Private comment about this domain block. Will only be shown to other admins, so this is a useful way of internally keeping track of why a certain domain ended up blocked.
in: formData
name: private_comment
type: string
produces:
- application/json
responses:
"200":
description: The updated domain block.
schema:
$ref: '#/definitions/domainPermission'
"400":
description: bad request
"401":
description: unauthorized
"403":
description: forbidden
"404":
description: not found
"406":
description: not acceptable
"500":
description: internal server error
security:
- OAuth2 Bearer:
- admin:write:domain_blocks
summary: Update a single domain block.
tags:
- admin
/api/v1/admin/domain_keys_expire: /api/v1/admin/domain_keys_expire:
post: post:
consumes: consumes:

43
go.mod
View file

@ -8,7 +8,7 @@ toolchain go1.23.3
replace github.com/go-swagger/go-swagger => codeberg.org/superseriousbusiness/go-swagger v0.31.0-gts-go1.23-fix replace github.com/go-swagger/go-swagger => codeberg.org/superseriousbusiness/go-swagger v0.31.0-gts-go1.23-fix
// Replace modernc/sqlite with our version that fixes the concurrency INTERRUPT issue // Replace modernc/sqlite with our version that fixes the concurrency INTERRUPT issue
replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.36.2-concurrency-workaround replace modernc.org/sqlite => gitlab.com/NyaaaWhatsUpDoc/sqlite v1.37.0-concurrency-workaround
require ( require (
codeberg.org/gruf/go-bytes v1.0.2 codeberg.org/gruf/go-bytes v1.0.2
@ -23,11 +23,11 @@ require (
codeberg.org/gruf/go-kv v1.6.5 codeberg.org/gruf/go-kv v1.6.5
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f
codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760 codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760
codeberg.org/gruf/go-mutexes v1.5.1 codeberg.org/gruf/go-mutexes v1.5.2
codeberg.org/gruf/go-runners v1.6.3 codeberg.org/gruf/go-runners v1.6.3
codeberg.org/gruf/go-sched v1.2.4 codeberg.org/gruf/go-sched v1.2.4
codeberg.org/gruf/go-storage v0.2.0 codeberg.org/gruf/go-storage v0.2.0
codeberg.org/gruf/go-structr v0.9.0 codeberg.org/gruf/go-structr v0.9.6
codeberg.org/superseriousbusiness/activity v1.13.0-gts codeberg.org/superseriousbusiness/activity v1.13.0-gts
codeberg.org/superseriousbusiness/exif-terminator v0.10.0 codeberg.org/superseriousbusiness/exif-terminator v0.10.0
codeberg.org/superseriousbusiness/httpsig v1.3.0-SSB codeberg.org/superseriousbusiness/httpsig v1.3.0-SSB
@ -37,7 +37,7 @@ require (
github.com/SherClockHolmes/webpush-go v1.4.0 github.com/SherClockHolmes/webpush-go v1.4.0
github.com/buckket/go-blurhash v1.1.0 github.com/buckket/go-blurhash v1.1.0
github.com/coreos/go-oidc/v3 v3.12.0 github.com/coreos/go-oidc/v3 v3.12.0
github.com/gin-contrib/cors v1.7.3 github.com/gin-contrib/cors v1.7.4
github.com/gin-contrib/gzip v1.2.2 github.com/gin-contrib/gzip v1.2.2
github.com/gin-contrib/sessions v1.0.2 github.com/gin-contrib/sessions v1.0.2
github.com/gin-gonic/gin v1.10.0 github.com/gin-gonic/gin v1.10.0
@ -52,17 +52,17 @@ require (
github.com/k3a/html2text v1.2.1 github.com/k3a/html2text v1.2.1
github.com/microcosm-cc/bluemonday v1.0.27 github.com/microcosm-cc/bluemonday v1.0.27
github.com/miekg/dns v1.1.64 github.com/miekg/dns v1.1.64
github.com/minio/minio-go/v7 v7.0.85 github.com/minio/minio-go/v7 v7.0.89
github.com/mitchellh/mapstructure v1.5.0 github.com/mitchellh/mapstructure v1.5.0
github.com/ncruces/go-sqlite3 v0.24.0 github.com/ncruces/go-sqlite3 v0.25.0
github.com/oklog/ulid v1.3.1 github.com/oklog/ulid v1.3.1
github.com/pquerna/otp v1.4.0 github.com/pquerna/otp v1.4.0
github.com/prometheus/client_golang v1.21.1 github.com/prometheus/client_golang v1.21.1
github.com/rivo/uniseg v0.4.7 github.com/rivo/uniseg v0.4.7
github.com/spf13/cobra v1.9.1 github.com/spf13/cobra v1.9.1
github.com/spf13/viper v1.20.0 github.com/spf13/viper v1.20.1
github.com/stretchr/testify v1.10.0 github.com/stretchr/testify v1.10.0
github.com/tdewolff/minify/v2 v2.22.3 github.com/tdewolff/minify/v2 v2.23.0
github.com/technologize/otel-go-contrib v1.1.1 github.com/technologize/otel-go-contrib v1.1.1
github.com/temoto/robotstxt v1.1.2 github.com/temoto/robotstxt v1.1.2
github.com/tetratelabs/wazero v1.9.0 github.com/tetratelabs/wazero v1.9.0
@ -83,12 +83,12 @@ require (
go.opentelemetry.io/otel/sdk/metric v1.34.0 go.opentelemetry.io/otel/sdk/metric v1.34.0
go.opentelemetry.io/otel/trace v1.35.0 go.opentelemetry.io/otel/trace v1.35.0
go.uber.org/automaxprocs v1.6.0 go.uber.org/automaxprocs v1.6.0
golang.org/x/crypto v0.36.0 golang.org/x/crypto v0.37.0
golang.org/x/image v0.24.0 golang.org/x/image v0.24.0
golang.org/x/net v0.37.0 golang.org/x/net v0.38.0
golang.org/x/oauth2 v0.27.0 golang.org/x/oauth2 v0.27.0
golang.org/x/sys v0.31.0 golang.org/x/sys v0.32.0
golang.org/x/text v0.23.0 golang.org/x/text v0.24.0
gopkg.in/mcuadros/go-syslog.v2 v2.3.0 gopkg.in/mcuadros/go-syslog.v2 v2.3.0
gopkg.in/yaml.v3 v3.0.1 gopkg.in/yaml.v3 v3.0.1
modernc.org/sqlite v0.0.0-00010101000000-000000000000 modernc.org/sqlite v0.0.0-00010101000000-000000000000
@ -97,7 +97,7 @@ require (
require ( require (
codeberg.org/gruf/go-fastpath/v2 v2.0.0 // indirect codeberg.org/gruf/go-fastpath/v2 v2.0.0 // indirect
codeberg.org/gruf/go-mangler v1.4.3 // indirect codeberg.org/gruf/go-mangler v1.4.4 // indirect
codeberg.org/gruf/go-maps v1.0.4 // indirect codeberg.org/gruf/go-maps v1.0.4 // indirect
codeberg.org/superseriousbusiness/go-jpeg-image-structure/v2 v2.1.0-SSB // indirect codeberg.org/superseriousbusiness/go-jpeg-image-structure/v2 v2.1.0-SSB // indirect
codeberg.org/superseriousbusiness/go-png-image-structure/v2 v2.1.0-SSB // indirect codeberg.org/superseriousbusiness/go-png-image-structure/v2 v2.1.0-SSB // indirect
@ -166,12 +166,13 @@ require (
github.com/josharian/intern v1.0.0 // indirect github.com/josharian/intern v1.0.0 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/compress v1.18.0 // indirect github.com/klauspost/compress v1.18.0 // indirect
github.com/klauspost/cpuid/v2 v2.2.9 // indirect github.com/klauspost/cpuid/v2 v2.2.10 // indirect
github.com/kr/pretty v0.3.1 // indirect github.com/kr/pretty v0.3.1 // indirect
github.com/kr/text v0.2.0 // indirect github.com/kr/text v0.2.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mailru/easyjson v0.7.7 // indirect github.com/mailru/easyjson v0.7.7 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/minio/crc64nvme v1.0.1 // indirect
github.com/minio/md5-simd v1.1.2 // indirect github.com/minio/md5-simd v1.1.2 // indirect
github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect
github.com/mitchellh/reflectwalk v1.0.2 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect
@ -199,7 +200,7 @@ require (
github.com/spf13/cast v1.7.1 // indirect github.com/spf13/cast v1.7.1 // indirect
github.com/spf13/pflag v1.0.6 // indirect github.com/spf13/pflag v1.0.6 // indirect
github.com/subosito/gotenv v1.6.0 // indirect github.com/subosito/gotenv v1.6.0 // indirect
github.com/tdewolff/parse/v2 v2.7.21 // indirect github.com/tdewolff/parse/v2 v2.7.22 // indirect
github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect github.com/tmthrgd/go-hex v0.0.0-20190904060850-447a3041c3bc // indirect
github.com/toqueteos/webbrowser v1.2.0 // indirect github.com/toqueteos/webbrowser v1.2.0 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
@ -213,16 +214,16 @@ require (
go.opentelemetry.io/proto/otlp v1.5.0 // indirect go.opentelemetry.io/proto/otlp v1.5.0 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/arch v0.13.0 // indirect golang.org/x/arch v0.13.0 // indirect
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 // indirect golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 // indirect
golang.org/x/mod v0.23.0 // indirect golang.org/x/mod v0.24.0 // indirect
golang.org/x/sync v0.12.0 // indirect golang.org/x/sync v0.13.0 // indirect
golang.org/x/tools v0.30.0 // indirect golang.org/x/tools v0.31.0 // indirect
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250218202821-56aae31c358a // indirect
google.golang.org/grpc v1.71.0 // indirect google.golang.org/grpc v1.71.0 // indirect
google.golang.org/protobuf v1.36.5 // indirect google.golang.org/protobuf v1.36.5 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect
modernc.org/libc v1.61.13 // indirect modernc.org/libc v1.62.1 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect
modernc.org/memory v1.8.2 // indirect modernc.org/memory v1.9.1 // indirect
) )

102
go.sum generated
View file

@ -24,22 +24,22 @@ codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f h1:Ss6Z+vygy+jOGhj9
codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f/go.mod h1:F9pl4h34iuVN7kucKam9fLwsItTc+9mmaKt7pNXRd/4= codeberg.org/gruf/go-list v0.0.0-20240425093752-494db03d641f/go.mod h1:F9pl4h34iuVN7kucKam9fLwsItTc+9mmaKt7pNXRd/4=
codeberg.org/gruf/go-loosy v0.0.0-20231007123304-bb910d1ab5c4 h1:IXwfoU7f2whT6+JKIKskNl/hBlmWmnF1vZd84Eb3cyA= codeberg.org/gruf/go-loosy v0.0.0-20231007123304-bb910d1ab5c4 h1:IXwfoU7f2whT6+JKIKskNl/hBlmWmnF1vZd84Eb3cyA=
codeberg.org/gruf/go-loosy v0.0.0-20231007123304-bb910d1ab5c4/go.mod h1:fiO8HE1wjZCephcYmRRsVnNI/i0+mhy44Z5dQalS0rM= codeberg.org/gruf/go-loosy v0.0.0-20231007123304-bb910d1ab5c4/go.mod h1:fiO8HE1wjZCephcYmRRsVnNI/i0+mhy44Z5dQalS0rM=
codeberg.org/gruf/go-mangler v1.4.3 h1:mdtcbGDyj0AS9LE/H1imQreICVn6BQiks554jzdAozc= codeberg.org/gruf/go-mangler v1.4.4 h1:moQl7FSSLLaByS7w5UP7b3Z7r2ex/F4IpvSp+PyRWK4=
codeberg.org/gruf/go-mangler v1.4.3/go.mod h1:mDmW8Ia352RvNFaXoP9K60TgcmCZJtX0j6wm3vjAsJE= codeberg.org/gruf/go-mangler v1.4.4/go.mod h1:mDmW8Ia352RvNFaXoP9K60TgcmCZJtX0j6wm3vjAsJE=
codeberg.org/gruf/go-maps v1.0.4 h1:K+Ww4vvR3TZqm5jqrKVirmguZwa3v1VUvmig2SE8uxY= codeberg.org/gruf/go-maps v1.0.4 h1:K+Ww4vvR3TZqm5jqrKVirmguZwa3v1VUvmig2SE8uxY=
codeberg.org/gruf/go-maps v1.0.4/go.mod h1:ASX7osM7kFwt5O8GfGflcFjrwYGD8eIuRLl/oMjhEi8= codeberg.org/gruf/go-maps v1.0.4/go.mod h1:ASX7osM7kFwt5O8GfGflcFjrwYGD8eIuRLl/oMjhEi8=
codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760 h1:m2/UCRXhjDwAg4vyji6iKCpomKw6P4PmBOUi5DvAMH4= codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760 h1:m2/UCRXhjDwAg4vyji6iKCpomKw6P4PmBOUi5DvAMH4=
codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760/go.mod h1:E3RcaCFNq4zXpvaJb8lfpPqdUAmSkP5F1VmMiEUYTEk= codeberg.org/gruf/go-mempool v0.0.0-20240507125005-cef10d64a760/go.mod h1:E3RcaCFNq4zXpvaJb8lfpPqdUAmSkP5F1VmMiEUYTEk=
codeberg.org/gruf/go-mutexes v1.5.1 h1:xICU0WXhWr6wf+Iror4eE3xT+xnXNPrO6o77D/G6QuY= codeberg.org/gruf/go-mutexes v1.5.2 h1:rp2o774ApGUVtOHDksqtBiqIcvniVfgFWSazszDluy0=
codeberg.org/gruf/go-mutexes v1.5.1/go.mod h1:rPEqQ/y6CmGITaZ3GPTMQVsoZAOzbsAHyIaLsJcOqVE= codeberg.org/gruf/go-mutexes v1.5.2/go.mod h1:AnhagsMzUISL/nBVwhnHwDwTZOAxMILwCOG8/wKOblg=
codeberg.org/gruf/go-runners v1.6.3 h1:To/AX7eTrWuXrTkA3RA01YTP5zha1VZ68LQ+0D4RY7E= codeberg.org/gruf/go-runners v1.6.3 h1:To/AX7eTrWuXrTkA3RA01YTP5zha1VZ68LQ+0D4RY7E=
codeberg.org/gruf/go-runners v1.6.3/go.mod h1:oXAaUmG2VxoKttpCqZGv5nQBeSvZSR2BzIk7h1yTRlU= codeberg.org/gruf/go-runners v1.6.3/go.mod h1:oXAaUmG2VxoKttpCqZGv5nQBeSvZSR2BzIk7h1yTRlU=
codeberg.org/gruf/go-sched v1.2.4 h1:ddBB9o0D/2oU8NbQ0ldN5aWxogpXPRBATWi58+p++Hw= codeberg.org/gruf/go-sched v1.2.4 h1:ddBB9o0D/2oU8NbQ0ldN5aWxogpXPRBATWi58+p++Hw=
codeberg.org/gruf/go-sched v1.2.4/go.mod h1:wad6l+OcYGWMA2TzNLMmLObsrbBDxdJfEy5WvTgBjNk= codeberg.org/gruf/go-sched v1.2.4/go.mod h1:wad6l+OcYGWMA2TzNLMmLObsrbBDxdJfEy5WvTgBjNk=
codeberg.org/gruf/go-storage v0.2.0 h1:mKj3Lx6AavEkuXXtxqPhdq+akW9YwrnP16yQBF7K5ZI= codeberg.org/gruf/go-storage v0.2.0 h1:mKj3Lx6AavEkuXXtxqPhdq+akW9YwrnP16yQBF7K5ZI=
codeberg.org/gruf/go-storage v0.2.0/go.mod h1:o3GzMDE5QNUaRnm/daUzFqvuAaC4utlgXDXYO79sWKU= codeberg.org/gruf/go-storage v0.2.0/go.mod h1:o3GzMDE5QNUaRnm/daUzFqvuAaC4utlgXDXYO79sWKU=
codeberg.org/gruf/go-structr v0.9.0 h1:UYw8igp3I4UBnlsRyDR2AbF3g7NPEP7HBrQs1I15218= codeberg.org/gruf/go-structr v0.9.6 h1:FSbJ1A0ubTQB82rC0K4o6qyiqrDGH1t9ivttm8Zy64o=
codeberg.org/gruf/go-structr v0.9.0/go.mod h1:mUvBvn4q1iM/I+d3Fj1w/gxGUU/Ve9GpiNo6dPmBJnk= codeberg.org/gruf/go-structr v0.9.6/go.mod h1:9k5hYztZ4PsBS+m1v5hUTeFiVUBTLF5VA7d9cd1OEMs=
codeberg.org/superseriousbusiness/activity v1.13.0-gts h1:4WZLc/SNt+Vt5x2UjL2n6V5dHlIL9ECudUPx8Ld5rxw= codeberg.org/superseriousbusiness/activity v1.13.0-gts h1:4WZLc/SNt+Vt5x2UjL2n6V5dHlIL9ECudUPx8Ld5rxw=
codeberg.org/superseriousbusiness/activity v1.13.0-gts/go.mod h1:enxU1Lva4OcK6b/NBXscoHSEgEMsKJvdHrQFifQxp4o= codeberg.org/superseriousbusiness/activity v1.13.0-gts/go.mod h1:enxU1Lva4OcK6b/NBXscoHSEgEMsKJvdHrQFifQxp4o=
codeberg.org/superseriousbusiness/exif-terminator v0.10.0 h1:FiLX/AK07tzceS36I+kOP2aEH+aytjPSIlFoYePMEyg= codeberg.org/superseriousbusiness/exif-terminator v0.10.0 h1:FiLX/AK07tzceS36I+kOP2aEH+aytjPSIlFoYePMEyg=
@ -135,8 +135,8 @@ github.com/gabriel-vasile/mimetype v1.4.8 h1:FfZ3gj38NjllZIeJAmMhr+qKL8Wu+nOoI3G
github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8= github.com/gabriel-vasile/mimetype v1.4.8/go.mod h1:ByKUIKGjh1ODkGM1asKUbQZOLGrPjydw3hYPU2YU9t8=
github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8= github.com/gavv/httpexpect v2.0.0+incompatible h1:1X9kcRshkSKEjNJJxX9Y9mQ5BRfbxU5kORdjhlA1yX8=
github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc= github.com/gavv/httpexpect v2.0.0+incompatible/go.mod h1:x+9tiU1YnrOvnB725RkpoLv1M62hOWzwo5OXotisrKc=
github.com/gin-contrib/cors v1.7.3 h1:hV+a5xp8hwJoTw7OY+a70FsL8JkVVFTXw9EcfrYUdns= github.com/gin-contrib/cors v1.7.4 h1:/fC6/wk7rCRtqKqki8lLr2Xq+hnV49aXDLIuSek9g4k=
github.com/gin-contrib/cors v1.7.3/go.mod h1:M3bcKZhxzsvI+rlRSkkxHyljJt1ESd93COUvemZ79j4= github.com/gin-contrib/cors v1.7.4/go.mod h1:vGc/APSgLMlQfEJV5NAzkrAHb0C8DetL3K6QZuvGii0=
github.com/gin-contrib/gzip v1.2.2 h1:iUU/EYCM8ENfkjmZaVrxbjF/ZC267Iqv5S0MMCMEliI= github.com/gin-contrib/gzip v1.2.2 h1:iUU/EYCM8ENfkjmZaVrxbjF/ZC267Iqv5S0MMCMEliI=
github.com/gin-contrib/gzip v1.2.2/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s= github.com/gin-contrib/gzip v1.2.2/go.mod h1:C1a5cacjlDsS20cKnHlZRCPUu57D3qH6B2pV0rl+Y/s=
github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA= github.com/gin-contrib/sessions v1.0.2 h1:UaIjUvTH1cMeOdj3in6dl+Xb6It8RiKRF9Z1anbUyCA=
@ -282,8 +282,8 @@ github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zt
github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ=
github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.1/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.9 h1:66ze0taIn2H33fBvCkXuv9BmCwDfafmiIVpKV9kKGuY= github.com/klauspost/cpuid/v2 v2.2.10 h1:tBs3QSyvjDyFTq3uoc/9xFpCuOsJQFNPiAhYdw2skhE=
github.com/klauspost/cpuid/v2 v2.2.9/go.mod h1:rqkxqrZ1EhYM9G+hXH7YdowN5R5RGN6NK4QwQ3WMXF8= github.com/klauspost/cpuid/v2 v2.2.10/go.mod h1:hqwkgyIinND0mEev00jJYCxPNVRVXFQeu1XKlok6oO0=
github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M= github.com/knz/go-libedit v1.10.1/go.mod h1:MZTVkCWyz0oBc7JOWP3wNAzd002ZbM/5hgShxwh4x8M=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
@ -301,10 +301,12 @@ github.com/microcosm-cc/bluemonday v1.0.27 h1:MpEUotklkwCSLeH+Qdx1VJgNqLlpY2KXwX
github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA= github.com/microcosm-cc/bluemonday v1.0.27/go.mod h1:jFi9vgW+H7c3V0lb6nR74Ib/DIB5OBs92Dimizgw2cA=
github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ= github.com/miekg/dns v1.1.64 h1:wuZgD9wwCE6XMT05UU/mlSko71eRSXEAm2EbjQXLKnQ=
github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck= github.com/miekg/dns v1.1.64/go.mod h1:Dzw9769uoKVaLuODMDZz9M6ynFU6Em65csPuoi8G0ck=
github.com/minio/crc64nvme v1.0.1 h1:DHQPrYPdqK7jQG/Ls5CTBZWeex/2FMS3G5XGkycuFrY=
github.com/minio/crc64nvme v1.0.1/go.mod h1:eVfm2fAzLlxMdUGc0EEBGSMmPwmXD5XiNRpnu9J3bvg=
github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34= github.com/minio/md5-simd v1.1.2 h1:Gdi1DZK69+ZVMoNHRXJyNcxrMA4dSxoYHZSQbirFg34=
github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM= github.com/minio/md5-simd v1.1.2/go.mod h1:MzdKDxYpY2BT9XQFocsiZf/NKVtR7nkE4RoEpN+20RM=
github.com/minio/minio-go/v7 v7.0.85 h1:9psTLS/NTvC3MWoyjhjXpwcKoNbkongaCSF3PNpSuXo= github.com/minio/minio-go/v7 v7.0.89 h1:hx4xV5wwTUfyv8LarhJAwNecnXpoTsj9v3f3q/ZkiJU=
github.com/minio/minio-go/v7 v7.0.85/go.mod h1:57YXpvc5l3rjPdhqNrDsvVlY0qPI6UTk1bflAe+9doY= github.com/minio/minio-go/v7 v7.0.89/go.mod h1:2rFnGAp02p7Dddo1Fq4S2wYOfpF0MUTSeLTRC90I204=
github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw= github.com/mitchellh/copystructure v1.0.0/go.mod h1:SNtv71yrdKgLRyLFxmLdkAbkKEFWgYaq1OVrnRcwhnw=
github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw= github.com/mitchellh/copystructure v1.2.0 h1:vpKXTN4ewci03Vljg/q9QvCGUDttBOGBIa15WveJJGw=
github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s= github.com/mitchellh/copystructure v1.2.0/go.mod h1:qLl+cE2AmVv+CoeAwDPye/v+N2HKCj9FbZEVFJRxO9s=
@ -324,8 +326,8 @@ github.com/moul/http2curl v1.0.0 h1:dRMWoAtb+ePxMlLkrCbAqh4TlPHXvoGUSQ323/9Zahs=
github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ= github.com/moul/http2curl v1.0.0/go.mod h1:8UbvGypXm98wA/IqH45anm5Y2Z6ep6O31QGOAZ3H0fQ=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA=
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ=
github.com/ncruces/go-sqlite3 v0.24.0 h1:Z4jfmzu2NCd4SmyFwLT2OmF3EnTZbqwATvdiuNHNhLA= github.com/ncruces/go-sqlite3 v0.25.0 h1:trugKUs98Zwy9KwRr/EUxZHL92LYt7UqcKqAfpGpK+I=
github.com/ncruces/go-sqlite3 v0.24.0/go.mod h1:/Vs8ACZHjJ1SA6E9RZUn3EyB1OP3nDQ4z/ar+0fplTQ= github.com/ncruces/go-sqlite3 v0.25.0/go.mod h1:n6Z7036yFilJx04yV0mi5JWaF66rUmXn1It9Ux8dx68=
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M= github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
@ -392,8 +394,8 @@ github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
github.com/spf13/viper v1.20.0 h1:zrxIyR3RQIOsarIrgL8+sAvALXul9jeEPa06Y0Ph6vY= github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4=
github.com/spf13/viper v1.20.0/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
@ -410,10 +412,10 @@ github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOf
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
github.com/tdewolff/minify/v2 v2.22.3 h1:iWXbYdEwvyMXq+KoZlM7Aybp2ASq1VTibUIUxtiyfWo= github.com/tdewolff/minify/v2 v2.23.0 h1:ZdVmMkGYApnUpmOL/H/PCEk6qg6OFHzVDXgk07z9TW0=
github.com/tdewolff/minify/v2 v2.22.3/go.mod h1:K/R8TT7aivpcU8QCNUU1UdR6etfnFPr7L11TO/X7shk= github.com/tdewolff/minify/v2 v2.23.0/go.mod h1:ll/rxPfOGIgN9G4JXg+3jMtPTPEnEJB3nGtEG08sHl8=
github.com/tdewolff/parse/v2 v2.7.21 h1:OCuPFtGr4mXdnfKikQlUb0n654ROJANhBqCk+wioJ/A= github.com/tdewolff/parse/v2 v2.7.22 h1:ROVbrjtp5RoXi22YSZaOks5DaOcXBJ3PZO5hyyQ9Bbs=
github.com/tdewolff/parse/v2 v2.7.21/go.mod h1:I7TXO37t3aSG9SlPUBefAhgIF8nt7yYUwVGgETIoBcA= github.com/tdewolff/parse/v2 v2.7.22/go.mod h1:I7TXO37t3aSG9SlPUBefAhgIF8nt7yYUwVGgETIoBcA=
github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE= github.com/tdewolff/test v1.0.11 h1:FdLbwQVHxqG16SlkGveC0JVyrJN62COWTRyUFzfbtBE=
github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8= github.com/tdewolff/test v1.0.11/go.mod h1:XPuWBzvdUzhCuxWO1ojpXsyzsA5bFoS3tO/Q3kFuTG8=
github.com/technologize/otel-go-contrib v1.1.1 h1:wZH9aSPNWZWIkEh3vfaKfMb15AJ80jJ1aVj/4GZdqIw= github.com/technologize/otel-go-contrib v1.1.1 h1:wZH9aSPNWZWIkEh3vfaKfMb15AJ80jJ1aVj/4GZdqIw=
@ -487,8 +489,8 @@ github.com/yudai/golcs v0.0.0-20170316035057-ecda9a501e82/go.mod h1:lgjkn3NuSvDf
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic= github.com/yuin/goldmark v1.7.8 h1:iERMLn0/QJeHFhxSt3p6PeN9mGnvIKSpG9YYorDMnic=
github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E= github.com/yuin/goldmark v1.7.8/go.mod h1:uzxRWxtg69N339t3louHJ7+O03ezfj6PlliRlaOzY1E=
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.36.2-concurrency-workaround h1:1NAPhEPvJJbD+qwXi+IWtfvntCZRWF9frtqeQLtf+TE= gitlab.com/NyaaaWhatsUpDoc/sqlite v1.37.0-concurrency-workaround h1:QbfrBqNKgAFSSK89fYf547vxDQuz8p6iJUzzAMrusNk=
gitlab.com/NyaaaWhatsUpDoc/sqlite v1.36.2-concurrency-workaround/go.mod h1:ADySlx7K4FdY5MaJcEv86hTJ0PjedAloTUuif0YS3ws= gitlab.com/NyaaaWhatsUpDoc/sqlite v1.37.0-concurrency-workaround/go.mod h1:5YiWv+YviqGMuGw4V+PNplcyaJ5v+vQd7TQOgkACoJM=
go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80= go.mongodb.org/mongo-driver v1.14.0 h1:P98w8egYRjYe3XDjxhYJagTokP/H6HzlsnojRgZRd80=
go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c= go.mongodb.org/mongo-driver v1.14.0/go.mod h1:Vzb0Mk/pa7e6cWw85R4F/endUC3u0U9jGcNU603k65c=
go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA=
@ -529,10 +531,10 @@ golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliY
golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU=
golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8=
golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk=
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34= golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc= golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225 h1:LfspQV/FYTatPTr/3HzIcmiUFH7PGP+OQ6mgDYo3yuQ= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394 h1:nDVHiLt8aIbd/VzvPWN6kSOPE7+F/fNFDSXLVYkE/Iw=
golang.org/x/exp v0.0.0-20240222234643-814bf88cf225/go.mod h1:CxmFvTBINI24O/j8iY7H1xHzx2i4OsyguNBmN/uPtqc= golang.org/x/exp v0.0.0-20250305212735-054e65f0b394/go.mod h1:sIifuuw/Yco/y6yb6+bDNfyeQ/MdPUy/hKEMYQV17cM=
golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ= golang.org/x/image v0.24.0 h1:AN7zRgVsbvmTfNyqIbbOraYL8mSwcKncEj8ofjgzcMQ=
golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8= golang.org/x/image v0.24.0/go.mod h1:4b/ITuLfqYq1hqZcjofwctIhi7sZh2WaCjvsBNjjya8=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
@ -540,8 +542,8 @@ golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.15.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.17.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
golang.org/x/mod v0.23.0 h1:Zb7khfcRGKk+kqfxFaP5tZqCnDZMjC5VtUBs87Hr6QM= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=
golang.org/x/mod v0.23.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.24.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww=
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
@ -558,8 +560,8 @@ golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg=
golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM=
golang.org/x/net v0.37.0 h1:1zLorHbz+LYj7MQlSf1+2tPIIgibq2eL5xkrGk6f+2c= golang.org/x/net v0.38.0 h1:vRMAPTMaeGqVhG5QyLJHqNDwecKTomGeqbnfZyKlBI8=
golang.org/x/net v0.37.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8= golang.org/x/net v0.38.0/go.mod h1:ivrbrMbzFq5J41QOQh0siUuly180yBYtLp+CKbEaFx8=
golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M= golang.org/x/oauth2 v0.27.0 h1:da9Vo7/tDv5RH/7nZDz1eMGS/q1Vv1N/7FCrBhI9I3M=
golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8= golang.org/x/oauth2 v0.27.0/go.mod h1:onh5ek6nERTohokkhCD/y2cV4Do3fxFHFuAejCkRWT8=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@ -569,8 +571,8 @@ golang.org/x/sync v0.3.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.12.0 h1:MHc5BpPuC30uJk597Ri8TV3CNZcTLu6B6z4lJy+g6Jw= golang.org/x/sync v0.13.0 h1:AauUjRAJ9OSnvULf/ARrrVywoJDy0YS2AwQ98I37610=
golang.org/x/sync v0.12.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.13.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -587,8 +589,8 @@ golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik= golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE= golang.org/x/telemetry v0.0.0-20240228155512-f48c80bd79b2/go.mod h1:TeRTkGYfJXctD9OcfyVLyj2J3IxLnKwHJR8f4D8a3YE=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@ -599,8 +601,8 @@ golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU=
golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk=
golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY=
golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM= golang.org/x/term v0.27.0/go.mod h1:iMsnZpn0cago0GOrHO2+Y7u7JPn5AylBrcoWkElMTSM=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y= golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g= golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
@ -611,8 +613,8 @@ golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ=
golang.org/x/text v0.23.0 h1:D71I7dUrlY+VX0gQShAThNGHFxZ13dGLBHQLVl1mJlY= golang.org/x/text v0.24.0 h1:dd5Bzh4yt5KYA8f9CJHCP4FB4D51c2c6JvN37xJJkJ0=
golang.org/x/text v0.23.0/go.mod h1:/BLNzu4aZCJ1+kcD0DNRotWKage4q2rGVAg4o22unh4= golang.org/x/text v0.24.0/go.mod h1:L8rBsPeo2pSS+xqN0d5u2ikmjtmoJbDBT1b7nHvFCdU=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
@ -620,8 +622,8 @@ golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58= golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk= golang.org/x/tools v0.21.1-0.20240508182429-e35e4ccd0d2d/go.mod h1:aiJjzUbINMkxbQROHiO6hDPo2LHcIPhhQsa9DLh0yGk=
golang.org/x/tools v0.30.0 h1:BgcpHewrV5AUp2G9MebG4XPFI1E2W41zU1SaqVA9vJY= golang.org/x/tools v0.31.0 h1:0EedkvKDbh+qistFTd0Bcwe/YLh4vHwWEkiI0toFIBU=
golang.org/x/tools v0.30.0/go.mod h1:c347cR/OJfw5TI+GfX7RUPNMdDRRbjvYTS0jPyvsVtY= golang.org/x/tools v0.31.0/go.mod h1:naFTU+Cev749tSJRXJlna0T3WxKvb1kWEx15xA4SdmQ=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a h1:nwKuGPlUAt+aR+pcrkfFRrTU1BVrSmYyYMxYbUIVHr0=
google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU= google.golang.org/genproto/googleapis/api v0.0.0-20250218202821-56aae31c358a/go.mod h1:3kWAYMk1I75K4vykHtKt2ycnOgpA6974V7bREqbsenU=
@ -644,20 +646,20 @@ 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.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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
modernc.org/cc/v4 v4.24.4 h1:TFkx1s6dCkQpd6dKurBNmpo+G8Zl4Sq/ztJ+2+DEsh0= modernc.org/cc/v4 v4.25.2 h1:T2oH7sZdGvTaie0BRNFbIYsabzCxUQg8nLqCdQ2i0ic=
modernc.org/cc/v4 v4.24.4/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0= modernc.org/cc/v4 v4.25.2/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
modernc.org/ccgo/v4 v4.23.16 h1:Z2N+kk38b7SfySC1ZkpGLN2vthNJP1+ZzGZIlH7uBxo= modernc.org/ccgo/v4 v4.25.1 h1:TFSzPrAGmDsdnhT9X2UrcPMI3N/mJ9/X9ykKXwLhDsU=
modernc.org/ccgo/v4 v4.23.16/go.mod h1:nNma8goMTY7aQZQNTyN9AIoJfxav4nvTnvKThAeMDdo= modernc.org/ccgo/v4 v4.25.1/go.mod h1:njjuAYiPflywOOrm3B7kCB444ONP5pAVr8PIEoE0uDw=
modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE= modernc.org/fileutil v1.3.0 h1:gQ5SIzK3H9kdfai/5x41oQiKValumqNTDXMvKo62HvE=
modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ= modernc.org/fileutil v1.3.0/go.mod h1:XatxS8fZi3pS8/hKG2GH/ArUogfxjpEKs3Ku3aK4JyQ=
modernc.org/gc/v2 v2.6.3 h1:aJVhcqAte49LF+mGveZ5KPlsp4tdGdAOT4sipJXADjw= modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
modernc.org/gc/v2 v2.6.3/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito= modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
modernc.org/libc v1.61.13 h1:3LRd6ZO1ezsFiX1y+bHd1ipyEHIJKvuprv0sLTBwLW8= modernc.org/libc v1.62.1 h1:s0+fv5E3FymN8eJVmnk0llBe6rOxCu/DEU+XygRbS8s=
modernc.org/libc v1.61.13/go.mod h1:8F/uJWL/3nNil0Lgt1Dpz+GgkApWh04N3el3hxJcA6E= modernc.org/libc v1.62.1/go.mod h1:iXhATfJQLjG3NWy56a6WVU73lWOcdYVxsvwCgoPljuo=
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU= modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg= modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
modernc.org/memory v1.8.2 h1:cL9L4bcoAObu4NkxOlKWBWtNHIsnnACGF/TbqQ6sbcI= modernc.org/memory v1.9.1 h1:V/Z1solwAVmMW1yttq3nDdZPJqV1rM05Ccq6KMSZ34g=
modernc.org/memory v1.8.2/go.mod h1:ZbjSvMO5NQ1A2i3bWeDiVMxIorXwdClKE/0SZ+BMotU= modernc.org/memory v1.9.1/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8= modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns= modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w= modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=

View file

@ -376,10 +376,9 @@ func (suite *InboxPostTestSuite) TestPostUpdate() {
suite.EqualValues(requestingAccount.HeaderMediaAttachment, dbUpdatedAccount.HeaderMediaAttachment) suite.EqualValues(requestingAccount.HeaderMediaAttachment, dbUpdatedAccount.HeaderMediaAttachment)
suite.EqualValues(requestingAccount.HeaderRemoteURL, dbUpdatedAccount.HeaderRemoteURL) suite.EqualValues(requestingAccount.HeaderRemoteURL, dbUpdatedAccount.HeaderRemoteURL)
suite.EqualValues(requestingAccount.Note, dbUpdatedAccount.Note) suite.EqualValues(requestingAccount.Note, dbUpdatedAccount.Note)
suite.EqualValues(requestingAccount.Memorial, dbUpdatedAccount.Memorial) suite.EqualValues(requestingAccount.MemorializedAt, dbUpdatedAccount.MemorializedAt)
suite.EqualValues(requestingAccount.AlsoKnownAsURIs, dbUpdatedAccount.AlsoKnownAsURIs) suite.EqualValues(requestingAccount.AlsoKnownAsURIs, dbUpdatedAccount.AlsoKnownAsURIs)
suite.EqualValues(requestingAccount.MovedToURI, dbUpdatedAccount.MovedToURI) suite.EqualValues(requestingAccount.MovedToURI, dbUpdatedAccount.MovedToURI)
suite.EqualValues(requestingAccount.Bot, dbUpdatedAccount.Bot)
suite.EqualValues(requestingAccount.Locked, dbUpdatedAccount.Locked) suite.EqualValues(requestingAccount.Locked, dbUpdatedAccount.Locked)
suite.EqualValues(requestingAccount.Discoverable, dbUpdatedAccount.Discoverable) suite.EqualValues(requestingAccount.Discoverable, dbUpdatedAccount.Discoverable)
suite.EqualValues(requestingAccount.URI, dbUpdatedAccount.URI) suite.EqualValues(requestingAccount.URI, dbUpdatedAccount.URI)

View file

@ -88,7 +88,7 @@ func (suite *AccountVerifyTestSuite) TestAccountVerifyGet() {
suite.Equal(testAccount.Username, apimodelAccount.Acct) suite.Equal(testAccount.Username, apimodelAccount.Acct)
suite.Equal(testAccount.DisplayName, apimodelAccount.DisplayName) suite.Equal(testAccount.DisplayName, apimodelAccount.DisplayName)
suite.Equal(*testAccount.Locked, apimodelAccount.Locked) suite.Equal(*testAccount.Locked, apimodelAccount.Locked)
suite.Equal(*testAccount.Bot, apimodelAccount.Bot) suite.False(apimodelAccount.Bot)
suite.WithinDuration(testAccount.CreatedAt, createdAt, 30*time.Second) // we lose a bit of accuracy serializing so fuzz this a bit suite.WithinDuration(testAccount.CreatedAt, createdAt, 30*time.Second) // we lose a bit of accuracy serializing so fuzz this a bit
suite.Equal(testAccount.URL, apimodelAccount.URL) suite.Equal(testAccount.URL, apimodelAccount.URL)
suite.Equal("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg", apimodelAccount.Avatar) suite.Equal("http://localhost:8080/fileserver/01F8MH1H7YV1Z7D2C8K2730QBF/avatar/original/01F8MH58A357CV5K7R7TJMSH6S.jpg", apimodelAccount.Avatar)

View file

@ -204,7 +204,7 @@ func (suite *AccountsGetTestSuite) TestAccountsGetFromTop() {
"display_name": "", "display_name": "",
"locked": false, "locked": false,
"discoverable": true, "discoverable": true,
"bot": false, "bot": true,
"created_at": "2020-05-17T13:10:59.000Z", "created_at": "2020-05-17T13:10:59.000Z",
"note": "", "note": "",
"url": "http://localhost:8080/@localhost:8080", "url": "http://localhost:8080/@localhost:8080",

View file

@ -102,12 +102,14 @@ func (m *Module) Route(attachHandler func(method string, path string, f ...gin.H
attachHandler(http.MethodPost, DomainBlocksPath, m.DomainBlocksPOSTHandler) attachHandler(http.MethodPost, DomainBlocksPath, m.DomainBlocksPOSTHandler)
attachHandler(http.MethodGet, DomainBlocksPath, m.DomainBlocksGETHandler) attachHandler(http.MethodGet, DomainBlocksPath, m.DomainBlocksGETHandler)
attachHandler(http.MethodGet, DomainBlocksPathWithID, m.DomainBlockGETHandler) attachHandler(http.MethodGet, DomainBlocksPathWithID, m.DomainBlockGETHandler)
attachHandler(http.MethodPut, DomainBlocksPathWithID, m.DomainBlockUpdatePUTHandler)
attachHandler(http.MethodDelete, DomainBlocksPathWithID, m.DomainBlockDELETEHandler) attachHandler(http.MethodDelete, DomainBlocksPathWithID, m.DomainBlockDELETEHandler)
// domain allow stuff // domain allow stuff
attachHandler(http.MethodPost, DomainAllowsPath, m.DomainAllowsPOSTHandler) attachHandler(http.MethodPost, DomainAllowsPath, m.DomainAllowsPOSTHandler)
attachHandler(http.MethodGet, DomainAllowsPath, m.DomainAllowsGETHandler) attachHandler(http.MethodGet, DomainAllowsPath, m.DomainAllowsGETHandler)
attachHandler(http.MethodGet, DomainAllowsPathWithID, m.DomainAllowGETHandler) attachHandler(http.MethodGet, DomainAllowsPathWithID, m.DomainAllowGETHandler)
attachHandler(http.MethodPut, DomainAllowsPathWithID, m.DomainAllowUpdatePUTHandler)
attachHandler(http.MethodDelete, DomainAllowsPathWithID, m.DomainAllowDELETEHandler) attachHandler(http.MethodDelete, DomainAllowsPathWithID, m.DomainAllowDELETEHandler)
// domain permission draft stuff // domain permission draft stuff

View file

@ -0,0 +1,91 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package admin
import (
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// DomainAllowUpdatePUTHandler swagger:operation PUT /api/v1/admin/domain_allows/{id} domainAllowUpdate
//
// Update a single domain allow.
//
// ---
// tags:
// - admin
//
// consumes:
// - multipart/form-data
//
// produces:
// - application/json
//
// parameters:
// -
// name: id
// type: string
// description: The id of the domain allow.
// in: path
// required: true
// -
// name: obfuscate
// in: formData
// description: >-
// Obfuscate the name of the domain when serving it publicly.
// Eg., `example.org` becomes something like `ex***e.org`.
// type: boolean
// -
// name: public_comment
// in: formData
// description: >-
// Public comment about this domain allow.
// This will be displayed alongside the domain allow if you choose to share allows.
// type: string
// -
// name: private_comment
// in: formData
// description: >-
// Private comment about this domain allow. Will only be shown to other admins, so this
// is a useful way of internally keeping track of why a certain domain ended up allowed.
// type: string
//
// security:
// - OAuth2 Bearer:
// - admin:write:domain_allows
//
// responses:
// '200':
// description: The updated domain allow.
// schema:
// "$ref": "#/definitions/domainPermission"
// '400':
// description: bad request
// '401':
// description: unauthorized
// '403':
// description: forbidden
// '404':
// description: not found
// '406':
// description: not acceptable
// '500':
// description: internal server error
func (m *Module) DomainAllowUpdatePUTHandler(c *gin.Context) {
m.updateDomainPermission(c, gtsmodel.DomainPermissionAllow)
}

View file

@ -0,0 +1,91 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package admin
import (
"github.com/gin-gonic/gin"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
)
// DomainBlockUpdatePUTHandler swagger:operation PUT /api/v1/admin/domain_blocks/{id} domainBlockUpdate
//
// Update a single domain block.
//
// ---
// tags:
// - admin
//
// consumes:
// - multipart/form-data
//
// produces:
// - application/json
//
// parameters:
// -
// name: id
// type: string
// description: The id of the domain block.
// in: path
// required: true
// -
// name: obfuscate
// in: formData
// description: >-
// Obfuscate the name of the domain when serving it publicly.
// Eg., `example.org` becomes something like `ex***e.org`.
// type: boolean
// -
// name: public_comment
// in: formData
// description: >-
// Public comment about this domain block.
// This will be displayed alongside the domain block if you choose to share blocks.
// type: string
// -
// name: private_comment
// in: formData
// description: >-
// Private comment about this domain block. Will only be shown to other admins, so this
// is a useful way of internally keeping track of why a certain domain ended up blocked.
// type: string
//
// security:
// - OAuth2 Bearer:
// - admin:write:domain_blocks
//
// responses:
// '200':
// description: The updated domain block.
// schema:
// "$ref": "#/definitions/domainPermission"
// '400':
// description: bad request
// '401':
// description: unauthorized
// '403':
// description: forbidden
// '404':
// description: not found
// '406':
// description: not acceptable
// '500':
// description: internal server error
func (m *Module) DomainBlockUpdatePUTHandler(c *gin.Context) {
m.updateDomainPermission(c, gtsmodel.DomainPermissionBlock)
}

View file

@ -29,6 +29,7 @@ import (
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
) )
type singleDomainPermCreate func( type singleDomainPermCreate func(
@ -112,7 +113,7 @@ func (m *Module) createDomainPermissions(
if importing && form.Domains.Size == 0 { if importing && form.Domains.Size == 0 {
err = errors.New("import was specified but list of domains is empty") err = errors.New("import was specified but list of domains is empty")
} else if !importing && form.Domain == "" { } else if !importing && form.Domain == "" {
err = errors.New("empty domain provided") err = errors.New("no domain provided")
} }
if err != nil { if err != nil {
@ -122,14 +123,14 @@ func (m *Module) createDomainPermissions(
if !importing { if !importing {
// Single domain permission creation. // Single domain permission creation.
domainBlock, _, errWithCode := single( perm, _, errWithCode := single(
c.Request.Context(), c.Request.Context(),
permType, permType,
authed.Account, authed.Account,
form.Domain, form.Domain,
form.Obfuscate, util.PtrOrZero(form.Obfuscate),
form.PublicComment, util.PtrOrZero(form.PublicComment),
form.PrivateComment, util.PtrOrZero(form.PrivateComment),
"", // No sub ID for single perm creation. "", // No sub ID for single perm creation.
) )
@ -138,7 +139,7 @@ func (m *Module) createDomainPermissions(
return return
} }
apiutil.JSON(c, http.StatusOK, domainBlock) apiutil.JSON(c, http.StatusOK, perm)
return return
} }
@ -177,6 +178,82 @@ func (m *Module) createDomainPermissions(
apiutil.JSON(c, http.StatusOK, domainPerms) apiutil.JSON(c, http.StatusOK, domainPerms)
} }
func (m *Module) updateDomainPermission(
c *gin.Context,
permType gtsmodel.DomainPermissionType,
) {
// Scope differs based on permType.
var requireScope apiutil.Scope
if permType == gtsmodel.DomainPermissionBlock {
requireScope = apiutil.ScopeAdminWriteDomainBlocks
} else {
requireScope = apiutil.ScopeAdminWriteDomainAllows
}
authed, errWithCode := apiutil.TokenAuth(c,
true, true, true, true,
requireScope,
)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
if !*authed.User.Admin {
err := fmt.Errorf("user %s not an admin", authed.User.ID)
apiutil.ErrorHandler(c, gtserror.NewErrorForbidden(err, err.Error()), m.processor.InstanceGetV1)
return
}
if authed.Account.IsMoving() {
apiutil.ForbiddenAfterMove(c)
return
}
if _, err := apiutil.NegotiateAccept(c, apiutil.JSONAcceptHeaders...); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
return
}
permID, errWithCode := apiutil.ParseID(c.Param(apiutil.IDKey))
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
// Parse + validate form.
form := new(apimodel.DomainPermissionRequest)
if err := c.ShouldBind(form); err != nil {
apiutil.ErrorHandler(c, gtserror.NewErrorBadRequest(err, err.Error()), m.processor.InstanceGetV1)
return
}
if form.Obfuscate == nil &&
form.PrivateComment == nil &&
form.PublicComment == nil {
const errText = "empty form submitted"
errWithCode := gtserror.NewErrorBadRequest(errors.New(errText), errText)
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
perm, errWithCode := m.processor.Admin().DomainPermissionUpdate(
c.Request.Context(),
permType,
permID,
form.Obfuscate,
form.PublicComment,
form.PrivateComment,
nil, // Can't update perm sub ID this way yet.
)
if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
return
}
apiutil.JSON(c, http.StatusOK, perm)
}
// deleteDomainPermission deletes a single domain permission (block or allow). // deleteDomainPermission deletes a single domain permission (block or allow).
func (m *Module) deleteDomainPermission( func (m *Module) deleteDomainPermission(
c *gin.Context, c *gin.Context,

View file

@ -26,6 +26,7 @@ import (
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/util"
) )
// DomainPermissionDraftsPOSTHandler swagger:operation POST /api/v1/admin/domain_permission_drafts domainPermissionDraftCreate // DomainPermissionDraftsPOSTHandler swagger:operation POST /api/v1/admin/domain_permission_drafts domainPermissionDraftCreate
@ -148,9 +149,9 @@ func (m *Module) DomainPermissionDraftsPOSTHandler(c *gin.Context) {
authed.Account, authed.Account,
form.Domain, form.Domain,
permType, permType,
form.Obfuscate, util.PtrOrZero(form.Obfuscate),
form.PublicComment, util.PtrOrZero(form.PublicComment),
form.PrivateComment, util.PtrOrZero(form.PrivateComment),
) )
if errWithCode != nil { if errWithCode != nil {
apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1) apiutil.ErrorHandler(c, errWithCode, m.processor.InstanceGetV1)

View file

@ -97,14 +97,21 @@ func (suite *DomainPermissionSubscriptionTestTestSuite) TestDomainPermissionSubs
suite.Equal(`[ suite.Equal(`[
{ {
"domain": "bumfaces.net", "domain": "bumfaces.net",
"public_comment": "big jerks" "public_comment": "big jerks",
"obfuscate": false,
"private_comment": ""
}, },
{ {
"domain": "peepee.poopoo", "domain": "peepee.poopoo",
"public_comment": "harassment" "public_comment": "harassment",
"obfuscate": false,
"private_comment": ""
}, },
{ {
"domain": "nothanks.com" "domain": "nothanks.com",
"public_comment": "",
"obfuscate": false,
"private_comment": ""
} }
]`, dst.String()) ]`, dst.String())
@ -177,13 +184,22 @@ func (suite *DomainPermissionSubscriptionTestTestSuite) TestDomainPermissionSubs
// Ensure expected. // Ensure expected.
suite.Equal(`[ suite.Equal(`[
{ {
"domain": "bumfaces.net" "domain": "bumfaces.net",
"public_comment": "",
"obfuscate": false,
"private_comment": ""
}, },
{ {
"domain": "peepee.poopoo" "domain": "peepee.poopoo",
"public_comment": "",
"obfuscate": false,
"private_comment": ""
}, },
{ {
"domain": "nothanks.com" "domain": "nothanks.com",
"public_comment": "",
"obfuscate": false,
"private_comment": ""
} }
]`, dst.String()) ]`, dst.String())

View file

@ -136,7 +136,7 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetOnlySuspended() {
{ {
"domain": "replyguys.com", "domain": "replyguys.com",
"suspended_at": "2020-05-13T13:29:12.000Z", "suspended_at": "2020-05-13T13:29:12.000Z",
"public_comment": "reply-guying to tech posts" "comment": "reply-guying to tech posts"
} }
]`, dst.String()) ]`, dst.String())
} }
@ -186,7 +186,7 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetOnlySuspendedAuthori
{ {
"domain": "replyguys.com", "domain": "replyguys.com",
"suspended_at": "2020-05-13T13:29:12.000Z", "suspended_at": "2020-05-13T13:29:12.000Z",
"public_comment": "reply-guying to tech posts" "comment": "reply-guying to tech posts"
} }
]`, dst.String()) ]`, dst.String())
} }
@ -219,7 +219,7 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetAll() {
{ {
"domain": "replyguys.com", "domain": "replyguys.com",
"suspended_at": "2020-05-13T13:29:12.000Z", "suspended_at": "2020-05-13T13:29:12.000Z",
"public_comment": "reply-guying to tech posts" "comment": "reply-guying to tech posts"
} }
]`, dst.String()) ]`, dst.String())
} }
@ -263,12 +263,12 @@ func (suite *InstancePeersGetTestSuite) TestInstancePeersGetAllWithObfuscated()
{ {
"domain": "o*g.*u**.t**.*or*t.*r**ev**", "domain": "o*g.*u**.t**.*or*t.*r**ev**",
"suspended_at": "2021-06-09T10:34:55.000Z", "suspended_at": "2021-06-09T10:34:55.000Z",
"public_comment": "just absolutely the worst, wowza" "comment": "just absolutely the worst, wowza"
}, },
{ {
"domain": "replyguys.com", "domain": "replyguys.com",
"suspended_at": "2020-05-13T13:29:12.000Z", "suspended_at": "2020-05-13T13:29:12.000Z",
"public_comment": "reply-guying to tech posts" "comment": "reply-guying to tech posts"
} }
]`, dst.String()) ]`, dst.String())
} }

View file

@ -31,7 +31,6 @@ import (
"testing" "testing"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/api/client/search" "github.com/superseriousbusiness/gotosocial/internal/api/client/search"
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
@ -1402,7 +1401,7 @@ func (suite *SearchGetTestSuite) TestSearchRemoteInstanceAccountPartial() {
FollowersURI: "http://" + theirDomain + "/users/" + theirDomain + "/followers", FollowersURI: "http://" + theirDomain + "/users/" + theirDomain + "/followers",
FollowingURI: "http://" + theirDomain + "/users/" + theirDomain + "/following", FollowingURI: "http://" + theirDomain + "/users/" + theirDomain + "/following",
FeaturedCollectionURI: "http://" + theirDomain + "/users/" + theirDomain + "/collections/featured", FeaturedCollectionURI: "http://" + theirDomain + "/users/" + theirDomain + "/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: key, PrivateKey: key,
PublicKey: &key.PublicKey, PublicKey: &key.PublicKey,
}); err != nil { }); err != nil {

View file

@ -33,8 +33,13 @@ type Domain struct {
// example: 2021-07-30T09:20:25+00:00 // example: 2021-07-30T09:20:25+00:00
SilencedAt string `json:"silenced_at,omitempty"` SilencedAt string `json:"silenced_at,omitempty"`
// If the domain is blocked, what's the publicly-stated reason for the block. // If the domain is blocked, what's the publicly-stated reason for the block.
// Alternative to `public_comment` to be used when serializing/deserializing via /api/v1/instance.
// example: they smell // example: they smell
PublicComment string `form:"public_comment" json:"public_comment,omitempty"` Comment *string `form:"comment" json:"comment,omitempty"`
// If the domain is blocked, what's the publicly-stated reason for the block.
// Alternative to `comment` to be used when serializing/deserializing NOT via /api/v1/instance.
// example: they smell
PublicComment *string `form:"public_comment" json:"public_comment,omitempty"`
} }
// DomainPermission represents a permission applied to one domain (explicit block/allow). // DomainPermission represents a permission applied to one domain (explicit block/allow).
@ -48,10 +53,10 @@ type DomainPermission struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
// Obfuscate the domain name when serving this domain permission entry publicly. // Obfuscate the domain name when serving this domain permission entry publicly.
// example: false // example: false
Obfuscate bool `json:"obfuscate,omitempty"` Obfuscate *bool `json:"obfuscate,omitempty"`
// Private comment for this permission entry, visible to this instance's admins only. // Private comment for this permission entry, visible to this instance's admins only.
// example: they are poopoo // example: they are poopoo
PrivateComment string `json:"private_comment,omitempty"` PrivateComment *string `json:"private_comment,omitempty"`
// If applicable, the ID of the subscription that caused this domain permission entry to be created. // If applicable, the ID of the subscription that caused this domain permission entry to be created.
// example: 01FBW25TF5J67JW3HFHZCSD23K // example: 01FBW25TF5J67JW3HFHZCSD23K
SubscriptionID string `json:"subscription_id,omitempty"` SubscriptionID string `json:"subscription_id,omitempty"`
@ -80,14 +85,14 @@ type DomainPermissionRequest struct {
// Obfuscate the domain name when displaying this permission entry publicly. // Obfuscate the domain name when displaying this permission entry publicly.
// Ie., instead of 'example.org' show something like 'e**mpl*.or*'. // Ie., instead of 'example.org' show something like 'e**mpl*.or*'.
// example: false // example: false
Obfuscate bool `form:"obfuscate" json:"obfuscate"` Obfuscate *bool `form:"obfuscate" json:"obfuscate"`
// Private comment for other admins on why this permission entry was created. // Private comment for other admins on why this permission entry was created.
// example: don't like 'em!!!! // example: don't like 'em!!!!
PrivateComment string `form:"private_comment" json:"private_comment"` PrivateComment *string `form:"private_comment" json:"private_comment"`
// Public comment on why this permission entry was created. // Public comment on why this permission entry was created.
// Will be visible to requesters at /api/v1/instance/peers if this endpoint is exposed. // Will be visible to requesters at /api/v1/instance/peers if this endpoint is exposed.
// example: foss dorks 😫 // example: foss dorks 😫
PublicComment string `form:"public_comment" json:"public_comment"` PublicComment *string `form:"public_comment" json:"public_comment"`
// Permission type to create (only applies to domain permission drafts, not explicit blocks and allows). // Permission type to create (only applies to domain permission drafts, not explicit blocks and allows).
PermissionType string `form:"permission_type" json:"permission_type"` PermissionType string `form:"permission_type" json:"permission_type"`
} }

View file

@ -30,7 +30,6 @@ import (
"testing" "testing"
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/ap"
apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util" apiutil "github.com/superseriousbusiness/gotosocial/internal/api/util"
"github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger" "github.com/superseriousbusiness/gotosocial/internal/api/wellknown/webfinger"
"github.com/superseriousbusiness/gotosocial/internal/cleaner" "github.com/superseriousbusiness/gotosocial/internal/cleaner"
@ -124,7 +123,7 @@ func (suite *WebfingerGetTestSuite) funkifyAccountDomain(host string, accountDom
FollowingURI: "http://" + host + "/users/new_account_domain_user/following", FollowingURI: "http://" + host + "/users/new_account_domain_user/following",
FollowersURI: "http://" + host + "/users/new_account_domain_user/followers", FollowersURI: "http://" + host + "/users/new_account_domain_user/followers",
FeaturedCollectionURI: "http://" + host + "/users/new_account_domain_user/collections/featured", FeaturedCollectionURI: "http://" + host + "/users/new_account_domain_user/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: privateKey, PrivateKey: privateKey,
PublicKey: publicKey, PublicKey: publicKey,
PublicKeyURI: "http://" + host + "/users/new_account_domain_user/main-key", PublicKeyURI: "http://" + host + "/users/new_account_domain_user/main-key",

View file

@ -306,13 +306,8 @@ func (c *Caches) initAccount() {
Indices: []structr.IndexConfig{ Indices: []structr.IndexConfig{
{Fields: "ID"}, {Fields: "ID"},
{Fields: "URI"}, {Fields: "URI"},
{Fields: "URL"},
{Fields: "Username,Domain", AllowZero: true},
{Fields: "PublicKeyURI"}, {Fields: "PublicKeyURI"},
{Fields: "InboxURI"}, {Fields: "Username,Domain", AllowZero: true},
{Fields: "OutboxURI"},
{Fields: "FollowersURI"},
{Fields: "FollowingURI"},
}, },
MaxSize: cap, MaxSize: cap,
IgnoreErr: ignoreErrors, IgnoreErr: ignoreErrors,

View file

@ -240,13 +240,12 @@ func sizeofAccount() uintptr {
DisplayName: exampleUsername, DisplayName: exampleUsername,
Note: exampleText, Note: exampleText,
NoteRaw: exampleText, NoteRaw: exampleText,
Memorial: func() *bool { ok := false; return &ok }(), MemorializedAt: exampleTime,
CreatedAt: exampleTime, CreatedAt: exampleTime,
UpdatedAt: exampleTime, UpdatedAt: exampleTime,
FetchedAt: exampleTime, FetchedAt: exampleTime,
Bot: func() *bool { ok := true; return &ok }(), Locked: util.Ptr(true),
Locked: func() *bool { ok := true; return &ok }(), Discoverable: util.Ptr(false),
Discoverable: func() *bool { ok := false; return &ok }(),
URI: exampleURI, URI: exampleURI,
URL: exampleURI, URL: exampleURI,
InboxURI: exampleURI, InboxURI: exampleURI,
@ -254,7 +253,7 @@ func sizeofAccount() uintptr {
FollowersURI: exampleURI, FollowersURI: exampleURI,
FollowingURI: exampleURI, FollowingURI: exampleURI,
FeaturedCollectionURI: exampleURI, FeaturedCollectionURI: exampleURI,
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
PublicKeyURI: exampleURI, PublicKeyURI: exampleURI,

View file

@ -27,37 +27,37 @@ import (
// Account contains functions related to account getting/setting/creation. // Account contains functions related to account getting/setting/creation.
type Account interface { type Account interface {
// GetAccountByID returns one account with the given ID, or an error if something goes wrong. // GetAccountByID returns one account with the given ID.
GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, error) GetAccountByID(ctx context.Context, id string) (*gtsmodel.Account, error)
// GetAccountsByIDs returns accounts corresponding to given IDs. // GetAccountsByIDs returns accounts corresponding to given IDs.
GetAccountsByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Account, error) GetAccountsByIDs(ctx context.Context, ids []string) ([]*gtsmodel.Account, error)
// GetAccountByURI returns one account with the given URI, or an error if something goes wrong. // GetAccountByURI returns one account with the given ActivityStreams URI.
GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.Account, error) GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.Account, error)
// GetAccountByURL returns one account with the given URL, or an error if something goes wrong. // GetOneAccountByURL returns *one* account with the given ActivityStreams URL.
GetAccountByURL(ctx context.Context, uri string) (*gtsmodel.Account, error) // If more than one account has the given url, ErrMultipleEntries will be returned.
GetOneAccountByURL(ctx context.Context, url string) (*gtsmodel.Account, error)
// GetAccountByUsernameDomain returns one account with the given username and domain, or an error if something goes wrong. // GetAccountsByURL returns accounts with the given ActivityStreams URL.
GetAccountsByURL(ctx context.Context, url string) ([]*gtsmodel.Account, error)
// GetAccountByUsernameDomain returns one account with the given username and domain.
GetAccountByUsernameDomain(ctx context.Context, username string, domain string) (*gtsmodel.Account, error) GetAccountByUsernameDomain(ctx context.Context, username string, domain string) (*gtsmodel.Account, error)
// GetAccountByPubkeyID returns one account with the given public key URI (ID), or an error if something goes wrong. // GetAccountByPubkeyID returns one account with the given public key URI (ID).
GetAccountByPubkeyID(ctx context.Context, id string) (*gtsmodel.Account, error) GetAccountByPubkeyID(ctx context.Context, id string) (*gtsmodel.Account, error)
// GetAccountByInboxURI returns one account with the given inbox_uri, or an error if something goes wrong. // GetOneAccountByInboxURI returns one account with the given inbox_uri.
GetAccountByInboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) // If more than one account has the given URL, ErrMultipleEntries will be returned.
GetOneAccountByInboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error)
// GetAccountByOutboxURI returns one account with the given outbox_uri, or an error if something goes wrong. // GetOneAccountByOutboxURI returns one account with the given outbox_uri.
GetAccountByOutboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) // If more than one account has the given uri, ErrMultipleEntries will be returned.
GetOneAccountByOutboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error)
// GetAccountByFollowingURI returns one account with the given following_uri, or an error if something goes wrong. // GetAccountsByMovedToURI returns any accounts with given moved_to_uri set.
GetAccountByFollowingURI(ctx context.Context, uri string) (*gtsmodel.Account, error)
// GetAccountByFollowersURI returns one account with the given followers_uri, or an error if something goes wrong.
GetAccountByFollowersURI(ctx context.Context, uri string) (*gtsmodel.Account, error)
// GetAccountByMovedToURI returns any accounts with given moved_to_uri set.
GetAccountsByMovedToURI(ctx context.Context, uri string) ([]*gtsmodel.Account, error) GetAccountsByMovedToURI(ctx context.Context, uri string) ([]*gtsmodel.Account, error)
// GetAccounts returns accounts // GetAccounts returns accounts

View file

@ -121,18 +121,46 @@ func (a *accountDB) GetAccountByURI(ctx context.Context, uri string) (*gtsmodel.
) )
} }
func (a *accountDB) GetAccountByURL(ctx context.Context, url string) (*gtsmodel.Account, error) { func (a *accountDB) GetOneAccountByURL(ctx context.Context, url string) (*gtsmodel.Account, error) {
return a.getAccount( // Select IDs of all
ctx, // accounts with this url.
"URL", var ids []string
func(account *gtsmodel.Account) error { if err := a.db.NewSelect().
return a.db.NewSelect(). TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")).
Model(account). Column("account.id").
Where("? = ?", bun.Ident("account.url"), url). Where("? = ?", bun.Ident("account.url"), url).
Scan(ctx) Scan(ctx, &ids); err != nil {
}, return nil, err
url, }
)
// Ensure exactly one account.
if len(ids) == 0 {
return nil, db.ErrNoEntries
}
if len(ids) > 1 {
return nil, db.ErrMultipleEntries
}
return a.GetAccountByID(ctx, ids[0])
}
func (a *accountDB) GetAccountsByURL(ctx context.Context, url string) ([]*gtsmodel.Account, error) {
// Select IDs of all
// accounts with this url.
var ids []string
if err := a.db.NewSelect().
TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")).
Column("account.id").
Where("? = ?", bun.Ident("account.url"), url).
Scan(ctx, &ids); err != nil {
return nil, err
}
if len(ids) == 0 {
return nil, db.ErrNoEntries
}
return a.GetAccountsByIDs(ctx, ids)
} }
func (a *accountDB) GetAccountByUsernameDomain(ctx context.Context, username string, domain string) (*gtsmodel.Account, error) { func (a *accountDB) GetAccountByUsernameDomain(ctx context.Context, username string, domain string) (*gtsmodel.Account, error) {
@ -184,60 +212,50 @@ func (a *accountDB) GetAccountByPubkeyID(ctx context.Context, id string) (*gtsmo
) )
} }
func (a *accountDB) GetAccountByInboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { func (a *accountDB) GetOneAccountByInboxURI(ctx context.Context, inboxURI string) (*gtsmodel.Account, error) {
return a.getAccount( // Select IDs of all accounts
ctx, // with this inbox_uri.
"InboxURI", var ids []string
func(account *gtsmodel.Account) error { if err := a.db.NewSelect().
return a.db.NewSelect(). TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")).
Model(account). Column("account.id").
Where("? = ?", bun.Ident("account.inbox_uri"), uri). Where("? = ?", bun.Ident("account.inbox_uri"), inboxURI).
Scan(ctx) Scan(ctx, &ids); err != nil {
}, return nil, err
uri,
)
} }
func (a *accountDB) GetAccountByOutboxURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { // Ensure exactly one account.
return a.getAccount( if len(ids) == 0 {
ctx, return nil, db.ErrNoEntries
"OutboxURI", }
func(account *gtsmodel.Account) error { if len(ids) > 1 {
return a.db.NewSelect(). return nil, db.ErrMultipleEntries
Model(account).
Where("? = ?", bun.Ident("account.outbox_uri"), uri).
Scan(ctx)
},
uri,
)
} }
func (a *accountDB) GetAccountByFollowersURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { return a.GetAccountByID(ctx, ids[0])
return a.getAccount(
ctx,
"FollowersURI",
func(account *gtsmodel.Account) error {
return a.db.NewSelect().
Model(account).
Where("? = ?", bun.Ident("account.followers_uri"), uri).
Scan(ctx)
},
uri,
)
} }
func (a *accountDB) GetAccountByFollowingURI(ctx context.Context, uri string) (*gtsmodel.Account, error) { func (a *accountDB) GetOneAccountByOutboxURI(ctx context.Context, outboxURI string) (*gtsmodel.Account, error) {
return a.getAccount( // Select IDs of all accounts
ctx, // with this outbox_uri.
"FollowingURI", var ids []string
func(account *gtsmodel.Account) error { if err := a.db.NewSelect().
return a.db.NewSelect(). TableExpr("? AS ?", bun.Ident("accounts"), bun.Ident("account")).
Model(account). Column("account.id").
Where("? = ?", bun.Ident("account.following_uri"), uri). Where("? = ?", bun.Ident("account.outbox_uri"), outboxURI).
Scan(ctx) Scan(ctx, &ids); err != nil {
}, return nil, err
uri, }
)
// Ensure exactly one account.
if len(ids) == 0 {
return nil, db.ErrNoEntries
}
if len(ids) > 1 {
return nil, db.ErrMultipleEntries
}
return a.GetAccountByID(ctx, ids[0])
} }
func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gtsmodel.Account, error) { func (a *accountDB) GetInstanceAccount(ctx context.Context, domain string) (*gtsmodel.Account, error) {
@ -587,7 +605,11 @@ func (a *accountDB) GetAccounts(
return a.state.DB.GetAccountsByIDs(ctx, accountIDs) return a.state.DB.GetAccountsByIDs(ctx, accountIDs)
} }
func (a *accountDB) getAccount(ctx context.Context, lookup string, dbQuery func(*gtsmodel.Account) error, keyParts ...any) (*gtsmodel.Account, error) { func (a *accountDB) getAccount(
ctx context.Context,
lookup string,
dbQuery func(*gtsmodel.Account) error, keyParts ...any,
) (*gtsmodel.Account, error) {
// Fetch account from database cache with loader callback // Fetch account from database cache with loader callback
account, err := a.state.Caches.DB.Account.LoadOne(lookup, func() (*gtsmodel.Account, error) { account, err := a.state.Caches.DB.Account.LoadOne(lookup, func() (*gtsmodel.Account, error) {
var account gtsmodel.Account var account gtsmodel.Account

View file

@ -32,11 +32,10 @@ import (
"github.com/stretchr/testify/suite" "github.com/stretchr/testify/suite"
"github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb" "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/paging" "github.com/superseriousbusiness/gotosocial/internal/paging"
"github.com/superseriousbusiness/gotosocial/internal/util" "github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/uptrace/bun"
) )
type AccountTestSuite struct { type AccountTestSuite struct {
@ -255,7 +254,20 @@ func (suite *AccountTestSuite) TestGetAccountBy() {
if account.URL == "" { if account.URL == "" {
return nil, sentinelErr return nil, sentinelErr
} }
return suite.db.GetAccountByURL(ctx, account.URL) return suite.db.GetOneAccountByURL(ctx, account.URL)
},
"url_multi": func() (*gtsmodel.Account, error) {
if account.URL == "" {
return nil, sentinelErr
}
accounts, err := suite.db.GetAccountsByURL(ctx, account.URL)
if err != nil {
return nil, err
}
return accounts[0], nil
}, },
"username@domain": func() (*gtsmodel.Account, error) { "username@domain": func() (*gtsmodel.Account, error) {
@ -281,28 +293,14 @@ func (suite *AccountTestSuite) TestGetAccountBy() {
if account.InboxURI == "" { if account.InboxURI == "" {
return nil, sentinelErr return nil, sentinelErr
} }
return suite.db.GetAccountByInboxURI(ctx, account.InboxURI) return suite.db.GetOneAccountByInboxURI(ctx, account.InboxURI)
}, },
"outbox_uri": func() (*gtsmodel.Account, error) { "outbox_uri": func() (*gtsmodel.Account, error) {
if account.OutboxURI == "" { if account.OutboxURI == "" {
return nil, sentinelErr return nil, sentinelErr
} }
return suite.db.GetAccountByOutboxURI(ctx, account.OutboxURI) return suite.db.GetOneAccountByOutboxURI(ctx, account.OutboxURI)
},
"following_uri": func() (*gtsmodel.Account, error) {
if account.FollowingURI == "" {
return nil, sentinelErr
}
return suite.db.GetAccountByFollowingURI(ctx, account.FollowingURI)
},
"followers_uri": func() (*gtsmodel.Account, error) {
if account.FollowersURI == "" {
return nil, sentinelErr
}
return suite.db.GetAccountByFollowersURI(ctx, account.FollowersURI)
}, },
} { } {
@ -345,71 +343,37 @@ func (suite *AccountTestSuite) TestGetAccountBy() {
} }
} }
func (suite *AccountTestSuite) TestUpdateAccount() { func (suite *AccountTestSuite) TestGetAccountsByURLMulti() {
ctx := context.Background() ctx := context.Background()
testAccount := suite.testAccounts["local_account_1"] // Update admin account to have the same url as zork.
testAccount1 := suite.testAccounts["local_account_1"]
testAccount.DisplayName = "new display name!" testAccount2 := new(gtsmodel.Account)
testAccount.EmojiIDs = []string{"01GD36ZKWTKY3T1JJ24JR7KY1Q", "01GD36ZV904SHBHNAYV6DX5QEF"} *testAccount2 = *suite.testAccounts["admin_account"]
testAccount2.URL = testAccount1.URL
err := suite.db.UpdateAccount(ctx, testAccount) if err := suite.state.DB.UpdateAccount(ctx, testAccount2, "url"); err != nil {
suite.NoError(err) suite.FailNow(err.Error())
updated, err := suite.db.GetAccountByID(ctx, testAccount.ID)
suite.NoError(err)
suite.Equal("new display name!", updated.DisplayName)
suite.Equal([]string{"01GD36ZKWTKY3T1JJ24JR7KY1Q", "01GD36ZV904SHBHNAYV6DX5QEF"}, updated.EmojiIDs)
suite.WithinDuration(time.Now(), updated.UpdatedAt, 5*time.Second)
// get account without cache + make sure it's really in the db as desired
dbService, ok := suite.db.(*bundb.DBService)
if !ok {
panic("db was not *bundb.DBService")
} }
noCache := &gtsmodel.Account{} // Select all accounts with that URL.
err = dbService.DB(). // Should return 2.
NewSelect(). accounts, err := suite.state.DB.GetAccountsByURL(
Model(noCache). gtscontext.SetBarebones(ctx),
Where("? = ?", bun.Ident("account.id"), testAccount.ID). testAccount1.URL,
Relation("AvatarMediaAttachment"). )
Relation("HeaderMediaAttachment"). if err != nil {
Relation("Emojis"). suite.FailNow(err.Error())
Scan(ctx) }
suite.Len(accounts, 2)
suite.NoError(err) // Try to select one account with that URL.
suite.Equal("new display name!", noCache.DisplayName) // Should error.
suite.Equal([]string{"01GD36ZKWTKY3T1JJ24JR7KY1Q", "01GD36ZV904SHBHNAYV6DX5QEF"}, noCache.EmojiIDs) account, err := suite.state.DB.GetOneAccountByURL(
suite.WithinDuration(time.Now(), noCache.UpdatedAt, 5*time.Second) gtscontext.SetBarebones(ctx),
suite.NotNil(noCache.AvatarMediaAttachment) testAccount1.URL,
suite.NotNil(noCache.HeaderMediaAttachment) )
suite.Nil(account)
// update again to remove emoji associations suite.ErrorIs(err, db.ErrMultipleEntries)
testAccount.EmojiIDs = []string{}
err = suite.db.UpdateAccount(ctx, testAccount)
suite.NoError(err)
updated, err = suite.db.GetAccountByID(ctx, testAccount.ID)
suite.NoError(err)
suite.Equal("new display name!", updated.DisplayName)
suite.Empty(updated.EmojiIDs)
suite.WithinDuration(time.Now(), updated.UpdatedAt, 5*time.Second)
err = dbService.DB().
NewSelect().
Model(noCache).
Where("? = ?", bun.Ident("account.id"), testAccount.ID).
Relation("AvatarMediaAttachment").
Relation("HeaderMediaAttachment").
Relation("Emojis").
Scan(ctx)
suite.NoError(err)
suite.Equal("new display name!", noCache.DisplayName)
suite.Empty(noCache.EmojiIDs)
suite.WithinDuration(time.Now(), noCache.UpdatedAt, 5*time.Second)
} }
func (suite *AccountTestSuite) TestInsertAccountWithDefaults() { func (suite *AccountTestSuite) TestInsertAccountWithDefaults() {
@ -422,7 +386,7 @@ func (suite *AccountTestSuite) TestInsertAccountWithDefaults() {
Domain: "example.org", Domain: "example.org",
URI: "https://example.org/users/test_service", URI: "https://example.org/users/test_service",
URL: "https://example.org/@test_service", URL: "https://example.org/@test_service",
ActorType: ap.ActorService, ActorType: gtsmodel.AccountActorTypeService,
PublicKey: &key.PublicKey, PublicKey: &key.PublicKey,
PublicKeyURI: "https://example.org/users/test_service#main-key", PublicKeyURI: "https://example.org/users/test_service#main-key",
} }
@ -433,7 +397,6 @@ func (suite *AccountTestSuite) TestInsertAccountWithDefaults() {
suite.WithinDuration(time.Now(), newAccount.CreatedAt, 30*time.Second) suite.WithinDuration(time.Now(), newAccount.CreatedAt, 30*time.Second)
suite.WithinDuration(time.Now(), newAccount.UpdatedAt, 30*time.Second) suite.WithinDuration(time.Now(), newAccount.UpdatedAt, 30*time.Second)
suite.True(*newAccount.Locked) suite.True(*newAccount.Locked)
suite.False(*newAccount.Bot)
suite.False(*newAccount.Discoverable) suite.False(*newAccount.Discoverable)
} }

View file

@ -28,7 +28,6 @@ import (
"time" "time"
"github.com/google/uuid" "github.com/google/uuid"
"github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
@ -131,7 +130,7 @@ func (a *adminDB) NewSignup(ctx context.Context, newSignup gtsmodel.NewSignup) (
FollowingURI: uris.FollowingURI, FollowingURI: uris.FollowingURI,
FollowersURI: uris.FollowersURI, FollowersURI: uris.FollowersURI,
FeaturedCollectionURI: uris.FeaturedCollectionURI, FeaturedCollectionURI: uris.FeaturedCollectionURI,
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: privKey, PrivateKey: privKey,
PublicKey: &privKey.PublicKey, PublicKey: &privKey.PublicKey,
PublicKeyURI: uris.PublicKeyURI, PublicKeyURI: uris.PublicKeyURI,
@ -283,7 +282,7 @@ func (a *adminDB) CreateInstanceAccount(ctx context.Context) error {
PrivateKey: key, PrivateKey: key,
PublicKey: &key.PublicKey, PublicKey: &key.PublicKey,
PublicKeyURI: newAccountURIs.PublicKeyURI, PublicKeyURI: newAccountURIs.PublicKeyURI,
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypeService,
URI: newAccountURIs.UserURI, URI: newAccountURIs.UserURI,
InboxURI: newAccountURIs.InboxURI, InboxURI: newAccountURIs.InboxURI,
OutboxURI: newAccountURIs.OutboxURI, OutboxURI: newAccountURIs.OutboxURI,

View file

@ -55,7 +55,7 @@ func (suite *BasicTestSuite) TestPutAccountWithBunDefaultFields() {
URL: "https://example.org/@test", URL: "https://example.org/@test",
InboxURI: "https://example.org/users/test/inbox", InboxURI: "https://example.org/users/test/inbox",
OutboxURI: "https://example.org/users/test/outbox", OutboxURI: "https://example.org/users/test/outbox",
ActorType: "Person", ActorType: gtsmodel.AccountActorTypePerson,
PublicKeyURI: "https://example.org/test#main-key", PublicKeyURI: "https://example.org/test#main-key",
PublicKey: &key.PublicKey, PublicKey: &key.PublicKey,
} }
@ -87,7 +87,6 @@ func (suite *BasicTestSuite) TestPutAccountWithBunDefaultFields() {
suite.Empty(a.NoteRaw) suite.Empty(a.NoteRaw)
suite.Empty(a.AlsoKnownAsURIs) suite.Empty(a.AlsoKnownAsURIs)
suite.Empty(a.MovedToURI) suite.Empty(a.MovedToURI)
suite.False(*a.Bot)
// Locked is especially important, since it's a bool that defaults // Locked is especially important, since it's a bool that defaults
// to true, which is why we use pointers for bools in the first place // to true, which is why we use pointers for bools in the first place
suite.True(*a.Locked) suite.True(*a.Locked)

View file

@ -36,7 +36,7 @@ type domainDB struct {
state *state.State state *state.State
} }
func (d *domainDB) CreateDomainAllow(ctx context.Context, allow *gtsmodel.DomainAllow) (err error) { func (d *domainDB) PutDomainAllow(ctx context.Context, allow *gtsmodel.DomainAllow) (err error) {
// Normalize the domain as punycode, note the extra // Normalize the domain as punycode, note the extra
// validation step for domain name write operations. // validation step for domain name write operations.
allow.Domain, err = util.PunifySafely(allow.Domain) allow.Domain, err = util.PunifySafely(allow.Domain)
@ -162,7 +162,7 @@ func (d *domainDB) DeleteDomainAllow(ctx context.Context, domain string) error {
return nil return nil
} }
func (d *domainDB) CreateDomainBlock(ctx context.Context, block *gtsmodel.DomainBlock) error { func (d *domainDB) PutDomainBlock(ctx context.Context, block *gtsmodel.DomainBlock) error {
var err error var err error
// Normalize the domain as punycode, note the extra // Normalize the domain as punycode, note the extra

View file

@ -46,7 +46,7 @@ func (suite *DomainTestSuite) TestIsDomainBlocked() {
suite.NoError(err) suite.NoError(err)
suite.False(blocked) suite.False(blocked)
err = suite.db.CreateDomainBlock(ctx, domainBlock) err = suite.db.PutDomainBlock(ctx, domainBlock)
suite.NoError(err) suite.NoError(err)
// domain block now exists // domain block now exists
@ -75,7 +75,7 @@ func (suite *DomainTestSuite) TestIsDomainBlockedWithAllow() {
suite.False(blocked) suite.False(blocked)
// Block this domain. // Block this domain.
if err := suite.db.CreateDomainBlock(ctx, domainBlock); err != nil { if err := suite.db.PutDomainBlock(ctx, domainBlock); err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }
@ -96,7 +96,7 @@ func (suite *DomainTestSuite) TestIsDomainBlockedWithAllow() {
CreatedByAccount: suite.testAccounts["admin_account"], CreatedByAccount: suite.testAccounts["admin_account"],
} }
if err := suite.db.CreateDomainAllow(ctx, domainAllow); err != nil { if err := suite.db.PutDomainAllow(ctx, domainAllow); err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }
@ -124,7 +124,7 @@ func (suite *DomainTestSuite) TestIsDomainBlockedWildcard() {
suite.NoError(err) suite.NoError(err)
suite.False(blocked) suite.False(blocked)
err = suite.db.CreateDomainBlock(ctx, domainBlock) err = suite.db.PutDomainBlock(ctx, domainBlock)
suite.NoError(err) suite.NoError(err)
// Start with the base block domain // Start with the base block domain
@ -164,7 +164,7 @@ func (suite *DomainTestSuite) TestIsDomainBlockedNonASCII() {
suite.NoError(err) suite.NoError(err)
suite.False(blocked) suite.False(blocked)
err = suite.db.CreateDomainBlock(ctx, domainBlock) err = suite.db.PutDomainBlock(ctx, domainBlock)
suite.NoError(err) suite.NoError(err)
// domain block now exists // domain block now exists
@ -200,7 +200,7 @@ func (suite *DomainTestSuite) TestIsDomainBlockedNonASCII2() {
suite.NoError(err) suite.NoError(err)
suite.False(blocked) suite.False(blocked)
err = suite.db.CreateDomainBlock(ctx, domainBlock) err = suite.db.PutDomainBlock(ctx, domainBlock)
suite.NoError(err) suite.NoError(err)
// domain block now exists // domain block now exists
@ -232,7 +232,7 @@ func (suite *DomainTestSuite) TestIsOtherDomainBlockedWildcardAndExplicit() {
} }
for _, block := range blocks { for _, block := range blocks {
if err := suite.db.CreateDomainBlock(ctx, block); err != nil { if err := suite.db.PutDomainBlock(ctx, block); err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }
} }

View file

@ -80,7 +80,7 @@ func (suite *DomainPermissionSubscriptionTestSuite) TestCount() {
// Whack the perms in the db. // Whack the perms in the db.
for _, perm := range perms { for _, perm := range perms {
if err := suite.state.DB.CreateDomainBlock(ctx, perm); err != nil { if err := suite.state.DB.PutDomainBlock(ctx, perm); err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }
} }

View file

@ -81,12 +81,13 @@ func init() {
return err return err
} }
// Set instance app // Set instance app ID on
// ID on all users. // users where it's null.
if _, err := tx. if _, err := tx.
NewUpdate(). NewUpdate().
Table("users"). Table("users").
Set("? = ?", bun.Ident("created_by_application_id"), instanceAppID). Set("? = ?", bun.Ident("created_by_application_id"), instanceAppID).
Where("? IS NULL", bun.Ident("created_by_application_id")).
Exec(ctx); err != nil { Exec(ctx); err != nil {
return err return err
} }

View file

@ -0,0 +1,398 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package migrations
import (
"context"
"errors"
"fmt"
"strings"
"github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
new_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20250321131230_relax_account_uri_uniqueness/new"
old_gtsmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20250321131230_relax_account_uri_uniqueness/old"
"github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/uptrace/bun"
"github.com/uptrace/bun/dialect"
)
func init() {
up := func(ctx context.Context, bdb *bun.DB) error {
log.Info(ctx, "converting accounts to new model; this may take a while, please don't interrupt!")
return bdb.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
var (
// We have to use different
// syntax for this query
// depending on dialect.
dbDialect = tx.Dialect().Name()
// ID for paging.
maxID string
// Batch size for
// selecting + updating.
batchsz = 100
// Number of accounts
// updated so far.
updated int
// We need to know our own host
// for updating instance account.
host = config.GetHost()
)
// Create the new accounts table.
if _, err := tx.
NewCreateTable().
ModelTableExpr("new_accounts").
Model(&new_gtsmodel.Account{}).
Exec(ctx); err != nil {
return err
}
// Count number of accounts
// we need to update.
total, err := tx.
NewSelect().
Table("accounts").
Count(ctx)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return err
}
// Create a subquery for
// Postgres to reuse.
var orderQPG *bun.RawQuery
if dbDialect == dialect.PG {
orderQPG = tx.NewRaw(
"(COALESCE(?, ?) || ? || ?) COLLATE ?",
bun.Ident("domain"), "",
"/@",
bun.Ident("username"),
bun.Ident("C"),
)
}
var orderQSqlite *bun.RawQuery
if dbDialect == dialect.SQLite {
orderQSqlite = tx.NewRaw(
"(COALESCE(?, ?) || ? || ?)",
bun.Ident("domain"), "",
"/@",
bun.Ident("username"),
)
}
for {
// Batch of old model account IDs to select.
oldAccountIDs := make([]string, 0, batchsz)
// Start building IDs query.
idsQ := tx.
NewSelect().
Table("accounts").
Column("id").
Limit(batchsz)
if dbDialect == dialect.SQLite {
// For SQLite we can just select
// our indexed expression once
// as a column alias.
idsQ = idsQ.
ColumnExpr(
"(COALESCE(?, ?) || ? || ?) AS ?",
bun.Ident("domain"), "",
"/@",
bun.Ident("username"),
bun.Ident("domain_username"),
)
}
// Return only accounts with `[domain]/@[username]`
// later in the alphabet (a-z) than provided maxID.
if maxID != "" {
if dbDialect == dialect.SQLite {
idsQ = idsQ.Where("? > ?", bun.Ident("domain_username"), maxID)
} else {
idsQ = idsQ.Where("? > ?", orderQPG, maxID)
}
}
// Page down.
// It's counterintuitive because it
// says ASC in the query, but we're
// going forwards in the alphabet,
// and z > a in a string comparison.
if dbDialect == dialect.SQLite {
idsQ = idsQ.OrderExpr("? ASC", bun.Ident("domain_username"))
} else {
idsQ = idsQ.OrderExpr("? ASC", orderQPG)
}
// Select this batch, providing a
// slice to throw away username_domain.
err := idsQ.Scan(ctx, &oldAccountIDs, new([]string))
if err != nil {
return err
}
l := len(oldAccountIDs)
if len(oldAccountIDs) == 0 {
// Nothing left
// to update.
break
}
// Get ready to select old accounts by their IDs.
oldAccounts := make([]*old_gtsmodel.Account, 0, l)
batchQ := tx.
NewSelect().
Model(&oldAccounts).
Where("? IN (?)", bun.Ident("id"), bun.In(oldAccountIDs))
// Order batch by usernameDomain
// to ensure paging consistent.
if dbDialect == dialect.SQLite {
batchQ = batchQ.OrderExpr("? ASC", orderQSqlite)
} else {
batchQ = batchQ.OrderExpr("? ASC", orderQPG)
}
// Select old accounts.
if err := batchQ.Scan(ctx); err != nil {
return err
}
// Convert old accounts into new accounts.
newAccounts := make([]*new_gtsmodel.Account, 0, l)
for _, oldAccount := range oldAccounts {
var actorType new_gtsmodel.AccountActorType
if oldAccount.Domain == "" && oldAccount.Username == host {
// This is our instance account, override actor
// type to Service, as previously it was just person.
actorType = new_gtsmodel.AccountActorTypeService
} else {
// Not our instance account, just parse new actor type.
actorType = new_gtsmodel.ParseAccountActorType(oldAccount.ActorType)
}
if actorType == new_gtsmodel.AccountActorTypeUnknown {
// This should not really happen, but it if does
// just warn + set to person rather than failing.
log.Warnf(ctx,
"account %s actor type %s was not a recognized actor type, falling back to Person",
oldAccount.ID, oldAccount.ActorType,
)
actorType = new_gtsmodel.AccountActorTypePerson
}
newAccount := &new_gtsmodel.Account{
ID: oldAccount.ID,
CreatedAt: oldAccount.CreatedAt,
UpdatedAt: oldAccount.UpdatedAt,
FetchedAt: oldAccount.FetchedAt,
Username: oldAccount.Username,
Domain: oldAccount.Domain,
AvatarMediaAttachmentID: oldAccount.AvatarMediaAttachmentID,
AvatarRemoteURL: oldAccount.AvatarRemoteURL,
HeaderMediaAttachmentID: oldAccount.HeaderMediaAttachmentID,
HeaderRemoteURL: oldAccount.HeaderRemoteURL,
DisplayName: oldAccount.DisplayName,
EmojiIDs: oldAccount.EmojiIDs,
Fields: oldAccount.Fields,
FieldsRaw: oldAccount.FieldsRaw,
Note: oldAccount.Note,
NoteRaw: oldAccount.NoteRaw,
AlsoKnownAsURIs: oldAccount.AlsoKnownAsURIs,
MovedToURI: oldAccount.MovedToURI,
MoveID: oldAccount.MoveID,
Locked: oldAccount.Locked,
Discoverable: oldAccount.Discoverable,
URI: oldAccount.URI,
URL: oldAccount.URL,
InboxURI: oldAccount.InboxURI,
SharedInboxURI: oldAccount.SharedInboxURI,
OutboxURI: oldAccount.OutboxURI,
FollowingURI: oldAccount.FollowingURI,
FollowersURI: oldAccount.FollowersURI,
FeaturedCollectionURI: oldAccount.FeaturedCollectionURI,
ActorType: actorType,
PrivateKey: oldAccount.PrivateKey,
PublicKey: oldAccount.PublicKey,
PublicKeyURI: oldAccount.PublicKeyURI,
PublicKeyExpiresAt: oldAccount.PublicKeyExpiresAt,
SensitizedAt: oldAccount.SensitizedAt,
SilencedAt: oldAccount.SilencedAt,
SuspendedAt: oldAccount.SuspendedAt,
SuspensionOrigin: oldAccount.SuspensionOrigin,
}
newAccounts = append(newAccounts, newAccount)
}
// Insert this batch of accounts.
res, err := tx.
NewInsert().
Model(&newAccounts).
Returning("").
Exec(ctx)
if err != nil {
return err
}
rowsAffected, err := res.RowsAffected()
if err != nil {
return err
}
// Add to updated count.
updated += int(rowsAffected)
if updated == total {
// Done.
break
}
// Set next page.
fromAcct := oldAccounts[l-1]
maxID = fromAcct.Domain + "/@" + fromAcct.Username
// Log helpful message to admin.
log.Infof(ctx,
"migrated %d of %d accounts (next page will be from %s)",
updated, total, maxID,
)
}
if total != int(updated) {
// Return error here in order to rollback the whole transaction.
return fmt.Errorf("total=%d does not match updated=%d", total, updated)
}
log.Infof(ctx, "finished migrating %d accounts", total)
// Drop the old table.
log.Info(ctx, "dropping old accounts table")
if _, err := tx.
NewDropTable().
Table("accounts").
Exec(ctx); err != nil {
return err
}
// Rename new table to old table.
log.Info(ctx, "renaming new accounts table")
if _, err := tx.
ExecContext(
ctx,
"ALTER TABLE ? RENAME TO ?",
bun.Ident("new_accounts"),
bun.Ident("accounts"),
); err != nil {
return err
}
// Add all account indexes to the new table.
log.Info(ctx, "recreating indexes on new accounts table")
for index, columns := range map[string][]string{
"accounts_domain_idx": {"domain"},
"accounts_uri_idx": {"uri"},
"accounts_url_idx": {"url"},
"accounts_inbox_uri_idx": {"inbox_uri"},
"accounts_outbox_uri_idx": {"outbox_uri"},
"accounts_followers_uri_idx": {"followers_uri"},
"accounts_following_uri_idx": {"following_uri"},
} {
if _, err := tx.
NewCreateIndex().
Table("accounts").
Index(index).
Column(columns...).
Exec(ctx); err != nil {
return err
}
}
if dbDialect == dialect.PG {
log.Info(ctx, "moving postgres constraints from old table to new table")
type spec struct {
old string
new string
columns []string
}
// Rename uniqueness constraints from
// "new_accounts_*" to "accounts_*".
for _, spec := range []spec{
{
old: "new_accounts_pkey",
new: "accounts_pkey",
columns: []string{"id"},
},
{
old: "new_accounts_uri_key",
new: "accounts_uri_key",
columns: []string{"uri"},
},
{
old: "new_accounts_public_key_uri_key",
new: "accounts_public_key_uri_key",
columns: []string{"public_key_uri"},
},
} {
if _, err := tx.ExecContext(
ctx,
"ALTER TABLE ? DROP CONSTRAINT IF EXISTS ?",
bun.Ident("public.accounts"),
bun.Safe(spec.old),
); err != nil {
return err
}
if _, err := tx.ExecContext(
ctx,
"ALTER TABLE ? ADD CONSTRAINT ? UNIQUE(?)",
bun.Ident("public.accounts"),
bun.Safe(spec.new),
bun.Safe(strings.Join(spec.columns, ",")),
); err != nil {
return err
}
}
}
return nil
})
}
down := func(ctx context.Context, db *bun.DB) error {
return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error {
return nil
})
}
if err := Migrations.Register(up, down); err != nil {
panic(err)
}
}

View file

@ -0,0 +1,26 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package common
import "time"
type Field struct {
Name string
Value string
VerifiedAt time.Time `bun:",nullzero"`
}

View file

@ -0,0 +1,98 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package gtsmodel
import (
"crypto/rsa"
"strings"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20250321131230_relax_account_uri_uniqueness/common"
"github.com/uptrace/bun"
)
type Account struct {
bun.BaseModel `bun:"table:new_accounts"`
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
FetchedAt time.Time `bun:"type:timestamptz,nullzero"`
Username string `bun:",nullzero,notnull,unique:accounts_username_domain_uniq"`
Domain string `bun:",nullzero,unique:accounts_username_domain_uniq"`
AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"`
AvatarRemoteURL string `bun:",nullzero"`
HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"`
HeaderRemoteURL string `bun:",nullzero"`
DisplayName string `bun:",nullzero"`
EmojiIDs []string `bun:"emojis,array"`
Fields []*common.Field `bun:",nullzero"`
FieldsRaw []*common.Field `bun:",nullzero"`
Note string `bun:",nullzero"`
NoteRaw string `bun:",nullzero"`
MemorializedAt time.Time `bun:"type:timestamptz,nullzero"`
AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"`
MovedToURI string `bun:",nullzero"`
MoveID string `bun:"type:CHAR(26),nullzero"`
Locked *bool `bun:",nullzero,notnull,default:true"`
Discoverable *bool `bun:",nullzero,notnull,default:false"`
URI string `bun:",nullzero,notnull,unique"`
URL string `bun:",nullzero"`
InboxURI string `bun:",nullzero"`
SharedInboxURI *string `bun:""`
OutboxURI string `bun:",nullzero"`
FollowingURI string `bun:",nullzero"`
FollowersURI string `bun:",nullzero"`
FeaturedCollectionURI string `bun:",nullzero"`
ActorType AccountActorType `bun:",nullzero,notnull"`
PrivateKey *rsa.PrivateKey `bun:""`
PublicKey *rsa.PublicKey `bun:",notnull"`
PublicKeyURI string `bun:",nullzero,notnull,unique"`
PublicKeyExpiresAt time.Time `bun:"type:timestamptz,nullzero"`
SensitizedAt time.Time `bun:"type:timestamptz,nullzero"`
SilencedAt time.Time `bun:"type:timestamptz,nullzero"`
SuspendedAt time.Time `bun:"type:timestamptz,nullzero"`
SuspensionOrigin string `bun:"type:CHAR(26),nullzero"`
}
type AccountActorType int16
const (
AccountActorTypeUnknown AccountActorType = 0
AccountActorTypeApplication AccountActorType = 1 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application
AccountActorTypeGroup AccountActorType = 2 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group
AccountActorTypeOrganization AccountActorType = 3 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-organization
AccountActorTypePerson AccountActorType = 4 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person
AccountActorTypeService AccountActorType = 5 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service
)
func ParseAccountActorType(in string) AccountActorType {
switch strings.ToLower(in) {
case "application":
return AccountActorTypeApplication
case "group":
return AccountActorTypeGroup
case "organization":
return AccountActorTypeOrganization
case "person":
return AccountActorTypePerson
case "service":
return AccountActorTypeService
default:
return AccountActorTypeUnknown
}
}

View file

@ -0,0 +1,70 @@
// GoToSocial
// Copyright (C) GoToSocial Authors admin@gotosocial.org
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
package gtsmodel
import (
"crypto/rsa"
"time"
"github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20250321131230_relax_account_uri_uniqueness/common"
"github.com/uptrace/bun"
)
type Account struct {
bun.BaseModel `bun:"table:accounts"`
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
FetchedAt time.Time `bun:"type:timestamptz,nullzero"`
Username string `bun:",nullzero,notnull,unique:usernamedomain"`
Domain string `bun:",nullzero,unique:usernamedomain"`
AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"`
AvatarRemoteURL string `bun:",nullzero"`
HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"`
HeaderRemoteURL string `bun:",nullzero"`
DisplayName string `bun:""`
EmojiIDs []string `bun:"emojis,array"`
Fields []*common.Field `bun:""`
FieldsRaw []*common.Field `bun:""`
Note string `bun:""`
NoteRaw string `bun:""`
Memorial *bool `bun:",default:false"`
AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"`
MovedToURI string `bun:",nullzero"`
MoveID string `bun:"type:CHAR(26),nullzero"`
Bot *bool `bun:",default:false"`
Locked *bool `bun:",default:true"`
Discoverable *bool `bun:",default:false"`
URI string `bun:",nullzero,notnull,unique"`
URL string `bun:",nullzero,unique"`
InboxURI string `bun:",nullzero,unique"`
SharedInboxURI *string `bun:""`
OutboxURI string `bun:",nullzero,unique"`
FollowingURI string `bun:",nullzero,unique"`
FollowersURI string `bun:",nullzero,unique"`
FeaturedCollectionURI string `bun:",nullzero,unique"`
ActorType string `bun:",nullzero,notnull"`
PrivateKey *rsa.PrivateKey `bun:""`
PublicKey *rsa.PublicKey `bun:",notnull"`
PublicKeyURI string `bun:",nullzero,notnull,unique"`
PublicKeyExpiresAt time.Time `bun:"type:timestamptz,nullzero"`
SensitizedAt time.Time `bun:"type:timestamptz,nullzero"`
SilencedAt time.Time `bun:"type:timestamptz,nullzero"`
SuspendedAt time.Time `bun:"type:timestamptz,nullzero"`
SuspensionOrigin string `bun:"type:CHAR(26),nullzero"`
}

View file

@ -31,8 +31,8 @@ type Domain interface {
Block/allow storage + retrieval functions. Block/allow storage + retrieval functions.
*/ */
// CreateDomainAllow puts the given instance-level domain allow into the database. // PutDomainAllow puts the given instance-level domain allow into the database.
CreateDomainAllow(ctx context.Context, allow *gtsmodel.DomainAllow) error PutDomainAllow(ctx context.Context, allow *gtsmodel.DomainAllow) error
// GetDomainAllow returns one instance-level domain allow with the given domain, if it exists. // GetDomainAllow returns one instance-level domain allow with the given domain, if it exists.
GetDomainAllow(ctx context.Context, domain string) (*gtsmodel.DomainAllow, error) GetDomainAllow(ctx context.Context, domain string) (*gtsmodel.DomainAllow, error)
@ -49,8 +49,8 @@ type Domain interface {
// DeleteDomainAllow deletes an instance-level domain allow with the given domain, if it exists. // DeleteDomainAllow deletes an instance-level domain allow with the given domain, if it exists.
DeleteDomainAllow(ctx context.Context, domain string) error DeleteDomainAllow(ctx context.Context, domain string) error
// CreateDomainBlock puts the given instance-level domain block into the database. // PutDomainBlock puts the given instance-level domain block into the database.
CreateDomainBlock(ctx context.Context, block *gtsmodel.DomainBlock) error PutDomainBlock(ctx context.Context, block *gtsmodel.DomainBlock) error
// GetDomainBlock returns one instance-level domain block with the given domain, if it exists. // GetDomainBlock returns one instance-level domain block with the given domain, if it exists.
GetDomainBlock(ctx context.Context, domain string) (*gtsmodel.DomainBlock, error) GetDomainBlock(ctx context.Context, domain string) (*gtsmodel.DomainBlock, error)

View file

@ -29,4 +29,8 @@ var (
// ErrAlreadyExists is returned when a conflict was encountered in the db when doing an insert. // ErrAlreadyExists is returned when a conflict was encountered in the db when doing an insert.
ErrAlreadyExists = errors.New("already exists") ErrAlreadyExists = errors.New("already exists")
// ErrMultipleEntries is returned when multiple entries
// are found in the db when only one entry is sought.
ErrMultipleEntries = errors.New("multiple entries")
) )

View file

@ -31,6 +31,7 @@ import (
"github.com/google/uuid" "github.com/google/uuid"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/util"
) )
func (s *sender) sendTemplate(template string, subject string, data any, toAddresses ...string) error { func (s *sender) sendTemplate(template string, subject string, data any, toAddresses ...string) error {
@ -105,7 +106,7 @@ func assembleMessage(mailSubject string, mailBody string, mailFrom string, msgID
// msg headers.' // msg headers.'
msg.WriteString("To: Undisclosed Recipients:;" + CRLF) msg.WriteString("To: Undisclosed Recipients:;" + CRLF)
} }
msg.WriteString("Date: " + time.Now().Format(time.RFC822Z) + CRLF) msg.WriteString("Date: " + util.FormatRFC2822(time.Now()) + CRLF)
msg.WriteString("From: " + mailFrom + CRLF) msg.WriteString("From: " + mailFrom + CRLF)
msg.WriteString("Message-ID: <" + uuid.New().String() + "@" + msgIDHost + ">" + CRLF) msg.WriteString("Message-ID: <" + uuid.New().String() + "@" + msgIDHost + ">" + CRLF)
msg.WriteString("Subject: " + mailSubject + CRLF) msg.WriteString("Subject: " + mailSubject + CRLF)

View file

@ -199,9 +199,11 @@ func (f *Federator) AuthenticateFederatedRequest(ctx context.Context, requestedU
} }
// Dereference the account located at owner URI. // Dereference the account located at owner URI.
// Use exact URI match, not URL match.
pubKeyAuth.Owner, _, err = f.GetAccountByURI(ctx, pubKeyAuth.Owner, _, err = f.GetAccountByURI(ctx,
requestedUsername, requestedUsername,
pubKeyAuth.OwnerURI, pubKeyAuth.OwnerURI,
false,
) )
if err != nil { if err != nil {
if gtserror.StatusCode(err) == http.StatusGone { if gtserror.StatusCode(err) == http.StatusGone {

View file

@ -24,6 +24,7 @@ import (
"net/url" "net/url"
"time" "time"
errorsv2 "codeberg.org/gruf/go-errors/v2"
"codeberg.org/superseriousbusiness/activity/pub" "codeberg.org/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
@ -88,14 +89,30 @@ func accountFresh(
return !time.Now().After(staleAt) return !time.Now().After(staleAt)
} }
// GetAccountByURI will attempt to fetch an accounts by its URI, first checking the database. In the case of a newly-met remote model, or a remote model // GetAccountByURI will attempt to fetch an accounts by its
// whose last_fetched date is beyond a certain interval, the account will be dereferenced. In the case of dereferencing, some low-priority account information // URI, first checking the database. In the case of a newly-met
// may be enqueued for asynchronous fetching, e.g. featured account statuses (pins). An ActivityPub object indicates the account was dereferenced. // remote model, or a remote model whose last_fetched date is
func (d *Dereferencer) GetAccountByURI(ctx context.Context, requestUser string, uri *url.URL) (*gtsmodel.Account, ap.Accountable, error) { // beyond a certain interval, the account will be dereferenced.
// In the case of dereferencing, some low-priority account info
// may be enqueued for asynchronous fetching, e.g. pinned statuses.
// An ActivityPub object indicates the account was dereferenced.
//
// if tryURL is true, then the database will also check for a *single*
// account where uri == account.url, not just uri == account.uri.
// Because url does not guarantee uniqueness, you should only set
// tryURL to true when doing searches set in motion by a user,
// ie., when it's not important that an exact account is returned.
func (d *Dereferencer) GetAccountByURI(
ctx context.Context,
requestUser string,
uri *url.URL,
tryURL bool,
) (*gtsmodel.Account, ap.Accountable, error) {
// Fetch and dereference account if necessary. // Fetch and dereference account if necessary.
account, accountable, err := d.getAccountByURI(ctx, account, accountable, err := d.getAccountByURI(ctx,
requestUser, requestUser,
uri, uri,
tryURL,
) )
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
@ -117,8 +134,15 @@ func (d *Dereferencer) GetAccountByURI(ctx context.Context, requestUser string,
return account, accountable, nil return account, accountable, nil
} }
// getAccountByURI is a package internal form of .GetAccountByURI() that doesn't bother dereferencing featured posts on update. // getAccountByURI is a package internal form of
func (d *Dereferencer) getAccountByURI(ctx context.Context, requestUser string, uri *url.URL) (*gtsmodel.Account, ap.Accountable, error) { // .GetAccountByURI() that doesn't bother dereferencing
// featured posts on update.
func (d *Dereferencer) getAccountByURI(
ctx context.Context,
requestUser string,
uri *url.URL,
tryURL bool,
) (*gtsmodel.Account, ap.Accountable, error) {
var ( var (
account *gtsmodel.Account account *gtsmodel.Account
uriStr = uri.String() uriStr = uri.String()
@ -126,9 +150,8 @@ func (d *Dereferencer) getAccountByURI(ctx context.Context, requestUser string,
) )
// Search the database for existing account with URI. // Search the database for existing account with URI.
// URI is unique so if we get a hit it's that account for sure.
account, err = d.state.DB.GetAccountByURI( account, err = d.state.DB.GetAccountByURI(
// request a barebones object, it may be in the
// db but with related models not yet dereferenced.
gtscontext.SetBarebones(ctx), gtscontext.SetBarebones(ctx),
uriStr, uriStr,
) )
@ -136,13 +159,20 @@ func (d *Dereferencer) getAccountByURI(ctx context.Context, requestUser string,
return nil, nil, gtserror.Newf("error checking database for account %s by uri: %w", uriStr, err) return nil, nil, gtserror.Newf("error checking database for account %s by uri: %w", uriStr, err)
} }
if account == nil { if account == nil && tryURL {
// Else, search the database for existing by URL. // Else if we're permitted, search the database for *ONE*
account, err = d.state.DB.GetAccountByURL( // account with this URL. This can return multiple hits
// so check for ErrMultipleEntries. If we get exactly one
// hit it's *probably* the account we're looking for.
account, err = d.state.DB.GetOneAccountByURL(
gtscontext.SetBarebones(ctx), gtscontext.SetBarebones(ctx),
uriStr, uriStr,
) )
if err != nil && !errors.Is(err, db.ErrNoEntries) { if err != nil && !errorsv2.IsV2(
err,
db.ErrNoEntries,
db.ErrMultipleEntries,
) {
return nil, nil, gtserror.Newf("error checking database for account %s by url: %w", uriStr, err) return nil, nil, gtserror.Newf("error checking database for account %s by url: %w", uriStr, err)
} }
} }

View file

@ -54,6 +54,7 @@ func (suite *AccountTestSuite) TestDereferenceGroup() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
groupURL, groupURL,
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(group) suite.NotNil(group)
@ -67,7 +68,7 @@ func (suite *AccountTestSuite) TestDereferenceGroup() {
dbGroup, err := suite.db.GetAccountByURI(context.Background(), group.URI) dbGroup, err := suite.db.GetAccountByURI(context.Background(), group.URI)
suite.NoError(err) suite.NoError(err)
suite.Equal(group.ID, dbGroup.ID) suite.Equal(group.ID, dbGroup.ID)
suite.Equal(ap.ActorGroup, dbGroup.ActorType) suite.Equal(ap.ActorGroup, dbGroup.ActorType.String())
} }
func (suite *AccountTestSuite) TestDereferenceService() { func (suite *AccountTestSuite) TestDereferenceService() {
@ -78,6 +79,7 @@ func (suite *AccountTestSuite) TestDereferenceService() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
serviceURL, serviceURL,
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(service) suite.NotNil(service)
@ -91,7 +93,7 @@ func (suite *AccountTestSuite) TestDereferenceService() {
dbService, err := suite.db.GetAccountByURI(context.Background(), service.URI) dbService, err := suite.db.GetAccountByURI(context.Background(), service.URI)
suite.NoError(err) suite.NoError(err)
suite.Equal(service.ID, dbService.ID) suite.Equal(service.ID, dbService.ID)
suite.Equal(ap.ActorService, dbService.ActorType) suite.Equal(ap.ActorService, dbService.ActorType.String())
suite.Equal("example.org", dbService.Domain) suite.Equal("example.org", dbService.Domain)
} }
@ -110,6 +112,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsRemoteURL() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI), testrig.URLMustParse(targetAccount.URI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(fetchedAccount) suite.NotNil(fetchedAccount)
@ -129,6 +132,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsRemoteURLNoSharedInb
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI), testrig.URLMustParse(targetAccount.URI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(fetchedAccount) suite.NotNil(fetchedAccount)
@ -143,6 +147,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsUsername() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI), testrig.URLMustParse(targetAccount.URI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(fetchedAccount) suite.NotNil(fetchedAccount)
@ -157,6 +162,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountAsUsernameDomain() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse(targetAccount.URI), testrig.URLMustParse(targetAccount.URI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(fetchedAccount) suite.NotNil(fetchedAccount)
@ -213,6 +219,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountWithUnknownUserURI() {
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse("http://localhost:8080/users/thisaccountdoesnotexist"), testrig.URLMustParse("http://localhost:8080/users/thisaccountdoesnotexist"),
false,
) )
suite.True(gtserror.IsUnretrievable(err)) suite.True(gtserror.IsUnretrievable(err))
suite.EqualError(err, db.ErrNoEntries.Error()) suite.EqualError(err, db.ErrNoEntries.Error())
@ -265,7 +272,7 @@ func (suite *AccountTestSuite) TestDereferenceLocalAccountByRedirect() {
uri := testrig.URLMustParse("https://this-will-be-redirected.butts/") uri := testrig.URLMustParse("https://this-will-be-redirected.butts/")
// Try dereference the test URI, since it correctly redirects to us it should return our account. // Try dereference the test URI, since it correctly redirects to us it should return our account.
account, accountable, err := suite.dereferencer.GetAccountByURI(ctx, fetchingAccount.Username, uri) account, accountable, err := suite.dereferencer.GetAccountByURI(ctx, fetchingAccount.Username, uri, false)
suite.NoError(err) suite.NoError(err)
suite.Nil(accountable) suite.Nil(accountable)
suite.NotNil(account) suite.NotNil(account)
@ -318,7 +325,7 @@ func (suite *AccountTestSuite) TestDereferenceMasqueradingLocalAccount() {
) )
// Try dereference the test URI, since it correctly redirects to us it should return our account. // Try dereference the test URI, since it correctly redirects to us it should return our account.
account, accountable, err := suite.dereferencer.GetAccountByURI(ctx, fetchingAccount.Username, uri) account, accountable, err := suite.dereferencer.GetAccountByURI(ctx, fetchingAccount.Username, uri, false)
suite.NotNil(err) suite.NotNil(err)
suite.Nil(account) suite.Nil(account)
suite.Nil(accountable) suite.Nil(accountable)
@ -341,6 +348,7 @@ func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithNonMatchingURI()
context.Background(), context.Background(),
fetchingAccount.Username, fetchingAccount.Username,
testrig.URLMustParse(remoteAltURI), testrig.URLMustParse(remoteAltURI),
false,
) )
suite.Equal(err.Error(), fmt.Sprintf("enrichAccount: account uri %s does not match %s", remoteURI, remoteAltURI)) suite.Equal(err.Error(), fmt.Sprintf("enrichAccount: account uri %s does not match %s", remoteURI, remoteAltURI))
suite.Nil(fetchedAccount) suite.Nil(fetchedAccount)
@ -357,6 +365,7 @@ func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithUnexpectedKeyChan
remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx, remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx,
fetchingAcc.Username, fetchingAcc.Username,
testrig.URLMustParse(remoteURI), testrig.URLMustParse(remoteURI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(remoteAcc) suite.NotNil(remoteAcc)
@ -395,6 +404,7 @@ func (suite *AccountTestSuite) TestDereferenceRemoteAccountWithExpectedKeyChange
remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx, remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx,
fetchingAcc.Username, fetchingAcc.Username,
testrig.URLMustParse(remoteURI), testrig.URLMustParse(remoteURI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(remoteAcc) suite.NotNil(remoteAcc)
@ -436,6 +446,7 @@ func (suite *AccountTestSuite) TestRefreshFederatedRemoteAccountWithKeyChange()
remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx, remoteAcc, _, err := suite.dereferencer.GetAccountByURI(ctx,
fetchingAcc.Username, fetchingAcc.Username,
testrig.URLMustParse(remoteURI), testrig.URLMustParse(remoteURI),
false,
) )
suite.NoError(err) suite.NoError(err)
suite.NotNil(remoteAcc) suite.NotNil(remoteAcc)

View file

@ -454,7 +454,8 @@ func (d *Dereferencer) enrichStatus(
// Ensure we have the author account of the status dereferenced (+ up-to-date). If this is a new status // Ensure we have the author account of the status dereferenced (+ up-to-date). If this is a new status
// (i.e. status.AccountID == "") then any error here is irrecoverable. status.AccountID must ALWAYS be set. // (i.e. status.AccountID == "") then any error here is irrecoverable. status.AccountID must ALWAYS be set.
if _, _, err := d.getAccountByURI(ctx, requestUser, attributedTo); err != nil && status.AccountID == "" { // We want the exact URI match here as well, not the imprecise URL match.
if _, _, err := d.getAccountByURI(ctx, requestUser, attributedTo, false); err != nil && status.AccountID == "" {
// Note that we specifically DO NOT wrap the error, instead collapsing it as string. // Note that we specifically DO NOT wrap the error, instead collapsing it as string.
// Errors fetching an account do not necessarily relate to dereferencing the status. // Errors fetching an account do not necessarily relate to dereferencing the status.
@ -671,7 +672,7 @@ func (d *Dereferencer) fetchStatusMentions(
// Search existing status for a mention already stored, // Search existing status for a mention already stored,
// else ensure new mention's target account is populated. // else ensure new mention's target account is populated.
mention, alreadyExists, err = d.getPopulatedMention(ctx, mention, alreadyExists, err = d.populateMentionTarget(ctx,
requestUser, requestUser,
existing, existing,
mention, mention,
@ -1290,7 +1291,7 @@ func (d *Dereferencer) handleStatusEdit(
return cols, nil return cols, nil
} }
// getPopulatedMention tries to populate the given // populateMentionTarget tries to populate the given
// mention with the correct TargetAccount and (if not // mention with the correct TargetAccount and (if not
// yet set) TargetAccountURI, returning the populated // yet set) TargetAccountURI, returning the populated
// mention. // mention.
@ -1302,7 +1303,13 @@ func (d *Dereferencer) handleStatusEdit(
// Otherwise, this function will try to parse first // Otherwise, this function will try to parse first
// the Href of the mention, and then the namestring, // the Href of the mention, and then the namestring,
// to see who it targets, and go fetch that account. // to see who it targets, and go fetch that account.
func (d *Dereferencer) getPopulatedMention( //
// Note: Ordinarily it would make sense to try the
// namestring first, as it definitely can't be a URL
// rather than a URI, but because some remotes do
// silly things like only provide `@username` instead
// of `@username@domain`, we try by URI first.
func (d *Dereferencer) populateMentionTarget(
ctx context.Context, ctx context.Context,
requestUser string, requestUser string,
existing *gtsmodel.Status, existing *gtsmodel.Status,
@ -1312,8 +1319,9 @@ func (d *Dereferencer) getPopulatedMention(
bool, // True if mention already exists in the DB. bool, // True if mention already exists in the DB.
error, error,
) { ) {
// Mentions can be created using Name or Href. // Mentions can be created using `name` or `href`.
// Prefer Href (TargetAccountURI), fall back to Name. //
// Prefer `href` (TargetAccountURI), fall back to Name.
if mention.TargetAccountURI != "" { if mention.TargetAccountURI != "" {
// Look for existing mention with target account's URI, if so use this. // Look for existing mention with target account's URI, if so use this.
@ -1323,19 +1331,24 @@ func (d *Dereferencer) getPopulatedMention(
} }
// Ensure that mention account URI is parseable. // Ensure that mention account URI is parseable.
accountURI, err := url.Parse(mention.TargetAccountURI) targetAccountURI, err := url.Parse(mention.TargetAccountURI)
if err != nil { if err != nil {
err := gtserror.Newf("invalid account uri %q: %w", mention.TargetAccountURI, err) err := gtserror.Newf("invalid account uri %q: %w", mention.TargetAccountURI, err)
return nil, false, err return nil, false, err
} }
// Ensure we have account of the mention target dereferenced. // Ensure we have the account of
// the mention target dereferenced.
//
// Use exact URI match only, not URL,
// as we want to be precise here.
mention.TargetAccount, _, err = d.getAccountByURI(ctx, mention.TargetAccount, _, err = d.getAccountByURI(ctx,
requestUser, requestUser,
accountURI, targetAccountURI,
false,
) )
if err != nil { if err != nil {
err := gtserror.Newf("failed to dereference account %s: %w", accountURI, err) err := gtserror.Newf("failed to dereference account %s: %w", targetAccountURI, err)
return nil, false, err return nil, false, err
} }
} else { } else {
@ -1353,17 +1366,32 @@ func (d *Dereferencer) getPopulatedMention(
return existingMention, true, nil return existingMention, true, nil
} }
// Ensure we have the account of the mention target dereferenced. // Ensure we have the account of
// the mention target dereferenced.
//
// This might fail if the remote does
// something silly like only setting
// `@username` and not `@username@domain`.
mention.TargetAccount, _, err = d.getAccountByUsernameDomain(ctx, mention.TargetAccount, _, err = d.getAccountByUsernameDomain(ctx,
requestUser, requestUser,
username, username,
domain, domain,
) )
if err != nil { if err != nil && !errors.Is(err, db.ErrNoEntries) {
err := gtserror.Newf("failed to dereference account %s: %w", mention.NameString, err) err := gtserror.Newf("failed to dereference account %s: %w", mention.NameString, err)
return nil, false, err return nil, false, err
} }
if mention.TargetAccount == nil {
// Probably failed for abovementioned
// silly reason. Nothing we can do about it.
err := gtserror.Newf(
"failed to populate mention target account (badly formatted namestring?) %s: %w",
mention.NameString, err,
)
return nil, false, err
}
// Look for existing mention with target account's URI, if so use this. // Look for existing mention with target account's URI, if so use this.
existingMention, ok = existing.GetMentionByTargetURI(mention.TargetAccountURI) existingMention, ok = existing.GetMentionByTargetURI(mention.TargetAccountURI)
if ok && existingMention.ID != "" { if ok && existingMention.ID != "" {

View file

@ -33,7 +33,7 @@ import (
// //
// The library makes this call only after acquiring a lock first. // The library makes this call only after acquiring a lock first.
func (f *federatingDB) Followers(ctx context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) { func (f *federatingDB) Followers(ctx context.Context, actorIRI *url.URL) (followers vocab.ActivityStreamsCollection, err error) {
acct, err := f.getAccountForIRI(ctx, actorIRI) acct, err := f.state.DB.GetAccountByURI(ctx, actorIRI.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -32,7 +32,7 @@ import (
// //
// The library makes this call only after acquiring a lock first. // The library makes this call only after acquiring a lock first.
func (f *federatingDB) Following(ctx context.Context, actorIRI *url.URL) (following vocab.ActivityStreamsCollection, err error) { func (f *federatingDB) Following(ctx context.Context, actorIRI *url.URL) (following vocab.ActivityStreamsCollection, err error) {
acct, err := f.getAccountForIRI(ctx, actorIRI) acct, err := f.state.DB.GetAccountByURI(ctx, actorIRI.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -46,12 +46,12 @@ func (f *federatingDB) SetOutbox(ctx context.Context, outbox vocab.ActivityStrea
return nil return nil
} }
// OutboxForInbox fetches the corresponding actor's outbox IRI for the // OutboxForInbox fetches the corresponding local actor's outbox IRI for the
// actor's inbox IRI. // actor's inbox IRI.
// //
// The library makes this call only after acquiring a lock first. // The library makes this call only after acquiring a lock first.
func (f *federatingDB) OutboxForInbox(ctx context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) { func (f *federatingDB) OutboxForInbox(ctx context.Context, inboxIRI *url.URL) (outboxIRI *url.URL, err error) {
acct, err := f.getAccountForIRI(ctx, inboxIRI) acct, err := f.state.DB.GetOneAccountByInboxURI(ctx, inboxIRI.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }

View file

@ -28,7 +28,6 @@ import (
"codeberg.org/superseriousbusiness/activity/streams/vocab" "codeberg.org/superseriousbusiness/activity/streams/vocab"
"github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/config" "github.com/superseriousbusiness/gotosocial/internal/config"
"github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtscontext" "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/id"
@ -126,83 +125,30 @@ func (f *federatingDB) NewID(ctx context.Context, t vocab.Type) (idURL *url.URL,
return url.Parse(fmt.Sprintf("%s://%s/%s", config.GetProtocol(), config.GetHost(), newID)) return url.Parse(fmt.Sprintf("%s://%s/%s", config.GetProtocol(), config.GetHost(), newID))
} }
// ActorForOutbox fetches the actor's IRI for the given outbox IRI. // ActorForOutbox fetches the local actor's IRI for the given outbox IRI.
// //
// The library makes this call only after acquiring a lock first. // The library makes this call only after acquiring a lock first.
func (f *federatingDB) ActorForOutbox(ctx context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) { func (f *federatingDB) ActorForOutbox(ctx context.Context, outboxIRI *url.URL) (actorIRI *url.URL, err error) {
acct, err := f.getAccountForIRI(ctx, outboxIRI) acct, err := f.state.DB.GetOneAccountByOutboxURI(ctx, outboxIRI.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return url.Parse(acct.URI) return url.Parse(acct.URI)
} }
// ActorForInbox fetches the actor's IRI for the given outbox IRI. // ActorForInbox fetches the local actor's IRI for the given inbox IRI.
// //
// The library makes this call only after acquiring a lock first. // The library makes this call only after acquiring a lock first.
func (f *federatingDB) ActorForInbox(ctx context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) { func (f *federatingDB) ActorForInbox(ctx context.Context, inboxIRI *url.URL) (actorIRI *url.URL, err error) {
acct, err := f.getAccountForIRI(ctx, inboxIRI) acct, err := f.state.DB.GetOneAccountByInboxURI(ctx, inboxIRI.String())
if err != nil { if err != nil {
return nil, err return nil, err
} }
return url.Parse(acct.URI) return url.Parse(acct.URI)
} }
// getAccountForIRI returns the account that corresponds to or owns the given IRI.
func (f *federatingDB) getAccountForIRI(ctx context.Context, iri *url.URL) (*gtsmodel.Account, error) {
var (
acct *gtsmodel.Account
err error
)
switch {
case uris.IsUserPath(iri):
if acct, err = f.state.DB.GetAccountByURI(ctx, iri.String()); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to uri %s", iri.String())
}
return nil, fmt.Errorf("db error searching for actor with uri %s", iri.String())
}
return acct, nil
case uris.IsInboxPath(iri):
if acct, err = f.state.DB.GetAccountByInboxURI(ctx, iri.String()); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to inbox %s", iri.String())
}
return nil, fmt.Errorf("db error searching for actor with inbox %s", iri.String())
}
return acct, nil
case uris.IsOutboxPath(iri):
if acct, err = f.state.DB.GetAccountByOutboxURI(ctx, iri.String()); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to outbox %s", iri.String())
}
return nil, fmt.Errorf("db error searching for actor with outbox %s", iri.String())
}
return acct, nil
case uris.IsFollowersPath(iri):
if acct, err = f.state.DB.GetAccountByFollowersURI(ctx, iri.String()); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to followers_uri %s", iri.String())
}
return nil, fmt.Errorf("db error searching for actor with followers_uri %s", iri.String())
}
return acct, nil
case uris.IsFollowingPath(iri):
if acct, err = f.state.DB.GetAccountByFollowingURI(ctx, iri.String()); err != nil {
if err == db.ErrNoEntries {
return nil, fmt.Errorf("no actor found that corresponds to following_uri %s", iri.String())
}
return nil, fmt.Errorf("db error searching for actor with following_uri %s", iri.String())
}
return acct, nil
default:
return nil, fmt.Errorf("getActorForIRI: iri %s not recognised", iri)
}
}
// collectFollows takes a slice of iris and converts them into ActivityStreamsCollection of IRIs. // collectFollows takes a slice of iris and converts them into ActivityStreamsCollection of IRIs.
func (f *federatingDB) collectIRIs(ctx context.Context, iris []*url.URL) (vocab.ActivityStreamsCollection, error) { func (f *federatingDB) collectIRIs(_ context.Context, iris []*url.URL) (vocab.ActivityStreamsCollection, error) {
collection := streams.NewActivityStreamsCollection() collection := streams.NewActivityStreamsCollection()
items := streams.NewActivityStreamsItemsProperty() items := streams.NewActivityStreamsItemsProperty()
for _, i := range iris { for _, i := range iris {

View file

@ -31,57 +31,247 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
) )
// Account represents either a local or a remote fediverse // Account represents either a local or a remote ActivityPub actor.
// account, gotosocial or otherwise (mastodon, pleroma, etc). // https://www.w3.org/TR/activitypub/#actor-objects
type Account struct { type Account struct {
ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database // Database ID of the account.
CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created. ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"`
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item was last updated.
FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched. // Datetime when the account was created.
Username string `bun:",nullzero,notnull,unique:usernamedomain"` // Username of the account, should just be a string of [a-zA-Z0-9_]. Can be added to domain to create the full username in the form ``[username]@[domain]`` eg., ``user_96@example.org``. Username and domain should be unique *with* each other // Corresponds to ActivityStreams `published` prop.
Domain string `bun:",nullzero,unique:usernamedomain"` // Domain of the account, will be null if this is a local account, otherwise something like ``example.org``. Should be unique with username. CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present
AvatarMediaAttachment *MediaAttachment `bun:"rel:belongs-to"` // MediaAttachment corresponding to avatarMediaAttachmentID // Datetime when was the account was last updated,
AvatarRemoteURL string `bun:",nullzero"` // For a non-local account, where can the header be fetched? // ie., when the actor last sent out an Update
HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"` // Database ID of the media attachment, if present // activity, or if never, when it was `published`.
HeaderMediaAttachment *MediaAttachment `bun:"rel:belongs-to"` // MediaAttachment corresponding to headerMediaAttachmentID UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"`
HeaderRemoteURL string `bun:",nullzero"` // For a non-local account, where can the header be fetched?
DisplayName string `bun:""` // DisplayName for this account. Can be empty, then just the Username will be used for display purposes. // Datetime when the account was last fetched /
EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this account's bio, display name, etc // dereferenced by this GoToSocial instance.
Emojis []*Emoji `bun:"attached_emojis,m2m:account_to_emojis"` // Emojis corresponding to emojiIDs. https://bun.uptrace.dev/guide/relations.html#many-to-many-relation FetchedAt time.Time `bun:"type:timestamptz,nullzero"`
Fields []*Field `bun:""` // A slice of of fields that this account has added to their profile.
FieldsRaw []*Field `bun:""` // The raw (unparsed) content of fields that this account has added to their profile, without conversion to HTML, only available when requester = target // Username of the account.
Note string `bun:""` // A note that this account has on their profile (ie., the account's bio/description of themselves) //
NoteRaw string `bun:""` // The raw contents of .Note without conversion to HTML, only available when requester = target // Corresponds to AS `preferredUsername` prop, which gives
Memorial *bool `bun:",default:false"` // Is this a memorial account, ie., has the user passed away? // no uniqueness guarantee. However, we do enforce uniqueness
AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"` // This account is associated with these account URIs. // for it as, in practice, it always is and we rely on this.
AlsoKnownAs []*Account `bun:"-"` // This account is associated with these accounts (field not stored in the db). Username string `bun:",nullzero,notnull,unique:accounts_username_domain_uniq"`
MovedToURI string `bun:",nullzero"` // This account has (or claims to have) moved to this account URI. Even if this field is set the move may not yet have been processed. Check `move` for this.
MovedTo *Account `bun:"-"` // This account has moved to this account (field not stored in the db). // Domain of the account, discovered via webfinger.
MoveID string `bun:"type:CHAR(26),nullzero"` // ID of a Move in the database for this account. Only set if we received or created a Move activity for which this account URI was the origin. //
Move *Move `bun:"-"` // Move corresponding to MoveID, if set. // Null if this is a local account, otherwise
Bot *bool `bun:",default:false"` // Does this account identify itself as a bot? // something like `example.org`.
Locked *bool `bun:",default:true"` // Does this account need an approval for new followers? Domain string `bun:",nullzero,unique:accounts_username_domain_uniq"`
Discoverable *bool `bun:",default:false"` // Should this account be shown in the instance's profile directory?
URI string `bun:",nullzero,notnull,unique"` // ActivityPub URI for this account. // Database ID of the account's avatar MediaAttachment, if set.
URL string `bun:",nullzero,unique"` // Web URL for this account's profile AvatarMediaAttachmentID string `bun:"type:CHAR(26),nullzero"`
InboxURI string `bun:",nullzero,unique"` // Address of this account's ActivityPub inbox, for sending activity to
SharedInboxURI *string `bun:""` // Address of this account's ActivityPub sharedInbox. Gotcha warning: this is a string pointer because it has three possible states: 1. We don't know yet if the account has a shared inbox -- null. 2. We know it doesn't have a shared inbox -- empty string. 3. We know it does have a shared inbox -- url string. // MediaAttachment corresponding to AvatarMediaAttachmentID.
OutboxURI string `bun:",nullzero,unique"` // Address of this account's activitypub outbox AvatarMediaAttachment *MediaAttachment `bun:"-"`
FollowingURI string `bun:",nullzero,unique"` // URI for getting the following list of this account
FollowersURI string `bun:",nullzero,unique"` // URI for getting the followers list of this account // URL of the avatar media.
FeaturedCollectionURI string `bun:",nullzero,unique"` // URL for getting the featured collection list of this account //
ActorType string `bun:",nullzero,notnull"` // What type of activitypub actor is this account? // Null for local accounts.
PrivateKey *rsa.PrivateKey `bun:""` // Privatekey for signing activitypub requests, will only be defined for local accounts AvatarRemoteURL string `bun:",nullzero"`
PublicKey *rsa.PublicKey `bun:",notnull"` // Publickey for authorizing signed activitypub requests, will be defined for both local and remote accounts
PublicKeyURI string `bun:",nullzero,notnull,unique"` // Web-reachable location of this account's public key // Database ID of the account's header MediaAttachment, if set.
PublicKeyExpiresAt time.Time `bun:"type:timestamptz,nullzero"` // PublicKey will expire/has expired at given time, and should be fetched again as appropriate. Only ever set for remote accounts. HeaderMediaAttachmentID string `bun:"type:CHAR(26),nullzero"`
SensitizedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account set to have all its media shown as sensitive?
SilencedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account silenced (eg., statuses only visible to followers, not public)? // MediaAttachment corresponding to HeaderMediaAttachmentID.
SuspendedAt time.Time `bun:"type:timestamptz,nullzero"` // When was this account suspended (eg., don't allow it to log in/post, don't accept media/posts from this account) HeaderMediaAttachment *MediaAttachment `bun:"-"`
SuspensionOrigin string `bun:"type:CHAR(26),nullzero"` // id of the database entry that caused this account to become suspended -- can be an account ID or a domain block ID
Settings *AccountSettings `bun:"-"` // gtsmodel.AccountSettings for this account. // URL of the header media.
Stats *AccountStats `bun:"-"` // gtsmodel.AccountStats for this account. //
// Null for local accounts.
HeaderRemoteURL string `bun:",nullzero"`
// Display name for this account, if set.
//
// Corresponds to the ActivityStreams `name` property.
//
// If null, fall back to username for display purposes.
DisplayName string `bun:",nullzero"`
// Database IDs of any emojis used in
// this account's bio, display name, etc
EmojiIDs []string `bun:"emojis,array"`
// Emojis corresponding to EmojiIDs.
Emojis []*Emoji `bun:"-"`
// A slice of of key/value fields that
// this account has added to their profile.
//
// Corresponds to schema.org PropertyValue types in `attachments`.
Fields []*Field `bun:",nullzero"`
// The raw (unparsed) content of fields that this
// account has added to their profile, before
// conversion to HTML.
//
// Only set for local accounts.
FieldsRaw []*Field `bun:",nullzero"`
// A note that this account has on their profile
// (ie., the account's bio/description of themselves).
//
// Corresponds to the ActivityStreams `summary` property.
Note string `bun:",nullzero"`
// The raw (unparsed) version of Note, before conversion to HTML.
//
// Only set for local accounts.
NoteRaw string `bun:",nullzero"`
// ActivityPub URI/IDs by which this account is also known.
//
// Corresponds to the ActivityStreams `alsoKnownAs` property.
AlsoKnownAsURIs []string `bun:"also_known_as_uris,array"`
// Accounts matching AlsoKnownAsURIs.
AlsoKnownAs []*Account `bun:"-"`
// URI/ID to which the account has (or claims to have) moved.
//
// Corresponds to the ActivityStreams `movedTo` property.
//
// Even if this field is set the move may not yet have been
// processed. Check `move` for this.
MovedToURI string `bun:",nullzero"`
// Account matching MovedToURI.
MovedTo *Account `bun:"-"`
// ID of a Move in the database for this account.
// Only set if we received or created a Move activity
// for which this account URI was the origin.
MoveID string `bun:"type:CHAR(26),nullzero"`
// Move corresponding to MoveID, if set.
Move *Move `bun:"-"`
// True if account requires manual approval of Follows.
//
// Corresponds to AS `manuallyApprovesFollowers` prop.
Locked *bool `bun:",nullzero,notnull,default:true"`
// True if account has opted in to being shown in
// directories and exposed to search engines.
//
// Corresponds to the toot `discoverable` property.
Discoverable *bool `bun:",nullzero,notnull,default:false"`
// ActivityPub URI/ID for this account.
//
// Must be set, must be unique.
URI string `bun:",nullzero,notnull,unique"`
// URL at which a web representation of this
// account should be available, if set.
//
// Corresponds to ActivityStreams `url` prop.
URL string `bun:",nullzero"`
// URI of the actor's inbox.
//
// Corresponds to ActivityPub `inbox` property.
//
// According to AP this MUST be set, but some
// implementations don't set it for service actors.
InboxURI string `bun:",nullzero"`
// URI/ID of this account's sharedInbox, if set.
//
// Corresponds to ActivityPub `endpoints.sharedInbox`.
//
// Gotcha warning: this is a string pointer because
// it has three possible states:
//
// 1. null: We don't know (yet) if actor has a shared inbox.
// 2. empty: We know it doesn't have a shared inbox.
// 3. not empty: We know it does have a shared inbox.
SharedInboxURI *string `bun:""`
// URI/ID of the actor's outbox.
//
// Corresponds to ActivityPub `outbox` property.
//
// According to AP this MUST be set, but some
// implementations don't set it for service actors.
OutboxURI string `bun:",nullzero"`
// URI/ID of the actor's following collection.
//
// Corresponds to ActivityPub `following` property.
//
// According to AP this SHOULD be set.
FollowingURI string `bun:",nullzero"`
// URI/ID of the actor's followers collection.
//
// Corresponds to ActivityPub `followers` property.
//
// According to AP this SHOULD be set.
FollowersURI string `bun:",nullzero"`
// URI/ID of the actor's featured collection.
//
// Corresponds to the Toot `featured` property.
FeaturedCollectionURI string `bun:",nullzero"`
// ActivityStreams type of the actor.
//
// Application, Group, Organization, Person, or Service.
ActorType AccountActorType `bun:",nullzero,notnull"`
// Private key for signing http requests.
//
// Only defined for local accounts
PrivateKey *rsa.PrivateKey `bun:""`
// Public key for authorizing signed http requests.
//
// Defined for both local and remote accounts
PublicKey *rsa.PublicKey `bun:",notnull"`
// Dereferenceable location of this actor's public key.
//
// Corresponds to https://w3id.org/security/v1 `publicKey.id`.
PublicKeyURI string `bun:",nullzero,notnull,unique"`
// Datetime at which public key will expire/has expired,
// and should be fetched again as appropriate.
//
// Only ever set for remote accounts.
PublicKeyExpiresAt time.Time `bun:"type:timestamptz,nullzero"`
// Datetime at which account was marked as a "memorial",
// ie., user owning the account has passed away.
MemorializedAt time.Time `bun:"type:timestamptz,nullzero"`
// Datetime at which account was set to
// have all its media shown as sensitive.
SensitizedAt time.Time `bun:"type:timestamptz,nullzero"`
// Datetime at which account was silenced.
SilencedAt time.Time `bun:"type:timestamptz,nullzero"`
// Datetime at which account was suspended.
SuspendedAt time.Time `bun:"type:timestamptz,nullzero"`
// ID of the database entry that caused this account to
// be suspended. Can be an account ID or a domain block ID.
SuspensionOrigin string `bun:"type:CHAR(26),nullzero"`
// gtsmodel.AccountSettings for this account.
//
// Local, non-instance-actor accounts only.
Settings *AccountSettings `bun:"-"`
// gtsmodel.AccountStats for this account.
//
// Local accounts only.
Stats *AccountStats `bun:"-"`
} }
// UsernameDomain returns account @username@domain (missing domain if local). // UsernameDomain returns account @username@domain (missing domain if local).
@ -215,6 +405,59 @@ type Field struct {
VerifiedAt time.Time `bun:",nullzero"` // This field was verified at (optional). VerifiedAt time.Time `bun:",nullzero"` // This field was verified at (optional).
} }
// AccountActorType is the ActivityStreams type of an actor.
type AccountActorType enumType
const (
AccountActorTypeUnknown AccountActorType = 0
AccountActorTypeApplication AccountActorType = 1 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-application
AccountActorTypeGroup AccountActorType = 2 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-group
AccountActorTypeOrganization AccountActorType = 3 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-organization
AccountActorTypePerson AccountActorType = 4 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-person
AccountActorTypeService AccountActorType = 5 // https://www.w3.org/TR/activitystreams-vocabulary/#dfn-service
)
// String returns a stringified form of AccountActorType.
func (t AccountActorType) String() string {
switch t {
case AccountActorTypeApplication:
return "Application"
case AccountActorTypeGroup:
return "Group"
case AccountActorTypeOrganization:
return "Organization"
case AccountActorTypePerson:
return "Person"
case AccountActorTypeService:
return "Service"
default:
panic("invalid notification type")
}
}
// ParseAccountActorType returns an
// actor type from the given value.
func ParseAccountActorType(in string) AccountActorType {
switch strings.ToLower(in) {
case "application":
return AccountActorTypeApplication
case "group":
return AccountActorTypeGroup
case "organization":
return AccountActorTypeOrganization
case "person":
return AccountActorTypePerson
case "service":
return AccountActorTypeService
default:
return AccountActorTypeUnknown
}
}
func (t AccountActorType) IsBot() bool {
return t == AccountActorTypeApplication || t == AccountActorTypeService
}
// Relationship describes a requester's relationship with another account. // Relationship describes a requester's relationship with another account.
type Relationship struct { type Relationship struct {
ID string // The account id. ID string // The account id.

View file

@ -26,7 +26,7 @@ type DomainAllow struct {
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
Domain string `bun:",nullzero,notnull"` // domain to allow. Eg. 'whatever.com' Domain string `bun:",nullzero,notnull"` // domain to allow. Eg. 'whatever.com'
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this allow CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this allow
CreatedByAccount *Account `bun:"rel:belongs-to"` // Account corresponding to createdByAccountID CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID
PrivateComment string `bun:""` // Private comment on this allow, viewable to admins PrivateComment string `bun:""` // Private comment on this allow, viewable to admins
PublicComment string `bun:""` // Public comment on this allow, viewable (optionally) by everyone PublicComment string `bun:""` // Public comment on this allow, viewable (optionally) by everyone
Obfuscate *bool `bun:",nullzero,notnull,default:false"` // whether the domain name should appear obfuscated when displaying it publicly Obfuscate *bool `bun:",nullzero,notnull,default:false"` // whether the domain name should appear obfuscated when displaying it publicly

View file

@ -26,7 +26,7 @@ type DomainBlock struct {
UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated
Domain string `bun:",nullzero,notnull"` // domain to block. Eg. 'whatever.com' Domain string `bun:",nullzero,notnull"` // domain to block. Eg. 'whatever.com'
CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this block CreatedByAccountID string `bun:"type:CHAR(26),nullzero,notnull"` // Account ID of the creator of this block
CreatedByAccount *Account `bun:"rel:belongs-to"` // Account corresponding to createdByAccountID CreatedByAccount *Account `bun:"-"` // Account corresponding to createdByAccountID
PrivateComment string `bun:""` // Private comment on this block, viewable to admins PrivateComment string `bun:""` // Private comment on this block, viewable to admins
PublicComment string `bun:""` // Public comment on this block, viewable (optionally) by everyone PublicComment string `bun:""` // Public comment on this block, viewable (optionally) by everyone
Obfuscate *bool `bun:",nullzero,notnull,default:false"` // whether the domain name should appear obfuscated when displaying it publicly Obfuscate *bool `bun:",nullzero,notnull,default:false"` // whether the domain name should appear obfuscated when displaying it publicly

View file

@ -107,9 +107,14 @@ func (p *Processor) Alias(
} }
// Ensure we have account dereferenced. // Ensure we have account dereferenced.
//
// As this comes from user input, allow checking
// by URL to make things easier, not just to an
// exact AP URI (which a user might not even know).
targetAccount, _, err := p.federator.GetAccountByURI(ctx, targetAccount, _, err := p.federator.GetAccountByURI(ctx,
account.Username, account.Username,
newAKA.uri, newAKA.uri,
true,
) )
if err != nil { if err != nil {
err := fmt.Errorf( err := fmt.Errorf(

View file

@ -528,7 +528,7 @@ func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
account.Fields = nil account.Fields = nil
account.Note = "" account.Note = ""
account.NoteRaw = "" account.NoteRaw = ""
account.Memorial = util.Ptr(false) account.MemorializedAt = never
account.AlsoKnownAsURIs = nil account.AlsoKnownAsURIs = nil
account.MovedToURI = "" account.MovedToURI = ""
account.Discoverable = util.Ptr(false) account.Discoverable = util.Ptr(false)
@ -546,7 +546,7 @@ func stubbifyAccount(account *gtsmodel.Account, origin string) []string {
"fields", "fields",
"note", "note",
"note_raw", "note_raw",
"memorial", "memorialized_at",
"also_known_as_uris", "also_known_as_uris",
"moved_to_uri", "moved_to_uri",
"discoverable", "discoverable",

View file

@ -64,7 +64,7 @@ func (suite *AccountDeleteTestSuite) TestAccountDeleteLocal() {
suite.Nil(updatedAccount.Fields) suite.Nil(updatedAccount.Fields)
suite.Zero(updatedAccount.Note) suite.Zero(updatedAccount.Note)
suite.Zero(updatedAccount.NoteRaw) suite.Zero(updatedAccount.NoteRaw)
suite.False(*updatedAccount.Memorial) suite.Zero(updatedAccount.MemorializedAt)
suite.Empty(updatedAccount.AlsoKnownAsURIs) suite.Empty(updatedAccount.AlsoKnownAsURIs)
suite.False(*updatedAccount.Discoverable) suite.False(*updatedAccount.Discoverable)
suite.WithinDuration(time.Now(), updatedAccount.SuspendedAt, 1*time.Minute) suite.WithinDuration(time.Now(), updatedAccount.SuspendedAt, 1*time.Minute)

View file

@ -66,10 +66,13 @@ func (p *Processor) Get(ctx context.Context, requestingAccount *gtsmodel.Account
// Perform a last-minute fetch of target account to // Perform a last-minute fetch of target account to
// ensure remote account header / avatar is cached. // ensure remote account header / avatar is cached.
//
// Match by URI only.
latest, _, err := p.federator.GetAccountByURI( latest, _, err := p.federator.GetAccountByURI(
gtscontext.SetFastFail(ctx), gtscontext.SetFastFail(ctx),
requestingAccount.Username, requestingAccount.Username,
targetAccountURI, targetAccountURI,
false,
) )
if err != nil { if err != nil {
log.Errorf(ctx, "error fetching latest target account: %v", err) log.Errorf(ctx, "error fetching latest target account: %v", err)

View file

@ -119,11 +119,15 @@ func (p *Processor) MoveSelf(
unlock := p.state.ProcessingLocks.Lock(lockKey) unlock := p.state.ProcessingLocks.Lock(lockKey)
defer unlock() defer unlock()
// Ensure we have a valid, up-to-date representation of the target account. // Ensure we have a valid, up-to-date
// representation of the target account.
//
// Match by uri only.
targetAcct, targetAcctable, err = p.federator.GetAccountByURI( targetAcct, targetAcctable, err = p.federator.GetAccountByURI(
ctx, ctx,
originAcct.Username, originAcct.Username,
targetAcctURI, targetAcctURI,
false,
) )
if err != nil { if err != nil {
const text = "error dereferencing moved_to_uri" const text = "error dereferencing moved_to_uri"

View file

@ -78,8 +78,8 @@ func (p *Processor) Update(ctx context.Context, account *gtsmodel.Account, form
} }
if form.Bot != nil { if form.Bot != nil {
account.Bot = form.Bot account.ActorType = gtsmodel.AccountActorTypeService
acctColumns = append(acctColumns, "bot") acctColumns = append(acctColumns, "actor_type")
} }
if form.Locked != nil { if form.Locked != nil {

View file

@ -60,7 +60,7 @@ func (p *Processor) createDomainAllow(
} }
// Insert the new allow into the database. // Insert the new allow into the database.
if err := p.state.DB.CreateDomainAllow(ctx, domainAllow); err != nil { if err := p.state.DB.PutDomainAllow(ctx, domainAllow); err != nil {
err = gtserror.Newf("db error putting domain allow %s: %w", domain, err) err = gtserror.Newf("db error putting domain allow %s: %w", domain, err)
return nil, "", gtserror.NewErrorInternalError(err) return nil, "", gtserror.NewErrorInternalError(err)
} }
@ -92,6 +92,54 @@ func (p *Processor) createDomainAllow(
return apiDomainAllow, action.ID, nil return apiDomainAllow, action.ID, nil
} }
func (p *Processor) updateDomainAllow(
ctx context.Context,
domainAllowID string,
obfuscate *bool,
publicComment *string,
privateComment *string,
subscriptionID *string,
) (*apimodel.DomainPermission, gtserror.WithCode) {
domainAllow, err := p.state.DB.GetDomainAllowByID(ctx, domainAllowID)
if err != nil {
if !errors.Is(err, db.ErrNoEntries) {
// Real error.
err = gtserror.Newf("db error getting domain allow: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
// There are just no entries for this ID.
err = fmt.Errorf("no domain allow entry exists with ID %s", domainAllowID)
return nil, gtserror.NewErrorNotFound(err, err.Error())
}
var columns []string
if obfuscate != nil {
domainAllow.Obfuscate = obfuscate
columns = append(columns, "obfuscate")
}
if publicComment != nil {
domainAllow.PublicComment = *publicComment
columns = append(columns, "public_comment")
}
if privateComment != nil {
domainAllow.PrivateComment = *privateComment
columns = append(columns, "private_comment")
}
if subscriptionID != nil {
domainAllow.SubscriptionID = *subscriptionID
columns = append(columns, "subscription_id")
}
// Update the domain allow.
if err := p.state.DB.UpdateDomainAllow(ctx, domainAllow, columns...); err != nil {
err = gtserror.Newf("db error updating domain allow: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
return p.apiDomainPerm(ctx, domainAllow, false)
}
func (p *Processor) deleteDomainAllow( func (p *Processor) deleteDomainAllow(
ctx context.Context, ctx context.Context,
adminAcct *gtsmodel.Account, adminAcct *gtsmodel.Account,

View file

@ -60,7 +60,7 @@ func (p *Processor) createDomainBlock(
} }
// Insert the new block into the database. // Insert the new block into the database.
if err := p.state.DB.CreateDomainBlock(ctx, domainBlock); err != nil { if err := p.state.DB.PutDomainBlock(ctx, domainBlock); err != nil {
err = gtserror.Newf("db error putting domain block %s: %w", domain, err) err = gtserror.Newf("db error putting domain block %s: %w", domain, err)
return nil, "", gtserror.NewErrorInternalError(err) return nil, "", gtserror.NewErrorInternalError(err)
} }
@ -93,6 +93,54 @@ func (p *Processor) createDomainBlock(
return apiDomainBlock, action.ID, nil return apiDomainBlock, action.ID, nil
} }
func (p *Processor) updateDomainBlock(
ctx context.Context,
domainBlockID string,
obfuscate *bool,
publicComment *string,
privateComment *string,
subscriptionID *string,
) (*apimodel.DomainPermission, gtserror.WithCode) {
domainBlock, err := p.state.DB.GetDomainBlockByID(ctx, domainBlockID)
if err != nil {
if !errors.Is(err, db.ErrNoEntries) {
// Real error.
err = gtserror.Newf("db error getting domain block: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
// There are just no entries for this ID.
err = fmt.Errorf("no domain block entry exists with ID %s", domainBlockID)
return nil, gtserror.NewErrorNotFound(err, err.Error())
}
var columns []string
if obfuscate != nil {
domainBlock.Obfuscate = obfuscate
columns = append(columns, "obfuscate")
}
if publicComment != nil {
domainBlock.PublicComment = *publicComment
columns = append(columns, "public_comment")
}
if privateComment != nil {
domainBlock.PrivateComment = *privateComment
columns = append(columns, "private_comment")
}
if subscriptionID != nil {
domainBlock.SubscriptionID = *subscriptionID
columns = append(columns, "subscription_id")
}
// Update the domain block.
if err := p.state.DB.UpdateDomainBlock(ctx, domainBlock, columns...); err != nil {
err = gtserror.Newf("db error updating domain block: %w", err)
return nil, gtserror.NewErrorInternalError(err)
}
return p.apiDomainPerm(ctx, domainBlock, false)
}
func (p *Processor) deleteDomainBlock( func (p *Processor) deleteDomainBlock(
ctx context.Context, ctx context.Context,
adminAcct *gtsmodel.Account, adminAcct *gtsmodel.Account,

View file

@ -18,6 +18,7 @@
package admin package admin
import ( import (
"cmp"
"context" "context"
"encoding/json" "encoding/json"
"errors" "errors"
@ -29,6 +30,7 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtserror"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
) )
// DomainPermissionCreate creates an instance-level permission // DomainPermissionCreate creates an instance-level permission
@ -84,6 +86,50 @@ func (p *Processor) DomainPermissionCreate(
} }
} }
// DomainPermissionUpdate updates a domain permission
// of the given permissionType, with the given ID.
func (p *Processor) DomainPermissionUpdate(
ctx context.Context,
permissionType gtsmodel.DomainPermissionType,
permID string,
obfuscate *bool,
publicComment *string,
privateComment *string,
subscriptionID *string,
) (*apimodel.DomainPermission, gtserror.WithCode) {
switch permissionType {
// Explicitly block a domain.
case gtsmodel.DomainPermissionBlock:
return p.updateDomainBlock(
ctx,
permID,
obfuscate,
publicComment,
privateComment,
subscriptionID,
)
// Explicitly allow a domain.
case gtsmodel.DomainPermissionAllow:
return p.updateDomainAllow(
ctx,
permID,
obfuscate,
publicComment,
privateComment,
subscriptionID,
)
// 🎵 Why don't we all strap bombs to our chests,
// and ride our bikes to the next G7 picnic?
// Seems easier with every clock-tick. 🎵
default:
err := gtserror.Newf("unrecognized permission type %d", permissionType)
return nil, gtserror.NewErrorInternalError(err)
}
}
// DomainPermissionDelete removes one domain block with the given ID, // DomainPermissionDelete removes one domain block with the given ID,
// and processes side effects of removing the block asynchronously. // and processes side effects of removing the block asynchronously.
// //
@ -153,14 +199,14 @@ func (p *Processor) DomainPermissionsImport(
} }
defer file.Close() defer file.Close()
// Parse file as slice of domain blocks. // Parse file as slice of domain permissions.
domainPerms := make([]*apimodel.DomainPermission, 0) apiDomainPerms := make([]*apimodel.DomainPermission, 0)
if err := json.NewDecoder(file).Decode(&domainPerms); err != nil { if err := json.NewDecoder(file).Decode(&apiDomainPerms); err != nil {
err = gtserror.Newf("error parsing attachment as domain permissions: %w", err) err = gtserror.Newf("error parsing attachment as domain permissions: %w", err)
return nil, gtserror.NewErrorBadRequest(err, err.Error()) return nil, gtserror.NewErrorBadRequest(err, err.Error())
} }
count := len(domainPerms) count := len(apiDomainPerms)
if count == 0 { if count == 0 {
err = gtserror.New("error importing domain permissions: 0 entries provided") err = gtserror.New("error importing domain permissions: 0 entries provided")
return nil, gtserror.NewErrorBadRequest(err, err.Error()) return nil, gtserror.NewErrorBadRequest(err, err.Error())
@ -170,52 +216,97 @@ func (p *Processor) DomainPermissionsImport(
// between successes and errors so that the caller can // between successes and errors so that the caller can
// try failed imports again if desired. // try failed imports again if desired.
multiStatusEntries := make([]apimodel.MultiStatusEntry, 0, count) multiStatusEntries := make([]apimodel.MultiStatusEntry, 0, count)
for _, apiDomainPerm := range apiDomainPerms {
for _, domainPerm := range domainPerms { multiStatusEntries = append(
var ( multiStatusEntries,
domain = domainPerm.Domain.Domain p.importOrUpdateDomainPerm(
obfuscate = domainPerm.Obfuscate
publicComment = domainPerm.PublicComment
privateComment = domainPerm.PrivateComment
subscriptionID = "" // No sub ID for imports.
errWithCode gtserror.WithCode
)
domainPerm, _, errWithCode = p.DomainPermissionCreate(
ctx, ctx,
permissionType, permissionType,
account, account,
domain, apiDomainPerm,
),
)
}
return apimodel.NewMultiStatus(multiStatusEntries), nil
}
func (p *Processor) importOrUpdateDomainPerm(
ctx context.Context,
permType gtsmodel.DomainPermissionType,
account *gtsmodel.Account,
apiDomainPerm *apimodel.DomainPermission,
) apimodel.MultiStatusEntry {
var (
domain = apiDomainPerm.Domain.Domain
obfuscate = apiDomainPerm.Obfuscate
publicComment = cmp.Or(apiDomainPerm.PublicComment, apiDomainPerm.Comment)
privateComment = apiDomainPerm.PrivateComment
subscriptionID = "" // No sub ID for imports.
)
// Check if this domain
// perm already exists.
var (
domainPerm gtsmodel.DomainPermission
err error
)
if permType == gtsmodel.DomainPermissionBlock {
domainPerm, err = p.state.DB.GetDomainBlock(ctx, domain)
} else {
domainPerm, err = p.state.DB.GetDomainAllow(ctx, domain)
}
if err != nil && !errors.Is(err, db.ErrNoEntries) {
// Real db error.
return apimodel.MultiStatusEntry{
Resource: domain,
Message: "db error checking for existence of domain permission",
Status: http.StatusInternalServerError,
}
}
var errWithCode gtserror.WithCode
if domainPerm != nil {
// Permission already exists, update it.
apiDomainPerm, errWithCode = p.DomainPermissionUpdate(
ctx,
permType,
domainPerm.GetID(),
obfuscate, obfuscate,
publicComment, publicComment,
privateComment, privateComment,
nil,
)
} else {
// Permission didn't exist yet, create it.
apiDomainPerm, _, errWithCode = p.DomainPermissionCreate(
ctx,
permType,
account,
domain,
util.PtrOrZero(obfuscate),
util.PtrOrZero(publicComment),
util.PtrOrZero(privateComment),
subscriptionID, subscriptionID,
) )
}
var entry *apimodel.MultiStatusEntry
if errWithCode != nil { if errWithCode != nil {
entry = &apimodel.MultiStatusEntry{ return apimodel.MultiStatusEntry{
// Use the failed domain entry as the resource value.
Resource: domain, Resource: domain,
Message: errWithCode.Safe(), Message: errWithCode.Safe(),
Status: errWithCode.Code(), Status: errWithCode.Code(),
} }
} else { }
entry = &apimodel.MultiStatusEntry{
// Use successfully created API model domain block as the resource value. return apimodel.MultiStatusEntry{
Resource: domainPerm, Resource: apiDomainPerm,
Message: http.StatusText(http.StatusOK), Message: http.StatusText(http.StatusOK),
Status: http.StatusOK, Status: http.StatusOK,
} }
} }
multiStatusEntries = append(multiStatusEntries, *entry)
}
return apimodel.NewMultiStatus(multiStatusEntries), nil
}
// DomainPermissionsGet returns all existing domain // DomainPermissionsGet returns all existing domain
// permissions of the requested type. If export is // permissions of the requested type. If export is
// true, the format will be suitable for writing out // true, the format will be suitable for writing out

View file

@ -108,7 +108,7 @@ func (p *Processor) InstancePeersGet(ctx context.Context, includeSuspended bool,
domains = append(domains, &apimodel.Domain{ domains = append(domains, &apimodel.Domain{
Domain: d, Domain: d,
SuspendedAt: util.FormatISO8601(domainBlock.CreatedAt), SuspendedAt: util.FormatISO8601(domainBlock.CreatedAt),
PublicComment: domainBlock.PublicComment, Comment: &domainBlock.PublicComment,
}) })
} }
} }

View file

@ -490,7 +490,7 @@ func (p *Processor) byURI(
if includeAccounts(queryType) { if includeAccounts(queryType) {
// Check if URI points to an account. // Check if URI points to an account.
foundAccount, err := p.accountByURI(ctx, requestingAccount, uri, resolve) foundAccounts, err := p.accountsByURI(ctx, requestingAccount, uri, resolve)
if err != nil { if err != nil {
// Check for semi-expected error types. // Check for semi-expected error types.
// On one of these, we can continue. // On one of these, we can continue.
@ -508,7 +508,9 @@ func (p *Processor) byURI(
} else { } else {
// Hit! Return early since it's extremely unlikely // Hit! Return early since it's extremely unlikely
// a status and an account will have the same URL. // a status and an account will have the same URL.
for _, foundAccount := range foundAccounts {
appendAccount(foundAccount) appendAccount(foundAccount)
}
return nil return nil
} }
} }
@ -544,35 +546,42 @@ func (p *Processor) byURI(
return nil return nil
} }
// accountByURI looks for one account with the given URI. // accountsByURI looks for one account with the given URI/ID,
// then if nothing is found, multiple accounts with the given URL.
//
// If resolve is false, it will only look in the database. // If resolve is false, it will only look in the database.
// If resolve is true, it will try to resolve the account // If resolve is true, it will try to resolve the account
// from remote using the URI, if necessary. // from remote using the URI, if necessary.
// //
// Will return either a hit, ErrNotRetrievable, ErrWrongType, // Will return either a hit, ErrNotRetrievable, ErrWrongType,
// or a real error that the caller should handle. // or a real error that the caller should handle.
func (p *Processor) accountByURI( func (p *Processor) accountsByURI(
ctx context.Context, ctx context.Context,
requestingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account,
uri *url.URL, uri *url.URL,
resolve bool, resolve bool,
) (*gtsmodel.Account, error) { ) ([]*gtsmodel.Account, error) {
if resolve { if resolve {
// We're allowed to resolve, leave the // We're allowed to resolve, leave the
// rest up to the dereferencer functions. // rest up to the dereferencer functions.
//
// Allow dereferencing by URL and not just URI;
// there are many cases where someone might
// paste a URL into the search bar.
account, _, err := p.federator.GetAccountByURI( account, _, err := p.federator.GetAccountByURI(
gtscontext.SetFastFail(ctx), gtscontext.SetFastFail(ctx),
requestingAccount.Username, requestingAccount.Username,
uri, uri,
true,
) )
return account, err return []*gtsmodel.Account{account}, err
} }
// We're not allowed to resolve; search database only. // We're not allowed to resolve; search database only.
uriStr := uri.String() // stringify uri just once uriStr := uri.String() // stringify uri just once
// Search by ActivityPub URI. // Search for single acct by ActivityPub URI.
account, err := p.state.DB.GetAccountByURI(ctx, uriStr) account, err := p.state.DB.GetAccountByURI(ctx, uriStr)
if err != nil && !errors.Is(err, db.ErrNoEntries) { if err != nil && !errors.Is(err, db.ErrNoEntries) {
err = gtserror.Newf("error checking database for account using URI %s: %w", uriStr, err) err = gtserror.Newf("error checking database for account using URI %s: %w", uriStr, err)
@ -581,22 +590,22 @@ func (p *Processor) accountByURI(
if account != nil { if account != nil {
// We got a hit! No need to continue. // We got a hit! No need to continue.
return account, nil return []*gtsmodel.Account{account}, nil
} }
// No hit yet. Fallback to try by URL. // No hit yet. Fallback to look for any accounts with URL.
account, err = p.state.DB.GetAccountByURL(ctx, uriStr) accounts, err := p.state.DB.GetAccountsByURL(ctx, uriStr)
if err != nil && !errors.Is(err, db.ErrNoEntries) { if err != nil && !errors.Is(err, db.ErrNoEntries) {
err = gtserror.Newf("error checking database for account using URL %s: %w", uriStr, err) err = gtserror.Newf("error checking database for accounts using URL %s: %w", uriStr, err)
return nil, err return nil, err
} }
if account != nil { if len(accounts) != 0 {
// We got a hit! No need to continue. // We got hits! No need to continue.
return account, nil return accounts, nil
} }
err = fmt.Errorf("account %s could not be retrieved locally and we cannot resolve", uriStr) err = fmt.Errorf("account(s) %s could not be retrieved locally and we cannot resolve", uriStr)
return nil, gtserror.SetUnretrievable(err) return nil, gtserror.SetUnretrievable(err)
} }

View file

@ -303,10 +303,13 @@ func (p *fediAPI) MoveAccount(ctx context.Context, fMsg *messages.FromFediAPI) e
} }
// Account to which the Move is taking place. // Account to which the Move is taking place.
//
// Match by uri only.
targetAcct, targetAcctable, err := p.federate.GetAccountByURI( targetAcct, targetAcctable, err := p.federate.GetAccountByURI(
ctx, ctx,
fMsg.Receiving.Username, fMsg.Receiving.Username,
targetAcctURI, targetAcctURI,
false,
) )
if err != nil { if err != nil {
return gtserror.Newf( return gtserror.Newf(

View file

@ -438,7 +438,7 @@ func (s *Subscriptions) processDomainPermission(
Obfuscate: wantedPerm.GetObfuscate(), Obfuscate: wantedPerm.GetObfuscate(),
SubscriptionID: permSub.ID, SubscriptionID: permSub.ID,
} }
insertF = func() error { return s.state.DB.CreateDomainBlock(ctx, domainBlock) } insertF = func() error { return s.state.DB.PutDomainBlock(ctx, domainBlock) }
action = &gtsmodel.AdminAction{ action = &gtsmodel.AdminAction{
ID: id.NewULID(), ID: id.NewULID(),
@ -461,7 +461,7 @@ func (s *Subscriptions) processDomainPermission(
Obfuscate: wantedPerm.GetObfuscate(), Obfuscate: wantedPerm.GetObfuscate(),
SubscriptionID: permSub.ID, SubscriptionID: permSub.ID,
} }
insertF = func() error { return s.state.DB.CreateDomainAllow(ctx, domainAllow) } insertF = func() error { return s.state.DB.PutDomainAllow(ctx, domainAllow) }
action = &gtsmodel.AdminAction{ action = &gtsmodel.AdminAction{
ID: id.NewULID(), ID: id.NewULID(),
@ -564,13 +564,13 @@ func permsFromCSV(
for i, columnHeader := range columnHeaders { for i, columnHeader := range columnHeaders {
// Remove leading # if present. // Remove leading # if present.
normal := strings.TrimLeft(columnHeader, "#") columnHeader = strings.TrimLeft(columnHeader, "#")
// Find index of each column header we // Find index of each column header we
// care about, ensuring no duplicates. // care about, ensuring no duplicates.
switch normal { switch {
case "domain": case columnHeader == "domain":
if domainI != nil { if domainI != nil {
body.Close() body.Close()
err := gtserror.NewfAt(3, "duplicate domain column header in csv: %+v", columnHeaders) err := gtserror.NewfAt(3, "duplicate domain column header in csv: %+v", columnHeaders)
@ -578,7 +578,7 @@ func permsFromCSV(
} }
domainI = &i domainI = &i
case "severity": case columnHeader == "severity":
if severityI != nil { if severityI != nil {
body.Close() body.Close()
err := gtserror.NewfAt(3, "duplicate severity column header in csv: %+v", columnHeaders) err := gtserror.NewfAt(3, "duplicate severity column header in csv: %+v", columnHeaders)
@ -586,15 +586,15 @@ func permsFromCSV(
} }
severityI = &i severityI = &i
case "public_comment": case columnHeader == "public_comment" || columnHeader == "comment":
if publicCommentI != nil { if publicCommentI != nil {
body.Close() body.Close()
err := gtserror.NewfAt(3, "duplicate public_comment column header in csv: %+v", columnHeaders) err := gtserror.NewfAt(3, "duplicate public_comment or comment column header in csv: %+v", columnHeaders)
return nil, err return nil, err
} }
publicCommentI = &i publicCommentI = &i
case "obfuscate": case columnHeader == "obfuscate":
if obfuscateI != nil { if obfuscateI != nil {
body.Close() body.Close()
err := gtserror.NewfAt(3, "duplicate obfuscate column header in csv: %+v", columnHeaders) err := gtserror.NewfAt(3, "duplicate obfuscate column header in csv: %+v", columnHeaders)
@ -674,15 +674,15 @@ func permsFromCSV(
perm.SetPublicComment(record[*publicCommentI]) perm.SetPublicComment(record[*publicCommentI])
} }
var obfuscate bool
if obfuscateI != nil { if obfuscateI != nil {
obfuscate, err := strconv.ParseBool(record[*obfuscateI]) obfuscate, err = strconv.ParseBool(record[*obfuscateI])
if err != nil { if err != nil {
l.Warnf("couldn't parse obfuscate field of record: %+v", record) l.Warnf("couldn't parse obfuscate field of record: %+v", record)
continue continue
} }
perm.SetObfuscate(&obfuscate)
} }
perm.SetObfuscate(&obfuscate)
// We're done. // We're done.
perms = append(perms, perm) perms = append(perms, perm)
@ -742,8 +742,9 @@ func permsFromJSON(
} }
// Set remaining fields. // Set remaining fields.
perm.SetPublicComment(apiPerm.PublicComment) publicComment := cmp.Or(apiPerm.PublicComment, apiPerm.Comment)
perm.SetObfuscate(&apiPerm.Obfuscate) perm.SetPublicComment(util.PtrOrZero(publicComment))
perm.SetObfuscate(util.Ptr(util.PtrOrZero(apiPerm.Obfuscate)))
// We're done. // We're done.
perms = append(perms, perm) perms = append(perms, perm)
@ -792,9 +793,15 @@ func permsFromPlain(
var perm gtsmodel.DomainPermission var perm gtsmodel.DomainPermission
switch permType { switch permType {
case gtsmodel.DomainPermissionBlock: case gtsmodel.DomainPermissionBlock:
perm = &gtsmodel.DomainBlock{Domain: domain} perm = &gtsmodel.DomainBlock{
Domain: domain,
Obfuscate: util.Ptr(false),
}
case gtsmodel.DomainPermissionAllow: case gtsmodel.DomainPermissionAllow:
perm = &gtsmodel.DomainAllow{Domain: domain} perm = &gtsmodel.DomainAllow{
Domain: domain,
Obfuscate: util.Ptr(false),
}
} }
// We're done. // We're done.

View file

@ -775,7 +775,7 @@ func (suite *SubscriptionsTestSuite) TestAdoption() {
existingBlock2, existingBlock2,
existingBlock3, existingBlock3,
} { } {
if err := testStructs.State.DB.CreateDomainBlock( if err := testStructs.State.DB.PutDomainBlock(
ctx, block, ctx, block,
); err != nil { ); err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
@ -876,7 +876,7 @@ func (suite *SubscriptionsTestSuite) TestDomainAllowsAndBlocks() {
} }
// Store existing allow. // Store existing allow.
if err := testStructs.State.DB.CreateDomainAllow(ctx, existingAllow); err != nil { if err := testStructs.State.DB.PutDomainAllow(ctx, existingAllow); err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }

View file

@ -103,8 +103,7 @@ func (suite *ImportMinimalTestSuite) TestImportMinimalOK() {
suite.Equal(testAccountBefore.DisplayName, testAccountAfter.DisplayName) suite.Equal(testAccountBefore.DisplayName, testAccountAfter.DisplayName)
suite.Equal(testAccountBefore.Note, testAccountAfter.Note) suite.Equal(testAccountBefore.Note, testAccountAfter.Note)
suite.Equal(testAccountBefore.NoteRaw, testAccountAfter.NoteRaw) suite.Equal(testAccountBefore.NoteRaw, testAccountAfter.NoteRaw)
suite.Equal(testAccountBefore.Memorial, testAccountAfter.Memorial) suite.Equal(testAccountBefore.MemorializedAt, testAccountAfter.MemorializedAt)
suite.Equal(testAccountBefore.Bot, testAccountAfter.Bot)
suite.Equal(testAccountBefore.Locked, testAccountAfter.Locked) suite.Equal(testAccountBefore.Locked, testAccountAfter.Locked)
suite.Equal(testAccountBefore.URI, testAccountAfter.URI) suite.Equal(testAccountBefore.URI, testAccountAfter.URI)
suite.Equal(testAccountBefore.URL, testAccountAfter.URL) suite.Equal(testAccountBefore.URL, testAccountAfter.URL)

View file

@ -34,8 +34,6 @@ type Account struct {
DisplayName string `json:"displayName,omitempty" bun:",nullzero"` DisplayName string `json:"displayName,omitempty" bun:",nullzero"`
Note string `json:"note,omitempty" bun:",nullzero"` Note string `json:"note,omitempty" bun:",nullzero"`
NoteRaw string `json:"noteRaw,omitempty" bun:",nullzero"` NoteRaw string `json:"noteRaw,omitempty" bun:",nullzero"`
Memorial *bool `json:"memorial"`
Bot *bool `json:"bot"`
Locked *bool `json:"locked"` Locked *bool `json:"locked"`
Discoverable *bool `json:"discoverable"` Discoverable *bool `json:"discoverable"`
URI string `json:"uri" bun:",nullzero"` URI string `json:"uri" bun:",nullzero"`
@ -45,7 +43,7 @@ type Account struct {
FollowingURI string `json:"followingUri" bun:",nullzero"` FollowingURI string `json:"followingUri" bun:",nullzero"`
FollowersURI string `json:"followersUri" bun:",nullzero"` FollowersURI string `json:"followersUri" bun:",nullzero"`
FeaturedCollectionURI string `json:"featuredCollectionUri" bun:",nullzero"` FeaturedCollectionURI string `json:"featuredCollectionUri" bun:",nullzero"`
ActorType string `json:"actorType" bun:",nullzero"` ActorType int16 `json:"actorType" bun:",nullzero"`
PrivateKey *rsa.PrivateKey `json:"-" mapstructure:"-"` PrivateKey *rsa.PrivateKey `json:"-" mapstructure:"-"`
PrivateKeyString string `json:"privateKey,omitempty" mapstructure:"privateKey" bun:"-"` PrivateKeyString string `json:"privateKey,omitempty" mapstructure:"privateKey" bun:"-"`
PublicKey *rsa.PublicKey `json:"-" mapstructure:"-"` PublicKey *rsa.PublicKey `json:"-" mapstructure:"-"`

View file

@ -70,19 +70,10 @@ func (c *Converter) ASRepresentationToAccount(
acct.URI = uri acct.URI = uri
// Check whether account is a usable actor type. // Check whether account is a usable actor type.
switch acct.ActorType = accountable.GetTypeName(); acct.ActorType { actorTypeName := accountable.GetTypeName()
acct.ActorType = gtsmodel.ParseAccountActorType(actorTypeName)
// people, groups, and organizations aren't bots if acct.ActorType == gtsmodel.AccountActorTypeUnknown {
case ap.ActorPerson, ap.ActorGroup, ap.ActorOrganization: err := gtserror.Newf("unusable actor type %s for %s", actorTypeName, uri)
acct.Bot = util.Ptr(false)
// apps and services are
case ap.ActorApplication, ap.ActorService:
acct.Bot = util.Ptr(true)
// we don't know what this is!
default:
err := gtserror.Newf("unusable actor type for %s", uri)
return nil, gtserror.SetMalformed(err) return nil, gtserror.SetMalformed(err)
} }
@ -161,7 +152,7 @@ func (c *Converter) ASRepresentationToAccount(
acct.Note = ap.ExtractSummary(accountable) acct.Note = ap.ExtractSummary(accountable)
// Assume not memorial (todo) // Assume not memorial (todo)
acct.Memorial = util.Ptr(false) acct.MemorializedAt = time.Time{}
// Extract 'manuallyApprovesFollowers' aka locked account (default = true). // Extract 'manuallyApprovesFollowers' aka locked account (default = true).
manuallyApprovesFollowers := ap.GetManuallyApprovesFollowers(accountable) manuallyApprovesFollowers := ap.GetManuallyApprovesFollowers(accountable)

View file

@ -204,7 +204,6 @@ func (suite *ASToInternalTestSuite) TestParseOwncastService() {
suite.Equal("https://owncast.example.org/logo/external", acct.HeaderRemoteURL) suite.Equal("https://owncast.example.org/logo/external", acct.HeaderRemoteURL)
suite.Equal("Rob's Owncast Server", acct.DisplayName) suite.Equal("Rob's Owncast Server", acct.DisplayName)
suite.Equal("linux audio stuff", acct.Note) suite.Equal("linux audio stuff", acct.Note)
suite.True(*acct.Bot)
suite.False(*acct.Locked) suite.False(*acct.Locked)
suite.True(*acct.Discoverable) suite.True(*acct.Discoverable)
suite.Equal("https://owncast.example.org/federation/user/rgh", acct.URI) suite.Equal("https://owncast.example.org/federation/user/rgh", acct.URI)
@ -212,7 +211,7 @@ func (suite *ASToInternalTestSuite) TestParseOwncastService() {
suite.Equal("https://owncast.example.org/federation/user/rgh/inbox", acct.InboxURI) suite.Equal("https://owncast.example.org/federation/user/rgh/inbox", acct.InboxURI)
suite.Equal("https://owncast.example.org/federation/user/rgh/outbox", acct.OutboxURI) suite.Equal("https://owncast.example.org/federation/user/rgh/outbox", acct.OutboxURI)
suite.Equal("https://owncast.example.org/federation/user/rgh/followers", acct.FollowersURI) suite.Equal("https://owncast.example.org/federation/user/rgh/followers", acct.FollowersURI)
suite.Equal("Service", acct.ActorType) suite.Equal(gtsmodel.AccountActorTypeService, acct.ActorType)
suite.Equal("https://owncast.example.org/federation/user/rgh#main-key", acct.PublicKeyURI) suite.Equal("https://owncast.example.org/federation/user/rgh#main-key", acct.PublicKeyURI)
acct.ID = "01G42D57DTCJQE8XT9KD4K88RK" acct.ID = "01G42D57DTCJQE8XT9KD4K88RK"

View file

@ -36,7 +36,6 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/log"
"github.com/superseriousbusiness/gotosocial/internal/uris" "github.com/superseriousbusiness/gotosocial/internal/uris"
"github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/internal/util/xslices" "github.com/superseriousbusiness/gotosocial/internal/util/xslices"
) )
@ -49,7 +48,7 @@ func (c *Converter) AccountToAS(
// accountable is a service if this // accountable is a service if this
// is a bot account, otherwise a person. // is a bot account, otherwise a person.
var accountable ap.Accountable var accountable ap.Accountable
if util.PtrOrZero(a.Bot) { if a.ActorType.IsBot() {
accountable = streams.NewActivityStreamsService() accountable = streams.NewActivityStreamsService()
} else { } else {
accountable = streams.NewActivityStreamsPerson() accountable = streams.NewActivityStreamsPerson()
@ -393,7 +392,7 @@ func (c *Converter) AccountToASMinimal(
// accountable is a service if this // accountable is a service if this
// is a bot account, otherwise a person. // is a bot account, otherwise a person.
var accountable ap.Accountable var accountable ap.Accountable
if util.PtrOrZero(a.Bot) { if a.ActorType.IsBot() {
accountable = streams.NewActivityStreamsService() accountable = streams.NewActivityStreamsService()
} else { } else {
accountable = streams.NewActivityStreamsPerson() accountable = streams.NewActivityStreamsPerson()

View file

@ -27,7 +27,6 @@ import (
"github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/ap"
"github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/db"
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
"github.com/superseriousbusiness/gotosocial/internal/util"
"github.com/superseriousbusiness/gotosocial/testrig" "github.com/superseriousbusiness/gotosocial/testrig"
) )
@ -100,7 +99,7 @@ func (suite *InternalToASTestSuite) TestAccountToASBot() {
*testAccount = *suite.testAccounts["local_account_1"] // take zork for this test *testAccount = *suite.testAccounts["local_account_1"] // take zork for this test
// Update zork to be a bot. // Update zork to be a bot.
testAccount.Bot = util.Ptr(true) testAccount.ActorType = gtsmodel.AccountActorTypeService
if err := suite.state.DB.UpdateAccount(context.Background(), testAccount); err != nil { if err := suite.state.DB.UpdateAccount(context.Background(), testAccount); err != nil {
suite.FailNow(err.Error()) suite.FailNow(err.Error())
} }

View file

@ -365,7 +365,6 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
var ( var (
locked = util.PtrOrValue(a.Locked, true) locked = util.PtrOrValue(a.Locked, true)
discoverable = util.PtrOrValue(a.Discoverable, false) discoverable = util.PtrOrValue(a.Discoverable, false)
bot = util.PtrOrValue(a.Bot, false)
) )
// Remaining properties are simple and // Remaining properties are simple and
@ -378,7 +377,7 @@ func (c *Converter) accountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A
DisplayName: a.DisplayName, DisplayName: a.DisplayName,
Locked: locked, Locked: locked,
Discoverable: discoverable, Discoverable: discoverable,
Bot: bot, Bot: a.ActorType.IsBot(),
CreatedAt: util.FormatISO8601(a.CreatedAt), CreatedAt: util.FormatISO8601(a.CreatedAt),
Note: a.Note, Note: a.Note,
URL: a.URL, URL: a.URL,
@ -522,7 +521,7 @@ func (c *Converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel.
ID: a.ID, ID: a.ID,
Username: a.Username, Username: a.Username,
Acct: acct, Acct: acct,
Bot: *a.Bot, Bot: a.ActorType.IsBot(),
CreatedAt: util.FormatISO8601(a.CreatedAt), CreatedAt: util.FormatISO8601(a.CreatedAt),
URL: a.URL, URL: a.URL,
// Empty array (not nillable). // Empty array (not nillable).
@ -2186,7 +2185,7 @@ func (c *Converter) DomainPermToAPIDomainPerm(
domainPerm := &apimodel.DomainPermission{ domainPerm := &apimodel.DomainPermission{
Domain: apimodel.Domain{ Domain: apimodel.Domain{
Domain: domain, Domain: domain,
PublicComment: d.GetPublicComment(), PublicComment: util.Ptr(d.GetPublicComment()),
}, },
} }
@ -2197,8 +2196,8 @@ func (c *Converter) DomainPermToAPIDomainPerm(
} }
domainPerm.ID = d.GetID() domainPerm.ID = d.GetID()
domainPerm.Obfuscate = util.PtrOrZero(d.GetObfuscate()) domainPerm.Obfuscate = d.GetObfuscate()
domainPerm.PrivateComment = d.GetPrivateComment() domainPerm.PrivateComment = util.Ptr(d.GetPrivateComment())
domainPerm.SubscriptionID = d.GetSubscriptionID() domainPerm.SubscriptionID = d.GetSubscriptionID()
domainPerm.CreatedBy = d.GetCreatedByAccountID() domainPerm.CreatedBy = d.GetCreatedByAccountID()
if createdAt := d.GetCreatedAt(); !createdAt.IsZero() { if createdAt := d.GetCreatedAt(); !createdAt.IsZero() {

View file

@ -404,7 +404,7 @@ func (suite *InternalToFrontendTestSuite) TestLocalInstanceAccountToFrontendPubl
"display_name": "", "display_name": "",
"locked": false, "locked": false,
"discoverable": true, "discoverable": true,
"bot": false, "bot": true,
"created_at": "2020-05-17T13:10:59.000Z", "created_at": "2020-05-17T13:10:59.000Z",
"note": "", "note": "",
"url": "http://localhost:8080/@localhost:8080", "url": "http://localhost:8080/@localhost:8080",
@ -444,7 +444,7 @@ func (suite *InternalToFrontendTestSuite) TestLocalInstanceAccountToFrontendBloc
"display_name": "", "display_name": "",
"locked": false, "locked": false,
"discoverable": false, "discoverable": false,
"bot": false, "bot": true,
"created_at": "2020-05-17T13:10:59.000Z", "created_at": "2020-05-17T13:10:59.000Z",
"note": "", "note": "",
"url": "http://localhost:8080/@localhost:8080", "url": "http://localhost:8080/@localhost:8080",

View file

@ -19,9 +19,11 @@ package util
import "time" import "time"
// ISO8601 is a formatter for serializing times that forces ISO8601 behavior. const (
const ISO8601 = "2006-01-02T15:04:05.000Z" ISO8601 = "2006-01-02T15:04:05.000Z"
const ISO8601Date = "2006-01-02" ISO8601Date = "2006-01-02"
RFC2822 = "Mon, 02 Jan 2006 15:04:05 -0700"
)
// FormatISO8601 converts the given time to UTC and then formats it // FormatISO8601 converts the given time to UTC and then formats it
// using the ISO8601 const, which the Mastodon API is able to understand. // using the ISO8601 const, which the Mastodon API is able to understand.
@ -39,3 +41,11 @@ func FormatISO8601Date(t time.Time) string {
func ParseISO8601(in string) (time.Time, error) { func ParseISO8601(in string) (time.Time, error) {
return time.Parse(ISO8601, in) return time.Parse(ISO8601, in)
} }
// FormatRFC2822 converts the given time to local and then formats it using
// the RFC2822 const, which conforms with email Date header requirements.
//
// See: https://www.rfc-editor.org/rfc/rfc2822#section-3.3
func FormatRFC2822(t time.Time) string {
return t.Local().Format(RFC2822)
}

View file

@ -294,101 +294,60 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
"instance_account": { "instance_account": {
ID: "01AY6P665V14JJR0AFVRT7311Y", ID: "01AY6P665V14JJR0AFVRT7311Y",
Username: "localhost:8080", Username: "localhost:8080",
AvatarMediaAttachmentID: "",
HeaderMediaAttachmentID: "",
DisplayName: "",
Fields: []*gtsmodel.Field{},
Note: "",
NoteRaw: "",
Memorial: util.Ptr(false),
MovedToURI: "",
CreatedAt: TimeMustParse("2020-05-17T13:10:59Z"), CreatedAt: TimeMustParse("2020-05-17T13:10:59Z"),
UpdatedAt: TimeMustParse("2020-05-17T13:10:59Z"), UpdatedAt: TimeMustParse("2020-05-17T13:10:59Z"),
Bot: util.Ptr(false),
Locked: util.Ptr(false), Locked: util.Ptr(false),
Discoverable: util.Ptr(true), Discoverable: util.Ptr(true),
URI: "http://localhost:8080/users/localhost:8080", URI: "http://localhost:8080/users/localhost:8080",
URL: "http://localhost:8080/@localhost:8080", URL: "http://localhost:8080/@localhost:8080",
PublicKeyURI: "http://localhost:8080/users/localhost:8080#main-key", PublicKeyURI: "http://localhost:8080/users/localhost:8080#main-key",
FetchedAt: time.Time{},
InboxURI: "http://localhost:8080/users/localhost:8080/inbox", InboxURI: "http://localhost:8080/users/localhost:8080/inbox",
OutboxURI: "http://localhost:8080/users/localhost:8080/outbox", OutboxURI: "http://localhost:8080/users/localhost:8080/outbox",
FollowersURI: "http://localhost:8080/users/localhost:8080/followers", FollowersURI: "http://localhost:8080/users/localhost:8080/followers",
FollowingURI: "http://localhost:8080/users/localhost:8080/following", FollowingURI: "http://localhost:8080/users/localhost:8080/following",
FeaturedCollectionURI: "http://localhost:8080/users/localhost:8080/collections/featured", FeaturedCollectionURI: "http://localhost:8080/users/localhost:8080/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypeService,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
SuspensionOrigin: "",
}, },
"unconfirmed_account": { "unconfirmed_account": {
ID: "01F8MH0BBE4FHXPH513MBVFHB0", ID: "01F8MH0BBE4FHXPH513MBVFHB0",
Username: "weed_lord420", Username: "weed_lord420",
AvatarMediaAttachmentID: "",
HeaderMediaAttachmentID: "",
DisplayName: "",
Fields: []*gtsmodel.Field{},
Note: "",
Memorial: util.Ptr(false),
MovedToURI: "",
CreatedAt: TimeMustParse("2022-06-04T13:12:00Z"), CreatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"), UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Bot: util.Ptr(false),
Locked: util.Ptr(false), Locked: util.Ptr(false),
Discoverable: util.Ptr(false), Discoverable: util.Ptr(false),
URI: "http://localhost:8080/users/weed_lord420", URI: "http://localhost:8080/users/weed_lord420",
URL: "http://localhost:8080/@weed_lord420", URL: "http://localhost:8080/@weed_lord420",
FetchedAt: time.Time{},
InboxURI: "http://localhost:8080/users/weed_lord420/inbox", InboxURI: "http://localhost:8080/users/weed_lord420/inbox",
OutboxURI: "http://localhost:8080/users/weed_lord420/outbox", OutboxURI: "http://localhost:8080/users/weed_lord420/outbox",
FollowersURI: "http://localhost:8080/users/weed_lord420/followers", FollowersURI: "http://localhost:8080/users/weed_lord420/followers",
FollowingURI: "http://localhost:8080/users/weed_lord420/following", FollowingURI: "http://localhost:8080/users/weed_lord420/following",
FeaturedCollectionURI: "http://localhost:8080/users/weed_lord420/collections/featured", FeaturedCollectionURI: "http://localhost:8080/users/weed_lord420/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
PublicKeyURI: "http://localhost:8080/users/weed_lord420#main-key", PublicKeyURI: "http://localhost:8080/users/weed_lord420#main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
SuspensionOrigin: "",
Settings: settings["unconfirmed_account"], Settings: settings["unconfirmed_account"],
}, },
"admin_account": { "admin_account": {
ID: "01F8MH17FWEB39HZJ76B6VXSKF", ID: "01F8MH17FWEB39HZJ76B6VXSKF",
Username: "admin", Username: "admin",
AvatarMediaAttachmentID: "",
HeaderMediaAttachmentID: "",
DisplayName: "",
Fields: []*gtsmodel.Field{},
Note: "",
NoteRaw: "",
Memorial: util.Ptr(false),
MovedToURI: "",
CreatedAt: TimeMustParse("2022-05-17T13:10:59Z"), CreatedAt: TimeMustParse("2022-05-17T13:10:59Z"),
UpdatedAt: TimeMustParse("2022-05-17T13:10:59Z"), UpdatedAt: TimeMustParse("2022-05-17T13:10:59Z"),
Bot: util.Ptr(false),
Locked: util.Ptr(false), Locked: util.Ptr(false),
Discoverable: util.Ptr(true), Discoverable: util.Ptr(true),
URI: "http://localhost:8080/users/admin", URI: "http://localhost:8080/users/admin",
URL: "http://localhost:8080/@admin", URL: "http://localhost:8080/@admin",
PublicKeyURI: "http://localhost:8080/users/admin#main-key", PublicKeyURI: "http://localhost:8080/users/admin#main-key",
FetchedAt: time.Time{},
InboxURI: "http://localhost:8080/users/admin/inbox", InboxURI: "http://localhost:8080/users/admin/inbox",
OutboxURI: "http://localhost:8080/users/admin/outbox", OutboxURI: "http://localhost:8080/users/admin/outbox",
FollowersURI: "http://localhost:8080/users/admin/followers", FollowersURI: "http://localhost:8080/users/admin/followers",
FollowingURI: "http://localhost:8080/users/admin/following", FollowingURI: "http://localhost:8080/users/admin/following",
FeaturedCollectionURI: "http://localhost:8080/users/admin/collections/featured", FeaturedCollectionURI: "http://localhost:8080/users/admin/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
SuspensionOrigin: "",
Settings: settings["admin_account"], Settings: settings["admin_account"],
}, },
"local_account_1": { "local_account_1": {
@ -397,39 +356,28 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
AvatarMediaAttachmentID: "01F8MH58A357CV5K7R7TJMSH6S", AvatarMediaAttachmentID: "01F8MH58A357CV5K7R7TJMSH6S",
HeaderMediaAttachmentID: "01PFPMWK2FF0D9WMHEJHR07C3Q", HeaderMediaAttachmentID: "01PFPMWK2FF0D9WMHEJHR07C3Q",
DisplayName: "original zork (he/they)", DisplayName: "original zork (he/they)",
Fields: []*gtsmodel.Field{},
Note: "<p>hey yo this is my profile!</p>", Note: "<p>hey yo this is my profile!</p>",
NoteRaw: "hey yo this is my profile!", NoteRaw: "hey yo this is my profile!",
Memorial: util.Ptr(false),
MovedToURI: "",
CreatedAt: TimeMustParse("2022-05-20T11:09:18Z"), CreatedAt: TimeMustParse("2022-05-20T11:09:18Z"),
UpdatedAt: TimeMustParse("2022-05-20T11:09:18Z"), UpdatedAt: TimeMustParse("2022-05-20T11:09:18Z"),
Bot: util.Ptr(false),
Locked: util.Ptr(false), Locked: util.Ptr(false),
Discoverable: util.Ptr(true), Discoverable: util.Ptr(true),
URI: "http://localhost:8080/users/the_mighty_zork", URI: "http://localhost:8080/users/the_mighty_zork",
URL: "http://localhost:8080/@the_mighty_zork", URL: "http://localhost:8080/@the_mighty_zork",
FetchedAt: time.Time{},
InboxURI: "http://localhost:8080/users/the_mighty_zork/inbox", InboxURI: "http://localhost:8080/users/the_mighty_zork/inbox",
OutboxURI: "http://localhost:8080/users/the_mighty_zork/outbox", OutboxURI: "http://localhost:8080/users/the_mighty_zork/outbox",
FollowersURI: "http://localhost:8080/users/the_mighty_zork/followers", FollowersURI: "http://localhost:8080/users/the_mighty_zork/followers",
FollowingURI: "http://localhost:8080/users/the_mighty_zork/following", FollowingURI: "http://localhost:8080/users/the_mighty_zork/following",
FeaturedCollectionURI: "http://localhost:8080/users/the_mighty_zork/collections/featured", FeaturedCollectionURI: "http://localhost:8080/users/the_mighty_zork/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
PublicKeyURI: "http://localhost:8080/users/the_mighty_zork/main-key", PublicKeyURI: "http://localhost:8080/users/the_mighty_zork/main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
SuspensionOrigin: "",
Settings: settings["local_account_1"], Settings: settings["local_account_1"],
}, },
"local_account_2": { "local_account_2": {
ID: "01F8MH5NBDF2MV7CTC4Q5128HF", ID: "01F8MH5NBDF2MV7CTC4Q5128HF",
Username: "1happyturtle", Username: "1happyturtle",
AvatarMediaAttachmentID: "",
HeaderMediaAttachmentID: "",
DisplayName: "happy little turtle :3", DisplayName: "happy little turtle :3",
Fields: []*gtsmodel.Field{ Fields: []*gtsmodel.Field{
{ {
@ -453,29 +401,21 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
}, },
Note: "<p>i post about things that concern me</p>", Note: "<p>i post about things that concern me</p>",
NoteRaw: "i post about things that concern me", NoteRaw: "i post about things that concern me",
Memorial: util.Ptr(false),
MovedToURI: "",
CreatedAt: TimeMustParse("2022-06-04T13:12:00Z"), CreatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"), UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Bot: util.Ptr(false),
Locked: util.Ptr(true), Locked: util.Ptr(true),
Discoverable: util.Ptr(false), Discoverable: util.Ptr(false),
URI: "http://localhost:8080/users/1happyturtle", URI: "http://localhost:8080/users/1happyturtle",
URL: "http://localhost:8080/@1happyturtle", URL: "http://localhost:8080/@1happyturtle",
FetchedAt: time.Time{},
InboxURI: "http://localhost:8080/users/1happyturtle/inbox", InboxURI: "http://localhost:8080/users/1happyturtle/inbox",
OutboxURI: "http://localhost:8080/users/1happyturtle/outbox", OutboxURI: "http://localhost:8080/users/1happyturtle/outbox",
FollowersURI: "http://localhost:8080/users/1happyturtle/followers", FollowersURI: "http://localhost:8080/users/1happyturtle/followers",
FollowingURI: "http://localhost:8080/users/1happyturtle/following", FollowingURI: "http://localhost:8080/users/1happyturtle/following",
FeaturedCollectionURI: "http://localhost:8080/users/1happyturtle/collections/featured", FeaturedCollectionURI: "http://localhost:8080/users/1happyturtle/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
PublicKeyURI: "http://localhost:8080/users/1happyturtle#main-key", PublicKeyURI: "http://localhost:8080/users/1happyturtle#main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
SuspensionOrigin: "",
Settings: settings["local_account_2"], Settings: settings["local_account_2"],
}, },
"local_account_3": { "local_account_3": {
@ -483,7 +423,6 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
Username: "media_mogul", Username: "media_mogul",
AvatarMediaAttachmentID: "01JPHQZ0ZHC2AXJK1JQNXRXQZN", AvatarMediaAttachmentID: "01JPHQZ0ZHC2AXJK1JQNXRXQZN",
HeaderMediaAttachmentID: "01JPHRB7F2RXPTEQFRYC85EPD9", HeaderMediaAttachmentID: "01JPHRB7F2RXPTEQFRYC85EPD9",
DisplayName: "",
Fields: []*gtsmodel.Field{ Fields: []*gtsmodel.Field{
{ {
Name: "I'm going to post a lot of", Name: "I'm going to post a lot of",
@ -506,29 +445,21 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
}, },
Note: "<p>I'm a test account that posts a shitload of media and I have my account rendered in \"gallery\" mode</p>", Note: "<p>I'm a test account that posts a shitload of media and I have my account rendered in \"gallery\" mode</p>",
NoteRaw: "I'm a test account that posts a shitload of media and I have my account rendered in \"gallery\" mode", NoteRaw: "I'm a test account that posts a shitload of media and I have my account rendered in \"gallery\" mode",
Memorial: util.Ptr(false),
MovedToURI: "",
CreatedAt: TimeMustParse("2025-03-15T11:08:00Z"), CreatedAt: TimeMustParse("2025-03-15T11:08:00Z"),
UpdatedAt: TimeMustParse("2025-03-15T11:08:00Z"), UpdatedAt: TimeMustParse("2025-03-15T11:08:00Z"),
Bot: util.Ptr(false),
Locked: util.Ptr(false), Locked: util.Ptr(false),
Discoverable: util.Ptr(false), Discoverable: util.Ptr(false),
URI: "http://localhost:8080/users/media_mogul", URI: "http://localhost:8080/users/media_mogul",
URL: "http://localhost:8080/@media_mogul", URL: "http://localhost:8080/@media_mogul",
FetchedAt: time.Time{},
InboxURI: "http://localhost:8080/users/media_mogul/inbox", InboxURI: "http://localhost:8080/users/media_mogul/inbox",
OutboxURI: "http://localhost:8080/users/media_mogul/outbox", OutboxURI: "http://localhost:8080/users/media_mogul/outbox",
FollowersURI: "http://localhost:8080/users/media_mogul/followers", FollowersURI: "http://localhost:8080/users/media_mogul/followers",
FollowingURI: "http://localhost:8080/users/media_mogul/following", FollowingURI: "http://localhost:8080/users/media_mogul/following",
FeaturedCollectionURI: "http://localhost:8080/users/media_mogul/collections/featured", FeaturedCollectionURI: "http://localhost:8080/users/media_mogul/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
PublicKeyURI: "http://localhost:8080/users/media_mogul#main-key", PublicKeyURI: "http://localhost:8080/users/media_mogul#main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
SuspensionOrigin: "",
Settings: settings["local_account_3"], Settings: settings["local_account_3"],
}, },
"remote_account_1": { "remote_account_1": {
@ -536,109 +467,77 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
Username: "foss_satan", Username: "foss_satan",
Domain: "fossbros-anonymous.io", Domain: "fossbros-anonymous.io",
DisplayName: "big gerald", DisplayName: "big gerald",
Fields: []*gtsmodel.Field{},
Note: "i post about like, i dunno, stuff, or whatever!!!!", Note: "i post about like, i dunno, stuff, or whatever!!!!",
Memorial: util.Ptr(false),
MovedToURI: "",
CreatedAt: TimeMustParse("2021-09-26T12:52:36+02:00"), CreatedAt: TimeMustParse("2021-09-26T12:52:36+02:00"),
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"), UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Bot: util.Ptr(false),
Locked: util.Ptr(false), Locked: util.Ptr(false),
Discoverable: util.Ptr(true), Discoverable: util.Ptr(true),
URI: "http://fossbros-anonymous.io/users/foss_satan", URI: "http://fossbros-anonymous.io/users/foss_satan",
URL: "http://fossbros-anonymous.io/@foss_satan", URL: "http://fossbros-anonymous.io/@foss_satan",
FetchedAt: time.Time{},
InboxURI: "http://fossbros-anonymous.io/users/foss_satan/inbox", InboxURI: "http://fossbros-anonymous.io/users/foss_satan/inbox",
SharedInboxURI: util.Ptr("http://fossbros-anonymous.io/inbox"), SharedInboxURI: util.Ptr("http://fossbros-anonymous.io/inbox"),
OutboxURI: "http://fossbros-anonymous.io/users/foss_satan/outbox", OutboxURI: "http://fossbros-anonymous.io/users/foss_satan/outbox",
FollowersURI: "http://fossbros-anonymous.io/users/foss_satan/followers", FollowersURI: "http://fossbros-anonymous.io/users/foss_satan/followers",
FollowingURI: "http://fossbros-anonymous.io/users/foss_satan/following", FollowingURI: "http://fossbros-anonymous.io/users/foss_satan/following",
FeaturedCollectionURI: "http://fossbros-anonymous.io/users/foss_satan/collections/featured", FeaturedCollectionURI: "http://fossbros-anonymous.io/users/foss_satan/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
PublicKeyURI: "http://fossbros-anonymous.io/users/foss_satan/main-key", PublicKeyURI: "http://fossbros-anonymous.io/users/foss_satan/main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
SuspensionOrigin: "",
}, },
"remote_account_2": { "remote_account_2": {
ID: "01FHMQX3GAABWSM0S2VZEC2SWC", ID: "01FHMQX3GAABWSM0S2VZEC2SWC",
Username: "Some_User", Username: "Some_User",
Domain: "example.org", Domain: "example.org",
DisplayName: "some user", DisplayName: "some user",
Fields: []*gtsmodel.Field{},
Note: "i'm a real son of a gun", Note: "i'm a real son of a gun",
Memorial: util.Ptr(false),
MovedToURI: "",
CreatedAt: TimeMustParse("2020-08-10T14:13:28+02:00"), CreatedAt: TimeMustParse("2020-08-10T14:13:28+02:00"),
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"), UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Bot: util.Ptr(false),
Locked: util.Ptr(true), Locked: util.Ptr(true),
Discoverable: util.Ptr(true), Discoverable: util.Ptr(true),
URI: "http://example.org/users/Some_User", URI: "http://example.org/users/Some_User",
URL: "http://example.org/@Some_User", URL: "http://example.org/@Some_User",
FetchedAt: time.Time{},
InboxURI: "http://example.org/users/Some_User/inbox", InboxURI: "http://example.org/users/Some_User/inbox",
SharedInboxURI: util.Ptr(""), SharedInboxURI: util.Ptr(""),
OutboxURI: "http://example.org/users/Some_User/outbox", OutboxURI: "http://example.org/users/Some_User/outbox",
FollowersURI: "http://example.org/users/Some_User/followers", FollowersURI: "http://example.org/users/Some_User/followers",
FollowingURI: "http://example.org/users/Some_User/following", FollowingURI: "http://example.org/users/Some_User/following",
FeaturedCollectionURI: "http://example.org/users/Some_User/collections/featured", FeaturedCollectionURI: "http://example.org/users/Some_User/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
PublicKeyURI: "http://example.org/users/Some_User#main-key", PublicKeyURI: "http://example.org/users/Some_User#main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
SuspensionOrigin: "",
}, },
"remote_account_3": { "remote_account_3": {
ID: "062G5WYKY35KKD12EMSM3F8PJ8", ID: "062G5WYKY35KKD12EMSM3F8PJ8",
Username: "her_fuckin_maj", Username: "her_fuckin_maj",
Domain: "thequeenisstillalive.technology", Domain: "thequeenisstillalive.technology",
DisplayName: "lizzzieeeeeeeeeeee", DisplayName: "lizzzieeeeeeeeeeee",
Fields: []*gtsmodel.Field{},
Note: "if i die blame charles don't let that fuck become king", Note: "if i die blame charles don't let that fuck become king",
Memorial: util.Ptr(false),
MovedToURI: "",
CreatedAt: TimeMustParse("2020-08-10T14:13:28+02:00"), CreatedAt: TimeMustParse("2020-08-10T14:13:28+02:00"),
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"), UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Bot: util.Ptr(false),
Locked: util.Ptr(true), Locked: util.Ptr(true),
Discoverable: util.Ptr(true), Discoverable: util.Ptr(true),
URI: "http://thequeenisstillalive.technology/users/her_fuckin_maj", URI: "http://thequeenisstillalive.technology/users/her_fuckin_maj",
URL: "http://thequeenisstillalive.technology/@her_fuckin_maj", URL: "http://thequeenisstillalive.technology/@her_fuckin_maj",
FetchedAt: time.Time{},
InboxURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/inbox", InboxURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/inbox",
SharedInboxURI: util.Ptr(""), SharedInboxURI: util.Ptr(""),
OutboxURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/outbox", OutboxURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/outbox",
FollowersURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/followers", FollowersURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/followers",
FollowingURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/following", FollowingURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/following",
FeaturedCollectionURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/collections/featured", FeaturedCollectionURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
PublicKeyURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj#main-key", PublicKeyURI: "http://thequeenisstillalive.technology/users/her_fuckin_maj#main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
SuspensionOrigin: "",
HeaderMediaAttachmentID: "01PFPMWK2FF0D9WMHEJHR07C3R", HeaderMediaAttachmentID: "01PFPMWK2FF0D9WMHEJHR07C3R",
}, },
"remote_account_4": { "remote_account_4": {
ID: "07GZRBAEMBNKGZ8Z9VSKSXKR98", ID: "07GZRBAEMBNKGZ8Z9VSKSXKR98",
Username: "üser", Username: "üser",
Domain: "xn--xample-ova.org", Domain: "xn--xample-ova.org",
DisplayName: "",
Note: "",
Memorial: util.Ptr(false),
MovedToURI: "",
CreatedAt: TimeMustParse("2020-08-10T14:13:28+02:00"), CreatedAt: TimeMustParse("2020-08-10T14:13:28+02:00"),
UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"), UpdatedAt: TimeMustParse("2022-06-04T13:12:00Z"),
Bot: util.Ptr(false),
Locked: util.Ptr(false), Locked: util.Ptr(false),
Discoverable: util.Ptr(false), Discoverable: util.Ptr(false),
URI: "https://xn--xample-ova.org/users/%C3%BCser", URI: "https://xn--xample-ova.org/users/%C3%BCser",
@ -650,15 +549,10 @@ func NewTestAccounts() map[string]*gtsmodel.Account {
FollowersURI: "https://xn--xample-ova.org/users/%C3%BCser/followers", FollowersURI: "https://xn--xample-ova.org/users/%C3%BCser/followers",
FollowingURI: "https://xn--xample-ova.org/users/%C3%BCser/following", FollowingURI: "https://xn--xample-ova.org/users/%C3%BCser/following",
FeaturedCollectionURI: "https://xn--xample-ova.org/users/%C3%BCser/collections/featured", FeaturedCollectionURI: "https://xn--xample-ova.org/users/%C3%BCser/collections/featured",
ActorType: ap.ActorPerson, ActorType: gtsmodel.AccountActorTypePerson,
PrivateKey: &rsa.PrivateKey{}, PrivateKey: &rsa.PrivateKey{},
PublicKey: &rsa.PublicKey{}, PublicKey: &rsa.PublicKey{},
PublicKeyURI: "https://xn--xample-ova.org/users/%C3%BCser#main-key", PublicKeyURI: "https://xn--xample-ova.org/users/%C3%BCser#main-key",
SensitizedAt: time.Time{},
SilencedAt: time.Time{},
SuspendedAt: time.Time{},
SuspensionOrigin: "",
HeaderMediaAttachmentID: "",
}, },
} }

View file

@ -627,7 +627,7 @@ nothanks.com`
{ {
"domain": "bumfaces.net", "domain": "bumfaces.net",
"suspended_at": "2020-05-13T13:29:12.000Z", "suspended_at": "2020-05-13T13:29:12.000Z",
"public_comment": "big jerks" "comment": "big jerks"
}, },
{ {
"domain": "peepee.poopoo", "domain": "peepee.poopoo",

View file

@ -1,4 +1,4 @@
//go:build go1.19 || go1.20 || go1.21 || go1.22 || go1.23 //go:build go1.19 && !go1.25
package mangler package mangler

View file

@ -2,7 +2,6 @@ package mutexes
import ( import (
"sync" "sync"
"sync/atomic"
"unsafe" "unsafe"
"codeberg.org/gruf/go-mempool" "codeberg.org/gruf/go-mempool"
@ -185,34 +184,11 @@ func (mu *rwmutex) Unlock() bool {
// Fully unlocked. // Fully unlocked.
mu.t = 0 mu.t = 0
// NOTE: must remain in
// sync with runtime.notifyList{}.
//
// goexperiment.staticlockranking
// does change it slightly, but
// this does not alter the first
// 2 fields which are all we need.
type notifyList struct {
_ uint32
notify uint32
// ... other fields
}
// NOTE: must remain in
// sync with sync.Cond{}.
type syncCond struct {
_ struct{}
L sync.Locker
n notifyList
// ... other fields
}
// Awake all blocked goroutines and check // Awake all blocked goroutines and check
// for change in the last notified ticket. // for change in the last notified ticket.
cptr := (*syncCond)(unsafe.Pointer(&mu.c)) before := syncCond_last_ticket(&mu.c)
before := atomic.LoadUint32(&cptr.n.notify)
mu.c.Broadcast() // awakes all blocked! mu.c.Broadcast() // awakes all blocked!
after := atomic.LoadUint32(&cptr.n.notify) after := syncCond_last_ticket(&mu.c)
// If ticket changed, this indicates // If ticket changed, this indicates
// AT LEAST one goroutine was awoken. // AT LEAST one goroutine was awoken.

41
vendor/codeberg.org/gruf/go-mutexes/map_unsafe.go generated vendored Normal file
View file

@ -0,0 +1,41 @@
//go:build go1.22 && !go1.25
package mutexes
import (
"sync"
"sync/atomic"
"unsafe"
)
// syncCond_last_ticket is an unsafe function that returns
// the ticket of the last awoken / notified goroutine by a
// a sync.Cond{}. it relies on expected memory layout.
func syncCond_last_ticket(c *sync.Cond) uint32 {
// NOTE: must remain in
// sync with runtime.notifyList{}.
//
// goexperiment.staticlockranking
// does change it slightly, but
// this does not alter the first
// 2 fields which are all we need.
type notifyList struct {
_ atomic.Uint32
notify uint32
// ... other fields
}
// NOTE: must remain in
// sync with sync.Cond{}.
type syncCond struct {
_ struct{}
L sync.Locker
n notifyList
// ... other fields
}
// This field must be atomcially accessed.
cptr := (*syncCond)(unsafe.Pointer(c))
return atomic.LoadUint32(&cptr.n.notify)
}

View file

@ -1,5 +0,0 @@
## Timeline Todos
- optimize store() to operate on sorted list
- finish writing code comments

View file

@ -1,6 +1,7 @@
package structr package structr
import ( import (
"os"
"reflect" "reflect"
"strings" "strings"
"sync" "sync"
@ -244,6 +245,39 @@ func (i *Index) key(buf *byteutil.Buffer, parts []unsafe.Pointer) string {
return string(buf.B) return string(buf.B)
} }
// add will attempt to add given index entry to appropriate
// doubly-linked-list in index hashmap. in the case of an
// existing entry in a "unique" index, it will return false.
func (i *Index) add(key string, item *indexed_item) bool {
// Look for existing.
l := i.data.Get(key)
if l == nil {
// Allocate new.
l = new_list()
i.data.Put(key, l)
} else if is_unique(i.flags) {
// Collision!
return false
}
// Prepare new index entry.
entry := new_index_entry()
entry.item = item
entry.key = key
entry.index = i
// Add ourselves to item's index tracker.
item.indexed = append(item.indexed, entry)
// Add entry to index list.
l.push_front(&entry.elem)
return true
}
// append will append the given index entry to appropriate // append will append the given index entry to appropriate
// doubly-linked-list in index hashmap. this handles case of // doubly-linked-list in index hashmap. this handles case of
// overwriting "unique" index entries, and removes from given // overwriting "unique" index entries, and removes from given
@ -403,7 +437,8 @@ func new_index_entry() *index_entry {
func free_index_entry(entry *index_entry) { func free_index_entry(entry *index_entry) {
if entry.elem.next != nil || if entry.elem.next != nil ||
entry.elem.prev != nil { entry.elem.prev != nil {
should_not_reach(false) msg := assert("entry not in use")
os.Stderr.WriteString(msg + "\n")
return return
} }
entry.key = "" entry.key = ""

View file

@ -1,6 +1,7 @@
package structr package structr
import ( import (
"os"
"sync" "sync"
"unsafe" "unsafe"
) )
@ -37,7 +38,8 @@ func free_indexed_item(item *indexed_item) {
if len(item.indexed) > 0 || if len(item.indexed) > 0 ||
item.elem.next != nil || item.elem.next != nil ||
item.elem.prev != nil { item.elem.prev != nil {
should_not_reach(false) msg := assert("item not in use")
os.Stderr.WriteString(msg + "\n")
return return
} }
item.data = nil item.data = nil

View file

@ -1,6 +1,7 @@
package structr package structr
import ( import (
"os"
"sync" "sync"
"unsafe" "unsafe"
) )
@ -43,7 +44,8 @@ func free_list(list *list) {
if list.head != nil || if list.head != nil ||
list.tail != nil || list.tail != nil ||
list.len != 0 { list.len != 0 {
should_not_reach(false) msg := assert("list not in use")
os.Stderr.WriteString(msg + "\n")
return return
} }
list_pool.Put(list) list_pool.Put(list)

View file

@ -1,10 +1,9 @@
//go:build go1.22 || go1.23 //go:build go1.22 && !go1.25
package structr package structr
import ( import (
"fmt" "fmt"
"os"
"reflect" "reflect"
"runtime" "runtime"
"strings" "strings"
@ -140,7 +139,7 @@ func extract_fields(ptr unsafe.Pointer, fields []struct_field) []unsafe.Pointer
// Prepare slice of field value pointers. // Prepare slice of field value pointers.
ptrs := make([]unsafe.Pointer, len(fields)) ptrs := make([]unsafe.Pointer, len(fields))
if len(ptrs) != len(fields) { if len(ptrs) != len(fields) {
panic("BCE") panic(assert("BCE"))
} }
for i, field := range fields { for i, field := range fields {
@ -264,12 +263,12 @@ func panicf(format string, args ...any) {
panic(fmt.Sprintf(format, args...)) panic(fmt.Sprintf(format, args...))
} }
// should_not_reach can be called to indicated a // assert can be called to indicated a block
// block of code should not be able to be reached, // of code should not be able to be reached,
// else it prints callsite info with a BUG report. // it returns a BUG report with callsite.
// //
//go:noinline //go:noinline
func should_not_reach(exit bool) { func assert(assert string) string {
pcs := make([]uintptr, 1) pcs := make([]uintptr, 1)
_ = runtime.Callers(2, pcs) _ = runtime.Callers(2, pcs)
fn := runtime.FuncForPC(pcs[0]) fn := runtime.FuncForPC(pcs[0])
@ -280,9 +279,11 @@ func should_not_reach(exit bool) {
funcname = funcname[i+1:] funcname = funcname[i+1:]
} }
} }
if exit { var buf strings.Builder
panic("BUG: assertion failed in " + funcname) buf.Grow(32 + len(assert) + len(funcname))
} else { buf.WriteString("BUG: assertion \"")
os.Stderr.WriteString("BUG: assertion failed in " + funcname + "\n") buf.WriteString(assert)
} buf.WriteString("\" failed in ")
buf.WriteString(funcname)
return buf.String()
} }

View file

@ -2,6 +2,7 @@ package structr
import ( import (
"cmp" "cmp"
"os"
"reflect" "reflect"
"slices" "slices"
"sync" "sync"
@ -43,7 +44,7 @@ type TimelineConfig[StructType any, PK cmp.Ordered] struct {
// case only a single field is permitted, though // case only a single field is permitted, though
// it may be nested, and as described above the // it may be nested, and as described above the
// type must conform to cmp.Ordered. // type must conform to cmp.Ordered.
PKey string PKey IndexConfig
// Indices defines indices to create // Indices defines indices to create
// in the Timeline for the receiving // in the Timeline for the receiving
@ -103,14 +104,11 @@ func (t *Timeline[T, PK]) Init(config TimelineConfig[T, PK]) {
t.mutex.Lock() t.mutex.Lock()
defer t.mutex.Unlock() defer t.mutex.Unlock()
// The first index is created from PKey. // The first index is created from PKey,
// other indices are created as expected.
t.indices = make([]Index, len(config.Indices)+1) t.indices = make([]Index, len(config.Indices)+1)
t.indices[0].ptr = unsafe.Pointer(t) t.indices[0].ptr = unsafe.Pointer(t)
t.indices[0].init(rt, IndexConfig{ t.indices[0].init(rt, config.PKey, 0)
Fields: config.PKey,
AllowZero: true,
Multiple: true,
}, 0)
if len(t.indices[0].fields) > 1 { if len(t.indices[0].fields) > 1 {
panic("primary key must contain only 1 field") panic("primary key must contain only 1 field")
} }
@ -119,8 +117,7 @@ func (t *Timeline[T, PK]) Init(config TimelineConfig[T, PK]) {
t.indices[i+1].init(rt, cfg, 0) t.indices[i+1].init(rt, cfg, 0)
} }
// Before extracting // Extract pkey details from index.
// first index for pkey.
field := t.indices[0].fields[0] field := t.indices[0].fields[0]
t.pkey = pkey_field{ t.pkey = pkey_field{
rtype: field.rtype, rtype: field.rtype,
@ -207,7 +204,7 @@ func (t *Timeline[T, PK]) Insert(values ...T) {
// Allocate a slice of our value wrapping struct type. // Allocate a slice of our value wrapping struct type.
with_keys := make([]value_with_pk[T, PK], len(values)) with_keys := make([]value_with_pk[T, PK], len(values))
if len(with_keys) != len(values) { if len(with_keys) != len(values) {
panic("BCE") panic(assert("BCE"))
} }
// Range the provided values. // Range the provided values.
@ -387,6 +384,54 @@ func (t *Timeline[T, PK]) Range(dir Direction) func(yield func(T) bool) {
} }
} }
// RangeUnsafe is functionally similar to Range(), except it does not pass *copies* of
// data. It allows you to operate on the data directly and modify it. As such it can also
// be more performant to use this function, even for read-write operations.
//
// Please note that the entire Timeline{} will be locked for the duration of the range
// operation, i.e. from the beginning of the first yield call until the end of the last.
func (t *Timeline[T, PK]) RangeUnsafe(dir Direction) func(yield func(T) bool) {
return func(yield func(T) bool) {
if t.copy == nil {
panic("not initialized")
} else if yield == nil {
panic("nil func")
}
// Acquire lock.
t.mutex.Lock()
defer t.mutex.Unlock()
switch dir {
case Asc:
// Iterate through linked list from bottom (i.e. tail).
for prev := t.list.tail; prev != nil; prev = prev.prev {
// Extract item from list element.
item := (*timeline_item)(prev.data)
// Pass to given function.
if !yield(item.data.(T)) {
break
}
}
case Desc:
// Iterate through linked list from top (i.e. head).
for next := t.list.head; next != nil; next = next.next {
// Extract item from list element.
item := (*timeline_item)(next.data)
// Pass to given function.
if !yield(item.data.(T)) {
break
}
}
}
}
}
// RangeKeys will iterate over all values for given keys in the given index. // RangeKeys will iterate over all values for given keys in the given index.
// //
// Please note that the entire Timeline{} will be locked for the duration of the range // Please note that the entire Timeline{} will be locked for the duration of the range
@ -430,6 +475,48 @@ func (t *Timeline[T, PK]) RangeKeys(index *Index, keys ...Key) func(yield func(T
} }
} }
// RangeKeysUnsafe is functionally similar to RangeKeys(), except it does not pass *copies*
// of data. It allows you to operate on the data directly and modify it. As such it can also
// be more performant to use this function, even for read-write operations.
//
// Please note that the entire Timeline{} will be locked for the duration of the range
// operation, i.e. from the beginning of the first yield call until the end of the last.
func (t *Timeline[T, PK]) RangeKeysUnsafe(index *Index, keys ...Key) func(yield func(T) bool) {
return func(yield func(T) bool) {
if t.copy == nil {
panic("not initialized")
} else if index == nil {
panic("no index given")
} else if index.ptr != unsafe.Pointer(t) {
panic("invalid index for timeline")
} else if yield == nil {
panic("nil func")
}
// Acquire lock.
t.mutex.Lock()
defer t.mutex.Unlock()
for _, key := range keys {
var done bool
// Iterate over values in index under key.
index.get(key.key, func(i *indexed_item) {
// Cast to timeline_item type.
item := to_timeline_item(i)
// Pass value data to yield function.
done = done || !yield(item.data.(T))
})
if done {
break
}
}
}
}
// Trim will remove entries from the timeline in given // Trim will remove entries from the timeline in given
// direction, ensuring timeline is no larger than 'max'. // direction, ensuring timeline is no larger than 'max'.
// If 'max' >= t.Len(), this function is a no-op. // If 'max' >= t.Len(), this function is a no-op.
@ -538,7 +625,7 @@ func (t *Timeline[T, PK]) select_asc(min PK, max *PK, length *int) (values []T)
pkey := *(*PK)(item.pk) pkey := *(*PK)(item.pk)
// Check below min. // Check below min.
if pkey < min { if pkey <= min {
continue continue
} }
@ -661,7 +748,7 @@ func (t *Timeline[T, PK]) select_desc(min *PK, max PK, length *int) (values []T)
pkey := *(*PK)(item.pk) pkey := *(*PK)(item.pk)
// Check above max. // Check above max.
if pkey > max { if pkey >= max {
continue continue
} }
@ -804,60 +891,25 @@ func (t *Timeline[T, PK]) store_one(last *list_elem, value value_with_pk[T, PK])
t_item.data = value.v t_item.data = value.v
t_item.pk = value.kptr t_item.pk = value.kptr
// Get zero'th index, i.e.
// the primary key index.
idx0 := (&t.indices[0])
// Acquire key buf. // Acquire key buf.
buf := new_buffer() buf := new_buffer()
// Convert to indexed_item ptr. // Calculate index key from already extracted
i_item := from_timeline_item(t_item) // primary key, checking for zero return value.
// Append already-extracted
// primary key to 0th index.
idx := (&t.indices[0])
partptrs := []unsafe.Pointer{value.kptr} partptrs := []unsafe.Pointer{value.kptr}
key := idx.key(buf, partptrs) key := idx0.key(buf, partptrs)
evicted := idx.append(key, i_item) if key == "" { // i.e. (!allow_zero && pkey == zero)
if evicted != nil { free_timeline_item(t_item)
// This item is no longer
// indexed, remove from list.
t.list.remove(&evicted.elem)
// Now convert from index_item ptr
// and release it to global mem pool.
evicted := to_timeline_item(evicted)
free_timeline_item(evicted)
}
for i := 1; i < len(t.indices); i++ {
// Get current index ptr.
idx := (&t.indices[i])
// Extract fields comprising index key from value.
parts := extract_fields(value.vptr, idx.fields)
// Calculate this index key.
key := idx.key(buf, parts)
if key == "" {
continue
}
// Append this item to index.
evicted := idx.append(key, i_item)
if evicted != nil {
// This item is no longer
// indexed, remove from list.
t.list.remove(&evicted.elem)
// Now convert from index_item ptr
// and release it to global mem pool.
evicted := to_timeline_item(evicted)
free_timeline_item(evicted)
}
}
// Done with buf.
free_buffer(buf) free_buffer(buf)
return last
}
// Convert to indexed_item pointer.
i_item := from_timeline_item(t_item)
if last == nil { if last == nil {
// No previous element was provided, this is // No previous element was provided, this is
@ -869,28 +921,67 @@ func (t *Timeline[T, PK]) store_one(last *list_elem, value value_with_pk[T, PK])
// The easiest case, this will // The easiest case, this will
// be the first item in list. // be the first item in list.
t.list.push_front(&t_item.elem) t.list.push_front(&t_item.elem)
return t.list.head last = t.list.head // return value
goto indexing
} }
// Extract head item and its primary key. // Extract head item and its primary key.
headItem := (*timeline_item)(t.list.head.data) headItem := (*timeline_item)(t.list.head.data)
headPK := *(*PK)(headItem.pk) headPK := *(*PK)(headItem.pk)
if value.k >= headPK { if value.k > headPK {
// Another easier case, this also // Another easier case, this also
// will be the first item in list. // will be the first item in list.
t.list.push_front(&t_item.elem) t.list.push_front(&t_item.elem)
last = t.list.head // return value
goto indexing
}
// Check (and drop) if pkey is a collision!
if value.k == headPK && is_unique(idx0.flags) {
free_timeline_item(t_item)
free_buffer(buf)
return t.list.head return t.list.head
} }
// Set last=head // Set last = head.next
// to work from. // as next to work from.
last = t.list.head last = t.list.head.next
} }
// Iterate through linked list // Iterate through list from head
// from head to find location. // to find location. Optimized into two
for next := last.next; // // cases to minimize loop CPU cycles.
if is_unique(idx0.flags) {
for next := last; //
next != nil; next = next.next {
// Extract item and it's primary key.
nextItem := (*timeline_item)(next.data)
nextPK := *(*PK)(nextItem.pk)
// If pkey smaller than
// cursor's, keep going.
if value.k < nextPK {
continue
}
// Check (and drop) if
// pkey is a collision!
if value.k == nextPK {
free_timeline_item(t_item)
free_buffer(buf)
return next
}
// New pkey is larger than cursor,
// insert into list just before it.
t.list.insert(&t_item.elem, next.prev)
last = next // return value
goto indexing
}
} else {
for next := last; //
next != nil; next = next.next { next != nil; next = next.next {
// Extract item and it's primary key. // Extract item and it's primary key.
@ -906,13 +997,51 @@ func (t *Timeline[T, PK]) store_one(last *list_elem, value value_with_pk[T, PK])
// New pkey is larger than cursor, // New pkey is larger than cursor,
// insert into list just before it. // insert into list just before it.
t.list.insert(&t_item.elem, next.prev) t.list.insert(&t_item.elem, next.prev)
return next last = next // return value
goto indexing
}
} }
// We reached the end of the // We reached the end of the
// list, insert at tail pos. // list, insert at tail pos.
t.list.push_back(&t_item.elem) t.list.push_back(&t_item.elem)
return t.list.tail last = t.list.tail // return value
goto indexing
indexing:
// Append already-extracted
// primary key to 0th index.
_ = idx0.add(key, i_item)
// Insert item into each of indices.
for i := 1; i < len(t.indices); i++ {
// Get current index ptr.
idx := (&t.indices[i])
// Extract fields comprising index key from value.
parts := extract_fields(value.vptr, idx.fields)
// Calculate this index key,
// checking for zero values.
key := idx.key(buf, parts)
if key == "" {
continue
}
// Add this item to index,
// checking for collisions.
if !idx.add(key, i_item) {
t.delete(t_item)
free_buffer(buf)
return last
}
}
// Done with bufs.
free_buffer(buf)
return last
} }
func (t *Timeline[T, PK]) delete(i *timeline_item) { func (t *Timeline[T, PK]) delete(i *timeline_item) {
@ -957,7 +1086,7 @@ func init() {
// we rely on this to allow a ptr to one to be a ptr to either of them. // we rely on this to allow a ptr to one to be a ptr to either of them.
const off = unsafe.Offsetof(timeline_item{}.indexed_item) const off = unsafe.Offsetof(timeline_item{}.indexed_item)
if off != 0 { if off != 0 {
panic("invalid timeline_item{}.indexed_item offset") panic(assert("offset_of(timeline_item{}.indexed_item) = 0"))
} }
} }
@ -971,10 +1100,9 @@ func from_timeline_item(item *timeline_item) *indexed_item {
func to_timeline_item(item *indexed_item) *timeline_item { func to_timeline_item(item *indexed_item) *timeline_item {
to := (*timeline_item)(unsafe.Pointer(item)) to := (*timeline_item)(unsafe.Pointer(item))
if to.ck != ^uint(0) { if to.ck != ^uint(0) {
// ensure check bits are // ensure check bits are set indicating
// set indicating it was a // it was a timeline_item originally.
// timeline_item originally. panic(assert("check bits are set"))
should_not_reach(true)
} }
return to return to
} }
@ -999,7 +1127,8 @@ func free_timeline_item(item *timeline_item) {
if len(item.indexed) > 0 || if len(item.indexed) > 0 ||
item.elem.next != nil || item.elem.next != nil ||
item.elem.prev != nil { item.elem.prev != nil {
should_not_reach(false) msg := assert("item not in use")
os.Stderr.WriteString(msg + "\n")
return return
} }
item.data = nil item.data = nil

View file

@ -7,7 +7,6 @@ linters:
- dogsled - dogsled
- dupl - dupl
- errcheck - errcheck
- exportloopref
- exhaustive - exhaustive
- gochecknoinits - gochecknoinits
- goconst - goconst

View file

@ -2,6 +2,7 @@ package cors
import ( import (
"net/http" "net/http"
"regexp"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
@ -122,21 +123,32 @@ func (cors *cors) isOriginValid(c *gin.Context, origin string) bool {
return valid return valid
} }
var originRegex = regexp.MustCompile(`^/(.+)/[gimuy]?$`)
func (cors *cors) validateOrigin(origin string) bool { func (cors *cors) validateOrigin(origin string) bool {
if cors.allowAllOrigins { if cors.allowAllOrigins {
return true return true
} }
for _, value := range cors.allowOrigins { for _, value := range cors.allowOrigins {
if value == origin { if !originRegex.MatchString(value) && value == origin {
return true
}
if originRegex.MatchString(value) &&
regexp.MustCompile(originRegex.FindStringSubmatch(value)[1]).MatchString(origin) {
return true return true
} }
} }
if len(cors.wildcardOrigins) > 0 && cors.validateWildcardOrigin(origin) { if len(cors.wildcardOrigins) > 0 && cors.validateWildcardOrigin(origin) {
return true return true
} }
if cors.allowOriginFunc != nil { if cors.allowOriginFunc != nil {
return cors.allowOriginFunc(origin) return cors.allowOriginFunc(origin)
} }
return false return false
} }

View file

@ -3,6 +3,7 @@ package cors
import ( import (
"errors" "errors"
"fmt" "fmt"
"regexp"
"strings" "strings"
"time" "time"
@ -103,8 +104,17 @@ func (c Config) getAllowedSchemas() []string {
return allowedSchemas return allowedSchemas
} }
var regexpBasedOrigin = regexp.MustCompile(`^\/(.+)\/[gimuy]?$`)
func (c Config) validateAllowedSchemas(origin string) bool { func (c Config) validateAllowedSchemas(origin string) bool {
allowedSchemas := c.getAllowedSchemas() allowedSchemas := c.getAllowedSchemas()
if regexpBasedOrigin.MatchString(origin) {
// Normalize regexp-based origins
origin = regexpBasedOrigin.FindStringSubmatch(origin)[1]
origin = strings.Replace(origin, "?", "", 1)
}
for _, schema := range allowedSchemas { for _, schema := range allowedSchemas {
if strings.HasPrefix(origin, schema) { if strings.HasPrefix(origin, schema) {
return true return true

View file

@ -1,5 +1,4 @@
# This is an example goreleaser.yaml file with some sane defaults. version: 2
# Make sure to check the documentation at http://goreleaser.com
builds: builds:
- -
@ -27,16 +26,7 @@ builds:
archives: archives:
- -
id: cpuid id: cpuid
name_template: "cpuid-{{ .Os }}_{{ .Arch }}_{{ .Version }}" name_template: "cpuid-{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
replacements:
aix: AIX
darwin: OSX
linux: Linux
windows: Windows
386: i386
amd64: x86_64
freebsd: FreeBSD
netbsd: NetBSD
format_overrides: format_overrides:
- goos: windows - goos: windows
format: zip format: zip
@ -44,8 +34,6 @@ archives:
- LICENSE - LICENSE
checksum: checksum:
name_template: 'checksums.txt' name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Tag }}-next"
changelog: changelog:
sort: asc sort: asc
filters: filters:
@ -58,7 +46,7 @@ changelog:
nfpms: nfpms:
- -
file_name_template: "cpuid_package_{{ .Version }}_{{ .Os }}_{{ .Arch }}" file_name_template: "cpuid_package_{{ .Os }}_{{ .Arch }}{{ if .Arm }}v{{ .Arm }}{{ end }}"
vendor: Klaus Post vendor: Klaus Post
homepage: https://github.com/klauspost/cpuid homepage: https://github.com/klauspost/cpuid
maintainer: Klaus Post <klauspost@gmail.com> maintainer: Klaus Post <klauspost@gmail.com>
@ -67,8 +55,3 @@ nfpms:
formats: formats:
- deb - deb
- rpm - rpm
replacements:
darwin: Darwin
linux: Linux
freebsd: FreeBSD
amd64: x86_64

View file

@ -282,7 +282,9 @@ Exit Code 1
| AMXINT8 | Tile computational operations on 8-bit integers | | AMXINT8 | Tile computational operations on 8-bit integers |
| AMXFP16 | Tile computational operations on FP16 numbers | | AMXFP16 | Tile computational operations on FP16 numbers |
| AMXFP8 | Tile computational operations on FP8 numbers | | AMXFP8 | Tile computational operations on FP8 numbers |
| AMXCOMPLEX | Tile computational operations on complex numbers |
| AMXTILE | Tile architecture | | AMXTILE | Tile architecture |
| AMXTF32 | Matrix Multiplication of TF32 Tiles into Packed Single Precision Tile |
| APX_F | Intel APX | | APX_F | Intel APX |
| AVX | AVX functions | | AVX | AVX functions |
| AVX10 | If set the Intel AVX10 Converged Vector ISA is supported | | AVX10 | If set the Intel AVX10 Converged Vector ISA is supported |
@ -480,12 +482,16 @@ Exit Code 1
| DCPOP | Data cache clean to Point of Persistence (DC CVAP) | | DCPOP | Data cache clean to Point of Persistence (DC CVAP) |
| EVTSTRM | Generic timer | | EVTSTRM | Generic timer |
| FCMA | Floatin point complex number addition and multiplication | | FCMA | Floatin point complex number addition and multiplication |
| FHM | FMLAL and FMLSL instructions |
| FP | Single-precision and double-precision floating point | | FP | Single-precision and double-precision floating point |
| FPHP | Half-precision floating point | | FPHP | Half-precision floating point |
| GPA | Generic Pointer Authentication | | GPA | Generic Pointer Authentication |
| JSCVT | Javascript-style double->int convert (FJCVTZS) | | JSCVT | Javascript-style double->int convert (FJCVTZS) |
| LRCPC | Weaker release consistency (LDAPR, etc) | | LRCPC | Weaker release consistency (LDAPR, etc) |
| PMULL | Polynomial Multiply instructions (PMULL/PMULL2) | | PMULL | Polynomial Multiply instructions (PMULL/PMULL2) |
| RNDR | Random Number instructions |
| TLB | Outer Shareable and TLB range maintenance instructions |
| TS | Flag manipulation instructions |
| SHA1 | SHA-1 instructions (SHA1C, etc) | | SHA1 | SHA-1 instructions (SHA1C, etc) |
| SHA2 | SHA-2 instructions (SHA256H, etc) | | SHA2 | SHA-2 instructions (SHA256H, etc) |
| SHA3 | SHA-3 instructions (EOR3, RAXI, XAR, BCAX) | | SHA3 | SHA-3 instructions (EOR3, RAXI, XAR, BCAX) |

View file

@ -83,6 +83,8 @@ const (
AMXINT8 // Tile computational operations on 8-bit integers AMXINT8 // Tile computational operations on 8-bit integers
AMXFP8 // Tile computational operations on FP8 numbers AMXFP8 // Tile computational operations on FP8 numbers
AMXTILE // Tile architecture AMXTILE // Tile architecture
AMXTF32 // Tile architecture
AMXCOMPLEX // Matrix Multiplication of TF32 Tiles into Packed Single Precision Tile
APX_F // Intel APX APX_F // Intel APX
AVX // AVX functions AVX // AVX functions
AVX10 // If set the Intel AVX10 Converged Vector ISA is supported AVX10 // If set the Intel AVX10 Converged Vector ISA is supported
@ -282,12 +284,16 @@ const (
DCPOP // Data cache clean to Point of Persistence (DC CVAP) DCPOP // Data cache clean to Point of Persistence (DC CVAP)
EVTSTRM // Generic timer EVTSTRM // Generic timer
FCMA // Floatin point complex number addition and multiplication FCMA // Floatin point complex number addition and multiplication
FHM // FMLAL and FMLSL instructions
FP // Single-precision and double-precision floating point FP // Single-precision and double-precision floating point
FPHP // Half-precision floating point FPHP // Half-precision floating point
GPA // Generic Pointer Authentication GPA // Generic Pointer Authentication
JSCVT // Javascript-style double->int convert (FJCVTZS) JSCVT // Javascript-style double->int convert (FJCVTZS)
LRCPC // Weaker release consistency (LDAPR, etc) LRCPC // Weaker release consistency (LDAPR, etc)
PMULL // Polynomial Multiply instructions (PMULL/PMULL2) PMULL // Polynomial Multiply instructions (PMULL/PMULL2)
RNDR // Random Number instructions
TLB // Outer Shareable and TLB range maintenance instructions
TS // Flag manipulation instructions
SHA1 // SHA-1 instructions (SHA1C, etc) SHA1 // SHA-1 instructions (SHA1C, etc)
SHA2 // SHA-2 instructions (SHA256H, etc) SHA2 // SHA-2 instructions (SHA256H, etc)
SHA3 // SHA-3 instructions (EOR3, RAXI, XAR, BCAX) SHA3 // SHA-3 instructions (EOR3, RAXI, XAR, BCAX)
@ -532,7 +538,7 @@ func (c CPUInfo) Ia32TscAux() uint32 {
return ecx return ecx
} }
// SveLengths returns arm SVE vector and predicate lengths. // SveLengths returns arm SVE vector and predicate lengths in bits.
// Will return 0, 0 if SVE is not enabled or otherwise unable to detect. // Will return 0, 0 if SVE is not enabled or otherwise unable to detect.
func (c CPUInfo) SveLengths() (vl, pl uint64) { func (c CPUInfo) SveLengths() (vl, pl uint64) {
if !c.Has(SVE) { if !c.Has(SVE) {
@ -1284,6 +1290,8 @@ func support() flagSet {
// CPUID.(EAX=7, ECX=1).EDX // CPUID.(EAX=7, ECX=1).EDX
fs.setIf(edx1&(1<<4) != 0, AVXVNNIINT8) fs.setIf(edx1&(1<<4) != 0, AVXVNNIINT8)
fs.setIf(edx1&(1<<5) != 0, AVXNECONVERT) fs.setIf(edx1&(1<<5) != 0, AVXNECONVERT)
fs.setIf(edx1&(1<<7) != 0, AMXTF32)
fs.setIf(edx1&(1<<8) != 0, AMXCOMPLEX)
fs.setIf(edx1&(1<<10) != 0, AVXVNNIINT16) fs.setIf(edx1&(1<<10) != 0, AVXVNNIINT16)
fs.setIf(edx1&(1<<14) != 0, PREFETCHI) fs.setIf(edx1&(1<<14) != 0, PREFETCHI)
fs.setIf(edx1&(1<<19) != 0, AVX10) fs.setIf(edx1&(1<<19) != 0, AVX10)

View file

@ -157,6 +157,10 @@ func addInfo(c *CPUInfo, safe bool) {
// x--------------------------------------------------x // x--------------------------------------------------x
// | Name | bits | visible | // | Name | bits | visible |
// |--------------------------------------------------| // |--------------------------------------------------|
// | RNDR | [63-60] | y |
// |--------------------------------------------------|
// | TLB | [59-56] | y |
// |--------------------------------------------------|
// | TS | [55-52] | y | // | TS | [55-52] | y |
// |--------------------------------------------------| // |--------------------------------------------------|
// | FHM | [51-48] | y | // | FHM | [51-48] | y |
@ -182,12 +186,10 @@ func addInfo(c *CPUInfo, safe bool) {
// | AES | [7-4] | y | // | AES | [7-4] | y |
// x--------------------------------------------------x // x--------------------------------------------------x
// if instAttrReg0&(0xf<<52) != 0 { f.setIf(instAttrReg0&(0xf<<60) != 0, RNDR)
// fmt.Println("TS") f.setIf(instAttrReg0&(0xf<<56) != 0, TLB)
// } f.setIf(instAttrReg0&(0xf<<52) != 0, TS)
// if instAttrReg0&(0xf<<48) != 0 { f.setIf(instAttrReg0&(0xf<<48) != 0, FHM)
// fmt.Println("FHM")
// }
f.setIf(instAttrReg0&(0xf<<44) != 0, ASIMDDP) f.setIf(instAttrReg0&(0xf<<44) != 0, ASIMDDP)
f.setIf(instAttrReg0&(0xf<<40) != 0, SM4) f.setIf(instAttrReg0&(0xf<<40) != 0, SM4)
f.setIf(instAttrReg0&(0xf<<36) != 0, SM3) f.setIf(instAttrReg0&(0xf<<36) != 0, SM3)

View file

@ -17,223 +17,229 @@ func _() {
_ = x[AMXINT8-7] _ = x[AMXINT8-7]
_ = x[AMXFP8-8] _ = x[AMXFP8-8]
_ = x[AMXTILE-9] _ = x[AMXTILE-9]
_ = x[APX_F-10] _ = x[AMXTF32-10]
_ = x[AVX-11] _ = x[AMXCOMPLEX-11]
_ = x[AVX10-12] _ = x[APX_F-12]
_ = x[AVX10_128-13] _ = x[AVX-13]
_ = x[AVX10_256-14] _ = x[AVX10-14]
_ = x[AVX10_512-15] _ = x[AVX10_128-15]
_ = x[AVX2-16] _ = x[AVX10_256-16]
_ = x[AVX512BF16-17] _ = x[AVX10_512-17]
_ = x[AVX512BITALG-18] _ = x[AVX2-18]
_ = x[AVX512BW-19] _ = x[AVX512BF16-19]
_ = x[AVX512CD-20] _ = x[AVX512BITALG-20]
_ = x[AVX512DQ-21] _ = x[AVX512BW-21]
_ = x[AVX512ER-22] _ = x[AVX512CD-22]
_ = x[AVX512F-23] _ = x[AVX512DQ-23]
_ = x[AVX512FP16-24] _ = x[AVX512ER-24]
_ = x[AVX512IFMA-25] _ = x[AVX512F-25]
_ = x[AVX512PF-26] _ = x[AVX512FP16-26]
_ = x[AVX512VBMI-27] _ = x[AVX512IFMA-27]
_ = x[AVX512VBMI2-28] _ = x[AVX512PF-28]
_ = x[AVX512VL-29] _ = x[AVX512VBMI-29]
_ = x[AVX512VNNI-30] _ = x[AVX512VBMI2-30]
_ = x[AVX512VP2INTERSECT-31] _ = x[AVX512VL-31]
_ = x[AVX512VPOPCNTDQ-32] _ = x[AVX512VNNI-32]
_ = x[AVXIFMA-33] _ = x[AVX512VP2INTERSECT-33]
_ = x[AVXNECONVERT-34] _ = x[AVX512VPOPCNTDQ-34]
_ = x[AVXSLOW-35] _ = x[AVXIFMA-35]
_ = x[AVXVNNI-36] _ = x[AVXNECONVERT-36]
_ = x[AVXVNNIINT8-37] _ = x[AVXSLOW-37]
_ = x[AVXVNNIINT16-38] _ = x[AVXVNNI-38]
_ = x[BHI_CTRL-39] _ = x[AVXVNNIINT8-39]
_ = x[BMI1-40] _ = x[AVXVNNIINT16-40]
_ = x[BMI2-41] _ = x[BHI_CTRL-41]
_ = x[CETIBT-42] _ = x[BMI1-42]
_ = x[CETSS-43] _ = x[BMI2-43]
_ = x[CLDEMOTE-44] _ = x[CETIBT-44]
_ = x[CLMUL-45] _ = x[CETSS-45]
_ = x[CLZERO-46] _ = x[CLDEMOTE-46]
_ = x[CMOV-47] _ = x[CLMUL-47]
_ = x[CMPCCXADD-48] _ = x[CLZERO-48]
_ = x[CMPSB_SCADBS_SHORT-49] _ = x[CMOV-49]
_ = x[CMPXCHG8-50] _ = x[CMPCCXADD-50]
_ = x[CPBOOST-51] _ = x[CMPSB_SCADBS_SHORT-51]
_ = x[CPPC-52] _ = x[CMPXCHG8-52]
_ = x[CX16-53] _ = x[CPBOOST-53]
_ = x[EFER_LMSLE_UNS-54] _ = x[CPPC-54]
_ = x[ENQCMD-55] _ = x[CX16-55]
_ = x[ERMS-56] _ = x[EFER_LMSLE_UNS-56]
_ = x[F16C-57] _ = x[ENQCMD-57]
_ = x[FLUSH_L1D-58] _ = x[ERMS-58]
_ = x[FMA3-59] _ = x[F16C-59]
_ = x[FMA4-60] _ = x[FLUSH_L1D-60]
_ = x[FP128-61] _ = x[FMA3-61]
_ = x[FP256-62] _ = x[FMA4-62]
_ = x[FSRM-63] _ = x[FP128-63]
_ = x[FXSR-64] _ = x[FP256-64]
_ = x[FXSROPT-65] _ = x[FSRM-65]
_ = x[GFNI-66] _ = x[FXSR-66]
_ = x[HLE-67] _ = x[FXSROPT-67]
_ = x[HRESET-68] _ = x[GFNI-68]
_ = x[HTT-69] _ = x[HLE-69]
_ = x[HWA-70] _ = x[HRESET-70]
_ = x[HYBRID_CPU-71] _ = x[HTT-71]
_ = x[HYPERVISOR-72] _ = x[HWA-72]
_ = x[IA32_ARCH_CAP-73] _ = x[HYBRID_CPU-73]
_ = x[IA32_CORE_CAP-74] _ = x[HYPERVISOR-74]
_ = x[IBPB-75] _ = x[IA32_ARCH_CAP-75]
_ = x[IBPB_BRTYPE-76] _ = x[IA32_CORE_CAP-76]
_ = x[IBRS-77] _ = x[IBPB-77]
_ = x[IBRS_PREFERRED-78] _ = x[IBPB_BRTYPE-78]
_ = x[IBRS_PROVIDES_SMP-79] _ = x[IBRS-79]
_ = x[IBS-80] _ = x[IBRS_PREFERRED-80]
_ = x[IBSBRNTRGT-81] _ = x[IBRS_PROVIDES_SMP-81]
_ = x[IBSFETCHSAM-82] _ = x[IBS-82]
_ = x[IBSFFV-83] _ = x[IBSBRNTRGT-83]
_ = x[IBSOPCNT-84] _ = x[IBSFETCHSAM-84]
_ = x[IBSOPCNTEXT-85] _ = x[IBSFFV-85]
_ = x[IBSOPSAM-86] _ = x[IBSOPCNT-86]
_ = x[IBSRDWROPCNT-87] _ = x[IBSOPCNTEXT-87]
_ = x[IBSRIPINVALIDCHK-88] _ = x[IBSOPSAM-88]
_ = x[IBS_FETCH_CTLX-89] _ = x[IBSRDWROPCNT-89]
_ = x[IBS_OPDATA4-90] _ = x[IBSRIPINVALIDCHK-90]
_ = x[IBS_OPFUSE-91] _ = x[IBS_FETCH_CTLX-91]
_ = x[IBS_PREVENTHOST-92] _ = x[IBS_OPDATA4-92]
_ = x[IBS_ZEN4-93] _ = x[IBS_OPFUSE-93]
_ = x[IDPRED_CTRL-94] _ = x[IBS_PREVENTHOST-94]
_ = x[INT_WBINVD-95] _ = x[IBS_ZEN4-95]
_ = x[INVLPGB-96] _ = x[IDPRED_CTRL-96]
_ = x[KEYLOCKER-97] _ = x[INT_WBINVD-97]
_ = x[KEYLOCKERW-98] _ = x[INVLPGB-98]
_ = x[LAHF-99] _ = x[KEYLOCKER-99]
_ = x[LAM-100] _ = x[KEYLOCKERW-100]
_ = x[LBRVIRT-101] _ = x[LAHF-101]
_ = x[LZCNT-102] _ = x[LAM-102]
_ = x[MCAOVERFLOW-103] _ = x[LBRVIRT-103]
_ = x[MCDT_NO-104] _ = x[LZCNT-104]
_ = x[MCOMMIT-105] _ = x[MCAOVERFLOW-105]
_ = x[MD_CLEAR-106] _ = x[MCDT_NO-106]
_ = x[MMX-107] _ = x[MCOMMIT-107]
_ = x[MMXEXT-108] _ = x[MD_CLEAR-108]
_ = x[MOVBE-109] _ = x[MMX-109]
_ = x[MOVDIR64B-110] _ = x[MMXEXT-110]
_ = x[MOVDIRI-111] _ = x[MOVBE-111]
_ = x[MOVSB_ZL-112] _ = x[MOVDIR64B-112]
_ = x[MOVU-113] _ = x[MOVDIRI-113]
_ = x[MPX-114] _ = x[MOVSB_ZL-114]
_ = x[MSRIRC-115] _ = x[MOVU-115]
_ = x[MSRLIST-116] _ = x[MPX-116]
_ = x[MSR_PAGEFLUSH-117] _ = x[MSRIRC-117]
_ = x[NRIPS-118] _ = x[MSRLIST-118]
_ = x[NX-119] _ = x[MSR_PAGEFLUSH-119]
_ = x[OSXSAVE-120] _ = x[NRIPS-120]
_ = x[PCONFIG-121] _ = x[NX-121]
_ = x[POPCNT-122] _ = x[OSXSAVE-122]
_ = x[PPIN-123] _ = x[PCONFIG-123]
_ = x[PREFETCHI-124] _ = x[POPCNT-124]
_ = x[PSFD-125] _ = x[PPIN-125]
_ = x[RDPRU-126] _ = x[PREFETCHI-126]
_ = x[RDRAND-127] _ = x[PSFD-127]
_ = x[RDSEED-128] _ = x[RDPRU-128]
_ = x[RDTSCP-129] _ = x[RDRAND-129]
_ = x[RRSBA_CTRL-130] _ = x[RDSEED-130]
_ = x[RTM-131] _ = x[RDTSCP-131]
_ = x[RTM_ALWAYS_ABORT-132] _ = x[RRSBA_CTRL-132]
_ = x[SBPB-133] _ = x[RTM-133]
_ = x[SERIALIZE-134] _ = x[RTM_ALWAYS_ABORT-134]
_ = x[SEV-135] _ = x[SBPB-135]
_ = x[SEV_64BIT-136] _ = x[SERIALIZE-136]
_ = x[SEV_ALTERNATIVE-137] _ = x[SEV-137]
_ = x[SEV_DEBUGSWAP-138] _ = x[SEV_64BIT-138]
_ = x[SEV_ES-139] _ = x[SEV_ALTERNATIVE-139]
_ = x[SEV_RESTRICTED-140] _ = x[SEV_DEBUGSWAP-140]
_ = x[SEV_SNP-141] _ = x[SEV_ES-141]
_ = x[SGX-142] _ = x[SEV_RESTRICTED-142]
_ = x[SGXLC-143] _ = x[SEV_SNP-143]
_ = x[SHA-144] _ = x[SGX-144]
_ = x[SME-145] _ = x[SGXLC-145]
_ = x[SME_COHERENT-146] _ = x[SHA-146]
_ = x[SPEC_CTRL_SSBD-147] _ = x[SME-147]
_ = x[SRBDS_CTRL-148] _ = x[SME_COHERENT-148]
_ = x[SRSO_MSR_FIX-149] _ = x[SPEC_CTRL_SSBD-149]
_ = x[SRSO_NO-150] _ = x[SRBDS_CTRL-150]
_ = x[SRSO_USER_KERNEL_NO-151] _ = x[SRSO_MSR_FIX-151]
_ = x[SSE-152] _ = x[SRSO_NO-152]
_ = x[SSE2-153] _ = x[SRSO_USER_KERNEL_NO-153]
_ = x[SSE3-154] _ = x[SSE-154]
_ = x[SSE4-155] _ = x[SSE2-155]
_ = x[SSE42-156] _ = x[SSE3-156]
_ = x[SSE4A-157] _ = x[SSE4-157]
_ = x[SSSE3-158] _ = x[SSE42-158]
_ = x[STIBP-159] _ = x[SSE4A-159]
_ = x[STIBP_ALWAYSON-160] _ = x[SSSE3-160]
_ = x[STOSB_SHORT-161] _ = x[STIBP-161]
_ = x[SUCCOR-162] _ = x[STIBP_ALWAYSON-162]
_ = x[SVM-163] _ = x[STOSB_SHORT-163]
_ = x[SVMDA-164] _ = x[SUCCOR-164]
_ = x[SVMFBASID-165] _ = x[SVM-165]
_ = x[SVML-166] _ = x[SVMDA-166]
_ = x[SVMNP-167] _ = x[SVMFBASID-167]
_ = x[SVMPF-168] _ = x[SVML-168]
_ = x[SVMPFT-169] _ = x[SVMNP-169]
_ = x[SYSCALL-170] _ = x[SVMPF-170]
_ = x[SYSEE-171] _ = x[SVMPFT-171]
_ = x[TBM-172] _ = x[SYSCALL-172]
_ = x[TDX_GUEST-173] _ = x[SYSEE-173]
_ = x[TLB_FLUSH_NESTED-174] _ = x[TBM-174]
_ = x[TME-175] _ = x[TDX_GUEST-175]
_ = x[TOPEXT-176] _ = x[TLB_FLUSH_NESTED-176]
_ = x[TSCRATEMSR-177] _ = x[TME-177]
_ = x[TSXLDTRK-178] _ = x[TOPEXT-178]
_ = x[VAES-179] _ = x[TSCRATEMSR-179]
_ = x[VMCBCLEAN-180] _ = x[TSXLDTRK-180]
_ = x[VMPL-181] _ = x[VAES-181]
_ = x[VMSA_REGPROT-182] _ = x[VMCBCLEAN-182]
_ = x[VMX-183] _ = x[VMPL-183]
_ = x[VPCLMULQDQ-184] _ = x[VMSA_REGPROT-184]
_ = x[VTE-185] _ = x[VMX-185]
_ = x[WAITPKG-186] _ = x[VPCLMULQDQ-186]
_ = x[WBNOINVD-187] _ = x[VTE-187]
_ = x[WRMSRNS-188] _ = x[WAITPKG-188]
_ = x[X87-189] _ = x[WBNOINVD-189]
_ = x[XGETBV1-190] _ = x[WRMSRNS-190]
_ = x[XOP-191] _ = x[X87-191]
_ = x[XSAVE-192] _ = x[XGETBV1-192]
_ = x[XSAVEC-193] _ = x[XOP-193]
_ = x[XSAVEOPT-194] _ = x[XSAVE-194]
_ = x[XSAVES-195] _ = x[XSAVEC-195]
_ = x[AESARM-196] _ = x[XSAVEOPT-196]
_ = x[ARMCPUID-197] _ = x[XSAVES-197]
_ = x[ASIMD-198] _ = x[AESARM-198]
_ = x[ASIMDDP-199] _ = x[ARMCPUID-199]
_ = x[ASIMDHP-200] _ = x[ASIMD-200]
_ = x[ASIMDRDM-201] _ = x[ASIMDDP-201]
_ = x[ATOMICS-202] _ = x[ASIMDHP-202]
_ = x[CRC32-203] _ = x[ASIMDRDM-203]
_ = x[DCPOP-204] _ = x[ATOMICS-204]
_ = x[EVTSTRM-205] _ = x[CRC32-205]
_ = x[FCMA-206] _ = x[DCPOP-206]
_ = x[FP-207] _ = x[EVTSTRM-207]
_ = x[FPHP-208] _ = x[FCMA-208]
_ = x[GPA-209] _ = x[FHM-209]
_ = x[JSCVT-210] _ = x[FP-210]
_ = x[LRCPC-211] _ = x[FPHP-211]
_ = x[PMULL-212] _ = x[GPA-212]
_ = x[SHA1-213] _ = x[JSCVT-213]
_ = x[SHA2-214] _ = x[LRCPC-214]
_ = x[SHA3-215] _ = x[PMULL-215]
_ = x[SHA512-216] _ = x[RNDR-216]
_ = x[SM3-217] _ = x[TLB-217]
_ = x[SM4-218] _ = x[TS-218]
_ = x[SVE-219] _ = x[SHA1-219]
_ = x[lastID-220] _ = x[SHA2-220]
_ = x[SHA3-221]
_ = x[SHA512-222]
_ = x[SM3-223]
_ = x[SM4-224]
_ = x[SVE-225]
_ = x[lastID-226]
_ = x[firstID-0] _ = x[firstID-0]
} }
const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXFP16AMXINT8AMXFP8AMXTILEAPX_FAVXAVX10AVX10_128AVX10_256AVX10_512AVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXIFMAAVXNECONVERTAVXSLOWAVXVNNIAVXVNNIINT8AVXVNNIINT16BHI_CTRLBMI1BMI2CETIBTCETSSCLDEMOTECLMULCLZEROCMOVCMPCCXADDCMPSB_SCADBS_SHORTCMPXCHG8CPBOOSTCPPCCX16EFER_LMSLE_UNSENQCMDERMSF16CFLUSH_L1DFMA3FMA4FP128FP256FSRMFXSRFXSROPTGFNIHLEHRESETHTTHWAHYBRID_CPUHYPERVISORIA32_ARCH_CAPIA32_CORE_CAPIBPBIBPB_BRTYPEIBRSIBRS_PREFERREDIBRS_PROVIDES_SMPIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKIBS_FETCH_CTLXIBS_OPDATA4IBS_OPFUSEIBS_PREVENTHOSTIBS_ZEN4IDPRED_CTRLINT_WBINVDINVLPGBKEYLOCKERKEYLOCKERWLAHFLAMLBRVIRTLZCNTMCAOVERFLOWMCDT_NOMCOMMITMD_CLEARMMXMMXEXTMOVBEMOVDIR64BMOVDIRIMOVSB_ZLMOVUMPXMSRIRCMSRLISTMSR_PAGEFLUSHNRIPSNXOSXSAVEPCONFIGPOPCNTPPINPREFETCHIPSFDRDPRURDRANDRDSEEDRDTSCPRRSBA_CTRLRTMRTM_ALWAYS_ABORTSBPBSERIALIZESEVSEV_64BITSEV_ALTERNATIVESEV_DEBUGSWAPSEV_ESSEV_RESTRICTEDSEV_SNPSGXSGXLCSHASMESME_COHERENTSPEC_CTRL_SSBDSRBDS_CTRLSRSO_MSR_FIXSRSO_NOSRSO_USER_KERNEL_NOSSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSTIBP_ALWAYSONSTOSB_SHORTSUCCORSVMSVMDASVMFBASIDSVMLSVMNPSVMPFSVMPFTSYSCALLSYSEETBMTDX_GUESTTLB_FLUSH_NESTEDTMETOPEXTTSCRATEMSRTSXLDTRKVAESVMCBCLEANVMPLVMSA_REGPROTVMXVPCLMULQDQVTEWAITPKGWBNOINVDWRMSRNSX87XGETBV1XOPXSAVEXSAVECXSAVEOPTXSAVESAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFPFPHPGPAJSCVTLRCPCPMULLSHA1SHA2SHA3SHA512SM3SM4SVElastID" const _FeatureID_name = "firstIDADXAESNIAMD3DNOWAMD3DNOWEXTAMXBF16AMXFP16AMXINT8AMXFP8AMXTILEAMXTF32AMXCOMPLEXAPX_FAVXAVX10AVX10_128AVX10_256AVX10_512AVX2AVX512BF16AVX512BITALGAVX512BWAVX512CDAVX512DQAVX512ERAVX512FAVX512FP16AVX512IFMAAVX512PFAVX512VBMIAVX512VBMI2AVX512VLAVX512VNNIAVX512VP2INTERSECTAVX512VPOPCNTDQAVXIFMAAVXNECONVERTAVXSLOWAVXVNNIAVXVNNIINT8AVXVNNIINT16BHI_CTRLBMI1BMI2CETIBTCETSSCLDEMOTECLMULCLZEROCMOVCMPCCXADDCMPSB_SCADBS_SHORTCMPXCHG8CPBOOSTCPPCCX16EFER_LMSLE_UNSENQCMDERMSF16CFLUSH_L1DFMA3FMA4FP128FP256FSRMFXSRFXSROPTGFNIHLEHRESETHTTHWAHYBRID_CPUHYPERVISORIA32_ARCH_CAPIA32_CORE_CAPIBPBIBPB_BRTYPEIBRSIBRS_PREFERREDIBRS_PROVIDES_SMPIBSIBSBRNTRGTIBSFETCHSAMIBSFFVIBSOPCNTIBSOPCNTEXTIBSOPSAMIBSRDWROPCNTIBSRIPINVALIDCHKIBS_FETCH_CTLXIBS_OPDATA4IBS_OPFUSEIBS_PREVENTHOSTIBS_ZEN4IDPRED_CTRLINT_WBINVDINVLPGBKEYLOCKERKEYLOCKERWLAHFLAMLBRVIRTLZCNTMCAOVERFLOWMCDT_NOMCOMMITMD_CLEARMMXMMXEXTMOVBEMOVDIR64BMOVDIRIMOVSB_ZLMOVUMPXMSRIRCMSRLISTMSR_PAGEFLUSHNRIPSNXOSXSAVEPCONFIGPOPCNTPPINPREFETCHIPSFDRDPRURDRANDRDSEEDRDTSCPRRSBA_CTRLRTMRTM_ALWAYS_ABORTSBPBSERIALIZESEVSEV_64BITSEV_ALTERNATIVESEV_DEBUGSWAPSEV_ESSEV_RESTRICTEDSEV_SNPSGXSGXLCSHASMESME_COHERENTSPEC_CTRL_SSBDSRBDS_CTRLSRSO_MSR_FIXSRSO_NOSRSO_USER_KERNEL_NOSSESSE2SSE3SSE4SSE42SSE4ASSSE3STIBPSTIBP_ALWAYSONSTOSB_SHORTSUCCORSVMSVMDASVMFBASIDSVMLSVMNPSVMPFSVMPFTSYSCALLSYSEETBMTDX_GUESTTLB_FLUSH_NESTEDTMETOPEXTTSCRATEMSRTSXLDTRKVAESVMCBCLEANVMPLVMSA_REGPROTVMXVPCLMULQDQVTEWAITPKGWBNOINVDWRMSRNSX87XGETBV1XOPXSAVEXSAVECXSAVEOPTXSAVESAESARMARMCPUIDASIMDASIMDDPASIMDHPASIMDRDMATOMICSCRC32DCPOPEVTSTRMFCMAFHMFPFPHPGPAJSCVTLRCPCPMULLRNDRTLBTSSHA1SHA2SHA3SHA512SM3SM4SVElastID"
var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 61, 68, 73, 76, 81, 90, 99, 108, 112, 122, 134, 142, 150, 158, 166, 173, 183, 193, 201, 211, 222, 230, 240, 258, 273, 280, 292, 299, 306, 317, 329, 337, 341, 345, 351, 356, 364, 369, 375, 379, 388, 406, 414, 421, 425, 429, 443, 449, 453, 457, 466, 470, 474, 479, 484, 488, 492, 499, 503, 506, 512, 515, 518, 528, 538, 551, 564, 568, 579, 583, 597, 614, 617, 627, 638, 644, 652, 663, 671, 683, 699, 713, 724, 734, 749, 757, 768, 778, 785, 794, 804, 808, 811, 818, 823, 834, 841, 848, 856, 859, 865, 870, 879, 886, 894, 898, 901, 907, 914, 927, 932, 934, 941, 948, 954, 958, 967, 971, 976, 982, 988, 994, 1004, 1007, 1023, 1027, 1036, 1039, 1048, 1063, 1076, 1082, 1096, 1103, 1106, 1111, 1114, 1117, 1129, 1143, 1153, 1165, 1172, 1191, 1194, 1198, 1202, 1206, 1211, 1216, 1221, 1226, 1240, 1251, 1257, 1260, 1265, 1274, 1278, 1283, 1288, 1294, 1301, 1306, 1309, 1318, 1334, 1337, 1343, 1353, 1361, 1365, 1374, 1378, 1390, 1393, 1403, 1406, 1413, 1421, 1428, 1431, 1438, 1441, 1446, 1452, 1460, 1466, 1472, 1480, 1485, 1492, 1499, 1507, 1514, 1519, 1524, 1531, 1535, 1537, 1541, 1544, 1549, 1554, 1559, 1563, 1567, 1571, 1577, 1580, 1583, 1586, 1592} var _FeatureID_index = [...]uint16{0, 7, 10, 15, 23, 34, 41, 48, 55, 61, 68, 75, 85, 90, 93, 98, 107, 116, 125, 129, 139, 151, 159, 167, 175, 183, 190, 200, 210, 218, 228, 239, 247, 257, 275, 290, 297, 309, 316, 323, 334, 346, 354, 358, 362, 368, 373, 381, 386, 392, 396, 405, 423, 431, 438, 442, 446, 460, 466, 470, 474, 483, 487, 491, 496, 501, 505, 509, 516, 520, 523, 529, 532, 535, 545, 555, 568, 581, 585, 596, 600, 614, 631, 634, 644, 655, 661, 669, 680, 688, 700, 716, 730, 741, 751, 766, 774, 785, 795, 802, 811, 821, 825, 828, 835, 840, 851, 858, 865, 873, 876, 882, 887, 896, 903, 911, 915, 918, 924, 931, 944, 949, 951, 958, 965, 971, 975, 984, 988, 993, 999, 1005, 1011, 1021, 1024, 1040, 1044, 1053, 1056, 1065, 1080, 1093, 1099, 1113, 1120, 1123, 1128, 1131, 1134, 1146, 1160, 1170, 1182, 1189, 1208, 1211, 1215, 1219, 1223, 1228, 1233, 1238, 1243, 1257, 1268, 1274, 1277, 1282, 1291, 1295, 1300, 1305, 1311, 1318, 1323, 1326, 1335, 1351, 1354, 1360, 1370, 1378, 1382, 1391, 1395, 1407, 1410, 1420, 1423, 1430, 1438, 1445, 1448, 1455, 1458, 1463, 1469, 1477, 1483, 1489, 1497, 1502, 1509, 1516, 1524, 1531, 1536, 1541, 1548, 1552, 1555, 1557, 1561, 1564, 1569, 1574, 1579, 1583, 1586, 1588, 1592, 1596, 1600, 1606, 1609, 1612, 1615, 1621}
func (i FeatureID) String() string { func (i FeatureID) String() string {
if i < 0 || i >= FeatureID(len(_FeatureID_index)-1) { if i < 0 || i >= FeatureID(len(_FeatureID_index)-1) {

View file

@ -96,9 +96,11 @@ func tryToFillCPUInfoFomSysctl(c *CPUInfo) {
setFeature(c, "hw.optional.arm.FEAT_DPB", DCPOP) setFeature(c, "hw.optional.arm.FEAT_DPB", DCPOP)
// setFeature(c, "", EVTSTRM) // setFeature(c, "", EVTSTRM)
setFeature(c, "hw.optional.arm.FEAT_FCMA", FCMA) setFeature(c, "hw.optional.arm.FEAT_FCMA", FCMA)
setFeature(c, "hw.optional.arm.FEAT_FHM", FHM)
setFeature(c, "hw.optional.arm.FEAT_FP", FP) setFeature(c, "hw.optional.arm.FEAT_FP", FP)
setFeature(c, "hw.optional.arm.FEAT_FP16", FPHP) setFeature(c, "hw.optional.arm.FEAT_FP16", FPHP)
setFeature(c, "hw.optional.arm.FEAT_PAuth", GPA) setFeature(c, "hw.optional.arm.FEAT_PAuth", GPA)
setFeature(c, "hw.optional.arm.FEAT_RNG", RNDR)
setFeature(c, "hw.optional.arm.FEAT_JSCVT", JSCVT) setFeature(c, "hw.optional.arm.FEAT_JSCVT", JSCVT)
setFeature(c, "hw.optional.arm.FEAT_LRCPC", LRCPC) setFeature(c, "hw.optional.arm.FEAT_LRCPC", LRCPC)
setFeature(c, "hw.optional.arm.FEAT_PMULL", PMULL) setFeature(c, "hw.optional.arm.FEAT_PMULL", PMULL)
@ -106,6 +108,10 @@ func tryToFillCPUInfoFomSysctl(c *CPUInfo) {
setFeature(c, "hw.optional.arm.FEAT_SHA256", SHA2) setFeature(c, "hw.optional.arm.FEAT_SHA256", SHA2)
setFeature(c, "hw.optional.arm.FEAT_SHA3", SHA3) setFeature(c, "hw.optional.arm.FEAT_SHA3", SHA3)
setFeature(c, "hw.optional.arm.FEAT_SHA512", SHA512) setFeature(c, "hw.optional.arm.FEAT_SHA512", SHA512)
setFeature(c, "hw.optional.arm.FEAT_TLBIOS", TLB)
setFeature(c, "hw.optional.arm.FEAT_TLBIRANGE", TLB)
setFeature(c, "hw.optional.arm.FEAT_FlagM", TS)
setFeature(c, "hw.optional.arm.FEAT_FlagM2", TS)
// setFeature(c, "", SM3) // setFeature(c, "", SM3)
// setFeature(c, "", SM4) // setFeature(c, "", SM4)
setFeature(c, "hw.optional.arm.FEAT_SVE", SVE) setFeature(c, "hw.optional.arm.FEAT_SVE", SVE)

View file

@ -39,6 +39,80 @@ const (
hwcap_SHA512 = 1 << 21 hwcap_SHA512 = 1 << 21
hwcap_SVE = 1 << 22 hwcap_SVE = 1 << 22
hwcap_ASIMDFHM = 1 << 23 hwcap_ASIMDFHM = 1 << 23
hwcap_DIT = 1 << 24
hwcap_USCAT = 1 << 25
hwcap_ILRCPC = 1 << 26
hwcap_FLAGM = 1 << 27
hwcap_SSBS = 1 << 28
hwcap_SB = 1 << 29
hwcap_PACA = 1 << 30
hwcap_PACG = 1 << 31
hwcap_GCS = 1 << 32
hwcap2_DCPODP = 1 << 0
hwcap2_SVE2 = 1 << 1
hwcap2_SVEAES = 1 << 2
hwcap2_SVEPMULL = 1 << 3
hwcap2_SVEBITPERM = 1 << 4
hwcap2_SVESHA3 = 1 << 5
hwcap2_SVESM4 = 1 << 6
hwcap2_FLAGM2 = 1 << 7
hwcap2_FRINT = 1 << 8
hwcap2_SVEI8MM = 1 << 9
hwcap2_SVEF32MM = 1 << 10
hwcap2_SVEF64MM = 1 << 11
hwcap2_SVEBF16 = 1 << 12
hwcap2_I8MM = 1 << 13
hwcap2_BF16 = 1 << 14
hwcap2_DGH = 1 << 15
hwcap2_RNG = 1 << 16
hwcap2_BTI = 1 << 17
hwcap2_MTE = 1 << 18
hwcap2_ECV = 1 << 19
hwcap2_AFP = 1 << 20
hwcap2_RPRES = 1 << 21
hwcap2_MTE3 = 1 << 22
hwcap2_SME = 1 << 23
hwcap2_SME_I16I64 = 1 << 24
hwcap2_SME_F64F64 = 1 << 25
hwcap2_SME_I8I32 = 1 << 26
hwcap2_SME_F16F32 = 1 << 27
hwcap2_SME_B16F32 = 1 << 28
hwcap2_SME_F32F32 = 1 << 29
hwcap2_SME_FA64 = 1 << 30
hwcap2_WFXT = 1 << 31
hwcap2_EBF16 = 1 << 32
hwcap2_SVE_EBF16 = 1 << 33
hwcap2_CSSC = 1 << 34
hwcap2_RPRFM = 1 << 35
hwcap2_SVE2P1 = 1 << 36
hwcap2_SME2 = 1 << 37
hwcap2_SME2P1 = 1 << 38
hwcap2_SME_I16I32 = 1 << 39
hwcap2_SME_BI32I32 = 1 << 40
hwcap2_SME_B16B16 = 1 << 41
hwcap2_SME_F16F16 = 1 << 42
hwcap2_MOPS = 1 << 43
hwcap2_HBC = 1 << 44
hwcap2_SVE_B16B16 = 1 << 45
hwcap2_LRCPC3 = 1 << 46
hwcap2_LSE128 = 1 << 47
hwcap2_FPMR = 1 << 48
hwcap2_LUT = 1 << 49
hwcap2_FAMINMAX = 1 << 50
hwcap2_F8CVT = 1 << 51
hwcap2_F8FMA = 1 << 52
hwcap2_F8DP4 = 1 << 53
hwcap2_F8DP2 = 1 << 54
hwcap2_F8E4M3 = 1 << 55
hwcap2_F8E5M2 = 1 << 56
hwcap2_SME_LUTV2 = 1 << 57
hwcap2_SME_F8F16 = 1 << 58
hwcap2_SME_F8F32 = 1 << 59
hwcap2_SME_SF8FMA = 1 << 60
hwcap2_SME_SF8DP4 = 1 << 61
hwcap2_SME_SF8DP2 = 1 << 62
hwcap2_POE = 1 << 63
) )
func detectOS(c *CPUInfo) bool { func detectOS(c *CPUInfo) bool {
@ -104,11 +178,15 @@ func detectOS(c *CPUInfo) bool {
c.featureSet.setIf(isSet(hwcap, hwcap_DCPOP), DCPOP) c.featureSet.setIf(isSet(hwcap, hwcap_DCPOP), DCPOP)
c.featureSet.setIf(isSet(hwcap, hwcap_EVTSTRM), EVTSTRM) c.featureSet.setIf(isSet(hwcap, hwcap_EVTSTRM), EVTSTRM)
c.featureSet.setIf(isSet(hwcap, hwcap_FCMA), FCMA) c.featureSet.setIf(isSet(hwcap, hwcap_FCMA), FCMA)
c.featureSet.setIf(isSet(hwcap, hwcap_ASIMDFHM), FHM)
c.featureSet.setIf(isSet(hwcap, hwcap_FP), FP) c.featureSet.setIf(isSet(hwcap, hwcap_FP), FP)
c.featureSet.setIf(isSet(hwcap, hwcap_FPHP), FPHP) c.featureSet.setIf(isSet(hwcap, hwcap_FPHP), FPHP)
c.featureSet.setIf(isSet(hwcap, hwcap_JSCVT), JSCVT) c.featureSet.setIf(isSet(hwcap, hwcap_JSCVT), JSCVT)
c.featureSet.setIf(isSet(hwcap, hwcap_LRCPC), LRCPC) c.featureSet.setIf(isSet(hwcap, hwcap_LRCPC), LRCPC)
c.featureSet.setIf(isSet(hwcap, hwcap_PMULL), PMULL) c.featureSet.setIf(isSet(hwcap, hwcap_PMULL), PMULL)
c.featureSet.setIf(isSet(hwcap, hwcap2_RNG), RNDR)
// c.featureSet.setIf(isSet(hwcap, hwcap_), TLB)
// c.featureSet.setIf(isSet(hwcap, hwcap_), TS)
c.featureSet.setIf(isSet(hwcap, hwcap_SHA1), SHA1) c.featureSet.setIf(isSet(hwcap, hwcap_SHA1), SHA1)
c.featureSet.setIf(isSet(hwcap, hwcap_SHA2), SHA2) c.featureSet.setIf(isSet(hwcap, hwcap_SHA2), SHA2)
c.featureSet.setIf(isSet(hwcap, hwcap_SHA3), SHA3) c.featureSet.setIf(isSet(hwcap, hwcap_SHA3), SHA3)

202
vendor/github.com/minio/crc64nvme/LICENSE generated vendored Normal file
View file

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

20
vendor/github.com/minio/crc64nvme/README.md generated vendored Normal file
View file

@ -0,0 +1,20 @@
## crc64nvme
This Golang package calculates CRC64 checksums using carryless-multiplication accelerated with SIMD instructions for both ARM and x86. It is based on the NVME polynomial as specified in the [NVM Express® NVM Command Set Specification](https://nvmexpress.org/wp-content/uploads/NVM-Express-NVM-Command-Set-Specification-1.0d-2023.12.28-Ratified.pdf).
The code is based on the [crc64fast-nvme](https://github.com/awesomized/crc64fast-nvme.git) package in Rust and is released under the Apache 2.0 license.
For more background on the exact technique used, see this [Fast CRC Computation for Generic Polynomials Using PCLMULQDQ Instruction](https://web.archive.org/web/20131224125630/https://www.intel.com/content/dam/www/public/us/en/documents/white-papers/fast-crc-computation-generic-polynomials-pclmulqdq-paper.pdf) paper.
### Performance
To follow.
### Requirements
All Go versions >= 1.22 are supported.
### Contributing
Contributions are welcome, please send PRs for any enhancements.

180
vendor/github.com/minio/crc64nvme/crc64.go generated vendored Normal file
View file

@ -0,0 +1,180 @@
// Copyright (c) 2025 Minio Inc. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
// Package crc64nvme implements the 64-bit cyclic redundancy check with NVME polynomial.
package crc64nvme
import (
"encoding/binary"
"errors"
"hash"
"sync"
"unsafe"
)
const (
// The size of a CRC-64 checksum in bytes.
Size = 8
// The NVME polynoimial (reversed, as used by Go)
NVME = 0x9a6c9329ac4bc9b5
)
var (
// precalculated table.
nvmeTable = makeTable(NVME)
)
// table is a 256-word table representing the polynomial for efficient processing.
type table [256]uint64
var (
slicing8TablesBuildOnce sync.Once
slicing8TableNVME *[8]table
)
func buildSlicing8TablesOnce() {
slicing8TablesBuildOnce.Do(buildSlicing8Tables)
}
func buildSlicing8Tables() {
slicing8TableNVME = makeSlicingBy8Table(makeTable(NVME))
}
func makeTable(poly uint64) *table {
t := new(table)
for i := 0; i < 256; i++ {
crc := uint64(i)
for j := 0; j < 8; j++ {
if crc&1 == 1 {
crc = (crc >> 1) ^ poly
} else {
crc >>= 1
}
}
t[i] = crc
}
return t
}
func makeSlicingBy8Table(t *table) *[8]table {
var helperTable [8]table
helperTable[0] = *t
for i := 0; i < 256; i++ {
crc := t[i]
for j := 1; j < 8; j++ {
crc = t[crc&0xff] ^ (crc >> 8)
helperTable[j][i] = crc
}
}
return &helperTable
}
// digest represents the partial evaluation of a checksum.
type digest struct {
crc uint64
}
// New creates a new hash.Hash64 computing the CRC-64 checksum using the
// NVME polynomial. Its Sum method will lay the
// value out in big-endian byte order. The returned Hash64 also
// implements [encoding.BinaryMarshaler] and [encoding.BinaryUnmarshaler] to
// marshal and unmarshal the internal state of the hash.
func New() hash.Hash64 { return &digest{0} }
func (d *digest) Size() int { return Size }
func (d *digest) BlockSize() int { return 1 }
func (d *digest) Reset() { d.crc = 0 }
const (
magic = "crc\x02"
marshaledSize = len(magic) + 8 + 8
)
func (d *digest) MarshalBinary() ([]byte, error) {
b := make([]byte, 0, marshaledSize)
b = append(b, magic...)
b = binary.BigEndian.AppendUint64(b, tableSum)
b = binary.BigEndian.AppendUint64(b, d.crc)
return b, nil
}
func (d *digest) UnmarshalBinary(b []byte) error {
if len(b) < len(magic) || string(b[:len(magic)]) != magic {
return errors.New("hash/crc64: invalid hash state identifier")
}
if len(b) != marshaledSize {
return errors.New("hash/crc64: invalid hash state size")
}
if tableSum != binary.BigEndian.Uint64(b[4:]) {
return errors.New("hash/crc64: tables do not match")
}
d.crc = binary.BigEndian.Uint64(b[12:])
return nil
}
func update(crc uint64, p []byte) uint64 {
if hasAsm && len(p) > 127 {
ptr := unsafe.Pointer(&p[0])
if align := (uintptr(ptr)+15)&^0xf - uintptr(ptr); align > 0 {
// Align to 16-byte boundary.
crc = update(crc, p[:align])
p = p[align:]
}
runs := len(p) / 128
crc = updateAsm(crc, p[:128*runs])
return update(crc, p[128*runs:])
}
buildSlicing8TablesOnce()
crc = ^crc
// table comparison is somewhat expensive, so avoid it for small sizes
for len(p) >= 64 {
var helperTable = slicing8TableNVME
// Update using slicing-by-8
for len(p) > 8 {
crc ^= binary.LittleEndian.Uint64(p)
crc = helperTable[7][crc&0xff] ^
helperTable[6][(crc>>8)&0xff] ^
helperTable[5][(crc>>16)&0xff] ^
helperTable[4][(crc>>24)&0xff] ^
helperTable[3][(crc>>32)&0xff] ^
helperTable[2][(crc>>40)&0xff] ^
helperTable[1][(crc>>48)&0xff] ^
helperTable[0][crc>>56]
p = p[8:]
}
}
// For reminders or small sizes
for _, v := range p {
crc = nvmeTable[byte(crc)^v] ^ (crc >> 8)
}
return ^crc
}
// Update returns the result of adding the bytes in p to the crc.
func Update(crc uint64, p []byte) uint64 {
return update(crc, p)
}
func (d *digest) Write(p []byte) (n int, err error) {
d.crc = update(d.crc, p)
return len(p), nil
}
func (d *digest) Sum64() uint64 { return d.crc }
func (d *digest) Sum(in []byte) []byte {
s := d.Sum64()
return append(in, byte(s>>56), byte(s>>48), byte(s>>40), byte(s>>32), byte(s>>24), byte(s>>16), byte(s>>8), byte(s))
}
// Checksum returns the CRC-64 checksum of data
// using the NVME polynomial.
func Checksum(data []byte) uint64 { return update(0, data) }
// ISO tablesum of NVME poly
const tableSum = 0x8ddd9ee4402c7163

15
vendor/github.com/minio/crc64nvme/crc64_amd64.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
// Copyright (c) 2025 Minio Inc. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
//go:build !noasm && !appengine && !gccgo
package crc64nvme
import (
"github.com/klauspost/cpuid/v2"
)
var hasAsm = cpuid.CPU.Supports(cpuid.SSE2, cpuid.CLMUL, cpuid.SSE4)
func updateAsm(crc uint64, p []byte) (checksum uint64)

157
vendor/github.com/minio/crc64nvme/crc64_amd64.s generated vendored Normal file
View file

@ -0,0 +1,157 @@
// Copyright (c) 2025 Minio Inc. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
//go:build !noasm && !appengine && !gccgo
#include "textflag.h"
TEXT ·updateAsm(SB), $0-40
MOVQ crc+0(FP), AX // checksum
MOVQ p_base+8(FP), SI // start pointer
MOVQ p_len+16(FP), CX // length of buffer
NOTQ AX
SHRQ $7, CX
CMPQ CX, $1
JLT skip128
VMOVDQA 0x00(SI), X0
VMOVDQA 0x10(SI), X1
VMOVDQA 0x20(SI), X2
VMOVDQA 0x30(SI), X3
VMOVDQA 0x40(SI), X4
VMOVDQA 0x50(SI), X5
VMOVDQA 0x60(SI), X6
VMOVDQA 0x70(SI), X7
MOVQ AX, X8
PXOR X8, X0
CMPQ CX, $1
JE tail128
MOVQ $0xa1ca681e733f9c40, AX
MOVQ AX, X8
MOVQ $0x5f852fb61e8d92dc, AX
PINSRQ $0x1, AX, X9
loop128:
ADDQ $128, SI
SUBQ $1, CX
VMOVDQA X0, X10
PCLMULQDQ $0x00, X8, X10
PCLMULQDQ $0x11, X9, X0
PXOR X10, X0
PXOR 0(SI), X0
VMOVDQA X1, X10
PCLMULQDQ $0x00, X8, X10
PCLMULQDQ $0x11, X9, X1
PXOR X10, X1
PXOR 0x10(SI), X1
VMOVDQA X2, X10
PCLMULQDQ $0x00, X8, X10
PCLMULQDQ $0x11, X9, X2
PXOR X10, X2
PXOR 0x20(SI), X2
VMOVDQA X3, X10
PCLMULQDQ $0x00, X8, X10
PCLMULQDQ $0x11, X9, X3
PXOR X10, X3
PXOR 0x30(SI), X3
VMOVDQA X4, X10
PCLMULQDQ $0x00, X8, X10
PCLMULQDQ $0x11, X9, X4
PXOR X10, X4
PXOR 0x40(SI), X4
VMOVDQA X5, X10
PCLMULQDQ $0x00, X8, X10
PCLMULQDQ $0x11, X9, X5
PXOR X10, X5
PXOR 0x50(SI), X5
VMOVDQA X6, X10
PCLMULQDQ $0x00, X8, X10
PCLMULQDQ $0x11, X9, X6
PXOR X10, X6
PXOR 0x60(SI), X6
VMOVDQA X7, X10
PCLMULQDQ $0x00, X8, X10
PCLMULQDQ $0x11, X9, X7
PXOR X10, X7
PXOR 0x70(SI), X7
CMPQ CX, $1
JGT loop128
tail128:
MOVQ $0xd083dd594d96319d, AX
MOVQ AX, X11
PCLMULQDQ $0x00, X0, X11
MOVQ $0x946588403d4adcbc, AX
PINSRQ $0x1, AX, X12
PCLMULQDQ $0x11, X12, X0
PXOR X11, X7
PXOR X0, X7
MOVQ $0x3c255f5ebc414423, AX
MOVQ AX, X11
PCLMULQDQ $0x00, X1, X11
MOVQ $0x34f5a24e22d66e90, AX
PINSRQ $0x1, AX, X12
PCLMULQDQ $0x11, X12, X1
PXOR X11, X1
PXOR X7, X1
MOVQ $0x7b0ab10dd0f809fe, AX
MOVQ AX, X11
PCLMULQDQ $0x00, X2, X11
MOVQ $0x03363823e6e791e5, AX
PINSRQ $0x1, AX, X12
PCLMULQDQ $0x11, X12, X2
PXOR X11, X2
PXOR X1, X2
MOVQ $0x0c32cdb31e18a84a, AX
MOVQ AX, X11
PCLMULQDQ $0x00, X3, X11
MOVQ $0x62242240ace5045a, AX
PINSRQ $0x1, AX, X12
PCLMULQDQ $0x11, X12, X3
PXOR X11, X3
PXOR X2, X3
MOVQ $0xbdd7ac0ee1a4a0f0, AX
MOVQ AX, X11
PCLMULQDQ $0x00, X4, X11
MOVQ $0xa3ffdc1fe8e82a8b, AX
PINSRQ $0x1, AX, X12
PCLMULQDQ $0x11, X12, X4
PXOR X11, X4
PXOR X3, X4
MOVQ $0xb0bc2e589204f500, AX
MOVQ AX, X11
PCLMULQDQ $0x00, X5, X11
MOVQ $0xe1e0bb9d45d7a44c, AX
PINSRQ $0x1, AX, X12
PCLMULQDQ $0x11, X12, X5
PXOR X11, X5
PXOR X4, X5
MOVQ $0xeadc41fd2ba3d420, AX
MOVQ AX, X11
PCLMULQDQ $0x00, X6, X11
MOVQ $0x21e9761e252621ac, AX
PINSRQ $0x1, AX, X12
PCLMULQDQ $0x11, X12, X6
PXOR X11, X6
PXOR X5, X6
MOVQ AX, X5
PCLMULQDQ $0x00, X6, X5
PSHUFD $0xee, X6, X6
PXOR X5, X6
MOVQ $0x27ecfa329aef9f77, AX
MOVQ AX, X4
PCLMULQDQ $0x00, X4, X6
PEXTRQ $0, X6, BX
MOVQ $0x34d926535897936b, AX
MOVQ AX, X4
PCLMULQDQ $0x00, X4, X6
PXOR X5, X6
PEXTRQ $1, X6, AX
XORQ BX, AX
skip128:
NOTQ AX
MOVQ AX, checksum+32(FP)
RET

15
vendor/github.com/minio/crc64nvme/crc64_arm64.go generated vendored Normal file
View file

@ -0,0 +1,15 @@
// Copyright (c) 2025 Minio Inc. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
//go:build !noasm && !appengine && !gccgo
package crc64nvme
import (
"github.com/klauspost/cpuid/v2"
)
var hasAsm = cpuid.CPU.Supports(cpuid.ASIMD) && cpuid.CPU.Supports(cpuid.PMULL)
func updateAsm(crc uint64, p []byte) (checksum uint64)

157
vendor/github.com/minio/crc64nvme/crc64_arm64.s generated vendored Normal file
View file

@ -0,0 +1,157 @@
// Copyright (c) 2025 Minio Inc. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
//go:build !noasm && !appengine && !gccgo
#include "textflag.h"
TEXT ·updateAsm(SB), $0-40
MOVD crc+0(FP), R0 // checksum
MOVD p_base+8(FP), R1 // start pointer
MOVD p_len+16(FP), R2 // length of buffer
MOVD $·const(SB), R3 // constants
MVN R0, R0
LSR $7, R2, R2
CMP $1, R2
BLT skip128
FLDPQ (R1), (F0, F1)
FLDPQ 32(R1), (F2, F3)
FLDPQ 64(R1), (F4, F5)
FLDPQ 96(R1), (F6, F7)
FMOVD R0, F8
VMOVI $0, V9.B16
VMOV V9.D[0], V8.D[1]
VEOR V8.B16, V0.B16, V0.B16
CMP $1, R2
BEQ tail128
MOVD 112(R3), R4
MOVD 120(R3), R5
FMOVD R4, F8
VDUP R5, V9.D2
loop128:
ADD $128, R1, R1
SUB $1, R2, R2
VPMULL V0.D1, V8.D1, V10.Q1
VPMULL2 V0.D2, V9.D2, V0.Q1
FLDPQ (R1), (F11, F12)
VEOR3 V0.B16, V11.B16, V10.B16, V0.B16
VPMULL V1.D1, V8.D1, V10.Q1
VPMULL2 V1.D2, V9.D2, V1.Q1
VEOR3 V1.B16, V12.B16, V10.B16, V1.B16
VPMULL V2.D1, V8.D1, V10.Q1
VPMULL2 V2.D2, V9.D2, V2.Q1
FLDPQ 32(R1), (F11, F12)
VEOR3 V2.B16, V11.B16, V10.B16, V2.B16
VPMULL V3.D1, V8.D1, V10.Q1
VPMULL2 V3.D2, V9.D2, V3.Q1
VEOR3 V3.B16, V12.B16, V10.B16, V3.B16
VPMULL V4.D1, V8.D1, V10.Q1
VPMULL2 V4.D2, V9.D2, V4.Q1
FLDPQ 64(R1), (F11, F12)
VEOR3 V4.B16, V11.B16, V10.B16, V4.B16
VPMULL V5.D1, V8.D1, V10.Q1
VPMULL2 V5.D2, V9.D2, V5.Q1
VEOR3 V5.B16, V12.B16, V10.B16, V5.B16
VPMULL V6.D1, V8.D1, V10.Q1
VPMULL2 V6.D2, V9.D2, V6.Q1
FLDPQ 96(R1), (F11, F12)
VEOR3 V6.B16, V11.B16, V10.B16, V6.B16
VPMULL V7.D1, V8.D1, V10.Q1
VPMULL2 V7.D2, V9.D2, V7.Q1
VEOR3 V7.B16, V12.B16, V10.B16, V7.B16
CMP $1, R2
BHI loop128
tail128:
MOVD (R3), R4
FMOVD R4, F11
VPMULL V0.D1, V11.D1, V11.Q1
MOVD 8(R3), R4
VDUP R4, V12.D2
VPMULL2 V0.D2, V12.D2, V0.Q1
VEOR3 V0.B16, V7.B16, V11.B16, V7.B16
MOVD 16(R3), R4
FMOVD R4, F11
VPMULL V1.D1, V11.D1, V11.Q1
MOVD 24(R3), R4
VDUP R4, V12.D2
VPMULL2 V1.D2, V12.D2, V1.Q1
VEOR3 V1.B16, V11.B16, V7.B16, V1.B16
MOVD 32(R3), R4
FMOVD R4, F11
VPMULL V2.D1, V11.D1, V11.Q1
MOVD 40(R3), R4
VDUP R4, V12.D2
VPMULL2 V2.D2, V12.D2, V2.Q1
VEOR3 V2.B16, V11.B16, V1.B16, V2.B16
MOVD 48(R3), R4
FMOVD R4, F11
VPMULL V3.D1, V11.D1, V11.Q1
MOVD 56(R3), R4
VDUP R4, V12.D2
VPMULL2 V3.D2, V12.D2, V3.Q1
VEOR3 V3.B16, V11.B16, V2.B16, V3.B16
MOVD 64(R3), R4
FMOVD R4, F11
VPMULL V4.D1, V11.D1, V11.Q1
MOVD 72(R3), R4
VDUP R4, V12.D2
VPMULL2 V4.D2, V12.D2, V4.Q1
VEOR3 V4.B16, V11.B16, V3.B16, V4.B16
MOVD 80(R3), R4
FMOVD R4, F11
VPMULL V5.D1, V11.D1, V11.Q1
MOVD 88(R3), R4
VDUP R4, V12.D2
VPMULL2 V5.D2, V12.D2, V5.Q1
VEOR3 V5.B16, V11.B16, V4.B16, V5.B16
MOVD 96(R3), R4
FMOVD R4, F11
VPMULL V6.D1, V11.D1, V11.Q1
MOVD 104(R3), R4
VDUP R4, V12.D2
VPMULL2 V6.D2, V12.D2, V6.Q1
VEOR3 V6.B16, V11.B16, V5.B16, V6.B16
FMOVD R4, F5
VPMULL V6.D1, V5.D1, V5.Q1
VDUP V6.D[1], V6.D2
VEOR V5.B8, V6.B8, V6.B8
MOVD 128(R3), R4
FMOVD R4, F4
VPMULL V4.D1, V6.D1, V6.Q1
FMOVD F6, R4
MOVD 136(R3), R5
FMOVD R5, F4
VPMULL V4.D1, V6.D1, V6.Q1
VEOR V6.B16, V5.B16, V6.B16
VMOV V6.D[1], R5
EOR R4, R5, R0
skip128:
MVN R0, R0
MOVD R0, checksum+32(FP)
RET
DATA ·const+0x000(SB)/8, $0xd083dd594d96319d // K_959
DATA ·const+0x008(SB)/8, $0x946588403d4adcbc // K_895
DATA ·const+0x010(SB)/8, $0x3c255f5ebc414423 // K_831
DATA ·const+0x018(SB)/8, $0x34f5a24e22d66e90 // K_767
DATA ·const+0x020(SB)/8, $0x7b0ab10dd0f809fe // K_703
DATA ·const+0x028(SB)/8, $0x03363823e6e791e5 // K_639
DATA ·const+0x030(SB)/8, $0x0c32cdb31e18a84a // K_575
DATA ·const+0x038(SB)/8, $0x62242240ace5045a // K_511
DATA ·const+0x040(SB)/8, $0xbdd7ac0ee1a4a0f0 // K_447
DATA ·const+0x048(SB)/8, $0xa3ffdc1fe8e82a8b // K_383
DATA ·const+0x050(SB)/8, $0xb0bc2e589204f500 // K_319
DATA ·const+0x058(SB)/8, $0xe1e0bb9d45d7a44c // K_255
DATA ·const+0x060(SB)/8, $0xeadc41fd2ba3d420 // K_191
DATA ·const+0x068(SB)/8, $0x21e9761e252621ac // K_127
DATA ·const+0x070(SB)/8, $0xa1ca681e733f9c40 // K_1087
DATA ·const+0x078(SB)/8, $0x5f852fb61e8d92dc // K_1023
DATA ·const+0x080(SB)/8, $0x27ecfa329aef9f77 // MU
DATA ·const+0x088(SB)/8, $0x34d926535897936b // POLY
GLOBL ·const(SB), (NOPTR+RODATA), $144

11
vendor/github.com/minio/crc64nvme/crc64_other.go generated vendored Normal file
View file

@ -0,0 +1,11 @@
// Copyright (c) 2025 Minio Inc. All rights reserved.
// Use of this source code is governed by a license that can be
// found in the LICENSE file.
//go:build (!amd64 || noasm || appengine || gccgo) && (!arm64 || noasm || appengine || gccgo)
package crc64nvme
var hasAsm = false
func updateAsm(crc uint64, p []byte) (checksum uint64) { panic("should not be reached") }

View file

@ -1,27 +1,72 @@
linters-settings: version: "2"
misspell:
locale: US
linters: linters:
disable-all: true disable-all: true
enable: enable:
- typecheck - durationcheck
- goimports - gocritic
- misspell - gomodguard
- revive
- govet - govet
- ineffassign - ineffassign
- gosimple - misspell
- revive
- staticcheck
- unconvert
- unused - unused
- gocritic - usetesting
- whitespace
settings:
misspell:
locale: US
staticcheck:
checks:
- all
- -SA1008
- -SA1019
- -SA4000
- -SA9004
- -ST1000
- -ST1005
- -ST1016
- -ST1021
- -ST1020
- -U1000
exclusions:
generated: lax
rules:
- path: (.+)\.go$
text: "empty-block:"
- path: (.+)\.go$
text: "unused-parameter:"
- path: (.+)\.go$
text: "dot-imports:"
- path: (.+)\.go$
text: "singleCaseSwitch: should rewrite switch statement to if statement"
- path: (.+)\.go$
text: "unlambda: replace"
- path: (.+)\.go$
text: "captLocal:"
- path: (.+)\.go$
text: "should have a package comment"
- path: (.+)\.go$
text: "ifElseChain:"
- path: (.+)\.go$
text: "elseif:"
- path: (.+)\.go$
text: "Error return value of"
- path: (.+)\.go$
text: "unnecessary conversion"
- path: (.+)\.go$
text: "Error return value is not checked"
issues: issues:
exclude-use-default: false max-issues-per-linter: 100
exclude: max-same-issues: 100
# todo fix these when we get enough time. formatters:
- "singleCaseSwitch: should rewrite switch statement to if statement" enable:
- "unlambda: replace" - gofumpt
- "captLocal:" - goimports
- "ifElseChain:" exclusions:
- "elseif:" generated: lax
- "should have a package comment" paths:
- third_party$
- builtin$
- examples$

Some files were not shown because too many files have changed in this diff Show more