• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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