/* * Copyright (c) 1998-2011 Caucho Technology -- all rights reserved * * This file is part of Resin(R) Open Source * * Each copy or derived work must preserve the copyright notice and this * notice unmodified. * * Resin Open Source is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * Resin Open Source is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE, or any warranty * of NON-INFRINGEMENT. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with Resin Open Source; if not, write to the * * Free Software Foundation, Inc. * 59 Temple Place, Suite 330 * Boston, MA 02111-1307 USA * * @author Scott Ferguson */ package com.caucho.quercus; import java.io.IOException; import java.sql.Connection; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.LockSupport; import java.util.logging.Level; import java.util.logging.Logger; import javax.cache.Cache; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.sql.DataSource; import com.caucho.config.ConfigException; import com.caucho.java.JavaCompiler; import com.caucho.quercus.annotation.ClassImplementation; import com.caucho.quercus.env.ArrayValue; import com.caucho.quercus.env.ArrayValueImpl; import com.caucho.quercus.env.BooleanValue; import com.caucho.quercus.env.ConstStringValue; import com.caucho.quercus.env.DoubleValue; import com.caucho.quercus.env.Env; import com.caucho.quercus.env.LongValue; import com.caucho.quercus.env.MethodIntern; import com.caucho.quercus.env.NullValue; import com.caucho.quercus.env.QuercusClass; import com.caucho.quercus.env.SessionArrayValue; import com.caucho.quercus.env.StringValue; import com.caucho.quercus.env.UnicodeBuilderValue; import com.caucho.quercus.env.Value; import com.caucho.quercus.expr.ExprFactory; import com.caucho.quercus.function.AbstractFunction; import com.caucho.quercus.lib.db.JavaSqlDriverWrapper; import com.caucho.quercus.lib.file.FileModule; import com.caucho.quercus.lib.regexp.RegexpModule; import com.caucho.quercus.lib.session.QuercusSessionManager; import com.caucho.quercus.module.IniDefinition; import com.caucho.quercus.module.IniDefinitions; import com.caucho.quercus.module.ModuleContext; import com.caucho.quercus.module.ModuleInfo; import com.caucho.quercus.module.ModuleStartupListener; import com.caucho.quercus.module.QuercusModule; import com.caucho.quercus.page.InterpretedPage; import com.caucho.quercus.page.PageManager; import com.caucho.quercus.page.QuercusPage; import com.caucho.quercus.parser.QuercusParser; import com.caucho.quercus.program.ClassDef; import com.caucho.quercus.program.JavaClassDef; import com.caucho.quercus.program.QuercusProgram; import com.caucho.quercus.program.UndefinedFunction; import com.caucho.util.IntMap; import com.caucho.util.L10N; import com.caucho.util.LruCache; import com.caucho.util.TimedCache; import com.caucho.vfs.FilePath; import com.caucho.vfs.Path; import com.caucho.vfs.ReadStream; import com.caucho.vfs.WriteStream; /** * Facade for the PHP language. */ public class QuercusContext { private static L10N L = new L10N(QuercusContext.class); private static final Logger log = Logger.getLogger(QuercusContext.class.getName()); private static HashSet<String> _superGlobals = new HashSet<String>(); private static IniDefinitions _ini = new IniDefinitions(); private final PageManager _pageManager; private final QuercusSessionManager _sessionManager; private final ClassLoader _loader; private ModuleContext _moduleContext; private static LruCache<String, UnicodeBuilderValue> _unicodeMap = new LruCache<String, UnicodeBuilderValue>(8 * 1024); private static LruCache<String, StringValue> _stringMap = new LruCache<String, StringValue>(8 * 1024); private HashMap<String, ModuleInfo> _modules = new HashMap<String, ModuleInfo>(); private HashSet<ModuleStartupListener> _moduleStartupListeners = new HashSet<ModuleStartupListener>(); private HashSet<String> _extensionSet = new HashSet<String>(); private HashSet<String> _extensionSetLowerCase = new HashSet<String>(); private HashMap<String, AbstractFunction> _funMap = new HashMap<String, AbstractFunction>(); private HashMap<String, AbstractFunction> _lowerFunMap = new HashMap<String, AbstractFunction>(); /* private ClassDef _stdClassDef; private QuercusClass _stdClass; */ private ConcurrentHashMap<String, JavaClassDef> _javaClassWrappers = new ConcurrentHashMap<String, JavaClassDef>(); private LruCache<String, String> _classNotFoundCache = new LruCache<String, String>(64); private HashMap<String, JavaClassDef> _lowerJavaClassWrappers = new HashMap<String, JavaClassDef>(); private final IniDefinitions _iniDefinitions = new IniDefinitions(); private Path _iniFile; private HashMap<String, Value> _iniMap; private HashMap<Value, Value> _serverEnvMap = new HashMap<Value, Value>(); private IntMap _classNameMap = new IntMap(8192); private String []_classNames = new String[256]; private ClassDef []_classDefMap = new ClassDef[256]; private QuercusClass []_classCacheMap = new QuercusClass[256]; private IntMap _constantNameMap = new IntMap(8192); private int []_constantLowerMap = new int[256]; private Value []_constantNameList = new Value[256]; private Value []_constantMap = new Value[256]; // protected to allow locking from pro protected IntMap _functionNameMap = new IntMap(8192); private AbstractFunction []_functionMap = new AbstractFunction[256]; private LruCache<String, QuercusProgram> _evalCache = new LruCache<String, QuercusProgram>(4096); private int _includeCacheMax = 8192; private long _includeCacheTimeout = 10000L; private TimedCache<IncludeKey, Path> _includeCache; //private LruCache<DefinitionKey,SoftReference<DefinitionState>> _defCache // = new LruCache<DefinitionKey,SoftReference<DefinitionState>>(4096); private long _defCacheHitCount; private long _defCacheMissCount; // XXX: needs to be a timed LRU //private LruCache<String, SessionArrayValue> _sessionMap // = new LruCache<String, SessionArrayValue>(4096); private ConcurrentHashMap<String, Object> _specialMap = new ConcurrentHashMap<String, Object>(); private String _scriptEncoding; private String _phpVersion = "5.3.2"; private String _mySqlVersion; private StringValue _phpVersionValue; private boolean _isStrict; private boolean _isLooseParse; private boolean _isRequireSource; private boolean _isConnectionPool = true; private Boolean _isUnicodeSemantics; private DataSource _database; private ConcurrentHashMap<String,DataSource> _databaseMap = new ConcurrentHashMap<String,DataSource>(); private ConcurrentHashMap<Env,Env> _activeEnvSet = new ConcurrentHashMap<Env,Env>(); private long _staticId; private Path _pwd; private Path _workDir; private ServletContext _servletContext; private QuercusTimer _quercusTimer; private EnvTimeoutThread _envTimeoutThread; protected long _envTimeout = 60000L; // how long to sleep the env timeout thread, // for fast, complete tomcat undeploys protected static final long ENV_TIMEOUT_UPDATE_INTERVAL = 1000L; private boolean _isClosed; /** * Constructor. */ public QuercusContext() { _loader = Thread.currentThread().getContextClassLoader(); _moduleContext = getLocalContext(); _pageManager = createPageManager(); _sessionManager = createSessionManager(); for (Map.Entry<String,String> entry : System.getenv().entrySet()) { _serverEnvMap.put(createString(entry.getKey()), createString(entry.getValue())); } } /** * Returns the current time. */ public long getCurrentTime() { return _quercusTimer.getCurrentTime(); } /** * Returns the current time in nanoseconds. */ public long getExactTimeNanoseconds() { return _quercusTimer.getExactTimeNanoseconds(); } /** * Returns the exact current time in milliseconds. */ public long getExactTime() { return _quercusTimer.getExactTime(); } /** * Returns the working directory. */ public Path getPwd() { if (_pwd == null) _pwd = new FilePath(System.getProperty("user.dir")); return _pwd; } /** * Sets the working directory. */ public void setPwd(Path path) { _pwd = path; } public Path getWorkDir() { if (_workDir == null) _workDir = getPwd().lookup("WEB-INF/work"); return _workDir; } public void setWorkDir(Path workDir) { _workDir = workDir; } public String getCookieName() { return "JSESSIONID"; } public long getDependencyCheckInterval() { return 2000L; } public int getIncludeCacheMax() { return _includeCacheMax; } public void setIncludeCacheMax(int cacheMax) { _includeCacheMax = cacheMax; } public void setIncludeCacheTimeout(long timeout) { _includeCacheTimeout = timeout; } public long getIncludeCacheTimeout() { return _includeCacheTimeout; } public String getVersion() { return "Open Source " + QuercusVersion.getVersionNumber(); } public String getVersionDate() { return QuercusVersion.getVersionDate(); } /** * Returns the SAPI (Server API) name. */ public String getSapiName() { return "apache"; } public boolean isProfile() { return false; } public int getProfileIndex(String name) { return -1; } public void setProfileProbability(double probability) { } protected PageManager createPageManager() { return new PageManager(this); } protected QuercusSessionManager createSessionManager() { return new QuercusSessionManager(this); } /** * Returns the context for this class loader. */ public final ModuleContext getLocalContext() { return getLocalContext(_loader); } public ModuleContext getLocalContext(ClassLoader loader) { synchronized (this) { if (_moduleContext == null) { _moduleContext = createModuleContext(null, loader); _moduleContext.init(); } } return _moduleContext; } protected ModuleContext createModuleContext(ModuleContext parent, ClassLoader loader) { return new ModuleContext(parent, loader); } /** * Returns the module context. */ public ModuleContext getModuleContext() { return _moduleContext; } public QuercusSessionManager getQuercusSessionManager() { return _sessionManager; } /** * true if the pages should be compiled. */ public boolean isCompile() { return _pageManager.isCompile(); } /** * Returns true if this is the Professional version. */ public boolean isPro() { return false; } /** * Returns true if Quercus is running under Resin. */ public boolean isResin() { return false; } public void setUnicodeSemantics(boolean isUnicode) { _isUnicodeSemantics = isUnicode; } /** * Returns true if unicode.semantics is on. */ public boolean isUnicodeSemantics() { if (_isUnicodeSemantics == null) { _isUnicodeSemantics = Boolean.valueOf(getIniBoolean("unicode.semantics")); } return _isUnicodeSemantics.booleanValue(); } /* * Returns true if URLs may be arguments of include(). */ public boolean isAllowUrlInclude() { return getIniBoolean("allow_url_include"); } /* * Returns true if URLs may be arguments of fopen(). */ public boolean isAllowUrlFopen() { return getIniBoolean("allow_url_fopen"); } /** * Set true if pages should be compiled. */ public void setCompile(boolean isCompile) { _pageManager.setCompile(isCompile); } /** * Set true if pages should be compiled. */ public void setLazyCompile(boolean isCompile) { _pageManager.setLazyCompile(isCompile); } /* * true if interpreted pages should be used if pages fail to compile. */ public void setCompileFailover(boolean isCompileFailover) { _pageManager.setCompileFailover(isCompileFailover); } /* * Returns the expected encoding of php scripts. */ public String getScriptEncoding() { if (_scriptEncoding != null) return _scriptEncoding; else if (isUnicodeSemantics()) return "utf-8"; else return "iso-8859-1"; } /* * Sets the expected encoding of php scripts. */ public void setScriptEncoding(String encoding) { _scriptEncoding = encoding; } /* * Returns the mysql version to report to to PHP applications. * It is user set-able to allow cloaking of the underlying mysql * JDBC driver version for application compatibility. */ public String getMysqlVersion() { return _mySqlVersion; } /* * Sets the mysql version to report to applications. This cloaks * the underlying JDBC driver version, so that when an application * asks for the mysql version, this version string is returned instead. */ public void setMysqlVersion(String version) { _mySqlVersion = version; } public String getPhpVersion() { return _phpVersion; } public void setPhpVersion(String version) { _phpVersion = version; _phpVersionValue = null; } public StringValue getPhpVersionValue() { if (_phpVersionValue == null) { if (isUnicodeSemantics()) _phpVersionValue = createUnicodeString(_phpVersion); else _phpVersionValue = createString(_phpVersion); } return _phpVersionValue; } /* * Sets the ServletContext. */ public void setServletContext(ServletContext servletContext) { _servletContext = servletContext; } /* * Returns the ServletContext. */ public ServletContext getServletContext() { return _servletContext; } /** * Sets the default data source. */ public void setDatabase(DataSource database) { _database = database; } /** * Gets the default data source. */ public DataSource getDatabase() { return _database; } /** * Gets the default data source. */ public DataSource findDatabase(String driver, String url) { if (_database != null) return _database; else { try { String key = driver + ";" + url; DataSource database = _databaseMap.get(key); if (database != null) return database; ClassLoader loader = Thread.currentThread().getContextClassLoader(); Class cls = loader.loadClass(driver); Object ds = cls.newInstance(); if (ds instanceof DataSource) database = (DataSource) ds; else database = new JavaSqlDriverWrapper((java.sql.Driver) ds, url); _databaseMap.put(key, database); return database; } catch (ClassNotFoundException e) { throw new QuercusModuleException(e); } catch (InstantiationException e) { throw new QuercusModuleException(e); } catch (IllegalAccessException e) { throw new QuercusModuleException(e); } } } /* * Marks the connection for removal from the connection pool. */ public void markForPoolRemoval(Connection conn) { } /** * Unwrap connection if necessary. */ public Connection getConnection(Connection conn) { return conn; } /** * Unwrap statement if necessary. */ public java.sql.Statement getStatement(java.sql.Statement stmt) { return stmt; } /** * Sets the strict mode. */ public void setStrict(boolean isStrict) { _isStrict = isStrict; } /** * Gets the strict mode. */ public boolean isStrict() { return _isStrict; } /** * Sets the loose mode. */ public void setLooseParse(boolean isLoose) { _isLooseParse = isLoose; } /** * Gets the loose mode. */ public boolean isLooseParse() { return _isLooseParse; } /* * Gets the max size of the page cache. */ public int getPageCacheSize() { return _pageManager.getPageCacheSize(); } /* * Sets the capacity of the page cache. */ public void setPageCacheSize(int size) { _pageManager.setPageCacheSize(size); } /* * Gets the max size of the regexp cache. */ public int getRegexpCacheSize() { return RegexpModule.getRegexpCacheSize(); } /* * Sets the capacity of the regexp cache. */ public void setRegexpCacheSize(int size) { RegexpModule.setRegexpCacheSize(size); } /* * Set to true if compiled pages need to be backed by php source files. */ public void setRequireSource(boolean isRequireSource) { _isRequireSource = isRequireSource; } /* * Returns whether the php source is required for compiled files. */ public boolean isRequireSource() { return _isRequireSource; } /* * Turns connection pooling on or off. */ public void setConnectionPool(boolean isEnable) { _isConnectionPool = isEnable; } /* * Returns true if connections should be pooled. */ public boolean isConnectionPool() { return _isConnectionPool; } /** * Adds a module */ /* public void addModule(QuercusModule module) throws ConfigException { try { introspectPhpModuleClass(module.getClass()); } catch (Exception e) { throw ConfigException.create(e); } } */ /** * Adds a java class */ public void addJavaClass(String name, Class type) throws ConfigException { try { if (type.isAnnotationPresent(ClassImplementation.class)) _moduleContext.introspectJavaImplClass(name, type, null); else _moduleContext.introspectJavaClass(name, type, null, null); } catch (Exception e) { throw ConfigException.create(e); } } /** * Adds a java class */ public void addJavaClass(String phpName, String className) { Class type; try { type = Class.forName(className, false, _loader); } catch (ClassNotFoundException e) { throw new QuercusRuntimeException(L.l("`{0}' not valid: {1}", className, e.toString()), e); } addJavaClass(phpName, type); } /** * Adds a impl class */ public void addImplClass(String name, Class type) throws ConfigException { throw new UnsupportedOperationException( "XXX: need to merge with ModuleContext: " + name); /* try { introspectJavaImplClass(name, type, null); } catch (Exception e) { throw ConfigException.create(e); } */ } /** * Adds a java class */ public JavaClassDef getJavaClassDefinition(Class<?> type, String className) { JavaClassDef def; if (_classNotFoundCache.get(className) != null) return null; def = _javaClassWrappers.get(className); if (def == null) { try { def = getModuleContext().getJavaClassDefinition(type, className); int id = getClassId(className); _classDefMap[id] = def; _javaClassWrappers.put(className, def); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new QuercusRuntimeException(e); } } def.init(); return def; } /** * Adds a java class */ public JavaClassDef getJavaClassDefinition(String className) { JavaClassDef def; if (_classNotFoundCache.get(className) != null) return null; def = _javaClassWrappers.get(className); if (def == null) { try { def = getModuleContext().getJavaClassDefinition(className); _javaClassWrappers.put(className, def); } catch (RuntimeException e) { _classNotFoundCache.put(className, className); throw e; } catch (Exception e) { throw new QuercusRuntimeException(e); } } def.init(); return def; } /** * Finds the java class wrapper. */ public ClassDef findJavaClassWrapper(String name) { ClassDef def = _javaClassWrappers.get(name); if (def != null) return def; return _lowerJavaClassWrappers.get(name.toLowerCase(Locale.ENGLISH)); } /** * Sets an ini file. */ public void setIniFile(Path path) { // XXX: Not sure why this dependency would be useful // Environment.addDependency(new Depend(path)); if (path.canRead()) { Env env = new Env(this); Value result = FileModule.parse_ini_file(env, path, false); if (result instanceof ArrayValue) { ArrayValue array = (ArrayValue) result; for (Map.Entry<Value,Value> entry : array.entrySet()) { setIni(entry.getKey().toString(), entry.getValue().toString()); } } _iniFile = path; } } /** * Returns the ini file. */ public Path getIniFile() { return _iniFile; } /** * Returns the IniDefinitions for all ini that have been defined by modules. */ public IniDefinitions getIniDefinitions() { return _iniDefinitions; } /** * Returns a map of the ini values that have been explicitly set. */ public HashMap<String, Value> getIniMap(boolean create) { if (_iniMap == null && create) _iniMap = new HashMap<String, Value>(); return _iniMap; } /** * Sets an ini value. */ public void setIni(String name, StringValue value) { _iniDefinitions.get(name).set(this, value); } /** * Sets an ini value. */ public void setIni(String name, String value) { _iniDefinitions.get(name).set(this, value); } /** * Returns an ini value. */ public boolean getIniBoolean(String name) { return _iniDefinitions.get(name).getAsBoolean(this); } /** * Returns an ini value as a long. */ public long getIniLong(String name) { return _iniDefinitions.get(name).getAsLongValue(this).toLong(); } /** * Returns an ini value. */ public Value getIniValue(String name) { return _iniDefinitions.get(name).getValue(this); } /** * Sets a server env value. */ public void setServerEnv(String name, String value) { // php/3j58 if (isUnicodeSemantics()) setServerEnv(createUnicodeString(name), createUnicodeString(value)); else setServerEnv(createString(name), createString(value)); } /** * Sets a server env value. */ public void setServerEnv(StringValue name, StringValue value) { _serverEnvMap.put(name, value); } /** * Gets a server env value. */ public Value getServerEnv(StringValue name) { return _serverEnvMap.get(name); } /** * Returns the server env map. */ public HashMap<Value,Value> getServerEnvMap() { return _serverEnvMap; } /** * Returns the compile classloader */ public ClassLoader getCompileClassLoader() { return null; } /** * Sets the compile classloader */ public void setCompileClassLoader(ClassLoader loader) { } /** * Returns the relative path. */ public final String getClassName(Path path) { if (path == null) return "tmp.eval"; String pathName = path.getFullPath(); String pwdName = getPwd().getFullPath(); String relPath; if (pathName.startsWith(pwdName)) relPath = pathName.substring(pwdName.length()); else relPath = pathName; return "_quercus." + JavaCompiler.mangleName(relPath); } /** * Returns an include path. */ public Path getIncludeCache(StringValue include, String includePath, Path pwd, Path scriptPwd) { IncludeKey key = new IncludeKey(include, includePath, pwd, scriptPwd); Path path = _includeCache.get(key); return path; } /** * Adds an include path. */ public void putIncludeCache(StringValue include, String includePath, Path pwd, Path scriptPwd, Path path) { IncludeKey key = new IncludeKey(include, includePath, pwd, scriptPwd); _includeCache.put(key, path); } /** * Returns the definition cache hit count. */ public long getDefCacheHitCount() { return _defCacheHitCount; } /** * Returns the definition cache miss count. */ public long getDefCacheMissCount() { return _defCacheMissCount; } /** * Returns the definition state for an include. */ /* public DefinitionState getDefinitionCache(DefinitionKey key) { SoftReference<DefinitionState> defStateRef = _defCache.get(key); if (defStateRef != null) { DefinitionState defState = defStateRef.get(); if (defState != null) { _defCacheHitCount++; return defState.copyLazy(); } } _defCacheMissCount++; return null; } */ /** * Returns the definition state for an include. */ /* public void putDefinitionCache(DefinitionKey key, DefinitionState defState) { _defCache.put(key, new SoftReference<DefinitionState>(defState.copy())); } */ /** * Clears the definition cache. */ public void clearDefinitionCache() { // _defCache.clear(); } /** * Returns true if a precompiled page exists */ public boolean includeExists(Path path) { return _pageManager.precompileExists(path); } /** * Parses a quercus program. * * @param path the source file path * @return the parsed program * @throws IOException */ public QuercusPage parse(Path path) throws IOException { return _pageManager.parse(path); } /** * Parses a quercus program. * * @param path the source file path * @return the parsed program * @throws IOException */ public QuercusPage parse(Path path, String fileName, int line) throws IOException { return _pageManager.parse(path, fileName, line); } /** * Parses a quercus program. * * @param path the source file path * @return the parsed program * @throws IOException */ public QuercusPage parse(ReadStream is) throws IOException { return new InterpretedPage(QuercusParser.parse(this, is)); } /** * Parses a quercus string. * * @param code the source code * @return the parsed program * @throws IOException */ public QuercusProgram parseCode(String code) throws IOException { QuercusProgram program = _evalCache.get(code); if (program == null) { program = QuercusParser.parseEval(this, code); _evalCache.put(code, program); } return program; } /** * Parses a quercus string. * * @param code the source code * @return the parsed program * @throws IOException */ public QuercusProgram parseEvalExpr(String code) throws IOException { // XXX: possible conflict with parse eval because of the // return value changes QuercusProgram program = _evalCache.get(code); if (program == null) { program = QuercusParser.parseEvalExpr(this, code); _evalCache.put(code, program); } return program; } /** * Parses a function. * * @param args the arguments * @param code the source code * @return the parsed program * @throws IOException */ public AbstractFunction parseFunction(String name, String args, String code) throws IOException { return QuercusParser.parseFunction(this, name, args, code); } /** * Returns the function with the given name. */ public AbstractFunction findFunction(String name) { AbstractFunction fun = _funMap.get(name); if ((fun == null) && ! isStrict()) fun = _lowerFunMap.get(name.toLowerCase(Locale.ENGLISH)); return fun; } /** * Returns the function with the given name. */ public AbstractFunction findFunctionImpl(String name) { AbstractFunction fun = _funMap.get(name); return fun; } /** * Returns the function with the given name. */ public AbstractFunction findLowerFunctionImpl(String lowerName) { AbstractFunction fun = _lowerFunMap.get(lowerName); return fun; } /** * Returns an array of the defined functions. */ public ArrayValue getDefinedFunctions() { ArrayValue internal = new ArrayValueImpl(); for (String name : _funMap.keySet()) { internal.put(name); } return internal; } // // name to id mappings // /** * Returns the id for a function name. */ public int getFunctionId(String name) { if (! isStrict()) name = name.toLowerCase(Locale.ENGLISH); if (name.startsWith("\\")) { // php/0m18 name = name.substring(1); } int id = _functionNameMap.get(name); if (id >= 0) return id; synchronized (_functionNameMap) { id = _functionNameMap.get(name); if (id >= 0) return id; // 0 is used for an undefined function // php/1p0g id = _functionNameMap.size() + 1; _functionNameMap.put(name, id); extendFunctionMap(name, id); } return id; } protected void extendFunctionMap(String name, int id) { if (_functionMap.length <= id) { AbstractFunction []functionMap = new AbstractFunction[id + 256]; System.arraycopy(_functionMap, 0, functionMap, 0, _functionMap.length); _functionMap = functionMap; } int globalId = -1; int ns = name.lastIndexOf('\\'); if (ns > 0) { globalId = getFunctionId(name.substring(ns + 1)); } _functionMap[id] = new UndefinedFunction(id, name, globalId); } /** * Returns the id for a function name. */ public int findFunctionId(String name) { if (! isStrict()) name = name.toLowerCase(Locale.ENGLISH); if (name.startsWith("\\")) name = name.substring(1); // IntMap is internally synchronized return _functionNameMap.get(name); } /** * Returns the number of functions */ public int getFunctionIdCount() { return _functionNameMap.size(); } /** * Returns the undefined functions */ public AbstractFunction []getFunctionMap() { return _functionMap; } public int setFunction(String name, AbstractFunction fun) { int id = getFunctionId(name); _functionMap[id] = fun; return id; } /** * Returns the id for a class name. */ public int getClassId(String className) { int id = _classNameMap.get(className); if (id >= 0) return id; if (className.startsWith("\\")) className = className.substring(1); id = _classNameMap.get(className); if (id >= 0) return id; synchronized (_classNameMap) { String name = className.toLowerCase(Locale.ENGLISH); id = _classNameMap.get(name); if (id >= 0) { _classNameMap.put(className, id); return id; } id = _classNameMap.size(); if (_classDefMap.length <= id) { String []classNames = new String[id + 256]; System.arraycopy(_classNames, 0, classNames, 0, _classNames.length); _classNames = classNames; ClassDef []classDefMap = new ClassDef[_classNames.length]; System.arraycopy(_classDefMap, 0, classDefMap, 0, _classDefMap.length); _classDefMap = classDefMap; QuercusClass []classCacheMap = new QuercusClass[_classNames.length]; System.arraycopy(_classCacheMap, 0, classCacheMap, 0, _classCacheMap.length); _classCacheMap = classCacheMap; } _classNames[id] = className; // _classMap[id] = new UndefinedClass(name); _classNameMap.put(className, id); _classNameMap.put(name, id); } return id; } public String getClassName(int id) { return _classNames[id]; } /** * Returns the id for a function name. */ public int findClassId(String name) { return _classNameMap.get(name); } /** * Returns the number of classes */ public int getClassIdCount() { return _classNameMap.size(); } /** * Returns the undefined functions */ public ClassDef []getClassDefMap() { return _classDefMap; } /** * Returns the class def with the given index. */ public ClassDef getClassDef(int id) { return _classDefMap[id]; } /** * Returns the undefined functions */ public QuercusClass []getClassCacheMap() { return _classCacheMap; } /** * Returns the undefined functions */ public QuercusClass getCachedClass(int id) { return _classCacheMap[id]; } /** * Returns the undefined functions */ public void setCachedClass(int id, QuercusClass qClass) { _classCacheMap[id] = qClass; } /** * Returns the id for a constant */ public int getConstantId(String name) { // php/3j12 if (isUnicodeSemantics()) return getConstantId(new UnicodeBuilderValue(name)); else return getConstantId(new ConstStringValue(name)); } /** * Returns the id for a constant */ public int getConstantId(StringValue name) { int id = _constantNameMap.get(name); if (id >= 0) return id; synchronized (_constantNameMap) { id = _constantNameMap.get(name); if (id >= 0) return id; // php/313j id = _constantNameMap.size() + 1; if (_classDefMap.length <= id) { Value []constantMap = new Value[id + 256]; System.arraycopy(_constantMap, 0, constantMap, 0, _constantMap.length); _constantMap = constantMap; Value []constantNameList = new Value[id + 256]; System.arraycopy(_constantNameList, 0, constantNameList, 0, _constantNameList.length); _constantNameList = constantNameList; int []constantLowerMap = new int[_constantMap.length]; System.arraycopy(_constantLowerMap, 0, constantLowerMap, 0, _constantLowerMap.length); _constantLowerMap = constantLowerMap; } // XXX: i18n _constantNameList[id] = name; // php/1a0g, php/1d06 _constantNameMap.put(name, id); // php/050a - only case-insensitive constants should add lower case, // i.e. use addLowerConstantId } return id; } /** * Returns the id for a constant */ public int addLowerConstantId(StringValue name) { int id = getConstantId(name); int lowerId = getConstantId(name.toLowerCase()); _constantLowerMap[id] = lowerId; return id; } /** * Returns the name map. */ public int getConstantLower(int id) { return _constantLowerMap[id]; } /** * Returns the constant id. */ public int getConstantLowerId(String name) { return getConstantId(name.toLowerCase(Locale.ENGLISH)); } /** * Returns the name map. */ public Value getConstantName(int id) { return _constantNameList[id]; } /** * Returns the name map. */ public Value []getConstantMap() { return _constantMap; } /** * Returns the number of defined constants */ public int getConstantIdCount() { return _constantNameMap.size(); } /** * Returns true if the variable is a superglobal. */ public static boolean isSuperGlobal(StringValue name) { return _superGlobals.contains(name.toString()); } /** * Returns the stdClass definition. */ public QuercusClass getStdClass() { return _moduleContext.getStdClass(); } /** * Returns the class with the given name. */ public ClassDef findClass(String name) { int id = getClassId(name); return _classDefMap[id]; } /** * Returns the class maps. */ public HashMap<String, ClassDef> getClassMap() { throw new UnsupportedOperationException(); } /** * Returns the module with the given name. */ public QuercusModule findModule(String name) { ModuleInfo moduleInfo = _modules.get(name); QuercusModule module = null; if (moduleInfo != null) module = moduleInfo.getModule(); else module = getModuleContext().findModule(name); if (module == null) throw new IllegalStateException(L.l("'{0}' is an unknown quercus module", name)); return module; } /** * Returns a list of the modules that have some startup code to run. */ public HashSet<ModuleStartupListener> getModuleStartupListeners() { return _moduleStartupListeners; } /** * Returns true if an extension is loaded. */ public boolean isExtensionLoaded(String name) { return _extensionSet.contains(name) || _extensionSetLowerCase.contains(name.toLowerCase(Locale.ENGLISH)); } /** * Returns the loaded extensions. */ public HashSet<String> getLoadedExtensions() { return _extensionSet; } /** * Returns true if an extension is loaded. */ public Value getExtensionFuncs(String name) { ArrayValue value = null; for (ModuleInfo moduleInfo : _modules.values()) { Set<String> extensionSet = moduleInfo.getLoadedExtensions(); if (extensionSet.contains(name)) { for (String functionName : moduleInfo.getFunctions().keySet()) { if (value == null) value = new ArrayValueImpl(); value.put(functionName); } } } if (value != null) return value; else return BooleanValue.FALSE; } public Collection<ModuleInfo> getModules() { return _modules.values(); } /** * Initialize the engine */ public void init() { initModules(); initClasses(); _workDir = getWorkDir(); _iniDefinitions.addAll(_ini); _includeCache = new TimedCache<IncludeKey, Path>(getIncludeCacheMax(), getIncludeCacheTimeout()); initLocal(); } public void addModule(QuercusModule module) { ModuleInfo info = new ModuleInfo(_moduleContext, module.getClass().getName(), module); addModuleInfo(info); } /** * Initializes from the ModuleContext */ private void initModules() { for (ModuleInfo info : _moduleContext.getModules()) { addModuleInfo(info); } } protected void addModuleInfo(ModuleInfo info) { _modules.put(info.getName(), info); if (info.getModule() instanceof ModuleStartupListener) _moduleStartupListeners.add((ModuleStartupListener)info.getModule()); for (String ext : info.getLoadedExtensions()) { _extensionSet.add(ext); _extensionSetLowerCase.add(ext.toLowerCase(Locale.ENGLISH)); } Map<StringValue, Value> map; if (isUnicodeSemantics()) map = info.getUnicodeConstMap(); else map = info.getConstMap(); if (map != null) { for (Map.Entry<StringValue,Value> entry : map.entrySet()) { int id = getConstantId(entry.getKey()); _constantMap[id] = entry.getValue(); } } _iniDefinitions.addAll(info.getIniDefinitions()); for (Map.Entry<String, AbstractFunction> entry : info.getFunctions().entrySet()) { String funName = entry.getKey(); AbstractFunction fun = entry.getValue(); _funMap.put(funName, fun); _lowerFunMap.put(funName.toLowerCase(Locale.ENGLISH), fun); setFunction(funName, fun); } } /** * Initializes from the ModuleContext */ private void initClasses() { for (Map.Entry<String,JavaClassDef> entry : _moduleContext.getWrapperMap().entrySet()) { String name = entry.getKey(); JavaClassDef def = entry.getValue(); _javaClassWrappers.put(name, def); _lowerJavaClassWrappers.put(name.toLowerCase(Locale.ENGLISH), def); } for (Map.Entry<String,ClassDef> entry : _moduleContext.getClassMap().entrySet()) { String name = entry.getKey(); ClassDef def = entry.getValue(); int id = getClassId(name); _classDefMap[id] = def; } } /** * Creates a string. Because these strings are typically Java * constants, they fit into a lru cache. */ public UnicodeBuilderValue createUnicodeString(String name) { UnicodeBuilderValue value = _unicodeMap.get(name); if (value == null) { value = new UnicodeBuilderValue(name); _unicodeMap.put(name, value); } return value; } /** * Creates a string. Because these strings are typically Java * constants, they fit into a lru cache. */ public StringValue createString(String name) { StringValue value = _stringMap.get(name); if (value == null) { value = new ConstStringValue(name); _stringMap.put(name, value); } return value; } /** * Interns a string. */ /* public StringValue intern(String name) { StringValue value = _internMap.get(name); if (value != null) return value; synchronized (_internMap) { value = _internMap.get(name); if (value != null) return value; if (value == null) { name = name.intern(); value = new StringBuilderValue(name); _internMap.put(name, value); } return value; } } */ /** * Returns a named constant. */ public Value getConstant(int id) { return _constantMap[id]; } public StringValue createStaticName() { return MethodIntern.intern("s" + _staticId++); } public Cache getSessionCache() { return null; } public void setSessionTimeout(long sessionTimeout) { } /** * Loads the session from the backing. */ public SessionArrayValue loadSession(Env env, String sessionId) { long now = env.getCurrentTime(); SessionArrayValue session = _sessionManager.getSession(env, sessionId, now); if (session == null) session = _sessionManager.createSession(env, sessionId, now); return session; } /** * Saves the session to the backing. */ public void saveSession(Env env, SessionArrayValue session) { _sessionManager.saveSession(env, session); } /** * Removes the session from the backing. */ public void destroySession(String sessionId) { _sessionManager.removeSession(sessionId); } /** * Loads a special value */ public Object getSpecial(String key) { return _specialMap.get(key); } /** * Saves a special value */ public void setSpecial(String key, Object value) { _specialMap.put(key, value); } public static Value objectToValue(Object obj) { if (obj == null) return NullValue.NULL; else if (Byte.class.equals(obj.getClass()) || Short.class.equals(obj.getClass()) || Integer.class.equals(obj.getClass()) || Long.class.equals(obj.getClass())) { return LongValue.create(((Number) obj).longValue()); } else if (Float.class.equals(obj.getClass()) || Double.class.equals(obj.getClass())) { return DoubleValue.create(((Number) obj).doubleValue()); } else if (String.class.equals(obj.getClass())) { // XXX: i18n return new ConstStringValue((String) obj); } else { // XXX: unknown types, e.g. Character? return null; } } /** * Initialize local configuration, e.g. finding the PHP and PEAR libraries */ protected void initLocal() { StringBuilder sb = new StringBuilder("."); Path pwd = getPwd(); String []paths = new String[] { "/usr/share/php", "/usr/lib/php", "/usr/local/lib/php", "/usr/share/pear", "/usr/lib/pear", "/usr/local/lib/pear" }; for (String path : paths) { if (pwd.lookup(path).isDirectory()) { sb.append(":").append(pwd.lookup(path).getPath()); } } setIni("include_path", sb.toString()); } public void start() { try { _quercusTimer = new QuercusTimer(); _envTimeoutThread = new EnvTimeoutThread(); _envTimeoutThread.start(); } catch (Exception e) { log.log(Level.FINE, e.getMessage(), e); } } public Env createEnv(QuercusPage page, WriteStream out, HttpServletRequest request, HttpServletResponse response) { return new Env(this, page, out, request, response); } public ExprFactory createExprFactory() { return new ExprFactory(); } protected Map<Env,Env> getActiveEnvSet() { return _activeEnvSet; } public void startEnv(Env env) { _activeEnvSet.put(env, env); } public void completeEnv(Env env) { _activeEnvSet.remove(env); } protected boolean isClosed() { return _isClosed; } public void close() { _isClosed = true; _sessionManager.close(); _pageManager.close(); if (_envTimeoutThread != null) _envTimeoutThread.shutdown(); if (_quercusTimer != null) { _quercusTimer.shutdown(); } } static class IncludeKey { private final StringValue _include; private final String _includePath; private final Path _pwd; private final Path _scriptPwd; IncludeKey(StringValue include, String includePath, Path pwd, Path scriptPwd) { _include = include; _includePath = includePath; _pwd = pwd; _scriptPwd = scriptPwd; } public int hashCode() { int hash = 37; hash = 65537 * hash + _include.hashCode(); hash = 65537 * hash + _includePath.hashCode(); hash = 65537 * hash + _pwd.hashCode(); hash = 65537 * hash + _scriptPwd.hashCode(); return hash; } public boolean equals(Object o) { if (! (o instanceof IncludeKey)) return false; IncludeKey key = (IncludeKey) o; return (_include.equals(key._include) && _includePath.equals(key._includePath) && _pwd.equals(key._pwd) && _scriptPwd.equals(key._scriptPwd)); } } class EnvTimeoutThread extends Thread { private volatile boolean _isRunnable = true; private final long _timeout = _envTimeout; private long _quantumCount; EnvTimeoutThread() { super("quercus-env-timeout"); setDaemon(true); //setPriority(Thread.MAX_PRIORITY); } public void shutdown() { _isRunnable = false; LockSupport.unpark(this); } public void run() { while (_isRunnable) { if (_quantumCount >= _timeout) { _quantumCount = 0; try { ArrayList<Env> activeEnv = new ArrayList<Env>(_activeEnvSet.keySet()); for (Env env : activeEnv) { env.updateTimeout(); } } catch (Throwable e) { } } else { _quantumCount += ENV_TIMEOUT_UPDATE_INTERVAL; } LockSupport.parkNanos(ENV_TIMEOUT_UPDATE_INTERVAL * 1000000L); } } } static { _superGlobals.add("GLOBALS"); _superGlobals.add("_COOKIE"); _superGlobals.add("_ENV"); _superGlobals.add("_FILES"); _superGlobals.add("_GET"); _superGlobals.add("_POST"); _superGlobals.add("_SERVER"); _superGlobals.add("_SESSION"); _superGlobals.add("_REQUEST"); /* String includePath; if (Path.isWindows()) includePath = "." + FileModule.PATH_SEPARATOR + "C:\\php5\\pear"; else includePath = "." + FileModule.PATH_SEPARATOR + "/usr/share/php" + FileModule.PATH_SEPARATOR + "/usr/share/pear"; INI_INCLUDE_PATH = _ini.add( "include_path", includePath, IniDefinition.PHP_INI_ALL); */ } public static final IniDefinition INI_INCLUDE_PATH = _ini.add("include_path", ".", IniDefinition.PHP_INI_ALL); public static final IniDefinition INI_REGISTER_LONG_ARRAYS = _ini.add("register_long_arrays", true, IniDefinition.PHP_INI_PERDIR); public static final IniDefinition INI_ALWAYS_POPULATE_RAW_POST_DATA = _ini.add( "always_populate_raw_post_data", false, IniDefinition.PHP_INI_PERDIR); // unicode ini public static final IniDefinition INI_UNICODE_SEMANTICS = _ini.add("unicode.semantics", false, IniDefinition.PHP_INI_SYSTEM); public static final IniDefinition INI_UNICODE_FALLBACK_ENCODING = _ini.add("unicode.fallback_encoding", "utf-8", IniDefinition.PHP_INI_ALL); public static final IniDefinition INI_UNICODE_FROM_ERROR_MODE = _ini.add("unicode.from_error_mode", "2", IniDefinition.PHP_INI_ALL); public static final IniDefinition INI_UNICODE_FROM_ERROR_SUBST_CHAR = _ini.add( "unicode.from_error_subst_char", "3f", IniDefinition.PHP_INI_ALL); public static final IniDefinition INI_UNICODE_HTTP_INPUT_ENCODING = _ini.add("unicode.http_input_encoding", null, IniDefinition.PHP_INI_ALL); public static final IniDefinition INI_UNICODE_OUTPUT_ENCODING = _ini.add("unicode.output_encoding", null, IniDefinition.PHP_INI_ALL); public static final IniDefinition INI_UNICODE_RUNTIME_ENCODING = _ini.add("unicode.runtime_encoding", null, IniDefinition.PHP_INI_ALL); public static final IniDefinition INI_UNICODE_SCRIPT_ENCODING = _ini.add("unicode.script_encoding", null, IniDefinition.PHP_INI_ALL); }