• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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	"bufio"
19	"bytes"
20	"encoding/json"
21	"io"
22	"os"
23	"regexp"
24	"strings"
25	"unicode"
26)
27
28type jsonWhitelist struct {
29	Paths               []string
30	IgnoreMatchingLines []string
31}
32
33type whitelist struct {
34	path                string
35	ignoreMatchingLines []string
36}
37
38func parseWhitelists(whitelists []string, whitelistFiles []string) ([]whitelist, error) {
39	var ret []whitelist
40
41	add := func(path string, ignoreMatchingLines []string) {
42		for _, x := range ret {
43			if x.path == path {
44				x.ignoreMatchingLines = append(x.ignoreMatchingLines, ignoreMatchingLines...)
45				return
46			}
47		}
48
49		ret = append(ret, whitelist{
50			path:                path,
51			ignoreMatchingLines: ignoreMatchingLines,
52		})
53	}
54
55	for _, file := range whitelistFiles {
56		newWhitelists, err := parseWhitelistFile(file)
57		if err != nil {
58			return nil, err
59		}
60
61		for _, w := range newWhitelists {
62			add(w.path, w.ignoreMatchingLines)
63		}
64	}
65
66	for _, s := range whitelists {
67		colon := strings.IndexRune(s, ':')
68		var ignoreMatchingLines []string
69		if colon >= 0 {
70			ignoreMatchingLines = []string{s[colon+1:]}
71		}
72		add(s, ignoreMatchingLines)
73	}
74
75	return ret, nil
76}
77
78func parseWhitelistFile(file string) ([]whitelist, error) {
79	r, err := os.Open(file)
80	if err != nil {
81		return nil, err
82	}
83	defer r.Close()
84
85	d := json.NewDecoder(newJSONCommentStripper(r))
86
87	var jsonWhitelists []jsonWhitelist
88
89	err = d.Decode(&jsonWhitelists)
90
91	var whitelists []whitelist
92	for _, w := range jsonWhitelists {
93		for _, p := range w.Paths {
94			whitelists = append(whitelists, whitelist{
95				path:                p,
96				ignoreMatchingLines: w.IgnoreMatchingLines,
97			})
98		}
99	}
100
101	return whitelists, err
102}
103
104func filterModifiedPaths(l [][2]*ZipArtifactFile, whitelists []whitelist) ([][2]*ZipArtifactFile, error) {
105outer:
106	for i := 0; i < len(l); i++ {
107		for _, w := range whitelists {
108			if match, err := Match(w.path, l[i][0].Name); err != nil {
109				return l, err
110			} else if match {
111				if match, err := diffIgnoringMatchingLines(l[i][0], l[i][1], w.ignoreMatchingLines); err != nil {
112					return l, err
113				} else if match || len(w.ignoreMatchingLines) == 0 {
114					l = append(l[:i], l[i+1:]...)
115					i--
116				}
117				continue outer
118			}
119		}
120	}
121
122	if len(l) == 0 {
123		l = nil
124	}
125
126	return l, nil
127}
128
129func filterNewPaths(l []*ZipArtifactFile, whitelists []whitelist) ([]*ZipArtifactFile, error) {
130outer:
131	for i := 0; i < len(l); i++ {
132		for _, w := range whitelists {
133			if match, err := Match(w.path, l[i].Name); err != nil {
134				return l, err
135			} else if match && len(w.ignoreMatchingLines) == 0 {
136				l = append(l[:i], l[i+1:]...)
137				i--
138			}
139			continue outer
140		}
141	}
142
143	if len(l) == 0 {
144		l = nil
145	}
146
147	return l, nil
148}
149
150func diffIgnoringMatchingLines(a *ZipArtifactFile, b *ZipArtifactFile, ignoreMatchingLines []string) (match bool, err error) {
151	lineMatchesIgnores := func(b []byte) (bool, error) {
152		for _, m := range ignoreMatchingLines {
153			if match, err := regexp.Match(m, b); err != nil {
154				return false, err
155			} else if match {
156				return match, nil
157			}
158		}
159		return false, nil
160	}
161
162	filter := func(z *ZipArtifactFile) ([]byte, error) {
163		var ret []byte
164
165		r, err := z.Open()
166		if err != nil {
167			return nil, err
168		}
169		s := bufio.NewScanner(r)
170
171		for s.Scan() {
172			if match, err := lineMatchesIgnores(s.Bytes()); err != nil {
173				return nil, err
174			} else if !match {
175				ret = append(ret, "\n"...)
176				ret = append(ret, s.Bytes()...)
177			}
178		}
179
180		return ret, nil
181	}
182
183	bufA, err := filter(a)
184	if err != nil {
185		return false, err
186	}
187	bufB, err := filter(b)
188	if err != nil {
189		return false, err
190	}
191
192	return bytes.Compare(bufA, bufB) == 0, nil
193}
194
195func applyWhitelists(diff zipDiff, whitelists []whitelist) (zipDiff, error) {
196	var err error
197
198	diff.modified, err = filterModifiedPaths(diff.modified, whitelists)
199	if err != nil {
200		return diff, err
201	}
202	diff.onlyInA, err = filterNewPaths(diff.onlyInA, whitelists)
203	if err != nil {
204		return diff, err
205	}
206	diff.onlyInB, err = filterNewPaths(diff.onlyInB, whitelists)
207	if err != nil {
208		return diff, err
209	}
210
211	return diff, nil
212}
213
214func newJSONCommentStripper(r io.Reader) *jsonCommentStripper {
215	return &jsonCommentStripper{
216		r: bufio.NewReader(r),
217	}
218}
219
220type jsonCommentStripper struct {
221	r   *bufio.Reader
222	b   []byte
223	err error
224}
225
226func (j *jsonCommentStripper) Read(buf []byte) (int, error) {
227	for len(j.b) == 0 {
228		if j.err != nil {
229			return 0, j.err
230		}
231
232		j.b, j.err = j.r.ReadBytes('\n')
233
234		if isComment(j.b) {
235			j.b = nil
236		}
237	}
238
239	n := copy(buf, j.b)
240	j.b = j.b[n:]
241	return n, nil
242}
243
244var commentPrefix = []byte("//")
245
246func isComment(b []byte) bool {
247	for len(b) > 0 && unicode.IsSpace(rune(b[0])) {
248		b = b[1:]
249	}
250	return bytes.HasPrefix(b, commentPrefix)
251}
252