403Webshell
Server IP : 157.230.181.24  /  Your IP : 216.73.217.11
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux conductive 6.8.0-117-generic #117-Ubuntu SMP PREEMPT_DYNAMIC Tue May 5 19:26:24 UTC 2026 x86_64
User :  ( 1000)
PHP Version : 8.3.31
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /var/www/vhosts/ceagon/open-oscar-server/state/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /var/www/vhosts/ceagon/open-oscar-server/state/feedbag_list.go
package state

import (
	"errors"
	"fmt"
	"math"
	"slices"

	"github.com/mk6i/open-oscar-server/wire"
)

// ErrGroupNotFound is returned when a feedbag group cannot be found.
var ErrGroupNotFound = errors.New("group not found")

// ErrGroupExists is returned when a feedbag group cannot be created or renamed
// because another group already has the target name.
var ErrGroupExists = errors.New("group already exists")

// ErrBuddyNotFound is returned when a feedbag buddy cannot be found.
var ErrBuddyNotFound = errors.New("buddy not found")

// FeedbagList provides operations for manipulating a collection of feedbag
// items. It supports lookups by class/name/group, item insertion with
// automatic ID generation, and transparent root group management.
type FeedbagList struct {
	items          []*wire.FeedbagItem
	randInt        func(int) int
	pendingUpdates []*wire.FeedbagItem
	pendingDeletes []*wire.FeedbagItem
}

// NewFeedbagList creates a FeedbagList from the given items. The randInt
// function is used for generating unique item/group IDs; inject a
// deterministic function in tests to assert exact feedbag item slices.
func NewFeedbagList(items []wire.FeedbagItem, randInt func(int) int) *FeedbagList {
	ptrs := make([]*wire.FeedbagItem, len(items))
	for i := range items {
		ptrs[i] = &items[i]
	}
	return &FeedbagList{
		items:   ptrs,
		randInt: randInt,
	}
}

// SetMode upserts the permit/deny mode item.
func (f *FeedbagList) SetMode(mode uint8) {
	f.upsertItem(wire.FeedbagItem{
		ClassID: wire.FeedbagClassIdPdinfo,
		TLVLBlock: wire.TLVLBlock{
			TLVList: wire.TLVList{
				wire.NewTLVBE(wire.FeedbagAttributesPdMode, mode),
			},
		},
	})
}

// AddGroup returns the existing group with the given name or creates a new
// one with an auto-generated GroupID. When a new group is created, the root
// group's order TLV is updated to include it. Call PendingUpdates to retrieve
// new or modified items for persistence. Returns the group item.
func (f *FeedbagList) AddGroup(name string) wire.FeedbagItem {
	if g := f.groupByName(name); g != nil {
		return *g
	}

	group := &wire.FeedbagItem{
		ClassID: wire.FeedbagClassIdGroup,
		Name:    name,
		GroupID: f.genID(),
	}
	f.items = append(f.items, group)

	root := f.rootGroup()
	root.AppendOrderMembers(group.GroupID)
	f.trackUpdate(root)
	f.trackUpdate(group)

	return *group
}

// DeleteGroup marks a group item for deletion by name. If the group exists
// and is not the root group, the root group's order TLV is updated.
func (f *FeedbagList) DeleteGroup(groupName string) {
	groupItem := f.groupByName(groupName)
	if groupItem == nil {
		return
	}

	var toDelete []*wire.FeedbagItem

	for _, item := range f.items {
		if item.GroupID == groupItem.GroupID {
			toDelete = append(toDelete, item)
		}
	}

	for _, item := range toDelete {
		f.deleteItem(*item)
	}

	if len(toDelete) > 0 && groupItem.GroupID != 0 {
		for _, item := range f.items {
			if item.ClassID == wire.FeedbagClassIdGroup && item.GroupID == 0 {
				item.RemoveOrderMembers(groupItem.GroupID)
				f.trackUpdate(item)
			}
		}
	}
}

// AddBuddy upserts a buddy item in the given group (by name), optionally
// attaching alias and note attributes. Returns true if a new buddy was inserted.
func (f *FeedbagList) AddBuddy(groupName, screenName, alias, note string) (bool, error) {
	var attrs wire.TLVList
	if alias != "" {
		attrs.Append(wire.NewTLVBE(wire.FeedbagAttributesAlias, alias))
	}
	if note != "" {
		attrs.Append(wire.NewTLVBE(wire.FeedbagAttributesNote, note))
	}
	return f.addBuddyItem(groupName, screenName, attrs)
}

// addBuddyItem upserts a buddy item carrying the given attribute TLVs into the
// named group. Returns true if a new buddy was inserted.
func (f *FeedbagList) addBuddyItem(groupName, screenName string, attrs wire.TLVList) (bool, error) {
	group := f.groupByName(groupName)
	if group == nil {
		return false, fmt.Errorf("group %q not found", groupName)
	}
	item := wire.FeedbagItem{
		ClassID: wire.FeedbagClassIdBuddy,
		GroupID: group.GroupID,
		Name:    screenName,
	}
	item.TLVList = attrs
	result, inserted := f.upsertItem(item)
	if inserted {
		group.AppendOrderMembers(result.ItemID)
		f.trackUpdate(group)
	}
	return inserted, nil
}

// DeleteBuddy marks a buddy item for deletion in the given group (by name).
// The parent group's order TLV is updated to remove the buddy.
// Pass "*" as groupName to remove the buddy from all groups.
func (f *FeedbagList) DeleteBuddy(groupName, buddyName string) error {
	var groups []*wire.FeedbagItem

	if groupName == "*" {
		// delete from all groups
		for _, item := range f.items {
			if item.ClassID == wire.FeedbagClassIdGroup && item.GroupID != 0 {
				groups = append(groups, item)
			}
		}
	} else {
		group := f.groupByName(groupName)
		if group == nil {
			return fmt.Errorf("%w: %q", ErrGroupNotFound, groupName)
		}
		groups = []*wire.FeedbagItem{group}
	}

	for _, group := range groups {
		deleted, found := f.deleteItem(wire.FeedbagItem{
			ClassID: wire.FeedbagClassIdBuddy,
			GroupID: group.GroupID,
			Name:    buddyName,
		})
		if found {
			group.RemoveOrderMembers(deleted.ItemID)
			f.trackUpdate(group)
		}
	}

	return nil
}

// RenameGroup changes a group's name in place, preserving its GroupID and
// ItemID. Returns ErrGroupNotFound if oldName does not exist, or ErrGroupExists
// if a different group already uses newName. The group is renamed in place
// rather than re-inserted because upsertItem keys groups on name and would
// otherwise create a duplicate.
func (f *FeedbagList) RenameGroup(oldName, newName string) error {
	group := f.groupByName(oldName)
	if group == nil {
		return fmt.Errorf("%w: %q", ErrGroupNotFound, oldName)
	}
	if newName != oldName && f.groupByName(newName) != nil {
		return fmt.Errorf("%w: %q", ErrGroupExists, newName)
	}
	if group.Name == newName {
		return nil
	}
	group.Name = newName
	f.trackUpdate(group)
	return nil
}

// MoveBuddy moves a buddy between groups and/or repositions it within a group's
// order. When toGroup names a different group than fromGroup, the buddy's
// feedbag item is deleted from the source group and re-inserted into the
// destination (a buddy's identity includes its GroupID at the protocol level),
// carrying over all of its attribute TLVs. When beforeBuddy is non-empty,
// the buddy is positioned immediately before that buddy in the destination
// group's order; otherwise it is appended. Returns ErrGroupNotFound or
// ErrBuddyNotFound if the source group/buddy or destination group is missing.
func (f *FeedbagList) MoveBuddy(fromGroup, toGroup, buddyName, beforeBuddy string) error {
	src := f.groupByName(fromGroup)
	if src == nil {
		return fmt.Errorf("%w: %q", ErrGroupNotFound, fromGroup)
	}
	srcBuddy := f.buddyItem(src, buddyName)
	if srcBuddy == nil {
		return fmt.Errorf("%w: %q", ErrBuddyNotFound, buddyName)
	}

	dst := src
	if toGroup != "" && toGroup != fromGroup {
		dst = f.groupByName(toGroup)
		if dst == nil {
			return fmt.Errorf("%w: %q", ErrGroupNotFound, toGroup)
		}

		// preserve every attribute TLV (alias, note, auth state, etc.), not
		// just alias/note. Clone so the re-inserted item does not share a
		// backing array with the snapshot queued for deletion.
		attrs := slices.Clone(srcBuddy.TLVList)

		if err := f.DeleteBuddy(fromGroup, buddyName); err != nil {
			return err
		}
		if _, err := f.addBuddyItem(toGroup, buddyName, attrs); err != nil {
			return err
		}
	}

	if beforeBuddy != "" {
		moved := f.buddyItem(dst, buddyName)
		before := f.buddyItem(dst, beforeBuddy)
		if moved != nil && before != nil {
			f.reorderInGroupOrder(dst, moved.ItemID, before.ItemID)
		}
	}

	return nil
}

// SetBuddyAlias sets (or, when alias is empty, clears) the alias attribute on
// every buddy item matching buddyName across all groups. Returns true if at
// least one buddy item was found.
func (f *FeedbagList) SetBuddyAlias(buddyName, alias string) (bool, error) {
	buddies := f.buddyItemsByName(buddyName)
	if len(buddies) == 0 {
		return false, nil
	}
	for _, buddy := range buddies {
		if alias != "" {
			buddy.Set(wire.NewTLVBE(wire.FeedbagAttributesAlias, alias))
		} else {
			buddy.Remove(wire.FeedbagAttributesAlias)
		}
		f.trackUpdate(buddy)
	}
	return true, nil
}

// SetGroupCollapsed sets (or, when collapsed is false, clears) the collapsed
// attribute on a group. An empty groupName targets the unnamed default group.
// Returns ErrGroupNotFound if the group does not exist.
func (f *FeedbagList) SetGroupCollapsed(groupName string, collapsed bool) error {
	group := f.groupByName(groupName)
	if group == nil {
		return fmt.Errorf("%w: %q", ErrGroupNotFound, groupName)
	}
	if collapsed {
		group.Set(wire.NewTLVBE(wire.FeedbagAttributesCollapsed, []byte{}))
	} else {
		group.Remove(wire.FeedbagAttributesCollapsed)
	}
	f.trackUpdate(group)
	return nil
}

// PermitUser upserts a permit-list entry for the given screen name.
func (f *FeedbagList) PermitUser(screenName string) {
	f.upsertItem(wire.FeedbagItem{
		ClassID: wire.FeedbagClassIDPermit,
		Name:    screenName,
	})
}

// DenyUser upserts a deny-list entry for the given screen name.
func (f *FeedbagList) DenyUser(screenName string) {
	f.upsertItem(wire.FeedbagItem{
		ClassID: wire.FeedbagClassIDDeny,
		Name:    screenName,
	})
}

// DeletePermit marks a permit-list entry for deletion.
func (f *FeedbagList) DeletePermit(screenName string) {
	f.deleteItem(wire.FeedbagItem{
		ClassID: wire.FeedbagClassIDPermit,
		Name:    screenName,
	})
}

// DeleteDeny marks a deny-list entry for deletion.
func (f *FeedbagList) DeleteDeny(screenName string) {
	f.deleteItem(wire.FeedbagItem{
		ClassID: wire.FeedbagClassIDDeny,
		Name:    screenName,
	})
}

// AddLinkedScreenName adds a linked screen name.
func (f *FeedbagList) AddLinkedScreenName(screenName string) {
	f.upsertItem(wire.FeedbagItem{
		ClassID: wire.FeedbagClassIdAlInfo,
		Name:    screenName,
	})
}

// DeleteLinkedScreenName deletes a linked screen name.
func (f *FeedbagList) DeleteLinkedScreenName(screenName string) {
	_, deleted := f.deleteItem(wire.FeedbagItem{
		ClassID: wire.FeedbagClassIdAlInfo,
		Name:    screenName,
	})

	if deleted {
		// touch the root group so that the client purges its local
		// buddy list cache
		f.trackUpdate(f.rootGroup())
	}
}

// LinkedScreenNames returns all linked screen names in the feedbag.
func (f *FeedbagList) LinkedScreenNames() []IdentScreenName {
	var names []IdentScreenName
	for _, item := range f.items {
		if item.ClassID == wire.FeedbagClassIdAlInfo {
			names = append(names, NewIdentScreenName(item.Name))
		}
	}
	return names
}

