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  }