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