From c4341433d60920db98fc8a0f6fc76ab28f3cebc8 Mon Sep 17 00:00:00 2001 From: Bohdan Horbeshko Date: Mon, 25 Aug 2025 10:51:26 -0400 Subject: [PATCH] Support legacy MAM versions --- xmpp/extensions/extensions.go | 177 ++++++++++++++++++++++++++++++++++ xmpp/gateway/gateway.go | 47 +++++++-- xmpp/handlers.go | 121 +++++++++++++++++------ 3 files changed, 305 insertions(+), 40 deletions(-) diff --git a/xmpp/extensions/extensions.go b/xmpp/extensions/extensions.go index ba03643..0414fc8 100644 --- a/xmpp/extensions/extensions.go +++ b/xmpp/extensions/extensions.go @@ -376,6 +376,30 @@ type MAM2Query struct { FlipPage *FlipPage `xml:"flip-page"` } +// MAM1Query is from XEP-0313 +type MAM1Query struct { + XMLName xml.Name `xml:"urn:xmpp:mam:1 query"` + Form *stanza.Form `xml:"jabber:x:data x"` + QueryId string `xml:"queryid,attr,omitempty"` + ResultSet *stanza.ResultSet `xml:"set,omitempty"` +} + +// MAM0Query is from XEP-0313 +type MAM0Query struct { + XMLName xml.Name `xml:"urn:xmpp:mam:0 query"` + Form *stanza.Form `xml:"jabber:x:data x"` + QueryId string `xml:"queryid,attr,omitempty"` + ResultSet *stanza.ResultSet `xml:"set,omitempty"` +} + +type MAMQuery interface { + Namespace() string + GetForm() *stanza.Form + GetQueryId() string + GetSet() *stanza.ResultSet + GetFlipPage() *FlipPage +} + // FlipPage is an extended element from XEP-0313 type FlipPage struct { XMLName xml.Name `xml:"flip-page"` @@ -396,6 +420,22 @@ type MAM2MessageResult struct { Id string `xml:"id,attr,omitempty"` } +// MAM1MessageResult is from XEP-0313 +type MAM1MessageResult struct { + XMLName xml.Name `xml:"urn:xmpp:mam:1 result"` + Forwarded *ForwardedMessage `xml:"urn:xmpp:forward:0 forwarded,omitempty"` + QueryId string `xml:"queryid,attr,omitempty"` + Id string `xml:"id,attr,omitempty"` +} + +// MAM0MessageResult is from XEP-0313 +type MAM0MessageResult struct { + XMLName xml.Name `xml:"urn:xmpp:mam:0 result"` + Forwarded *ForwardedMessage `xml:"urn:xmpp:forward:0 forwarded,omitempty"` + QueryId string `xml:"queryid,attr,omitempty"` + Id string `xml:"id,attr,omitempty"` +} + // MAM2Fin is from XEP-0313 type MAM2Fin struct { XMLName xml.Name `xml:"urn:xmpp:mam:2 fin"` @@ -404,6 +444,22 @@ type MAM2Fin struct { Stable bool `xml:"stable,attr"` } +// MAM1Fin is from XEP-0313 +type MAM1Fin struct { + XMLName xml.Name `xml:"urn:xmpp:mam:1 fin"` + ResultSet *stanza.ResultSet `xml:"set,omitempty"` + Complete bool `xml:"complete,attr,omitempty"` + Stable bool `xml:"stable,attr"` +} + +// MAM0Fin is from XEP-0313 +type MAM0Fin struct { + XMLName xml.Name `xml:"urn:xmpp:mam:0 fin"` + ResultSet *stanza.ResultSet `xml:"set,omitempty"` + Complete bool `xml:"complete,attr,omitempty"` + Stable bool `xml:"stable,attr"` +} + // MAM2Metadata is from XEP-0313 type MAM2Metadata struct { XMLName xml.Name `xml:"urn:xmpp:mam:2 metadata"` @@ -536,11 +592,76 @@ func (c MAM2Query) Namespace() string { return c.XMLName.Space } +// GetForm obtains the query form +func (c MAM2Query) GetForm() *stanza.Form { + return c.Form +} + +// GetQueryId obtains the query id +func (c MAM2Query) GetQueryId() string { + return c.QueryId +} + // GetSet getsets! func (c MAM2Query) GetSet() *stanza.ResultSet { return c.ResultSet } +// GetFlipPage obtains the flip-page element +func (c MAM2Query) GetFlipPage() *FlipPage { + return c.FlipPage +} + +// Namespace is a namespace! +func (c MAM1Query) Namespace() string { + return c.XMLName.Space +} + +// GetForm obtains the query form +func (c MAM1Query) GetForm() *stanza.Form { + return c.Form +} + +// GetQueryId obtains the query id +func (c MAM1Query) GetQueryId() string { + return c.QueryId +} + +// GetSet getsets! +func (c MAM1Query) GetSet() *stanza.ResultSet { + return c.ResultSet +} + +// GetFlipPage is a stub as it's not supported in this MAM version +func (c MAM1Query) GetFlipPage() *FlipPage { + return nil +} + +// Namespace is a namespace! +func (c MAM0Query) Namespace() string { + return c.XMLName.Space +} + +// GetForm obtains the query form +func (c MAM0Query) GetForm() *stanza.Form { + return c.Form +} + +// GetQueryId obtains the query id +func (c MAM0Query) GetQueryId() string { + return c.QueryId +} + +// GetSet getsets! +func (c MAM0Query) GetSet() *stanza.ResultSet { + return c.ResultSet +} + +// GetFlipPage is a stub as it's not supported in this MAM version +func (c MAM0Query) GetFlipPage() *FlipPage { + return nil +} + // Namespace is a namespace! func (c MAM2Fin) Namespace() string { return c.XMLName.Space @@ -551,6 +672,26 @@ func (c MAM2Fin) GetSet() *stanza.ResultSet { return c.ResultSet } +// Namespace is a namespace! +func (c MAM1Fin) Namespace() string { + return c.XMLName.Space +} + +// GetSet getsets! +func (c MAM1Fin) GetSet() *stanza.ResultSet { + return c.ResultSet +} + +// Namespace is a namespace! +func (c MAM0Fin) Namespace() string { + return c.XMLName.Space +} + +// GetSet getsets! +func (c MAM0Fin) GetSet() *stanza.ResultSet { + return c.ResultSet +} + // Namespace is a namespace! func (c MAM2Metadata) Namespace() string { return c.XMLName.Space @@ -701,18 +842,54 @@ func init() { "query", }, MAM2Query{}) + // MAM1 query + stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ + "urn:xmpp:mam:1", + "query", + }, MAM1Query{}) + + // MAM0 query + stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ + "urn:xmpp:mam:0", + "query", + }, MAM0Query{}) + // MAM2 message result stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ "urn:xmpp:mam:2", "result", }, MAM2MessageResult{}) + // MAM1 message result + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ + "urn:xmpp:mam:1", + "result", + }, MAM1MessageResult{}) + + // MAM0 message result + stanza.TypeRegistry.MapExtension(stanza.PKTMessage, xml.Name{ + "urn:xmpp:mam:0", + "result", + }, MAM0MessageResult{}) + // MAM2 fin stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ "urn:xmpp:mam:2", "fin", }, MAM2Fin{}) + // MAM1 fin + stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ + "urn:xmpp:mam:1", + "fin", + }, MAM1Fin{}) + + // MAM0 fin + stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ + "urn:xmpp:mam:0", + "fin", + }, MAM0Fin{}) + // MAM2 metadata stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{ "urn:xmpp:mam:2", diff --git a/xmpp/gateway/gateway.go b/xmpp/gateway/gateway.go index 4657a2f..f8e6533 100644 --- a/xmpp/gateway/gateway.go +++ b/xmpp/gateway/gateway.go @@ -50,6 +50,10 @@ const NodeAvatarMetadataNotify string = NodeAvatarMetadata + "+notify" const NodeAvatarData string = "urn:xmpp:avatar:data" const NSCommand string = "http://jabber.org/protocol/commands" +const NS_MAM2 = "urn:xmpp:mam:2" +const NS_MAM1 = "urn:xmpp:mam:1" +const NS_MAM0 = "urn:xmpp:mam:0" + // Queue stores presences to send later var Queue = make(map[string]*stanza.Presence) var QueueLock = sync.Mutex{} @@ -437,23 +441,46 @@ func sendMessageWrapper(to, from, body, subject, errorText, id string, component } sendMessage(&privilegeMessage, component) } else if mamQueryId != "" { + mneVpadluProbrasyvatJoshParametryPoraRefaktorit := strings.Split(mamQueryId, " ") + ns := mneVpadluProbrasyvatJoshParametryPoraRefaktorit[0] + mamQueryId = mneVpadluProbrasyvatJoshParametryPoraRefaktorit[1] delay := extensions.NewMessageDelay(timestamp, "") + + forwarded := extensions.ForwardedMessage{ + Delay: &delay, + Message: &message, + } + + var ext stanza.MsgExtension + + switch ns { + case NS_MAM2: + ext = extensions.MAM2MessageResult{ + Id: stanzaId, + QueryId: mamQueryId, + Forwarded: &forwarded, + } + case NS_MAM1: + ext = extensions.MAM1MessageResult{ + Id: stanzaId, + QueryId: mamQueryId, + Forwarded: &forwarded, + } + case NS_MAM0: + ext = extensions.MAM0MessageResult{ + Id: stanzaId, + QueryId: mamQueryId, + Forwarded: &forwarded, + } + } + mamMessage := stanza.Message{ Attrs: stanza.Attrs{ From: mucJID, To: to, Type: messageType, }, - Extensions: []stanza.MsgExtension{ - extensions.MAM2MessageResult{ - Id: stanzaId, - QueryId: mamQueryId, - Forwarded: &extensions.ForwardedMessage{ - Delay: &delay, - Message: &message, - }, - }, - }, + Extensions: []stanza.MsgExtension{ext}, } sendMessage(&mamMessage, component) } else { diff --git a/xmpp/handlers.go b/xmpp/handlers.go index a6560e3..057aeff 100644 --- a/xmpp/handlers.go +++ b/xmpp/handlers.go @@ -85,7 +85,17 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) { } queryMAM2, ok := iq.Payload.(*extensions.MAM2Query) if ok { - go handleGetQueryMAM2(s, iq, queryMAM2) + go handleGetQueryMAM(s, iq, queryMAM2) + return + } + queryMAM1, ok := iq.Payload.(*extensions.MAM1Query) + if ok { + go handleGetQueryMAM(s, iq, queryMAM1) + return + } + queryMAM0, ok := iq.Payload.(*extensions.MAM0Query) + if ok { + go handleGetQueryMAM(s, iq, queryMAM0) return } _, ok = iq.Payload.(*extensions.MAM2Metadata) @@ -116,7 +126,17 @@ func HandleIq(s xmpp.Sender, p stanza.Packet) { } queryMAM2, ok := iq.Payload.(*extensions.MAM2Query) if ok { - go handleSetQueryMAM2(s, iq, queryMAM2) + go handleSetQueryMAM(s, iq, queryMAM2) + return + } + queryMAM1, ok := iq.Payload.(*extensions.MAM1Query) + if ok { + go handleSetQueryMAM(s, iq, queryMAM1) + return + } + queryMAM0, ok := iq.Payload.(*extensions.MAM0Query) + if ok { + go handleSetQueryMAM(s, iq, queryMAM0) return } } else if iq.Type == stanza.IQTypeResult { @@ -976,7 +996,9 @@ func handleGetDiscoInfo(s xmpp.Sender, iq *stanza.IQ, di *stanza.DiscoInfo) { "muc_unsecured", "http://jabber.org/protocol/muc#stable_id", "jabber:iq:register", - "urn:xmpp:mam:2", + gateway.NS_MAM0, + gateway.NS_MAM1, + gateway.NS_MAM2, "urn:xmpp:mam:2#extended", "urn:xmpp:sid:0", "vcard-temp", @@ -1382,23 +1404,33 @@ func handleGetQueryMucOwner(s xmpp.Sender, iq *stanza.IQ) { } } -func handleGetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Query) { +func handleGetQueryMAM(s xmpp.Sender, iq *stanza.IQ, query extensions.MAMQuery) { component, answer, ok := iqResultStub(s, iq) if !ok { return } defer gateway.ResumableSend(component, answer) - payload := &extensions.MAM2Query{} - answer.Payload = payload + payload2 := &extensions.MAM2Query{} + payload1 := &extensions.MAM1Query{} + payload0 := &extensions.MAM0Query{} + ns := query.Namespace() + switch ns { + case gateway.NS_MAM2: + answer.Payload = payload2 + case gateway.NS_MAM1: + answer.Payload = payload1 + case gateway.NS_MAM0: + answer.Payload = payload0 + } - payload.Form = &stanza.Form{ + form := stanza.Form{ Type: stanza.FormTypeForm, Fields: []*stanza.Field{ &stanza.Field{ Var: "FORM_TYPE", Type: stanza.FieldTypeHidden, - ValuesList: []string{"urn:xmpp:mam:2"}, + ValuesList: []string{ns}, }, &stanza.Field{ Var: "with", @@ -1426,6 +1458,9 @@ func handleGetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer }, }, } + payload2.Form = &form + payload1.Form = &form + payload0.Form = &form log.Debugf("MAM info request: %#v", query) } @@ -2154,7 +2189,7 @@ func handleSetQueryMucOwner(s xmpp.Sender, iq *stanza.IQ, query *extensions.Quer } -func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Query) { +func handleSetQueryMAM(s xmpp.Sender, iq *stanza.IQ, query extensions.MAMQuery) { component, answer, ok := iqResultStub(s, iq) if !ok { return @@ -2194,9 +2229,13 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer var rsmLimit int32 var justCount bool - log.Debugf("MAM query to %v: %#v %#v %#v", toID, query, query.Form, query.ResultSet) - if query.Form != nil && query.Form.Type == stanza.FormTypeSubmit { - for _, field := range query.Form.Fields { + form := query.GetForm() + queryRs := query.GetSet() + ns := query.Namespace() + + log.Debugf("MAM query to %v: %#v %#v %#v", toID, query, form, queryRs) + if form != nil && form.Type == stanza.FormTypeSubmit { + for _, field := range form.Fields { if len(field.ValuesList) < 1 { iqAnswerSetError(answer, 400) return @@ -2207,7 +2246,7 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer switch field.Var { case "FORM_TYPE": - if value != "urn:xmpp:mam:2" { + if value != ns { iqAnswerSetError(answer, 400) return } @@ -2246,22 +2285,22 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer } } - if query.ResultSet != nil { - if query.ResultSet.After != nil { - rsmAfter, ok = parseMessageId(*query.ResultSet.After) + if queryRs != nil { + if queryRs.After != nil { + rsmAfter, ok = parseMessageId(*queryRs.After) if !ok { iqAnswerSetError(answer, 400) return } log.Debugf("MAM RSM after: %v", rsmAfter) } - if query.ResultSet.Before != nil { - before := *query.ResultSet.Before + if queryRs.Before != nil { + before := *queryRs.Before if before == "" { rsmLastPage = true log.Debugf("MAM RSM last page") } else { - rsmBefore, ok = parseMessageId(*query.ResultSet.Before) + rsmBefore, ok = parseMessageId(*queryRs.Before) if !ok { iqAnswerSetError(answer, 400) return @@ -2269,23 +2308,23 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer log.Debugf("MAM RSM before: %v", rsmBefore) } } - if query.ResultSet.Max != nil { - rsmLimit = int32(*query.ResultSet.Max) + if queryRs.Max != nil { + rsmLimit = int32(*queryRs.Max) if rsmLimit == 0 { justCount = true } log.Debugf("MAM RSM max: %v", rsmLimit) } - if query.ResultSet.First != nil { + if queryRs.First != nil { iqAnswerSetError(answer, 400) return } - if query.ResultSet.Index != nil { + if queryRs.Index != nil { iqAnswerSetError(answer, 501) return } - if query.ResultSet.Last != nil { + if queryRs.Last != nil { iqAnswerSetError(answer, 400) return } @@ -2317,7 +2356,7 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer fromStart := true var overallyFirstMessageId, overallyLastMessageId int64 - reverse := query.FlipPage != nil + reverse := query.GetFlipPage() != nil if ids != nil { for _, sId := range ids { @@ -2532,15 +2571,37 @@ func handleSetQueryMAM2(s xmpp.Sender, iq *stanza.IQ, query *extensions.MAM2Quer } log.Debugf("obtained %v messages", len(messages)) + // ty zhe lopnesh, detochka + queryId := ns + " " + query.GetQueryId() for _, message := range messages { - session.SendDelayedMUCMessage(toID, message, iq.From, query.QueryId) + session.SendDelayedMUCMessage(toID, message, iq.From, queryId) } rs := stanza.ResultSet{} - answer.Payload = &extensions.MAM2Fin{ - ResultSet: &rs, - Complete: complete, - Stable: false, + switch ns { + case gateway.NS_MAM2: + answer.Payload = &extensions.MAM2Fin{ + ResultSet: &rs, + Complete: complete, + Stable: false, + } + case gateway.NS_MAM1: + answer.Payload = &extensions.MAM1Fin{ + ResultSet: &rs, + Complete: complete, + Stable: false, + } + case gateway.NS_MAM0: + answer.Payload = &extensions.MAM0Fin{ + ResultSet: &rs, + Complete: complete, + Stable: false, + } + } + + if answer.Payload == nil { + log.Error("Unknown MAM version") + return } if beyond {