blob: 77b324d00fbccbd075397a14fa8af38105500648 [file] [log] [blame]
Stuart McCulloch42151ee2012-07-16 13:43:38 +00001package aQute.bnd.osgi;
Stuart McCullochf3173222012-06-07 21:57:32 +00002
3import java.io.*;
4import java.util.*;
5import java.util.Map.Entry;
6import java.util.jar.*;
7import java.util.regex.*;
8
Stuart McCulloch42151ee2012-07-16 13:43:38 +00009import aQute.bnd.header.*;
10import aQute.bnd.osgi.Descriptors.PackageRef;
11import aQute.bnd.osgi.Descriptors.TypeRef;
Stuart McCullochf3173222012-06-07 21:57:32 +000012import aQute.lib.base64.*;
13import aQute.lib.io.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000014import aQute.libg.cryptography.*;
Stuart McCullochf3173222012-06-07 21:57:32 +000015import aQute.libg.qtokens.*;
16
17public class Verifier extends Processor {
18
19 private final Jar dot;
20 private final Manifest manifest;
21 private final Domain main;
22
23 private boolean r3;
24 private boolean usesRequire;
25
Stuart McCulloch4482c702012-06-15 13:27:53 +000026 final static Pattern EENAME = Pattern.compile("CDC-1\\.0/Foundation-1\\.0" + "|CDC-1\\.1/Foundation-1\\.1"
27 + "|OSGi/Minimum-1\\.[1-9]" + "|JRE-1\\.1" + "|J2SE-1\\.2" + "|J2SE-1\\.3"
28 + "|J2SE-1\\.4" + "|J2SE-1\\.5" + "|JavaSE-1\\.6" + "|JavaSE-1\\.7"
Stuart McCullochf3173222012-06-07 21:57:32 +000029 + "|PersonalJava-1\\.1" + "|PersonalJava-1\\.2"
Stuart McCulloch4482c702012-06-15 13:27:53 +000030 + "|CDC-1\\.0/PersonalBasis-1\\.0" + "|CDC-1\\.0/PersonalJava-1\\.0");
Stuart McCullochf3173222012-06-07 21:57:32 +000031
32 final static int V1_1 = 45;
33 final static int V1_2 = 46;
34 final static int V1_3 = 47;
35 final static int V1_4 = 48;
36 final static int V1_5 = 49;
37 final static int V1_6 = 50;
38 final static int V1_7 = 51;
39 final static int V1_8 = 52;
40
41 static class EE {
42 String name;
43 int target;
44
Stuart McCulloch669423b2012-06-26 16:34:24 +000045 EE(String name, @SuppressWarnings("unused") int source, int target) {
Stuart McCullochf3173222012-06-07 21:57:32 +000046 this.name = name;
47 this.target = target;
48 }
49
Stuart McCulloch2929e2d2012-08-07 10:57:21 +000050 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +000051 public String toString() {
52 return name + "(" + target + ")";
53 }
54 }
55
56 final static EE[] ees = {
57 new EE("CDC-1.0/Foundation-1.0", V1_3, V1_1),
58 new EE("CDC-1.1/Foundation-1.1", V1_3, V1_2),
59 new EE("OSGi/Minimum-1.0", V1_3, V1_1),
60 new EE("OSGi/Minimum-1.1", V1_3, V1_2),
61 new EE("JRE-1.1", V1_1, V1_1), //
62 new EE("J2SE-1.2", V1_2, V1_1), //
63 new EE("J2SE-1.3", V1_3, V1_1), //
64 new EE("J2SE-1.4", V1_3, V1_2), //
65 new EE("J2SE-1.5", V1_5, V1_5), //
66 new EE("JavaSE-1.6", V1_6, V1_6), //
67 new EE("PersonalJava-1.1", V1_1, V1_1), //
68 new EE("JavaSE-1.7", V1_7, V1_7), //
69 new EE("PersonalJava-1.1", V1_1, V1_1), //
Stuart McCulloch4482c702012-06-15 13:27:53 +000070 new EE("PersonalJava-1.2", V1_1, V1_1), new EE("CDC-1.0/PersonalBasis-1.0", V1_3, V1_1),
71 new EE("CDC-1.0/PersonalJava-1.0", V1_3, V1_1), new EE("CDC-1.1/PersonalBasis-1.1", V1_3, V1_2),
72 new EE("CDC-1.1/PersonalJava-1.1", V1_3, V1_2)
73 };
Stuart McCullochf3173222012-06-07 21:57:32 +000074
Stuart McCulloch4482c702012-06-15 13:27:53 +000075 final static Pattern CARDINALITY_PATTERN = Pattern.compile("single|multiple");
76 final static Pattern RESOLUTION_PATTERN = Pattern.compile("optional|mandatory");
Stuart McCullochf3173222012-06-07 21:57:32 +000077 final static Pattern BUNDLEMANIFESTVERSION = Pattern.compile("2");
78 public final static String SYMBOLICNAME_STRING = "[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*";
Stuart McCulloch4482c702012-06-15 13:27:53 +000079 public final static Pattern SYMBOLICNAME = Pattern.compile(SYMBOLICNAME_STRING);
Stuart McCullochf3173222012-06-07 21:57:32 +000080
Stuart McCullochc990fb02012-11-22 00:42:55 +000081 public final static String VERSION_STRING = "[0-9]{1,9}(\\.[0-9]{1,9}(\\.[0-9]{1,9}(\\.[0-9A-Za-z_-]+)?)?)?";
Stuart McCullochf3173222012-06-07 21:57:32 +000082 public final static Pattern VERSION = Pattern.compile(VERSION_STRING);
83 final static Pattern FILTEROP = Pattern.compile("=|<=|>=|~=");
84 public final static Pattern VERSIONRANGE = Pattern.compile("((\\(|\\[)"
85
Stuart McCulloch4482c702012-06-15 13:27:53 +000086 + VERSION_STRING + "," + VERSION_STRING + "(\\]|\\)))|"
Stuart McCullochf3173222012-06-07 21:57:32 +000087 + VERSION_STRING);
88 final static Pattern FILE = Pattern
89 .compile("/?[^/\"\n\r\u0000]+(/[^/\"\n\r\u0000]+)*");
90 final static Pattern WILDCARDPACKAGE = Pattern
91 .compile("((\\p{Alnum}|_)+(\\.(\\p{Alnum}|_)+)*(\\.\\*)?)|\\*");
92 public final static Pattern ISO639 = Pattern.compile("[A-Z][A-Z]");
Stuart McCulloch4482c702012-06-15 13:27:53 +000093 public final static Pattern HEADER_PATTERN = Pattern.compile("[A-Za-z0-9][-a-zA-Z0-9_]+");
Stuart McCullochf3173222012-06-07 21:57:32 +000094 public final static Pattern TOKEN = Pattern.compile("[-a-zA-Z0-9_]+");
95
96 public final static Pattern NUMBERPATTERN = Pattern.compile("\\d+");
97 public final static Pattern PACKAGEPATTERN = Pattern
98 .compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*");
99 public final static Pattern PATHPATTERN = Pattern.compile(".*");
100 public final static Pattern FQNPATTERN = Pattern.compile(".*");
101 public final static Pattern URLPATTERN = Pattern.compile(".*");
102 public final static Pattern ANYPATTERN = Pattern.compile(".*");
103 public final static Pattern FILTERPATTERN = Pattern.compile(".*");
Stuart McCulloch4482c702012-06-15 13:27:53 +0000104 public final static Pattern TRUEORFALSEPATTERN = Pattern.compile("true|false|TRUE|FALSE");
Stuart McCullochf3173222012-06-07 21:57:32 +0000105 public static final Pattern WILDCARDNAMEPATTERN = Pattern.compile(".*");
106 public static final Pattern BUNDLE_ACTIVATIONPOLICYPATTERN = Pattern.compile("lazy");
107
Stuart McCulloch4482c702012-06-15 13:27:53 +0000108 public final static String EES[] = {
109 "CDC-1.0/Foundation-1.0", "CDC-1.1/Foundation-1.1", "OSGi/Minimum-1.0", "OSGi/Minimum-1.1",
110 "OSGi/Minimum-1.2", "JRE-1.1", "J2SE-1.2", "J2SE-1.3", "J2SE-1.4", "J2SE-1.5", "JavaSE-1.6", "JavaSE-1.7",
111 "PersonalJava-1.1", "PersonalJava-1.2", "CDC-1.0/PersonalBasis-1.0", "CDC-1.0/PersonalJava-1.0"
112 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000113
Stuart McCulloch4482c702012-06-15 13:27:53 +0000114 public final static String OSNAMES[] = {
115 "AIX", // IBM
Stuart McCullochf3173222012-06-07 21:57:32 +0000116 "DigitalUnix", // Compaq
117 "Embos", // Segger Embedded Software Solutions
118 "Epoc32", // SymbianOS Symbian OS
119 "FreeBSD", // Free BSD
120 "HPUX", // hp-ux Hewlett Packard
121 "IRIX", // Silicon Graphics
122 "Linux", // Open source
123 "MacOS", // Apple
124 "NetBSD", // Open source
125 "Netware", // Novell
126 "OpenBSD", // Open source
127 "OS2", // OS/2 IBM
128 "QNX", // procnto QNX
129 "Solaris", // Sun (almost an alias of SunOS)
130 "SunOS", // Sun Microsystems
131 "VxWorks", // WindRiver Systems
132 "Windows95", "Win32", "Windows98", "WindowsNT", "WindowsCE", "Windows2000", // Win2000
133 "Windows2003", // Win2003
Stuart McCulloch4482c702012-06-15 13:27:53 +0000134 "WindowsXP", "WindowsVista",
135 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000136
137 public final static String PROCESSORNAMES[] = { //
Stuart McCulloch4482c702012-06-15 13:27:53 +0000138 //
Stuart McCullochf3173222012-06-07 21:57:32 +0000139 "68k", // Motorola 68000
140 "ARM_LE", // Intel Strong ARM. Deprecated because it does not
141 // specify the endianness. See the following two rows.
142 "arm_le", // Intel Strong ARM Little Endian mode
143 "arm_be", // Intel String ARM Big Endian mode
144 "Alpha", //
145 "ia64n",// Hewlett Packard 32 bit
146 "ia64w",// Hewlett Packard 64 bit mode
147 "Ignite", // psc1k PTSC
148 "Mips", // SGI
149 "PArisc", // Hewlett Packard
150 "PowerPC", // power ppc Motorola/IBM Power PC
151 "Sh4", // Hitachi
152 "Sparc", // SUN
153 "Sparcv9", // SUN
154 "S390", // IBM Mainframe 31 bit
155 "S390x", // IBM Mainframe 64-bit
156 "V850E", // NEC V850E
157 "x86", // pentium i386
158 "i486", // i586 i686 Intel& AMD 32 bit
Stuart McCulloch4482c702012-06-15 13:27:53 +0000159 "x86-64",
160 };
Stuart McCullochf3173222012-06-07 21:57:32 +0000161
162 final Analyzer analyzer;
163 private Instructions dynamicImports;
164
165 public Verifier(Jar jar) throws Exception {
166 this.analyzer = new Analyzer(this);
167 this.analyzer.use(this);
168 addClose(analyzer);
169 this.analyzer.setJar(jar);
170 this.manifest = this.analyzer.calcManifest();
171 this.main = Domain.domain(manifest);
172 this.dot = jar;
173 getInfo(analyzer);
174 }
175
176 public Verifier(Analyzer analyzer) throws Exception {
177 this.analyzer = analyzer;
178 this.dot = analyzer.getJar();
179 this.manifest = dot.getManifest();
180 this.main = Domain.domain(manifest);
181 }
182
183 private void verifyHeaders() {
184 for (String h : main) {
185 if (!HEADER_PATTERN.matcher(h).matches())
186 error("Invalid Manifest header: " + h + ", pattern=" + HEADER_PATTERN);
187 }
188 }
189
190 /*
191 * Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( ’,’ optional) ?
192 * nativecode ::= path ( ';' path )* // See 1.4.2 ( ';' parameter )+
193 * optional ::= ’*’
194 */
195 public void verifyNative() {
196 String nc = get("Bundle-NativeCode");
197 doNative(nc);
198 }
199
200 public void doNative(String nc) {
201 if (nc != null) {
202 QuotedTokenizer qt = new QuotedTokenizer(nc, ",;=", false);
203 char del;
204 do {
205 do {
206 String name = qt.nextToken();
207 if (name == null) {
208 error("Can not parse name from bundle native code header: " + nc);
209 return;
210 }
211 del = qt.getSeparator();
212 if (del == ';') {
213 if (dot != null && !dot.exists(name)) {
214 error("Native library not found in JAR: " + name);
215 }
216 } else {
217 String value = null;
218 if (del == '=')
219 value = qt.nextToken();
220
221 String key = name.toLowerCase();
222 if (key.equals("osname")) {
223 // ...
224 } else if (key.equals("osversion")) {
225 // verify version range
226 verify(value, VERSIONRANGE);
227 } else if (key.equals("language")) {
228 verify(value, ISO639);
229 } else if (key.equals("processor")) {
230 // verify(value, PROCESSORS);
231 } else if (key.equals("selection-filter")) {
232 // verify syntax filter
233 verifyFilter(value);
234 } else if (name.equals("*") && value == null) {
235 // Wildcard must be at end.
236 if (qt.nextToken() != null)
237 error("Bundle-Native code header may only END in wildcard: nc");
238 } else {
239 warning("Unknown attribute in native code: " + name + "=" + value);
240 }
241 del = qt.getSeparator();
242 }
243 } while (del == ';');
244 } while (del == ',');
245 }
246 }
247
248 public boolean verifyFilter(String value) {
249 String s = validateFilter(value);
250 if (s == null)
251 return true;
252
253 error(s);
254 return false;
255 }
256
257 public static String validateFilter(String value) {
258 try {
259 verifyFilter(value, 0);
260 return null;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000261 }
262 catch (Exception e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000263 return "Not a valid filter: " + value + e.getMessage();
264 }
265 }
266
267 private void verifyActivator() throws Exception {
268 String bactivator = main.get("Bundle-Activator");
269 if (bactivator != null) {
270 TypeRef ref = analyzer.getTypeRefFromFQN(bactivator);
271 if (analyzer.getClassspace().containsKey(ref))
272 return;
273
274 PackageRef packageRef = ref.getPackageRef();
275 if (packageRef.isDefaultPackage())
276 error("The Bundle Activator is not in the bundle and it is in the default package ");
277 else if (!analyzer.isImported(packageRef)) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000278 error("Bundle-Activator not found on the bundle class path nor in imports: " + bactivator);
Stuart McCullochf3173222012-06-07 21:57:32 +0000279 }
280 }
281 }
282
283 private void verifyComponent() {
284 String serviceComponent = main.get("Service-Component");
285 if (serviceComponent != null) {
286 Parameters map = parseHeader(serviceComponent);
287 for (String component : map.keySet()) {
288 if (component.indexOf("*") < 0 && !dot.exists(component)) {
289 error("Service-Component entry can not be located in JAR: " + component);
290 } else {
291 // validate component ...
292 }
293 }
294 }
295 }
296
297 /**
298 * Check for unresolved imports. These are referrals that are not imported
299 * by the manifest and that are not part of our bundle class path. The are
300 * calculated by removing all the imported packages and contained from the
301 * referred packages.
302 */
303 private void verifyUnresolvedReferences() {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000304 Set<PackageRef> unresolvedReferences = new TreeSet<PackageRef>(analyzer.getReferred().keySet());
Stuart McCullochf3173222012-06-07 21:57:32 +0000305 unresolvedReferences.removeAll(analyzer.getImports().keySet());
306 unresolvedReferences.removeAll(analyzer.getContained().keySet());
307
308 // Remove any java.** packages.
309 for (Iterator<PackageRef> p = unresolvedReferences.iterator(); p.hasNext();) {
310 PackageRef pack = p.next();
311 if (pack.isJava())
312 p.remove();
313 else {
314 // Remove any dynamic imports
315 if (isDynamicImport(pack))
316 p.remove();
317 }
318 }
319
320 if (!unresolvedReferences.isEmpty()) {
321 // Now we want to know the
322 // classes that are the culprits
323 Set<String> culprits = new HashSet<String>();
324 for (Clazz clazz : analyzer.getClassspace().values()) {
325 if (hasOverlap(unresolvedReferences, clazz.getReferred()))
326 culprits.add(clazz.getAbsolutePath());
327 }
328
Stuart McCulloch4482c702012-06-15 13:27:53 +0000329 error("Unresolved references to %s by class(es) %s on the Bundle-Classpath: %s", unresolvedReferences,
330 culprits, analyzer.getBundleClasspath().keySet());
Stuart McCullochf3173222012-06-07 21:57:32 +0000331 }
332 }
333
334 /**
335 * @param p
336 * @param pack
337 */
338 private boolean isDynamicImport(PackageRef pack) {
339 if (dynamicImports == null)
340 dynamicImports = new Instructions(main.getDynamicImportPackage());
341
342 return dynamicImports.matches(pack.getFQN());
343 }
344
Stuart McCulloch4482c702012-06-15 13:27:53 +0000345 private boolean hasOverlap(Set< ? > a, Set< ? > b) {
346 for (Iterator< ? > i = a.iterator(); i.hasNext();) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000347 if (b.contains(i.next()))
348 return true;
349 }
350 return false;
351 }
352
353 public void verify() throws Exception {
354 verifyHeaders();
Stuart McCulloch4482c702012-06-15 13:27:53 +0000355 verifyDirectives("Export-Package", "uses:|mandatory:|include:|exclude:|" + IMPORT_DIRECTIVE, PACKAGEPATTERN,
356 "package");
Stuart McCullochf3173222012-06-07 21:57:32 +0000357 verifyDirectives("Import-Package", "resolution:", PACKAGEPATTERN, "package");
358 verifyDirectives("Require-Bundle", "visibility:|resolution:", SYMBOLICNAME, "bsn");
359 verifyDirectives("Fragment-Host", "extension:", SYMBOLICNAME, "bsn");
360 verifyDirectives("Provide-Capability", "effective:|uses:", null, null);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000361 verifyDirectives("Require-Capability", "effective:|resolution:|filter:", null, null);
362 verifyDirectives("Bundle-SymbolicName", "singleton:|fragment-attachment:|mandatory:", SYMBOLICNAME, "bsn");
Stuart McCullochf3173222012-06-07 21:57:32 +0000363
364 verifyManifestFirst();
365 verifyActivator();
366 verifyActivationPolicy();
367 verifyComponent();
368 verifyNative();
369 verifyUnresolvedReferences();
370 verifySymbolicName();
371 verifyListHeader("Bundle-RequiredExecutionEnvironment", EENAME, false);
372 verifyHeader("Bundle-ManifestVersion", BUNDLEMANIFESTVERSION, false);
373 verifyHeader("Bundle-Version", VERSION, true);
374 verifyListHeader("Bundle-Classpath", FILE, false);
375 verifyDynamicImportPackage();
376 verifyBundleClasspath();
377 verifyUses();
378 if (usesRequire) {
379 if (!getErrors().isEmpty()) {
380 getWarnings()
381 .add(0,
382 "Bundle uses Require Bundle, this can generate false errors because then not enough information is available without the required bundles");
383 }
384 }
385
386 verifyRequirements();
387 verifyCapabilities();
388 }
389
390 private void verifyRequirements() {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000391 Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.REQUIRE_CAPABILITY));
Stuart McCullochf3173222012-06-07 21:57:32 +0000392 for (String key : map.keySet()) {
393 Attrs attrs = map.get(key);
394 verify(attrs, "filter:", FILTERPATTERN, false, "Requirement %s filter not correct", key);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000395 verify(attrs, "cardinality:", CARDINALITY_PATTERN, false, "Requirement %s cardinality not correct", key);
396 verify(attrs, "resolution:", RESOLUTION_PATTERN, false, "Requirement %s resolution not correct", key);
Stuart McCullochf3173222012-06-07 21:57:32 +0000397
398 if (key.equals("osgi.extender")) {
399 // No requirements on extender
400 } else if (key.equals("osgi.serviceloader")) {
401 verify(attrs, "register:", PACKAGEPATTERN, false,
402 "Service Loader extender register: directive not a fully qualified Java name");
403 } else if (key.equals("osgi.contract")) {
404
405 } else if (key.equals("osgi.service")) {
406
407 } else if (key.equals("osgi.ee")) {
408
409 } else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
410 error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
411 }
412
413 verifyAttrs(attrs);
414
415 if (attrs.containsKey("mandatory:"))
416 error("mandatory: directive is intended for Capabilities, not Requirement %s", key);
417
418 if (attrs.containsKey("uses:"))
419 error("uses: directive is intended for Capabilities, not Requirement %s", key);
420 }
421 }
422
423 /**
424 * @param attrs
425 */
426 void verifyAttrs(Attrs attrs) {
427 for (String a : attrs.keySet()) {
428 String v = attrs.get(a);
429
430 if (!a.endsWith(":")) {
431 Attrs.Type t = attrs.getType(a);
432 if ("version".equals(a)) {
433 if (t != Attrs.Type.VERSION)
434 error("Version attributes should always be of type version, it is %s", t);
435 } else
436 verifyType(t, v);
437 }
438 }
439 }
440
441 private void verifyCapabilities() {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000442 Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.PROVIDE_CAPABILITY));
Stuart McCullochf3173222012-06-07 21:57:32 +0000443 for (String key : map.keySet()) {
444 Attrs attrs = map.get(key);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000445 verify(attrs, "cardinality:", CARDINALITY_PATTERN, false, "Requirement %s cardinality not correct", key);
446 verify(attrs, "resolution:", RESOLUTION_PATTERN, false, "Requirement %s resolution not correct", key);
Stuart McCullochf3173222012-06-07 21:57:32 +0000447
448 if (key.equals("osgi.extender")) {
449 verify(attrs, "osgi.extender", SYMBOLICNAME, true,
450 "Extender %s must always have the osgi.extender attribute set", key);
Stuart McCulloch4482c702012-06-15 13:27:53 +0000451 verify(attrs, "version", VERSION, true, "Extender %s must always have a version", key);
Stuart McCullochf3173222012-06-07 21:57:32 +0000452 } else if (key.equals("osgi.serviceloader")) {
453 verify(attrs, "register:", PACKAGEPATTERN, false,
454 "Service Loader extender register: directive not a fully qualified Java name");
455 } else if (key.equals("osgi.contract")) {
456 verify(attrs, "osgi.contract", SYMBOLICNAME, true,
457 "Contracts %s must always have the osgi.contract attribute set", key);
458
459 } else if (key.equals("osgi.service")) {
460 verify(attrs, "objectClass", PACKAGEPATTERN, true,
461 "osgi.service %s must have the objectClass attribute set", key);
462
463 } else if (key.equals("osgi.ee")) {
464 // TODO
465 } else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
466 error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
467 }
468
469 verifyAttrs(attrs);
470
471 if (attrs.containsKey("filter:"))
472 error("filter: directive is intended for Requirements, not Capability %s", key);
473 if (attrs.containsKey("cardinality:"))
474 error("cardinality: directive is intended for Requirements, not Capability %s", key);
475 if (attrs.containsKey("resolution:"))
476 error("resolution: directive is intended for Requirements, not Capability %s", key);
477 }
478 }
479
Stuart McCulloch4482c702012-06-15 13:27:53 +0000480 private void verify(Attrs attrs, String ad, Pattern pattern, boolean mandatory, String msg, String... args) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000481 String v = attrs.get(ad);
482 if (v == null) {
483 if (mandatory)
484 error("Missing required attribute/directive %s", ad);
485 } else {
486 Matcher m = pattern.matcher(v);
487 if (!m.matches())
488 error(msg, (Object[]) args);
489 }
490 }
491
Stuart McCulloch669423b2012-06-26 16:34:24 +0000492 private void verifyType(@SuppressWarnings("unused") Attrs.Type type, @SuppressWarnings("unused") String string) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000493
494 }
495
496 /**
497 * Verify if the header does not contain any other directives
498 *
499 * @param header
500 * @param directives
501 */
502 private void verifyDirectives(String header, String directives, Pattern namePattern, String type) {
503 Pattern pattern = Pattern.compile(directives);
504 Parameters map = parseHeader(manifest.getMainAttributes().getValue(header));
Stuart McCulloch4482c702012-06-15 13:27:53 +0000505 for (Entry<String,Attrs> entry : map.entrySet()) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000506 String pname = removeDuplicateMarker(entry.getKey());
507
508 if (namePattern != null) {
509 if (!namePattern.matcher(pname).matches())
510 if (isPedantic())
511 error("Invalid %s name: '%s'", type, pname);
512 else
513 warning("Invalid %s name: '%s'", type, pname);
514 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000515
Stuart McCullochf3173222012-06-07 21:57:32 +0000516 for (String key : entry.getValue().keySet()) {
517 if (key.endsWith(":")) {
518 if (!key.startsWith("x-")) {
519 Matcher m = pattern.matcher(key);
520 if (m.matches())
521 continue;
522
Stuart McCulloch4482c702012-06-15 13:27:53 +0000523 warning("Unknown directive %s in %s, allowed directives are %s, and 'x-*'.", key, header,
524 directives.replace('|', ','));
Stuart McCullochf3173222012-06-07 21:57:32 +0000525 }
526 }
527 }
528 }
529 }
530
531 /**
532 * Verify the use clauses
533 */
534 private void verifyUses() {
535 // Set<String> uses = Create.set();
536 // for ( Map<String,String> attrs : analyzer.getExports().values()) {
537 // if ( attrs.containsKey(Constants.USES_DIRECTIVE)) {
538 // String s = attrs.get(Constants.USES_DIRECTIVE);
539 // uses.addAll( split(s));
540 // }
541 // }
542 // uses.removeAll(analyzer.getExports().keySet());
543 // uses.removeAll(analyzer.getImports().keySet());
544 // if ( !uses.isEmpty())
545 // warning("Export-Package uses: directive contains packages that are not imported nor exported: %s",
546 // uses);
547 }
548
549 public boolean verifyActivationPolicy() {
550 String policy = main.get(Constants.BUNDLE_ACTIVATIONPOLICY);
551 if (policy == null)
552 return true;
553
554 return verifyActivationPolicy(policy);
555 }
556
557 public boolean verifyActivationPolicy(String policy) {
558 Parameters map = parseHeader(policy);
559 if (map.size() == 0)
560 warning("Bundle-ActivationPolicy is set but has no argument %s", policy);
561 else if (map.size() > 1)
562 warning("Bundle-ActivationPolicy has too many arguments %s", policy);
563 else {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000564 Map<String,String> s = map.get("lazy");
Stuart McCullochf3173222012-06-07 21:57:32 +0000565 if (s == null)
566 warning("Bundle-ActivationPolicy set but is not set to lazy: %s", policy);
567 else
568 return true;
569 }
570
571 return false;
572 }
573
574 public void verifyBundleClasspath() {
575 Parameters bcp = main.getBundleClassPath();
576 if (bcp.isEmpty() || bcp.containsKey("."))
577 return;
578
579 for (String path : bcp.keySet()) {
580 if (path.endsWith("/"))
581 error("A Bundle-ClassPath entry must not end with '/': %s", path);
582
583 if (dot.getDirectories().containsKey(path))
584 // We assume that any classes are in a directory
585 // and therefore do not care when the bundle is included
586 return;
587 }
588
589 for (String path : dot.getResources().keySet()) {
590 if (path.endsWith(".class")) {
591 warning("The Bundle-Classpath does not contain the actual bundle JAR (as specified with '.' in the Bundle-Classpath) but the JAR does contain classes. Is this intentional?");
592 return;
593 }
594 }
595 }
596
597 /**
598 * <pre>
599 * DynamicImport-Package ::= dynamic-description
600 * ( ',' dynamic-description )*
601 *
602 * dynamic-description::= wildcard-names ( ';' parameter )*
603 * wildcard-names ::= wildcard-name ( ';' wildcard-name )*
604 * wildcard-name ::= package-name
605 * | ( package-name '.*' ) // See 1.4.2
606 * | '*'
607 * </pre>
608 */
609 private void verifyDynamicImportPackage() {
610 verifyListHeader("DynamicImport-Package", WILDCARDPACKAGE, true);
611 String dynamicImportPackage = get("DynamicImport-Package");
612 if (dynamicImportPackage == null)
613 return;
614
615 Parameters map = main.getDynamicImportPackage();
616 for (String name : map.keySet()) {
617 name = name.trim();
618 if (!verify(name, WILDCARDPACKAGE))
619 error("DynamicImport-Package header contains an invalid package name: " + name);
620
Stuart McCulloch4482c702012-06-15 13:27:53 +0000621 Map<String,String> sub = map.get(name);
Stuart McCullochf3173222012-06-07 21:57:32 +0000622 if (r3 && sub.size() != 0) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000623 error("DynamicPackage-Import has attributes on import: " + name
Stuart McCullochf3173222012-06-07 21:57:32 +0000624 + ". This is however, an <=R3 bundle and attributes on this header were introduced in R4. ");
625 }
626 }
627 }
628
629 private void verifyManifestFirst() {
630 if (!dot.isManifestFirst()) {
631 error("Invalid JAR stream: Manifest should come first to be compatible with JarInputStream, it was not");
632 }
633 }
634
635 private void verifySymbolicName() {
636 Parameters bsn = parseHeader(main.get(Analyzer.BUNDLE_SYMBOLICNAME));
637 if (!bsn.isEmpty()) {
638 if (bsn.size() > 1)
639 error("More than one BSN specified " + bsn);
640
641 String name = bsn.keySet().iterator().next();
642 if (!isBsn(name)) {
643 error("Symbolic Name has invalid format: " + name);
644 }
645 }
646 }
647
648 /**
649 * @param name
650 * @return
651 */
652 public static boolean isBsn(String name) {
653 return SYMBOLICNAME.matcher(name).matches();
654 }
655
656 /**
657 * <pre>
658 * filter ::= ’(’ filter-comp ’)’
659 * filter-comp ::= and | or | not | operation
660 * and ::= ’&amp;’ filter-list
661 * or ::= ’|’ filter-list
662 * not ::= ’!’ filter
663 * filter-list ::= filter | filter filter-list
664 * operation ::= simple | present | substring
665 * simple ::= attr filter-type value
666 * filter-type ::= equal | approx | greater | less
667 * equal ::= ’=’
668 * approx ::= ’&tilde;=’
669 * greater ::= ’&gt;=’
670 * less ::= ’&lt;=’
671 * present ::= attr ’=*’
672 * substring ::= attr ’=’ initial any final
673 * inital ::= () | value
674 * any ::= ’*’ star-value
675 * star-value ::= () | value ’*’ star-value
676 * final ::= () | value
677 * value ::= &lt;see text&gt;
678 * </pre>
679 *
680 * @param expr
681 * @param index
682 * @return
683 */
684
685 public static int verifyFilter(String expr, int index) {
686 try {
687 while (Character.isWhitespace(expr.charAt(index)))
688 index++;
689
690 if (expr.charAt(index) != '(')
Stuart McCulloch4482c702012-06-15 13:27:53 +0000691 throw new IllegalArgumentException("Filter mismatch: expected ( at position " + index + " : " + expr);
Stuart McCullochf3173222012-06-07 21:57:32 +0000692
693 index++; // skip (
694
695 while (Character.isWhitespace(expr.charAt(index)))
696 index++;
697
698 switch (expr.charAt(index)) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000699 case '!' :
700 index++; // skip !
701 while (Character.isWhitespace(expr.charAt(index)))
702 index++;
Stuart McCullochf3173222012-06-07 21:57:32 +0000703
Stuart McCulloch4482c702012-06-15 13:27:53 +0000704 if (expr.charAt(index) != '(')
705 throw new IllegalArgumentException("Filter mismatch: ! (not) must have one sub expression "
706 + index + " : " + expr);
707 while (Character.isWhitespace(expr.charAt(index)))
708 index++;
Stuart McCullochf3173222012-06-07 21:57:32 +0000709
Stuart McCullochf3173222012-06-07 21:57:32 +0000710 index = verifyFilter(expr, index);
711 while (Character.isWhitespace(expr.charAt(index)))
712 index++;
Stuart McCulloch4482c702012-06-15 13:27:53 +0000713 if (expr.charAt(index) != ')')
714 throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
715 + expr);
716 return index + 1;
Stuart McCullochf3173222012-06-07 21:57:32 +0000717
Stuart McCulloch4482c702012-06-15 13:27:53 +0000718 case '&' :
719 case '|' :
720 index++; // skip operator
721 while (Character.isWhitespace(expr.charAt(index)))
722 index++;
723 while (expr.charAt(index) == '(') {
724 index = verifyFilter(expr, index);
725 while (Character.isWhitespace(expr.charAt(index)))
726 index++;
727 }
Stuart McCullochf3173222012-06-07 21:57:32 +0000728
Stuart McCulloch4482c702012-06-15 13:27:53 +0000729 if (expr.charAt(index) != ')')
730 throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
731 + expr);
732 return index + 1; // skip )
733
734 default :
735 index = verifyFilterOperation(expr, index);
736 if (expr.charAt(index) != ')')
737 throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
738 + expr);
739 return index + 1;
Stuart McCullochf3173222012-06-07 21:57:32 +0000740 }
Stuart McCulloch4482c702012-06-15 13:27:53 +0000741 }
742 catch (IndexOutOfBoundsException e) {
Stuart McCullochf3173222012-06-07 21:57:32 +0000743 throw new IllegalArgumentException("Filter mismatch: early EOF from " + index);
744 }
745 }
746
747 static private int verifyFilterOperation(String expr, int index) {
748 StringBuilder sb = new StringBuilder();
749 while ("=><~()".indexOf(expr.charAt(index)) < 0) {
750 sb.append(expr.charAt(index++));
751 }
752 String attr = sb.toString().trim();
753 if (attr.length() == 0)
754 throw new IllegalArgumentException("Filter mismatch: attr at index " + index + " is 0");
755 sb = new StringBuilder();
756 while ("=><~".indexOf(expr.charAt(index)) >= 0) {
757 sb.append(expr.charAt(index++));
758 }
759 String operator = sb.toString();
760 if (!verify(operator, FILTEROP))
Stuart McCulloch4482c702012-06-15 13:27:53 +0000761 throw new IllegalArgumentException("Filter error, illegal operator " + operator + " at index " + index);
Stuart McCullochf3173222012-06-07 21:57:32 +0000762
763 sb = new StringBuilder();
764 while (")".indexOf(expr.charAt(index)) < 0) {
765 switch (expr.charAt(index)) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000766 case '\\' :
767 if ("\\)(*".indexOf(expr.charAt(index + 1)) >= 0)
768 index++;
769 else
770 throw new IllegalArgumentException("Filter error, illegal use of backslash at index " + index
771 + ". Backslash may only be used before * or () or \\");
Stuart McCullochf3173222012-06-07 21:57:32 +0000772 }
773 sb.append(expr.charAt(index++));
774 }
775 return index;
776 }
777
778 private boolean verifyHeader(String name, Pattern regex, boolean error) {
779 String value = manifest.getMainAttributes().getValue(name);
780 if (value == null)
781 return false;
782
783 QuotedTokenizer st = new QuotedTokenizer(value.trim(), ",");
784 for (Iterator<String> i = st.getTokenSet().iterator(); i.hasNext();) {
785 if (!verify(i.next(), regex)) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000786 String msg = "Invalid value for " + name + ", " + value + " does not match " + regex.pattern();
Stuart McCullochf3173222012-06-07 21:57:32 +0000787 if (error)
788 error(msg);
789 else
790 warning(msg);
791 }
792 }
793 return true;
794 }
795
796 static private boolean verify(String value, Pattern regex) {
797 return regex.matcher(value).matches();
798 }
799
800 private boolean verifyListHeader(String name, Pattern regex, boolean error) {
801 String value = manifest.getMainAttributes().getValue(name);
802 if (value == null)
803 return false;
804
805 Parameters map = parseHeader(value);
806 for (String header : map.keySet()) {
807 if (!regex.matcher(header).matches()) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000808 String msg = "Invalid value for " + name + ", " + value + " does not match " + regex.pattern();
Stuart McCullochf3173222012-06-07 21:57:32 +0000809 if (error)
810 error(msg);
811 else
812 warning(msg);
813 }
814 }
815 return true;
816 }
817
Stuart McCulloch2929e2d2012-08-07 10:57:21 +0000818 @Override
Stuart McCullochf3173222012-06-07 21:57:32 +0000819 public String getProperty(String key, String deflt) {
820 if (properties == null)
821 return deflt;
822 return properties.getProperty(key, deflt);
823 }
824
825 public static boolean isVersion(String version) {
826 return VERSION.matcher(version).matches();
827 }
828
829 public static boolean isIdentifier(String value) {
830 if (value.length() < 1)
831 return false;
832
833 if (!Character.isJavaIdentifierStart(value.charAt(0)))
834 return false;
835
836 for (int i = 1; i < value.length(); i++) {
837 if (!Character.isJavaIdentifierPart(value.charAt(i)))
838 return false;
839 }
840 return true;
841 }
842
843 public static boolean isMember(String value, String[] matches) {
844 for (String match : matches) {
845 if (match.equals(value))
846 return true;
847 }
848 return false;
849 }
850
851 public static boolean isFQN(String name) {
852 if (name.length() == 0)
853 return false;
854 if (!Character.isJavaIdentifierStart(name.charAt(0)))
855 return false;
856
857 for (int i = 1; i < name.length(); i++) {
858 char c = name.charAt(i);
859 if (Character.isJavaIdentifierPart(c) || c == '$' || c == '.')
860 continue;
861
862 return false;
863 }
864
865 return true;
866 }
867
868 /**
869 * Verify checksums
870 */
871 /**
872 * Verify the checksums from the manifest against the real thing.
873 *
874 * @param all
875 * if each resources must be digested
876 * @return true if ok
877 * @throws Exception
878 */
879
880 public void verifyChecksums(boolean all) throws Exception {
881 Manifest m = dot.getManifest();
882 if (m == null || m.getEntries().isEmpty()) {
883 if (all)
884 error("Verify checksums with all but no digests");
885 return;
886 }
887
888 List<String> missingDigest = new ArrayList<String>();
889
890 for (String path : dot.getResources().keySet()) {
891 if (path.equals("META-INF/MANIFEST.MF"))
892 continue;
893
894 Attributes a = m.getAttributes(path);
895 String digest = a.getValue("SHA1-Digest");
896 if (digest == null) {
897 if (!path.matches(""))
898 missingDigest.add(path);
899 } else {
900 byte[] d = Base64.decodeBase64(digest);
901 SHA1 expected = new SHA1(d);
902 Digester<SHA1> digester = SHA1.getDigester();
903 InputStream in = dot.getResource(path).openInputStream();
904 IO.copy(in, digester);
905 digester.digest();
906 if (!expected.equals(digester.digest())) {
Stuart McCulloch4482c702012-06-15 13:27:53 +0000907 error("Checksum mismatch %s, expected %s, got %s", path, expected, digester.digest());
Stuart McCullochf3173222012-06-07 21:57:32 +0000908 }
909 }
910 }
911 if (missingDigest.size() > 0) {
912 error("Entries in the manifest are missing digests: %s", missingDigest);
913 }
914 }
915}