blob: a8f06a4f366b8a54510118316a984e670efd15f5 [file] [log] [blame]
Jeff Gaston01547b22017-08-21 20:13:28 -07001// 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 (
Colin Cross635acc92017-09-12 22:50:46 -070018 "bytes"
Jeff Gaston01547b22017-08-21 20:13:28 -070019 "fmt"
Colin Cross9cb51db2019-06-17 14:12:41 -070020 "io"
Colin Cross635acc92017-09-12 22:50:46 -070021 "os"
Jeff Gaston01547b22017-08-21 20:13:28 -070022 "strings"
Colin Cross9cb51db2019-06-17 14:12:41 -070023 "text/scanner"
Colin Cross635acc92017-09-12 22:50:46 -070024 "time"
Colin Cross9cb51db2019-06-17 14:12:41 -070025 "unicode"
Colin Cross635acc92017-09-12 22:50:46 -070026
27 "android/soong/third_party/zip"
Jeff Gaston01547b22017-08-21 20:13:28 -070028)
29
Colin Cross34540312017-09-06 12:52:37 -070030const (
31 MetaDir = "META-INF/"
32 ManifestFile = MetaDir + "MANIFEST.MF"
33 ModuleInfoClass = "module-info.class"
34)
35
Colin Crossbddcf132017-10-04 17:02:23 -070036var DefaultTime = time.Date(2008, 1, 1, 0, 0, 0, 0, time.UTC)
Colin Cross635acc92017-09-12 22:50:46 -070037
38var MetaDirExtra = [2]byte{0xca, 0xfe}
39
Jeff Gaston01547b22017-08-21 20:13:28 -070040// 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{
Colin Cross34540312017-09-06 12:52:37 -070060 MetaDir,
61 ManifestFile,
62 MetaDir + "*",
Jeff Gaston01547b22017-08-21 20:13:28 -070063 "*",
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}
Colin Cross635acc92017-09-12 22:50:46 -070074
75func MetaDirFileHeader() *zip.FileHeader {
76 dirHeader := &zip.FileHeader{
77 Name: MetaDir,
78 Extra: []byte{MetaDirExtra[1], MetaDirExtra[0], 0, 0},
79 }
80 dirHeader.SetMode(0700 | os.ModeDir)
81 dirHeader.SetModTime(DefaultTime)
82
83 return dirHeader
84}
85
Colin Cross05518bc2018-09-27 15:06:19 -070086// 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)
Colin Cross635acc92017-09-12 22:50:46 -070089 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 }
Colin Cross2825cb32017-09-29 13:57:10 -070098 fh.SetMode(0700)
99 fh.SetModTime(DefaultTime)
Colin Cross635acc92017-09-12 22:50:46 -0700100
101 return fh, b, nil
102}
103
Colin Cross05518bc2018-09-27 15:06:19 -0700104// Create manifest contents, using the provided contents if any.
105func manifestContents(contents []byte) ([]byte, error) {
Colin Cross635acc92017-09-12 22:50:46 -0700106 manifestMarker := []byte("Manifest-Version:")
107 header := append(manifestMarker, []byte(" 1.0\nCreated-By: soong_zip\n")...)
108
109 var finalBytes []byte
Colin Cross05518bc2018-09-27 15:06:19 -0700110 if !bytes.Contains(contents, manifestMarker) {
111 finalBytes = append(append(header, contents...), byte('\n'))
Colin Cross635acc92017-09-12 22:50:46 -0700112 } else {
Colin Cross05518bc2018-09-27 15:06:19 -0700113 finalBytes = contents
Colin Cross635acc92017-09-12 22:50:46 -0700114 }
115
116 return finalBytes, nil
117}
Colin Cross9cb51db2019-06-17 14:12:41 -0700118
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}