mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-28 08:06:24 -07:00
Start adding ahash fingerprinting support
This commit is contained in:
parent
0d68fc78cf
commit
5e7a648e37
7 changed files with 170 additions and 7 deletions
1
go.mod
1
go.mod
|
@ -28,6 +28,7 @@ require (
|
|||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/devedge/imagehash v0.0.0-20180324030135-7061aa3b4066 // indirect
|
||||
github.com/kr/pretty v0.3.1 // indirect
|
||||
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||
|
|
2
go.sum
2
go.sum
|
@ -29,6 +29,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3
|
|||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/devedge/imagehash v0.0.0-20180324030135-7061aa3b4066 h1:+QbuEqZjC9bIWKkf73EQ5oaIA7g/lB6fE7uFuEV0SeY=
|
||||
github.com/devedge/imagehash v0.0.0-20180324030135-7061aa3b4066/go.mod h1:FdoOQDHSR0xYTQCl+G8ZhqsB5dKYbyxVWUoUFW7F0Lw=
|
||||
github.com/disintegration/imaging v1.6.2 h1:w1LecBlG2Lnp8B3jk5zSuNqd7b4DXhcjwek1ei82L+c=
|
||||
github.com/disintegration/imaging v1.6.2/go.mod h1:44/5580QXChDfwIclfc/PCwrr44amcmDAg8hxG0Ewe4=
|
||||
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
|
||||
|
|
|
@ -186,6 +186,8 @@ type SiteConfig struct {
|
|||
GeoIPType string
|
||||
GeoIPOptions map[string]any
|
||||
Captcha CaptchaConfig
|
||||
|
||||
FingerprinterOptions map[string]map[string]any
|
||||
}
|
||||
|
||||
type CaptchaConfig struct {
|
||||
|
|
|
@ -54,12 +54,15 @@ type Board struct {
|
|||
// FileBan contains the information associated with a specific file ban.
|
||||
// table: DBPREFIXfile_ban
|
||||
type FileBan struct {
|
||||
ID int // sql: `id`
|
||||
BoardID *int // sql: `board_id`
|
||||
StaffID int // sql: `staff_id`
|
||||
StaffNote string // sql: `staff_note`
|
||||
IssuedAt time.Time // sql: `issued_at`
|
||||
Checksum string // sql: `checksum`
|
||||
ID int // sql: `id`
|
||||
BoardID *int // sql: `board_id`
|
||||
StaffID int // sql: `staff_id`
|
||||
StaffNote string // sql: `staff_note`
|
||||
IssuedAt time.Time // sql: `issued_at`
|
||||
Checksum string // sql: `checksum`
|
||||
Fingerprinter *string // sql: `fingerprinter`
|
||||
BanIP bool // sql: `ban_ip`
|
||||
BanIPMessage *string // sql: `ban_ip_message`
|
||||
}
|
||||
|
||||
type filenameOrUsernameBanBase struct {
|
||||
|
|
105
pkg/posting/uploads/fingerprinting/ahash.go
Normal file
105
pkg/posting/uploads/fingerprinting/ahash.go
Normal file
|
@ -0,0 +1,105 @@
|
|||
package fingerprinting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"path"
|
||||
"strings"
|
||||
|
||||
"github.com/devedge/imagehash"
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
)
|
||||
|
||||
type ahashHandler struct {
|
||||
hashVideoThumb bool // if true and the upload is a video, hash the thumb
|
||||
hashLength int
|
||||
}
|
||||
|
||||
func (ah *ahashHandler) Init(options map[string]any) error {
|
||||
var ok bool
|
||||
for key, val := range options {
|
||||
switch strings.ToLower(key) {
|
||||
case "hashvideothumb":
|
||||
fallthrough
|
||||
case "hashvideothumbnail":
|
||||
ah.hashVideoThumb, ok = val.(bool)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid value type for %q, expected boolean, got %T", key, val)
|
||||
}
|
||||
case "hashlength":
|
||||
ah.hashLength, ok = val.(int)
|
||||
if !ok {
|
||||
return fmt.Errorf("invalid value type for %q, expected voolean, got %T", key, val)
|
||||
}
|
||||
}
|
||||
}
|
||||
if ah.hashLength < 1 {
|
||||
ah.hashLength = defaultHashLength
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (ah *ahashHandler) getImage(source *FingerprintSource) (image.Image, error) {
|
||||
if source.Img == nil {
|
||||
if source.FilePath == "" {
|
||||
file, _, err := source.Request.FormFile("imagefile")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
source.Img, _, err = image.Decode(file)
|
||||
return source.Img, err
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (ah *ahashHandler) CheckFile(source *FingerprintSource, board string) (*gcsql.FileBan, error) {
|
||||
img, err := ah.getImage(source)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ba, err := imagehash.Ahash(img, ah.hashLength)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
const query = `SELECT id,board_id,staff_id,staff_note,issued_at,checksum,fingerprinter,ban_ip,ban_message
|
||||
FROM DBPREFIXfile_ban WHERE fingerprinter = 'ahash' AND checksum = ? LIMIT 1`
|
||||
|
||||
var fileBan gcsql.FileBan
|
||||
err = gcsql.QueryRowSQL(query, []any{fmt.Sprintf("%x", ba)}, []any{
|
||||
&fileBan.ID, &fileBan.BoardID, &fileBan.StaffID, &fileBan.StaffNote,
|
||||
&fileBan.IssuedAt, &fileBan.Checksum, &fileBan.Fingerprinter,
|
||||
&fileBan.BanIP, &fileBan.BanIPMessage,
|
||||
})
|
||||
return &fileBan, err
|
||||
}
|
||||
|
||||
func (ah *ahashHandler) IsCompatible(upload *gcsql.Upload) bool {
|
||||
switch strings.ToLower(path.Ext(upload.OriginalFilename)) {
|
||||
case ".jpg":
|
||||
fallthrough
|
||||
case ".jpeg":
|
||||
fallthrough
|
||||
case ".png":
|
||||
fallthrough
|
||||
case ".gif":
|
||||
fallthrough
|
||||
case ".tif":
|
||||
fallthrough
|
||||
case ".tiff":
|
||||
fallthrough
|
||||
case ".bmp":
|
||||
fallthrough
|
||||
case ".webp":
|
||||
return true
|
||||
case ".mp4":
|
||||
fallthrough
|
||||
case ".webm":
|
||||
return ah.hashVideoThumb
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (ahashHandler) Close() error {
|
||||
return nil
|
||||
}
|
50
pkg/posting/uploads/fingerprinting/fingerprinting.go
Normal file
50
pkg/posting/uploads/fingerprinting/fingerprinting.go
Normal file
|
@ -0,0 +1,50 @@
|
|||
package fingerprinting
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"net/http"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultHashLength = 16
|
||||
)
|
||||
|
||||
var (
|
||||
fingerprinterHandlers map[string]UploadFingerprinter
|
||||
)
|
||||
|
||||
type UploadFingerprinter interface {
|
||||
// Init initializes the fingerprinter, and accepts options assumed to be in gochan.json
|
||||
Init(options map[string]any) error
|
||||
// IsCompatible returns true if the fingerprinter is able to handle the incoming upload
|
||||
IsCompatible(upload *gcsql.Upload) bool
|
||||
// CheckFile scans the incoming file and scans it against files in DBPREFIXfile_ban
|
||||
// with the fingerprinter's id. It returns true if the file is banned
|
||||
CheckFile(source *FingerprintSource, board string) (*gcsql.FileBan, error)
|
||||
// Close closes the fingerprinter. This may or may not be necessary
|
||||
Close() error
|
||||
}
|
||||
|
||||
type FingerprintSource struct {
|
||||
FilePath string
|
||||
Img image.Image
|
||||
Request *http.Request
|
||||
}
|
||||
|
||||
// RegisterFingerprinter registers the given id. It must be in the configuration,
|
||||
// in the FingerprinterOptions map
|
||||
func RegisterFingerprinter(id string, handler UploadFingerprinter) error {
|
||||
_, ok := fingerprinterHandlers[id]
|
||||
if ok {
|
||||
return fmt.Errorf("a fingerprinter has already been registered to the ID %q", id)
|
||||
}
|
||||
fingerprinterHandlers[id] = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
func init() {
|
||||
RegisterFingerprinter("ahash", &ahashHandler{})
|
||||
}
|
|
@ -85,7 +85,7 @@ func processImage(upload *gcsql.Upload, post *gcsql.Post, board string, filePath
|
|||
return err
|
||||
}
|
||||
if err = syscall.Symlink(path.Join(documentRoot, "spoiler.png"), thumbPath); err != nil {
|
||||
errEv.Err(err).
|
||||
errEv.Err(err).Caller().
|
||||
Str("thumbPath", thumbPath).
|
||||
Msg("Error creating symbolic link to thumbnail path")
|
||||
return err
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue