github-advanced-security[bot] commented on code in PR #14162: URL: https://github.com/apache/grails-core/pull/14162#discussion_r2049031867
########## grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageResourceLoader.java: ########## @@ -0,0 +1,66 @@ +/* + * Copyright 2004-2005 Graeme Rocher + * + * Licensed 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.grails.gsp; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.grails.core.io.StaticResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +/** + * A StaticResourceLoader that loads GSPs from a local grails-app folder instead of from WEB-INF in + * development mode. + * + * @see org.grails.core.io.StaticResourceLoader + * + * @author Graeme Rocher + * @since 0.5 + */ +public class GroovyPageResourceLoader extends StaticResourceLoader { + + /** + * The id of the instance of this bean to be used in the Spring context + */ + public static final String BEAN_ID = "groovyPageResourceLoader"; + + private static final Log LOG = LogFactory.getLog(GroovyPageResourceLoader.class); + private static final String PLUGINS_PATH = "/plugins/"; + + private Resource localBaseResource; + + @Override + public void setBaseResource(Resource baseResource) { + localBaseResource = baseResource; + super.setBaseResource(baseResource); + } + + @Override + public Resource getResource(String location) { + Assert.hasLength(location, "Argument [location] cannot be null or blank"); + + Resource resource = super.getResource(location); Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). This path depends on a [user-provided value](2). [Show more details](https://github.com/apache/grails-core/security/code-scanning/10) ########## grails-gsp/core/src/main/groovy/org/grails/gsp/GroovyPageResourceLoader.java: ########## @@ -0,0 +1,66 @@ +/* + * Copyright 2004-2005 Graeme Rocher + * + * Licensed 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.grails.gsp; + + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.grails.core.io.StaticResourceLoader; +import org.springframework.core.io.Resource; +import org.springframework.util.Assert; + +/** + * A StaticResourceLoader that loads GSPs from a local grails-app folder instead of from WEB-INF in + * development mode. + * + * @see org.grails.core.io.StaticResourceLoader + * + * @author Graeme Rocher + * @since 0.5 + */ +public class GroovyPageResourceLoader extends StaticResourceLoader { + + /** + * The id of the instance of this bean to be used in the Spring context + */ + public static final String BEAN_ID = "groovyPageResourceLoader"; + + private static final Log LOG = LogFactory.getLog(GroovyPageResourceLoader.class); + private static final String PLUGINS_PATH = "/plugins/"; + + private Resource localBaseResource; + + @Override + public void setBaseResource(Resource baseResource) { + localBaseResource = baseResource; + super.setBaseResource(baseResource); + } + + @Override + public Resource getResource(String location) { + Assert.hasLength(location, "Argument [location] cannot be null or blank"); + + Resource resource = super.getResource(location); Review Comment: ## Server-side request forgery Potential server-side request forgery due to a [user-provided value](1). Potential server-side request forgery due to a [user-provided value](2). [Show more details](https://github.com/apache/grails-core/security/code-scanning/18) ########## grails-gsp/core/src/main/groovy/org/grails/gsp/io/DefaultGroovyPageLocator.java: ########## @@ -0,0 +1,442 @@ +/* + * Copyright 2011 SpringSource + * + * Licensed 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.grails.gsp.io; + +import grails.plugins.GrailsPlugin; +import grails.plugins.GrailsPluginManager; +import grails.plugins.PluginManagerAware; +import grails.util.CollectionUtils; +import grails.util.Environment; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.grails.gsp.GroovyPage; +import org.grails.gsp.GroovyPageBinding; +import org.grails.io.support.GrailsResourceUtils; +import org.grails.plugins.BinaryGrailsPlugin; +import org.grails.taglib.TemplateVariableBinding; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import java.io.File; +import java.security.PrivilegedAction; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Used to locate GSPs whether in development or WAR deployed mode from static + * resources, custom resource loaders and binary plugins. + * + * @author Graeme Rocher + * @since 2.0 + */ +public class DefaultGroovyPageLocator implements GroovyPageLocator, ResourceLoaderAware, ApplicationContextAware, PluginManagerAware { + + private static final Log LOG = LogFactory.getLog(DefaultGroovyPageLocator.class); + public static final String PATH_TO_WEB_INF_VIEWS = "/WEB-INF/grails-app/views"; + private static final String SLASHED_VIEWS_DIR_PATH = "/" + GrailsResourceUtils.VIEWS_DIR_PATH; + private static final String PLUGINS_PATH = "/plugins/"; + private static final String BLANK = ""; + protected Collection<ResourceLoader> resourceLoaders = new ConcurrentLinkedQueue<ResourceLoader>(); + protected GrailsPluginManager pluginManager; + private ConcurrentMap<String, String> precompiledGspMap; + protected boolean warDeployed = Environment.isWarDeployed(); + protected boolean reloadEnabled = !warDeployed; + private Set<String> reloadedPrecompiledGspClassNames = new CopyOnWriteArraySet<String>(); + + public void setResourceLoader(ResourceLoader resourceLoader) { + addResourceLoader(resourceLoader); + } + + public void addResourceLoader(ResourceLoader resourceLoader) { + if (resourceLoader != null && !resourceLoaders.contains(resourceLoader)) { + resourceLoaders.add(resourceLoader); + } + } + + public void setPrecompiledGspMap(Map<String, String> precompiledGspMap) { + if (precompiledGspMap == null) { + this.precompiledGspMap = null; + } else { + this.precompiledGspMap = new ConcurrentHashMap<String, String>(precompiledGspMap); + } + } + + public GroovyPageScriptSource findPage(final String uri) { + GroovyPageScriptSource scriptSource = findResourceScriptSource(uri); + if (scriptSource == null) { + scriptSource = findBinaryScriptSource(uri); + } + if (scriptSource == null) { + scriptSource = findResourceScriptSourceInPlugins(uri); + } + return scriptSource; + } + + protected Resource findReloadablePage(final String uri) { + Resource resource = findResource(uri); + if (resource == null) { + resource = findResourceInPlugins(uri); + } + return resource; + } + + public GroovyPageScriptSource findPageInBinding(String pluginName, String uri, TemplateVariableBinding binding) { + + GroovyPageScriptSource scriptSource = null; + String contextPath = resolveContextPath(pluginName, uri, binding); + String fullURI = GrailsResourceUtils.appendPiecesForUri(contextPath, uri); + + if(pluginManager != null) { + GrailsPlugin grailsPlugin = pluginManager.getGrailsPlugin(pluginName); + if(grailsPlugin instanceof BinaryGrailsPlugin) { + BinaryGrailsPlugin binaryGrailsPlugin = (BinaryGrailsPlugin) grailsPlugin; + File projectDirectory = binaryGrailsPlugin.getProjectDirectory(); + if(projectDirectory != null) { + File f = new File(projectDirectory, GrailsResourceUtils.VIEWS_DIR_PATH + uri); + if(f.exists()) { + scriptSource = new GroovyPageResourceScriptSource(uri, new FileSystemResource(f)); + } + } + else { + scriptSource = resolveViewInBinaryPlugin(binaryGrailsPlugin, uri); + } + } + } + + if(scriptSource == null) { + scriptSource = findPageInBinding(fullURI, binding); + } + + if (scriptSource == null) { + scriptSource = findResourceScriptSource(uri); + } + + + //last effort to resolve and force name of plugin to use camel case + if (scriptSource == null) { + contextPath = resolveContextPath(pluginName, uri, binding, true); + scriptSource = findPageInBinding(GrailsResourceUtils.appendPiecesForUri(contextPath, uri), binding); + } + + return scriptSource; + } + + protected String resolveContextPath(String pluginName, String uri, TemplateVariableBinding binding) { + return resolveContextPath(pluginName, uri, binding, false); + } + + protected String resolveContextPath(String pluginName, String uri, TemplateVariableBinding binding, boolean forceCamelCase) { + String contextPath = null; + + if (uri.startsWith("/plugins/")) { + contextPath = BLANK; + } else if (pluginName != null && pluginManager != null) { + contextPath = pluginManager.getPluginPath(pluginName); + } else if (binding instanceof GroovyPageBinding) { + String pluginContextPath = ((GroovyPageBinding) binding).getPluginContextPath(); + contextPath = pluginContextPath != null ? pluginContextPath : BLANK; + } else { + contextPath = BLANK; + } + + return contextPath; + } + + public void removePrecompiledPage(GroovyPageCompiledScriptSource scriptSource) { + reloadedPrecompiledGspClassNames.add(scriptSource.getCompiledClass().getName()); + if (scriptSource.getURI() != null && precompiledGspMap != null) { + precompiledGspMap.remove(scriptSource.getURI()); + } + } + + public GroovyPageScriptSource findPageInBinding(String uri, TemplateVariableBinding binding) { + GroovyPageScriptSource scriptSource = findResourceScriptSource(uri); + + if (scriptSource == null) { + GrailsPlugin pagePlugin = binding instanceof GroovyPageBinding ? ((GroovyPageBinding) binding).getPagePlugin() : null; + if (pagePlugin instanceof BinaryGrailsPlugin) { + BinaryGrailsPlugin binaryPlugin = (BinaryGrailsPlugin) pagePlugin; + scriptSource = resolveViewInBinaryPlugin(binaryPlugin, uri); + } else if (pagePlugin != null) { + scriptSource = findResourceScriptSource(resolvePluginViewPath(uri, pagePlugin)); + } + } + + if (scriptSource == null) { + scriptSource = findBinaryScriptSource(uri); + } + + return scriptSource; + } + + protected GroovyPageScriptSource resolveViewInBinaryPlugin(BinaryGrailsPlugin binaryPlugin, String uri) { + GroovyPageScriptSource scriptSource = null; + uri = removeViewLocationPrefixes(uri); + uri = GrailsResourceUtils.appendPiecesForUri(PATH_TO_WEB_INF_VIEWS, uri); + Class<?> viewClass = binaryPlugin.resolveView(uri); + if (viewClass != null && !reloadedPrecompiledGspClassNames.contains(viewClass.getName())) { + scriptSource = createGroovyPageCompiledScriptSource(uri, uri, viewClass); + // we know we have binary plugin, sp setting to null in the resourceCallable to skip reloading. + ((GroovyPageCompiledScriptSource) scriptSource).setResourceCallable(null); + } + return scriptSource; + } + + protected GroovyPageCompiledScriptSource createGroovyPageCompiledScriptSource(final String uri, String fullPath, Class<?> viewClass) { + GroovyPageCompiledScriptSource scriptSource = new GroovyPageCompiledScriptSource(uri, fullPath, viewClass); + if (reloadEnabled) { + scriptSource.setResourceCallable(new PrivilegedAction<Resource>() { + public Resource run() { + return findReloadablePage(uri); + } + }); + } + return scriptSource; + } + + protected GroovyPageScriptSource findBinaryScriptSource(String uri) { + if (pluginManager == null) { + return null; + } + + List<GrailsPlugin> allPlugins = Arrays.asList(pluginManager.getAllPlugins()); + Collections.reverse(allPlugins); + + for (GrailsPlugin plugin : allPlugins) { + if (!(plugin instanceof BinaryGrailsPlugin)) { + continue; + } + + BinaryGrailsPlugin binaryPlugin = (BinaryGrailsPlugin) plugin; + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Searching plugin [%s] for GSP view [%s]", plugin.getName(), uri)); + } + GroovyPageScriptSource scriptSource = resolveViewInBinaryPlugin(binaryPlugin, uri); + if (scriptSource != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Found GSP view [%s] in plugin [%s]", uri, plugin.getName())); + } + return scriptSource; + } + else if(binaryPlugin.getProjectDirectory() != null) { + scriptSource = resolveViewInPluginProjectDirectory(binaryPlugin, uri); + if(scriptSource != null) { + return scriptSource; + } + } + } + + return null; + } + + private GroovyPageScriptSource resolveViewInPluginProjectDirectory(BinaryGrailsPlugin binaryPlugin, String uri) { + File projectDirectory = binaryPlugin.getProjectDirectory(); + File f = new File(projectDirectory, "grails-app/views" + uri); + if(f.exists()) { Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). This path depends on a [user-provided value](2). [Show more details](https://github.com/apache/grails-core/security/code-scanning/11) ########## grails-gsp/core/src/main/groovy/org/grails/gsp/io/DefaultGroovyPageLocator.java: ########## @@ -0,0 +1,442 @@ +/* + * Copyright 2011 SpringSource + * + * Licensed 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.grails.gsp.io; + +import grails.plugins.GrailsPlugin; +import grails.plugins.GrailsPluginManager; +import grails.plugins.PluginManagerAware; +import grails.util.CollectionUtils; +import grails.util.Environment; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.grails.gsp.GroovyPage; +import org.grails.gsp.GroovyPageBinding; +import org.grails.io.support.GrailsResourceUtils; +import org.grails.plugins.BinaryGrailsPlugin; +import org.grails.taglib.TemplateVariableBinding; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import java.io.File; +import java.security.PrivilegedAction; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Used to locate GSPs whether in development or WAR deployed mode from static + * resources, custom resource loaders and binary plugins. + * + * @author Graeme Rocher + * @since 2.0 + */ +public class DefaultGroovyPageLocator implements GroovyPageLocator, ResourceLoaderAware, ApplicationContextAware, PluginManagerAware { + + private static final Log LOG = LogFactory.getLog(DefaultGroovyPageLocator.class); + public static final String PATH_TO_WEB_INF_VIEWS = "/WEB-INF/grails-app/views"; + private static final String SLASHED_VIEWS_DIR_PATH = "/" + GrailsResourceUtils.VIEWS_DIR_PATH; + private static final String PLUGINS_PATH = "/plugins/"; + private static final String BLANK = ""; + protected Collection<ResourceLoader> resourceLoaders = new ConcurrentLinkedQueue<ResourceLoader>(); + protected GrailsPluginManager pluginManager; + private ConcurrentMap<String, String> precompiledGspMap; + protected boolean warDeployed = Environment.isWarDeployed(); + protected boolean reloadEnabled = !warDeployed; + private Set<String> reloadedPrecompiledGspClassNames = new CopyOnWriteArraySet<String>(); + + public void setResourceLoader(ResourceLoader resourceLoader) { + addResourceLoader(resourceLoader); + } + + public void addResourceLoader(ResourceLoader resourceLoader) { + if (resourceLoader != null && !resourceLoaders.contains(resourceLoader)) { + resourceLoaders.add(resourceLoader); + } + } + + public void setPrecompiledGspMap(Map<String, String> precompiledGspMap) { + if (precompiledGspMap == null) { + this.precompiledGspMap = null; + } else { + this.precompiledGspMap = new ConcurrentHashMap<String, String>(precompiledGspMap); + } + } + + public GroovyPageScriptSource findPage(final String uri) { + GroovyPageScriptSource scriptSource = findResourceScriptSource(uri); + if (scriptSource == null) { + scriptSource = findBinaryScriptSource(uri); + } + if (scriptSource == null) { + scriptSource = findResourceScriptSourceInPlugins(uri); + } + return scriptSource; + } + + protected Resource findReloadablePage(final String uri) { + Resource resource = findResource(uri); + if (resource == null) { + resource = findResourceInPlugins(uri); + } + return resource; + } + + public GroovyPageScriptSource findPageInBinding(String pluginName, String uri, TemplateVariableBinding binding) { + + GroovyPageScriptSource scriptSource = null; + String contextPath = resolveContextPath(pluginName, uri, binding); + String fullURI = GrailsResourceUtils.appendPiecesForUri(contextPath, uri); + + if(pluginManager != null) { + GrailsPlugin grailsPlugin = pluginManager.getGrailsPlugin(pluginName); + if(grailsPlugin instanceof BinaryGrailsPlugin) { + BinaryGrailsPlugin binaryGrailsPlugin = (BinaryGrailsPlugin) grailsPlugin; + File projectDirectory = binaryGrailsPlugin.getProjectDirectory(); + if(projectDirectory != null) { + File f = new File(projectDirectory, GrailsResourceUtils.VIEWS_DIR_PATH + uri); + if(f.exists()) { + scriptSource = new GroovyPageResourceScriptSource(uri, new FileSystemResource(f)); + } + } + else { + scriptSource = resolveViewInBinaryPlugin(binaryGrailsPlugin, uri); + } + } + } + + if(scriptSource == null) { + scriptSource = findPageInBinding(fullURI, binding); + } + + if (scriptSource == null) { + scriptSource = findResourceScriptSource(uri); + } + + + //last effort to resolve and force name of plugin to use camel case + if (scriptSource == null) { + contextPath = resolveContextPath(pluginName, uri, binding, true); + scriptSource = findPageInBinding(GrailsResourceUtils.appendPiecesForUri(contextPath, uri), binding); + } + + return scriptSource; + } + + protected String resolveContextPath(String pluginName, String uri, TemplateVariableBinding binding) { + return resolveContextPath(pluginName, uri, binding, false); + } + + protected String resolveContextPath(String pluginName, String uri, TemplateVariableBinding binding, boolean forceCamelCase) { + String contextPath = null; + + if (uri.startsWith("/plugins/")) { + contextPath = BLANK; + } else if (pluginName != null && pluginManager != null) { + contextPath = pluginManager.getPluginPath(pluginName); + } else if (binding instanceof GroovyPageBinding) { + String pluginContextPath = ((GroovyPageBinding) binding).getPluginContextPath(); + contextPath = pluginContextPath != null ? pluginContextPath : BLANK; + } else { + contextPath = BLANK; + } + + return contextPath; + } + + public void removePrecompiledPage(GroovyPageCompiledScriptSource scriptSource) { + reloadedPrecompiledGspClassNames.add(scriptSource.getCompiledClass().getName()); + if (scriptSource.getURI() != null && precompiledGspMap != null) { + precompiledGspMap.remove(scriptSource.getURI()); + } + } + + public GroovyPageScriptSource findPageInBinding(String uri, TemplateVariableBinding binding) { + GroovyPageScriptSource scriptSource = findResourceScriptSource(uri); + + if (scriptSource == null) { + GrailsPlugin pagePlugin = binding instanceof GroovyPageBinding ? ((GroovyPageBinding) binding).getPagePlugin() : null; + if (pagePlugin instanceof BinaryGrailsPlugin) { + BinaryGrailsPlugin binaryPlugin = (BinaryGrailsPlugin) pagePlugin; + scriptSource = resolveViewInBinaryPlugin(binaryPlugin, uri); + } else if (pagePlugin != null) { + scriptSource = findResourceScriptSource(resolvePluginViewPath(uri, pagePlugin)); + } + } + + if (scriptSource == null) { + scriptSource = findBinaryScriptSource(uri); + } + + return scriptSource; + } + + protected GroovyPageScriptSource resolveViewInBinaryPlugin(BinaryGrailsPlugin binaryPlugin, String uri) { + GroovyPageScriptSource scriptSource = null; + uri = removeViewLocationPrefixes(uri); + uri = GrailsResourceUtils.appendPiecesForUri(PATH_TO_WEB_INF_VIEWS, uri); + Class<?> viewClass = binaryPlugin.resolveView(uri); + if (viewClass != null && !reloadedPrecompiledGspClassNames.contains(viewClass.getName())) { + scriptSource = createGroovyPageCompiledScriptSource(uri, uri, viewClass); + // we know we have binary plugin, sp setting to null in the resourceCallable to skip reloading. + ((GroovyPageCompiledScriptSource) scriptSource).setResourceCallable(null); + } + return scriptSource; + } + + protected GroovyPageCompiledScriptSource createGroovyPageCompiledScriptSource(final String uri, String fullPath, Class<?> viewClass) { + GroovyPageCompiledScriptSource scriptSource = new GroovyPageCompiledScriptSource(uri, fullPath, viewClass); + if (reloadEnabled) { + scriptSource.setResourceCallable(new PrivilegedAction<Resource>() { + public Resource run() { + return findReloadablePage(uri); + } + }); + } + return scriptSource; + } + + protected GroovyPageScriptSource findBinaryScriptSource(String uri) { + if (pluginManager == null) { + return null; + } + + List<GrailsPlugin> allPlugins = Arrays.asList(pluginManager.getAllPlugins()); + Collections.reverse(allPlugins); + + for (GrailsPlugin plugin : allPlugins) { + if (!(plugin instanceof BinaryGrailsPlugin)) { + continue; + } + + BinaryGrailsPlugin binaryPlugin = (BinaryGrailsPlugin) plugin; + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Searching plugin [%s] for GSP view [%s]", plugin.getName(), uri)); + } + GroovyPageScriptSource scriptSource = resolveViewInBinaryPlugin(binaryPlugin, uri); + if (scriptSource != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Found GSP view [%s] in plugin [%s]", uri, plugin.getName())); + } + return scriptSource; + } + else if(binaryPlugin.getProjectDirectory() != null) { + scriptSource = resolveViewInPluginProjectDirectory(binaryPlugin, uri); + if(scriptSource != null) { + return scriptSource; + } + } + } + + return null; + } + + private GroovyPageScriptSource resolveViewInPluginProjectDirectory(BinaryGrailsPlugin binaryPlugin, String uri) { + File projectDirectory = binaryPlugin.getProjectDirectory(); + File f = new File(projectDirectory, "grails-app/views" + uri); + if(f.exists()) { + return new GroovyPageResourceScriptSource(uri, new FileSystemResource(f)); + } + return null; + } + + protected GroovyPageScriptSource findResourceScriptSourceInPlugins(String uri) { + if (pluginManager == null) { + return null; + } + + for (GrailsPlugin plugin : pluginManager.getAllPlugins()) { + if (plugin instanceof BinaryGrailsPlugin) { + continue; + } + + GroovyPageScriptSource scriptSource = findResourceScriptSource(resolvePluginViewPath(uri, plugin)); + if (scriptSource != null) { + return scriptSource; + } + } + + return null; + } + + protected Resource findResourceInPlugins(String uri) { + if (pluginManager == null) { + return null; + } + + for (GrailsPlugin plugin : pluginManager.getAllPlugins()) { + if (plugin instanceof BinaryGrailsPlugin) { + continue; + } + + Resource resource = findResource(resolvePluginViewPath(uri, plugin)); + if (resource != null) { + return resource; + } + } + + return null; + } + + protected String resolvePluginViewPath(String uri, GrailsPlugin plugin) { + uri = removeViewLocationPrefixes(uri); + return GrailsResourceUtils.appendPiecesForUri(plugin.getPluginPath(), GrailsResourceUtils.VIEWS_DIR_PATH, uri); + } + + protected String removeViewLocationPrefixes(String uri) { + uri = removePrefix(uri, GrailsResourceUtils.WEB_INF); + uri = removePrefix(uri, SLASHED_VIEWS_DIR_PATH); + uri = removePrefix(uri, GrailsResourceUtils.VIEWS_DIR_PATH); + return uri; + } + + protected String removePrefix(String uri, String prefix) { + if (uri.startsWith(prefix)) { + uri = uri.substring(prefix.length()); + } + return uri; + } + + protected GroovyPageScriptSource findResourceScriptSource(final String uri) { + List<String> searchPaths = resolveSearchPaths(uri); + + return findResourceScriptPathForSearchPaths(uri, searchPaths); + } + + protected List<String> resolveSearchPaths(String uri) { + List<String> searchPaths = null; + + uri = removeViewLocationPrefixes(uri); + if (warDeployed) { + if (uri.startsWith(PLUGINS_PATH)) { + PluginViewPathInfo pathInfo = getPluginViewPathInfo(uri); + + searchPaths = CollectionUtils.newList( + GrailsResourceUtils.appendPiecesForUri(GrailsResourceUtils.WEB_INF, PLUGINS_PATH, pathInfo.pluginName, GrailsResourceUtils.VIEWS_DIR_PATH, pathInfo.path), + GrailsResourceUtils.appendPiecesForUri(GrailsResourceUtils.WEB_INF, uri), + uri); + } else { + searchPaths = CollectionUtils.newList( + GrailsResourceUtils.appendPiecesForUri(PATH_TO_WEB_INF_VIEWS, uri), + uri); + } + } else { + searchPaths = CollectionUtils.newList( + GrailsResourceUtils.appendPiecesForUri(SLASHED_VIEWS_DIR_PATH, uri), + GrailsResourceUtils.appendPiecesForUri(PATH_TO_WEB_INF_VIEWS, uri), + uri); + } + return searchPaths; + } + + @SuppressWarnings("unchecked") + protected GroovyPageScriptSource findResourceScriptPathForSearchPaths(String uri, List<String> searchPaths) { + if (isPrecompiledAvailable()) { + for (String searchPath : searchPaths) { + String gspClassName = precompiledGspMap.get(searchPath); + if (gspClassName != null && !reloadedPrecompiledGspClassNames.contains(gspClassName)) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Found pre-compiled GSP template [%s] for path [%s]", gspClassName, searchPath)); + } + Class<GroovyPage> gspClass = null; + try { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Loading GSP template [%s]", gspClassName)); + } + gspClass = (Class<GroovyPage>) Class.forName(gspClassName, true, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + LOG.warn("Cannot load class " + gspClassName + ". Resuming on non-precompiled implementation.", e); + } + if (gspClass != null) { + GroovyPageCompiledScriptSource groovyPageCompiledScriptSource = createGroovyPageCompiledScriptSource(uri, searchPath, gspClass); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Returning new GSP script source for class [%s]", gspClassName)); + } + return groovyPageCompiledScriptSource; + } + } + } + } + + Resource foundResource = findResource(searchPaths); + return foundResource == null ? null : new GroovyPageResourceScriptSource(uri, foundResource); + } + + protected Resource findResource(String uri) { + return findResource(resolveSearchPaths(uri)); + } + + protected Resource findResource(List<String> searchPaths) { + Resource foundResource = null; + Resource resource; + for (ResourceLoader loader : resourceLoaders) { + for (String path : searchPaths) { + resource = loader.getResource(path); Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). This path depends on a [user-provided value](2). [Show more details](https://github.com/apache/grails-core/security/code-scanning/13) ########## grails-gsp/core/src/main/groovy/org/grails/gsp/io/DefaultGroovyPageLocator.java: ########## @@ -0,0 +1,442 @@ +/* + * Copyright 2011 SpringSource + * + * Licensed 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.grails.gsp.io; + +import grails.plugins.GrailsPlugin; +import grails.plugins.GrailsPluginManager; +import grails.plugins.PluginManagerAware; +import grails.util.CollectionUtils; +import grails.util.Environment; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.grails.gsp.GroovyPage; +import org.grails.gsp.GroovyPageBinding; +import org.grails.io.support.GrailsResourceUtils; +import org.grails.plugins.BinaryGrailsPlugin; +import org.grails.taglib.TemplateVariableBinding; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import java.io.File; +import java.security.PrivilegedAction; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Used to locate GSPs whether in development or WAR deployed mode from static + * resources, custom resource loaders and binary plugins. + * + * @author Graeme Rocher + * @since 2.0 + */ +public class DefaultGroovyPageLocator implements GroovyPageLocator, ResourceLoaderAware, ApplicationContextAware, PluginManagerAware { + + private static final Log LOG = LogFactory.getLog(DefaultGroovyPageLocator.class); + public static final String PATH_TO_WEB_INF_VIEWS = "/WEB-INF/grails-app/views"; + private static final String SLASHED_VIEWS_DIR_PATH = "/" + GrailsResourceUtils.VIEWS_DIR_PATH; + private static final String PLUGINS_PATH = "/plugins/"; + private static final String BLANK = ""; + protected Collection<ResourceLoader> resourceLoaders = new ConcurrentLinkedQueue<ResourceLoader>(); + protected GrailsPluginManager pluginManager; + private ConcurrentMap<String, String> precompiledGspMap; + protected boolean warDeployed = Environment.isWarDeployed(); + protected boolean reloadEnabled = !warDeployed; + private Set<String> reloadedPrecompiledGspClassNames = new CopyOnWriteArraySet<String>(); + + public void setResourceLoader(ResourceLoader resourceLoader) { + addResourceLoader(resourceLoader); + } + + public void addResourceLoader(ResourceLoader resourceLoader) { + if (resourceLoader != null && !resourceLoaders.contains(resourceLoader)) { + resourceLoaders.add(resourceLoader); + } + } + + public void setPrecompiledGspMap(Map<String, String> precompiledGspMap) { + if (precompiledGspMap == null) { + this.precompiledGspMap = null; + } else { + this.precompiledGspMap = new ConcurrentHashMap<String, String>(precompiledGspMap); + } + } + + public GroovyPageScriptSource findPage(final String uri) { + GroovyPageScriptSource scriptSource = findResourceScriptSource(uri); + if (scriptSource == null) { + scriptSource = findBinaryScriptSource(uri); + } + if (scriptSource == null) { + scriptSource = findResourceScriptSourceInPlugins(uri); + } + return scriptSource; + } + + protected Resource findReloadablePage(final String uri) { + Resource resource = findResource(uri); + if (resource == null) { + resource = findResourceInPlugins(uri); + } + return resource; + } + + public GroovyPageScriptSource findPageInBinding(String pluginName, String uri, TemplateVariableBinding binding) { + + GroovyPageScriptSource scriptSource = null; + String contextPath = resolveContextPath(pluginName, uri, binding); + String fullURI = GrailsResourceUtils.appendPiecesForUri(contextPath, uri); + + if(pluginManager != null) { + GrailsPlugin grailsPlugin = pluginManager.getGrailsPlugin(pluginName); + if(grailsPlugin instanceof BinaryGrailsPlugin) { + BinaryGrailsPlugin binaryGrailsPlugin = (BinaryGrailsPlugin) grailsPlugin; + File projectDirectory = binaryGrailsPlugin.getProjectDirectory(); + if(projectDirectory != null) { + File f = new File(projectDirectory, GrailsResourceUtils.VIEWS_DIR_PATH + uri); + if(f.exists()) { + scriptSource = new GroovyPageResourceScriptSource(uri, new FileSystemResource(f)); + } + } + else { + scriptSource = resolveViewInBinaryPlugin(binaryGrailsPlugin, uri); + } + } + } + + if(scriptSource == null) { + scriptSource = findPageInBinding(fullURI, binding); + } + + if (scriptSource == null) { + scriptSource = findResourceScriptSource(uri); + } + + + //last effort to resolve and force name of plugin to use camel case + if (scriptSource == null) { + contextPath = resolveContextPath(pluginName, uri, binding, true); + scriptSource = findPageInBinding(GrailsResourceUtils.appendPiecesForUri(contextPath, uri), binding); + } + + return scriptSource; + } + + protected String resolveContextPath(String pluginName, String uri, TemplateVariableBinding binding) { + return resolveContextPath(pluginName, uri, binding, false); + } + + protected String resolveContextPath(String pluginName, String uri, TemplateVariableBinding binding, boolean forceCamelCase) { + String contextPath = null; + + if (uri.startsWith("/plugins/")) { + contextPath = BLANK; + } else if (pluginName != null && pluginManager != null) { + contextPath = pluginManager.getPluginPath(pluginName); + } else if (binding instanceof GroovyPageBinding) { + String pluginContextPath = ((GroovyPageBinding) binding).getPluginContextPath(); + contextPath = pluginContextPath != null ? pluginContextPath : BLANK; + } else { + contextPath = BLANK; + } + + return contextPath; + } + + public void removePrecompiledPage(GroovyPageCompiledScriptSource scriptSource) { + reloadedPrecompiledGspClassNames.add(scriptSource.getCompiledClass().getName()); + if (scriptSource.getURI() != null && precompiledGspMap != null) { + precompiledGspMap.remove(scriptSource.getURI()); + } + } + + public GroovyPageScriptSource findPageInBinding(String uri, TemplateVariableBinding binding) { + GroovyPageScriptSource scriptSource = findResourceScriptSource(uri); + + if (scriptSource == null) { + GrailsPlugin pagePlugin = binding instanceof GroovyPageBinding ? ((GroovyPageBinding) binding).getPagePlugin() : null; + if (pagePlugin instanceof BinaryGrailsPlugin) { + BinaryGrailsPlugin binaryPlugin = (BinaryGrailsPlugin) pagePlugin; + scriptSource = resolveViewInBinaryPlugin(binaryPlugin, uri); + } else if (pagePlugin != null) { + scriptSource = findResourceScriptSource(resolvePluginViewPath(uri, pagePlugin)); + } + } + + if (scriptSource == null) { + scriptSource = findBinaryScriptSource(uri); + } + + return scriptSource; + } + + protected GroovyPageScriptSource resolveViewInBinaryPlugin(BinaryGrailsPlugin binaryPlugin, String uri) { + GroovyPageScriptSource scriptSource = null; + uri = removeViewLocationPrefixes(uri); + uri = GrailsResourceUtils.appendPiecesForUri(PATH_TO_WEB_INF_VIEWS, uri); + Class<?> viewClass = binaryPlugin.resolveView(uri); + if (viewClass != null && !reloadedPrecompiledGspClassNames.contains(viewClass.getName())) { + scriptSource = createGroovyPageCompiledScriptSource(uri, uri, viewClass); + // we know we have binary plugin, sp setting to null in the resourceCallable to skip reloading. + ((GroovyPageCompiledScriptSource) scriptSource).setResourceCallable(null); + } + return scriptSource; + } + + protected GroovyPageCompiledScriptSource createGroovyPageCompiledScriptSource(final String uri, String fullPath, Class<?> viewClass) { + GroovyPageCompiledScriptSource scriptSource = new GroovyPageCompiledScriptSource(uri, fullPath, viewClass); + if (reloadEnabled) { + scriptSource.setResourceCallable(new PrivilegedAction<Resource>() { + public Resource run() { + return findReloadablePage(uri); + } + }); + } + return scriptSource; + } + + protected GroovyPageScriptSource findBinaryScriptSource(String uri) { + if (pluginManager == null) { + return null; + } + + List<GrailsPlugin> allPlugins = Arrays.asList(pluginManager.getAllPlugins()); + Collections.reverse(allPlugins); + + for (GrailsPlugin plugin : allPlugins) { + if (!(plugin instanceof BinaryGrailsPlugin)) { + continue; + } + + BinaryGrailsPlugin binaryPlugin = (BinaryGrailsPlugin) plugin; + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Searching plugin [%s] for GSP view [%s]", plugin.getName(), uri)); + } + GroovyPageScriptSource scriptSource = resolveViewInBinaryPlugin(binaryPlugin, uri); + if (scriptSource != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Found GSP view [%s] in plugin [%s]", uri, plugin.getName())); + } + return scriptSource; + } + else if(binaryPlugin.getProjectDirectory() != null) { + scriptSource = resolveViewInPluginProjectDirectory(binaryPlugin, uri); + if(scriptSource != null) { + return scriptSource; + } + } + } + + return null; + } + + private GroovyPageScriptSource resolveViewInPluginProjectDirectory(BinaryGrailsPlugin binaryPlugin, String uri) { + File projectDirectory = binaryPlugin.getProjectDirectory(); + File f = new File(projectDirectory, "grails-app/views" + uri); + if(f.exists()) { + return new GroovyPageResourceScriptSource(uri, new FileSystemResource(f)); + } + return null; + } + + protected GroovyPageScriptSource findResourceScriptSourceInPlugins(String uri) { + if (pluginManager == null) { + return null; + } + + for (GrailsPlugin plugin : pluginManager.getAllPlugins()) { + if (plugin instanceof BinaryGrailsPlugin) { + continue; + } + + GroovyPageScriptSource scriptSource = findResourceScriptSource(resolvePluginViewPath(uri, plugin)); + if (scriptSource != null) { + return scriptSource; + } + } + + return null; + } + + protected Resource findResourceInPlugins(String uri) { + if (pluginManager == null) { + return null; + } + + for (GrailsPlugin plugin : pluginManager.getAllPlugins()) { + if (plugin instanceof BinaryGrailsPlugin) { + continue; + } + + Resource resource = findResource(resolvePluginViewPath(uri, plugin)); + if (resource != null) { + return resource; + } + } + + return null; + } + + protected String resolvePluginViewPath(String uri, GrailsPlugin plugin) { + uri = removeViewLocationPrefixes(uri); + return GrailsResourceUtils.appendPiecesForUri(plugin.getPluginPath(), GrailsResourceUtils.VIEWS_DIR_PATH, uri); + } + + protected String removeViewLocationPrefixes(String uri) { + uri = removePrefix(uri, GrailsResourceUtils.WEB_INF); + uri = removePrefix(uri, SLASHED_VIEWS_DIR_PATH); + uri = removePrefix(uri, GrailsResourceUtils.VIEWS_DIR_PATH); + return uri; + } + + protected String removePrefix(String uri, String prefix) { + if (uri.startsWith(prefix)) { + uri = uri.substring(prefix.length()); + } + return uri; + } + + protected GroovyPageScriptSource findResourceScriptSource(final String uri) { + List<String> searchPaths = resolveSearchPaths(uri); + + return findResourceScriptPathForSearchPaths(uri, searchPaths); + } + + protected List<String> resolveSearchPaths(String uri) { + List<String> searchPaths = null; + + uri = removeViewLocationPrefixes(uri); + if (warDeployed) { + if (uri.startsWith(PLUGINS_PATH)) { + PluginViewPathInfo pathInfo = getPluginViewPathInfo(uri); + + searchPaths = CollectionUtils.newList( + GrailsResourceUtils.appendPiecesForUri(GrailsResourceUtils.WEB_INF, PLUGINS_PATH, pathInfo.pluginName, GrailsResourceUtils.VIEWS_DIR_PATH, pathInfo.path), + GrailsResourceUtils.appendPiecesForUri(GrailsResourceUtils.WEB_INF, uri), + uri); + } else { + searchPaths = CollectionUtils.newList( + GrailsResourceUtils.appendPiecesForUri(PATH_TO_WEB_INF_VIEWS, uri), + uri); + } + } else { + searchPaths = CollectionUtils.newList( + GrailsResourceUtils.appendPiecesForUri(SLASHED_VIEWS_DIR_PATH, uri), + GrailsResourceUtils.appendPiecesForUri(PATH_TO_WEB_INF_VIEWS, uri), + uri); + } + return searchPaths; + } + + @SuppressWarnings("unchecked") + protected GroovyPageScriptSource findResourceScriptPathForSearchPaths(String uri, List<String> searchPaths) { + if (isPrecompiledAvailable()) { + for (String searchPath : searchPaths) { + String gspClassName = precompiledGspMap.get(searchPath); + if (gspClassName != null && !reloadedPrecompiledGspClassNames.contains(gspClassName)) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Found pre-compiled GSP template [%s] for path [%s]", gspClassName, searchPath)); + } + Class<GroovyPage> gspClass = null; + try { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Loading GSP template [%s]", gspClassName)); + } + gspClass = (Class<GroovyPage>) Class.forName(gspClassName, true, Thread.currentThread().getContextClassLoader()); + } catch (ClassNotFoundException e) { + LOG.warn("Cannot load class " + gspClassName + ". Resuming on non-precompiled implementation.", e); + } + if (gspClass != null) { + GroovyPageCompiledScriptSource groovyPageCompiledScriptSource = createGroovyPageCompiledScriptSource(uri, searchPath, gspClass); + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Returning new GSP script source for class [%s]", gspClassName)); + } + return groovyPageCompiledScriptSource; + } + } + } + } + + Resource foundResource = findResource(searchPaths); + return foundResource == null ? null : new GroovyPageResourceScriptSource(uri, foundResource); + } + + protected Resource findResource(String uri) { + return findResource(resolveSearchPaths(uri)); + } + + protected Resource findResource(List<String> searchPaths) { + Resource foundResource = null; + Resource resource; + for (ResourceLoader loader : resourceLoaders) { + for (String path : searchPaths) { + resource = loader.getResource(path); Review Comment: ## Server-side request forgery Potential server-side request forgery due to a [user-provided value](1). Potential server-side request forgery due to a [user-provided value](2). [Show more details](https://github.com/apache/grails-core/security/code-scanning/19) ########## grails-testing-support-web/src/main/groovy/org/grails/testing/runtime/support/GroovyPageUnitTestResourceLoader.java: ########## @@ -0,0 +1,101 @@ +/* + * 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.grails.testing.runtime.support; + +import grails.config.Config; +import grails.config.Settings; +import grails.core.GrailsApplication; +import grails.core.support.GrailsApplicationAware; +import grails.util.BuildSettings; +import groovy.transform.CompileStatic; +import org.grails.io.support.GrailsResourceUtils; +import org.springframework.beans.factory.InitializingBean; +import org.springframework.core.io.ByteArrayResource; +import org.springframework.core.io.DefaultResourceLoader; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; + +import java.io.File; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * A {@link org.springframework.core.io.ResourceLoader} implementation + * that loads GSP views relative to the project base directory for unit tests. + * + */ +@CompileStatic +public class GroovyPageUnitTestResourceLoader extends DefaultResourceLoader implements GrailsApplicationAware, InitializingBean { + + public static final String WEB_INF_PREFIX = "/WEB-INF/grails-app/views"; + private Map<String,String> groovyPages = new ConcurrentHashMap<String, String>(); + private String basePath; + private GrailsApplication grailsApplication; + + public GroovyPageUnitTestResourceLoader(Map<String, String> groovyPages) { + this.groovyPages = groovyPages; + } + + @Override + public Resource getResource(String location) { + + if (location.startsWith(WEB_INF_PREFIX)) { + location = location.substring(WEB_INF_PREFIX.length()); + } + if (groovyPages.containsKey(location)) { + return new ByteArrayResource(groovyPages.get(location).getBytes(StandardCharsets.UTF_8), location); + } + + if(basePath == null) { + String basedir = BuildSettings.BASE_DIR.getAbsolutePath(); + basePath = basedir + File.separatorChar + GrailsResourceUtils.VIEWS_DIR_PATH; + } + + String path = basePath + location; + path = makeCanonical(path); + return new FileSystemResource(path); Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). This path depends on a [user-provided value](2). [Show more details](https://github.com/apache/grails-core/security/code-scanning/14) ########## grails-gsp/core/src/main/groovy/org/grails/gsp/io/DefaultGroovyPageLocator.java: ########## @@ -0,0 +1,442 @@ +/* + * Copyright 2011 SpringSource + * + * Licensed 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.grails.gsp.io; + +import grails.plugins.GrailsPlugin; +import grails.plugins.GrailsPluginManager; +import grails.plugins.PluginManagerAware; +import grails.util.CollectionUtils; +import grails.util.Environment; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.grails.gsp.GroovyPage; +import org.grails.gsp.GroovyPageBinding; +import org.grails.io.support.GrailsResourceUtils; +import org.grails.plugins.BinaryGrailsPlugin; +import org.grails.taglib.TemplateVariableBinding; +import org.springframework.beans.BeansException; +import org.springframework.context.ApplicationContext; +import org.springframework.context.ApplicationContextAware; +import org.springframework.context.ResourceLoaderAware; +import org.springframework.core.io.FileSystemResource; +import org.springframework.core.io.Resource; +import org.springframework.core.io.ResourceLoader; + +import java.io.File; +import java.security.PrivilegedAction; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArraySet; + +/** + * Used to locate GSPs whether in development or WAR deployed mode from static + * resources, custom resource loaders and binary plugins. + * + * @author Graeme Rocher + * @since 2.0 + */ +public class DefaultGroovyPageLocator implements GroovyPageLocator, ResourceLoaderAware, ApplicationContextAware, PluginManagerAware { + + private static final Log LOG = LogFactory.getLog(DefaultGroovyPageLocator.class); + public static final String PATH_TO_WEB_INF_VIEWS = "/WEB-INF/grails-app/views"; + private static final String SLASHED_VIEWS_DIR_PATH = "/" + GrailsResourceUtils.VIEWS_DIR_PATH; + private static final String PLUGINS_PATH = "/plugins/"; + private static final String BLANK = ""; + protected Collection<ResourceLoader> resourceLoaders = new ConcurrentLinkedQueue<ResourceLoader>(); + protected GrailsPluginManager pluginManager; + private ConcurrentMap<String, String> precompiledGspMap; + protected boolean warDeployed = Environment.isWarDeployed(); + protected boolean reloadEnabled = !warDeployed; + private Set<String> reloadedPrecompiledGspClassNames = new CopyOnWriteArraySet<String>(); + + public void setResourceLoader(ResourceLoader resourceLoader) { + addResourceLoader(resourceLoader); + } + + public void addResourceLoader(ResourceLoader resourceLoader) { + if (resourceLoader != null && !resourceLoaders.contains(resourceLoader)) { + resourceLoaders.add(resourceLoader); + } + } + + public void setPrecompiledGspMap(Map<String, String> precompiledGspMap) { + if (precompiledGspMap == null) { + this.precompiledGspMap = null; + } else { + this.precompiledGspMap = new ConcurrentHashMap<String, String>(precompiledGspMap); + } + } + + public GroovyPageScriptSource findPage(final String uri) { + GroovyPageScriptSource scriptSource = findResourceScriptSource(uri); + if (scriptSource == null) { + scriptSource = findBinaryScriptSource(uri); + } + if (scriptSource == null) { + scriptSource = findResourceScriptSourceInPlugins(uri); + } + return scriptSource; + } + + protected Resource findReloadablePage(final String uri) { + Resource resource = findResource(uri); + if (resource == null) { + resource = findResourceInPlugins(uri); + } + return resource; + } + + public GroovyPageScriptSource findPageInBinding(String pluginName, String uri, TemplateVariableBinding binding) { + + GroovyPageScriptSource scriptSource = null; + String contextPath = resolveContextPath(pluginName, uri, binding); + String fullURI = GrailsResourceUtils.appendPiecesForUri(contextPath, uri); + + if(pluginManager != null) { + GrailsPlugin grailsPlugin = pluginManager.getGrailsPlugin(pluginName); + if(grailsPlugin instanceof BinaryGrailsPlugin) { + BinaryGrailsPlugin binaryGrailsPlugin = (BinaryGrailsPlugin) grailsPlugin; + File projectDirectory = binaryGrailsPlugin.getProjectDirectory(); + if(projectDirectory != null) { + File f = new File(projectDirectory, GrailsResourceUtils.VIEWS_DIR_PATH + uri); + if(f.exists()) { + scriptSource = new GroovyPageResourceScriptSource(uri, new FileSystemResource(f)); + } + } + else { + scriptSource = resolveViewInBinaryPlugin(binaryGrailsPlugin, uri); + } + } + } + + if(scriptSource == null) { + scriptSource = findPageInBinding(fullURI, binding); + } + + if (scriptSource == null) { + scriptSource = findResourceScriptSource(uri); + } + + + //last effort to resolve and force name of plugin to use camel case + if (scriptSource == null) { + contextPath = resolveContextPath(pluginName, uri, binding, true); + scriptSource = findPageInBinding(GrailsResourceUtils.appendPiecesForUri(contextPath, uri), binding); + } + + return scriptSource; + } + + protected String resolveContextPath(String pluginName, String uri, TemplateVariableBinding binding) { + return resolveContextPath(pluginName, uri, binding, false); + } + + protected String resolveContextPath(String pluginName, String uri, TemplateVariableBinding binding, boolean forceCamelCase) { + String contextPath = null; + + if (uri.startsWith("/plugins/")) { + contextPath = BLANK; + } else if (pluginName != null && pluginManager != null) { + contextPath = pluginManager.getPluginPath(pluginName); + } else if (binding instanceof GroovyPageBinding) { + String pluginContextPath = ((GroovyPageBinding) binding).getPluginContextPath(); + contextPath = pluginContextPath != null ? pluginContextPath : BLANK; + } else { + contextPath = BLANK; + } + + return contextPath; + } + + public void removePrecompiledPage(GroovyPageCompiledScriptSource scriptSource) { + reloadedPrecompiledGspClassNames.add(scriptSource.getCompiledClass().getName()); + if (scriptSource.getURI() != null && precompiledGspMap != null) { + precompiledGspMap.remove(scriptSource.getURI()); + } + } + + public GroovyPageScriptSource findPageInBinding(String uri, TemplateVariableBinding binding) { + GroovyPageScriptSource scriptSource = findResourceScriptSource(uri); + + if (scriptSource == null) { + GrailsPlugin pagePlugin = binding instanceof GroovyPageBinding ? ((GroovyPageBinding) binding).getPagePlugin() : null; + if (pagePlugin instanceof BinaryGrailsPlugin) { + BinaryGrailsPlugin binaryPlugin = (BinaryGrailsPlugin) pagePlugin; + scriptSource = resolveViewInBinaryPlugin(binaryPlugin, uri); + } else if (pagePlugin != null) { + scriptSource = findResourceScriptSource(resolvePluginViewPath(uri, pagePlugin)); + } + } + + if (scriptSource == null) { + scriptSource = findBinaryScriptSource(uri); + } + + return scriptSource; + } + + protected GroovyPageScriptSource resolveViewInBinaryPlugin(BinaryGrailsPlugin binaryPlugin, String uri) { + GroovyPageScriptSource scriptSource = null; + uri = removeViewLocationPrefixes(uri); + uri = GrailsResourceUtils.appendPiecesForUri(PATH_TO_WEB_INF_VIEWS, uri); + Class<?> viewClass = binaryPlugin.resolveView(uri); + if (viewClass != null && !reloadedPrecompiledGspClassNames.contains(viewClass.getName())) { + scriptSource = createGroovyPageCompiledScriptSource(uri, uri, viewClass); + // we know we have binary plugin, sp setting to null in the resourceCallable to skip reloading. + ((GroovyPageCompiledScriptSource) scriptSource).setResourceCallable(null); + } + return scriptSource; + } + + protected GroovyPageCompiledScriptSource createGroovyPageCompiledScriptSource(final String uri, String fullPath, Class<?> viewClass) { + GroovyPageCompiledScriptSource scriptSource = new GroovyPageCompiledScriptSource(uri, fullPath, viewClass); + if (reloadEnabled) { + scriptSource.setResourceCallable(new PrivilegedAction<Resource>() { + public Resource run() { + return findReloadablePage(uri); + } + }); + } + return scriptSource; + } + + protected GroovyPageScriptSource findBinaryScriptSource(String uri) { + if (pluginManager == null) { + return null; + } + + List<GrailsPlugin> allPlugins = Arrays.asList(pluginManager.getAllPlugins()); + Collections.reverse(allPlugins); + + for (GrailsPlugin plugin : allPlugins) { + if (!(plugin instanceof BinaryGrailsPlugin)) { + continue; + } + + BinaryGrailsPlugin binaryPlugin = (BinaryGrailsPlugin) plugin; + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Searching plugin [%s] for GSP view [%s]", plugin.getName(), uri)); + } + GroovyPageScriptSource scriptSource = resolveViewInBinaryPlugin(binaryPlugin, uri); + if (scriptSource != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(String.format("Found GSP view [%s] in plugin [%s]", uri, plugin.getName())); + } + return scriptSource; + } + else if(binaryPlugin.getProjectDirectory() != null) { + scriptSource = resolveViewInPluginProjectDirectory(binaryPlugin, uri); + if(scriptSource != null) { + return scriptSource; + } + } + } + + return null; + } + + private GroovyPageScriptSource resolveViewInPluginProjectDirectory(BinaryGrailsPlugin binaryPlugin, String uri) { + File projectDirectory = binaryPlugin.getProjectDirectory(); + File f = new File(projectDirectory, "grails-app/views" + uri); + if(f.exists()) { + return new GroovyPageResourceScriptSource(uri, new FileSystemResource(f)); Review Comment: ## Uncontrolled data used in path expression This path depends on a [user-provided value](1). This path depends on a [user-provided value](2). [Show more details](https://github.com/apache/grails-core/security/code-scanning/12) -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
