1
0
Fork 0
mirror of https://github.com/Eggbertx/gochan.git synced 2025-08-02 15:06:23 -07:00

Add is_secure_tripcode column to posts and update gochan-migration to add it

This commit is contained in:
Eggbertx 2025-04-06 15:08:46 -07:00
parent ffac903eb5
commit 4c0ce122ad
16 changed files with 182 additions and 90 deletions

View file

@ -40,7 +40,7 @@ release_files = (
) )
GOCHAN_VERSION = "4.1.0" GOCHAN_VERSION = "4.1.0"
DATABASE_VERSION = "4" # stored in DBNAME.DBPREFIXdatabase_version DATABASE_VERSION = "5" # stored in DBNAME.DBPREFIXdatabase_version
PATH_NOTHING = -1 PATH_NOTHING = -1
PATH_UNKNOWN = 0 PATH_UNKNOWN = 0

View file

@ -32,7 +32,7 @@ func updateMysqlDB(ctx context.Context, dbu *GCDatabaseUpdater, sqlConfig *confi
} }
var rows *sql.Rows var rows *sql.Rows
query = `SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ?` query = `SELECT TABLE_NAME FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_TYPE = 'BASE TABLE'`
rows, err = db.QueryContextSQL(ctx, nil, query, dbName) rows, err = db.QueryContextSQL(ctx, nil, query, dbName)
if err != nil { if err != nil {
return err return err
@ -181,5 +181,17 @@ func updateMysqlDB(ctx context.Context, dbu *GCDatabaseUpdater, sqlConfig *confi
} }
} }
// add is_secure_tripcode column to DBPREFIXposts
dataType, err = common.ColumnType(ctx, db, nil, "is_secure_tripcode", "DBPREFIXposts", sqlConfig)
if err != nil {
return err
}
if dataType == "" {
query = `ALTER TABLE DBPREFIXposts ADD COLUMN is_secure_tripcode BOOL NOT NULL DEFAULT FALSE`
if _, err = db.ExecContextSQL(ctx, nil, query); err != nil {
return err
}
}
return nil return nil
} }

View file

@ -93,5 +93,17 @@ func updatePostgresDB(ctx context.Context, dbu *GCDatabaseUpdater, sqlConfig *co
} }
} }
// add is_secure_tripcode column to DBPREFIXposts
dataType, err = common.ColumnType(ctx, db, nil, "is_secure_tripcode", "DBPREFIXposts", sqlConfig)
if err != nil {
return err
}
if dataType == "" {
query = `ALTER TABLE DBPREFIXposts ADD COLUMN is_secure_tripcode BOOL NOT NULL DEFAULT FALSE`
if _, err = db.ExecContextSQL(ctx, nil, query); err != nil {
return err
}
}
return nil return nil
} }

View file

@ -111,5 +111,17 @@ func updateSqliteDB(ctx context.Context, dbu *GCDatabaseUpdater, sqlConfig *conf
} }
} }
// add is_secure_tripcode column to DBPREFIXposts
dataType, err = common.ColumnType(ctx, db, nil, "is_secure_tripcode", "DBPREFIXposts", sqlConfig)
if err != nil {
return err
}
if dataType == "" {
query = `ALTER TABLE DBPREFIXposts ADD COLUMN is_secure_tripcode BOOL NOT NULL DEFAULT FALSE`
if _, err = db.ExecContextSQL(ctx, nil, query); err != nil {
return err
}
}
return nil return nil
} }

View file

