|
1 | 1 | package server |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "bytes" |
4 | 5 | "encoding/json" |
5 | 6 | "fmt" |
6 | 7 | "io" |
@@ -28,6 +29,63 @@ func archiveFilename(filename string) string { |
28 | 29 | return filename |
29 | 30 | } |
30 | 31 |
|
| 32 | +// detectSingleRootDir returns the single top-level directory name if all files |
| 33 | +// in the archive live under one common directory (e.g. GitHub zipballs use |
| 34 | +// "repo-hash/"). Returns "" if there's no single root or the archive is flat. |
| 35 | +func detectSingleRootDir(reader archives.Reader) string { |
| 36 | + files, err := reader.List() |
| 37 | + if err != nil || len(files) == 0 { |
| 38 | + return "" |
| 39 | + } |
| 40 | + |
| 41 | + var root string |
| 42 | + for _, f := range files { |
| 43 | + parts := strings.SplitN(f.Path, "/", 2) |
| 44 | + if len(parts) == 0 { |
| 45 | + continue |
| 46 | + } |
| 47 | + dir := parts[0] |
| 48 | + if root == "" { |
| 49 | + root = dir |
| 50 | + } else if dir != root { |
| 51 | + return "" |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + if root == "" { |
| 56 | + return "" |
| 57 | + } |
| 58 | + return root + "/" |
| 59 | +} |
| 60 | + |
| 61 | +// openArchive opens a cached artifact as an archive reader, auto-detecting |
| 62 | +// and stripping a single top-level directory prefix (like GitHub zipballs). |
| 63 | +// For npm, the hardcoded "package/" prefix takes precedence. |
| 64 | +func openArchive(filename string, content io.Reader, ecosystem string) (archives.Reader, error) { |
| 65 | + fname := archiveFilename(filename) |
| 66 | + |
| 67 | + // npm always uses package/ prefix |
| 68 | + if ecosystem == "npm" { |
| 69 | + return archives.OpenWithPrefix(fname, content, "package/") |
| 70 | + } |
| 71 | + |
| 72 | + // Read content into memory so we can scan then wrap with prefix |
| 73 | + data, err := io.ReadAll(content) |
| 74 | + if err != nil { |
| 75 | + return nil, fmt.Errorf("reading artifact: %w", err) |
| 76 | + } |
| 77 | + |
| 78 | + // Open once to detect root prefix |
| 79 | + probe, err := archives.Open(fname, bytes.NewReader(data)) |
| 80 | + if err != nil { |
| 81 | + return nil, err |
| 82 | + } |
| 83 | + prefix := detectSingleRootDir(probe) |
| 84 | + _ = probe.Close() |
| 85 | + |
| 86 | + return archives.OpenWithPrefix(fname, bytes.NewReader(data), prefix) |
| 87 | +} |
| 88 | + |
31 | 89 | // getStripPrefix returns the path prefix to strip for a given ecosystem. |
32 | 90 | // npm packages wrap content in a "package/" directory. |
33 | 91 | func getStripPrefix(ecosystem string) string { |
@@ -185,9 +243,8 @@ func (s *Server) browseList(w http.ResponseWriter, r *http.Request, ecosystem, n |
185 | 243 | } |
186 | 244 | defer func() { _ = artifactReader.Close() }() |
187 | 245 |
|
188 | | - // Open archive with appropriate prefix stripping |
189 | | - stripPrefix := getStripPrefix(ecosystem) |
190 | | - archiveReader, err := archives.OpenWithPrefix(archiveFilename(cachedArtifact.Filename), artifactReader, stripPrefix) |
| 246 | + // Open archive with auto-detected prefix stripping |
| 247 | + archiveReader, err := openArchive(cachedArtifact.Filename, artifactReader, ecosystem) |
191 | 248 | if err != nil { |
192 | 249 | s.logger.Error("failed to open archive", "error", err, "filename", cachedArtifact.Filename) |
193 | 250 | http.Error(w, "failed to open archive", http.StatusInternalServerError) |
@@ -280,9 +337,8 @@ func (s *Server) browseFile(w http.ResponseWriter, r *http.Request, ecosystem, n |
280 | 337 | } |
281 | 338 | defer func() { _ = artifactReader.Close() }() |
282 | 339 |
|
283 | | - // Open archive with appropriate prefix stripping |
284 | | - stripPrefix := getStripPrefix(ecosystem) |
285 | | - archiveReader, err := archives.OpenWithPrefix(archiveFilename(cachedArtifact.Filename), artifactReader, stripPrefix) |
| 340 | + // Open archive with auto-detected prefix stripping |
| 341 | + archiveReader, err := openArchive(cachedArtifact.Filename, artifactReader, ecosystem) |
286 | 342 | if err != nil { |
287 | 343 | s.logger.Error("failed to open archive", "error", err, "filename", cachedArtifact.Filename) |
288 | 344 | http.Error(w, "failed to open archive", http.StatusInternalServerError) |
@@ -495,17 +551,15 @@ func (s *Server) compareDiff(w http.ResponseWriter, r *http.Request, ecosystem, |
495 | 551 | } |
496 | 552 | defer func() { _ = toReader.Close() }() |
497 | 553 |
|
498 | | - stripPrefix := getStripPrefix(ecosystem) |
499 | | - |
500 | | - fromArchive, err := archives.OpenWithPrefix(archiveFilename(fromArtifact.Filename), fromReader, stripPrefix) |
| 554 | + fromArchive, err := openArchive(fromArtifact.Filename, fromReader, ecosystem) |
501 | 555 | if err != nil { |
502 | 556 | s.logger.Error("failed to open from archive", "error", err) |
503 | 557 | http.Error(w, "failed to open from archive", http.StatusInternalServerError) |
504 | 558 | return |
505 | 559 | } |
506 | 560 | defer func() { _ = fromArchive.Close() }() |
507 | 561 |
|
508 | | - toArchive, err := archives.OpenWithPrefix(archiveFilename(toArtifact.Filename), toReader, stripPrefix) |
| 562 | + toArchive, err := openArchive(toArtifact.Filename, toReader, ecosystem) |
509 | 563 | if err != nil { |
510 | 564 | s.logger.Error("failed to open to archive", "error", err) |
511 | 565 | http.Error(w, "failed to open to archive", http.StatusInternalServerError) |
|
0 commit comments