mirror of
https://github.com/ergochat/ergo.git
synced 2024-11-28 23:19:30 +01:00
fix #1676
Fix various pagination issues with CHATHISTORY; also undo #491 (msgid munging).
This commit is contained in:
parent
b478e93c11
commit
5bbee02fe6
@ -962,7 +962,7 @@ history:
|
|||||||
# if `default` is false, store TAGMSG containing any of these tags:
|
# if `default` is false, store TAGMSG containing any of these tags:
|
||||||
whitelist:
|
whitelist:
|
||||||
- "+draft/react"
|
- "+draft/react"
|
||||||
- "react"
|
- "+react"
|
||||||
|
|
||||||
# if `default` is true, don't store TAGMSG containing any of these tags:
|
# if `default` is true, don't store TAGMSG containing any of these tags:
|
||||||
#blacklist:
|
#blacklist:
|
||||||
|
@ -917,7 +917,7 @@ func (channel *Channel) autoReplayHistory(client *Client, rb *ResponseBuffer, sk
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hasAutoreplayTimestamps {
|
if hasAutoreplayTimestamps {
|
||||||
_, seq, _ := channel.server.GetHistorySequence(channel, client, "")
|
_, seq, _ := channel.server.GetHistorySequence(channel, client, "", 0)
|
||||||
if seq != nil {
|
if seq != nil {
|
||||||
zncMax := channel.server.Config().History.ZNCMax
|
zncMax := channel.server.Config().History.ZNCMax
|
||||||
items, _ = seq.Between(history.Selector{Time: start}, history.Selector{Time: end}, zncMax)
|
items, _ = seq.Between(history.Selector{Time: start}, history.Selector{Time: end}, zncMax)
|
||||||
@ -935,7 +935,7 @@ func (channel *Channel) autoReplayHistory(client *Client, rb *ResponseBuffer, sk
|
|||||||
replayLimit = channel.server.Config().History.AutoreplayOnJoin
|
replayLimit = channel.server.Config().History.AutoreplayOnJoin
|
||||||
}
|
}
|
||||||
if 0 < replayLimit {
|
if 0 < replayLimit {
|
||||||
_, seq, _ := channel.server.GetHistorySequence(channel, client, "")
|
_, seq, _ := channel.server.GetHistorySequence(channel, client, "", 0)
|
||||||
if seq != nil {
|
if seq != nil {
|
||||||
items, _ = seq.Between(history.Selector{}, history.Selector{}, replayLimit)
|
items, _ = seq.Between(history.Selector{}, history.Selector{}, replayLimit)
|
||||||
}
|
}
|
||||||
@ -1084,7 +1084,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
|||||||
} else {
|
} else {
|
||||||
message = fmt.Sprintf(client.t("%[1]s [account: %[2]s] joined the channel"), nick, item.AccountName)
|
message = fmt.Sprintf(client.t("%[1]s [account: %[2]s] joined the channel"), nick, item.AccountName)
|
||||||
}
|
}
|
||||||
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
||||||
}
|
}
|
||||||
case history.Part:
|
case history.Part:
|
||||||
if eventPlayback {
|
if eventPlayback {
|
||||||
@ -1094,14 +1094,14 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
|||||||
continue // #474
|
continue // #474
|
||||||
}
|
}
|
||||||
message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
|
message := fmt.Sprintf(client.t("%[1]s left the channel (%[2]s)"), nick, item.Message.Message)
|
||||||
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
||||||
}
|
}
|
||||||
case history.Kick:
|
case history.Kick:
|
||||||
if eventPlayback {
|
if eventPlayback {
|
||||||
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "KICK", chname, item.Params[0], item.Message.Message)
|
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "KICK", chname, item.Params[0], item.Message.Message)
|
||||||
} else {
|
} else {
|
||||||
message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Params[0], item.Message.Message)
|
message := fmt.Sprintf(client.t("%[1]s kicked %[2]s (%[3]s)"), nick, item.Params[0], item.Message.Message)
|
||||||
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
||||||
}
|
}
|
||||||
case history.Quit:
|
case history.Quit:
|
||||||
if eventPlayback {
|
if eventPlayback {
|
||||||
@ -1111,21 +1111,21 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
|||||||
continue // #474
|
continue // #474
|
||||||
}
|
}
|
||||||
message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
|
message := fmt.Sprintf(client.t("%[1]s quit (%[2]s)"), nick, item.Message.Message)
|
||||||
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
||||||
}
|
}
|
||||||
case history.Nick:
|
case history.Nick:
|
||||||
if eventPlayback {
|
if eventPlayback {
|
||||||
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "NICK", item.Params[0])
|
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "NICK", item.Params[0])
|
||||||
} else {
|
} else {
|
||||||
message := fmt.Sprintf(client.t("%[1]s changed nick to %[2]s"), nick, item.Params[0])
|
message := fmt.Sprintf(client.t("%[1]s changed nick to %[2]s"), nick, item.Params[0])
|
||||||
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
||||||
}
|
}
|
||||||
case history.Topic:
|
case history.Topic:
|
||||||
if eventPlayback {
|
if eventPlayback {
|
||||||
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "TOPIC", chname, item.Message.Message)
|
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "TOPIC", chname, item.Message.Message)
|
||||||
} else {
|
} else {
|
||||||
message := fmt.Sprintf(client.t("%[1]s set the channel topic to: %[2]s"), nick, item.Message.Message)
|
message := fmt.Sprintf(client.t("%[1]s set the channel topic to: %[2]s"), nick, item.Message.Message)
|
||||||
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
||||||
}
|
}
|
||||||
case history.Mode:
|
case history.Mode:
|
||||||
params := make([]string, len(item.Message.Split)+1)
|
params := make([]string, len(item.Message.Split)+1)
|
||||||
@ -1137,7 +1137,7 @@ func (channel *Channel) replayHistoryItems(rb *ResponseBuffer, items []history.I
|
|||||||
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "MODE", params...)
|
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "MODE", params...)
|
||||||
} else {
|
} else {
|
||||||
message := fmt.Sprintf(client.t("%[1]s set channel modes: %[2]s"), nick, strings.Join(params[1:], " "))
|
message := fmt.Sprintf(client.t("%[1]s set channel modes: %[2]s"), nick, strings.Join(params[1:], " "))
|
||||||
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", chname, message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -883,7 +883,7 @@ func (client *Client) replayPrivmsgHistory(rb *ResponseBuffer, items []history.I
|
|||||||
if hasEventPlayback {
|
if hasEventPlayback {
|
||||||
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "INVITE", nick, item.Message.Message)
|
rb.AddFromClient(item.Message.Time, item.Message.Msgid, item.Nick, item.AccountName, item.IsBot, nil, "INVITE", nick, item.Message.Message)
|
||||||
} else {
|
} else {
|
||||||
rb.AddFromClient(item.Message.Time, utils.MungeSecretToken(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", fmt.Sprintf(client.t("%[1]s invited you to channel %[2]s"), NUHToNick(item.Nick), item.Message.Message))
|
rb.AddFromClient(item.Message.Time, history.HistservMungeMsgid(item.Message.Msgid), histservService.prefix, "*", false, nil, "PRIVMSG", fmt.Sprintf(client.t("%[1]s invited you to channel %[2]s"), NUHToNick(item.Nick), item.Message.Message))
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
case history.Privmsg:
|
case history.Privmsg:
|
||||||
@ -1713,7 +1713,7 @@ func (client *Client) listTargets(start, end history.Selector, limit int) (resul
|
|||||||
var base, extras []history.TargetListing
|
var base, extras []history.TargetListing
|
||||||
var chcfnames []string
|
var chcfnames []string
|
||||||
for _, channel := range client.Channels() {
|
for _, channel := range client.Channels() {
|
||||||
_, seq, err := client.server.GetHistorySequence(channel, client, "")
|
_, seq, err := client.server.GetHistorySequence(channel, client, "", 0)
|
||||||
if seq == nil || err != nil {
|
if seq == nil || err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -1734,7 +1734,7 @@ func (client *Client) listTargets(start, end history.Selector, limit int) (resul
|
|||||||
extras = append(extras, persistentExtras...)
|
extras = append(extras, persistentExtras...)
|
||||||
}
|
}
|
||||||
|
|
||||||
_, cSeq, err := client.server.GetHistorySequence(nil, client, "")
|
_, cSeq, err := client.server.GetHistorySequence(nil, client, "", 0)
|
||||||
if err == nil && cSeq != nil {
|
if err == nil && cSeq != nil {
|
||||||
correspondents, err := cSeq.ListCorrespondents(start, end, limit)
|
correspondents, err := cSeq.ListCorrespondents(start, end, limit)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
@ -1758,7 +1758,7 @@ func (client *Client) privmsgsBetween(startTime, endTime time.Time, targetLimit,
|
|||||||
if strings.HasPrefix(target.CfName, "#") {
|
if strings.HasPrefix(target.CfName, "#") {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
_, seq, err := client.server.GetHistorySequence(nil, client, target.CfName)
|
_, seq, err := client.server.GetHistorySequence(nil, client, target.CfName, 0)
|
||||||
if err == nil && seq != nil {
|
if err == nil && seq != nil {
|
||||||
items, err := seq.Between(start, end, messageLimit)
|
items, err := seq.Between(start, end, messageLimit)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -640,7 +640,7 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb *
|
|||||||
}
|
}
|
||||||
identifier, value := strings.ToLower(pieces[0]), pieces[1]
|
identifier, value := strings.ToLower(pieces[0]), pieces[1]
|
||||||
if identifier == "msgid" {
|
if identifier == "msgid" {
|
||||||
msgid, err = value, nil
|
msgid, err = history.NormalizeMsgid(value), nil
|
||||||
return
|
return
|
||||||
} else if identifier == "timestamp" {
|
} else if identifier == "timestamp" {
|
||||||
timestamp, err = time.Parse(IRCv3TimestampFormat, value)
|
timestamp, err = time.Parse(IRCv3TimestampFormat, value)
|
||||||
@ -725,7 +725,17 @@ func chathistoryHandler(server *Server, client *Client, msg ircmsg.Message, rb *
|
|||||||
if listTargets {
|
if listTargets {
|
||||||
targets, err = client.listTargets(start, end, limit)
|
targets, err = client.listTargets(start, end, limit)
|
||||||
} else {
|
} else {
|
||||||
channel, sequence, err = server.GetHistorySequence(nil, client, target)
|
// see #1676; for CHATHISTORY we need to make the paging window as exact as possible,
|
||||||
|
// hence filtering out undisplayable messages on the backend, in order to send a full
|
||||||
|
// paging window if possible
|
||||||
|
var flags history.ExcludeFlags
|
||||||
|
if !rb.session.capabilities.Has(caps.EventPlayback) {
|
||||||
|
flags |= history.ExcludeTagmsg
|
||||||
|
}
|
||||||
|
if client.AccountSettings().ReplayJoins == ReplayJoinsNever {
|
||||||
|
flags |= history.ExcludeJoins
|
||||||
|
}
|
||||||
|
channel, sequence, err = server.GetHistorySequence(nil, client, target, flags)
|
||||||
if err != nil || sequence == nil {
|
if err != nil || sequence == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -53,6 +53,17 @@ func (item *Item) HasMsgid(msgid string) bool {
|
|||||||
return item.Message.Msgid == msgid
|
return item.Message.Msgid == msgid
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (item *Item) IsExcluded(excludeFlags ExcludeFlags) bool {
|
||||||
|
switch item.Type {
|
||||||
|
case Tagmsg:
|
||||||
|
return excludeFlags&ExcludeTagmsg != 0
|
||||||
|
case Join, Part, Quit:
|
||||||
|
return excludeFlags&ExcludeJoins != 0
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
type Predicate func(item *Item) (matches bool)
|
type Predicate func(item *Item) (matches bool)
|
||||||
|
|
||||||
func Reverse(results []Item) {
|
func Reverse(results []Item) {
|
||||||
@ -155,7 +166,7 @@ func (list *Buffer) lookup(msgid string) (result Item, found bool) {
|
|||||||
// with an indication of whether the results are complete or are missing items
|
// with an indication of whether the results are complete or are missing items
|
||||||
// because some of that period was discarded. A zero value of `before` is considered
|
// because some of that period was discarded. A zero value of `before` is considered
|
||||||
// higher than all other times.
|
// higher than all other times.
|
||||||
func (list *Buffer) betweenHelper(start, end Selector, cutoff time.Time, pred Predicate, limit int) (results []Item, complete bool, err error) {
|
func (list *Buffer) betweenHelper(start, end Selector, cutoff time.Time, pred Predicate, limit int, excludeFlags ExcludeFlags) (results []Item, complete bool, err error) {
|
||||||
var ascending bool
|
var ascending bool
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
@ -195,7 +206,8 @@ func (list *Buffer) betweenHelper(start, end Selector, cutoff time.Time, pred Pr
|
|||||||
satisfies := func(item *Item) bool {
|
satisfies := func(item *Item) bool {
|
||||||
return (after.IsZero() || item.Message.Time.After(after)) &&
|
return (after.IsZero() || item.Message.Time.After(after)) &&
|
||||||
(before.IsZero() || item.Message.Time.Before(before)) &&
|
(before.IsZero() || item.Message.Time.Before(before)) &&
|
||||||
(pred == nil || pred(item))
|
(pred == nil || pred(item)) &&
|
||||||
|
!item.IsExcluded(excludeFlags)
|
||||||
}
|
}
|
||||||
|
|
||||||
return list.matchInternal(satisfies, ascending, limit), complete, nil
|
return list.matchInternal(satisfies, ascending, limit), complete, nil
|
||||||
@ -279,9 +291,10 @@ type bufferSequence struct {
|
|||||||
list *Buffer
|
list *Buffer
|
||||||
pred Predicate
|
pred Predicate
|
||||||
cutoff time.Time
|
cutoff time.Time
|
||||||
|
flags ExcludeFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (list *Buffer) MakeSequence(correspondent string, cutoff time.Time) Sequence {
|
func (list *Buffer) MakeSequence(correspondent string, cutoff time.Time, flags ExcludeFlags) Sequence {
|
||||||
var pred Predicate
|
var pred Predicate
|
||||||
if correspondent != "" {
|
if correspondent != "" {
|
||||||
pred = func(item *Item) bool {
|
pred = func(item *Item) bool {
|
||||||
@ -292,11 +305,12 @@ func (list *Buffer) MakeSequence(correspondent string, cutoff time.Time) Sequenc
|
|||||||
list: list,
|
list: list,
|
||||||
pred: pred,
|
pred: pred,
|
||||||
cutoff: cutoff,
|
cutoff: cutoff,
|
||||||
|
flags: flags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (seq *bufferSequence) Between(start, end Selector, limit int) (results []Item, err error) {
|
func (seq *bufferSequence) Between(start, end Selector, limit int) (results []Item, err error) {
|
||||||
results, _, err = seq.list.betweenHelper(start, end, seq.cutoff, seq.pred, limit)
|
results, _, err = seq.list.betweenHelper(start, end, seq.cutoff, seq.pred, limit, seq.flags)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -377,7 +391,7 @@ func (list *Buffer) Delete(predicate Predicate) (count int) {
|
|||||||
// latest returns the items most recently added, up to `limit`. If `limit` is 0,
|
// latest returns the items most recently added, up to `limit`. If `limit` is 0,
|
||||||
// it returns all items.
|
// it returns all items.
|
||||||
func (list *Buffer) latest(limit int) (results []Item) {
|
func (list *Buffer) latest(limit int) (results []Item) {
|
||||||
results, _, _ = list.betweenHelper(Selector{}, Selector{}, time.Time{}, nil, limit)
|
results, _, _ = list.betweenHelper(Selector{}, Selector{}, time.Time{}, nil, limit, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func betweenTimestamps(buf *Buffer, start, end time.Time, limit int) (result []Item, complete bool) {
|
func betweenTimestamps(buf *Buffer, start, end time.Time, limit int) (result []Item, complete bool) {
|
||||||
result, complete, _ = buf.betweenHelper(Selector{Time: start}, Selector{Time: end}, time.Time{}, nil, limit)
|
result, complete, _ = buf.betweenHelper(Selector{Time: start}, Selector{Time: end}, time.Time{}, nil, limit, 0)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -45,7 +45,7 @@ func TestEmptyBuffer(t *testing.T) {
|
|||||||
})
|
})
|
||||||
since, complete = betweenTimestamps(buf, pastTime, time.Now(), 0)
|
since, complete = betweenTimestamps(buf, pastTime, time.Now(), 0)
|
||||||
if len(since) != 1 {
|
if len(since) != 1 {
|
||||||
t.Error("should be able to store items in a nonempty buffer")
|
t.Errorf("should be able to store items in a nonempty buffer: expected %d, got %d", 1, len(since))
|
||||||
}
|
}
|
||||||
if !complete {
|
if !complete {
|
||||||
t.Error("results should be complete")
|
t.Error("results should be complete")
|
||||||
|
@ -4,9 +4,17 @@
|
|||||||
package history
|
package history
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type ExcludeFlags uint
|
||||||
|
|
||||||
|
const (
|
||||||
|
ExcludeTagmsg ExcludeFlags = 1 << iota
|
||||||
|
ExcludeJoins
|
||||||
|
)
|
||||||
|
|
||||||
// Selector represents a parameter to a CHATHISTORY command
|
// Selector represents a parameter to a CHATHISTORY command
|
||||||
type Selector struct {
|
type Selector struct {
|
||||||
Msgid string
|
Msgid string
|
||||||
@ -77,3 +85,16 @@ func MinMaxAsc(after, before, cutoff time.Time) (min, max time.Time, ascending b
|
|||||||
}
|
}
|
||||||
return after, before, ascending
|
return after, before, ascending
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// maps regular msgids from JOIN, etc. to a msgid suitable for attaching
|
||||||
|
// to a HistServ message describing the JOIN. See #491 for some history.
|
||||||
|
func HistservMungeMsgid(msgid string) string {
|
||||||
|
return "_" + msgid
|
||||||
|
}
|
||||||
|
|
||||||
|
// strips munging from a msgid. future schemes may not support a well-defined
|
||||||
|
// mapping of munged msgids to true msgids, but munged msgids should always contain
|
||||||
|
// a _, with metadata in front and data (possibly the true msgid) after.
|
||||||
|
func NormalizeMsgid(msgid string) string {
|
||||||
|
return strings.TrimPrefix(msgid, "_")
|
||||||
|
}
|
||||||
|
@ -199,7 +199,7 @@ func histservPlayHandler(service *ircService, server *Server, client *Client, co
|
|||||||
|
|
||||||
// handles parameter parsing and history queries for /HISTORY and /HISTSERV PLAY
|
// handles parameter parsing and history queries for /HISTORY and /HISTSERV PLAY
|
||||||
func easySelectHistory(server *Server, client *Client, params []string) (items []history.Item, channel *Channel, err error) {
|
func easySelectHistory(server *Server, client *Client, params []string) (items []history.Item, channel *Channel, err error) {
|
||||||
channel, sequence, err := server.GetHistorySequence(nil, client, params[0])
|
channel, sequence, err := server.GetHistorySequence(nil, client, params[0], 0)
|
||||||
|
|
||||||
if sequence == nil || err != nil {
|
if sequence == nil || err != nil {
|
||||||
return nil, nil, errNoSuchChannel
|
return nil, nil, errNoSuchChannel
|
||||||
|
@ -40,6 +40,10 @@ const (
|
|||||||
keySchemaMinorVersion = "db.minorversion"
|
keySchemaMinorVersion = "db.minorversion"
|
||||||
cleanupRowLimit = 50
|
cleanupRowLimit = 50
|
||||||
cleanupPauseTime = 10 * time.Minute
|
cleanupPauseTime = 10 * time.Minute
|
||||||
|
|
||||||
|
// if we don't fill the pagination window due to exclusions,
|
||||||
|
// retry with an expanded window at most this many times
|
||||||
|
maxPaginationRetries = 3
|
||||||
)
|
)
|
||||||
|
|
||||||
type e struct{}
|
type e struct{}
|
||||||
@ -1033,9 +1037,18 @@ type mySQLHistorySequence struct {
|
|||||||
target string
|
target string
|
||||||
correspondent string
|
correspondent string
|
||||||
cutoff time.Time
|
cutoff time.Time
|
||||||
|
excludeFlags history.ExcludeFlags
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *mySQLHistorySequence) Between(start, end history.Selector, limit int) (results []history.Item, err error) {
|
func (s *mySQLHistorySequence) Between(start, end history.Selector, limit int) (results []history.Item, err error) {
|
||||||
|
if s.excludeFlags == 0 {
|
||||||
|
return s.baseBetween(start, end, limit)
|
||||||
|
} else {
|
||||||
|
return s.betweenWithRetries(start, end, limit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *mySQLHistorySequence) baseBetween(start, end history.Selector, limit int) (results []history.Item, err error) {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), s.mysql.getTimeout())
|
ctx, cancel := context.WithTimeout(context.Background(), s.mysql.getTimeout())
|
||||||
defer cancel()
|
defer cancel()
|
||||||
|
|
||||||
@ -1058,7 +1071,45 @@ func (s *mySQLHistorySequence) Between(start, end history.Selector, limit int) (
|
|||||||
return results, err
|
return results, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *mySQLHistorySequence) betweenWithRetries(start, end history.Selector, limit int) (results []history.Item, err error) {
|
||||||
|
applyExclusions := func(currentResults []history.Item, excludeFlags history.ExcludeFlags, trueLimit int) (filteredResults []history.Item) {
|
||||||
|
filteredResults = make([]history.Item, 0, len(currentResults))
|
||||||
|
for _, item := range currentResults {
|
||||||
|
if !item.IsExcluded(excludeFlags) {
|
||||||
|
filteredResults = append(filteredResults, item)
|
||||||
|
}
|
||||||
|
if len(filteredResults) == trueLimit {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
i := 1
|
||||||
|
for {
|
||||||
|
currentLimit := limit * i
|
||||||
|
currentResults, err := s.baseBetween(start, end, currentLimit)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
results = applyExclusions(currentResults, s.excludeFlags, limit)
|
||||||
|
// we're done in any of these three cases:
|
||||||
|
// (1) we filled the window (2) we ran out of results on the backend (3) we can't retry anymore
|
||||||
|
if len(results) == limit || len(currentResults) < currentLimit || i == maxPaginationRetries {
|
||||||
|
return results, nil
|
||||||
|
}
|
||||||
|
i++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (s *mySQLHistorySequence) Around(start history.Selector, limit int) (results []history.Item, err error) {
|
func (s *mySQLHistorySequence) Around(start history.Selector, limit int) (results []history.Item, err error) {
|
||||||
|
// temporarily clear the exclude flags when running GenericAround, since we don't care about
|
||||||
|
// the exactness of the paging window at all
|
||||||
|
oldExcludeFlags := s.excludeFlags
|
||||||
|
s.excludeFlags = 0
|
||||||
|
defer func() {
|
||||||
|
s.excludeFlags = oldExcludeFlags
|
||||||
|
}()
|
||||||
return history.GenericAround(s, start, limit)
|
return history.GenericAround(s, start, limit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1083,11 +1134,12 @@ func (seq *mySQLHistorySequence) Ephemeral() bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (mysql *MySQL) MakeSequence(target, correspondent string, cutoff time.Time) history.Sequence {
|
func (mysql *MySQL) MakeSequence(target, correspondent string, cutoff time.Time, excludeFlags history.ExcludeFlags) history.Sequence {
|
||||||
return &mySQLHistorySequence{
|
return &mySQLHistorySequence{
|
||||||
target: target,
|
target: target,
|
||||||
correspondent: correspondent,
|
correspondent: correspondent,
|
||||||
mysql: mysql,
|
mysql: mysql,
|
||||||
cutoff: cutoff,
|
cutoff: cutoff,
|
||||||
|
excludeFlags: excludeFlags,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -862,7 +862,7 @@ func (server *Server) setupListeners(config *Config) (err error) {
|
|||||||
// suitable for ListCorrespondents (i.e., this function is still used to
|
// suitable for ListCorrespondents (i.e., this function is still used to
|
||||||
// decide whether the ringbuf or mysql is authoritative about the client's
|
// decide whether the ringbuf or mysql is authoritative about the client's
|
||||||
// message history).
|
// message history).
|
||||||
func (server *Server) GetHistorySequence(providedChannel *Channel, client *Client, query string) (channel *Channel, sequence history.Sequence, err error) {
|
func (server *Server) GetHistorySequence(providedChannel *Channel, client *Client, query string, excludeFlags history.ExcludeFlags) (channel *Channel, sequence history.Sequence, err error) {
|
||||||
config := server.Config()
|
config := server.Config()
|
||||||
// 4 cases: {persistent, ephemeral} x {normal, conversation}
|
// 4 cases: {persistent, ephemeral} x {normal, conversation}
|
||||||
// with ephemeral history, target is implicit in the choice of `hist`,
|
// with ephemeral history, target is implicit in the choice of `hist`,
|
||||||
@ -940,9 +940,9 @@ func (server *Server) GetHistorySequence(providedChannel *Channel, client *Clien
|
|||||||
}
|
}
|
||||||
|
|
||||||
if hist != nil {
|
if hist != nil {
|
||||||
sequence = hist.MakeSequence(correspondent, cutoff)
|
sequence = hist.MakeSequence(correspondent, cutoff, excludeFlags)
|
||||||
} else if target != "" {
|
} else if target != "" {
|
||||||
sequence = server.historyDB.MakeSequence(target, correspondent, cutoff)
|
sequence = server.historyDB.MakeSequence(target, correspondent, cutoff, excludeFlags)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -42,29 +42,6 @@ func GenerateSecretToken() string {
|
|||||||
return B32Encoder.EncodeToString(buf[:])
|
return B32Encoder.EncodeToString(buf[:])
|
||||||
}
|
}
|
||||||
|
|
||||||
// "munge" a secret token to a new value. requirements:
|
|
||||||
// 1. MUST be roughly as unlikely to collide with `GenerateSecretToken` outputs
|
|
||||||
// as those outputs are with each other
|
|
||||||
// 2. SHOULD be deterministic (motivation: if a JOIN line has msgid x,
|
|
||||||
// create a deterministic msgid y for the fake HistServ PRIVMSG that "replays" it)
|
|
||||||
// 3. SHOULD be in the same "namespace" as `GenerateSecretToken` outputs
|
|
||||||
// (same length and character set)
|
|
||||||
func MungeSecretToken(token string) (result string) {
|
|
||||||
bytes, err := B32Encoder.DecodeString(token)
|
|
||||||
if err != nil {
|
|
||||||
// this should never happen
|
|
||||||
return GenerateSecretToken()
|
|
||||||
}
|
|
||||||
// add 1 with carrying
|
|
||||||
for i := len(bytes) - 1; 0 <= i; i -= 1 {
|
|
||||||
bytes[i] += 1
|
|
||||||
if bytes[i] != 0 {
|
|
||||||
break
|
|
||||||
} // else: overflow, carry to the next place
|
|
||||||
}
|
|
||||||
return B32Encoder.EncodeToString(bytes)
|
|
||||||
}
|
|
||||||
|
|
||||||
// securely check if a supplied token matches a stored token
|
// securely check if a supplied token matches a stored token
|
||||||
func SecretTokensMatch(storedToken string, suppliedToken string) bool {
|
func SecretTokensMatch(storedToken string, suppliedToken string) bool {
|
||||||
// XXX fix a potential gotcha: if the stored token is uninitialized,
|
// XXX fix a potential gotcha: if the stored token is uninitialized,
|
||||||
|
@ -47,41 +47,12 @@ func TestTokenCompare(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestMunging(t *testing.T) {
|
|
||||||
count := 131072
|
|
||||||
set := make(map[string]bool)
|
|
||||||
var token string
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
token = GenerateSecretToken()
|
|
||||||
set[token] = true
|
|
||||||
}
|
|
||||||
// all tokens generated thus far should be unique
|
|
||||||
assertEqual(len(set), count, t)
|
|
||||||
|
|
||||||
// iteratively munge the last generated token an additional `count` times
|
|
||||||
mungedToken := token
|
|
||||||
for i := 0; i < count; i++ {
|
|
||||||
mungedToken = MungeSecretToken(mungedToken)
|
|
||||||
assertEqual(len(mungedToken), len(token), t)
|
|
||||||
set[mungedToken] = true
|
|
||||||
}
|
|
||||||
// munged tokens should not collide with generated tokens, or each other
|
|
||||||
assertEqual(len(set), count*2, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func BenchmarkGenerateSecretToken(b *testing.B) {
|
func BenchmarkGenerateSecretToken(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
GenerateSecretToken()
|
GenerateSecretToken()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func BenchmarkMungeSecretToken(b *testing.B) {
|
|
||||||
t := GenerateSecretToken()
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
t = MungeSecretToken(t)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestCertfpComparisons(t *testing.T) {
|
func TestCertfpComparisons(t *testing.T) {
|
||||||
opensslFP := "3D:6B:11:BF:B4:05:C3:F8:4B:38:CD:30:38:FB:EC:01:71:D5:03:54:79:04:07:88:4C:A5:5D:23:41:85:66:C9"
|
opensslFP := "3D:6B:11:BF:B4:05:C3:F8:4B:38:CD:30:38:FB:EC:01:71:D5:03:54:79:04:07:88:4C:A5:5D:23:41:85:66:C9"
|
||||||
oragonoFP := "3d6b11bfb405c3f84b38cd3038fbec0171d50354790407884ca55d23418566c9"
|
oragonoFP := "3d6b11bfb405c3f84b38cd3038fbec0171d50354790407884ca55d23418566c9"
|
||||||
|
@ -189,7 +189,7 @@ func zncPlaybackPlayHandler(client *Client, command string, params []string, rb
|
|||||||
}
|
}
|
||||||
|
|
||||||
func zncPlayPrivmsgsFrom(client *Client, rb *ResponseBuffer, target string, start, end time.Time) {
|
func zncPlayPrivmsgsFrom(client *Client, rb *ResponseBuffer, target string, start, end time.Time) {
|
||||||
_, sequence, err := client.server.GetHistorySequence(nil, client, target)
|
_, sequence, err := client.server.GetHistorySequence(nil, client, target, 0)
|
||||||
if sequence == nil || err != nil {
|
if sequence == nil || err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user