mirror of
https://github.com/superseriousbusiness/gotosocial.git
synced 2024-11-22 11:46:40 +00:00
Compare commits
4 commits
bc93776868
...
96181f23c9
Author | SHA1 | Date | |
---|---|---|---|
96181f23c9 | |||
2a437685fc | |||
a48cce82b9 | |||
c8fb4c17f1 |
|
@ -385,7 +385,7 @@ We use [golangci-lint](https://golangci-lint.run/) for linting, which allows us
|
|||
|
||||
If you make a PR that doesn't pass the linter, it will be rejected. As such, it's good practice to run the linter locally before pushing or opening a PR.
|
||||
|
||||
To do this, first install the linter following the instructions [here](https://golangci-lint.run/usage/install/#local-installation).
|
||||
To do this, first install the linter following the instructions [here](https://golangci-lint.run/welcome/install/).
|
||||
|
||||
Then, you can run the linter with:
|
||||
|
||||
|
|
|
@ -167,3 +167,11 @@ Links to the set contact account and/or email address will appear on the footer
|
|||
The selected **contact user** must be an active (not suspended) admin and/or moderator on the instance.
|
||||
|
||||
If you're on a single-user instance and you give admin privileges to your main account, you can just fill in your own username here; you don't need to make a separate admin account just for this.
|
||||
|
||||
### Instance Custom CSS
|
||||
|
||||
custom CSS allows you to further customize the way your instance looks when visited through a browser.
|
||||
|
||||
This custom CSS will be applied to all pages of your instance. Users themes and CSS still take precedence over this customization.
|
||||
|
||||
See the [Custom CSS](./custom_css.md) page for some tips on writing custom CSS for your instance.
|
||||
|
|
|
@ -1550,6 +1550,10 @@ definitions:
|
|||
$ref: '#/definitions/instanceV1Configuration'
|
||||
contact_account:
|
||||
$ref: '#/definitions/account'
|
||||
custom_css:
|
||||
description: Custom CSS for the instance.
|
||||
type: string
|
||||
x-go-name: CustomCSS
|
||||
debug:
|
||||
description: Whether or not instance is running in DEBUG mode. Omitted if false.
|
||||
type: boolean
|
||||
|
@ -1730,6 +1734,10 @@ definitions:
|
|||
$ref: '#/definitions/instanceV2Configuration'
|
||||
contact:
|
||||
$ref: '#/definitions/instanceV2Contact'
|
||||
custom_css:
|
||||
description: Instance Custom Css
|
||||
type: string
|
||||
x-go-name: CustomCSS
|
||||
debug:
|
||||
description: Whether or not instance is running in DEBUG mode. Omitted if false.
|
||||
type: boolean
|
||||
|
@ -2114,7 +2122,7 @@ definitions:
|
|||
bitrate:
|
||||
description: Bitrate of the media in bits per second.
|
||||
example: 1000000
|
||||
format: int64
|
||||
format: uint64
|
||||
type: integer
|
||||
x-go-name: Bitrate
|
||||
duration:
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 200 KiB After Width: | Height: | Size: 229 KiB |
|
@ -24,12 +24,12 @@ profile gotosocial flags=(attach_disconnected, mediate_deleted) {
|
|||
|
||||
# Embedded ffmpeg needs read
|
||||
# permission on /dev/urandom.
|
||||
owner /dev/ r,
|
||||
owner /dev/urandom r,
|
||||
/dev/ r,
|
||||
/dev/urandom r,
|
||||
|
||||
# Temp dir access is needed for storing
|
||||
# files briefly during media processing.
|
||||
owner /tmp/ r,
|
||||
/tmp/ r,
|
||||
owner /tmp/* rwk,
|
||||
|
||||
# If running with GTS_WAZERO_COMPILATION_CACHE set,
|
||||
|
@ -39,7 +39,7 @@ profile gotosocial flags=(attach_disconnected, mediate_deleted) {
|
|||
|
||||
# If you've enabled logging to syslog, allow GoToSocial
|
||||
# to write logs by uncommenting the following line:
|
||||
# owner /var/log/syslog w,
|
||||
# /var/log/syslog w,
|
||||
|
||||
# These directories are not currently used by any of
|
||||
# the recommended GoToSocial installation methods, but
|
||||
|
@ -65,6 +65,7 @@ profile gotosocial flags=(attach_disconnected, mediate_deleted) {
|
|||
/etc/services r,
|
||||
/proc/sys/net/core/somaxconn r,
|
||||
/sys/fs/cgroup/system.slice/gotosocial.service/{,*} r,
|
||||
/sys/kernel/mm/hugepages/ r,
|
||||
/sys/kernel/mm/transparent_hugepage/hpage_pmd_size r,
|
||||
owner /proc/*/cgroup r,
|
||||
owner /proc/*/cpuset r,
|
||||
|
|
|
@ -145,8 +145,8 @@ func validateCreateEmoji(form *apimodel.EmojiCreateRequest) error {
|
|||
return errors.New("no emoji given")
|
||||
}
|
||||
|
||||
maxSize := config.GetMediaEmojiLocalMaxSize()
|
||||
if form.Image.Size > int64(maxSize) {
|
||||
maxSize := int64(config.GetMediaEmojiLocalMaxSize()) // #nosec G115 -- Already validated.
|
||||
if form.Image.Size > maxSize {
|
||||
return fmt.Errorf("emoji image too large: image is %dKB but size limit for custom emojis is %dKB", form.Image.Size/1024, maxSize/1024)
|
||||
}
|
||||
|
||||
|
|
|
@ -208,8 +208,8 @@ func validateUpdateEmoji(form *apimodel.EmojiUpdateRequest) error {
|
|||
}
|
||||
|
||||
if hasImage {
|
||||
maxSize := config.GetMediaEmojiLocalMaxSize()
|
||||
if form.Image.Size > int64(maxSize) {
|
||||
maxSize := int64(config.GetMediaEmojiLocalMaxSize()) // #nosec G115 -- Already validated.
|
||||
if form.Image.Size > maxSize {
|
||||
return fmt.Errorf("emoji image too large: image is %dKB but size limit for custom emojis is %dKB", form.Image.Size/1024, maxSize/1024)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -175,6 +175,7 @@ func validateInstanceUpdate(form *apimodel.InstanceSettingsUpdateRequest) error
|
|||
form.ContactEmail == nil &&
|
||||
form.ShortDescription == nil &&
|
||||
form.Description == nil &&
|
||||
form.CustomCSS == nil &&
|
||||
form.Terms == nil &&
|
||||
form.Avatar == nil &&
|
||||
form.AvatarDescription == nil &&
|
||||
|
|
|
@ -160,7 +160,7 @@ type MediaDimensions struct {
|
|||
Duration float32 `json:"duration,omitempty"`
|
||||
// Bitrate of the media in bits per second.
|
||||
// example: 1000000
|
||||
Bitrate int `json:"bitrate,omitempty"`
|
||||
Bitrate uint64 `json:"bitrate,omitempty"`
|
||||
// Size of the media, in the format `[width]x[height]`.
|
||||
// Not set for audio.
|
||||
// example: 1920x1080
|
||||
|
|
|
@ -33,6 +33,8 @@ type InstanceSettingsUpdateRequest struct {
|
|||
ShortDescription *string `form:"short_description" json:"short_description" xml:"short_description"`
|
||||
// Longer description of the instance, max 5,000 chars. HTML formatting accepted.
|
||||
Description *string `form:"description" json:"description" xml:"description"`
|
||||
// Custom CSS for the instance.
|
||||
CustomCSS *string `form:"custom_css" json:"custom_css,omitempty" xml:"custom_css"`
|
||||
// Terms and conditions of the instance, max 5,000 chars. HTML formatting accepted.
|
||||
Terms *string `form:"terms" json:"terms" xml:"terms"`
|
||||
// Image to use as the instance thumbnail.
|
||||
|
|
|
@ -38,6 +38,8 @@ type InstanceV1 struct {
|
|||
//
|
||||
// This should be displayed on the 'about' page for an instance.
|
||||
Description string `json:"description"`
|
||||
// Custom CSS for the instance.
|
||||
CustomCSS string `json:"custom_css,omitempty"`
|
||||
// Raw (unparsed) version of description.
|
||||
DescriptionText string `json:"description_text,omitempty"`
|
||||
// A shorter description of the instance.
|
||||
|
|
|
@ -53,6 +53,8 @@ type InstanceV2 struct {
|
|||
Description string `json:"description"`
|
||||
// Raw (unparsed) version of description.
|
||||
DescriptionText string `json:"description_text,omitempty"`
|
||||
// Instance Custom Css
|
||||
CustomCSS string `json:"custom_css,omitempty"`
|
||||
// Basic anonymous usage data for this instance.
|
||||
Usage InstanceV2Usage `json:"usage"`
|
||||
// An image used to represent this instance.
|
||||
|
|
2
internal/cache/domain/domain.go
vendored
2
internal/cache/domain/domain.go
vendored
|
@ -220,7 +220,7 @@ func (n *node) getChild(part string) *node {
|
|||
|
||||
for i < j {
|
||||
// avoid overflow when computing h
|
||||
h := int(uint(i+j) >> 1)
|
||||
h := int(uint(i+j) >> 1) // #nosec G115
|
||||
// i ≤ h < j
|
||||
|
||||
if n.child[h].part < part {
|
||||
|
|
|
@ -25,6 +25,7 @@
|
|||
"encoding/pem"
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"net/url"
|
||||
"os"
|
||||
"runtime"
|
||||
|
@ -489,7 +490,10 @@ func deriveBunDBPGOptions() (*pgx.ConnConfig, error) {
|
|||
cfg.Host = address
|
||||
}
|
||||
if port := config.GetDbPort(); port > 0 {
|
||||
cfg.Port = uint16(port)
|
||||
if port > math.MaxUint16 {
|
||||
return nil, errors.New("invalid port, must be in range 1-65535")
|
||||
}
|
||||
cfg.Port = uint16(port) // #nosec G115 -- Just validated above.
|
||||
}
|
||||
if u := config.GetDbUser(); u != "" {
|
||||
cfg.User = u
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
// 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"
|
||||
"strings"
|
||||
|
||||
"github.com/uptrace/bun"
|
||||
)
|
||||
|
||||
func init() {
|
||||
up := func(ctx context.Context, db *bun.DB) error {
|
||||
_, err := db.ExecContext(ctx, "ALTER TABLE ? ADD COLUMN ? TEXT", bun.Ident("instances"), bun.Ident("custom_css"))
|
||||
if err != nil && !(strings.Contains(err.Error(), "already exists") || strings.Contains(err.Error(), "duplicate column name") || strings.Contains(err.Error(), "SQLSTATE 42701")) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
down := func(ctx context.Context, db *bun.DB) error {
|
||||
_, err := db.ExecContext(ctx, "ALTER TABLE ? DROP COLUMN ?", bun.Ident("instances"), bun.Ident("custom_css"))
|
||||
return err
|
||||
}
|
||||
|
||||
if err := Migrations.Register(up, down); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
|
@ -97,11 +97,11 @@ func() (*media.ProcessingEmoji, error) {
|
|||
}
|
||||
|
||||
// Get maximum supported remote emoji size.
|
||||
maxsz := config.GetMediaEmojiRemoteMaxSize()
|
||||
maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated.
|
||||
|
||||
// Prepare data function to dereference remote emoji media.
|
||||
data := func(context.Context) (io.ReadCloser, error) {
|
||||
return tsport.DereferenceMedia(ctx, url, int64(maxsz))
|
||||
return tsport.DereferenceMedia(ctx, url, maxsz)
|
||||
}
|
||||
|
||||
// Create new emoji with prepared info.
|
||||
|
@ -189,11 +189,11 @@ func() (*media.ProcessingEmoji, error) {
|
|||
}
|
||||
|
||||
// Get maximum supported remote emoji size.
|
||||
maxsz := config.GetMediaEmojiRemoteMaxSize()
|
||||
maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated.
|
||||
|
||||
// Prepare data function to dereference remote emoji media.
|
||||
data := func(context.Context) (io.ReadCloser, error) {
|
||||
return tsport.DereferenceMedia(ctx, url, int64(maxsz))
|
||||
return tsport.DereferenceMedia(ctx, url, maxsz)
|
||||
}
|
||||
|
||||
// Update emoji with prepared info.
|
||||
|
@ -255,11 +255,11 @@ func() (*media.ProcessingEmoji, error) {
|
|||
}
|
||||
|
||||
// Get maximum supported remote emoji size.
|
||||
maxsz := config.GetMediaEmojiRemoteMaxSize()
|
||||
maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated.
|
||||
|
||||
// Prepare data function to dereference remote emoji media.
|
||||
data := func(context.Context) (io.ReadCloser, error) {
|
||||
return tsport.DereferenceMedia(ctx, url, int64(maxsz))
|
||||
return tsport.DereferenceMedia(ctx, url, maxsz)
|
||||
}
|
||||
|
||||
// Recache emoji with prepared info.
|
||||
|
|
|
@ -77,14 +77,14 @@ func() (*media.ProcessingMedia, error) {
|
|||
}
|
||||
|
||||
// Get maximum supported remote media size.
|
||||
maxsz := config.GetMediaRemoteMaxSize()
|
||||
maxsz := int64(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
|
||||
|
||||
// Create media with prepared info.
|
||||
return d.mediaManager.CreateMedia(
|
||||
ctx,
|
||||
accountID,
|
||||
func(ctx context.Context) (io.ReadCloser, error) {
|
||||
return tsport.DereferenceMedia(ctx, url, int64(maxsz))
|
||||
return tsport.DereferenceMedia(ctx, url, maxsz)
|
||||
},
|
||||
info,
|
||||
)
|
||||
|
@ -168,14 +168,14 @@ func() (*media.ProcessingMedia, error) {
|
|||
}
|
||||
|
||||
// Get maximum supported remote media size.
|
||||
maxsz := config.GetMediaRemoteMaxSize()
|
||||
maxsz := int64(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
|
||||
|
||||
// Recache media with prepared info,
|
||||
// this will also update media in db.
|
||||
return d.mediaManager.CacheMedia(
|
||||
attach,
|
||||
func(ctx context.Context) (io.ReadCloser, error) {
|
||||
return tsport.DereferenceMedia(ctx, url, int64(maxsz))
|
||||
return tsport.DereferenceMedia(ctx, url, maxsz)
|
||||
},
|
||||
), nil
|
||||
},
|
||||
|
|
|
@ -34,6 +34,7 @@ type Instance struct {
|
|||
ShortDescriptionText string `bun:""` // Raw text version of short description (before parsing).
|
||||
Description string `bun:""` // Longer description of this instance.
|
||||
DescriptionText string `bun:""` // Raw text version of long description (before parsing).
|
||||
CustomCSS string `bun:",nullzero"` // Custom CSS for the instance.
|
||||
Terms string `bun:""` // Terms and conditions of this instance.
|
||||
TermsText string `bun:""` // Raw text version of terms (before parsing).
|
||||
ContactEmail string `bun:""` // Contact email address for this instance
|
||||
|
|
|
@ -340,14 +340,14 @@ func (c *Client) do(r *Request) (rsp *http.Response, retry bool, err error) {
|
|||
|
||||
if u, _ := strconv.ParseUint(after, 10, 32); u != 0 {
|
||||
// An integer no. of backoff seconds was provided.
|
||||
r.backoff = time.Duration(u) * time.Second
|
||||
r.backoff = time.Duration(u) * time.Second // #nosec G115 -- We clamp backoff below.
|
||||
} else if at, _ := http.ParseTime(after); !at.Before(now) {
|
||||
// An HTTP formatted future date-time was provided.
|
||||
r.backoff = at.Sub(now)
|
||||
}
|
||||
|
||||
// Don't let their provided backoff exceed our max.
|
||||
if max := baseBackoff * time.Duration(c.retries); //
|
||||
if max := baseBackoff * time.Duration(c.retries); // #nosec G115 -- We control c.retries.
|
||||
r.backoff > max {
|
||||
r.backoff = max
|
||||
}
|
||||
|
|
|
@ -556,10 +556,10 @@ func (res *ffprobeResult) Process() (*result, error) {
|
|||
if p := strings.SplitN(str, "/", 2); len(p) == 2 {
|
||||
n, _ := strconv.ParseUint(p[0], 10, 32)
|
||||
d, _ := strconv.ParseUint(p[1], 10, 32)
|
||||
num, den = uint32(n), uint32(d)
|
||||
num, den = uint32(n), uint32(d) // #nosec G115 -- ParseUint is configured to check
|
||||
} else {
|
||||
n, _ := strconv.ParseUint(p[0], 10, 32)
|
||||
num = uint32(n)
|
||||
num = uint32(n) // #nosec G115 -- ParseUint is configured to check
|
||||
}
|
||||
|
||||
// Set final divised framerate.
|
||||
|
|
|
@ -399,9 +399,9 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
|
|||
g16 := uint16(s[1])
|
||||
b16 := uint16(s[2])
|
||||
a16 := uint16(a)
|
||||
d[0] = uint8(r16 * 0xff / a16)
|
||||
d[1] = uint8(g16 * 0xff / a16)
|
||||
d[2] = uint8(b16 * 0xff / a16)
|
||||
d[0] = uint8(r16 * 0xff / a16) // #nosec G115 -- Overflow desired.
|
||||
d[1] = uint8(g16 * 0xff / a16) // #nosec G115 -- Overflow desired.
|
||||
d[2] = uint8(b16 * 0xff / a16) // #nosec G115 -- Overflow desired.
|
||||
d[3] = a
|
||||
}
|
||||
j += 4
|
||||
|
@ -431,9 +431,9 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
|
|||
g32 := uint32(s[2])<<8 | uint32(s[3])
|
||||
b32 := uint32(s[4])<<8 | uint32(s[5])
|
||||
a32 := uint32(s[6])<<8 | uint32(s[7])
|
||||
d[0] = uint8((r32 * 0xffff / a32) >> 8)
|
||||
d[1] = uint8((g32 * 0xffff / a32) >> 8)
|
||||
d[2] = uint8((b32 * 0xffff / a32) >> 8)
|
||||
d[0] = uint8((r32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired.
|
||||
d[1] = uint8((g32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired.
|
||||
d[2] = uint8((b32 * 0xffff / a32) >> 8) // #nosec G115 -- Overflow desired.
|
||||
}
|
||||
d[3] = a
|
||||
j += 4
|
||||
|
@ -509,30 +509,30 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
|
|||
cr1 := int32(img.Cr[ic]) - 128
|
||||
|
||||
r := yy1 + 91881*cr1
|
||||
if uint32(r)&0xff000000 == 0 {
|
||||
if uint32(r)&0xff000000 == 0 { //nolint:gosec
|
||||
r >>= 16
|
||||
} else {
|
||||
r = ^(r >> 31)
|
||||
}
|
||||
|
||||
g := yy1 - 22554*cb1 - 46802*cr1
|
||||
if uint32(g)&0xff000000 == 0 {
|
||||
if uint32(g)&0xff000000 == 0 { //nolint:gosec
|
||||
g >>= 16
|
||||
} else {
|
||||
g = ^(g >> 31)
|
||||
}
|
||||
|
||||
b := yy1 + 116130*cb1
|
||||
if uint32(b)&0xff000000 == 0 {
|
||||
if uint32(b)&0xff000000 == 0 { //nolint:gosec
|
||||
b >>= 16
|
||||
} else {
|
||||
b = ^(b >> 31)
|
||||
}
|
||||
|
||||
d := dst[j : j+4 : j+4]
|
||||
d[0] = uint8(r)
|
||||
d[1] = uint8(g)
|
||||
d[2] = uint8(b)
|
||||
d[0] = uint8(r) // #nosec G115 -- Overflow desired.
|
||||
d[1] = uint8(g) // #nosec G115 -- Overflow desired.
|
||||
d[2] = uint8(b) // #nosec G115 -- Overflow desired.
|
||||
d[3] = 0xff
|
||||
|
||||
iy++
|
||||
|
@ -569,9 +569,9 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
|
|||
d := dst[j : j+4 : j+4]
|
||||
switch a16 {
|
||||
case 0xffff:
|
||||
d[0] = uint8(r16 >> 8)
|
||||
d[1] = uint8(g16 >> 8)
|
||||
d[2] = uint8(b16 >> 8)
|
||||
d[0] = uint8(r16 >> 8) // #nosec G115 -- Overflow desired.
|
||||
d[1] = uint8(g16 >> 8) // #nosec G115 -- Overflow desired.
|
||||
d[2] = uint8(b16 >> 8) // #nosec G115 -- Overflow desired.
|
||||
d[3] = 0xff
|
||||
case 0:
|
||||
d[0] = 0
|
||||
|
@ -579,10 +579,10 @@ func (s *scanner) scan(x1, y1, x2, y2 int, dst []uint8) {
|
|||
d[2] = 0
|
||||
d[3] = 0
|
||||
default:
|
||||
d[0] = uint8(((r16 * 0xffff) / a16) >> 8)
|
||||
d[1] = uint8(((g16 * 0xffff) / a16) >> 8)
|
||||
d[2] = uint8(((b16 * 0xffff) / a16) >> 8)
|
||||
d[3] = uint8(a16 >> 8)
|
||||
d[0] = uint8(((r16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired.
|
||||
d[1] = uint8(((g16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired.
|
||||
d[2] = uint8(((b16 * 0xffff) / a16) >> 8) // #nosec G115 -- Overflow desired.
|
||||
d[3] = uint8(a16 >> 8) // #nosec G115 -- Overflow desired.
|
||||
}
|
||||
j += 4
|
||||
}
|
||||
|
@ -617,7 +617,7 @@ func clampFloat(x float64) uint8 {
|
|||
return 255
|
||||
}
|
||||
if v > 0 {
|
||||
return uint8(v)
|
||||
return uint8(v) // #nosec G115 -- Just checked.
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
|
|
@ -49,9 +49,6 @@ func (m *Manager) RefetchEmojis(ctx context.Context, domain string, dereferenceM
|
|||
refetchIDs []string
|
||||
)
|
||||
|
||||
// Get max supported remote emoji media size.
|
||||
maxsz := config.GetMediaEmojiRemoteMaxSize()
|
||||
|
||||
// page through emojis 20 at a time, looking for those with missing images
|
||||
for {
|
||||
// Fetch next block of emojis from database
|
||||
|
@ -111,8 +108,10 @@ func (m *Manager) RefetchEmojis(ctx context.Context, domain string, dereferenceM
|
|||
continue
|
||||
}
|
||||
|
||||
// Get max supported remote emoji media size.
|
||||
maxsz := int64(config.GetMediaEmojiRemoteMaxSize()) // #nosec G115 -- Already validated.
|
||||
dataFunc := func(ctx context.Context) (reader io.ReadCloser, err error) {
|
||||
return dereferenceMedia(ctx, emojiImageIRI, int64(maxsz))
|
||||
return dereferenceMedia(ctx, emojiImageIRI, maxsz)
|
||||
}
|
||||
|
||||
processingEmoji, err := m.UpdateEmoji(ctx, emoji, dataFunc, AdditionalEmojiInfo{
|
||||
|
|
|
@ -145,7 +145,7 @@ func drainToTmp(rc io.ReadCloser) (string, error) {
|
|||
// Check to see if limit was reached,
|
||||
// (produces more useful error messages).
|
||||
if lr != nil && lr.N <= 0 {
|
||||
err := fmt.Errorf("reached read limit %s", bytesize.Size(limit))
|
||||
err := fmt.Errorf("reached read limit %s", bytesize.Size(limit)) // #nosec G115 -- Just logging
|
||||
return path, gtserror.SetLimitReached(err)
|
||||
}
|
||||
|
||||
|
|
|
@ -123,7 +123,7 @@ func Logger(logClientIP bool) gin.HandlerFunc {
|
|||
}
|
||||
|
||||
// Generate a nicer looking bytecount
|
||||
size := bytesize.Size(c.Writer.Size())
|
||||
size := bytesize.Size(c.Writer.Size()) // #nosec G115 -- Just logging
|
||||
|
||||
// Finally, write log entry with status text + body size.
|
||||
l.Logf(lvl, "%s: wrote %s", statusText, size)
|
||||
|
|
|
@ -48,7 +48,7 @@ func NewRequestID() string {
|
|||
b := make([]byte, 12)
|
||||
|
||||
// Get current time in milliseconds.
|
||||
ms := uint64(time.Now().UnixMilli())
|
||||
ms := uint64(time.Now().UnixMilli()) // #nosec G115 -- Pre-1970 clock?
|
||||
|
||||
// Store binary time data in byte buffer.
|
||||
binary.LittleEndian.PutUint64(b[0:8], ms)
|
||||
|
|
|
@ -82,12 +82,16 @@ func Throttle(cpuMultiplier int, retryAfter time.Duration) gin.HandlerFunc {
|
|||
return func(c *gin.Context) {}
|
||||
}
|
||||
|
||||
if retryAfter < 0 {
|
||||
retryAfter = 0
|
||||
}
|
||||
|
||||
var (
|
||||
limit = runtime.GOMAXPROCS(0) * cpuMultiplier
|
||||
queueLimit = limit * cpuMultiplier
|
||||
tokens = make(chan token, limit)
|
||||
requestCount = atomic.Int64{}
|
||||
retryAfterStr = strconv.FormatUint(uint64(retryAfter/time.Second), 10)
|
||||
retryAfterStr = strconv.FormatUint(uint64(retryAfter/time.Second), 10) // #nosec G115 -- Checked right above
|
||||
)
|
||||
|
||||
// prefill token channel
|
||||
|
|
|
@ -463,9 +463,10 @@ func (p *Processor) UpdateAvatar(
|
|||
) {
|
||||
// Get maximum supported local media size.
|
||||
maxsz := config.GetMediaLocalMaxSize()
|
||||
maxszInt64 := int64(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure media within size bounds.
|
||||
if avatar.Size > int64(maxsz) {
|
||||
if avatar.Size > maxszInt64 {
|
||||
text := fmt.Sprintf("media exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
@ -478,7 +479,7 @@ func (p *Processor) UpdateAvatar(
|
|||
}
|
||||
|
||||
// Wrap the multipart file reader to ensure is limited to max.
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz))
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxszInt64)
|
||||
|
||||
// Write to instance storage.
|
||||
return p.c.StoreLocalMedia(ctx,
|
||||
|
@ -508,9 +509,10 @@ func (p *Processor) UpdateHeader(
|
|||
) {
|
||||
// Get maximum supported local media size.
|
||||
maxsz := config.GetMediaLocalMaxSize()
|
||||
maxszInt64 := int64(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure media within size bounds.
|
||||
if header.Size > int64(maxsz) {
|
||||
if header.Size > maxszInt64 {
|
||||
text := fmt.Sprintf("media exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
@ -523,7 +525,7 @@ func (p *Processor) UpdateHeader(
|
|||
}
|
||||
|
||||
// Wrap the multipart file reader to ensure is limited to max.
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz))
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxszInt64)
|
||||
|
||||
// Write to instance storage.
|
||||
return p.c.StoreLocalMedia(ctx,
|
||||
|
|
|
@ -25,7 +25,6 @@
|
|||
"mime/multipart"
|
||||
"strings"
|
||||
|
||||
"codeberg.org/gruf/go-bytesize"
|
||||
"codeberg.org/gruf/go-iotools"
|
||||
apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/config"
|
||||
|
@ -46,9 +45,10 @@ func (p *Processor) EmojiCreate(
|
|||
|
||||
// Get maximum supported local emoji size.
|
||||
maxsz := config.GetMediaEmojiLocalMaxSize()
|
||||
maxszInt64 := int64(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure media within size bounds.
|
||||
if form.Image.Size > int64(maxsz) {
|
||||
if form.Image.Size > maxszInt64 {
|
||||
text := fmt.Sprintf("emoji exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ func (p *Processor) EmojiCreate(
|
|||
}
|
||||
|
||||
// Wrap the multipart file reader to ensure is limited to max.
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz))
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxszInt64)
|
||||
data := func(context.Context) (io.ReadCloser, error) {
|
||||
return rc, nil
|
||||
}
|
||||
|
@ -301,9 +301,10 @@ func (p *Processor) emojiUpdateCopy(
|
|||
|
||||
// Get maximum supported local emoji size.
|
||||
maxsz := config.GetMediaEmojiLocalMaxSize()
|
||||
maxszInt := int(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure target emoji image within size bounds.
|
||||
if bytesize.Size(target.ImageFileSize) > maxsz {
|
||||
if target.ImageFileSize > maxszInt {
|
||||
text := fmt.Sprintf("emoji exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
@ -442,9 +443,10 @@ func (p *Processor) emojiUpdateModify(
|
|||
|
||||
// Get maximum supported local emoji size.
|
||||
maxsz := config.GetMediaEmojiLocalMaxSize()
|
||||
maxszInt64 := int64(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure media within size bounds.
|
||||
if image.Size > int64(maxsz) {
|
||||
if image.Size > maxszInt64 {
|
||||
text := fmt.Sprintf("emoji exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
@ -457,7 +459,7 @@ func (p *Processor) emojiUpdateModify(
|
|||
}
|
||||
|
||||
// Wrap the multipart file reader to ensure is limited to max.
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz))
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz)) // #nosec G115 -- Already validated.
|
||||
data := func(context.Context) (io.ReadCloser, error) {
|
||||
return rc, nil
|
||||
}
|
||||
|
|
|
@ -227,6 +227,17 @@ func (p *Processor) InstancePatch(ctx context.Context, form *apimodel.InstanceSe
|
|||
columns = append(columns, []string{"description", "description_text"}...)
|
||||
}
|
||||
|
||||
// validate & update site custom css if it's set on the form
|
||||
if form.CustomCSS != nil {
|
||||
customCSS := *form.CustomCSS
|
||||
if err := validate.InstanceCustomCSS(customCSS); err != nil {
|
||||
return nil, gtserror.NewErrorBadRequest(err, err.Error())
|
||||
}
|
||||
|
||||
instance.CustomCSS = text.SanitizeToPlaintext(customCSS)
|
||||
columns = append(columns, []string{"custom_css"}...)
|
||||
}
|
||||
|
||||
// Validate & update site
|
||||
// terms if set on the form.
|
||||
if form.Terms != nil {
|
||||
|
|
|
@ -36,9 +36,10 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
|
|||
|
||||
// Get maximum supported local media size.
|
||||
maxsz := config.GetMediaLocalMaxSize()
|
||||
maxszInt64 := int64(maxsz) // #nosec G115 -- Already validated.
|
||||
|
||||
// Ensure media within size bounds.
|
||||
if form.File.Size > int64(maxsz) {
|
||||
if form.File.Size > maxszInt64 {
|
||||
text := fmt.Sprintf("media exceeds configured max size: %s", maxsz)
|
||||
return nil, gtserror.NewErrorBadRequest(errors.New(text), text)
|
||||
}
|
||||
|
@ -58,7 +59,7 @@ func (p *Processor) Create(ctx context.Context, account *gtsmodel.Account, form
|
|||
}
|
||||
|
||||
// Wrap the multipart file reader to ensure is limited to max.
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, int64(maxsz))
|
||||
rc, _, _ := iotools.UpdateReadCloserLimit(mpfile, maxszInt64)
|
||||
|
||||
// Create local media and write to instance storage.
|
||||
attachment, errWithCode := p.c.StoreLocalMedia(ctx,
|
||||
|
|
|
@ -53,7 +53,7 @@ func (t *transport) DereferenceMedia(ctx context.Context, iri *url.URL, maxsz in
|
|||
// Check media within size limit.
|
||||
if rsp.ContentLength > maxsz {
|
||||
_ = rsp.Body.Close() // close early.
|
||||
sz := bytesize.Size(maxsz) // nicer log format
|
||||
sz := bytesize.Size(maxsz) //nolint:gosec
|
||||
return nil, gtserror.Newf("media body exceeds max size %s", sz)
|
||||
}
|
||||
|
||||
|
|
|
@ -647,7 +647,7 @@ func (c *Converter) AttachmentToAPIAttachment(ctx context.Context, media *gtsmod
|
|||
Size: toAPISize(media.FileMeta.Original.Width, media.FileMeta.Original.Height),
|
||||
FrameRate: toAPIFrameRate(media.FileMeta.Original.Framerate),
|
||||
Duration: util.PtrOrZero(media.FileMeta.Original.Duration),
|
||||
Bitrate: int(util.PtrOrZero(media.FileMeta.Original.Bitrate)),
|
||||
Bitrate: util.PtrOrZero(media.FileMeta.Original.Bitrate),
|
||||
}
|
||||
|
||||
// Copy over local file URL.
|
||||
|
@ -1523,6 +1523,7 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
Title: i.Title,
|
||||
Description: i.Description,
|
||||
DescriptionText: i.DescriptionText,
|
||||
CustomCSS: i.CustomCSS,
|
||||
ShortDescription: i.ShortDescription,
|
||||
ShortDescriptionText: i.ShortDescriptionText,
|
||||
Email: i.ContactEmail,
|
||||
|
@ -1531,7 +1532,7 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
Registrations: config.GetAccountsRegistrationOpen(),
|
||||
ApprovalRequired: true, // approval always required
|
||||
InvitesEnabled: false, // todo: not supported yet
|
||||
MaxTootChars: uint(config.GetStatusesMaxChars()),
|
||||
MaxTootChars: uint(config.GetStatusesMaxChars()), // #nosec G115 -- Already validated.
|
||||
Rules: c.InstanceRulesToAPIRules(i.Rules),
|
||||
Terms: i.Terms,
|
||||
TermsRaw: i.TermsText,
|
||||
|
@ -1551,9 +1552,9 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
|
||||
instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes
|
||||
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
|
||||
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize())
|
||||
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
|
||||
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit
|
||||
instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize())
|
||||
instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
|
||||
instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit
|
||||
instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit
|
||||
instance.Configuration.Polls.MaxOptions = config.GetStatusesPollMaxOptions()
|
||||
|
@ -1563,7 +1564,7 @@ func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
instance.Configuration.Accounts.AllowCustomCSS = config.GetAccountsAllowCustomCSS()
|
||||
instance.Configuration.Accounts.MaxFeaturedTags = instanceAccountsMaxFeaturedTags
|
||||
instance.Configuration.Accounts.MaxProfileFields = instanceAccountsMaxProfileFields
|
||||
instance.Configuration.Emojis.EmojiSizeLimit = int(config.GetMediaEmojiLocalMaxSize())
|
||||
instance.Configuration.Emojis.EmojiSizeLimit = int(config.GetMediaEmojiLocalMaxSize()) // #nosec G115 -- Already validated.
|
||||
instance.Configuration.OIDCEnabled = config.GetOIDCEnabled()
|
||||
|
||||
// URLs
|
||||
|
@ -1644,6 +1645,7 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
SourceURL: instanceSourceURL,
|
||||
Description: i.Description,
|
||||
DescriptionText: i.DescriptionText,
|
||||
CustomCSS: i.CustomCSS,
|
||||
Usage: apimodel.InstanceV2Usage{}, // todo: not implemented
|
||||
Languages: config.GetInstanceLanguages().TagStrs(),
|
||||
Rules: c.InstanceRulesToAPIRules(i.Rules),
|
||||
|
@ -1695,9 +1697,9 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
instance.Configuration.Statuses.CharactersReservedPerURL = instanceStatusesCharactersReservedPerURL
|
||||
instance.Configuration.Statuses.SupportedMimeTypes = instanceStatusesSupportedMimeTypes
|
||||
instance.Configuration.MediaAttachments.SupportedMimeTypes = media.SupportedMIMETypes
|
||||
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize())
|
||||
instance.Configuration.MediaAttachments.ImageSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
|
||||
instance.Configuration.MediaAttachments.ImageMatrixLimit = instanceMediaAttachmentsImageMatrixLimit
|
||||
instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize())
|
||||
instance.Configuration.MediaAttachments.VideoSizeLimit = int(config.GetMediaRemoteMaxSize()) // #nosec G115 -- Already validated.
|
||||
instance.Configuration.MediaAttachments.VideoFrameRateLimit = instanceMediaAttachmentsVideoFrameRateLimit
|
||||
instance.Configuration.MediaAttachments.VideoMatrixLimit = instanceMediaAttachmentsVideoMatrixLimit
|
||||
instance.Configuration.Polls.MaxOptions = config.GetStatusesPollMaxOptions()
|
||||
|
@ -1707,7 +1709,7 @@ func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins
|
|||
instance.Configuration.Accounts.AllowCustomCSS = config.GetAccountsAllowCustomCSS()
|
||||
instance.Configuration.Accounts.MaxFeaturedTags = instanceAccountsMaxFeaturedTags
|
||||
instance.Configuration.Accounts.MaxProfileFields = instanceAccountsMaxProfileFields
|
||||
instance.Configuration.Emojis.EmojiSizeLimit = int(config.GetMediaEmojiLocalMaxSize())
|
||||
instance.Configuration.Emojis.EmojiSizeLimit = int(config.GetMediaEmojiLocalMaxSize()) // #nosec G115 -- Already validated.
|
||||
instance.Configuration.OIDCEnabled = config.GetOIDCEnabled()
|
||||
|
||||
// registrations
|
||||
|
|
|
@ -189,6 +189,16 @@ func CustomCSS(customCSS string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func InstanceCustomCSS(customCSS string) error {
|
||||
|
||||
maximumCustomCSSLength := config.GetAccountsCustomCSSLength()
|
||||
if length := len([]rune(customCSS)); length > maximumCustomCSSLength {
|
||||
return fmt.Errorf("custom_css must be less than %d characters, but submitted custom_css was %d characters", maximumCustomCSSLength, length)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// EmojiShortcode just runs the given shortcode through the regular expression
|
||||
// for emoji shortcodes, to figure out whether it's a valid shortcode, ie., 2-30 characters,
|
||||
// a-zA-Z, numbers, and underscores.
|
||||
|
|
|
@ -54,7 +54,7 @@ func (m *Module) aboutGETHandler(c *gin.Context) {
|
|||
Template: "about.tmpl",
|
||||
Instance: instance,
|
||||
OGMeta: apiutil.OGBase(instance),
|
||||
Stylesheets: []string{cssAbout},
|
||||
Stylesheets: []string{cssAbout, instanceCustomCSSPath},
|
||||
Extra: map[string]any{
|
||||
"showStrap": true,
|
||||
"blocklistExposed": config.GetInstanceExposeSuspendedWeb(),
|
||||
|
|
|
@ -129,6 +129,7 @@ func (m *Module) confirmEmailPOSTHandler(c *gin.Context) {
|
|||
page := apiutil.WebPage{
|
||||
Template: "confirmed_email.tmpl",
|
||||
Instance: instance,
|
||||
Stylesheets: []string{instanceCustomCSSPath},
|
||||
Extra: map[string]any{
|
||||
"email": user.Email,
|
||||
"username": user.Account.Username,
|
||||
|
|
|
@ -55,3 +55,22 @@ func (m *Module) customCSSGETHandler(c *gin.Context) {
|
|||
c.Header(cacheControlHeader, cacheControlNoCache)
|
||||
c.Data(http.StatusOK, textCSSUTF8, []byte(customCSS))
|
||||
}
|
||||
|
||||
func (m *Module) instanceCustomCSSGETHandler(c *gin.Context) {
|
||||
|
||||
if _, err := apiutil.NegotiateAccept(c, apiutil.TextCSS); err != nil {
|
||||
apiutil.WebErrorHandler(c, gtserror.NewErrorNotAcceptable(err, err.Error()), m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
instanceV1, errWithCode := m.processor.InstanceGetV1(c.Request.Context())
|
||||
if errWithCode != nil {
|
||||
apiutil.WebErrorHandler(c, errWithCode, m.processor.InstanceGetV1)
|
||||
return
|
||||
}
|
||||
|
||||
instanceCustomCSS := instanceV1.CustomCSS
|
||||
|
||||
c.Header(cacheControlHeader, cacheControlNoCache)
|
||||
c.Data(http.StatusOK, textCSSUTF8, []byte(instanceCustomCSS))
|
||||
}
|
||||
|
|
|
@ -67,7 +67,7 @@ func (m *Module) domainBlockListGETHandler(c *gin.Context) {
|
|||
Template: "domain-blocklist.tmpl",
|
||||
Instance: instance,
|
||||
OGMeta: apiutil.OGBase(instance),
|
||||
Stylesheets: []string{cssFA},
|
||||
Stylesheets: []string{cssFA, instanceCustomCSSPath},
|
||||
Javascript: []string{jsFrontend},
|
||||
Extra: map[string]any{"blocklist": domainBlocks},
|
||||
}
|
||||
|
|
|
@ -59,7 +59,7 @@ func (m *Module) indexHandler(c *gin.Context) {
|
|||
Template: "index.tmpl",
|
||||
Instance: instance,
|
||||
OGMeta: apiutil.OGBase(instance),
|
||||
Stylesheets: []string{cssAbout, cssIndex},
|
||||
Stylesheets: []string{cssAbout, cssIndex, instanceCustomCSSPath},
|
||||
Extra: map[string]any{"showStrap": true},
|
||||
}
|
||||
|
||||
|
|
|
@ -132,7 +132,7 @@ func (m *Module) profileGETHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Prepare stylesheets for profile.
|
||||
stylesheets := make([]string, 0, 6)
|
||||
stylesheets := make([]string, 0, 7)
|
||||
|
||||
// Basic profile stylesheets.
|
||||
stylesheets = append(
|
||||
|
@ -142,6 +142,7 @@ func (m *Module) profileGETHandler(c *gin.Context) {
|
|||
cssStatus,
|
||||
cssThread,
|
||||
cssProfile,
|
||||
instanceCustomCSSPath,
|
||||
}...,
|
||||
)
|
||||
|
||||
|
|
|
@ -53,6 +53,7 @@ func (m *Module) SettingsPanelHandler(c *gin.Context) {
|
|||
cssProfile, // Used for rendering stub/fake profiles.
|
||||
cssStatus, // Used for rendering stub/fake statuses.
|
||||
cssSettings,
|
||||
instanceCustomCSSPath,
|
||||
},
|
||||
Javascript: []string{jsSettings},
|
||||
}
|
||||
|
|
|
@ -128,6 +128,7 @@ func (m *Module) signupPOSTHandler(c *gin.Context) {
|
|||
page := apiutil.WebPage{
|
||||
Template: "signed-up.tmpl",
|
||||
Instance: instance,
|
||||
Stylesheets: []string{instanceCustomCSSPath},
|
||||
OGMeta: apiutil.OGBase(instance),
|
||||
Extra: map[string]any{
|
||||
"email": user.UnconfirmedEmail,
|
||||
|
|
|
@ -59,7 +59,7 @@ func (m *Module) tagGETHandler(c *gin.Context) {
|
|||
Template: "tag.tmpl",
|
||||
Instance: instance,
|
||||
OGMeta: apiutil.OGBase(instance),
|
||||
Stylesheets: []string{cssFA, cssThread, cssTag},
|
||||
Stylesheets: []string{cssFA, cssThread, cssTag, instanceCustomCSSPath},
|
||||
Extra: map[string]any{"tagName": tagName},
|
||||
}
|
||||
|
||||
|
|
|
@ -115,7 +115,7 @@ func (m *Module) threadGETHandler(c *gin.Context) {
|
|||
}
|
||||
|
||||
// Prepare stylesheets for thread.
|
||||
stylesheets := make([]string, 0, 5)
|
||||
stylesheets := make([]string, 0, 6)
|
||||
|
||||
// Basic thread stylesheets.
|
||||
stylesheets = append(
|
||||
|
@ -131,6 +131,7 @@ func (m *Module) threadGETHandler(c *gin.Context) {
|
|||
if theme := targetAccount.Theme; theme != "" {
|
||||
stylesheets = append(
|
||||
stylesheets,
|
||||
instanceCustomCSSPath,
|
||||
themesPathPrefix+"/"+theme,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -41,6 +41,7 @@
|
|||
statusPath = "/statuses/:" + apiutil.WebStatusIDKey // leave out the '/@:username' prefix as this will be served within the profile group
|
||||
tagsPath = "/tags/:" + apiutil.TagNameKey
|
||||
customCSSPath = profileGroupPath + "/custom.css"
|
||||
instanceCustomCSSPath = "/custom.css"
|
||||
rssFeedPath = profileGroupPath + "/feed.rss"
|
||||
assetsPathPrefix = "/assets"
|
||||
distPathPrefix = assetsPathPrefix + "/dist"
|
||||
|
@ -114,6 +115,7 @@ func (m *Module) Route(r *router.Router, mi ...gin.HandlerFunc) {
|
|||
r.AttachHandler(http.MethodGet, settingsPathPrefix, m.SettingsPanelHandler)
|
||||
r.AttachHandler(http.MethodGet, settingsPanelGlob, m.SettingsPanelHandler)
|
||||
r.AttachHandler(http.MethodGet, customCSSPath, m.customCSSGETHandler)
|
||||
r.AttachHandler(http.MethodGet, instanceCustomCSSPath, m.instanceCustomCSSGETHandler)
|
||||
r.AttachHandler(http.MethodGet, rssFeedPath, m.rssFeedGETHandler)
|
||||
r.AttachHandler(http.MethodGet, confirmEmailPath, m.confirmEmailGETHandler)
|
||||
r.AttachHandler(http.MethodPost, confirmEmailPath, m.confirmEmailPOSTHandler)
|
||||
|
|
|
@ -25,6 +25,7 @@ export interface InstanceV1 {
|
|||
description_text?: string;
|
||||
short_description: string;
|
||||
short_description_text?: string;
|
||||
custom_css: string;
|
||||
email: string;
|
||||
version: string;
|
||||
debug?: boolean;
|
||||
|
|
|
@ -66,6 +66,10 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
|
|||
valueSelector: (s: InstanceV1) => s.description_text,
|
||||
validator: (val: string) => val.length <= descLimit ? "" : `Instance description is ${val.length} characters; must be ${descLimit} characters or less`
|
||||
}),
|
||||
customCSS: useTextInput("custom_css", {
|
||||
source: instance,
|
||||
valueSelector: (s: InstanceV1) => s.custom_css
|
||||
}),
|
||||
terms: useTextInput("terms", {
|
||||
source: instance,
|
||||
// Select "raw" text version of parsed field for editing.
|
||||
|
@ -191,6 +195,15 @@ function InstanceSettingsForm({ data: instance }: InstanceSettingsFormProps) {
|
|||
type="email"
|
||||
/>
|
||||
|
||||
<TextArea
|
||||
field={form.customCSS}
|
||||
label={"Custom CSS"}
|
||||
className="monospace"
|
||||
rows={8}
|
||||
autoCapitalize="none"
|
||||
spellCheck="false"
|
||||
/>
|
||||
|
||||
<MutationButton label="Save" result={result} disabled={false} />
|
||||
</form>
|
||||
);
|
||||
|
|
Loading…
Reference in a new issue