github.com/pf-qiu/concourse/v6@v6.7.3-0.20201207032516-1f455d73275f/atc/api/cliserver/download.go (about) 1 package cliserver 2 3 import ( 4 "archive/tar" 5 "archive/zip" 6 "compress/gzip" 7 "fmt" 8 "io" 9 "net/http" 10 "os" 11 "path/filepath" 12 "regexp" 13 "strings" 14 ) 15 16 var whitelist = regexp.MustCompile(`^[a-z0-9]+$`) 17 18 func (s *Server) Download(w http.ResponseWriter, r *http.Request) { 19 if s.cliDownloadsDir == "" { 20 http.Error(w, "cli downloads directory not configured", http.StatusNotFound) 21 return 22 } 23 24 platform := strings.ToLower(r.URL.Query().Get("platform")) 25 arch := r.URL.Query().Get("arch") 26 27 if !whitelist.MatchString(platform) || !whitelist.MatchString(arch) { 28 // prevent attempts at accessing arbitrary paths 29 w.WriteHeader(http.StatusBadRequest) 30 return 31 } 32 33 var flyFile, archiveExtension string 34 if platform == "windows" { 35 flyFile = "fly.exe" 36 archiveExtension = "zip" 37 } else { 38 flyFile = "fly" 39 archiveExtension = "tgz" 40 } 41 42 archive := filepath.Join(s.cliDownloadsDir, "fly-"+platform+"-"+arch+"."+archiveExtension) 43 44 switch archiveExtension { 45 case "zip": 46 reader, err := zip.OpenReader(archive) 47 if err != nil { 48 http.Error(w, fmt.Sprintf("failed to open zip archive: %s", err), http.StatusInternalServerError) 49 return 50 } 51 52 defer reader.Close() 53 54 for _, f := range reader.File { 55 if f.Name != flyFile { 56 continue 57 } 58 59 w.Header().Set("Content-Type", "application/octet-stream") 60 w.Header().Set("Content-Length", fmt.Sprintf("%d", f.UncompressedSize64)) 61 w.Header().Set("Content-Disposition", "attachment; filename="+flyFile) 62 w.Header().Set("Last-Modified", f.ModTime().UTC().Format(http.TimeFormat)) 63 64 stream, err := f.Open() 65 if err != nil { 66 http.Error(w, fmt.Sprintf("failed to open fly file in zip: %s", err), http.StatusInternalServerError) 67 return 68 } 69 70 defer stream.Close() 71 72 _, _ = io.Copy(w, stream) 73 74 return 75 } 76 77 case "tgz": 78 tgz, err := os.Open(archive) 79 if err != nil { 80 http.Error(w, fmt.Sprintf("failed to open tgz archive: %s", err), http.StatusInternalServerError) 81 return 82 } 83 84 defer tgz.Close() 85 86 gzReader, err := gzip.NewReader(tgz) 87 if err != nil { 88 http.Error(w, fmt.Sprintf("failed to decompress tgz archive: %s", err), http.StatusInternalServerError) 89 return 90 } 91 92 defer gzReader.Close() 93 94 tarReader := tar.NewReader(gzReader) 95 96 for { 97 f, err := tarReader.Next() 98 if err != nil { 99 if err == io.EOF { 100 break 101 } 102 103 http.Error(w, fmt.Sprintf("failed to seek next header in tgz archive: %s", err), http.StatusInternalServerError) 104 return 105 } 106 107 if f.Name != flyFile { 108 continue 109 } 110 111 w.Header().Set("Content-Type", "application/octet-stream") 112 w.Header().Set("Content-Length", fmt.Sprintf("%d", f.Size)) 113 w.Header().Set("Content-Disposition", "attachment; filename="+flyFile) 114 w.Header().Set("Last-Modified", f.ModTime.UTC().Format(http.TimeFormat)) 115 116 _, _ = io.Copy(w, tarReader) 117 118 return 119 } 120 } 121 122 // normally we return out of the handler upon streaming the file from the 123 // archive; if we got here it's because the archive, for whatever reason, 124 // didn't have the fly binary 125 http.Error(w, "fly executable not found in archive", http.StatusInternalServerError) 126 }