mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-09-05 11:06:23 -07:00
Merge fingerprinting package into uploads package
This commit is contained in:
parent
5e7a648e37
commit
0795a0a8a8
6 changed files with 93 additions and 166 deletions
2
go.mod
2
go.mod
|
@ -7,6 +7,7 @@ require (
|
|||
github.com/Eggbertx/durationutil v1.0.0
|
||||
github.com/aquilax/tripcode v1.0.1
|
||||
github.com/cjoudrey/gluahttp v0.0.0-20201111170219-25003d9adfa9
|
||||
github.com/devedge/imagehash v0.0.0-20180324030135-7061aa3b4066
|
||||
github.com/disintegration/imaging v1.6.2
|
||||
github.com/frustra/bbcode v0.0.0-20201127003707-6ef347fbe1c8
|
||||
github.com/go-sql-driver/mysql v1.7.1
|
||||
|
@ -28,7 +29,6 @@ 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
|
||||
|
|
|
@ -179,7 +179,6 @@ type SiteConfig struct {
|
|||
MaxRecentPosts int
|
||||
RecentPostsWithNoFile bool
|
||||
EnableAppeals bool
|
||||
MaxLogDays int
|
||||
|
||||
MinifyHTML bool
|
||||
MinifyJS bool
|
||||
|
@ -187,7 +186,8 @@ type SiteConfig struct {
|
|||
GeoIPOptions map[string]any
|
||||
Captcha CaptchaConfig
|
||||
|
||||
FingerprinterOptions map[string]map[string]any
|
||||
FingerprintVideoThumbnails bool
|
||||
FingerprintHashLength int
|
||||
}
|
||||
|
||||
type CaptchaConfig struct {
|
||||
|
|
|
@ -23,7 +23,13 @@ import (
|
|||
)
|
||||
|
||||
var (
|
||||
uploadHandlers map[string]UploadHandler
|
||||
uploadHandlers map[string]UploadHandler
|
||||
imageExtensions = []string{
|
||||
".bmp", ".gif", ".jpg", ".jpeg", ".png", ".webp",
|
||||
}
|
||||
videoExtensions = []string{
|
||||
".mp4", ".webm",
|
||||
}
|
||||
)
|
||||
|
||||
type UploadHandler func(upload *gcsql.Upload, post *gcsql.Post, board string, filePath string, thumbPath string, catalogThumbPath string, infoEv *zerolog.Event, accessEv *zerolog.Event, errEv *zerolog.Event) error
|
||||
|
@ -35,13 +41,12 @@ func RegisterUploadHandler(ext string, handler UploadHandler) {
|
|||
|
||||
func init() {
|
||||
uploadHandlers = make(map[string]UploadHandler)
|
||||
RegisterUploadHandler(".gif", processImage)
|
||||
RegisterUploadHandler(".jpg", processImage)
|
||||
RegisterUploadHandler(".jpeg", processImage)
|
||||
RegisterUploadHandler(".png", processImage)
|
||||
RegisterUploadHandler(".webp", processImage)
|
||||
RegisterUploadHandler(".mp4", processVideo)
|
||||
RegisterUploadHandler(".webm", processVideo)
|
||||
for _, ext := range imageExtensions {
|
||||
uploadHandlers[ext] = processImage
|
||||
}
|
||||
for _, ext := range videoExtensions {
|
||||
uploadHandlers[ext] = processVideo
|
||||
}
|
||||
}
|
||||
|
||||
// AttachUploadFromRequest reads an incoming HTTP request and processes any incoming files.
|
||||
|
|
77
pkg/posting/uploads/fingerprinting.go
Normal file
77
pkg/posting/uploads/fingerprinting.go
Normal file
|
@ -0,0 +1,77 @@
|
|||
package uploads
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"image"
|
||||
"net/http"
|
||||
"path"
|
||||
|
||||
"github.com/devedge/imagehash"
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
)
|
||||
|
||||
const (
|
||||
defaultFingerprintHashLength = 16
|
||||
)
|
||||
|
||||
type FingerprintSource struct {
|
||||
FilePath string
|
||||
Img image.Image
|
||||
Request *http.Request
|
||||
}
|
||||
|
||||
func fingerprintImage(img image.Image, board string) (*gcsql.FileBan, error) {
|
||||
hashLength := config.GetSiteConfig().FingerprintHashLength
|
||||
if hashLength < 1 {
|
||||
hashLength = defaultFingerprintHashLength
|
||||
}
|
||||
ba, err := imagehash.Ahash(img, 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
|
||||
if 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,
|
||||
}); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if fileBan.ID == 0 {
|
||||
// no matches
|
||||
return nil, nil
|
||||
}
|
||||
return &fileBan, err
|
||||
}
|
||||
|
||||
func fingerprintFile(filePath string, board string) (*gcsql.FileBan, error) {
|
||||
img, err := imaging.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return fingerprintImage(img, board)
|
||||
}
|
||||
|
||||
func canFingerprint(filename string) bool {
|
||||
siteCfg := config.GetSiteConfig()
|
||||
ext := path.Ext(filename)
|
||||
for _, iExt := range imageExtensions {
|
||||
if iExt == ext {
|
||||
return true
|
||||
}
|
||||
}
|
||||
if siteCfg.FingerprintVideoThumbnails {
|
||||
for _, vExt := range videoExtensions {
|
||||
if vExt == ext {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
|
@ -1,105 +0,0 @@
|
|||
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
|
||||
}
|
|
@ -1,50 +0,0 @@
|
|||
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{})
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue