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