|
1 | 1 | package gitlab |
2 | 2 |
|
3 | 3 | import ( |
| 4 | + "archive/tar" |
| 5 | + "bytes" |
| 6 | + "compress/gzip" |
4 | 7 | "errors" |
5 | 8 | "fmt" |
| 9 | + "io" |
| 10 | + "os" |
| 11 | + "path/filepath" |
6 | 12 | "sheriff/internal/repository" |
| 13 | + "strings" |
7 | 14 | "sync" |
8 | 15 |
|
9 | 16 | "github.com/elliotchance/pie/v2" |
10 | | - "github.com/go-git/go-git/v5" |
11 | | - "github.com/go-git/go-git/v5/plumbing/transport/http" |
12 | 17 | "github.com/rs/zerolog/log" |
13 | 18 | gitlab "gitlab.com/gitlab-org/api/client-go" |
14 | 19 | ) |
@@ -115,17 +120,19 @@ func (s gitlabService) OpenVulnerabilityIssue(project repository.Project, report |
115 | 120 | return |
116 | 121 | } |
117 | 122 |
|
118 | | -func (s gitlabService) Clone(url string, dir string) (err error) { |
119 | | - _, err = git.PlainClone(dir, false, &git.CloneOptions{ |
120 | | - URL: url, |
121 | | - Auth: &http.BasicAuth{ |
122 | | - Username: "N/A", |
123 | | - Password: s.token, |
124 | | - }, |
125 | | - Depth: 1, |
126 | | - }) |
| 123 | +func (s gitlabService) Clone(project repository.Project, dir string) (err error) { |
| 124 | + archiveData, _, err := s.client.Archive(project.ID, &gitlab.ArchiveOptions{}) |
| 125 | + if err != nil { |
| 126 | + return fmt.Errorf("failed to download archive: %w", err) |
| 127 | + } |
| 128 | + |
| 129 | + // Create directory if it doesn't exist |
| 130 | + if err := os.MkdirAll(dir, 0755); err != nil { |
| 131 | + return fmt.Errorf("failed to create directory: %w", err) |
| 132 | + } |
127 | 133 |
|
128 | | - return err |
| 134 | + // Extract archive to directory |
| 135 | + return s.extractTarGz(bytes.NewReader(archiveData), dir) |
129 | 136 | } |
130 | 137 |
|
131 | 138 | // This function receives a list of paths which can be gitlab projects or groups |
@@ -344,3 +351,65 @@ func mapIssuePtr(i *gitlab.Issue) *repository.Issue { |
344 | 351 |
|
345 | 352 | return &issue |
346 | 353 | } |
| 354 | + |
| 355 | +// extractTarGz extracts a tar.gz archive |
| 356 | +func (s gitlabService) extractTarGz(reader io.Reader, destDir string) error { |
| 357 | + // TODO(#52): Move to a separate package when implementing GitHub "clone" through downloading archives |
| 358 | + gzReader, err := gzip.NewReader(reader) |
| 359 | + if err != nil { |
| 360 | + return fmt.Errorf("failed to create gzip reader: %w", err) |
| 361 | + } |
| 362 | + defer gzReader.Close() |
| 363 | + |
| 364 | + tarReader := tar.NewReader(gzReader) |
| 365 | + |
| 366 | + for { |
| 367 | + header, err := tarReader.Next() |
| 368 | + if err == io.EOF { |
| 369 | + break |
| 370 | + } |
| 371 | + if err != nil { |
| 372 | + return fmt.Errorf("failed to read tar header: %w", err) |
| 373 | + } |
| 374 | + |
| 375 | + // Skip the root directory (GitLab archives have a root folder) |
| 376 | + pathParts := strings.Split(header.Name, "/") |
| 377 | + if len(pathParts) <= 1 { |
| 378 | + continue |
| 379 | + } |
| 380 | + |
| 381 | + // Remove the first directory component (the root folder) |
| 382 | + relativePath := strings.Join(pathParts[1:], "/") |
| 383 | + if relativePath == "" { |
| 384 | + continue |
| 385 | + } |
| 386 | + |
| 387 | + targetPath := filepath.Join(destDir, relativePath) |
| 388 | + if !strings.HasPrefix(targetPath, filepath.Clean(destDir)+string(os.PathSeparator)) { |
| 389 | + return fmt.Errorf("content of tar file is trying to write outside of destination directory: %s", relativePath) |
| 390 | + } |
| 391 | + |
| 392 | + switch header.Typeflag { |
| 393 | + case tar.TypeDir: |
| 394 | + if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil { |
| 395 | + return fmt.Errorf("failed to create directory %s: %w", targetPath, err) |
| 396 | + } |
| 397 | + case tar.TypeReg: |
| 398 | + if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { |
| 399 | + return fmt.Errorf("failed to create parent directory for %s: %w", targetPath, err) |
| 400 | + } |
| 401 | + |
| 402 | + file, err := os.OpenFile(targetPath, os.O_CREATE|os.O_RDWR|os.O_TRUNC, os.FileMode(header.Mode)) |
| 403 | + if err != nil { |
| 404 | + return fmt.Errorf("failed to create file %s: %w", targetPath, err) |
| 405 | + } |
| 406 | + defer file.Close() |
| 407 | + |
| 408 | + if _, err := io.Copy(file, tarReader); err != nil { |
| 409 | + return fmt.Errorf("failed to write file %s: %w", targetPath, err) |
| 410 | + } |
| 411 | + } |
| 412 | + } |
| 413 | + |
| 414 | + return nil |
| 415 | +} |
0 commit comments