From 9d08dc994d51298e2d8e75d8ed4ee477312ec53a Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Sat, 7 Mar 2026 15:08:14 +0800 Subject: ref/refname: Add refname validation --- ref/refname/component.go | 88 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 ref/refname/component.go (limited to 'ref/refname/component.go') diff --git a/ref/refname/component.go b/ref/refname/component.go new file mode 100644 index 00000000..f5adba46 --- /dev/null +++ b/ref/refname/component.go @@ -0,0 +1,88 @@ +package refname + +import "strings" + +func checkRefnameComponent(name string, flags *int, sanitized *strings.Builder, fullName string) (int, error) { + var last byte + + componentStart := sanitizedLen(sanitized) + + for i := range len(name) { + ch := name[i] + disp := refnameDisposition(ch) + + if sanitized != nil && disp != 1 { + sanitized.WriteByte(ch) + } + + switch disp { + case 1: + goto out + case 2: + if last == '.' { + if sanitized != nil { + truncateBuilder(sanitized, sanitized.Len()-1) + } else { + return 0, &NameError{Name: fullName, Reason: "name contains '..'"} + } + } + case 3: + if last == '@' { + if sanitized != nil { + overwriteLastByte(sanitized, '-') + } else { + return 0, &NameError{Name: fullName, Reason: "name contains '@{'"} + } + } + case 4: + if sanitized != nil { + overwriteLastByte(sanitized, '-') + } else { + return 0, &NameError{Name: fullName, Reason: "name contains one forbidden character"} + } + case 5: + if *flags&refnameRefspecPattern == 0 { + if sanitized != nil { + overwriteLastByte(sanitized, '-') + } else { + return 0, &NameError{Name: fullName, Reason: "name contains '*'"} + } + } + + *flags &^= refnameRefspecPattern + } + + last = ch + } + +out: + componentLen := strings.IndexByte(name, '/') + + if componentLen < 0 { + componentLen = len(name) + } + + if componentLen == 0 { + return 0, nil + } + + if name[0] == '.' { + if sanitized != nil { + overwriteBuilderAt(sanitized, componentStart, '-') + } else { + return 0, &NameError{Name: fullName, Reason: "component starts with '.'"} + } + } + + if componentLen >= len(lockSuffix) && name[componentLen-len(lockSuffix):componentLen] == lockSuffix { + if sanitized == nil { + return 0, &NameError{Name: fullName, Reason: "component ends with .lock"} + } + + for strings.HasSuffix(sanitized.String(), lockSuffix) { + truncateBuilder(sanitized, sanitized.Len()-len(lockSuffix)) + } + } + + return componentLen, nil +} -- cgit v1.3.1-10-gc9f91