1// Copyright 2017 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 jar 16 17import ( 18 "bytes" 19 "fmt" 20 "io" 21 "os" 22 "strings" 23 "text/scanner" 24 "time" 25 "unicode" 26 27 "android/soong/third_party/zip" 28) 29 30const ( 31 MetaDir = "META-INF/" 32 ManifestFile = MetaDir + "MANIFEST.MF" 33 ModuleInfoClass = "module-info.class" 34) 35 36var DefaultTime = time.Date(2008, 1, 1, 0, 0, 0, 0, time.UTC) 37 38var MetaDirExtra = [2]byte{0xca, 0xfe} 39 40// EntryNamesLess tells whether <filepathA> should precede <filepathB> in 41// the order of files with a .jar 42func EntryNamesLess(filepathA string, filepathB string) (less bool) { 43 diff := index(filepathA) - index(filepathB) 44 if diff == 0 { 45 return filepathA < filepathB 46 } 47 return diff < 0 48} 49 50// Treats trailing * as a prefix match 51func patternMatch(pattern, name string) bool { 52 if strings.HasSuffix(pattern, "*") { 53 return strings.HasPrefix(name, strings.TrimSuffix(pattern, "*")) 54 } else { 55 return name == pattern 56 } 57} 58 59var jarOrder = []string{ 60 MetaDir, 61 ManifestFile, 62 MetaDir + "*", 63 "*", 64} 65 66func index(name string) int { 67 for i, pattern := range jarOrder { 68 if patternMatch(pattern, name) { 69 return i 70 } 71 } 72 panic(fmt.Errorf("file %q did not match any pattern", name)) 73} 74 75func MetaDirFileHeader() *zip.FileHeader { 76 dirHeader := &zip.FileHeader{ 77 Name: MetaDir, 78 Extra: []byte{MetaDirExtra[1], MetaDirExtra[0], 0, 0}, 79 } 80 dirHeader.SetMode(0755 | os.ModeDir) 81 dirHeader.SetModTime(DefaultTime) 82 83 return dirHeader 84} 85 86// Create a manifest zip header and contents using the provided contents if any. 87func ManifestFileContents(contents []byte) (*zip.FileHeader, []byte, error) { 88 b, err := manifestContents(contents) 89 if err != nil { 90 return nil, nil, err 91 } 92 93 fh := &zip.FileHeader{ 94 Name: ManifestFile, 95 Method: zip.Store, 96 UncompressedSize64: uint64(len(b)), 97 } 98 fh.SetMode(0644) 99 fh.SetModTime(DefaultTime) 100 101 return fh, b, nil 102} 103 104// Create manifest contents, using the provided contents if any. 105func manifestContents(contents []byte) ([]byte, error) { 106 manifestMarker := []byte("Manifest-Version:") 107 header := append(manifestMarker, []byte(" 1.0\nCreated-By: soong_zip\n")...) 108 109 var finalBytes []byte 110 if !bytes.Contains(contents, manifestMarker) { 111 finalBytes = append(append(header, contents...), byte('\n')) 112 } else { 113 finalBytes = contents 114 } 115 116 return finalBytes, nil 117} 118 119var javaIgnorableIdentifier = &unicode.RangeTable{ 120 R16: []unicode.Range16{ 121 {0x00, 0x08, 1}, 122 {0x0e, 0x1b, 1}, 123 {0x7f, 0x9f, 1}, 124 }, 125 LatinOffset: 3, 126} 127 128func javaIdentRune(ch rune, i int) bool { 129 if unicode.IsLetter(ch) { 130 return true 131 } 132 if unicode.IsDigit(ch) && i > 0 { 133 return true 134 } 135 136 if unicode.In(ch, 137 unicode.Nl, // letter number 138 unicode.Sc, // currency symbol 139 unicode.Pc, // connecting punctuation 140 ) { 141 return true 142 } 143 144 if unicode.In(ch, 145 unicode.Cf, // format 146 unicode.Mc, // combining mark 147 unicode.Mn, // non-spacing mark 148 javaIgnorableIdentifier, 149 ) && i > 0 { 150 return true 151 } 152 153 return false 154} 155 156// JavaPackage parses the package out of a java source file by looking for the package statement, or the first valid 157// non-package statement, in which case it returns an empty string for the package. 158func JavaPackage(r io.Reader, src string) (string, error) { 159 var s scanner.Scanner 160 var sErr error 161 162 s.Init(r) 163 s.Filename = src 164 s.Error = func(s *scanner.Scanner, msg string) { 165 sErr = fmt.Errorf("error parsing %q: %s", src, msg) 166 } 167 s.IsIdentRune = javaIdentRune 168 169 tok := s.Scan() 170 if sErr != nil { 171 return "", sErr 172 } 173 if tok == scanner.Ident { 174 switch s.TokenText() { 175 case "package": 176 // Nothing 177 case "import": 178 // File has no package statement, first keyword is an import 179 return "", nil 180 case "class", "enum", "interface": 181 // File has no package statement, first keyword is a type declaration 182 return "", nil 183 case "public", "protected", "private", "abstract", "static", "final", "strictfp": 184 // File has no package statement, first keyword is a modifier 185 return "", nil 186 case "module", "open": 187 // File has no package statement, first keyword is a module declaration 188 return "", nil 189 default: 190 return "", fmt.Errorf(`expected first token of java file to be "package", got %q`, s.TokenText()) 191 } 192 } else if tok == '@' { 193 // File has no package statement, first token is an annotation 194 return "", nil 195 } else if tok == scanner.EOF { 196 // File no package statement, it has no non-whitespace non-comment tokens 197 return "", nil 198 } else { 199 return "", fmt.Errorf(`expected first token of java file to be "package", got %q`, s.TokenText()) 200 } 201 202 var pkg string 203 for { 204 tok = s.Scan() 205 if sErr != nil { 206 return "", sErr 207 } 208 if tok != scanner.Ident { 209 return "", fmt.Errorf(`expected "package <package>;", got "package %s%s"`, pkg, s.TokenText()) 210 } 211 pkg += s.TokenText() 212 213 tok = s.Scan() 214 if sErr != nil { 215 return "", sErr 216 } 217 if tok == ';' { 218 return pkg, nil 219 } else if tok == '.' { 220 pkg += "." 221 } else { 222 return "", fmt.Errorf(`expected "package <package>;", got "package %s%s"`, pkg, s.TokenText()) 223 } 224 } 225} 226