@ -97,8 +97,8 @@ func (m *Pre2021Migrator) MigratePosts() error {
for rows.Next() { for rows.Next() {
var thread migrationPost var thread migrationPost
if err = rows.Scan( if err = rows.Scan(
&thread.oldID, &thread.oldBoardID, &thread.oldParentID, &thread.Name, &thread.Tripcode, &thread.Email, &thread.oldID, &thread.oldBoardID, &thread.oldParentID, &thread.Name, &thread.Tripcode, &thread.IsSecureTripcode,
&thread.Subject, &thread.Message, &thread.MessageRaw, &thread.Password, &thread.filename, &thread.Email, &thread.Subject, &thread.Message, &thread.MessageRaw, &thread.Password, &thread.filename,
&thread.filenameOriginal, &thread.fileChecksum, &thread.filesize, &thread.imageW, &thread.imageH, &thread.filenameOriginal, &thread.fileChecksum, &thread.filesize, &thread.imageW, &thread.imageH,
&thread.thumbW, &thread.thumbH, &thread.IP, &thread.CreatedOn, &thread.autosage, &thread.thumbW, &thread.thumbH, &thread.IP, &thread.CreatedOn, &thread.autosage,
&thread.bumped, &thread.stickied, &thread.locked, &thread.bumped, &thread.stickied, &thread.locked,
@ -137,8 +137,8 @@ func (m *Pre2021Migrator) MigratePosts() error {
for replyRows.Next() { for replyRows.Next() {
var reply migrationPost var reply migrationPost
if err = replyRows.Scan( if err = replyRows.Scan(
&reply.oldID, &reply.oldBoardID, &reply.oldParentID, &reply.Name, &reply.Tripcode, &reply.Email, &reply.oldID, &reply.oldBoardID, &reply.oldParentID, &reply.Name, &reply.Tripcode, &reply.IsSecureTripcode,
&reply.Subject, &reply.Message, &reply.MessageRaw, &reply.Password, &reply.filename, &reply.Email, &reply.Subject, &reply.Message, &reply.MessageRaw, &reply.Password, &reply.filename,
&reply.filenameOriginal, &reply.fileChecksum, &reply.filesize, &reply.imageW, &reply.imageH, &reply.filenameOriginal, &reply.fileChecksum, &reply.filesize, &reply.imageW, &reply.imageH,
&reply.thumbW, &reply.thumbH, &reply.IP, &reply.CreatedOn, &reply.autosage, &reply.thumbW, &reply.thumbH, &reply.IP, &reply.CreatedOn, &reply.autosage,
&reply.bumped, &reply.stickied, &reply.locked, &reply.bumped, &reply.stickied, &reply.locked,

View file

@ -8,7 +8,7 @@ default_style, locked, created_on, anonymous, forced_anon, autosage_after, no_im
redirect_to_thread, require_file, enable_catalog redirect_to_thread, require_file, enable_catalog
FROM DBPREFIXboards` FROM DBPREFIXboards`
postsQuery = `SELECT id, boardid, parentid, name, tripcode, email, subject, message, message_raw, password, filename, postsQuery = `SELECT id, boardid, parentid, name, tripcode, is_secure_tripcode, email, subject, message, message_raw, password, filename,
filename_original, file_checksum, filesize, image_w, image_h, thumb_w, thumb_h, ip, timestamp, autosage, filename_original, file_checksum, filesize, image_w, image_h, thumb_w, thumb_h, ip, timestamp, autosage,
bumped, stickied, locked FROM DBPREFIXposts WHERE deleted_timestamp IS NULL` bumped, stickied, locked FROM DBPREFIXposts WHERE deleted_timestamp IS NULL`

View file

@ -201,24 +201,25 @@ type IPBanAppealAudit struct {
// table: DBPREFIXposts // table: DBPREFIXposts
type Post struct { type Post struct {
ID int `json:"no"` // sql: id ID int `json:"no"` // sql: id
ThreadID int `json:"-"` // sql: thread_id ThreadID int `json:"-"` // sql: thread_id
IsTopPost bool `json:"-"` // sql: is_top_post IsTopPost bool `json:"-"` // sql: is_top_post
IP string `json:"-"` // sql: ip IP string `json:"-"` // sql: ip
CreatedOn time.Time `json:"time"` // sql: created_on CreatedOn time.Time `json:"time"` // sql: created_on
Name string `json:"name"` // sql: name Name string `json:"name"` // sql: name
Tripcode string `json:"trip"` // sql: tripcode Tripcode string `json:"trip"` // sql: tripcode
IsRoleSignature bool `json:"-"` // sql: is_role_signature IsSecureTripcode bool `json:"-"` // sql: is_secure_tripcode
Email string `json:"email"` // sql: email IsRoleSignature bool `json:"-"` // sql: is_role_signature
Subject string `json:"sub"` // sql: subject Email string `json:"email"` // sql: email
Message template.HTML `json:"-"` // sql: message Subject string `json:"sub"` // sql: subject
MessageRaw string `json:"com"` // sql: message_raw Message template.HTML `json:"-"` // sql: message
Password string `json:"-"` // sql: `password` MessageRaw string `json:"com"` // sql: message_raw
DeletedAt time.Time `json:"-"` // sql: deleted_at Password string `json:"-"` // sql: `password`
IsDeleted bool `json:"-"` // sql: is_deleted DeletedAt time.Time `json:"-"` // sql: deleted_at
BannedMessage string `json:"-"` // sql: banned_message IsDeleted bool `json:"-"` // sql: is_deleted
Flag string `json:"-"` // sql: flag BannedMessage string `json:"-"` // sql: banned_message
Country string `json:"-"` // sql: country Flag string `json:"-"` // sql: flag
Country string `json:"-"` // sql: country
// used for convenience to avoid needing to do multiple queries // used for convenience to avoid needing to do multiple queries
opID int opID int

View file

@ -77,44 +77,6 @@ func TestMarshalJSON(t *testing.T) {
} }
} }
func TestNameParsing(t *testing.T) {
testCases := []struct {
nameAndTrip string
expectedName string
expectedTripcode string
}{
{
nameAndTrip: "Name#Trip",
expectedName: "Name",
expectedTripcode: "piec1MorXg",
},
{
nameAndTrip: "#Trip",
expectedName: "",
expectedTripcode: "piec1MorXg",
},
{
nameAndTrip: "Name",
expectedName: "Name",
},
{
nameAndTrip: "Name#",
expectedName: "Name",
},
{
nameAndTrip: "#",
expectedName: "",
},
}
for _, tC := range testCases {
t.Run(tC.nameAndTrip, func(t *testing.T) {
name, trip := ParseName(tC.nameAndTrip)
assert.Equal(t, tC.expectedName, name)
assert.Equal(t, tC.expectedTripcode, trip)
})
}
}
func TestGetRealIP(t *testing.T) { func TestGetRealIP(t *testing.T) {
const remoteAddr = "192.168.56.1" const remoteAddr = "192.168.56.1"
const testIP = "192.168.56.2" const testIP = "192.168.56.2"

View file

@ -17,7 +17,6 @@ import (
"strings" "strings"
"time" "time"
"github.com/aquilax/tripcode"
"golang.org/x/crypto/bcrypt" "golang.org/x/crypto/bcrypt"
x_html "golang.org/x/net/html" x_html "golang.org/x/net/html"
) )
@ -147,22 +146,6 @@ func MarshalJSON(data any, indent bool) (string, error) {
return string(jsonBytes), err return string(jsonBytes), err
} }
// ParseName takes a name string from a request object and returns the name and tripcode parts
func ParseName(name string) (string, string) {
var namePart string
var tripcodePart string
if !strings.Contains(name, "#") {
namePart = name
} else if strings.Index(name, "#") == 0 {
tripcodePart = tripcode.Tripcode(name[1:])
} else if strings.Index(name, "#") > 0 {
postNameArr := strings.SplitN(name, "#", 2)
namePart = postNameArr[0]
tripcodePart = tripcode.Tripcode(postNameArr[1])
}
return namePart, tripcodePart
}
// RandomString returns a randomly generated string of the given length // RandomString returns a randomly generated string of the given length
func RandomString(length int) string { func RandomString(length int) string {
var str string var str string

View file

@ -1,6 +1,7 @@
package posting package posting
import ( import (
"encoding/base64"
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
@ -12,6 +13,7 @@ import (
"strings" "strings"
"time" "time"
"github.com/aquilax/tripcode"
"github.com/gochan-org/gochan/pkg/building" "github.com/gochan-org/gochan/pkg/building"
"github.com/gochan-org/gochan/pkg/config" "github.com/gochan-org/gochan/pkg/config"
"github.com/gochan-org/gochan/pkg/events" "github.com/gochan-org/gochan/pkg/events"
@ -170,7 +172,43 @@ func getEmailAndCommand(request *http.Request) (string, string) {
return formEmail[:sepIndex], formEmail[sepIndex+1:] return formEmail[:sepIndex], formEmail[sepIndex+1:]
} }
func getPostFromRequest(request *http.Request, infoEv, errEv *zerolog.Event) (post *gcsql.Post, err error) { // ParseName takes a name and board string and returns the name and tripcode parts,
// using the board's reserved tripcodes if applicable.
func ParseName(name string, boardConfig *config.BoardConfig) (string, string) {
var namePart string
var tripcodePart string
var secure bool
if strings.Contains(name, "##") {
parts := strings.SplitN(name, "##", 2)
namePart = parts[0]
tripcodePart = parts[1]
secure = true
} else if strings.Contains(name, "#") {
parts := strings.SplitN(name, "#", 2)
namePart = parts[0]
tripcodePart = parts[1]
} else {
namePart = name
}
if tripcodePart != "" {
reservedTrip, reserved := boardConfig.ReservedTrips[tripcodePart]
if secure && reserved {
tripcodePart = reservedTrip
} else if secure {
hash := gcutil.Md5Sum(tripcodePart + config.GetSystemCriticalConfig().RandomSeed)
for range 64 {
hash = gcutil.Md5Sum(hash + name)
}
tripcodePart = base64.StdEncoding.EncodeToString([]byte(hash))[:10]
} else {
tripcodePart = tripcode.Tripcode(tripcodePart)
}
}
return namePart, tripcodePart
}
func getPostFromRequest(request *http.Request, boardConfig *config.BoardConfig, infoEv, errEv *zerolog.Event) (post *gcsql.Post, err error) {
post = &gcsql.Post{ post = &gcsql.Post{
IP: gcutil.GetRealIP(request), IP: gcutil.GetRealIP(request),
Subject: request.PostFormValue("postsubject"), Subject: request.PostFormValue("postsubject"),
@ -197,7 +235,8 @@ func getPostFromRequest(request *http.Request, infoEv, errEv *zerolog.Event) (po
} }
} }
} }
post.Name, post.Tripcode = gcutil.ParseName(request.PostFormValue("postname")) post.Name, post.Tripcode = ParseName(request.PostFormValue("postname"), boardConfig)
post.IsSecureTripcode = strings.Contains(post.Tripcode, "##")
post.Email, _ = getEmailAndCommand(request) post.Email, _ = getEmailAndCommand(request)
password := request.PostFormValue("postpassword") password := request.PostFormValue("postpassword")
@ -300,12 +339,6 @@ func MakePost(writer http.ResponseWriter, request *http.Request) {
return return
} }
post, err := getPostFromRequest(request, infoEv, errEv)
if err != nil {
server.ServeError(writer, err.Error(), wantsJSON, nil)
return
}
boardidStr := request.PostFormValue("boardid") boardidStr := request.PostFormValue("boardid")
boardID, err := strconv.Atoi(boardidStr) boardID, err := strconv.Atoi(boardidStr)
if err != nil { if err != nil {
@ -335,6 +368,12 @@ func MakePost(writer http.ResponseWriter, request *http.Request) {
return return
} }
post, err := getPostFromRequest(request, boardConfig, infoEv, errEv)
if err != nil {
server.ServeError(writer, err.Error(), wantsJSON, nil)
return
}
if boardConfig.MaxMessageLength > 0 && len(post.MessageRaw) > boardConfig.MaxMessageLength { if boardConfig.MaxMessageLength > 0 && len(post.MessageRaw) > boardConfig.MaxMessageLength {
warnEv. warnEv.
Int("messageLength", len(post.MessageRaw)). Int("messageLength", len(post.MessageRaw)).

67
pkg/posting/post_test.go Normal file
View file

@ -0,0 +1,67 @@
package posting
import (
"testing"
"github.com/gochan-org/gochan/pkg/config"
"github.com/stretchr/testify/assert"
)
type parseNameTestCase struct {
nameAndTrip string
expectedName string
expectedTripcode string
}
func TestParseName(t *testing.T) {
config.SetVersion("4.1")
boardConfig := config.GetBoardConfig("test")
boardConfig.ReservedTrips = map[string]string{
"reserved": "TripOut",
}
config.SetBoardConfig("test", boardConfig)
config.SetRandomSeed("lol")
testCases := []parseNameTestCase{
{
nameAndTrip: "Name#Trip",
expectedName: "Name",
expectedTripcode: "piec1MorXg",
},
{
nameAndTrip: "#Trip",
expectedName: "",
expectedTripcode: "piec1MorXg",
},
{
nameAndTrip: "Name",
expectedName: "Name",
},
{
nameAndTrip: "Name#",
expectedName: "Name",
},
{
nameAndTrip: "#",
expectedName: "",
},
{
nameAndTrip: "Name##reserved",
expectedName: "Name",
expectedTripcode: "TripOut",
},
{
nameAndTrip: "Name##notReserved",
expectedName: "Name",
expectedTripcode: "MDlhNmNmYj",
},
}
for _, tC := range testCases {
t.Run(tC.nameAndTrip, func(t *testing.T) {
name, trip := ParseName(tC.nameAndTrip, boardConfig)
assert.Equal(t, tC.expectedName, name)
assert.Equal(t, tC.expectedTripcode, trip)
})
}
}

View file

@ -73,6 +73,7 @@ CREATE TABLE DBPREFIXposts(
created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
name VARCHAR(50) NOT NULL DEFAULT '', name VARCHAR(50) NOT NULL DEFAULT '',
tripcode VARCHAR(10) NOT NULL DEFAULT '', tripcode VARCHAR(10) NOT NULL DEFAULT '',
is_secure_tripcode BOOL NOT NULL DEFAULT FALSE,
is_role_signature BOOL NOT NULL DEFAULT FALSE, is_role_signature BOOL NOT NULL DEFAULT FALSE,
email VARCHAR(50) NOT NULL DEFAULT '', email VARCHAR(50) NOT NULL DEFAULT '',
subject VARCHAR(100) NOT NULL DEFAULT '', subject VARCHAR(100) NOT NULL DEFAULT '',

View file

@ -73,6 +73,7 @@ CREATE TABLE DBPREFIXposts(
created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
name VARCHAR(50) NOT NULL DEFAULT '', name VARCHAR(50) NOT NULL DEFAULT '',
tripcode VARCHAR(10) NOT NULL DEFAULT '', tripcode VARCHAR(10) NOT NULL DEFAULT '',
is_secure_tripcode BOOL NOT NULL DEFAULT FALSE,
is_role_signature BOOL NOT NULL DEFAULT FALSE, is_role_signature BOOL NOT NULL DEFAULT FALSE,
email VARCHAR(50) NOT NULL DEFAULT '', email VARCHAR(50) NOT NULL DEFAULT '',
subject VARCHAR(100) NOT NULL DEFAULT '', subject VARCHAR(100) NOT NULL DEFAULT '',

View file

@ -73,6 +73,7 @@ CREATE TABLE DBPREFIXposts(
created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
name VARCHAR(50) NOT NULL DEFAULT '', name VARCHAR(50) NOT NULL DEFAULT '',
tripcode VARCHAR(10) NOT NULL DEFAULT '', tripcode VARCHAR(10) NOT NULL DEFAULT '',
is_secure_tripcode BOOL NOT NULL DEFAULT FALSE,
is_role_signature BOOL NOT NULL DEFAULT FALSE, is_role_signature BOOL NOT NULL DEFAULT FALSE,
email VARCHAR(50) NOT NULL DEFAULT '', email VARCHAR(50) NOT NULL DEFAULT '',
subject VARCHAR(100) NOT NULL DEFAULT '', subject VARCHAR(100) NOT NULL DEFAULT '',

View file

@ -73,6 +73,7 @@ CREATE TABLE DBPREFIXposts(
created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, created_on TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
name VARCHAR(50) NOT NULL DEFAULT '', name VARCHAR(50) NOT NULL DEFAULT '',
tripcode VARCHAR(10) NOT NULL DEFAULT '', tripcode VARCHAR(10) NOT NULL DEFAULT '',
is_secure_tripcode BOOL NOT NULL DEFAULT FALSE,
is_role_signature BOOL NOT NULL DEFAULT FALSE, is_role_signature BOOL NOT NULL DEFAULT FALSE,
email VARCHAR(50) NOT NULL DEFAULT '', email VARCHAR(50) NOT NULL DEFAULT '',
subject VARCHAR(100) NOT NULL DEFAULT '', subject VARCHAR(100) NOT NULL DEFAULT '',

View file

@ -24,7 +24,7 @@ CREATE VIEW DBPREFIXv_top_post_thread_ids AS
SELECT id, thread_id FROM DBPREFIXposts WHERE is_top_post; SELECT id, thread_id FROM DBPREFIXposts WHERE is_top_post;
CREATE VIEW DBPREFIXv_building_posts AS CREATE VIEW DBPREFIXv_building_posts AS
SELECT p.id AS id, p.thread_id AS thread_id, ip, name, tripcode, SELECT p.id AS id, p.thread_id AS thread_id, ip, name, tripcode, is_secure_tripcode,
email, subject, created_on, created_on as last_modified, op.id AS parent_id, t.last_bump as last_bump, email, subject, created_on, created_on as last_modified, op.id AS parent_id, t.last_bump as last_bump,
message, message_raw, t.board_id, message, message_raw, t.board_id,
(SELECT dir FROM DBPREFIXboards WHERE id = t.board_id LIMIT 1) AS dir, (SELECT dir FROM DBPREFIXboards WHERE id = t.board_id LIMIT 1) AS dir,
@ -95,7 +95,7 @@ LEFT JOIN DBPREFIXv_thread_board_ids t ON t.id = DBPREFIXposts.thread_id
INNER JOIN DBPREFIXv_top_post_thread_ids op on op.thread_id = DBPREFIXposts.thread_id; INNER JOIN DBPREFIXv_top_post_thread_ids op on op.thread_id = DBPREFIXposts.thread_id;
CREATE VIEW DBPREFIXv_post_with_board AS CREATE VIEW DBPREFIXv_post_with_board AS
SELECT p.id, thread_id, is_top_post, created_on, name, tripcode, is_role_signature, email, SELECT p.id, thread_id, is_top_post, created_on, name, tripcode, is_secure_tripcode, is_role_signature, email,
subject, message, message_raw, password, p.deleted_at AS deleted_at, p.is_deleted AS is_deleted, subject, message, message_raw, password, p.deleted_at AS deleted_at, p.is_deleted AS is_deleted,
banned_message, ip, flag, country, dir, board_id banned_message, ip, flag, country, dir, board_id
FROM DBPREFIXposts p FROM DBPREFIXposts p