mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-09-05 11:06:23 -07:00
replace is_regex in filters with more string matching options (substring, regex, exact match)
This commit is contained in:
parent
dbc9702b6d
commit
ce293d3e24
20 changed files with 393 additions and 670 deletions
|
@ -95,7 +95,7 @@ func MigrateFileBans(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, cfg *confi
|
|||
field = "checksum"
|
||||
}
|
||||
if _, err = db.ExecContextSQL(ctx, tx,
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id,is_regex,search,field) VALUES(?,?,?,?)`, filterID, false, fBanChecksum, field,
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id,match_mode,search,field) VALUES(?,?,?,?)`, filterID, gcsql.ExactMatch, fBanChecksum, field,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -104,7 +104,7 @@ func MigrateFileBans(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, cfg *confi
|
|||
}
|
||||
|
||||
func MigrateFilenameBans(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, cfg *config.SQLConfig) error {
|
||||
rows, err := db.QueryContextSQL(ctx, nil, `SELECT board_id,staff_id,staff_note,issued_at,filename,is_regex FROM DBPREFIXfilename_ban`)
|
||||
rows, err := db.QueryContextSQL(ctx, nil, `SELECT board_id,staff_id,staff_note,issued_at,filename,match_mode FROM DBPREFIXfilename_ban`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -117,6 +117,7 @@ func MigrateFilenameBans(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, cfg *c
|
|||
var fnBanFilename string
|
||||
var fnBanIsRegex bool
|
||||
var filterID int
|
||||
var matchMode gcsql.StringMatchMode
|
||||
for rows.Next() {
|
||||
if err = rows.Scan(
|
||||
&fnBanBoardID, &fnBanStaffID, &fnBanStaffNote, &fnBanIssuedAt, &fnBanFilename, &fnBanIsRegex,
|
||||
|
@ -138,9 +139,14 @@ func MigrateFilenameBans(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, cfg *c
|
|||
}
|
||||
}
|
||||
|
||||
if fnBanIsRegex {
|
||||
matchMode = gcsql.RegexMatch
|
||||
} else {
|
||||
matchMode = gcsql.SubstrMatch
|
||||
}
|
||||
if _, err = db.ExecContextSQL(ctx, tx,
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id,is_regex,search,field) VALUES(?,?,?,?)`,
|
||||
filterID, fnBanIsRegex, fnBanFilename, "filename",
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id,match_mode,search,field) VALUES(?,?,?,?)`,
|
||||
filterID, matchMode, fnBanFilename, "filename",
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -162,6 +168,7 @@ func MigrateUsernameBans(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, cfg *c
|
|||
var unBanUsername string
|
||||
var unBanIsRegex bool
|
||||
var filterID int
|
||||
var matchMode gcsql.StringMatchMode
|
||||
for rows.Next() {
|
||||
if err = rows.Scan(
|
||||
&unBanBoardID, &unBanStaffID, &unBanStaffNote, &unBanIssuedAt, &unBanUsername, &unBanIsRegex,
|
||||
|
@ -183,10 +190,14 @@ func MigrateUsernameBans(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, cfg *c
|
|||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if unBanIsRegex {
|
||||
matchMode = gcsql.RegexMatch
|
||||
} else {
|
||||
matchMode = gcsql.SubstrMatch
|
||||
}
|
||||
if _, err = db.ExecContextSQL(ctx, tx,
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id,is_regex,search,field) VALUES(?,?,?,?)`,
|
||||
filterID, unBanIsRegex, unBanUsername, "name",
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id,match_mode,search,field) VALUES(?,?,?,?)`,
|
||||
filterID, matchMode, unBanUsername, "name",
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -196,7 +207,7 @@ func MigrateUsernameBans(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, cfg *c
|
|||
}
|
||||
|
||||
func MigrateWordfilters(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, sqlConfig *config.SQLConfig) error {
|
||||
rows, err := db.QueryContextSQL(ctx, nil, `SELECT board_dirs, staff_id, staff_note, issued_at, search, is_regex, change_to FROM DBPREFIXwordfilters`)
|
||||
rows, err := db.QueryContextSQL(ctx, nil, `SELECT board_dirs, staff_id, staff_note, issued_at, search, match_mode, change_to FROM DBPREFIXwordfilters`)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -212,6 +223,7 @@ func MigrateWordfilters(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, sqlConf
|
|||
var isRegex bool
|
||||
var changeTo string
|
||||
var filterID int
|
||||
var matchMode gcsql.StringMatchMode
|
||||
for rows.Next() {
|
||||
if err = rows.Scan(&boardDirsPtr, &staffID, &staffNote, &issuedAt, &search, &isRegex, &changeTo); err != nil {
|
||||
return err
|
||||
|
@ -250,9 +262,14 @@ func MigrateWordfilters(db *gcsql.GCDB, ctx context.Context, tx *sql.Tx, sqlConf
|
|||
}
|
||||
}
|
||||
}
|
||||
if isRegex {
|
||||
matchMode = gcsql.RegexMatch
|
||||
} else {
|
||||
matchMode = gcsql.SubstrMatch
|
||||
}
|
||||
|
||||
if _, err = db.ExecContextSQL(ctx, tx,
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id, is_regex, search, field) VALUES(?,?,?,'body')`, filterID, isRegex, search,
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id, match_mode, search, field) VALUES(?,?,?,'body')`, filterID, matchMode, search,
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -26,7 +26,7 @@ function onFieldChange(e:JQuery.ChangeEvent) {
|
|||
const isBoolean = e.target.value === "firsttimeboard" || e.target.value === "notfirsttimeboard" ||
|
||||
e.target.value === "firsttimesite" || e.target.value === "notfirsttimesite" || e.target.value === "isop" ||
|
||||
e.target.value === "notop" || e.target.value === "hasfile" || e.target.value === "nofile";
|
||||
const noRegex = isBoolean || e.target.value === "checksum" || e.target.value === "ahash";
|
||||
const noMatchMode = isBoolean || e.target.value === "checksum" || e.target.value === "ahash";
|
||||
const $searchContainer = $fieldset.find("tr.search-cndtn");
|
||||
if(isBoolean) {
|
||||
$searchContainer.hide();
|
||||
|
@ -34,10 +34,10 @@ function onFieldChange(e:JQuery.ChangeEvent) {
|
|||
$searchContainer.show();
|
||||
}
|
||||
|
||||
if(noRegex) {
|
||||
$fieldset.find(".regex-cndtn").hide();
|
||||
if(noMatchMode) {
|
||||
$fieldset.find(".strmatch-cndtn").hide();
|
||||
} else {
|
||||
$fieldset.find(".regex-cndtn").show();
|
||||
$fieldset.find(".strmatch-cndtn").show();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
package gcsql
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
|
@ -173,366 +171,96 @@ func (ipb *IPBan) Deactivate(_ int) error {
|
|||
return tx.Commit()
|
||||
}
|
||||
|
||||
func checkUsernameOrFilename(usernameFilename string, check string, boardID int) (*filenameOrUsernameBanBase, error) {
|
||||
query := `SELECT
|
||||
id, board_id, staff_id, staff_note, issued_at, ` + usernameFilename + `, is_regex
|
||||
FROM DBPREFIX` + usernameFilename + `_ban WHERE (` + usernameFilename + ` = ? OR is_regex) AND (board_id IS NULL OR board_id = ?)`
|
||||
rows, err := QuerySQL(query, check, boardID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
for rows.Next() {
|
||||
var ban filenameOrUsernameBanBase
|
||||
err = rows.Scan(&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.check, &ban.IsRegex)
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ban.IsRegex {
|
||||
match, err := regexp.MatchString(ban.check, check)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if match {
|
||||
return &ban, nil
|
||||
}
|
||||
} else if ban.check == check {
|
||||
return &ban, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
// NewNameBan is a wrapper around ApplyFilter for creating a name and/or tripcode ban.
|
||||
// func NewNameBan(name string, nameIsRegex bool, tripcode string, tripcodeIsRegex bool, boardID int, staffID int, staffNote string) (*Filter, error) {
|
||||
// const query = `INSERT INTO DBPREFIXusername_ban
|
||||
// (board_id, staff_id, staff_note, username, is_regex)
|
||||
// VALUES(?,?,?,?,?)`
|
||||
// var ban UsernameBan
|
||||
// if boardID > 0 {
|
||||
// ban.BoardID = new(int)
|
||||
// *ban.BoardID = boardID
|
||||
// }
|
||||
|
||||
func CheckNameBan(name string, boardID int) (*UsernameBan, error) {
|
||||
banBase, err := checkUsernameOrFilename("username", name, boardID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if banBase == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &UsernameBan{
|
||||
Username: banBase.check,
|
||||
filenameOrUsernameBanBase: *banBase,
|
||||
}, nil
|
||||
}
|
||||
// tx, err := BeginTx()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer tx.Rollback()
|
||||
|
||||
func NewNameBan(name string, isRegex bool, boardID int, staffID int, staffNote string) (*UsernameBan, error) {
|
||||
const query = `INSERT INTO DBPREFIXusername_ban
|
||||
(board_id, staff_id, staff_note, username, is_regex)
|
||||
VALUES(?,?,?,?,?)`
|
||||
var ban UsernameBan
|
||||
if boardID > 0 {
|
||||
ban.BoardID = new(int)
|
||||
*ban.BoardID = boardID
|
||||
}
|
||||
// stmt, err := PrepareSQL(query, tx)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer stmt.Close()
|
||||
// if _, err = stmt.Exec(ban.BoardID, staffID, staffNote, name, isRegex); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if ban.ID, err = getLatestID("DBPREFIXusername_ban", tx); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if err = tx.Commit(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
tx, err := BeginTx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
// ban.StaffID = staffID
|
||||
// ban.StaffNote = staffNote
|
||||
// ban.Username = name
|
||||
// ban.IsRegex = isRegex
|
||||
// return &ban, stmt.Close()
|
||||
// }
|
||||
|
||||
stmt, err := PrepareSQL(query, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
if _, err = stmt.Exec(ban.BoardID, staffID, staffNote, name, isRegex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ban.ID, err = getLatestID("DBPREFIXusername_ban", tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// func NewFilenameBan(filename string, isRegex bool, boardID int, staffID int, staffNote string) (*FilenameBan, error) {
|
||||
// const query = `INSERT INTO DBPREFIXfilename_ban (board_id, staff_id, staff_note, filename, is_regex) VALUES(?,?,?,?,?)`
|
||||
// var ban FilenameBan
|
||||
// if boardID > 0 {
|
||||
// ban.BoardID = new(int)
|
||||
// *ban.BoardID = boardID
|
||||
// }
|
||||
|
||||
ban.StaffID = staffID
|
||||
ban.StaffNote = staffNote
|
||||
ban.Username = name
|
||||
ban.IsRegex = isRegex
|
||||
return &ban, stmt.Close()
|
||||
}
|
||||
// tx, err := BeginTx()
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer tx.Rollback()
|
||||
// stmt, err := PrepareSQL(query, tx)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// defer stmt.Close()
|
||||
// if _, err = stmt.Exec(ban.BoardID, staffID, staffNote, filename, isRegex); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if ban.ID, err = getLatestID("DBPREFIXfilename_ban", tx); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// if err = tx.Commit(); err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
func GetNameBans(boardID int, limit int) ([]UsernameBan, error) {
|
||||
query := `SELECT
|
||||
id, board_id, staff_id, staff_note, issued_at, username, is_regex
|
||||
FROM DBPREFIXusername_ban`
|
||||
limitStr := ""
|
||||
if limit > 0 {
|
||||
limitStr = " LIMIT " + strconv.Itoa(limit)
|
||||
}
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
if boardID > 0 {
|
||||
query += " WHERE board_id = ?"
|
||||
rows, err = QuerySQL(query+limitStr, boardID)
|
||||
} else {
|
||||
rows, err = QuerySQL(query + limitStr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var bans []UsernameBan
|
||||
for rows.Next() {
|
||||
var ban UsernameBan
|
||||
if err = rows.Scan(
|
||||
&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Username,
|
||||
&ban.IsRegex,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bans = append(bans, ban)
|
||||
}
|
||||
return bans, nil
|
||||
}
|
||||
// ban.StaffID = staffID
|
||||
// ban.StaffNote = staffNote
|
||||
// ban.Filename = filename
|
||||
// ban.IsRegex = isRegex
|
||||
// return &ban, stmt.Close()
|
||||
// }
|
||||
|
||||
func DeleteNameBan(id int) error {
|
||||
const query = `DELETE FROM DBPREFIXusername_ban WHERE id = ?`
|
||||
_, err := ExecSQL(query, id)
|
||||
return err
|
||||
}
|
||||
|
||||
func GetFileBans(boardID int, limit int) ([]FileBan, error) {
|
||||
query := `SELECT id, board_id, staff_id, staff_note, issued_at, checksum, fingerprinter, ban_ip, ban_ip_message FROM DBPREFIXfile_ban`
|
||||
limitStr := ""
|
||||
if limit > 0 {
|
||||
limitStr = " LIMIT " + strconv.Itoa(limit)
|
||||
}
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
if boardID > 0 {
|
||||
query += " WHERE board_id = ?"
|
||||
rows, err = QuerySQL(query+limitStr, boardID)
|
||||
} else {
|
||||
rows, err = QuerySQL(query + limitStr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var bans []FileBan
|
||||
for rows.Next() {
|
||||
var ban FileBan
|
||||
if err = rows.Scan(
|
||||
&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt,
|
||||
&ban.Checksum, &ban.Fingerprinter, &ban.BanIP, &ban.BanIPMessage,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bans = append(bans, ban)
|
||||
}
|
||||
return bans, nil
|
||||
}
|
||||
|
||||
func GetFilenameBans(boardID int, limit int) ([]FilenameBan, error) {
|
||||
query := `SELECT id, board_id, staff_id, staff_note, issued_at, filename, is_regex FROM DBPREFIXfilename_ban`
|
||||
limitStr := ""
|
||||
if limit > 0 {
|
||||
limitStr = " LIMIT " + strconv.Itoa(limit)
|
||||
}
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
if boardID > 0 {
|
||||
query += " WHERE board_id = ?"
|
||||
rows, err = QuerySQL(query+limitStr, boardID)
|
||||
} else {
|
||||
rows, err = QuerySQL(query + limitStr)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
var bans []FilenameBan
|
||||
for rows.Next() {
|
||||
var ban FilenameBan
|
||||
if err = rows.Scan(&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Filename, &ban.IsRegex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bans = append(bans, ban)
|
||||
}
|
||||
return bans, nil
|
||||
}
|
||||
|
||||
func NewFilenameBan(filename string, isRegex bool, boardID int, staffID int, staffNote string) (*FilenameBan, error) {
|
||||
const query = `INSERT INTO DBPREFIXfilename_ban (board_id, staff_id, staff_note, filename, is_regex) VALUES(?,?,?,?,?)`
|
||||
var ban FilenameBan
|
||||
if boardID > 0 {
|
||||
ban.BoardID = new(int)
|
||||
*ban.BoardID = boardID
|
||||
}
|
||||
|
||||
tx, err := BeginTx()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
stmt, err := PrepareSQL(query, tx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer stmt.Close()
|
||||
if _, err = stmt.Exec(ban.BoardID, staffID, staffNote, filename, isRegex); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ban.ID, err = getLatestID("DBPREFIXfilename_ban", tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ban.StaffID = staffID
|
||||
ban.StaffNote = staffNote
|
||||
ban.Filename = filename
|
||||
ban.IsRegex = isRegex
|
||||
return &ban, stmt.Close()
|
||||
}
|
||||
|
||||
func (ub filenameOrUsernameBanBase) IsGlobalBan() bool {
|
||||
return ub.BoardID == nil
|
||||
}
|
||||
|
||||
func (fnb *FilenameBan) Deactivate(_ int) error {
|
||||
const deleteQuery = `DELETE FROM DBPREFIXfilename_ban WHERE id = ?`
|
||||
_, err := ExecSQL(deleteQuery, fnb.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (fnb *UsernameBan) Deactivate(_ int) error {
|
||||
const deleteQuery = `DELETE FROM DBPREFIXusername_ban WHERE id = ?`
|
||||
_, err := ExecSQL(deleteQuery, fnb.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func CheckFilenameBan(filename string, boardID int) (*FilenameBan, error) {
|
||||
banBase, err := checkUsernameOrFilename("filename", filename, boardID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if banBase == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return &FilenameBan{
|
||||
Filename: banBase.check,
|
||||
filenameOrUsernameBanBase: *banBase,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CheckFileChecksumBan checks to see if the given checksum is banned on the given boardID, or on all boards.
|
||||
// It returns the ban info (or nil if it is not banned) and any errors
|
||||
func CheckFileChecksumBan(checksum string, boardID int) (*FileBan, error) {
|
||||
const query = `SELECT
|
||||
id, board_id, staff_id, staff_note, issued_at, checksum
|
||||
FROM DBPREFIXfile_ban
|
||||
WHERE checksum = ? AND (board_id IS NULL OR board_id = ?) ORDER BY id DESC LIMIT 1`
|
||||
var ban FileBan
|
||||
err := QueryRowSQL(query, []any{checksum, boardID}, []any{
|
||||
&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Checksum,
|
||||
})
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, nil
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &ban, err
|
||||
}
|
||||
|
||||
func GetChecksumBans(boardID int, limit int) ([]FileBan, error) {
|
||||
query := `SELECT
|
||||
id, board_id, staff_id, staff_note, issued_at, checksum
|
||||
FROM DBPREFIXfile_ban`
|
||||
if boardID > 0 {
|
||||
query += " WHERE board_id = ?"
|
||||
}
|
||||
query += " LIMIT " + strconv.Itoa(limit)
|
||||
var rows *sql.Rows
|
||||
var cancel context.CancelFunc
|
||||
var err error
|
||||
if boardID > 0 {
|
||||
rows, cancel, err = QueryTimeoutSQL(nil, query, boardID)
|
||||
} else {
|
||||
rows, cancel, err = QueryTimeoutSQL(nil, query)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
rows.Close()
|
||||
cancel()
|
||||
}()
|
||||
var bans []FileBan
|
||||
for rows.Next() {
|
||||
var ban FileBan
|
||||
if err = rows.Scan(
|
||||
&ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Checksum,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
bans = append(bans, ban)
|
||||
}
|
||||
return bans, rows.Close()
|
||||
}
|
||||
|
||||
func NewFileChecksumBan(checksum string, fingerprinter string, boardID int, staffID int, staffNote string, banIP bool, banReason string) (*FileBan, error) {
|
||||
const query = `INSERT INTO DBPREFIXfile_ban
|
||||
(board_id, staff_id, staff_note, checksum, fingerprinter, ban_ip, ban_ip_message)
|
||||
VALUES(?,?,?,?,?,?,?)`
|
||||
var ban FileBan
|
||||
var err error
|
||||
|
||||
if boardID > 0 {
|
||||
ban.BoardID = new(int)
|
||||
*ban.BoardID = boardID
|
||||
}
|
||||
if banIP {
|
||||
ban.BanIP = banIP
|
||||
ban.BanIPMessage = new(string)
|
||||
*ban.BanIPMessage = banReason
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), gcdb.defaultTimeout)
|
||||
defer cancel()
|
||||
|
||||
tx, err := BeginContextTx(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer tx.Rollback()
|
||||
if _, err = ExecContextSQL(ctx, tx, query,
|
||||
ban.BoardID, staffID, staffNote, checksum, fingerprinter, banIP, banReason,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if ban.ID, err = getLatestID("DBPREFIXfile_ban", tx); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = tx.Commit(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ban.StaffID = staffID
|
||||
ban.StaffNote = staffNote
|
||||
ban.Checksum = checksum
|
||||
return &ban, nil
|
||||
}
|
||||
|
||||
func (fb *FileBan) IsGlobalBan() bool {
|
||||
return fb.BoardID == nil
|
||||
}
|
||||
|
||||
func (fb FileBan) Deactivate(_ int) error {
|
||||
const deleteQuery = `DELETE FROM DBPREFIXfile_ban WHERE id = ?`
|
||||
_, err := ExecTimeoutSQL(nil, deleteQuery, fb.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteFileBanByID deletes the ban, given the id column value
|
||||
func DeleteFileBanByID(id int) error {
|
||||
_, err := ExecTimeoutSQL(nil, "DELETE FROM DBPREFIXfile_ban WHERE id = ?", id)
|
||||
return err
|
||||
}
|
||||
// // CheckFileChecksumBan checks to see if the given checksum is banned on the given boardID, or on all boards.
|
||||
// // It returns the ban info (or nil if it is not banned) and any errors
|
||||
// func CheckFileChecksumBan(checksum string, boardID int) (*FileBan, error) {
|
||||
// const query = `SELECT
|
||||
// id, board_id, staff_id, staff_note, issued_at, checksum
|
||||
// FROM DBPREFIXfile_ban
|
||||
// WHERE checksum = ? AND (board_id IS NULL OR board_id = ?) ORDER BY id DESC LIMIT 1`
|
||||
// var ban FileBan
|
||||
// err := QueryRowSQL(query, []any{checksum, boardID}, []any{
|
||||
// &ban.ID, &ban.BoardID, &ban.StaffID, &ban.StaffNote, &ban.IssuedAt, &ban.Checksum,
|
||||
// })
|
||||
// if err == sql.ErrNoRows {
|
||||
// return nil, nil
|
||||
// }
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return &ban, err
|
||||
// }
|
||||
|
|
|
@ -97,14 +97,21 @@ func firstPost(post *Post, global bool) (bool, error) {
|
|||
}
|
||||
|
||||
func matchString(fc *FilterCondition, checkStr string) (bool, error) {
|
||||
if fc.IsRegex {
|
||||
switch fc.MatchMode {
|
||||
case SubstrMatch:
|
||||
return strings.Contains(checkStr, fc.Search), nil
|
||||
case SubstrMatchCaseInsensitive:
|
||||
return strings.Contains(strings.ToLower(checkStr), strings.ToLower(fc.Search)), nil
|
||||
case RegexMatch:
|
||||
re, err := regexp.Compile(fc.Search)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return re.MatchString(checkStr), nil
|
||||
case ExactMatch:
|
||||
return checkStr == fc.Search, nil
|
||||
}
|
||||
return strings.Contains(checkStr, fc.Search), nil
|
||||
return false, ErrInvalidStringMatchMode
|
||||
}
|
||||
|
||||
func init() {
|
||||
|
@ -118,7 +125,7 @@ func init() {
|
|||
"trip": &conditionHandler{
|
||||
fieldType: StringField,
|
||||
matchFunc: func(r *http.Request, p *Post, _ *Upload, fc *FilterCondition) (bool, error) {
|
||||
return matchString(fc, p.Name)
|
||||
return matchString(fc, p.Tripcode)
|
||||
},
|
||||
},
|
||||
"email": &conditionHandler{
|
||||
|
|
|
@ -13,23 +13,26 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
AllFilters ActiveFilter = iota
|
||||
OnlyActiveFilters
|
||||
OnlyInactiveFilters
|
||||
)
|
||||
const (
|
||||
onlyWordfilters wordFilterFilter = iota
|
||||
onlyNonWordfilters
|
||||
// SubstrMatch represents a condition that checks if the field condtains a string, case sensitive
|
||||
SubstrMatch StringMatchMode = iota
|
||||
// SubstrMatchCaseInsensitive represents a condition that checks if the field condtains a string, not case sensitive
|
||||
SubstrMatchCaseInsensitive
|
||||
// RegexMatch represents a condition that checks if the field matches a regular expression
|
||||
RegexMatch
|
||||
// ExactMatch represents a condition that checks if the field exactly matches string
|
||||
ExactMatch
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidConditionField = errors.New("unrecognized filter condition field")
|
||||
ErrInvalidMatchAction = errors.New("unrecognized filter match action")
|
||||
ErrInvalidFilter = errors.New("unrecognized filter id")
|
||||
ErrNoConditions = errors.New("error has no match conditions")
|
||||
ErrInvalidStringMatchMode = errors.New("invalid string match mode")
|
||||
ErrInvalidConditionField = errors.New("unrecognized filter condition field")
|
||||
ErrInvalidMatchAction = errors.New("unrecognized filter match action")
|
||||
ErrInvalidFilter = errors.New("unrecognized filter id")
|
||||
ErrNoConditions = errors.New("error has no match conditions")
|
||||
)
|
||||
|
||||
type wordFilterFilter int
|
||||
// StringMatchMode is used when matching a string, determining how it should be checked (substring, regex, or exact match)
|
||||
type StringMatchMode int
|
||||
|
||||
// GetFilterByID returns the filter with the given ID, and an error if one occured
|
||||
func GetFilterByID(id int) (*Filter, error) {
|
||||
|
@ -48,10 +51,10 @@ func GetFilterByID(id int) (*Filter, error) {
|
|||
|
||||
// GetAllFilters returns an array of all post filters, and an error if one occured. It can optionally return only the active or
|
||||
// only the inactive filters (or return all)
|
||||
func GetAllFilters(show ActiveFilter) ([]Filter, error) {
|
||||
func GetAllFilters(activeFilter BooleanFilter) ([]Filter, error) {
|
||||
query := `SELECT id, staff_id, staff_note, issued_at, match_action, match_detail, is_active
|
||||
FROM DBPREFIXfilters
|
||||
WHERE match_action <> 'replace'` + show.whereClause(true)
|
||||
WHERE match_action <> 'replace'` + activeFilter.whereClause("is_active", true)
|
||||
rows, cancel, err := QueryTimeoutSQL(nil, query)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
|
@ -75,29 +78,28 @@ func GetAllFilters(show ActiveFilter) ([]Filter, error) {
|
|||
return filters, rows.Close()
|
||||
}
|
||||
|
||||
func getFiltersByBoardDir(dir string, includeAllBoards bool, show ActiveFilter, filterWordFilters wordFilterFilter) ([]Filter, error) {
|
||||
func getFiltersByBoardDir(dir string, includeAllBoards bool, activeFilter BooleanFilter, useWordFilters bool) ([]Filter, error) {
|
||||
query := `SELECT DBPREFIXfilters.id, staff_id, staff_note, issued_at, match_action, match_detail, is_active
|
||||
FROM DBPREFIXfilters
|
||||
LEFT JOIN DBPREFIXfilter_boards ON filter_id = DBPREFIXfilters.id
|
||||
LEFT JOIN DBPREFIXboards ON DBPREFIXboards.id = board_id`
|
||||
|
||||
switch filterWordFilters {
|
||||
case onlyWordfilters:
|
||||
if useWordFilters {
|
||||
query += ` WHERE match_action = 'replace'`
|
||||
case onlyNonWordfilters:
|
||||
} else {
|
||||
query += ` WHERE match_action <> 'replace'`
|
||||
}
|
||||
|
||||
var params []any
|
||||
if dir == "" {
|
||||
query += show.whereClause(true)
|
||||
query += activeFilter.whereClause("is_active", true)
|
||||
params = []any{}
|
||||
} else {
|
||||
query += ` AND dir = ?`
|
||||
if includeAllBoards {
|
||||
query += " OR board_id IS NULL"
|
||||
}
|
||||
query += show.whereClause(true)
|
||||
query += activeFilter.whereClause("is_active", true)
|
||||
params = []any{dir}
|
||||
}
|
||||
rows, cancel, err := QueryTimeoutSQL(nil, query, params...)
|
||||
|
@ -126,21 +128,23 @@ func getFiltersByBoardDir(dir string, includeAllBoards bool, show ActiveFilter,
|
|||
// GetFiltersByBoardDir returns the filters associated with the given board dir, optionally including filters
|
||||
// not associated with a specific board. It can optionally return only the active or only the inactive filters
|
||||
// (or return all)
|
||||
func GetFiltersByBoardDir(dir string, includeAllBoards bool, show ActiveFilter) ([]Filter, error) {
|
||||
return getFiltersByBoardDir(dir, includeAllBoards, show, onlyNonWordfilters)
|
||||
func GetFiltersByBoardDir(dir string, includeAllBoards bool, show BooleanFilter) ([]Filter, error) {
|
||||
return getFiltersByBoardDir(dir, includeAllBoards, show, false)
|
||||
}
|
||||
|
||||
// GetFiltersByBoardID returns an array of post filters associated to the given board ID, including
|
||||
// filters set to "All boards" if includeAllBoards is true. It can optionally return only the active or
|
||||
// only the inactive filters (or return all)
|
||||
func GetFiltersByBoardID(boardID int, includeAllBoards bool, show ActiveFilter) ([]Filter, error) {
|
||||
func GetFiltersByBoardID(boardID int, includeAllBoards bool, activeFilter BooleanFilter) ([]Filter, error) {
|
||||
query := `SELECT DBPREFIXfilters.id, staff_id, staff_note, issued_at, match_action, match_detail, is_active
|
||||
FROM DBPREFIXfilters LEFT JOIN DBPREFIXfilter_boards ON filter_id = DBPREFIXfilters.id
|
||||
WHERE match_action <> 'replace' AND board_id = ?`
|
||||
WHERE match_action <> 'replace' AND`
|
||||
if includeAllBoards {
|
||||
query += " OR board_id IS NULL"
|
||||
query += " (board_id = ? OR board_id IS NULL) "
|
||||
} else {
|
||||
query += " board_id = ? "
|
||||
}
|
||||
query += show.whereClause(true)
|
||||
query += activeFilter.whereClause("is_active", true)
|
||||
|
||||
rows, cancel, err := QueryTimeoutSQL(nil, query, boardID)
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
|
@ -170,7 +174,7 @@ func (f *Filter) Conditions() ([]FilterCondition, error) {
|
|||
if len(f.conditions) > 0 {
|
||||
return f.conditions, nil
|
||||
}
|
||||
rows, cancel, err := QueryTimeoutSQL(nil, `SELECT id, filter_id, is_regex, search, field FROM DBPREFIXfilter_conditions WHERE filter_id = ?`, f.ID)
|
||||
rows, cancel, err := QueryTimeoutSQL(nil, `SELECT id, filter_id, match_mode, search, field FROM DBPREFIXfilter_conditions WHERE filter_id = ?`, f.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -180,7 +184,7 @@ func (f *Filter) Conditions() ([]FilterCondition, error) {
|
|||
}()
|
||||
for rows.Next() {
|
||||
var condition FilterCondition
|
||||
if err = rows.Scan(&condition.ID, &condition.FilterID, &condition.IsRegex, &condition.Search, &condition.Field); err != nil {
|
||||
if err = rows.Scan(&condition.ID, &condition.FilterID, &condition.MatchMode, &condition.Search, &condition.Field); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
f.conditions = append(f.conditions, condition)
|
||||
|
@ -528,11 +532,9 @@ func (fc *FilterCondition) testCondition(post *Post, upload *Upload, request *ht
|
|||
return match, err
|
||||
}
|
||||
|
||||
// CanDoRegex is a convenience function for templates. It returns true if the filter condition should show a regular expression
|
||||
// checkbox
|
||||
func (fc FilterCondition) CanDoRegex() bool {
|
||||
return fc.HasSearchField() && (fc.Field == "name" || fc.Field == "trip" || fc.Field == "email" || fc.Field == "subject" ||
|
||||
fc.Field == "body" || fc.Field == "filename" || fc.Field == "useragent")
|
||||
// ShowStringMatchOptions is a convenience function for templates.
|
||||
func (fc FilterCondition) ShowStringMatchOptions() bool {
|
||||
return fc.HasSearchField() && fc.Field != "checksum" && fc.Field != "ahash"
|
||||
}
|
||||
|
||||
// HasSearchField is a convenience function for templates. It returns true if the filter condition should show a search box
|
||||
|
@ -545,7 +547,7 @@ func (fc FilterCondition) HasSearchField() bool {
|
|||
// DoPostFiltering checks the filters against the given post. If a match is found, its respective action is taken and the filter
|
||||
// is returned. It logs any errors it receives and returns a sanitized error (if one occured) that can be shown to the end user
|
||||
func DoPostFiltering(post *Post, upload *Upload, boardID int, request *http.Request, errEv *zerolog.Event) (*Filter, error) {
|
||||
filters, err := GetFiltersByBoardID(boardID, true, OnlyActiveFilters)
|
||||
filters, err := GetFiltersByBoardID(boardID, true, OnlyTrue)
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Msg("Unable to get filter list")
|
||||
return nil, errors.New("unable to get post filter list")
|
||||
|
|
|
@ -32,7 +32,7 @@ var (
|
|||
`CREATE TABLE reports_audit\(\s+report_id BIGINT NOT NULL,\s+timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+handled_by_staff_id BIGINT,\s+is_cleared BOOL NOT NULL,\s+CONSTRAINT reports_audit_handled_by_staff_id_fk\s+FOREIGN KEY\(handled_by_staff_id\) REFERENCES staff\(id\),\s+CONSTRAINT reports_audit_report_id_fk\s+FOREIGN KEY\(report_id\) REFERENCES reports\(id\) ON DELETE CASCADE\s+\)`,
|
||||
`CREATE TABLE filters\(\s*id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s*staff_id BIGINT,\s*staff_note VARCHAR\(255\) NOT NULL,\s*issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s*match_action VARCHAR\(45\) NOT NULL DEFAULT 'replace',\s*match_detail TEXT NOT NULL,\s*is_active BOOL NOT NULL,\s*CONSTRAINT filters_staff_id_fk\s*FOREIGN KEY\(staff_id\) REFERENCES staff\(id\)\s*ON DELETE SET NULL\s*\)`,
|
||||
`CREATE TABLE filter_boards\(\s*id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s*filter_id BIGINT NOT NULL,\s*board_id BIGINT NOT NULL,\s*CONSTRAINT filter_boards_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE,\s*CONSTRAINT filter_boards_board_id_fk\s*FOREIGN KEY\(board_id\)\s*REFERENCES boards\(id\)\s*ON DELETE CASCADE\s*\)`,
|
||||
`CREATE TABLE filter_conditions\(\s*id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s*filter_id BIGINT NOT NULL,\s*is_regex SMALLINT NOT NULL,\s*search VARCHAR\(75\) NOT NULL,\s*field VARCHAR\(75\) NOT NULL,\s*CONSTRAINT filter_conditions_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE,\s*CONSTRAINT filter_conditions_search_check CHECK \(search <> ''\)\s*\)`,
|
||||
`CREATE TABLE filter_conditions\(\s*id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s*filter_id BIGINT NOT NULL,\s*match_mode SMALLINT NOT NULL,\s*search VARCHAR\(75\) NOT NULL,\s*field VARCHAR\(75\) NOT NULL,\s*CONSTRAINT filter_conditions_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE,\s*CONSTRAINT filter_conditions_search_check CHECK \(search <> ''\)\s*\)`,
|
||||
`CREATE TABLE filter_hits\(\s*id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,\s*filter_id BIGINT NOT NULL,\s*post_data TEXT NOT NULL,\s*match_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s*CONSTRAINT filter_hits_filter_id_fk\s*FOREIGN KEY\(filter_id\)\s*REFERENCES filters\(id\)\s*ON DELETE CASCADE\s*\)`,
|
||||
`INSERT INTO database_version\(component, version\)\s+VALUES\('gochan', 3\)`,
|
||||
}
|
||||
|
@ -57,7 +57,7 @@ var (
|
|||
`CREATE TABLE reports_audit\(\s+report_id BIGINT NOT NULL,\s+timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+handled_by_staff_id BIGINT,\s+is_cleared BOOL NOT NULL,\s+CONSTRAINT reports_audit_handled_by_staff_id_fk\s+FOREIGN KEY\(handled_by_staff_id\) REFERENCES staff\(id\),\s+CONSTRAINT reports_audit_report_id_fk\s+FOREIGN KEY\(report_id\) REFERENCES reports\(id\) ON DELETE CASCADE\s+\)`,
|
||||
`CREATE TABLE filters\(\s*id BIGSERIAL PRIMARY KEY,\s*staff_id BIGINT,\s*staff_note VARCHAR\(255\) NOT NULL,\s*issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s*match_action VARCHAR\(45\) NOT NULL DEFAULT 'replace',\s*match_detail TEXT NOT NULL,\s*is_active BOOL NOT NULL,\s*CONSTRAINT filters_staff_id_fk\s*FOREIGN KEY\(staff_id\) REFERENCES staff\(id\)\s*ON DELETE SET NULL\s*\)`,
|
||||
`CREATE TABLE filter_boards\(\s*id BIGSERIAL PRIMARY KEY,\s*filter_id BIGINT NOT NULL,\s*board_id BIGINT NOT NULL,\s*CONSTRAINT filter_boards_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE,\s*CONSTRAINT filter_boards_board_id_fk\s*FOREIGN KEY\(board_id\) REFERENCES boards\(id\)\s*ON DELETE CASCADE\s*\)`,
|
||||
`CREATE TABLE filter_conditions\(\s*id BIGSERIAL PRIMARY KEY,\s*filter_id BIGINT NOT NULL,\s*is_regex SMALLINT NOT NULL,\s*search VARCHAR\(75\) NOT NULL,\s*field VARCHAR\(75\) NOT NULL,\s*CONSTRAINT filter_conditions_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE,\s*CONSTRAINT filter_conditions_search_check CHECK \(search <> ''\)\s*\)`,
|
||||
`CREATE TABLE filter_conditions\(\s*id BIGSERIAL PRIMARY KEY,\s*filter_id BIGINT NOT NULL,\s*match_mode SMALLINT NOT NULL,\s*search VARCHAR\(75\) NOT NULL,\s*field VARCHAR\(75\) NOT NULL,\s*CONSTRAINT filter_conditions_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE,\s*CONSTRAINT filter_conditions_search_check CHECK \(search <> ''\)\s*\)`,
|
||||
`CREATE TABLE filter_hits\(\s*id BIGSERIAL PRIMARY KEY,\s*filter_id BIGINT NOT NULL,\s*post_data TEXT NOT NULL,\s*match_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s*CONSTRAINT filter_hits_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE\s*\)`,
|
||||
`INSERT INTO database_version\(component, version\)\s+VALUES\('gochan', 3\)`,
|
||||
}
|
||||
|
@ -82,7 +82,7 @@ var (
|
|||
`CREATE TABLE reports_audit\(\s+report_id BIGINT NOT NULL,\s+timestamp TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s+handled_by_staff_id BIGINT,\s+is_cleared BOOL NOT NULL,\s+CONSTRAINT reports_audit_handled_by_staff_id_fk\s+FOREIGN KEY\(handled_by_staff_id\) REFERENCES staff\(id\),\s+CONSTRAINT reports_audit_report_id_fk\s+FOREIGN KEY\(report_id\) REFERENCES reports\(id\) ON DELETE CASCADE\s+\)`,
|
||||
`CREATE TABLE filters\(\s*id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\s*staff_id BIGINT,\s*staff_note VARCHAR\(255\) NOT NULL,\s*issued_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s*match_action VARCHAR\(45\) NOT NULL DEFAULT 'replace',\s*match_detail TEXT NOT NULL,\s*is_active BOOL NOT NULL,\s*CONSTRAINT filters_staff_id_fk\s*FOREIGN KEY\(staff_id\) REFERENCES staff\(id\)\s*ON DELETE SET NULL\s*\)`,
|
||||
`CREATE TABLE filter_boards\(\s*id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\s*filter_id BIGINT NOT NULL,\s*board_id BIGINT NOT NULL,\s*CONSTRAINT filter_boards_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE,\s*CONSTRAINT filter_boards_board_id_fk\s*FOREIGN KEY\(board_id\) REFERENCES boards\(id\)\s*ON DELETE CASCADE\s*\)`,
|
||||
`CREATE TABLE filter_conditions\(\s*id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\s*filter_id BIGINT NOT NULL,\s*is_regex SMALLINT NOT NULL,\s*search VARCHAR\(75\) NOT NULL,\s*field VARCHAR\(75\) NOT NULL,\s*CONSTRAINT filter_conditions_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE,\s*CONSTRAINT filter_conditions_search_check CHECK \(search <> ''\)\s*\)`,
|
||||
`CREATE TABLE filter_conditions\(\s*id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\s*filter_id BIGINT NOT NULL,\s*match_mode SMALLINT NOT NULL,\s*search VARCHAR\(75\) NOT NULL,\s*field VARCHAR\(75\) NOT NULL,\s*CONSTRAINT filter_conditions_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE,\s*CONSTRAINT filter_conditions_search_check CHECK \(search <> ''\)\s*\)`,
|
||||
`CREATE TABLE filter_hits\(\s*id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,\s*filter_id BIGINT NOT NULL,\s*post_data TEXT NOT NULL,\s*match_time TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,\s*CONSTRAINT filter_hits_filter_id_fk\s*FOREIGN KEY\(filter_id\) REFERENCES filters\(id\)\s*ON DELETE CASCADE\s*\)`,
|
||||
`INSERT INTO database_version\(component, version\)\s+VALUES\('gochan', 3\)`,
|
||||
}
|
||||
|
|
|
@ -53,72 +53,53 @@ type Board struct {
|
|||
EnableCatalog bool // sql: enable_catalog
|
||||
}
|
||||
|
||||
// Deprecated, use PostFilter instead, with a condition field = "checksum" if Fingerprinter is nil
|
||||
// or "ahash" otherwise.
|
||||
// FileBan contains the information associated with a specific file 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
|
||||
Fingerprinter *string // sql: fingerprinter
|
||||
BanIP bool // sql: ban_ip
|
||||
BanIPMessage *string // sql: ban_ip_message
|
||||
}
|
||||
// // Deprecated, use PostFilter instead, with a condition field = "checksum" if Fingerprinter is nil
|
||||
// // or "ahash" otherwise.
|
||||
// // FileBan contains the information associated with a specific file 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
|
||||
// Fingerprinter *string // sql: fingerprinter
|
||||
// BanIP bool // sql: ban_ip
|
||||
// BanIPMessage *string // sql: ban_ip_message
|
||||
// }
|
||||
|
||||
// ApplyIPBan bans the given IP if it posted a banned image
|
||||
// If BanIP is false, it returns with no error
|
||||
func (fb *FileBan) ApplyIPBan(postIP string) error {
|
||||
if !fb.BanIP {
|
||||
return nil
|
||||
}
|
||||
now := time.Now()
|
||||
ipBan := &IPBan{
|
||||
RangeStart: postIP,
|
||||
RangeEnd: postIP,
|
||||
IssuedAt: now,
|
||||
}
|
||||
ipBan.IsActive = true
|
||||
ipBan.CanAppeal = true
|
||||
ipBan.AppealAt = now
|
||||
ipBan.StaffID = fb.StaffID
|
||||
ipBan.Permanent = true
|
||||
if fb.BoardID != nil {
|
||||
ipBan.BoardID = new(int)
|
||||
*ipBan.BoardID = *fb.BoardID
|
||||
}
|
||||
if fb.BanIPMessage == nil {
|
||||
ipBan.Message = "posting disallowed image, resulting in ban"
|
||||
} else {
|
||||
ipBan.Message = *fb.BanIPMessage
|
||||
}
|
||||
if fb.StaffNote == "" {
|
||||
ipBan.StaffNote = "fingerprint"
|
||||
}
|
||||
// // ApplyIPBan bans the given IP if it posted a banned image
|
||||
// // If BanIP is false, it returns with no error
|
||||
// func (fb *FileBan) ApplyIPBan(postIP string) error {
|
||||
// if !fb.BanIP {
|
||||
// return nil
|
||||
// }
|
||||
// now := time.Now()
|
||||
// ipBan := &IPBan{
|
||||
// RangeStart: postIP,
|
||||
// RangeEnd: postIP,
|
||||
// IssuedAt: now,
|
||||
// }
|
||||
// ipBan.IsActive = true
|
||||
// ipBan.CanAppeal = true
|
||||
// ipBan.AppealAt = now
|
||||
// ipBan.StaffID = fb.StaffID
|
||||
// ipBan.Permanent = true
|
||||
// if fb.BoardID != nil {
|
||||
// ipBan.BoardID = new(int)
|
||||
// *ipBan.BoardID = *fb.BoardID
|
||||
// }
|
||||
// if fb.BanIPMessage == nil {
|
||||
// ipBan.Message = "posting disallowed image, resulting in ban"
|
||||
// } else {
|
||||
// ipBan.Message = *fb.BanIPMessage
|
||||
// }
|
||||
// if fb.StaffNote == "" {
|
||||
// ipBan.StaffNote = "fingerprint"
|
||||
// }
|
||||
|
||||
return NewIPBan(ipBan)
|
||||
}
|
||||
|
||||
// Deprecated, use PostFilter instead
|
||||
type filenameOrUsernameBanBase 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
|
||||
check string // replaced with username or filename
|
||||
IsRegex bool // sql: is_regex
|
||||
}
|
||||
|
||||
// Deprecated, use PostFilter instead, with a condition field = "filename"
|
||||
// FilenameBan represents a ban on a specific filename or filename regular expression.
|
||||
type FilenameBan struct {
|
||||
filenameOrUsernameBanBase
|
||||
Filename string // sql: `filename`
|
||||
IsRegex bool // sql: `is_regex`
|
||||
}
|
||||
// return NewIPBan(ipBan)
|
||||
// }
|
||||
|
||||
// Filter represents an entry in gochan's new filter system which merges username bans, file bans, and filename bans,
|
||||
// and will allow moderators to block posts based on the user's name, email, subject, message content, and other fields.
|
||||
|
@ -137,17 +118,17 @@ type Filter struct {
|
|||
// FilterCondition represents a condition to be checked against when a post is submitted
|
||||
// table: DBPREFIXfilter_conditions
|
||||
type FilterCondition struct {
|
||||
ID int // sql: id
|
||||
FilterID int // sql: filter_id
|
||||
IsRegex bool // sql: is_regex
|
||||
Search string // sql: search
|
||||
Field string // sql: field
|
||||
ID int // sql: id
|
||||
FilterID int // sql: filter_id
|
||||
MatchMode StringMatchMode // sql: match_mode
|
||||
Search string // sql: search
|
||||
Field string // sql: field
|
||||
}
|
||||
|
||||
func (fc *FilterCondition) insert(ctx context.Context, tx *sql.Tx) error {
|
||||
_, err := ExecContextSQL(ctx, tx,
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id, is_regex, search, field) VALUES(?,?,?,?)`,
|
||||
fc.FilterID, fc.IsRegex, fc.Search, fc.Field,
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id, match_mode, search, field) VALUES(?,?,?,?)`,
|
||||
fc.FilterID, fc.MatchMode, fc.Search, fc.Field,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
@ -340,12 +321,6 @@ type Thread struct {
|
|||
IsDeleted bool // sql: `is_deleted`
|
||||
}
|
||||
|
||||
// Deprecated, use PostFilter instead, and a FilterCondition with Field = "name"
|
||||
type UsernameBan struct {
|
||||
filenameOrUsernameBanBase
|
||||
Username string // sql: `username`
|
||||
}
|
||||
|
||||
// Wordfilter is used for filters that are expected to have a single FilterCondition and a "replace" MatchAction
|
||||
type Wordfilter struct {
|
||||
Filter
|
||||
|
|
|
@ -11,6 +11,14 @@ import (
|
|||
"github.com/gochan-org/gochan/pkg/config"
|
||||
)
|
||||
|
||||
const (
|
||||
TrueOrFalse BooleanFilter = iota
|
||||
OnlyTrue
|
||||
OnlyFalse
|
||||
)
|
||||
|
||||
const ()
|
||||
|
||||
var (
|
||||
dateTimeFormats = []string{
|
||||
"2006-01-02 15:04:05",
|
||||
|
@ -20,19 +28,19 @@ var (
|
|||
ErrNotConnected = errors.New("error connecting to database")
|
||||
)
|
||||
|
||||
// ActiveFilter is used for optionally limiting the results of tables with an is_active column to
|
||||
type ActiveFilter int
|
||||
// BooleanFilter is used for optionally limiting results to true, false, or both
|
||||
type BooleanFilter int
|
||||
|
||||
// whereClause returns part of the where clause of a SQL string. If and is true, it starts with AND, otherwise it starts with WHERE
|
||||
func (af ActiveFilter) whereClause(and bool) string {
|
||||
func (af BooleanFilter) whereClause(columnName string, and bool) string {
|
||||
out := " WHERE "
|
||||
if and {
|
||||
out = " AND "
|
||||
}
|
||||
if af == OnlyActiveFilters {
|
||||
return out + "is_active = TRUE"
|
||||
} else if af == OnlyInactiveFilters {
|
||||
return out + "is_active = FALSE"
|
||||
if af == OnlyTrue {
|
||||
return out + columnName + " = TRUE"
|
||||
} else if af == OnlyFalse {
|
||||
return out + columnName + " = FALSE"
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
|
|
@ -18,11 +18,15 @@ var (
|
|||
// boards should be a comma separated list of board strings, or "*" for all boards
|
||||
func CreateWordFilter(from string, to string, isRegex bool, boards []string, staffID int, staffNote string) (*Wordfilter, error) {
|
||||
var err error
|
||||
var matchMode StringMatchMode
|
||||
if isRegex {
|
||||
_, err = regexp.Compile(from)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
matchMode = RegexMatch
|
||||
} else {
|
||||
matchMode = SubstrMatch
|
||||
}
|
||||
const query = `INSERT INTO DBPREFIXfilters
|
||||
(staff_id, staff_note, issued_at, match_action, match_detail, is_active)
|
||||
|
@ -70,8 +74,8 @@ func CreateWordFilter(from string, to string, isRegex bool, boards []string, sta
|
|||
|
||||
// set filter condition
|
||||
if _, err = ExecContextSQL(ctx, tx,
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id, is_regex, search, field) VALUES(?,?,?,'body')`,
|
||||
filter.ID, isRegex, from,
|
||||
`INSERT INTO DBPREFIXfilter_conditions(filter_id, match_mode, search, field) VALUES(?,?,?,'body')`,
|
||||
filter.ID, matchMode, from,
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -81,8 +85,8 @@ func CreateWordFilter(from string, to string, isRegex bool, boards []string, sta
|
|||
|
||||
// GetWordfilters gets a list of wordfilters from the database and returns an array of them and any errors
|
||||
// encountered
|
||||
func GetWordfilters(active ActiveFilter) ([]Wordfilter, error) {
|
||||
filters, err := getFiltersByBoardDir("", true, active, onlyWordfilters)
|
||||
func GetWordfilters(active BooleanFilter) ([]Wordfilter, error) {
|
||||
filters, err := getFiltersByBoardDir("", true, active, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -114,7 +118,7 @@ func GetWordfilterByID(id int) (*Wordfilter, error) {
|
|||
|
||||
// GetBoardWordfilters gets an array of wordfilters associated with the given board directory
|
||||
func GetBoardWordfilters(board string) ([]Wordfilter, error) {
|
||||
filters, err := getFiltersByBoardDir(board, true, OnlyActiveFilters, onlyWordfilters)
|
||||
filters, err := getFiltersByBoardDir(board, true, OnlyTrue, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
@ -168,7 +172,7 @@ func (wf *Wordfilter) Apply(message string) (string, error) {
|
|||
}
|
||||
condition := conditions[0]
|
||||
|
||||
if condition.IsRegex {
|
||||
if condition.MatchMode == RegexMatch {
|
||||
re, err := regexp.Compile(condition.Search)
|
||||
if err != nil {
|
||||
return message, err
|
||||
|
@ -210,7 +214,7 @@ func (wf *Wordfilter) IsRegex() bool {
|
|||
if err != nil || len(conditions) != 1 {
|
||||
return false
|
||||
}
|
||||
return conditions[0].IsRegex
|
||||
return conditions[0].MatchMode == RegexMatch
|
||||
}
|
||||
|
||||
// Deprecated, use the first element in wf.BoardDirs() instead. This is kept here for templates.
|
||||
|
|
|
@ -277,8 +277,19 @@ func filtersCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.
|
|||
return nil, errors.New("failed to get field data: " + err.Error())
|
||||
}
|
||||
fc := gcsql.FilterCondition{
|
||||
Field: v[0],
|
||||
IsRegex: request.PostFormValue("isregex"+fieldIDstr) == "on",
|
||||
Field: v[0],
|
||||
}
|
||||
switch request.PostFormValue("matchmode" + fieldIDstr) {
|
||||
case "substr":
|
||||
fc.MatchMode = gcsql.SubstrMatch
|
||||
case "substrci":
|
||||
fc.MatchMode = gcsql.SubstrMatchCaseInsensitive
|
||||
case "regex":
|
||||
fc.MatchMode = gcsql.RegexMatch
|
||||
case "exact":
|
||||
fc.MatchMode = gcsql.ExactMatch
|
||||
default:
|
||||
return nil, gcsql.ErrInvalidStringMatchMode
|
||||
}
|
||||
var validField bool
|
||||
for _, field := range filterFields {
|
||||
|
@ -351,14 +362,14 @@ func filtersCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.
|
|||
}
|
||||
|
||||
showStr := request.FormValue("show")
|
||||
var show gcsql.ActiveFilter
|
||||
var show gcsql.BooleanFilter
|
||||
switch showStr {
|
||||
case "inactive":
|
||||
show = gcsql.OnlyInactiveFilters
|
||||
show = gcsql.OnlyFalse
|
||||
case "all":
|
||||
show = gcsql.AllFilters
|
||||
show = gcsql.TrueOrFalse
|
||||
default:
|
||||
show = gcsql.OnlyActiveFilters
|
||||
show = gcsql.OnlyTrue
|
||||
}
|
||||
var filters []gcsql.Filter
|
||||
boardSearch := request.FormValue("boardsearch")
|
||||
|
@ -812,6 +823,11 @@ func wordfiltersCallback(_ http.ResponseWriter, request *http.Request, staff *gc
|
|||
searchFor := request.PostFormValue("searchfor")
|
||||
replaceWith := request.PostFormValue("replace")
|
||||
isRegex := request.PostFormValue("isregex") == "on"
|
||||
matchMode := gcsql.SubstrMatch
|
||||
if isRegex {
|
||||
matchMode = gcsql.RegexMatch
|
||||
}
|
||||
|
||||
staffNote := request.PostFormValue("staffnote")
|
||||
|
||||
var boards []string
|
||||
|
@ -838,10 +854,10 @@ func wordfiltersCallback(_ http.ResponseWriter, request *http.Request, staff *gc
|
|||
return nil, errors.New("unable to update wordfilter details")
|
||||
}
|
||||
if err = filter.SetConditions(gcsql.FilterCondition{
|
||||
FilterID: filter.ID,
|
||||
IsRegex: isRegex,
|
||||
Search: searchFor,
|
||||
Field: "body",
|
||||
FilterID: filter.ID,
|
||||
MatchMode: matchMode,
|
||||
Search: searchFor,
|
||||
Field: "body",
|
||||
}); err != nil {
|
||||
errEv.Err(err).Caller().Msg("Unable to set filter condition")
|
||||
return nil, errors.New("unable to set filter conditions")
|
||||
|
@ -859,7 +875,7 @@ func wordfiltersCallback(_ http.ResponseWriter, request *http.Request, staff *gc
|
|||
infoEv.Str("do", "create")
|
||||
}
|
||||
|
||||
wordfilters, err := gcsql.GetWordfilters(gcsql.AllFilters)
|
||||
wordfilters, err := gcsql.GetWordfilters(gcsql.TrueOrFalse)
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Msg("Unable to get wordfilters")
|
||||
return nil, err
|
||||
|
|
|
@ -62,39 +62,6 @@ func checkIpBan(post *gcsql.Post, postBoard *gcsql.Board, writer http.ResponseWr
|
|||
return true
|
||||
}
|
||||
|
||||
/* func checkUsernameBan(post *gcsql.Post, postBoard *gcsql.Board, writer http.ResponseWriter, request *http.Request) bool {
|
||||
nameTrip := post.Name
|
||||
if post.Tripcode != "" {
|
||||
nameTrip += "!" + post.Tripcode
|
||||
}
|
||||
if nameTrip == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
nameBan, err := gcsql.CheckNameBan(nameTrip, postBoard.ID)
|
||||
if err != nil {
|
||||
gcutil.LogError(err).Caller().
|
||||
Str("IP", post.IP).
|
||||
Str("nameTrip", nameTrip).
|
||||
Str("boardDir", postBoard.Dir).
|
||||
Msg("Error getting name banned status")
|
||||
server.ServeErrorPage(writer, "Error getting name ban info")
|
||||
return true
|
||||
}
|
||||
if nameBan == nil {
|
||||
return false // name is not banned
|
||||
}
|
||||
server.ServeError(writer, "Name or tripcode not allowed", serverutil.IsRequestingJSON(request), map[string]interface{}{})
|
||||
gcutil.LogWarning().
|
||||
Str("IP", post.IP).
|
||||
Str("boardDir", postBoard.Dir).
|
||||
Str("nameTrip", nameTrip).
|
||||
Str("banUsername", nameBan.Username).
|
||||
Bool("banIsRegex", nameBan.IsRegex).
|
||||
Msg("Rejected post with banned name/tripcode")
|
||||
return true
|
||||
} */
|
||||
|
||||
func handleAppeal(writer http.ResponseWriter, request *http.Request, infoEv *zerolog.Event, errEv *zerolog.Event) {
|
||||
banIDstr := request.FormValue("banid")
|
||||
if banIDstr == "" {
|
||||
|
|
|
@ -52,7 +52,7 @@ func (*MessageFormatter) ApplyWordFilters(message string, boardDir string) (stri
|
|||
var filters []gcsql.Wordfilter
|
||||
var err error
|
||||
if boardDir == "" {
|
||||
filters, err = gcsql.GetWordfilters(gcsql.OnlyActiveFilters)
|
||||
filters, err = gcsql.GetWordfilters(gcsql.OnlyTrue)
|
||||
} else {
|
||||
filters, err = gcsql.GetBoardWordfilters(boardDir)
|
||||
}
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package uploads
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/md5"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
@ -16,7 +15,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/disintegration/imaging"
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/events"
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
|
@ -96,11 +94,11 @@ func AttachUploadFromRequest(request *http.Request, writer http.ResponseWriter,
|
|||
return nil, ErrUnsupportedFileExt
|
||||
}
|
||||
|
||||
if err = CheckFilenameBan(upload, post, postBoard); err != nil {
|
||||
// If checkFilenameBan returns true, an error occured or the file was
|
||||
// rejected for having a banned filename, and the incident was logged either way
|
||||
return nil, err
|
||||
}
|
||||
// if err = CheckFilenameBan(upload, post, postBoard); err != nil {
|
||||
// // If checkFilenameBan returns true, an error occured or the file was
|
||||
// // rejected for having a banned filename, and the incident was logged either way
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
data, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
|
@ -111,11 +109,11 @@ func AttachUploadFromRequest(request *http.Request, writer http.ResponseWriter,
|
|||
|
||||
// Calculate image checksum
|
||||
upload.Checksum = fmt.Sprintf("%x", md5.Sum(data)) // skipcq: GSC-G401
|
||||
if err = CheckFileChecksumBan(upload, post, postBoard); err != nil {
|
||||
// If CheckFileChecksumBan returns a non-nil error, an error occured or the file was
|
||||
// rejected for having a banned checksum, and the incident was logged either way
|
||||
return nil, err
|
||||
}
|
||||
// if err = CheckFileChecksumBan(upload, post, postBoard); err != nil {
|
||||
// // If CheckFileChecksumBan returns a non-nil error, an error occured or the file was
|
||||
// // rejected for having a banned checksum, and the incident was logged either way
|
||||
// return nil, err
|
||||
// }
|
||||
|
||||
ext := strings.ToLower(filepath.Ext(upload.OriginalFilename))
|
||||
upload.Filename = getNewFilename() + ext
|
||||
|
@ -132,25 +130,25 @@ func AttachUploadFromRequest(request *http.Request, writer http.ResponseWriter,
|
|||
errEv.Str("catalogThumbPath", catalogThumbPath)
|
||||
}
|
||||
|
||||
if IsImage(filePath) {
|
||||
img, err := imaging.Decode(bytes.NewReader(data))
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Msg("unable to decode file")
|
||||
return nil, errors.New("unable to decode image")
|
||||
}
|
||||
fileBan, err := checkImageFingerprintBan(img, postBoard.Dir)
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Msg("unable to fingerprint image")
|
||||
return nil, err
|
||||
}
|
||||
if fileBan != nil {
|
||||
// image is fingerprint-banned
|
||||
if err = fileBan.ApplyIPBan(post.IP); err != nil {
|
||||
errEv.Err(err).Caller().Msg("unable to apply IP ban")
|
||||
}
|
||||
return nil, ErrFileNotAllowed
|
||||
}
|
||||
}
|
||||
// if IsImage(filePath) {
|
||||
// img, err := imaging.Decode(bytes.NewReader(data))
|
||||
// if err != nil {
|
||||
// errEv.Err(err).Caller().Msg("unable to decode file")
|
||||
// return nil, errors.New("unable to decode image")
|
||||
// }
|
||||
// fileBan, err := checkImageFingerprintBan(img, postBoard.Dir)
|
||||
// if err != nil {
|
||||
// errEv.Err(err).Caller().Msg("unable to fingerprint image")
|
||||
// return nil, err
|
||||
// }
|
||||
// if fileBan != nil {
|
||||
// // image is fingerprint-banned
|
||||
// if err = fileBan.ApplyIPBan(post.IP); err != nil {
|
||||
// errEv.Err(err).Caller().Msg("unable to apply IP ban")
|
||||
// }
|
||||
// return nil, ErrFileNotAllowed
|
||||
// }
|
||||
// }
|
||||
|
||||
if err = os.WriteFile(filePath, data, config.NormalFileMode); err != nil {
|
||||
errEv.Err(err).Caller().Send()
|
||||
|
@ -188,20 +186,20 @@ func AttachUploadFromRequest(request *http.Request, writer http.ResponseWriter,
|
|||
return nil, errors.New("error processing upload: " + err.Error())
|
||||
}
|
||||
|
||||
if IsVideo(filePath) && config.GetSiteConfig().FingerprintVideoThumbnails {
|
||||
fileBan, err := checkFileFingerprintBan(thumbPath, postBoard.Dir)
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Msg("unable to check video thumbnail ban")
|
||||
return nil, err
|
||||
}
|
||||
if fileBan != nil {
|
||||
// video thumbnail is fingerprint-banned
|
||||
if err = fileBan.ApplyIPBan(post.IP); err != nil {
|
||||
errEv.Err(err).Caller().Msg("unable to apply IP ban")
|
||||
}
|
||||
return nil, ErrFileNotAllowed
|
||||
}
|
||||
}
|
||||
// if IsVideo(filePath) && config.GetSiteConfig().FingerprintVideoThumbnails {
|
||||
// fileBan, err := checkFileFingerprintBan(thumbPath, postBoard.Dir)
|
||||
// if err != nil {
|
||||
// errEv.Err(err).Caller().Msg("unable to check video thumbnail ban")
|
||||
// return nil, err
|
||||
// }
|
||||
// if fileBan != nil {
|
||||
// // video thumbnail is fingerprint-banned
|
||||
// if err = fileBan.ApplyIPBan(post.IP); err != nil {
|
||||
// errEv.Err(err).Caller().Msg("unable to apply IP ban")
|
||||
// }
|
||||
// return nil, ErrFileNotAllowed
|
||||
// }
|
||||
// }
|
||||
|
||||
accessEv.Send()
|
||||
return upload, nil
|
||||
|
|
|
@ -2,9 +2,6 @@ package uploads
|
|||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/gcsql"
|
||||
"github.com/gochan-org/gochan/pkg/gcutil"
|
||||
)
|
||||
|
||||
var (
|
||||
|
@ -13,41 +10,41 @@ var (
|
|||
ErrFileNotAllowed = errors.New("uploaded file not allowed")
|
||||
)
|
||||
|
||||
func CheckFilenameBan(upload *gcsql.Upload, post *gcsql.Post, postBoard *gcsql.Board) error {
|
||||
filenameBan, err := gcsql.CheckFilenameBan(upload.OriginalFilename, postBoard.ID)
|
||||
if err != nil {
|
||||
gcutil.LogError(err).
|
||||
Str("IP", post.IP).
|
||||
Str("filename", upload.OriginalFilename).
|
||||
Str("boardDir", postBoard.Dir).
|
||||
Msg("Error getting name banned status")
|
||||
return ErrCheckingFileBan
|
||||
}
|
||||
if filenameBan == nil {
|
||||
return nil
|
||||
}
|
||||
gcutil.LogWarning().
|
||||
Str("originalFilename", upload.OriginalFilename).
|
||||
Msg("File rejected for having a banned filename")
|
||||
return ErrFilenameNotAllowed
|
||||
}
|
||||
// func CheckFilenameBan(upload *gcsql.Upload, post *gcsql.Post, postBoard *gcsql.Board) error {
|
||||
// filenameBan, err := gcsql.CheckFilenameBan(upload.OriginalFilename, postBoard.ID)
|
||||
// if err != nil {
|
||||
// gcutil.LogError(err).
|
||||
// Str("IP", post.IP).
|
||||
// Str("filename", upload.OriginalFilename).
|
||||
// Str("boardDir", postBoard.Dir).
|
||||
// Msg("Error getting name banned status")
|
||||
// return ErrCheckingFileBan
|
||||
// }
|
||||
// if filenameBan == nil {
|
||||
// return nil
|
||||
// }
|
||||
// gcutil.LogWarning().
|
||||
// Str("originalFilename", upload.OriginalFilename).
|
||||
// Msg("File rejected for having a banned filename")
|
||||
// return ErrFilenameNotAllowed
|
||||
// }
|
||||
|
||||
func CheckFileChecksumBan(upload *gcsql.Upload, post *gcsql.Post, postBoard *gcsql.Board) error {
|
||||
fileBan, err := gcsql.CheckFileChecksumBan(upload.Checksum, postBoard.ID)
|
||||
if err != nil {
|
||||
gcutil.LogError(err).
|
||||
Str("IP", post.IP).
|
||||
Str("boardDir", postBoard.Dir).
|
||||
Str("checksum", upload.Checksum).
|
||||
Msg("Error getting file checksum ban status")
|
||||
return ErrCheckingFileBan
|
||||
}
|
||||
if fileBan == nil {
|
||||
return nil
|
||||
}
|
||||
gcutil.LogWarning().
|
||||
Str("originalFilename", upload.OriginalFilename).
|
||||
Str("checksum", upload.Checksum).
|
||||
Msg("File rejected for having a banned checksum")
|
||||
return ErrFileNotAllowed
|
||||
}
|
||||
// func CheckFileChecksumBan(upload *gcsql.Upload, post *gcsql.Post, postBoard *gcsql.Board) error {
|
||||
// fileBan, err := gcsql.CheckFileChecksumBan(upload.Checksum, postBoard.ID)
|
||||
// if err != nil {
|
||||
// gcutil.LogError(err).
|
||||
// Str("IP", post.IP).
|
||||
// Str("boardDir", postBoard.Dir).
|
||||
// Str("checksum", upload.Checksum).
|
||||
// Msg("Error getting file checksum ban status")
|
||||
// return ErrCheckingFileBan
|
||||
// }
|
||||
// if fileBan == nil {
|
||||
// return nil
|
||||
// }
|
||||
// gcutil.LogWarning().
|
||||
// Str("originalFilename", upload.OriginalFilename).
|
||||
// Str("checksum", upload.Checksum).
|
||||
// Msg("File rejected for having a banned checksum")
|
||||
// return ErrFileNotAllowed
|
||||
// }
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package uploads
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"image"
|
||||
|
@ -36,29 +35,29 @@ func getHashLength() int {
|
|||
return hashLength
|
||||
}
|
||||
|
||||
func checkImageFingerprintBan(img image.Image, _ string) (*gcsql.FileBan, error) {
|
||||
hashLength := getHashLength()
|
||||
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_ip_message
|
||||
FROM DBPREFIXfile_ban WHERE fingerprinter = 'ahash' AND checksum = ? LIMIT 1`
|
||||
// func checkImageFingerprintBan(img image.Image, _ string) (*gcsql.FileBan, error) {
|
||||
// hashLength := getHashLength()
|
||||
// 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_ip_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,
|
||||
})
|
||||
if errors.Is(err, sql.ErrNoRows) {
|
||||
return nil, nil
|
||||
} else if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &fileBan, err
|
||||
}
|
||||
// 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,
|
||||
// })
|
||||
// if errors.Is(err, sql.ErrNoRows) {
|
||||
// return nil, nil
|
||||
// } else if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return &fileBan, err
|
||||
// }
|
||||
|
||||
func GetPostImageFingerprint(postID int) (string, error) {
|
||||
filename, board, err := gcsql.GetUploadFilenameAndBoard(postID)
|
||||
|
@ -93,10 +92,10 @@ func GetFileFingerprint(filePath string) (string, error) {
|
|||
return fmt.Sprintf("%x", ba), nil
|
||||
}
|
||||
|
||||
func checkFileFingerprintBan(filePath string, board string) (*gcsql.FileBan, error) {
|
||||
img, err := imaging.Open(filePath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return checkImageFingerprintBan(img, board)
|
||||
}
|
||||
// func checkFileFingerprintBan(filePath string, board string) (*gcsql.FileBan, error) {
|
||||
// img, err := imaging.Open(filePath)
|
||||
// if err != nil {
|
||||
// return nil, err
|
||||
// }
|
||||
// return checkImageFingerprintBan(img, board)
|
||||
// }
|
||||
|
|
|
@ -272,13 +272,13 @@ CREATE TABLE DBPREFIXfilter_boards(
|
|||
CREATE TABLE DBPREFIXfilter_conditions(
|
||||
id {serial pk},
|
||||
filter_id {fk to serial} NOT NULL,
|
||||
is_regex SMALLINT NOT NULL,
|
||||
match_mode SMALLINT NOT NULL,
|
||||
search VARCHAR(75) NOT NULL,
|
||||
field VARCHAR(75) NOT NULL,
|
||||
CONSTRAINT filter_conditions_filter_id_fk
|
||||
FOREIGN KEY(filter_id) REFERENCES DBPREFIXfilters(id)
|
||||
ON DELETE CASCADE,
|
||||
CONSTRAINT filter_conditions_search_check CHECK (search <> '')
|
||||
CONSTRAINT filter_conditions_search_check CHECK (search <> '' OR match_mode = 3)
|
||||
);
|
||||
|
||||
CREATE TABLE DBPREFIXfilter_hits(
|
||||
|
|
|
@ -272,7 +272,7 @@ CREATE TABLE DBPREFIXfilter_boards(
|
|||
CREATE TABLE DBPREFIXfilter_conditions(
|
||||
id BIGINT NOT NULL AUTO_INCREMENT UNIQUE PRIMARY KEY,
|
||||
filter_id BIGINT NOT NULL,
|
||||
is_regex SMALLINT NOT NULL,
|
||||
match_mode SMALLINT NOT NULL,
|
||||
search VARCHAR(75) NOT NULL,
|
||||
field VARCHAR(75) NOT NULL,
|
||||
CONSTRAINT filter_conditions_filter_id_fk
|
||||
|
|
|
@ -272,7 +272,7 @@ CREATE TABLE DBPREFIXfilter_boards(
|
|||
CREATE TABLE DBPREFIXfilter_conditions(
|
||||
id BIGSERIAL PRIMARY KEY,
|
||||
filter_id BIGINT NOT NULL,
|
||||
is_regex SMALLINT NOT NULL,
|
||||
match_mode SMALLINT NOT NULL,
|
||||
search VARCHAR(75) NOT NULL,
|
||||
field VARCHAR(75) NOT NULL,
|
||||
CONSTRAINT filter_conditions_filter_id_fk
|
||||
|
|
|
@ -272,7 +272,7 @@ CREATE TABLE DBPREFIXfilter_boards(
|
|||
CREATE TABLE DBPREFIXfilter_conditions(
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
|
||||
filter_id BIGINT NOT NULL,
|
||||
is_regex SMALLINT NOT NULL,
|
||||
match_mode SMALLINT NOT NULL,
|
||||
search VARCHAR(75) NOT NULL,
|
||||
field VARCHAR(75) NOT NULL,
|
||||
CONSTRAINT filter_conditions_filter_id_fk
|
||||
|
|
|
@ -15,9 +15,14 @@
|
|||
<th>Search:</th>
|
||||
<td><input type="text" name="search{{.conditionNo}}" value="{{.condition.Search}}"></td>
|
||||
</tr>
|
||||
<tr class="regex-cndtn" {{if not .condition.CanDoRegex}}style="display: none"{{end}}>
|
||||
<th></th>
|
||||
<td><label>Is regex <input type="checkbox" name="isregex{{.conditionNo}}" {{if .condition.IsRegex}}checked{{end}}/></label></td>
|
||||
<tr class="strmatch-cndtn" {{if not .condition.ShowStringMatchOptions}}style="display: none"{{end}}>
|
||||
<th>Match mode</th>
|
||||
<td><select name="matchmode{{.conditionNo}}">
|
||||
<option value="substr" {{if eq .condition.MatchMode 0}}selected{{end}}>Substring (case sensitive)</option>
|
||||
<option value="substrci" {{if eq .condition.MatchMode 1}}selected{{end}}>Substring (not case sensitive)</option>
|
||||
<option value="regex" {{if eq .condition.MatchMode 2}}selected{{end}}>Regular expression</option>
|
||||
<option value="exact" {{if eq .condition.MatchMode 3}}selected{{end}}>Exact match</option>
|
||||
</select></td>
|
||||
</tr>
|
||||
<tr class="btns-cndtn">
|
||||
<td></td>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue