/* * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ package org.apache.felix.framework.util.manifestparser; import java.nio.ByteOrder; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Dictionary; import java.util.HashMap; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; import org.apache.felix.framework.Logger; import org.apache.felix.framework.util.FelixConstants; import org.apache.felix.framework.util.VersionRange; import org.osgi.framework.BundleException; import org.osgi.framework.Constants; import org.osgi.framework.Filter; import org.osgi.framework.FrameworkUtil; import org.osgi.framework.Version; public class NativeLibraryClause { private static final String OS_AIX = "aix"; private static final String OS_DIGITALUNIX = "digitalunix"; private static final String OS_EPOC = "epoc32"; private static final String OS_HPUX = "hpux"; private static final String OS_IRIX = "irix"; private static final String OS_LINUX = "linux"; private static final String OS_MACOS = "macos"; private static final String OS_MACOSX = "macosx"; private static final String OS_NETBSD = "netbsd"; private static final String OS_NETWARE = "netware"; private static final String OS_OPENBSD = "openbsd"; private static final String OS_OS2 = "os2"; private static final String OS_QNX = "qnx"; private static final String OS_SOLARIS = "solaris"; private static final String OS_SUNOS = "sunos"; private static final String OS_VXWORKS = "vxworks"; private static final String OS_WINDOWS_2000 = "windows2000"; private static final String OS_WINDOWS_2003 = "windows2003"; private static final String OS_WINDOWS_7 = "windows7"; private static final String OS_WINDOWS_8 = "windows8"; private static final String OS_WINDOWS_9 = "windows9"; private static final String OS_WINDOWS_10 = "windows10"; private static final String OS_WINDOWS_95 = "windows95"; private static final String OS_WINDOWS_98 = "windows98"; private static final String OS_WINDOWS_CE = "windowsce"; private static final String OS_WINDOWS_NT = "windowsnt"; private static final String OS_WINDOWS_SERVER_2008 = "windowsserver2008"; private static final String OS_WINDOWS_SERVER_2012 = "windowsserver2012"; private static final String OS_WINDOWS_SERVER_2016 = "windowsserver2016"; private static final String OS_WINDOWS_VISTA = "windowsvista"; private static final String OS_WINDOWS_XP = "windowsxp"; private static final String OS_WIN_32 = "win32"; private static final String PROC_X86_64 = "x86-64"; private static final String PROC_X86 = "x86"; private static final String PROC_68K = "68k"; private static final String PROC_ARM_LE = "arm_le"; private static final String PROC_ARM_BE = "arm_be"; private static final String PROC_ARM = "arm"; private static final String PROC_ALPHA = "alpha"; private static final String PROC_IGNITE = "ignite"; private static final String PROC_MIPS = "mips"; private static final String PROC_PARISC = "parisc"; private static final String PROC_POWER_PC = "powerpc"; private static final String PROC_SPARC = "sparc"; private static final Map<String, List<String>> OS_ALIASES = new HashMap<String, List<String>>(); private static final Map<String, List<String>> PROC_ALIASES = new HashMap<String, List<String>>(); private final String[] m_libraryEntries; private final String[] m_osnames; private final String[] m_processors; private final String[] m_osversions; private final String[] m_languages; private final String m_selectionFilter; public NativeLibraryClause(String[] libraryEntries, String[] osnames, String[] processors, String[] osversions, String[] languages, String selectionFilter) { m_libraryEntries = libraryEntries; m_osnames = osnames; m_processors = processors; m_osversions = osversions; m_languages = languages; m_selectionFilter = selectionFilter; } public NativeLibraryClause(NativeLibraryClause library) { this(library.m_libraryEntries, library.m_osnames, library.m_osversions, library.m_processors, library.m_languages, library.m_selectionFilter); } /** * Initialize the processor and os name aliases from Felix Config. * * @param config */ public static synchronized void initializeNativeAliases(Map configMap) { Map<String, String> osNameKeyMap = getAllKeysWithPrefix(FelixConstants.NATIVE_OS_NAME_ALIAS_PREFIX, configMap); Map<String, String> processorKeyMap = getAllKeysWithPrefix(FelixConstants.NATIVE_PROC_NAME_ALIAS_PREFIX, configMap); parseNativeAliases(osNameKeyMap, OS_ALIASES); parseNativeAliases(processorKeyMap, PROC_ALIASES); } private static void parseNativeAliases(Map<String, String> aliasStringMap, Map<String, List<String>> aliasMap) { for(Map.Entry<String, String> aliasEntryString: aliasStringMap.entrySet()) { String currentAliasKey = aliasEntryString.getKey(); String currentNormalizedName = currentAliasKey.substring(currentAliasKey.lastIndexOf(".")+1); String currentAliasesString = aliasEntryString.getValue(); if(currentAliasesString != null) { String[] aliases = currentAliasesString.split(","); List<String> fullAliasList = new ArrayList<String>(); //normalized name is always first. fullAliasList.add(currentNormalizedName); fullAliasList.addAll(Arrays.asList(aliases)); aliasMap.put(currentNormalizedName, fullAliasList); for(String currentAlias: aliases) { List<String> aliasList = aliasMap.get(currentAlias); if(aliasList == null) { aliasMap.put(currentAlias, fullAliasList); } else { for(String newAliases: aliases) { if(!aliasList.contains(newAliases)) { aliasList.add(newAliases); } } } } } else { List<String> aliasList = aliasMap.get(currentNormalizedName); if(aliasList == null) { aliasMap.put(currentNormalizedName, new ArrayList<String>(Collections.singletonList(currentNormalizedName))); } else { //if the alias is also a normalized name make sure it's first aliasList.add(0, currentNormalizedName); } } } } private static Map<String, String> getAllKeysWithPrefix(String prefix, Map<String, String> configMap) { Map<String, String> keysWithPrefix = new HashMap<String, String>(); for(Map.Entry<String, String> currentEntry: configMap.entrySet()) { if(currentEntry.getKey().startsWith(prefix)) { keysWithPrefix.put(currentEntry.getKey(), currentEntry.getValue()); } } return keysWithPrefix; } public String[] getLibraryEntries() { return m_libraryEntries; } public String[] getOSNames() { return m_osnames; } public String[] getProcessors() { return m_processors; } public String[] getOSVersions() { return m_osversions; } public String[] getLanguages() { return m_languages; } public String getSelectionFilter() { return m_selectionFilter; } public boolean match(Map configMap) throws BundleException { String osName = (String) configMap.get(FelixConstants.FRAMEWORK_OS_NAME); String processorName = (String) configMap.get(FelixConstants.FRAMEWORK_PROCESSOR); String osVersion = (String) configMap.get(FelixConstants.FRAMEWORK_OS_VERSION); String language = (String) configMap.get(FelixConstants.FRAMEWORK_LANGUAGE); // Check library's osname. if (!checkOSNames(osName, getOSNames())) { return false; } // Check library's processor. if (!checkProcessors(processorName, getProcessors())) { return false; } // Check library's osversion if specified. if ((getOSVersions() != null) && (getOSVersions().length > 0) && !checkOSVersions(osVersion, getOSVersions())) { return false; } // Check library's language if specified. if ((getLanguages() != null) && (getLanguages().length > 0) && !checkLanguages(language, getLanguages())) { return false; } // Check library's selection-filter if specified. if ((getSelectionFilter() != null) && (getSelectionFilter().length() >= 0) && !checkSelectionFilter(configMap, getSelectionFilter())) { return false; } return true; } private boolean checkOSNames(String osName, String[] osnames) { List<String> capabilityOsNames = getOsNameWithAliases(osName); if (capabilityOsNames != null && osnames != null) { for (String curOsName : osnames) { if (capabilityOsNames.contains(curOsName)) { return true; } } } return false; } private boolean checkProcessors(String processorName, String[] processors) { List<String> capabilitiesProcessors = getProcessorWithAliases(processorName); if (capabilitiesProcessors != null && processors != null) { for (String currentProcessor : processors) { if (capabilitiesProcessors.contains(currentProcessor)) { return true; } } } return false; } private boolean checkOSVersions(String osVersion, String[] osversions) throws BundleException { Version currentOSVersion = Version.parseVersion(normalizeOSVersion(osVersion)); for (int i = 0; (osversions != null) && (i < osversions.length); i++) { try { VersionRange range = VersionRange.parse(osversions[i]); if (range.isInRange(currentOSVersion)) { return true; } } catch (Exception ex) { throw new BundleException( "Error evaluating osversion: " + osversions[i], ex); } } return false; } private boolean checkLanguages(String currentLanguage, String[] languages) { for (int i = 0; (languages != null) && (i < languages.length); i++) { if (languages[i].equals(currentLanguage)) { return true; } } return false; } private boolean checkSelectionFilter(Map configMap, String expr) throws BundleException { // Get all framework properties Dictionary dict = new Hashtable(); for (Iterator i = configMap.keySet().iterator(); i.hasNext(); ) { Object key = i.next(); dict.put(key, configMap.get(key)); } // Compute expression try { Filter filter = FrameworkUtil.createFilter(expr); return filter.match(dict); } catch (Exception ex) { throw new BundleException( "Error evaluating filter expression: " + expr, ex); } } public static NativeLibraryClause parse(Logger logger, String s) { try { if ((s == null) || (s.length() == 0)) { return null; } s = s.trim(); if (s.equals(FelixConstants.BUNDLE_NATIVECODE_OPTIONAL)) { return new NativeLibraryClause(null, null, null, null, null, null); } // The tokens are separated by semicolons and may include // any number of libraries along with one set of associated // properties. StringTokenizer st = new StringTokenizer(s, ";"); String[] libEntries = new String[st.countTokens()]; List osNameList = new ArrayList(); List osVersionList = new ArrayList(); List processorList = new ArrayList(); List languageList = new ArrayList(); String selectionFilter = null; int libCount = 0; while (st.hasMoreTokens()) { String token = st.nextToken().trim(); if (token.indexOf('=') < 0) { // Remove the slash, if necessary. libEntries[libCount] = (token.charAt(0) == '/') ? token.substring(1) : token; libCount++; } else { // Check for valid native library properties; defined as // a property name, an equal sign, and a value. // NOTE: StringTokenizer can not be used here because // a value can contain one or more "=" too, e.g., // selection-filter="(org.osgi.framework.windowing.system=gtk)" String property = null; String value = null; if (!(token.indexOf("=") > 1)) { throw new IllegalArgumentException( "Bundle manifest native library entry malformed: " + token); } else { property = (token.substring(0, token.indexOf("="))) .trim().toLowerCase(); value = (token.substring(token.indexOf("=") + 1, token .length())).trim(); } // Values may be quoted, so remove quotes if present. if (value.charAt(0) == '"') { // This should always be true, otherwise the // value wouldn't be properly quoted, but we // will check for safety. if (value.charAt(value.length() - 1) == '"') { value = value.substring(1, value.length() - 1); } else { value = value.substring(1); } } if (value != null) { value = value.toLowerCase(); } // Add the value to its corresponding property list. if (property.equals(Constants.BUNDLE_NATIVECODE_OSNAME)) { osNameList.add(value); } else if (property.equals(Constants.BUNDLE_NATIVECODE_OSVERSION)) { osVersionList.add(normalizeOSVersionRange(value)); } else if (property.equals(Constants.BUNDLE_NATIVECODE_PROCESSOR)) { processorList.add(value); } else if (property.equals(Constants.BUNDLE_NATIVECODE_LANGUAGE)) { languageList.add(value); } else if (property.equals(Constants.SELECTION_FILTER_ATTRIBUTE)) { // TODO: NATIVE - I believe we can have multiple selection filters too. selectionFilter = value; } } } if (libCount == 0) { return null; } // Shrink lib file array. String[] actualLibEntries = new String[libCount]; System.arraycopy(libEntries, 0, actualLibEntries, 0, libCount); return new NativeLibraryClause( actualLibEntries, (String[]) osNameList.toArray(new String[osNameList.size()]), (String[]) processorList.toArray(new String[processorList.size()]), (String[]) osVersionList.toArray(new String[osVersionList.size()]), (String[]) languageList.toArray(new String[languageList.size()]), selectionFilter); } catch (RuntimeException ex) { logger.log(Logger.LOG_ERROR, "Error parsing native library header.", ex); throw ex; } } public static List<String> getOsNameWithAliases(String osName) { //Can't assume this has been normalized osName = normalizeOSName(osName); List<String> result = OS_ALIASES.get(osName); if(result == null) { result = Collections.singletonList(osName); } return Collections.unmodifiableList(result); } public static List<String> getProcessorWithAliases(String processor) { //Can't assume this has been normalized processor = normalizeProcessor(processor); List<String> result = PROC_ALIASES.get(processor); if(result == null) { result = Collections.singletonList(processor); } return Collections.unmodifiableList(result); } public static String normalizeOSName(String value) { value = value.toLowerCase(); if (OS_ALIASES.containsKey(value)) { // we found an alias match return the first value which is the normalized name return OS_ALIASES.get(value).get(0); } //If we don't find a match do it the old way for compatibility if (value.startsWith("win")) { String os = OS_WIN_32; if (value.indexOf("32") >= 0 || value.indexOf("*") >= 0) { os = OS_WIN_32; } else if (value.indexOf("95") >= 0) { os = OS_WINDOWS_95; } else if (value.indexOf("98") >= 0) { os = OS_WINDOWS_98; } else if (value.indexOf("nt") >= 0) { os = OS_WINDOWS_NT; } else if (value.indexOf("2000") >= 0) { os = OS_WINDOWS_2000; } else if (value.indexOf("2003") >= 0) { os = OS_WINDOWS_2003; } else if (value.indexOf("2008") >= 0) { os = OS_WINDOWS_SERVER_2008; } else if (value.indexOf("2012") >= 0) { os = OS_WINDOWS_SERVER_2012; } else if (value.indexOf("2016") >= 0) { os = OS_WINDOWS_SERVER_2016; } else if (value.indexOf("xp") >= 0) { os = OS_WINDOWS_XP; } else if (value.indexOf("ce") >= 0) { os = OS_WINDOWS_CE; } else if (value.indexOf("vista") >= 0) { os = OS_WINDOWS_VISTA; } else if ((value.indexOf(" 7") >= 0) || value.startsWith(OS_WINDOWS_7) || value.equals("win7")) { os = OS_WINDOWS_7; } else if ((value.indexOf(" 8") >= 0) || value.startsWith(OS_WINDOWS_8) || value.equals("win8")) { os = OS_WINDOWS_8; } else if ((value.indexOf(" 9") >= 0) || value.startsWith(OS_WINDOWS_9) || value.equals("win9")) { os = OS_WINDOWS_9; } else if ((value.indexOf(" 10") >= 0) || value.startsWith(OS_WINDOWS_10) || value.equals("win10")) { os = OS_WINDOWS_10; } return os; } else if (value.startsWith(OS_LINUX)) { return OS_LINUX; } else if (value.startsWith(OS_AIX)) { return OS_AIX; } else if (value.startsWith(OS_DIGITALUNIX)) { return OS_DIGITALUNIX; } else if (value.startsWith(OS_HPUX)) { return OS_HPUX; } else if (value.startsWith(OS_IRIX)) { return OS_IRIX; } else if (value.startsWith(OS_MACOSX) || value.startsWith("mac os x")) { return OS_MACOSX; } else if (value.startsWith(OS_MACOS) || value.startsWith("mac os")) { return OS_MACOS; } else if (value.startsWith(OS_NETWARE)) { return OS_NETWARE; } else if (value.startsWith(OS_OPENBSD)) { return OS_OPENBSD; } else if (value.startsWith(OS_NETBSD)) { return OS_NETBSD; } else if (value.startsWith(OS_OS2) || value.startsWith("os/2")) { return OS_OS2; } else if (value.startsWith(OS_QNX) || value.startsWith("procnto")) { return OS_QNX; } else if (value.startsWith(OS_SOLARIS)) { return OS_SOLARIS; } else if (value.startsWith(OS_SUNOS)) { return OS_SUNOS; } else if (value.startsWith(OS_VXWORKS)) { return OS_VXWORKS; } else if (value.startsWith(OS_EPOC)) { return OS_EPOC; } return value; } public static String normalizeProcessor(String value) { value = value.toLowerCase(); if(PROC_ALIASES.containsKey(value)) { return PROC_ALIASES.get(value).get(0); } if (value.startsWith(PROC_X86_64) || value.startsWith("amd64") || value.startsWith("em64") || value.startsWith("x86_64")) { return PROC_X86_64; } else if (value.startsWith(PROC_X86) || value.startsWith("pentium") || value.startsWith("i386") || value.startsWith("i486") || value.startsWith("i586") || value.startsWith("i686")) { return PROC_X86; } else if (value.startsWith(PROC_68K)) { return PROC_68K; } else if (value.startsWith(PROC_ARM_LE)) { return PROC_ARM_LE; } else if (value.startsWith(PROC_ARM_BE)) { return PROC_ARM_BE; } else if (value.startsWith(PROC_ARM)) { return ByteOrder.nativeOrder() == ByteOrder.BIG_ENDIAN ? PROC_ARM_BE : PROC_ARM_LE; } else if (value.startsWith(PROC_ALPHA)) { return PROC_ALPHA; } else if (value.startsWith(PROC_IGNITE) || value.startsWith("psc1k")) { return PROC_IGNITE; } else if (value.startsWith(PROC_MIPS)) { return PROC_MIPS; } else if (value.startsWith(PROC_PARISC)) { return PROC_PARISC; } else if (value.startsWith(PROC_POWER_PC) || value.startsWith("power") || value.startsWith("ppc")) { return PROC_POWER_PC; } else if (value.startsWith(PROC_SPARC)) { return PROC_SPARC; } return value; } public static String normalizeOSVersionRange(String value) { if (value.indexOf(',') >= 0) { try { String s = value.substring(1, value.length() - 1); String vlo = s.substring(0, s.indexOf(',')).trim(); String vhi = s.substring(s.indexOf(',') + 1, s.length()).trim(); return new VersionRange(new Version(cleanupVersion(vlo)), (value.charAt(0) == '['), new Version( cleanupVersion(vhi)), (value.charAt(value.length() - 1) == ']')).toString(); } catch (Exception ex) { return Version.emptyVersion.toString(); } } return normalizeOSVersion(value); } public static String normalizeOSVersion(String value) { return new Version(cleanupVersion(value)).toString(); } private static final Pattern FUZZY_VERSION = Pattern.compile( "(\\d+)(\\.(\\d+)(\\.(\\d+))?)?([^a-zA-Z0-9](.*))?", Pattern.DOTALL ); private static String cleanupVersion( String version ) { StringBuffer result = new StringBuffer(); Matcher m = FUZZY_VERSION.matcher( version ); if ( m.matches() ) { String major = m.group( 1 ); String minor = m.group( 3 ); String micro = m.group( 5 ); String qualifier = m.group( 7 ); if ( major != null ) { result.append( major ); if ( minor != null ) { result.append( "." ); result.append( minor ); if ( micro != null ) { result.append( "." ); result.append( micro ); if ( qualifier != null ) { result.append( "." ); cleanupModifier( result, qualifier ); } } else if ( qualifier != null ) { result.append( ".0." ); cleanupModifier( result, qualifier ); } else { result.append( ".0" ); } } else if ( qualifier != null ) { result.append( ".0.0." ); cleanupModifier( result, qualifier ); } else { result.append( ".0.0" ); } } } else { result.append( "0.0.0." ); cleanupModifier( result, version ); } return result.toString(); } private static void cleanupModifier( StringBuffer result, String modifier ) { for ( int i = 0; i < modifier.length(); i++ ) { char c = modifier.charAt( i ); if ( ( c >= '0' && c <= '9' ) || ( c >= 'a' && c <= 'z' ) || ( c >= 'A' && c <= 'Z' ) || c == '_' || c == '-' ) result.append( c ); else result.append( '_' ); } } }