3
0
mirror of https://github.com/ergochat/ergo.git synced 2026-01-02 08:17:57 +01:00

fix some redact bugs (#2320)

* Consistently return UNKNOWN_MSGID for unknown or invalid msgids
* If both client's DMs are stored in persistent history, a single
  server.DeleteMessage will delete the single canonical copy of the message.
  So the second call will fail, which is fine.
This commit is contained in:
Shivaram Lingamneni 2025-12-31 02:15:11 -05:00 committed by GitHub
parent 6ba60c89c4
commit 6386b9ef70
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 24 additions and 10 deletions

View File

@ -2842,10 +2842,16 @@ func redactHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respo
} }
err := server.DeleteMessage(target, targetmsgid, accountName) err := server.DeleteMessage(target, targetmsgid, accountName)
if err == errNoop { switch err {
case history.ErrNotFound:
rb.Add(nil, server.name, "FAIL", "REDACT", "UNKNOWN_MSGID", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), client.t("This message does not exist or is too old")) rb.Add(nil, server.name, "FAIL", "REDACT", "UNKNOWN_MSGID", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), client.t("This message does not exist or is too old"))
return false return false
} else if err != nil { case history.ErrDisallowed:
rb.Add(nil, server.name, "FAIL", "REDACT", "REDACT_FORBIDDEN", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), client.t("You are not authorized to delete this message"))
return false
case nil:
// OK
default:
isOper := client.HasRoleCapabs("history") isOper := client.HasRoleCapabs("history")
if isOper { if isOper {
rb.Add(nil, server.name, "FAIL", "REDACT", "REDACT_FORBIDDEN", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), fmt.Sprintf(client.t("Error deleting message: %v"), err)) rb.Add(nil, server.name, "FAIL", "REDACT", "REDACT_FORBIDDEN", utils.SafeErrorParam(target), utils.SafeErrorParam(targetmsgid), fmt.Sprintf(client.t("Error deleting message: %v"), err))
@ -2860,7 +2866,8 @@ func redactHandler(server *Server, client *Client, msg ircmsg.Message, rb *Respo
// now we have to remove it from the buffer of the client who sent the REDACT command // now we have to remove it from the buffer of the client who sent the REDACT command
err := server.DeleteMessage(client.Nick(), targetmsgid, accountName) err := server.DeleteMessage(client.Nick(), targetmsgid, accountName)
if err != nil { // ErrNotFound is expected if both clients are using persistent history
if err != nil && err != history.ErrNotFound {
client.server.logger.Error("internal", fmt.Sprintf("Private message %s is not deletable by %s from their own buffer's even though we just deleted it from %s's. This is a bug, please report it in details.", targetmsgid, client.Nick(), target), client.Nick()) client.server.logger.Error("internal", fmt.Sprintf("Private message %s is not deletable by %s from their own buffer's even though we just deleted it from %s's. This is a bug, please report it in details.", targetmsgid, client.Nick(), target), client.Nick())
isOper := client.HasRoleCapabs("history") isOper := client.HasRoleCapabs("history")
if isOper { if isOper {

View File

@ -4,10 +4,16 @@
package history package history
import ( import (
"errors"
"io" "io"
"time" "time"
) )
var (
ErrDisallowed = errors.New("disallowed")
ErrNotFound = errors.New("not found")
)
// Database is an interface for persistent history storage backends. // Database is an interface for persistent history storage backends.
type Database interface { type Database interface {
// Close closes the database connection and releases resources. // Close closes the database connection and releases resources.

View File

@ -7,7 +7,6 @@ import (
"context" "context"
"database/sql" "database/sql"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io" "io"
"runtime/debug" "runtime/debug"
@ -23,10 +22,6 @@ import (
_ "github.com/go-sql-driver/mysql" _ "github.com/go-sql-driver/mysql"
) )
var (
ErrDisallowed = errors.New("disallowed")
)
const ( const (
// maximum length in bytes of any message target (nickname or channel name) in its // maximum length in bytes of any message target (nickname or channel name) in its
// canonicalized (i.e., casefolded) state: // canonicalized (i.e., casefolded) state:
@ -749,6 +744,9 @@ func (mysql *MySQL) DeleteMsgid(msgid, accountName string) (err error) {
_, id, data, err := mysql.lookupMsgid(ctx, msgid, true) _, id, data, err := mysql.lookupMsgid(ctx, msgid, true)
if err != nil { if err != nil {
if err == sql.ErrNoRows {
return history.ErrNotFound
}
return return
} }
@ -757,7 +755,7 @@ func (mysql *MySQL) DeleteMsgid(msgid, accountName string) (err error) {
err = history.UnmarshalItem(data, &item) err = history.UnmarshalItem(data, &item)
// delete if the entry is corrupt // delete if the entry is corrupt
if err == nil && item.AccountName != accountName { if err == nil && item.AccountName != accountName {
return ErrDisallowed return history.ErrDisallowed
} }
} }
@ -830,6 +828,9 @@ func (mysql *MySQL) Export(account string, writer io.Writer) {
func (mysql *MySQL) lookupMsgid(ctx context.Context, msgid string, includeData bool) (result time.Time, id uint64, data []byte, err error) { func (mysql *MySQL) lookupMsgid(ctx context.Context, msgid string, includeData bool) (result time.Time, id uint64, data []byte, err error) {
decoded, err := utils.DecodeSecretToken(msgid) decoded, err := utils.DecodeSecretToken(msgid)
if err != nil { if err != nil {
// use sql.ErrNoRows internally for consistency, translate to history.ErrNotFound
// at the package boundary if necessary
err = sql.ErrNoRows
return return
} }
cols := `sequence.nanotime, conversations.nanotime` cols := `sequence.nanotime, conversations.nanotime`

View File

@ -1242,7 +1242,7 @@ func (server *Server) DeleteMessage(target, msgid, accountName string) (err erro
return item.Message.Msgid == msgid && (accountName == "*" || item.AccountName == accountName) return item.Message.Msgid == msgid && (accountName == "*" || item.AccountName == accountName)
}) })
if count == 0 { if count == 0 {
err = errNoop err = history.ErrNotFound
} }
} }