blob: 8437c4a32d2a44e87ef209f02db5814a0472b452 [file] [log] [blame]
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19package aQute.bnd.osgi;
20
21import java.io.*;
22import java.util.*;
23import java.util.Map.Entry;
24import java.util.jar.*;
25import java.util.regex.*;
26
27import aQute.bnd.header.*;
28import aQute.bnd.osgi.Descriptors.PackageRef;
29import aQute.bnd.osgi.Descriptors.TypeRef;
30import aQute.bnd.version.*;
31import aQute.lib.base64.*;
32import aQute.lib.filter.*;
33import aQute.lib.io.*;
34import aQute.libg.cryptography.*;
35import aQute.libg.qtokens.*;
36
Stuart McCulloch5939d302014-05-24 16:18:35 +000037//
38// TODO check that component XML that refer to a properties file actually have such a file
39//
40
Guillaume Nodet1c9211a2014-05-20 08:56:13 +000041public class Verifier extends Processor {
42
Stuart McCulloch5939d302014-05-24 16:18:35 +000043 private final Jar dot;
44 private final Manifest manifest;
45 private final Domain main;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +000046
Stuart McCulloch5939d302014-05-24 16:18:35 +000047 private boolean r3;
48 private boolean usesRequire;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +000049
Stuart McCulloch5939d302014-05-24 16:18:35 +000050 final static Pattern EENAME = Pattern.compile("CDC-1\\.0/Foundation-1\\.0" + "|CDC-1\\.1/Foundation-1\\.1"
51 + "|OSGi/Minimum-1\\.[1-9]" + "|JRE-1\\.1" + "|J2SE-1\\.2" + "|J2SE-1\\.3"
52 + "|J2SE-1\\.4" + "|J2SE-1\\.5" + "|JavaSE-1\\.6" + "|JavaSE-1\\.7"
53 + "|JavaSE-1\\.8" + "|PersonalJava-1\\.1" + "|PersonalJava-1\\.2"
54 + "|CDC-1\\.0/PersonalBasis-1\\.0" + "|CDC-1\\.0/PersonalJava-1\\.0");
Guillaume Nodet1c9211a2014-05-20 08:56:13 +000055
Stuart McCulloch5939d302014-05-24 16:18:35 +000056 final static int V1_1 = 45;
57 final static int V1_2 = 46;
58 final static int V1_3 = 47;
59 final static int V1_4 = 48;
60 final static int V1_5 = 49;
61 final static int V1_6 = 50;
62 final static int V1_7 = 51;
63 final static int V1_8 = 52;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +000064
Stuart McCulloch5939d302014-05-24 16:18:35 +000065 static class EE {
66 String name;
67 int target;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +000068
Stuart McCulloch5939d302014-05-24 16:18:35 +000069 EE(String name, @SuppressWarnings("unused")
70 int source, int target) {
71 this.name = name;
72 this.target = target;
73 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +000074
Stuart McCulloch5939d302014-05-24 16:18:35 +000075 @Override
76 public String toString() {
77 return name + "(" + target + ")";
78 }
79 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +000080
Stuart McCulloch5939d302014-05-24 16:18:35 +000081 final static EE[] ees = {
82 new EE("CDC-1.0/Foundation-1.0", V1_3, V1_1),
83 new EE("CDC-1.1/Foundation-1.1", V1_3, V1_2),
84 new EE("OSGi/Minimum-1.0", V1_3, V1_1),
85 new EE("OSGi/Minimum-1.1", V1_3, V1_2),
86 new EE("JRE-1.1", V1_1, V1_1), //
87 new EE("J2SE-1.2", V1_2, V1_1), //
88 new EE("J2SE-1.3", V1_3, V1_1), //
89 new EE("J2SE-1.4", V1_3, V1_2), //
90 new EE("J2SE-1.5", V1_5, V1_5), //
91 new EE("JavaSE-1.6", V1_6, V1_6), //
92 new EE("PersonalJava-1.1", V1_1, V1_1), //
93 new EE("JavaSE-1.7", V1_7, V1_7), //
94 new EE("JavaSE-1.8", V1_8, V1_8), //
95 new EE("PersonalJava-1.1", V1_1, V1_1), //
96 new EE("PersonalJava-1.2", V1_1, V1_1), new EE("CDC-1.0/PersonalBasis-1.0", V1_3, V1_1),
97 new EE("CDC-1.0/PersonalJava-1.0", V1_3, V1_1), new EE("CDC-1.1/PersonalBasis-1.1", V1_3, V1_2),
98 new EE("CDC-1.1/PersonalJava-1.1", V1_3, V1_2)
99 };
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000100
Stuart McCulloch5939d302014-05-24 16:18:35 +0000101 public final static Pattern ReservedFileNames = Pattern
102 .compile(
103 "CON(\\..+)?|PRN(\\..+)?|AUX(\\..+)?|CLOCK$|NUL(\\..+)?|COM[1-9](\\..+)?|LPT[1-9](\\..+)?|"
104 + "\\$Mft|\\$MftMirr|\\$LogFile|\\$Volume|\\$AttrDef|\\$Bitmap|\\$Boot|\\$BadClus|\\$Secure|"
105 + "\\$Upcase|\\$Extend|\\$Quota|\\$ObjId|\\$Reparse",
106 Pattern.CASE_INSENSITIVE);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000107
Stuart McCulloch5939d302014-05-24 16:18:35 +0000108 final static Pattern CARDINALITY_PATTERN = Pattern.compile("single|multiple");
109 final static Pattern RESOLUTION_PATTERN = Pattern.compile("optional|mandatory");
110 final static Pattern BUNDLEMANIFESTVERSION = Pattern.compile("2");
111 public final static String SYMBOLICNAME_STRING = "[a-zA-Z0-9_-]+(\\.[a-zA-Z0-9_-]+)*";
112 public final static Pattern SYMBOLICNAME = Pattern.compile(SYMBOLICNAME_STRING);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000113
Stuart McCulloch5939d302014-05-24 16:18:35 +0000114 public final static String VERSION_STRING = "[0-9]{1,9}(\\.[0-9]{1,9}(\\.[0-9]{1,9}(\\.[0-9A-Za-z_-]+)?)?)?";
115 public final static Pattern VERSION = Pattern.compile(VERSION_STRING);
116 final static Pattern FILTEROP = Pattern.compile("=|<=|>=|~=");
117 public final static Pattern VERSIONRANGE = Pattern.compile("((\\(|\\[)"
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000118
Stuart McCulloch5939d302014-05-24 16:18:35 +0000119 + VERSION_STRING + "," + VERSION_STRING + "(\\]|\\)))|"
120 + VERSION_STRING);
121 final static Pattern FILE = Pattern
122 .compile("/?[^/\"\n\r\u0000]+(/[^/\"\n\r\u0000]+)*");
123 final static Pattern WILDCARDPACKAGE = Pattern
124 .compile("((\\p{Alnum}|_)+(\\.(\\p{Alnum}|_)+)*(\\.\\*)?)|\\*");
125 public final static Pattern ISO639 = Pattern.compile("[A-Z][A-Z]");
126 public final static Pattern HEADER_PATTERN = Pattern.compile("[A-Za-z0-9][-a-zA-Z0-9_]+");
127 public final static Pattern TOKEN = Pattern.compile("[-a-zA-Z0-9_]+");
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000128
Stuart McCulloch5939d302014-05-24 16:18:35 +0000129 public final static Pattern NUMBERPATTERN = Pattern.compile("\\d+");
130 public final static Pattern PACKAGEPATTERN = Pattern
131 .compile("\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*(\\.\\p{javaJavaIdentifierStart}\\p{javaJavaIdentifierPart}*)*");
132 public final static Pattern MULTIPACKAGEPATTERN = Pattern
133 .compile("(\\s*" + PACKAGEPATTERN + ")(" + LIST_SPLITTER + PACKAGEPATTERN + ")*\\s*");
134 public final static Pattern PATHPATTERN = Pattern.compile(".*");
135 public final static Pattern FQNPATTERN = Pattern.compile(".*");
136 public final static Pattern URLPATTERN = Pattern.compile(".*");
137 public final static Pattern ANYPATTERN = Pattern.compile(".*");
138 public final static Pattern FILTERPATTERN = Pattern.compile(".*");
139 public final static Pattern TRUEORFALSEPATTERN = Pattern.compile("true|false|TRUE|FALSE");
140 public static final Pattern WILDCARDNAMEPATTERN = Pattern.compile(".*");
141 public static final Pattern BUNDLE_ACTIVATIONPOLICYPATTERN = Pattern.compile("lazy");
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000142
Stuart McCulloch5939d302014-05-24 16:18:35 +0000143 public final static String VERSION_S = "[0-9]{1,9}(:?\\.[0-9]{1,9}(:?\\.[0-9]{1,9}(:?\\.[0-9A-Za-z_-]+)?)?)?";
144 public final static Pattern VERSION_P = Pattern.compile(VERSION_S);
145 public final static String VERSION_RANGE_S = "(?:(:?\\(|\\[)" + VERSION_S + "," + VERSION_S
146 + "(\\]|\\)))|" + VERSION_S;
147 public final static Pattern VERSIONRANGE_P = VERSIONRANGE;
148 public static String EXTENDED_S = "[-a-zA-Z0-9_.]+";
149 public static Pattern EXTENDED_P = Pattern.compile(EXTENDED_S);
150 public static String QUOTEDSTRING = "\"[^\"]*\"";
151 public static Pattern QUOTEDSTRING_P = Pattern.compile(QUOTEDSTRING);
152 public static String ARGUMENT_S = "(:?" + EXTENDED_S + ")|(?:" + QUOTEDSTRING + ")";
153 public static Pattern ARGUMENT_P = Pattern.compile(ARGUMENT_S);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000154
Stuart McCulloch5939d302014-05-24 16:18:35 +0000155 public final static String EES[] = {
156 "CDC-1.0/Foundation-1.0", "CDC-1.1/Foundation-1.1", "OSGi/Minimum-1.0", "OSGi/Minimum-1.1",
157 "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",
158 "PersonalJava-1.1", "PersonalJava-1.2", "CDC-1.0/PersonalBasis-1.0", "CDC-1.0/PersonalJava-1.0"
159 };
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000160
Stuart McCulloch5939d302014-05-24 16:18:35 +0000161 public final static String OSNAMES[] = {
162 "AIX", // IBM
163 "DigitalUnix", // Compaq
164 "Embos", // Segger Embedded Software Solutions
165 "Epoc32", // SymbianOS Symbian OS
166 "FreeBSD", // Free BSD
167 "HPUX", // hp-ux Hewlett Packard
168 "IRIX", // Silicon Graphics
169 "Linux", // Open source
170 "MacOS", // Apple
171 "NetBSD", // Open source
172 "Netware", // Novell
173 "OpenBSD", // Open source
174 "OS2", // OS/2 IBM
175 "QNX", // procnto QNX
176 "Solaris", // Sun (almost an alias of SunOS)
177 "SunOS", // Sun Microsystems
178 "VxWorks", // WindRiver Systems
179 "Windows95", "Win32", "Windows98", "WindowsNT", "WindowsCE", "Windows2000", // Win2000
180 "Windows2003", // Win2003
181 "WindowsXP", "WindowsVista",
182 };
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000183
Stuart McCulloch5939d302014-05-24 16:18:35 +0000184 public final static String PROCESSORNAMES[] = { //
185 //
186 "68k", // Motorola 68000
187 "ARM_LE", // Intel Strong ARM. Deprecated because it does not
188 // specify the endianness. See the following two rows.
189 "arm_le", // Intel Strong ARM Little Endian mode
190 "arm_be", // Intel String ARM Big Endian mode
191 "Alpha", //
192 "ia64n",// Hewlett Packard 32 bit
193 "ia64w",// Hewlett Packard 64 bit mode
194 "Ignite", // psc1k PTSC
195 "Mips", // SGI
196 "PArisc", // Hewlett Packard
197 "PowerPC", // power ppc Motorola/IBM Power PC
198 "Sh4", // Hitachi
199 "Sparc", // SUN
200 "Sparcv9", // SUN
201 "S390", // IBM Mainframe 31 bit
202 "S390x", // IBM Mainframe 64-bit
203 "V850E", // NEC V850E
204 "x86", // pentium i386
205 "i486", // i586 i686 Intel& AMD 32 bit
206 "x86-64",
207 };
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000208
Stuart McCulloch5939d302014-05-24 16:18:35 +0000209 final Analyzer analyzer;
210 private Instructions dynamicImports;
211 private boolean frombuilder;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000212
Stuart McCulloch5939d302014-05-24 16:18:35 +0000213 public Verifier(Jar jar) throws Exception {
214 this.analyzer = new Analyzer(this);
215 this.analyzer.use(this);
216 addClose(analyzer);
217 this.analyzer.setJar(jar);
218 this.manifest = this.analyzer.calcManifest();
219 this.main = Domain.domain(manifest);
220 this.dot = jar;
221 getInfo(analyzer);
222 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000223
Stuart McCulloch5939d302014-05-24 16:18:35 +0000224 public Verifier(Analyzer analyzer) throws Exception {
225 super(analyzer);
226 this.analyzer = analyzer;
227 this.dot = analyzer.getJar();
228 this.manifest = dot.getManifest();
229 this.main = Domain.domain(manifest);
230 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000231
Stuart McCulloch5939d302014-05-24 16:18:35 +0000232 private void verifyHeaders() {
233 for (String h : main) {
234 if (!HEADER_PATTERN.matcher(h).matches())
235 error("Invalid Manifest header: " + h + ", pattern=" + HEADER_PATTERN);
236 }
237 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000238
Stuart McCulloch5939d302014-05-24 16:18:35 +0000239 /*
240 * Bundle-NativeCode ::= nativecode ( ',' nativecode )* ( ’,’ optional) ?
241 * nativecode ::= path ( ';' path )* // See 1.4.2 ( ';' parameter )+
242 * optional ::= ’*’
243 */
244 public void verifyNative() {
245 String nc = get(Constants.BUNDLE_NATIVECODE);
246 doNative(nc);
247 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000248
Stuart McCulloch5939d302014-05-24 16:18:35 +0000249 public void doNative(String nc) {
250 if (nc != null) {
251 QuotedTokenizer qt = new QuotedTokenizer(nc, ",;=", false);
252 char del;
253 do {
254 do {
255 String name = qt.nextToken();
256 if (name == null) {
257 error("Can not parse name from bundle native code header: " + nc);
258 return;
259 }
260 del = qt.getSeparator();
261 if (del == ';') {
262 if (dot != null && !dot.exists(name)) {
263 error("Native library not found in JAR: " + name);
264 }
265 } else {
266 String value = null;
267 if (del == '=')
268 value = qt.nextToken();
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000269
Stuart McCulloch5939d302014-05-24 16:18:35 +0000270 String key = name.toLowerCase();
271 if (key.equals("osname")) {
272 // ...
273 } else if (key.equals("osversion")) {
274 // verify version range
275 verify(value, VERSIONRANGE);
276 } else if (key.equals("language")) {
277 verify(value, ISO639);
278 } else if (key.equals("processor")) {
279 // verify(value, PROCESSORS);
280 } else if (key.equals("selection-filter")) {
281 // verify syntax filter
282 verifyFilter(value);
283 } else if (name.equals("*") && value == null) {
284 // Wildcard must be at end.
285 if (qt.nextToken() != null)
286 error("Bundle-Native code header may only END in wildcard: nc");
287 } else {
288 warning("Unknown attribute in native code: " + name + "=" + value);
289 }
290 del = qt.getSeparator();
291 }
292 } while (del == ';');
293 } while (del == ',');
294 }
295 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000296
Stuart McCulloch5939d302014-05-24 16:18:35 +0000297 public boolean verifyFilter(String value) {
298 String s = validateFilter(value);
299 if (s == null)
300 return true;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000301
Stuart McCulloch5939d302014-05-24 16:18:35 +0000302 error(s);
303 return false;
304 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000305
Stuart McCulloch5939d302014-05-24 16:18:35 +0000306 public static String validateFilter(String value) {
307 try {
308 verifyFilter(value, 0);
309 return null;
310 }
311 catch (Exception e) {
312 return "Not a valid filter: " + value + e.getMessage();
313 }
314 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000315
Stuart McCulloch5939d302014-05-24 16:18:35 +0000316 private void verifyActivator() throws Exception {
317 String bactivator = main.get(Constants.BUNDLE_ACTIVATOR);
318 if (bactivator != null) {
319 TypeRef ref = analyzer.getTypeRefFromFQN(bactivator);
320 if (analyzer.getClassspace().containsKey(ref))
321 return;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000322
Stuart McCulloch5939d302014-05-24 16:18:35 +0000323 PackageRef packageRef = ref.getPackageRef();
324 if (packageRef.isDefaultPackage())
325 error("The Bundle Activator is not in the bundle and it is in the default package ");
326 else if (!analyzer.isImported(packageRef)) {
327 error(Constants.BUNDLE_ACTIVATOR + " not found on the bundle class path nor in imports: " + bactivator);
328 }
329 }
330 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000331
Stuart McCulloch5939d302014-05-24 16:18:35 +0000332 private void verifyComponent() {
333 String serviceComponent = main.get(Constants.SERVICE_COMPONENT);
334 if (serviceComponent != null) {
335 Parameters map = parseHeader(serviceComponent);
336 for (String component : map.keySet()) {
337 if (component.indexOf("*") < 0 && !dot.exists(component)) {
338 error(Constants.SERVICE_COMPONENT + " entry can not be located in JAR: " + component);
339 } else {
340 // validate component ...
341 }
342 }
343 }
344 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000345
Stuart McCulloch5939d302014-05-24 16:18:35 +0000346 /**
347 * Check for unresolved imports. These are referrals that are not imported
348 * by the manifest and that are not part of our bundle class path. The are
349 * calculated by removing all the imported packages and contained from the
350 * referred packages.
351 * @throws Exception
352 */
353 private void verifyUnresolvedReferences() throws Exception {
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000354
Stuart McCulloch5939d302014-05-24 16:18:35 +0000355 //
356 // If we're being called from the builder then this should
357 // already have been done
358 //
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000359
Stuart McCulloch5939d302014-05-24 16:18:35 +0000360 if (isFrombuilder())
361 return;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000362
Stuart McCulloch5939d302014-05-24 16:18:35 +0000363 Manifest m = analyzer.getJar().getManifest();
364 if (m == null) {
365 error("No manifest");
366 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000367
Stuart McCulloch5939d302014-05-24 16:18:35 +0000368 Domain domain = Domain.domain(m);
369
370 Set<PackageRef> unresolvedReferences = new TreeSet<PackageRef>(analyzer.getReferred().keySet());
371 unresolvedReferences.removeAll(analyzer.getContained().keySet());
372 for ( String pname : domain.getImportPackage().keySet()) {
373 PackageRef pref = analyzer.getPackageRef(pname);
374 unresolvedReferences.remove(pref);
375 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000376
Stuart McCulloch5939d302014-05-24 16:18:35 +0000377 // Remove any java.** packages.
378 for (Iterator<PackageRef> p = unresolvedReferences.iterator(); p.hasNext();) {
379 PackageRef pack = p.next();
380 if (pack.isJava())
381 p.remove();
382 else {
383 // Remove any dynamic imports
384 if (isDynamicImport(pack))
385 p.remove();
386 }
387 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000388
Stuart McCulloch5939d302014-05-24 16:18:35 +0000389 //
390 // If there is a Require bundle, all bets are off and
391 // we cannot verify anything
392 //
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000393
Stuart McCulloch5939d302014-05-24 16:18:35 +0000394 if (domain.getRequireBundle().isEmpty() && domain.get("ExtensionBundle-Activator") == null
395 && (domain.getFragmentHost()== null || domain.getFragmentHost().getKey().equals("system.bundle"))) {
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000396
Stuart McCulloch5939d302014-05-24 16:18:35 +0000397 if (!unresolvedReferences.isEmpty()) {
398 // Now we want to know the
399 // classes that are the culprits
400 Set<String> culprits = new HashSet<String>();
401 for (Clazz clazz : analyzer.getClassspace().values()) {
402 if (hasOverlap(unresolvedReferences, clazz.getReferred()))
403 culprits.add(clazz.getAbsolutePath());
404 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000405
Stuart McCulloch5939d302014-05-24 16:18:35 +0000406 if (analyzer instanceof Builder)
407 warning("Unresolved references to %s by class(es) %s on the " + Constants.BUNDLE_CLASSPATH + ": %s",
408 unresolvedReferences, culprits, analyzer.getBundleClasspath().keySet());
409 else
410 error("Unresolved references to %s by class(es) %s on the " + Constants.BUNDLE_CLASSPATH + ": %s",
411 unresolvedReferences, culprits, analyzer.getBundleClasspath().keySet());
412 return;
413 }
414 } else if (isPedantic())
415 warning("Use of " + Constants.REQUIRE_BUNDLE + ", ExtensionBundle-Activator, or a system bundle fragment makes it impossible to verify unresolved references");
416 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000417
Stuart McCulloch5939d302014-05-24 16:18:35 +0000418 /**
419 * @param p
420 * @param pack
421 */
422 private boolean isDynamicImport(PackageRef pack) {
423 if (dynamicImports == null)
424 dynamicImports = new Instructions(main.getDynamicImportPackage());
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000425
Stuart McCulloch5939d302014-05-24 16:18:35 +0000426 if (dynamicImports.isEmpty())
427 return false;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000428
Stuart McCulloch5939d302014-05-24 16:18:35 +0000429 return dynamicImports.matches(pack.getFQN());
430 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000431
Stuart McCulloch5939d302014-05-24 16:18:35 +0000432 private boolean hasOverlap(Set< ? > a, Set< ? > b) {
433 for (Iterator< ? > i = a.iterator(); i.hasNext();) {
434 if (b.contains(i.next()))
435 return true;
436 }
437 return false;
438 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000439
Stuart McCulloch5939d302014-05-24 16:18:35 +0000440 public void verify() throws Exception {
441 verifyHeaders();
442 verifyDirectives(Constants.EXPORT_PACKAGE, "uses:|mandatory:|include:|exclude:|" + IMPORT_DIRECTIVE, PACKAGEPATTERN,
443 "package");
444 verifyDirectives(Constants.IMPORT_PACKAGE, "resolution:", PACKAGEPATTERN, "package");
445 verifyDirectives(Constants.REQUIRE_BUNDLE, "visibility:|resolution:", SYMBOLICNAME, "bsn");
446 verifyDirectives(Constants.FRAGMENT_HOST, "extension:", SYMBOLICNAME, "bsn");
447 verifyDirectives(Constants.PROVIDE_CAPABILITY, "effective:|uses:", null, null);
448 verifyDirectives(Constants.REQUIRE_CAPABILITY, "effective:|resolution:|filter:", null, null);
449 verifyDirectives(Constants.BUNDLE_SYMBOLICNAME, "singleton:|fragment-attachment:|mandatory:", SYMBOLICNAME, "bsn");
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000450
Stuart McCulloch5939d302014-05-24 16:18:35 +0000451 verifyManifestFirst();
452 verifyActivator();
453 verifyActivationPolicy();
454 verifyComponent();
455 verifyNative();
456 verifyImports();
457 verifyExports();
458 verifyUnresolvedReferences();
459 verifySymbolicName();
460 verifyListHeader(Constants.BUNDLE_REQUIREDEXECUTIONENVIRONMENT, EENAME, false);
461 verifyHeader(Constants.BUNDLE_MANIFESTVERSION, BUNDLEMANIFESTVERSION, false);
462 verifyHeader(Constants.BUNDLE_VERSION, VERSION, true);
463 verifyListHeader(Constants.BUNDLE_CLASSPATH, FILE, false);
464 verifyDynamicImportPackage();
465 verifyBundleClasspath();
466 verifyUses();
467 if (usesRequire) {
468 if (!getErrors().isEmpty()) {
469 getWarnings()
470 .add(0,
471 "Bundle uses Require Bundle, this can generate false errors because then not enough information is available without the required bundles");
472 }
473 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000474
Stuart McCulloch5939d302014-05-24 16:18:35 +0000475 verifyRequirements();
476 verifyCapabilities();
477 verifyMetaPersistence();
478 verifyPathNames();
479 }
480
481 /**
482 * Verify of the path names in the JAR are valid on all OS's (mainly
483 * windows)
484 */
485 void verifyPathNames() {
486 if (!since(About._2_3))
487 return;
488
489 Set<String> invalidPaths = new HashSet<String>();
490 Pattern pattern = ReservedFileNames;
491 setProperty("@", ReservedFileNames.pattern());
492 String p = getProperty(INVALIDFILENAMES);
493 unsetProperty("@");
494 if (p != null) {
495 try {
496 pattern = Pattern.compile(p, Pattern.CASE_INSENSITIVE);
497 }
498 catch (Exception e) {
499 SetLocation error = error("%s is not a valid regular expression %s: %s", INVALIDFILENAMES,
500 e.getMessage(), p);
501 error.context(p).header(INVALIDFILENAMES);
502 return;
503 }
504 }
505
506 Set<String> segments = new HashSet<String>();
507 for (String path : dot.getResources().keySet()) {
508 String parts[] = path.split("/");
509 for (String part : parts) {
510 if (segments.add(part) && pattern.matcher(part).matches()) {
511 invalidPaths.add(path);
512 }
513 }
514 }
515
516 if (invalidPaths.isEmpty())
517 return;
518
519 error("Invalid file/directory names for Windows in JAR: %s. You can set the regular expression used with %s, the default expression is %s",
520 invalidPaths, INVALIDFILENAMES, ReservedFileNames.pattern());
521 }
522
523 /**
524 * Verify that the imports properly use version ranges.
525 */
526 private void verifyImports() {
527 if (isStrict()) {
528 Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.IMPORT_PACKAGE));
529 Set<String> noimports = new HashSet<String>();
530 Set<String> toobroadimports = new HashSet<String>();
531
532 for (Entry<String,Attrs> e : map.entrySet()) {
533 String version = e.getValue().get(Constants.VERSION_ATTRIBUTE);
534 if (version == null) {
535 if (!e.getKey().startsWith("javax.")) {
536 noimports.add(e.getKey());
537 }
538 } else {
539 if (!VERSIONRANGE.matcher(version).matches()) {
540 Location location = error("Import Package %s has an invalid version range syntax %s",
541 e.getKey(), version).location();
542 location.header = Constants.IMPORT_PACKAGE;
543 location.context = e.getKey();
544 } else {
545 try {
546 VersionRange range = new VersionRange(version);
547 if (!range.isRange()) {
548 toobroadimports.add(e.getKey());
549 }
550 if (range.includeHigh() == false && range.includeLow() == false
551 && range.getLow().equals(range.getHigh())) {
552 Location location = error(
553 "Import Package %s has an empty version range syntax %s, likely want to use [%s,%s]",
554 e.getKey(), version, range.getLow(), range.getHigh()).location();
555 location.header = Constants.IMPORT_PACKAGE;
556 location.context = e.getKey();
557 }
558 // TODO check for exclude low, include high?
559 }
560 catch (Exception ee) {
561 Location location = error("Import Package %s has an invalid version range syntax %s:%s",
562 e.getKey(), version, ee.getMessage()).location();
563 location.header = Constants.IMPORT_PACKAGE;
564 location.context = e.getKey();
565 }
566 }
567 }
568 }
569
570 if (!noimports.isEmpty()) {
571 Location location = error("Import Package clauses without version range (excluding javax.*): %s",
572 noimports).location();
573 location.header = Constants.IMPORT_PACKAGE;
574 }
575 if (!toobroadimports.isEmpty()) {
576 Location location = error(
577 "Import Package clauses which use a version instead of a version range. This imports EVERY later package and not as many expect until the next major number: %s",
578 toobroadimports).location();
579 location.header = Constants.IMPORT_PACKAGE;
580 }
581 }
582 }
583
584 /**
585 * Verify that the exports only use versions.
586 */
587 private void verifyExports() {
588 if (isStrict()) {
589 Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.EXPORT_PACKAGE));
590 Set<String> noexports = new HashSet<String>();
591
592 for (Entry<String,Attrs> e : map.entrySet()) {
593
594 String version = e.getValue().get(Constants.VERSION_ATTRIBUTE);
595 if (version == null) {
596 noexports.add(e.getKey());
597 } else {
598 if (!VERSION.matcher(version).matches()) {
599 Location location;
600 if (VERSIONRANGE.matcher(version).matches()) {
601 location = error(
602 "Export Package %s version is a range: %s; Exports do not allow for ranges.",
603 e.getKey(), version).location();
604 } else {
605 location = error("Export Package %s version has invalid syntax: %s", e.getKey(), version)
606 .location();
607 }
608 location.header = Constants.EXPORT_PACKAGE;
609 location.context = e.getKey();
610 }
611 }
612
613 if (e.getValue().containsKey(Constants.SPECIFICATION_VERSION)) {
614 Location location = error(
615 "Export Package %s uses deprecated specification-version instead of version", e.getKey())
616 .location();
617 location.header = Constants.EXPORT_PACKAGE;
618 location.context = e.getKey();
619 }
620
621 String mandatory = e.getValue().get(Constants.MANDATORY_DIRECTIVE);
622 if (mandatory != null) {
623 Set<String> missing = new HashSet<String>(split(mandatory));
624 missing.removeAll(e.getValue().keySet());
625 if (!missing.isEmpty()) {
626 Location location = error("Export Package %s misses mandatory attribute: %s", e.getKey(),
627 missing).location();
628 location.header = Constants.EXPORT_PACKAGE;
629 location.context = e.getKey();
630 }
631 }
632 }
633
634 if (!noexports.isEmpty()) {
635 Location location = error("Export Package clauses without version range: %s", noexports).location();
636 location.header = Constants.EXPORT_PACKAGE;
637 }
638 }
639 }
640
641 private void verifyRequirements() {
642 Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.REQUIRE_CAPABILITY));
643 for (String key : map.keySet()) {
644 Attrs attrs = map.get(key);
645 verify(attrs, "filter:", FILTERPATTERN, false, "Requirement %s filter not correct", key);
646
647 String filter = attrs.get("filter:");
648 if (filter != null) {
649 String verify = new Filter(filter).verify();
650 if (verify != null)
651 error("Invalid filter syntax in requirement %s=%s. Reason %s", key, attrs, verify);
652 }
653 verify(attrs, "cardinality:", CARDINALITY_PATTERN, false, "Requirement %s cardinality not correct", key);
654 verify(attrs, "resolution:", RESOLUTION_PATTERN, false, "Requirement %s resolution not correct", key);
655
656 if (key.equals("osgi.extender")) {
657 // No requirements on extender
658 } else if (key.equals("osgi.serviceloader")) {
659 verify(attrs, "register:", PACKAGEPATTERN, false,
660 "Service Loader extender register: directive not a fully qualified Java name");
661 } else if (key.equals("osgi.contract")) {
662
663 } else if (key.equals("osgi.service")) {
664
665 } else if (key.equals("osgi.ee")) {
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000666
Stuart McCulloch5939d302014-05-24 16:18:35 +0000667 } else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
668 error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
669 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000670
Stuart McCulloch5939d302014-05-24 16:18:35 +0000671 verifyAttrs(attrs);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000672
Stuart McCulloch5939d302014-05-24 16:18:35 +0000673 if (attrs.containsKey("mandatory:"))
674 error("mandatory: directive is intended for Capabilities, not Requirement %s", key);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000675
Stuart McCulloch5939d302014-05-24 16:18:35 +0000676 if (attrs.containsKey("uses:"))
677 error("uses: directive is intended for Capabilities, not Requirement %s", key);
678 }
679 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000680
Stuart McCulloch5939d302014-05-24 16:18:35 +0000681 /**
682 * @param attrs
683 */
684 void verifyAttrs(Attrs attrs) {
685 for (String a : attrs.keySet()) {
686 String v = attrs.get(a);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000687
Stuart McCulloch5939d302014-05-24 16:18:35 +0000688 if (!a.endsWith(":")) {
689 Attrs.Type t = attrs.getType(a);
690 if ("version".equals(a)) {
691 if (t != Attrs.Type.VERSION)
692 error("Version attributes should always be of type version, it is %s", t);
693 } else
694 verifyType(t, v);
695 }
696 }
697 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000698
Stuart McCulloch5939d302014-05-24 16:18:35 +0000699 private void verifyCapabilities() {
700 Parameters map = parseHeader(manifest.getMainAttributes().getValue(Constants.PROVIDE_CAPABILITY));
701 for (String key : map.keySet()) {
702 Attrs attrs = map.get(key);
703 verify(attrs, "cardinality:", CARDINALITY_PATTERN, false, "Requirement %s cardinality not correct", key);
704 verify(attrs, "resolution:", RESOLUTION_PATTERN, false, "Requirement %s resolution not correct", key);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000705
Stuart McCulloch5939d302014-05-24 16:18:35 +0000706 if (key.equals("osgi.extender")) {
707 verify(attrs, "osgi.extender", SYMBOLICNAME, true,
708 "Extender %s must always have the osgi.extender attribute set", key);
709 verify(attrs, "version", VERSION, true, "Extender %s must always have a version", key);
710 } else if (key.equals("osgi.serviceloader")) {
711 verify(attrs, "register:", PACKAGEPATTERN, false,
712 "Service Loader extender register: directive not a fully qualified Java name");
713 } else if (key.equals("osgi.contract")) {
714 verify(attrs, "osgi.contract", SYMBOLICNAME, true,
715 "Contracts %s must always have the osgi.contract attribute set", key);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000716
Stuart McCulloch5939d302014-05-24 16:18:35 +0000717 } else if (key.equals("osgi.service")) {
718 verify(attrs, "objectClass", MULTIPACKAGEPATTERN, true,
719 "osgi.service %s must have the objectClass attribute set", key);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000720
Stuart McCulloch5939d302014-05-24 16:18:35 +0000721 } else if (key.equals("osgi.ee")) {
722 // TODO
723 } else if (key.startsWith("osgi.wiring.") || key.startsWith("osgi.identity")) {
724 error("osgi.wiring.* namespaces must not be specified with generic requirements/capabilities");
725 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000726
Stuart McCulloch5939d302014-05-24 16:18:35 +0000727 verifyAttrs(attrs);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000728
Stuart McCulloch5939d302014-05-24 16:18:35 +0000729 if (attrs.containsKey("filter:"))
730 error("filter: directive is intended for Requirements, not Capability %s", key);
731 if (attrs.containsKey("cardinality:"))
732 error("cardinality: directive is intended for Requirements, not Capability %s", key);
733 if (attrs.containsKey("resolution:"))
734 error("resolution: directive is intended for Requirements, not Capability %s", key);
735 }
736 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000737
Stuart McCulloch5939d302014-05-24 16:18:35 +0000738 private void verify(Attrs attrs, String ad, Pattern pattern, boolean mandatory, String msg, String... args) {
739 String v = attrs.get(ad);
740 if (v == null) {
741 if (mandatory)
742 error("Missing required attribute/directive %s", ad);
743 } else {
744 Matcher m = pattern.matcher(v);
745 if (!m.matches())
746 error(msg, (Object[]) args);
747 }
748 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000749
Stuart McCulloch5939d302014-05-24 16:18:35 +0000750 private void verifyType(@SuppressWarnings("unused")
751 Attrs.Type type, @SuppressWarnings("unused")
752 String string) {
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000753
Stuart McCulloch5939d302014-05-24 16:18:35 +0000754 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000755
Stuart McCulloch5939d302014-05-24 16:18:35 +0000756 /**
757 * Verify if the header does not contain any other directives
758 *
759 * @param header
760 * @param directives
761 */
762 private void verifyDirectives(String header, String directives, Pattern namePattern, String type) {
763 Pattern pattern = Pattern.compile(directives);
764 Parameters map = parseHeader(manifest.getMainAttributes().getValue(header));
765 for (Entry<String,Attrs> entry : map.entrySet()) {
766 String pname = removeDuplicateMarker(entry.getKey());
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000767
Stuart McCulloch5939d302014-05-24 16:18:35 +0000768 if (namePattern != null) {
769 if (!namePattern.matcher(pname).matches())
770 if (isPedantic())
771 error("Invalid %s name: '%s'", type, pname);
772 else
773 warning("Invalid %s name: '%s'", type, pname);
774 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000775
Stuart McCulloch5939d302014-05-24 16:18:35 +0000776 for (String key : entry.getValue().keySet()) {
777 if (key.endsWith(":")) {
778 if (!key.startsWith("x-")) {
779 Matcher m = pattern.matcher(key);
780 if (m.matches())
781 continue;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000782
Stuart McCulloch5939d302014-05-24 16:18:35 +0000783 warning("Unknown directive %s in %s, allowed directives are %s, and 'x-*'.", key, header,
784 directives.replace('|', ','));
785 }
786 }
787 }
788 }
789 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000790
Stuart McCulloch5939d302014-05-24 16:18:35 +0000791 /**
792 * Verify the use clauses
793 */
794 private void verifyUses() {
795 // Set<String> uses = Create.set();
796 // for ( Map<String,String> attrs : analyzer.getExports().values()) {
797 // if ( attrs.containsKey(Constants.USES_DIRECTIVE)) {
798 // String s = attrs.get(Constants.USES_DIRECTIVE);
799 // uses.addAll( split(s));
800 // }
801 // }
802 // uses.removeAll(analyzer.getExports().keySet());
803 // uses.removeAll(analyzer.getImports().keySet());
804 // if ( !uses.isEmpty())
805 // warning(Constants.EXPORT_PACKAGE + " uses: directive contains packages that are not imported nor exported: %s",
806 // uses);
807 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000808
Stuart McCulloch5939d302014-05-24 16:18:35 +0000809 public boolean verifyActivationPolicy() {
810 String policy = main.get(Constants.BUNDLE_ACTIVATIONPOLICY);
811 if (policy == null)
812 return true;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000813
Stuart McCulloch5939d302014-05-24 16:18:35 +0000814 return verifyActivationPolicy(policy);
815 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000816
Stuart McCulloch5939d302014-05-24 16:18:35 +0000817 public boolean verifyActivationPolicy(String policy) {
818 Parameters map = parseHeader(policy);
819 if (map.size() == 0)
820 warning(Constants.BUNDLE_ACTIVATIONPOLICY + " is set but has no argument %s", policy);
821 else if (map.size() > 1)
822 warning(Constants.BUNDLE_ACTIVATIONPOLICY + " has too many arguments %s", policy);
823 else {
824 Map<String,String> s = map.get("lazy");
825 if (s == null)
826 warning(Constants.BUNDLE_ACTIVATIONPOLICY + " set but is not set to lazy: %s", policy);
827 else
828 return true;
829 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000830
Stuart McCulloch5939d302014-05-24 16:18:35 +0000831 return false;
832 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000833
Stuart McCulloch5939d302014-05-24 16:18:35 +0000834 public void verifyBundleClasspath() {
835 Parameters bcp = main.getBundleClassPath();
836 if (bcp.isEmpty() || bcp.containsKey("."))
837 return;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000838
Stuart McCulloch5939d302014-05-24 16:18:35 +0000839 for (String path : bcp.keySet()) {
840 if (path.endsWith("/"))
841 error("A " + Constants.BUNDLE_CLASSPATH + " entry must not end with '/': %s", path);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000842
Stuart McCulloch5939d302014-05-24 16:18:35 +0000843 if (dot.getDirectories().containsKey(path))
844 // We assume that any classes are in a directory
845 // and therefore do not care when the bundle is included
846 return;
847 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000848
Stuart McCulloch5939d302014-05-24 16:18:35 +0000849 for (String path : dot.getResources().keySet()) {
850 if (path.endsWith(".class")) {
851 warning("The " + Constants.BUNDLE_CLASSPATH + " does not contain the actual bundle JAR (as specified with '.' in the " + Constants.BUNDLE_CLASSPATH + ") but the JAR does contain classes. Is this intentional?");
852 return;
853 }
854 }
855 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000856
Stuart McCulloch5939d302014-05-24 16:18:35 +0000857 /**
858 * <pre>
859 * DynamicImport-Package ::= dynamic-description
860 * ( ',' dynamic-description )*
861 *
862 * dynamic-description::= wildcard-names ( ';' parameter )*
863 * wildcard-names ::= wildcard-name ( ';' wildcard-name )*
864 * wildcard-name ::= package-name
865 * | ( package-name '.*' ) // See 1.4.2
866 * | '*'
867 * </pre>
868 */
869 private void verifyDynamicImportPackage() {
870 verifyListHeader(Constants.DYNAMICIMPORT_PACKAGE, WILDCARDPACKAGE, true);
871 String dynamicImportPackage = get(Constants.DYNAMICIMPORT_PACKAGE);
872 if (dynamicImportPackage == null)
873 return;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000874
Stuart McCulloch5939d302014-05-24 16:18:35 +0000875 Parameters map = main.getDynamicImportPackage();
876 for (String name : map.keySet()) {
877 name = name.trim();
878 if (!verify(name, WILDCARDPACKAGE))
879 error(Constants.DYNAMICIMPORT_PACKAGE + " header contains an invalid package name: " + name);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000880
Stuart McCulloch5939d302014-05-24 16:18:35 +0000881 Map<String,String> sub = map.get(name);
882 if (r3 && sub.size() != 0) {
883 error("DynamicPackage-Import has attributes on import: " + name
884 + ". This is however, an <=R3 bundle and attributes on this header were introduced in R4. ");
885 }
886 }
887 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000888
Stuart McCulloch5939d302014-05-24 16:18:35 +0000889 private void verifyManifestFirst() {
890 if (!dot.isManifestFirst()) {
891 error("Invalid JAR stream: Manifest should come first to be compatible with JarInputStream, it was not");
892 }
893 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000894
Stuart McCulloch5939d302014-05-24 16:18:35 +0000895 private void verifySymbolicName() {
896 Parameters bsn = parseHeader(main.get(Analyzer.BUNDLE_SYMBOLICNAME));
897 if (!bsn.isEmpty()) {
898 if (bsn.size() > 1)
899 error("More than one BSN specified " + bsn);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000900
Stuart McCulloch5939d302014-05-24 16:18:35 +0000901 String name = bsn.keySet().iterator().next();
902 if (!isBsn(name)) {
903 error("Symbolic Name has invalid format: " + name);
904 }
905 }
906 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000907
Stuart McCulloch5939d302014-05-24 16:18:35 +0000908 /**
909 * @param name
910 * @return
911 */
912 public static boolean isBsn(String name) {
913 return SYMBOLICNAME.matcher(name).matches();
914 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000915
Stuart McCulloch5939d302014-05-24 16:18:35 +0000916 /**
917 * <pre>
918 * filter ::= ’(’ filter-comp ’)’
919 * filter-comp ::= and | or | not | operation
920 * and ::= ’&amp;’ filter-list
921 * or ::= ’|’ filter-list
922 * not ::= ’!’ filter
923 * filter-list ::= filter | filter filter-list
924 * operation ::= simple | present | substring
925 * simple ::= attr filter-type value
926 * filter-type ::= equal | approx | greater | less
927 * equal ::= ’=’
928 * approx ::= ’&tilde;=’
929 * greater ::= ’&gt;=’
930 * less ::= ’&lt;=’
931 * present ::= attr ’=*’
932 * substring ::= attr ’=’ initial any final
933 * inital ::= () | value
934 * any ::= ’*’ star-value
935 * star-value ::= () | value ’*’ star-value
936 * final ::= () | value
937 * value ::= &lt;see text&gt;
938 * </pre>
939 *
940 * @param expr
941 * @param index
942 * @return
943 */
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000944
Stuart McCulloch5939d302014-05-24 16:18:35 +0000945 public static int verifyFilter(String expr, int index) {
946 try {
947 while (Character.isWhitespace(expr.charAt(index)))
948 index++;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000949
Stuart McCulloch5939d302014-05-24 16:18:35 +0000950 if (expr.charAt(index) != '(')
951 throw new IllegalArgumentException("Filter mismatch: expected ( at position " + index + " : " + expr);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000952
Stuart McCulloch5939d302014-05-24 16:18:35 +0000953 index++; // skip (
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000954
Stuart McCulloch5939d302014-05-24 16:18:35 +0000955 while (Character.isWhitespace(expr.charAt(index)))
956 index++;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000957
Stuart McCulloch5939d302014-05-24 16:18:35 +0000958 switch (expr.charAt(index)) {
959 case '!' :
960 index++; // skip !
961 while (Character.isWhitespace(expr.charAt(index)))
962 index++;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000963
Stuart McCulloch5939d302014-05-24 16:18:35 +0000964 if (expr.charAt(index) != '(')
965 throw new IllegalArgumentException("Filter mismatch: ! (not) must have one sub expression "
966 + index + " : " + expr);
967 while (Character.isWhitespace(expr.charAt(index)))
968 index++;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000969
Stuart McCulloch5939d302014-05-24 16:18:35 +0000970 index = verifyFilter(expr, index);
971 while (Character.isWhitespace(expr.charAt(index)))
972 index++;
973 if (expr.charAt(index) != ')')
974 throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
975 + expr);
976 return index + 1;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000977
Stuart McCulloch5939d302014-05-24 16:18:35 +0000978 case '&' :
979 case '|' :
980 index++; // skip operator
981 while (Character.isWhitespace(expr.charAt(index)))
982 index++;
983 while (expr.charAt(index) == '(') {
984 index = verifyFilter(expr, index);
985 while (Character.isWhitespace(expr.charAt(index)))
986 index++;
987 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000988
Stuart McCulloch5939d302014-05-24 16:18:35 +0000989 if (expr.charAt(index) != ')')
990 throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
991 + expr);
992 return index + 1; // skip )
Guillaume Nodet1c9211a2014-05-20 08:56:13 +0000993
Stuart McCulloch5939d302014-05-24 16:18:35 +0000994 default :
995 index = verifyFilterOperation(expr, index);
996 if (expr.charAt(index) != ')')
997 throw new IllegalArgumentException("Filter mismatch: expected ) at position " + index + " : "
998 + expr);
999 return index + 1;
1000 }
1001 }
1002 catch (IndexOutOfBoundsException e) {
1003 throw new IllegalArgumentException("Filter mismatch: early EOF from " + index);
1004 }
1005 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001006
Stuart McCulloch5939d302014-05-24 16:18:35 +00001007 static private int verifyFilterOperation(String expr, int index) {
1008 StringBuilder sb = new StringBuilder();
1009 while ("=><~()".indexOf(expr.charAt(index)) < 0) {
1010 sb.append(expr.charAt(index++));
1011 }
1012 String attr = sb.toString().trim();
1013 if (attr.length() == 0)
1014 throw new IllegalArgumentException("Filter mismatch: attr at index " + index + " is 0");
1015 sb = new StringBuilder();
1016 while ("=><~".indexOf(expr.charAt(index)) >= 0) {
1017 sb.append(expr.charAt(index++));
1018 }
1019 String operator = sb.toString();
1020 if (!verify(operator, FILTEROP))
1021 throw new IllegalArgumentException("Filter error, illegal operator " + operator + " at index " + index);
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001022
Stuart McCulloch5939d302014-05-24 16:18:35 +00001023 sb = new StringBuilder();
1024 while (")".indexOf(expr.charAt(index)) < 0) {
1025 switch (expr.charAt(index)) {
1026 case '\\' :
1027 if ("\\)(*".indexOf(expr.charAt(index + 1)) >= 0)
1028 index++;
1029 else
1030 throw new IllegalArgumentException("Filter error, illegal use of backslash at index " + index
1031 + ". Backslash may only be used before * or () or \\");
1032 }
1033 sb.append(expr.charAt(index++));
1034 }
1035 return index;
1036 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001037
Stuart McCulloch5939d302014-05-24 16:18:35 +00001038 private boolean verifyHeader(String name, Pattern regex, boolean error) {
1039 String value = manifest.getMainAttributes().getValue(name);
1040 if (value == null)
1041 return false;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001042
Stuart McCulloch5939d302014-05-24 16:18:35 +00001043 QuotedTokenizer st = new QuotedTokenizer(value.trim(), ",");
1044 for (Iterator<String> i = st.getTokenSet().iterator(); i.hasNext();) {
1045 if (!verify(i.next(), regex)) {
1046 String msg = "Invalid value for " + name + ", " + value + " does not match " + regex.pattern();
1047 if (error)
1048 error(msg);
1049 else
1050 warning(msg);
1051 }
1052 }
1053 return true;
1054 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001055
Stuart McCulloch5939d302014-05-24 16:18:35 +00001056 static private boolean verify(String value, Pattern regex) {
1057 return regex.matcher(value).matches();
1058 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001059
Stuart McCulloch5939d302014-05-24 16:18:35 +00001060 private boolean verifyListHeader(String name, Pattern regex, boolean error) {
1061 String value = manifest.getMainAttributes().getValue(name);
1062 if (value == null)
1063 return false;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001064
Stuart McCulloch5939d302014-05-24 16:18:35 +00001065 Parameters map = parseHeader(value);
1066 for (String header : map.keySet()) {
1067 if (!regex.matcher(header).matches()) {
1068 String msg = "Invalid value for " + name + ", " + value + " does not match " + regex.pattern();
1069 if (error)
1070 error(msg);
1071 else
1072 warning(msg);
1073 }
1074 }
1075 return true;
1076 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001077
Stuart McCulloch5939d302014-05-24 16:18:35 +00001078 // @Override
1079 // public String getProperty(String key, String deflt) {
1080 // if (properties == null)
1081 // return deflt;
1082 // return properties.getProperty(key, deflt);
1083 // }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001084
Stuart McCulloch5939d302014-05-24 16:18:35 +00001085 public static boolean isVersion(String version) {
1086 return VERSION.matcher(version).matches();
1087 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001088
Stuart McCulloch5939d302014-05-24 16:18:35 +00001089 public static boolean isIdentifier(String value) {
1090 if (value.length() < 1)
1091 return false;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001092
Stuart McCulloch5939d302014-05-24 16:18:35 +00001093 if (!Character.isJavaIdentifierStart(value.charAt(0)))
1094 return false;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001095
Stuart McCulloch5939d302014-05-24 16:18:35 +00001096 for (int i = 1; i < value.length(); i++) {
1097 if (!Character.isJavaIdentifierPart(value.charAt(i)))
1098 return false;
1099 }
1100 return true;
1101 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001102
Stuart McCulloch5939d302014-05-24 16:18:35 +00001103 public static boolean isMember(String value, String[] matches) {
1104 for (String match : matches) {
1105 if (match.equals(value))
1106 return true;
1107 }
1108 return false;
1109 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001110
Stuart McCulloch5939d302014-05-24 16:18:35 +00001111 public static boolean isFQN(String name) {
1112 if (name.length() == 0)
1113 return false;
1114 if (!Character.isJavaIdentifierStart(name.charAt(0)))
1115 return false;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001116
Stuart McCulloch5939d302014-05-24 16:18:35 +00001117 for (int i = 1; i < name.length(); i++) {
1118 char c = name.charAt(i);
1119 if (Character.isJavaIdentifierPart(c) || c == '$' || c == '.')
1120 continue;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001121
Stuart McCulloch5939d302014-05-24 16:18:35 +00001122 return false;
1123 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001124
Stuart McCulloch5939d302014-05-24 16:18:35 +00001125 return true;
1126 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001127
Stuart McCulloch5939d302014-05-24 16:18:35 +00001128 /**
1129 * Verify checksums
1130 */
1131 /**
1132 * Verify the checksums from the manifest against the real thing.
1133 *
1134 * @param all
1135 * if each resources must be digested
1136 * @return true if ok
1137 * @throws Exception
1138 */
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001139
Stuart McCulloch5939d302014-05-24 16:18:35 +00001140 public void verifyChecksums(boolean all) throws Exception {
1141 Manifest m = dot.getManifest();
1142 if (m == null || m.getEntries().isEmpty()) {
1143 if (all)
1144 error("Verify checksums with all but no digests");
1145 return;
1146 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001147
Stuart McCulloch5939d302014-05-24 16:18:35 +00001148 List<String> missingDigest = new ArrayList<String>();
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001149
Stuart McCulloch5939d302014-05-24 16:18:35 +00001150 for (String path : dot.getResources().keySet()) {
1151 if (path.equals("META-INF/MANIFEST.MF"))
1152 continue;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001153
Stuart McCulloch5939d302014-05-24 16:18:35 +00001154 Attributes a = m.getAttributes(path);
1155 String digest = a.getValue("SHA1-Digest");
1156 if (digest == null) {
1157 if (!path.matches(""))
1158 missingDigest.add(path);
1159 } else {
1160 byte[] d = Base64.decodeBase64(digest);
1161 SHA1 expected = new SHA1(d);
1162 Digester<SHA1> digester = SHA1.getDigester();
1163 InputStream in = dot.getResource(path).openInputStream();
1164 IO.copy(in, digester);
1165 digester.digest();
1166 if (!expected.equals(digester.digest())) {
1167 error("Checksum mismatch %s, expected %s, got %s", path, expected, digester.digest());
1168 }
1169 }
1170 }
1171 if (missingDigest.size() > 0) {
1172 error("Entries in the manifest are missing digests: %s", missingDigest);
1173 }
1174 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001175
Stuart McCulloch5939d302014-05-24 16:18:35 +00001176 /**
1177 * Verify the EXTENDED_S syntax
1178 *
1179 * @param key
1180 * @return
1181 */
1182 public static boolean isExtended(String key) {
1183 if (key == null)
1184 return false;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001185
Stuart McCulloch5939d302014-05-24 16:18:35 +00001186 return EXTENDED_P.matcher(key).matches();
1187 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001188
Stuart McCulloch5939d302014-05-24 16:18:35 +00001189 /**
1190 * Verify the ARGUMENT_S syntax
1191 *
1192 * @param key
1193 * @return
1194 */
1195 public static boolean isArgument(String arg) {
1196 return arg != null && ARGUMENT_P.matcher(arg).matches();
1197 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001198
Stuart McCulloch5939d302014-05-24 16:18:35 +00001199 /**
1200 * Verify the QUOTEDSTRING syntax
1201 *
1202 * @param key
1203 * @return
1204 */
1205 public static boolean isQuotedString(String s) {
1206 return s != null && QUOTEDSTRING_P.matcher(s).matches();
1207 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001208
Stuart McCulloch5939d302014-05-24 16:18:35 +00001209 public static boolean isVersionRange(String range) {
1210 return range != null && VERSIONRANGE_P.matcher(range).matches();
1211 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001212
Stuart McCulloch5939d302014-05-24 16:18:35 +00001213 /**
1214 * Verify the Meta-Persistence header
1215 *
1216 * @throws Exception
1217 */
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001218
Stuart McCulloch5939d302014-05-24 16:18:35 +00001219 public void verifyMetaPersistence() throws Exception {
1220 List<String> list = new ArrayList<String>();
1221 String mp = dot.getManifest().getMainAttributes().getValue(META_PERSISTENCE);
1222 for (String location : OSGiHeader.parseHeader(mp).keySet()) {
1223 String[] parts = location.split("!/");
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001224
Stuart McCulloch5939d302014-05-24 16:18:35 +00001225 Resource resource = dot.getResource(parts[0]);
1226 if (resource == null)
1227 list.add(location);
1228 else {
1229 if (parts.length > 1) {
1230 Jar jar = new Jar("", resource.openInputStream());
1231 try {
1232 resource = jar.getResource(parts[1]);
1233 if (resource == null)
1234 list.add(location);
1235 }
1236 catch (Exception e) {
1237 list.add(location);
1238 }
1239 finally {
1240 jar.close();
1241 }
1242 }
1243 }
1244 }
1245 if (list.isEmpty())
1246 return;
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001247
Stuart McCulloch5939d302014-05-24 16:18:35 +00001248 error(Constants.META_PERSISTENCE + " refers to resources not in the bundle: %s", list).header(Constants.META_PERSISTENCE);
1249 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001250
Stuart McCulloch5939d302014-05-24 16:18:35 +00001251 /**
1252 * @return the frombuilder
1253 */
1254 public boolean isFrombuilder() {
1255 return frombuilder;
1256 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001257
Stuart McCulloch5939d302014-05-24 16:18:35 +00001258 /**
1259 * @param frombuilder
1260 * the frombuilder to set
1261 */
1262 public void setFrombuilder(boolean frombuilder) {
1263 this.frombuilder = frombuilder;
1264 }
Guillaume Nodet1c9211a2014-05-20 08:56:13 +00001265}