// HasLinkedScreenName returns whether the feedbag has a linked screen name.
func (f *FeedbagList) HasLinkedScreenName(screenName string) bool {
	return slices.ContainsFunc(f.items, func(item *wire.FeedbagItem) bool {
		return item.ClassID == wire.FeedbagClassIdAlInfo && item.Name == NewIdentScreenName(screenName).String()
	})
}

// PendingUpdates returns items that were explicitly upserted via upsertItem
// and items that were implicitly created or modified as side effects of other
// operations (e.g., group order updates from upsertItem, root group updates
// from AddGroup). The pending list is cleared after each call.
func (f *FeedbagList) PendingUpdates() []wire.FeedbagItem {
	var result []wire.FeedbagItem
	for _, p := range f.pendingUpdates {
		result = append(result, *p)
	}
	f.pendingUpdates = nil
	if len(result) == 0 {
		return nil
	}
	return result
}

// PendingDeletes returns items marked for deletion via deleteItem.
// The pending list is cleared after each call.
func (f *FeedbagList) PendingDeletes() []wire.FeedbagItem {
	var result []wire.FeedbagItem
	for _, p := range f.pendingDeletes {
		result = append(result, *p)
	}
	f.pendingDeletes = nil
	return result
}

func (f *FeedbagList) Items() []wire.FeedbagItem {
	var result []wire.FeedbagItem
	for _, p := range f.items {
		result = append(result, *p)
	}
	return result
}

// rootGroup retrieves the root group, creating one if non-existent.
func (f *FeedbagList) rootGroup() *wire.FeedbagItem {
	var root *wire.FeedbagItem
	for _, item := range f.items {
		if item.ClassID == wire.FeedbagClassIdGroup && item.GroupID == 0 {
			root = item
			break
		}
	}
	if root == nil {
		root = &wire.FeedbagItem{ClassID: wire.FeedbagClassIdGroup, GroupID: 0}
		f.items = append(f.items, root)
		f.trackUpdate(root)
	}
	return root
}

// groupByName returns the group item with the given name, or nil if not found.
// The root group (GroupID 0) is never returned; it holds the master group order
// rather than buddies, and an empty name matches the unnamed default buddy group.
func (f *FeedbagList) groupByName(name string) *wire.FeedbagItem {
	for _, item := range f.items {
		if item.ClassID == wire.FeedbagClassIdGroup && item.GroupID != 0 && item.Name == name {
			return item
		}
	}
	return nil
}

// buddyItem returns the buddy item with the given name in the given group, or
// nil if not found. Names are normalized before comparison.
func (f *FeedbagList) buddyItem(group *wire.FeedbagItem, buddyName string) *wire.FeedbagItem {
	want := NewIdentScreenName(buddyName).String()
	for _, item := range f.items {
		if item.ClassID != wire.FeedbagClassIdBuddy || item.GroupID != group.GroupID {
			continue
		}
		if NewIdentScreenName(item.Name).String() == want {
			return item
		}
	}
	return nil
}

// buddyItemsByName returns every buddy item matching buddyName across all
// groups. Names are normalized before comparison.
func (f *FeedbagList) buddyItemsByName(buddyName string) []*wire.FeedbagItem {
	want := NewIdentScreenName(buddyName).String()
	var out []*wire.FeedbagItem
	for _, item := range f.items {
		if item.ClassID != wire.FeedbagClassIdBuddy {
			continue
		}
		if NewIdentScreenName(item.Name).String() == want {
			out = append(out, item)
		}
	}
	return out
}

// reorderInGroupOrder moves itemID so that it sits immediately before
// beforeItemID in the group's order TLV. If beforeItemID is not present, itemID
// is appended. The group is tracked as updated.
func (f *FeedbagList) reorderInGroupOrder(group *wire.FeedbagItem, itemID, beforeItemID uint16) {
	order, ok := group.Uint16SliceBE(wire.FeedbagAttributesOrder)
	if !ok {
		return
	}
	filtered := make([]uint16, 0, len(order))
	for _, id := range order {
		if id != itemID {
			filtered = append(filtered, id)
		}
	}
	insertAt := len(filtered)
	for i, id := range filtered {
		if id == beforeItemID {
			insertAt = i
			break
		}
	}
	reordered := make([]uint16, 0, len(filtered)+1)
	reordered = append(reordered, filtered[:insertAt]...)
	reordered = append(reordered, itemID)
	reordered = append(reordered, filtered[insertAt:]...)

	group.Set(wire.NewTLVBE(wire.FeedbagAttributesOrder, reordered))
	f.trackUpdate(group)
}

