1// Copyright 2019 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package main
16
17import (
18	"archive/zip"
19	"context"
20	"fmt"
21	"hash/crc32"
22	"io"
23	"io/ioutil"
24	"os"
25	"path/filepath"
26)
27
28// ZipArtifact represents a zip file that may be local or remote.
29type ZipArtifact interface {
30	// Files returns the list of files contained in the zip file.
31	Files() ([]*ZipArtifactFile, error)
32
33	// Close closes the zip file artifact.
34	Close()
35}
36
37// localZipArtifact is a handle to a local zip file artifact.
38type localZipArtifact struct {
39	zr    *zip.ReadCloser
40	files []*ZipArtifactFile
41}
42
43// NewLocalZipArtifact returns a ZipArtifact for a local zip file..
44func NewLocalZipArtifact(name string) (ZipArtifact, error) {
45	zr, err := zip.OpenReader(name)
46	if err != nil {
47		return nil, err
48	}
49
50	var files []*ZipArtifactFile
51	for _, zf := range zr.File {
52		files = append(files, &ZipArtifactFile{zf})
53	}
54
55	return &localZipArtifact{
56		zr:    zr,
57		files: files,
58	}, nil
59}
60
61// Files returns the list of files contained in the local zip file artifact.
62func (z *localZipArtifact) Files() ([]*ZipArtifactFile, error) {
63	return z.files, nil
64}
65
66// Close closes the buffered reader of the local zip file artifact.
67func (z *localZipArtifact) Close() {
68	z.zr.Close()
69}
70
71// ZipArtifactFile contains a zip.File handle to the data inside the remote *-target_files-*.zip
72// build artifact.
73type ZipArtifactFile struct {
74	*zip.File
75}
76
77// Extract begins extract a file from inside a ZipArtifact.  It returns an
78// ExtractedZipArtifactFile handle.
79func (zf *ZipArtifactFile) Extract(ctx context.Context, dir string,
80	limiter chan bool) *ExtractedZipArtifactFile {
81
82	d := &ExtractedZipArtifactFile{
83		initCh: make(chan struct{}),
84	}
85
86	go func() {
87		defer close(d.initCh)
88		limiter <- true
89		defer func() { <-limiter }()
90
91		zr, err := zf.Open()
92		if err != nil {
93			d.err = err
94			return
95		}
96		defer zr.Close()
97
98		crc := crc32.NewIEEE()
99		r := io.TeeReader(zr, crc)
100
101		if filepath.Clean(zf.Name) != zf.Name {
102			d.err = fmt.Errorf("invalid filename %q", zf.Name)
103			return
104		}
105		path := filepath.Join(dir, zf.Name)
106
107		err = os.MkdirAll(filepath.Dir(path), 0777)
108		if err != nil {
109			d.err = err
110			return
111		}
112
113		err = os.Remove(path)
114		if err != nil && !os.IsNotExist(err) {
115			d.err = err
116			return
117		}
118
119		if zf.Mode().IsRegular() {
120			w, err := os.OpenFile(path, os.O_CREATE|os.O_WRONLY, zf.Mode())
121			if err != nil {
122				d.err = err
123				return
124			}
125			defer w.Close()
126
127			_, err = io.Copy(w, r)
128			if err != nil {
129				d.err = err
130				return
131			}
132		} else if zf.Mode()&os.ModeSymlink != 0 {
133			target, err := ioutil.ReadAll(r)
134			if err != nil {
135				d.err = err
136				return
137			}
138
139			err = os.Symlink(string(target), path)
140			if err != nil {
141				d.err = err
142				return
143			}
144		} else {
145			d.err = fmt.Errorf("unknown mode %q", zf.Mode())
146			return
147		}
148
149		if crc.Sum32() != zf.CRC32 {
150			d.err = fmt.Errorf("crc mismatch for %v", zf.Name)
151			return
152		}
153
154		d.path = path
155	}()
156
157	return d
158}
159
160// ExtractedZipArtifactFile is a handle to a downloaded file from a remoteZipArtifact.  The download
161// may still be in progress, and will be complete with Path() returns.
162type ExtractedZipArtifactFile struct {
163	initCh chan struct{}
164	err    error
165
166	path string
167}
168
169// Path returns the path to the downloaded file and any errors that occurred during the download.
170// It will block until the download is complete.
171func (d *ExtractedZipArtifactFile) Path() (string, error) {
172	<-d.initCh
173	return d.path, d.err
174}
175