mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 03:18:24 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			243 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			243 lines
		
	
	
		
			5.5 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
| package msgp
 | |
| 
 | |
| import (
 | |
| 	"math"
 | |
| )
 | |
| 
 | |
| // Locate returns a []byte pointing to the field
 | |
| // in a messagepack map with the provided key. (The returned []byte
 | |
| // points to a sub-slice of 'raw'; Locate does no allocations.) If the
 | |
| // key doesn't exist in the map, a zero-length []byte will be returned.
 | |
| func Locate(key string, raw []byte) []byte {
 | |
| 	s, n := locate(raw, key)
 | |
| 	return raw[s:n]
 | |
| }
 | |
| 
 | |
| // Replace takes a key ("key") in a messagepack map ("raw")
 | |
| // and replaces its value with the one provided and returns
 | |
| // the new []byte. The returned []byte may point to the same
 | |
| // memory as "raw". Replace makes no effort to evaluate the validity
 | |
| // of the contents of 'val'. It may use up to the full capacity of 'raw.'
 | |
| // Replace returns 'nil' if the field doesn't exist or if the object in 'raw'
 | |
| // is not a map.
 | |
| func Replace(key string, raw []byte, val []byte) []byte {
 | |
| 	start, end := locate(raw, key)
 | |
| 	if start == end {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return replace(raw, start, end, val, true)
 | |
| }
 | |
| 
 | |
| // CopyReplace works similarly to Replace except that the returned
 | |
| // byte slice does not point to the same memory as 'raw'. CopyReplace
 | |
| // returns 'nil' if the field doesn't exist or 'raw' isn't a map.
 | |
| func CopyReplace(key string, raw []byte, val []byte) []byte {
 | |
| 	start, end := locate(raw, key)
 | |
| 	if start == end {
 | |
| 		return nil
 | |
| 	}
 | |
| 	return replace(raw, start, end, val, false)
 | |
| }
 | |
| 
 | |
| // Remove removes a key-value pair from 'raw'. It returns
 | |
| // 'raw' unchanged if the key didn't exist.
 | |
| func Remove(key string, raw []byte) []byte {
 | |
| 	start, end := locateKV(raw, key)
 | |
| 	if start == end {
 | |
| 		return raw
 | |
| 	}
 | |
| 	raw = raw[:start+copy(raw[start:], raw[end:])]
 | |
| 	return resizeMap(raw, -1)
 | |
| }
 | |
| 
 | |
| // HasKey returns whether the map in 'raw' has
 | |
| // a field with key 'key'
 | |
| func HasKey(key string, raw []byte) bool {
 | |
| 	sz, bts, err := ReadMapHeaderBytes(raw)
 | |
| 	if err != nil {
 | |
| 		return false
 | |
| 	}
 | |
| 	var field []byte
 | |
| 	for i := uint32(0); i < sz; i++ {
 | |
| 		field, bts, err = ReadStringZC(bts)
 | |
| 		if err != nil {
 | |
| 			return false
 | |
| 		}
 | |
| 		if UnsafeString(field) == key {
 | |
| 			return true
 | |
| 		}
 | |
| 	}
 | |
| 	return false
 | |
| }
 | |
| 
 | |
| func replace(raw []byte, start int, end int, val []byte, inplace bool) []byte {
 | |
| 	ll := end - start // length of segment to replace
 | |
| 	lv := len(val)
 | |
| 
 | |
| 	if inplace {
 | |
| 		extra := lv - ll
 | |
| 
 | |
| 		// fastest case: we're doing
 | |
| 		// a 1:1 replacement
 | |
| 		if extra == 0 {
 | |
| 			copy(raw[start:], val)
 | |
| 			return raw
 | |
| 
 | |
| 		} else if extra < 0 {
 | |
| 			// 'val' smaller than replaced value
 | |
| 			// copy in place and shift back
 | |
| 
 | |
| 			x := copy(raw[start:], val)
 | |
| 			y := copy(raw[start+x:], raw[end:])
 | |
| 			return raw[:start+x+y]
 | |
| 
 | |
| 		} else if extra < cap(raw)-len(raw) {
 | |
| 			// 'val' less than (cap-len) extra bytes
 | |
| 			// copy in place and shift forward
 | |
| 			raw = raw[0 : len(raw)+extra]
 | |
| 			// shift end forward
 | |
| 			copy(raw[end+extra:], raw[end:])
 | |
| 			copy(raw[start:], val)
 | |
| 			return raw
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	// we have to allocate new space
 | |
| 	out := make([]byte, len(raw)+len(val)-ll)
 | |
| 	x := copy(out, raw[:start])
 | |
| 	y := copy(out[x:], val)
 | |
| 	copy(out[x+y:], raw[end:])
 | |
| 	return out
 | |
| }
 | |
| 
 | |
| // locate does a naive O(n) search for the map key; returns start, end
 | |
| // (returns 0,0 on error)
 | |
| func locate(raw []byte, key string) (start int, end int) {
 | |
| 	var (
 | |
| 		sz    uint32
 | |
| 		bts   []byte
 | |
| 		field []byte
 | |
| 		err   error
 | |
| 	)
 | |
| 	sz, bts, err = ReadMapHeaderBytes(raw)
 | |
| 	if err != nil {
 | |
| 		return
 | |
| 	}
 | |
| 
 | |
| 	// loop and locate field
 | |
| 	for i := uint32(0); i < sz; i++ {
 | |
| 		field, bts, err = ReadStringZC(bts)
 | |
| 		if err != nil {
 | |
| 			return 0, 0
 | |
| 		}
 | |
| 		if UnsafeString(field) == key {
 | |
| 			// start location
 | |
| 			l := len(raw)
 | |
| 			start = l - len(bts)
 | |
| 			bts, err = Skip(bts)
 | |
| 			if err != nil {
 | |
| 				return 0, 0
 | |
| 			}
 | |
| 			end = l - len(bts)
 | |
| 			return
 | |
| 		}
 | |
| 		bts, err = Skip(bts)
 | |
| 		if err != nil {
 | |
| 			return 0, 0
 | |
| 		}
 | |
| 	}
 | |
| 	return 0, 0
 | |
| }
 | |
| 
 | |
| // locate key AND value
 | |
| func locateKV(raw []byte, key string) (start int, end int) {
 | |
| 	var (
 | |
| 		sz    uint32
 | |
| 		bts   []byte
 | |
| 		field []byte
 | |
| 		err   error
 | |
| 	)
 | |
| 	sz, bts, err = ReadMapHeaderBytes(raw)
 | |
| 	if err != nil {
 | |
| 		return 0, 0
 | |
| 	}
 | |
| 
 | |
| 	for i := uint32(0); i < sz; i++ {
 | |
| 		tmp := len(bts)
 | |
| 		field, bts, err = ReadStringZC(bts)
 | |
| 		if err != nil {
 | |
| 			return 0, 0
 | |
| 		}
 | |
| 		if UnsafeString(field) == key {
 | |
| 			start = len(raw) - tmp
 | |
| 			bts, err = Skip(bts)
 | |
| 			if err != nil {
 | |
| 				return 0, 0
 | |
| 			}
 | |
| 			end = len(raw) - len(bts)
 | |
| 			return
 | |
| 		}
 | |
| 		bts, err = Skip(bts)
 | |
| 		if err != nil {
 | |
| 			return 0, 0
 | |
| 		}
 | |
| 	}
 | |
| 	return 0, 0
 | |
| }
 | |
| 
 | |
| // delta is delta on map size
 | |
| func resizeMap(raw []byte, delta int64) []byte {
 | |
| 	var sz int64
 | |
| 	switch raw[0] {
 | |
| 	case mmap16:
 | |
| 		sz = int64(big.Uint16(raw[1:]))
 | |
| 		if sz+delta <= math.MaxUint16 {
 | |
| 			big.PutUint16(raw[1:], uint16(sz+delta))
 | |
| 			return raw
 | |
| 		}
 | |
| 		if cap(raw)-len(raw) >= 2 {
 | |
| 			raw = raw[0 : len(raw)+2]
 | |
| 			copy(raw[5:], raw[3:])
 | |
| 			raw[0] = mmap32
 | |
| 			big.PutUint32(raw[1:], uint32(sz+delta))
 | |
| 			return raw
 | |
| 		}
 | |
| 		n := make([]byte, 0, len(raw)+5)
 | |
| 		n = AppendMapHeader(n, uint32(sz+delta))
 | |
| 		return append(n, raw[3:]...)
 | |
| 
 | |
| 	case mmap32:
 | |
| 		sz = int64(big.Uint32(raw[1:]))
 | |
| 		big.PutUint32(raw[1:], uint32(sz+delta))
 | |
| 		return raw
 | |
| 
 | |
| 	default:
 | |
| 		sz = int64(rfixmap(raw[0]))
 | |
| 		if sz+delta < 16 {
 | |
| 			raw[0] = wfixmap(uint8(sz + delta))
 | |
| 			return raw
 | |
| 		} else if sz+delta <= math.MaxUint16 {
 | |
| 			if cap(raw)-len(raw) >= 2 {
 | |
| 				raw = raw[0 : len(raw)+2]
 | |
| 				copy(raw[3:], raw[1:])
 | |
| 				raw[0] = mmap16
 | |
| 				big.PutUint16(raw[1:], uint16(sz+delta))
 | |
| 				return raw
 | |
| 			}
 | |
| 			n := make([]byte, 0, len(raw)+5)
 | |
| 			n = AppendMapHeader(n, uint32(sz+delta))
 | |
| 			return append(n, raw[1:]...)
 | |
| 		}
 | |
| 		if cap(raw)-len(raw) >= 4 {
 | |
| 			raw = raw[0 : len(raw)+4]
 | |
| 			copy(raw[5:], raw[1:])
 | |
| 			raw[0] = mmap32
 | |
| 			big.PutUint32(raw[1:], uint32(sz+delta))
 | |
| 			return raw
 | |
| 		}
 | |
| 		n := make([]byte, 0, len(raw)+5)
 | |
| 		n = AppendMapHeader(n, uint32(sz+delta))
 | |
| 		return append(n, raw[1:]...)
 | |
| 	}
 | |
| }
 |