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 allowList struct { 29 path string 30 ignoreMatchingLines []string 31} 32 33func parseAllowLists(allowLists []string, allowListFiles []string) ([]allowList, error) { 34 var ret []allowList 35 36 add := func(path string, ignoreMatchingLines []string) { 37 for _, x := range ret { 38 if x.path == path { 39 x.ignoreMatchingLines = append(x.ignoreMatchingLines, ignoreMatchingLines...) 40 return 41 } 42 } 43 44 ret = append(ret, allowList{ 45 path: path, 46 ignoreMatchingLines: ignoreMatchingLines, 47 }) 48 } 49 50 for _, file := range allowListFiles { 51 newAllowlists, err := parseAllowListFile(file) 52 if err != nil { 53 return nil, err 54 } 55 56 for _, w := range newAllowlists { 57 add(w.path, w.ignoreMatchingLines) 58 } 59 } 60 61 for _, s := range allowLists { 62 colon := strings.IndexRune(s, ':') 63 var ignoreMatchingLines []string 64 if colon >= 0 { 65 ignoreMatchingLines = []string{s[colon+1:]} 66 } 67 add(s, ignoreMatchingLines) 68 } 69 70 return ret, nil 71} 72 73func parseAllowListFile(file string) ([]allowList, error) { 74 r, err := os.Open(file) 75 if err != nil { 76 return nil, err 77 } 78 defer r.Close() 79 80 d := json.NewDecoder(newJSONCommentStripper(r)) 81 82 var jsonAllowLists []struct { 83 Paths []string 84 IgnoreMatchingLines []string 85 } 86 87 if err := d.Decode(&jsonAllowLists); err != nil { 88 return nil, err 89 } 90 91 var allowLists []allowList 92 for _, w := range jsonAllowLists { 93 for _, p := range w.Paths { 94 allowLists = append(allowLists, allowList{ 95 path: p, 96 ignoreMatchingLines: w.IgnoreMatchingLines, 97 }) 98 } 99 } 100 101 return allowLists, err 102} 103 104func filterModifiedPaths(l [][2]*ZipArtifactFile, allowLists []allowList) ([][2]*ZipArtifactFile, error) { 105outer: 106 for i := 0; i < len(l); i++ { 107 for _, w := range allowLists { 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, allowLists []allowList) ([]*ZipArtifactFile, error) { 130outer: 131 for i := 0; i < len(l); i++ { 132 for _, w := range allowLists { 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 applyAllowLists(diff zipDiff, allowLists []allowList) (zipDiff, error) { 196 var err error 197 198 diff.modified, err = filterModifiedPaths(diff.modified, allowLists) 199 if err != nil { 200 return diff, err 201 } 202 diff.onlyInA, err = filterNewPaths(diff.onlyInA, allowLists) 203 if err != nil { 204 return diff, err 205 } 206 diff.onlyInB, err = filterNewPaths(diff.onlyInB, allowLists) 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