// trackUpdate adds item to the pending-updates list if not already present.
func (f *FeedbagList) trackUpdate(item *wire.FeedbagItem) {
	if slices.Contains(f.pendingUpdates, item) {
		return // already tracked
	}
	f.pendingUpdates = append(f.pendingUpdates, item)
}

// itemsMatch reports whether two feedbag items are considered the same for
// upsert/delete (buddy: ClassID, Name, GroupID; others: ClassID and Name).
// Stored items are assumed to have normalized names; the input (b) name is
// normalized for comparison when the class is buddy, permit, or deny.
func (f *FeedbagList) itemsMatch(a, b *wire.FeedbagItem) bool {
	if a.ClassID != b.ClassID {
		return false
	}
	var nameMatch bool
	if hasScreenName(a.ClassID) {
		nameMatch = NewIdentScreenName(a.Name).String() == NewIdentScreenName(b.Name).String()
	} else {
		nameMatch = a.Name == b.Name
	}
	if !nameMatch {
		return false
	}
	if a.ClassID == wire.FeedbagClassIdBuddy {
		return a.GroupID == b.GroupID
	}
	return true
}

// deleteItem removes the first item matching the same criteria as upsertItem:
// buddy items by ClassID, Name, and GroupID; other items by ClassID and Name.
// Returns the deleted item and true if found, or a zero item and false otherwise.
func (f *FeedbagList) deleteItem(item wire.FeedbagItem) (wire.FeedbagItem, bool) {
	for i, existing := range f.items {
		if f.itemsMatch(existing, &item) {
			f.pendingDeletes = append(f.pendingDeletes, existing)
			f.items = append(f.items[:i], f.items[i+1:]...)
			return *existing, true
		}
	}
	return wire.FeedbagItem{}, false
}

// upsertItem updates an existing feedbag item or inserts a new one. Buddy
// items are matched by GroupID, ClassID, and Name; all other items are matched
// by ClassID and Name. When matched, the existing item is replaced in place
// (preserving its ItemID). When no match is found, a new item is inserted with
// an auto-generated ItemID. Names for buddy, permit, and deny items are
// normalized before storage. Returns the stored item and true if a new item
// was inserted, or the existing item and false if it was updated/unchanged.
func (f *FeedbagList) upsertItem(item wire.FeedbagItem) (wire.FeedbagItem, bool) {
	if hasScreenName(item.ClassID) {
		item.Name = NewIdentScreenName(item.Name).String() // normalize name
	}
	for _, existing := range f.items {
		if f.itemsMatch(existing, &item) {
			if !existing.IsEqual(item) {
				item.ItemID = existing.ItemID
				*existing = item
				f.trackUpdate(existing)
			}
			return *existing, false
		}
	}

	item.ItemID = f.genID()
	f.items = append(f.items, &item)
	f.pendingUpdates = append(f.pendingUpdates, &item)
	return item, true
}

// genID generates a unique ID that does not conflict with any existing ItemID
// or GroupID in the list.
func (f *FeedbagList) genID() uint16 {
	num := uint16(f.randInt(math.MaxUint16))
	for itemID := num; itemID != num-1; itemID++ {
		if itemID == 0 {
			continue
		}
		exists := false
		for _, item := range f.items {
			if item.GroupID == itemID || item.ItemID == itemID {
				exists = true
				break
			}
		}
		if !exists {
			return itemID
		}
	}
	return 0
}

// hasScreenName reports whether the feedbag class stores a screen name that
// should be normalized (buddy, permit, deny).
func hasScreenName(classID uint16) bool {
	return classID == wire.FeedbagClassIdBuddy ||
		classID == wire.FeedbagClassIDPermit ||
		classID == wire.FeedbagClassIdAlInfo ||
		classID == wire.FeedbagClassIDDeny
}

Youez - 2016 - github.com/yon3zu
LinuXploit