98 lines
2.6 KiB
Go
98 lines
2.6 KiB
Go
package archive
|
|
|
|
import (
|
|
"strings"
|
|
)
|
|
|
|
// Resolve parses the .archive request filename and returns the server-relative
|
|
// path (no leading slash) of the resolved file within the named project. The
|
|
// caller serves that file in place — the .archive URL is intentionally stable
|
|
// across revisions so external links like .archive/<tracking>.html#section
|
|
// keep tracking the latest copy without exposing the per-transmittal URL.
|
|
//
|
|
// Project is the top-level segment of the .archive contextPath
|
|
// (/<project>/.../.archive/<filename>). An empty project — i.e. a request
|
|
// against /.archive/ at the very root — returns ("", false): stable refs
|
|
// must be project-rooted to avoid cross-project tracking-number collisions.
|
|
//
|
|
// Supported URL filename patterns (after stripping .html suffix):
|
|
// - trackingNumber → highest base revision of trackingNumber
|
|
// - trackingNumber_rev → base revision file for rev
|
|
// - trackingNumber_rev+C1 → modifier file (C1, B1, N1, Q1)
|
|
//
|
|
// Returns ("", false) if project is empty, the filename cannot be parsed, or
|
|
// no match exists in the project.
|
|
func Resolve(idx *Index, project, filename string) (string, bool) {
|
|
if project == "" {
|
|
return "", false
|
|
}
|
|
|
|
// Strip .html suffix
|
|
stem := strings.TrimSuffix(filename, ".html")
|
|
if stem == filename {
|
|
// No .html suffix — not a valid archive request
|
|
return "", false
|
|
}
|
|
|
|
idx.mu.RLock()
|
|
defer idx.mu.RUnlock()
|
|
|
|
pe, ok := idx.ByProject[project]
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
|
|
// Try to split off revision part (last _ segment)
|
|
lastUnderscore := strings.LastIndex(stem, "_")
|
|
if lastUnderscore < 0 {
|
|
// No underscore — treat entire stem as tracking number
|
|
tracking := stem
|
|
te, ok := pe.ByTracking[tracking]
|
|
if !ok || te.HighestBaseRev == "" {
|
|
return "", false
|
|
}
|
|
re, ok := te.ByRevision[te.HighestBaseRev]
|
|
if !ok || re.BasePath == "" {
|
|
return "", false
|
|
}
|
|
return re.BasePath, true
|
|
}
|
|
|
|
tracking := stem[:lastUnderscore]
|
|
revPart := stem[lastUnderscore+1:]
|
|
|
|
// Split revPart on "+" to separate baseRev from modifier
|
|
plusIdx := strings.Index(revPart, "+")
|
|
var baseRev, modifier string
|
|
if plusIdx < 0 {
|
|
baseRev = revPart
|
|
modifier = ""
|
|
} else {
|
|
baseRev = revPart[:plusIdx]
|
|
modifier = revPart[plusIdx+1:]
|
|
}
|
|
|
|
te, ok := pe.ByTracking[tracking]
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
|
|
re, ok := te.ByRevision[baseRev]
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
|
|
if modifier == "" {
|
|
if re.BasePath == "" {
|
|
return "", false
|
|
}
|
|
return re.BasePath, true
|
|
}
|
|
|
|
// Modifier lookup
|
|
path, ok := re.Modifiers[modifier]
|
|
if !ok {
|
|
return "", false
|
|
}
|
|
return path, true
|
|
}
|