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	var tok rune
170	for {
171		tok = s.Scan()
172		if sErr != nil {
173			return "", sErr
174		}
175		// If the first token is an annotation, it could be annotating a package declaration, so consume them.
176		// Note that this does not support "complex" annotations with attributes, e.g. @Foo(x=y).
177		if tok != '@' {
178			break
179		}
180		tok = s.Scan()
181		if tok != scanner.Ident || sErr != nil {
182			return "", fmt.Errorf("expected annotation identifier, got @%v", tok)
183		}
184	}
185
186	if tok == scanner.Ident {
187		switch s.TokenText() {
188		case "package":
189		// Nothing
190		case "import":
191			// File has no package statement, first keyword is an import
192			return "", nil
193		case "class", "enum", "interface":
194			// File has no package statement, first keyword is a type declaration
195			return "", nil
196		case "public", "protected", "private", "abstract", "static", "final", "strictfp":
197			// File has no package statement, first keyword is a modifier
198			return "", nil
199		case "module", "open":
200			// File has no package statement, first keyword is a module declaration
201			return "", nil
202		default:
203			return "", fmt.Errorf(`expected first token of java file to be "package", got %q`, s.TokenText())
204		}
205	} else if tok == scanner.EOF {
206		// File no package statement, it has no non-whitespace non-comment tokens
207		return "", nil
208	} else {
209		return "", fmt.Errorf(`expected first token of java file to be "package", got %q`, s.TokenText())
210	}
211
212	var pkg string
213	for {
214		tok = s.Scan()
215		if sErr != nil {
216			return "", sErr
217		}
218		if tok != scanner.Ident {
219			return "", fmt.Errorf(`expected "package <package>;", got "package %s%s"`, pkg, s.TokenText())
220		}
221		pkg += s.TokenText()
222
223		tok = s.Scan()
224		if sErr != nil {
225			return "", sErr
226		}
227		if tok == ';' {
228			return pkg, nil
229		} else if tok == '.' {
230			pkg += "."
231		} else {
232			return "", fmt.Errorf(`expected "package <package>;", got "package %s%s"`, pkg, s.TokenText())
233		}
234	}
235}
236