ref: 2a5c12f0152240f54d01653b70b7605103393afd
dir: /network/protocol/v0v1/server/receivepack/capabilities.go/
package receivepack
import (
"fmt"
"slices"
"strings"
objectid "codeberg.org/lindenii/furgit/object/id"
)
// Capabilities describes one receive-pack capability set.
type Capabilities struct {
ReportStatus bool
ReportStatusV2 bool
DeleteRefs bool
SideBand64K bool
Quiet bool
Atomic bool
OfsDelta bool
PushOptions bool
PushCertNonce string
ObjectFormat objectid.Algorithm
SessionID string
Agent string
}
// Normalize returns one normalized copy of caps.
func (caps Capabilities) Normalize(defaultAlgorithm objectid.Algorithm) Capabilities {
if caps.ObjectFormat == objectid.AlgorithmUnknown {
caps.ObjectFormat = defaultAlgorithm
}
return caps
}
// Tokens returns capabilities in Git advertisement order.
func (caps Capabilities) Tokens(defaultAlgorithm objectid.Algorithm) []string {
caps = caps.Normalize(defaultAlgorithm)
tokens := make([]string, 0, 11)
if caps.ReportStatus {
tokens = append(tokens, "report-status")
}
if caps.ReportStatusV2 {
tokens = append(tokens, "report-status-v2")
}
if caps.DeleteRefs {
tokens = append(tokens, "delete-refs")
}
if caps.SideBand64K {
tokens = append(tokens, "side-band-64k")
}
if caps.Quiet {
tokens = append(tokens, "quiet")
}
if caps.Atomic {
tokens = append(tokens, "atomic")
}
if caps.OfsDelta {
tokens = append(tokens, "ofs-delta")
}
if caps.PushCertNonce != "" {
tokens = append(tokens, "push-cert="+caps.PushCertNonce)
}
if caps.PushOptions {
tokens = append(tokens, "push-options")
}
if caps.SessionID != "" {
tokens = append(tokens, "session-id="+caps.SessionID)
}
if caps.ObjectFormat != objectid.AlgorithmUnknown {
tokens = append(tokens, "object-format="+caps.ObjectFormat.String())
}
if caps.Agent != "" {
tokens = append(tokens, "agent="+caps.Agent)
}
return tokens
}
func (caps Capabilities) supportsToken(token string, defaultAlgorithm objectid.Algorithm) bool {
name, value, _ := strings.Cut(token, "=")
switch name {
case "report-status":
return caps.ReportStatus && value == ""
case "report-status-v2":
return caps.ReportStatusV2 && value == ""
case "delete-refs":
return caps.DeleteRefs && value == ""
case "side-band-64k":
return caps.SideBand64K && value == ""
case "quiet":
return caps.Quiet && value == ""
case "atomic":
return caps.Atomic && value == ""
case "ofs-delta":
return caps.OfsDelta && value == ""
case "push-options":
return caps.PushOptions && value == ""
case "push-cert":
return caps.PushCertNonce != "" && value != ""
case "object-format":
if value == "" {
return false
}
algo, ok := objectid.ParseAlgorithm(value)
return ok && algo == caps.Normalize(defaultAlgorithm).ObjectFormat
case "session-id":
return caps.SessionID != "" && value != ""
case "agent":
return caps.Agent != "" && value != ""
default:
return false
}
}
func parseCapabilityList(s string) ([]string, error) {
s = strings.TrimSuffix(s, "\n")
if s == "" {
return nil, nil
}
tokens := strings.Fields(s)
if slices.Contains(tokens, "") {
return nil, &ProtocolError{Reason: "empty capability token"}
}
return tokens, nil
}
func parseRequestedCapabilities(
tokens []string,
supported Capabilities,
defaultAlgorithm objectid.Algorithm,
) (Capabilities, error) {
var requested Capabilities
requested.ObjectFormat = defaultAlgorithm
for _, token := range tokens {
if !supported.supportsToken(token, defaultAlgorithm) {
return Capabilities{}, &ProtocolError{
Reason: fmt.Sprintf("unsupported capability %q", token),
}
}
name, value, _ := strings.Cut(token, "=")
switch name {
case "report-status":
requested.ReportStatus = true
case "report-status-v2":
requested.ReportStatusV2 = true
case "delete-refs":
requested.DeleteRefs = true
case "side-band-64k":
requested.SideBand64K = true
case "quiet":
requested.Quiet = true
case "atomic":
requested.Atomic = true
case "ofs-delta":
requested.OfsDelta = true
case "push-options":
requested.PushOptions = true
case "push-cert":
requested.PushCertNonce = value
case "object-format":
algo, _ := objectid.ParseAlgorithm(value)
requested.ObjectFormat = algo
case "session-id":
requested.SessionID = value
case "agent":
requested.Agent = value
}
}
return requested, nil
}