mirror of
https://github.com/Eggbertx/gochan.git
synced 2025-08-26 10:36:23 -07:00
Improve request validation and logging, implement cleanup for reports of deleted posts.
This commit is contained in:
parent
faf1457c36
commit
4aab676c67
7 changed files with 100 additions and 39 deletions
|
@ -7,7 +7,6 @@ import (
|
|||
"net"
|
||||
"net/http"
|
||||
"net/http/fcgi"
|
||||
"path"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
|
@ -89,7 +88,6 @@ func randomBanner(writer http.ResponseWriter, request *http.Request) {
|
|||
|
||||
// handles requests to /util
|
||||
func utilHandler(writer http.ResponseWriter, request *http.Request) {
|
||||
action := request.FormValue("action")
|
||||
board := request.FormValue("board")
|
||||
deleteBtn := request.PostFormValue("delete_btn")
|
||||
reportBtn := request.PostFormValue("report_btn")
|
||||
|
@ -97,28 +95,30 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) {
|
|||
doEdit := request.PostFormValue("doedit")
|
||||
moveBtn := request.PostFormValue("move_btn")
|
||||
doMove := request.PostFormValue("domove")
|
||||
systemCritical := config.GetSystemCriticalConfig()
|
||||
wantsJSON := serverutil.IsRequestingJSON(request)
|
||||
redirectTo := request.Referer()
|
||||
if wantsJSON {
|
||||
writer.Header().Set("Content-Type", "application/json")
|
||||
}
|
||||
if action == "" && deleteBtn != "Delete" && reportBtn != "Report" && editBtn != "Edit post" && doEdit != "post" && doEdit != "upload" && moveBtn != "Move thread" && doMove != "1" {
|
||||
gcutil.LogAccess(request).
|
||||
if redirectTo == "" || (deleteBtn != "Delete" && reportBtn != "Report" && editBtn != "Edit post" && doEdit != "post" && doEdit != "upload" && moveBtn != "Move thread" && doMove != "1") {
|
||||
accessEv := gcutil.LogAccess(request).
|
||||
Int("status", http.StatusBadRequest).
|
||||
Msg("received invalid /util request")
|
||||
if wantsJSON {
|
||||
writer.WriteHeader(http.StatusBadRequest)
|
||||
server.ServeJSON(writer, map[string]any{"error": "Invalid /util request"})
|
||||
Str("redirect", redirectTo)
|
||||
|
||||
if redirectTo == "" {
|
||||
accessEv.Msg("received /util request with no referer")
|
||||
} else {
|
||||
http.Redirect(writer, request, path.Join(systemCritical.WebRoot, "/"), http.StatusBadRequest)
|
||||
accessEv.Msg("received invalid /util request")
|
||||
}
|
||||
accessEv.Discard()
|
||||
server.ServeError(writer, server.NewServerError("bad /util request", http.StatusBadRequest), wantsJSON, nil)
|
||||
return
|
||||
}
|
||||
|
||||
var err error
|
||||
var id int
|
||||
var checkedPosts []int
|
||||
for key, val := range request.Form {
|
||||
for key, val := range request.PostForm {
|
||||
// get checked posts into an array
|
||||
if _, err = fmt.Sscanf(key, "check%d", &id); err != nil || val[0] != "on" {
|
||||
err = nil
|
||||
|
@ -146,11 +146,6 @@ func utilHandler(writer http.ResponseWriter, request *http.Request) {
|
|||
Str("board", board).
|
||||
Str("IP", gcutil.GetRealIP(request)).Send()
|
||||
|
||||
redirectTo := request.Referer()
|
||||
if redirectTo == "" {
|
||||
// request doesn't have a referer for some reason, redirect to board
|
||||
redirectTo = config.WebPath(board)
|
||||
}
|
||||
http.Redirect(writer, request, redirectTo, http.StatusFound)
|
||||
return
|
||||
}
|
||||
|
|
|
@ -9,11 +9,13 @@ import (
|
|||
"time"
|
||||
|
||||
"github.com/DATA-DOG/go-sqlmock"
|
||||
_ "github.com/go-sql-driver/mysql"
|
||||
"github.com/go-sql-driver/mysql"
|
||||
|
||||
_ "github.com/lib/pq"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
"github.com/gochan-org/gochan/pkg/gcutil"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -352,6 +354,7 @@ func setupDBConn(cfg *config.SQLConfig) (db *GCDB, err error) {
|
|||
case "mysql":
|
||||
db.connStr = fmt.Sprintf(mysqlConnStr, cfg.DBusername, cfg.DBpassword, cfg.DBhost, cfg.DBname)
|
||||
replacerArr = append(replacerArr, mysqlReplacerArr...)
|
||||
mysql.SetLogger(gcutil.Logger())
|
||||
case "postgres":
|
||||
db.connStr = fmt.Sprintf(postgresConnStr, cfg.DBusername, cfg.DBpassword, cfg.DBhost, cfg.DBname)
|
||||
replacerArr = append(replacerArr, postgresReplacerArr...)
|
||||
|
|
|
@ -33,13 +33,14 @@ var (
|
|||
TempPosts []Post
|
||||
)
|
||||
|
||||
func GetPostFromID(id int, onlyNotDeleted bool) (*Post, error) {
|
||||
func GetPostFromID(id int, onlyNotDeleted bool, requestOptions ...*RequestOptions) (*Post, error) {
|
||||
query := selectPostsBaseSQL + "WHERE id = ?"
|
||||
if onlyNotDeleted {
|
||||
query += " AND is_deleted = FALSE"
|
||||
}
|
||||
post := new(Post)
|
||||
err := QueryRow(nil, query, []any{id}, []any{
|
||||
opts := setupOptions(requestOptions...)
|
||||
err := QueryRow(opts, query, []any{id}, []any{
|
||||
&post.ID, &post.ThreadID, &post.IsTopPost, &post.IP, &post.CreatedOn, &post.Name,
|
||||
&post.Tripcode, &post.IsRoleSignature, &post.Email, &post.Subject, &post.Message,
|
||||
&post.MessageRaw, &post.Password, &post.DeletedAt, &post.IsDeleted,
|
||||
|
|
|
@ -123,3 +123,37 @@ func GetReports(includeCleared bool) ([]Report, error) {
|
|||
}
|
||||
return reports, rows.Close()
|
||||
}
|
||||
|
||||
// DeleteReportsOfDeletedPosts removes reports and report audits of posts that have been deleted
|
||||
func DeleteReportsOfDeletedPosts(requestOptions ...*RequestOptions) error {
|
||||
opts := setupOptionsWithTimeout(requestOptions...)
|
||||
|
||||
shouldCommit := opts.Tx == nil
|
||||
|
||||
var err error
|
||||
if shouldCommit {
|
||||
opts.Tx, err = BeginTx()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
opts.Tx.Rollback()
|
||||
opts.Tx = nil
|
||||
}()
|
||||
}
|
||||
|
||||
sql := `DELETE FROM DBPREFIXreports WHERE post_id IN (SELECT id FROM DBPREFIXposts WHERE is_deleted = TRUE)`
|
||||
if _, err = ExecTimeoutSQL(opts.Tx, sql); err != nil {
|
||||
return err
|
||||
}
|
||||
sql = `DELETE FROM DBPREFIXreports_audit WHERE report_id IN (SELECT id FROM DBPREFIXposts WHERE is_deleted = TRUE)`
|
||||
if _, err = ExecTimeoutSQL(opts.Tx, sql); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if shouldCommit {
|
||||
return opts.Tx.Commit()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package manage
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
|
@ -12,6 +13,7 @@ import (
|
|||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gochan-org/gochan/pkg/building"
|
||||
"github.com/gochan-org/gochan/pkg/config"
|
||||
|
@ -415,46 +417,66 @@ func reportsCallback(_ http.ResponseWriter, request *http.Request, staff *gcsql.
|
|||
Bool("blocked", block != "").
|
||||
Msg("Report cleared")
|
||||
}
|
||||
rows, err := gcsql.Query(nil, `SELECT id, handled_by_staff_id as staff_id,
|
||||
(SELECT username FROM DBPREFIXstaff WHERE id = DBPREFIXreports.handled_by_staff_id) as staff_user,
|
||||
post_id, IP_NTOA, reason, is_cleared from DBPREFIXreports WHERE is_cleared = FALSE`)
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), config.DefaultSQLTimeout*time.Second)
|
||||
defer cancel()
|
||||
|
||||
requestOptions := &gcsql.RequestOptions{
|
||||
Context: ctx,
|
||||
Cancel: cancel,
|
||||
}
|
||||
|
||||
if err = gcsql.DeleteReportsOfDeletedPosts(requestOptions); err != nil {
|
||||
errEv.Err(err).Caller().Send()
|
||||
return nil, server.NewServerError("failed to clean up reports of deleted posts", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
rows, err := gcsql.Query(requestOptions, `SELECT id, staff_id, staff_user, post_id, ip, reason, is_cleared FROM DBPREFIXv_post_reports`)
|
||||
if err != nil {
|
||||
errEv.Err(err).Caller().Send()
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
reports := make([]map[string]any, 0)
|
||||
for rows.Next() {
|
||||
var id int
|
||||
var staff_id any
|
||||
var staff_user []byte
|
||||
var post_id int
|
||||
var staffID any
|
||||
var staffUser []byte
|
||||
var postID int
|
||||
var ip string
|
||||
var reason string
|
||||
var is_cleared int
|
||||
err = rows.Scan(&id, &staff_id, &staff_user, &post_id, &ip, &reason, &is_cleared)
|
||||
var isCleared int
|
||||
err = rows.Scan(&id, &staffID, &staffUser, &postID, &ip, &reason, &isCleared)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
errEv.Err(err).Caller().Send()
|
||||
return nil, server.NewServerError("failed to scan report row", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
post, err := gcsql.GetPostFromID(post_id, true)
|
||||
post, err := gcsql.GetPostFromID(postID, true, requestOptions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
errEv.Err(err).Caller().Msg("failed to get post from ID")
|
||||
return nil, server.NewServerError("failed to get post from ID", http.StatusInternalServerError)
|
||||
}
|
||||
|
||||
staff_id_int, _ := staff_id.(int64)
|
||||
staffIDint, _ := staffID.(int64)
|
||||
reports = append(reports, map[string]any{
|
||||
"id": id,
|
||||
"staff_id": int(staff_id_int),
|
||||
"staff_user": string(staff_user),
|
||||
"staff_id": int(staffIDint),
|
||||
"staff_user": string(staffUser),
|
||||
"post_link": post.WebPath(),
|
||||
"ip": ip,
|
||||
"reason": reason,
|
||||
"is_cleared": is_cleared,
|
||||
"is_cleared": isCleared,
|
||||
})
|
||||
}
|
||||
if wantsJSON {
|
||||
return reports, err
|
||||
if err = rows.Close(); err != nil {
|
||||
errEv.Err(err).Caller().Send()
|
||||
return nil, err
|
||||
}
|
||||
if wantsJSON {
|
||||
return reports, nil
|
||||
}
|
||||
|
||||
reportsBuffer := bytes.NewBufferString("")
|
||||
err = serverutil.MinifyTemplate(gctemplates.ManageReports,
|
||||
map[string]any{
|
||||
|
@ -561,7 +583,7 @@ func threadAttrsCallback(_ http.ResponseWriter, request *http.Request, _ *gcsql.
|
|||
if err = building.BuildThreadPages(post); err != nil {
|
||||
return "", err
|
||||
}
|
||||
fmt.Println("Done rebuilding", board.Dir)
|
||||
gcutil.LogInfo().Msg("Done rebuilding")
|
||||
}
|
||||
data["thread"] = thread
|
||||
}
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
-- SQL views for simplifying queries in gochan
|
||||
|
||||
-- First drop views if they exist in reverse order to avoid dependency issues
|
||||
DROP VIEW IF EXISTS DBPREFIXv_post_reports;
|
||||
DROP VIEW IF EXISTS DBPREFIXv_post_with_board;
|
||||
DROP VIEW IF EXISTS DBPREFIXv_top_post_board_dir;
|
||||
DROP VIEW IF EXISTS DBPREFIXv_upload_info;
|
||||
|
@ -99,3 +100,8 @@ banned_message, ip, flag, country, dir, board_id
|
|||
FROM DBPREFIXposts p
|
||||
LEFT JOIN DBPREFIXthreads t ON t.id = p.thread_id
|
||||
LEFT JOIN DBPREFIXboards b ON b.id = t.board_id;
|
||||
|
||||
CREATE VIEW DBPREFIXv_post_reports AS
|
||||
SELECT r.id, handled_by_staff_id AS staff_id, username AS staff_user, post_id, IP_NTOA as ip, reason, is_cleared
|
||||
FROM DBPREFIXreports r LEFT JOIN DBPREFIXstaff s ON handled_by_staff_id = s.id
|
||||
WHERE is_cleared = FALSE;
|
||||
|
|
|
@ -34,8 +34,8 @@
|
|||
<img src="{{getThumbnailWebPath $.post.ID}}" alt="{{webPath $.board.Dir "src" .Filename}}" width="{{.ThumbnailWidth}}" height="{{.ThumbnailHeight}}" class="upload" />
|
||||
</td></tr>
|
||||
{{- end -}}
|
||||
<tr><th>Spoiler</th><td><input type="checkbox" name="spoiler" id="spoiler" {{with .upload}}{{if .IsSpoilered}}checked{{end}}{{end}}></td></tr>
|
||||
<tr><th>Replace</th><td>
|
||||
<tr><th class="postblock">Spoiler</th><td><input type="checkbox" name="spoiler" id="spoiler" {{with .upload}}{{if .IsSpoilered}}checked{{end}}{{end}}></td></tr>
|
||||
<tr><th class="postblock">Replace</th><td>
|
||||
<input name="imagefile" type="file" accept="image/jpeg,image/png,image/gif,video/webm,video/mp4" onchange="var sub = document.getElementById('update-file'); if(this.value != '') { sub.disabled = false; sub.value = 'Update file'; } else { sub.disabled = true; sub.value = 'No file selected'}"/>
|
||||
</td></tr>
|
||||
</table>
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue