Updated devMode to flow 2.7.1 (vaadin 14.7.1)

This commit is contained in:
Robinson 2021-10-08 15:31:34 +02:00
parent 19228ebd21
commit faf7164f68
No known key found for this signature in database
GPG Key ID: 8E7DB78588BD6F5C
5 changed files with 1038 additions and 824 deletions

View File

@ -1,11 +1,11 @@
package dorkbox.vaadin.devMode
import com.vaadin.flow.server.frontend.scanner.ClassFinder
import com.vaadin.flow.server.startup.DevModeInitializer
import java.util.*
import javax.servlet.annotation.HandlesTypes
/**
* THIS IS COPIED DIRECTLY FROM VAADIN 14.6.8 (flow 2.4.6)
* THIS IS COPIED DIRECTLY FROM VAADIN 14.7.1 (flow 2.7.1)
*
* CHANGES FROM DEFAULT ARE MANAGED AS DIFFERENT REVISIONS.
*
@ -13,33 +13,33 @@ import javax.servlet.annotation.HandlesTypes
*
* This file is NOT extensible/configurable AT-ALL, so this is required...
*/
internal class DevModeClassFinder(classes: Set<Class<*>?>?) : ClassFinder.DefaultClassFinder(classes) {
companion object {
private val APPLICABLE_CLASS_NAMES = Collections.unmodifiableSet(calculateApplicableClassNames())
internal class DevModeClassFinder(classes: Set<Class<*>?>?) : com.vaadin.flow.server.frontend.scanner.ClassFinder.DefaultClassFinder(classes) {
companion object {
private val APPLICABLE_CLASS_NAMES = Collections.unmodifiableSet(calculateApplicableClassNames())
private fun calculateApplicableClassNames(): Set<String> {
val handlesTypes: HandlesTypes = DevModeInitializer::class.java.getAnnotation(HandlesTypes::class.java)
return handlesTypes.value.map { it.qualifiedName!! }.toSet()
}
}
override fun getAnnotatedClasses(annotation: Class<out Annotation?>): Set<Class<*>> {
ensureImplementation(annotation)
return super.getAnnotatedClasses(annotation)
}
override fun <T> getSubTypesOf(type: Class<T>): Set<Class<out T>> {
ensureImplementation(type)
return super.getSubTypesOf(type)
}
private fun ensureImplementation(clazz: Class<*>) {
require(APPLICABLE_CLASS_NAMES.contains(clazz.name)) {
("Unexpected class name "
+ clazz + ". Implementation error: the class finder "
+ "instance is not aware of this class. "
+ "Fix @HandlesTypes annotation value for "
+ DevModeInitializer::class.java.name)
}
private fun calculateApplicableClassNames(): Set<String> {
val handlesTypes: HandlesTypes = DevModeInitializer::class.java.getAnnotation(HandlesTypes::class.java)
return handlesTypes.value.map { it.qualifiedName!! }.toSet()
}
}
override fun getAnnotatedClasses(annotation: Class<out Annotation?>): Set<Class<*>> {
ensureImplementation(annotation)
return super.getAnnotatedClasses(annotation)
}
override fun <T> getSubTypesOf(type: Class<T>): Set<Class<out T>> {
ensureImplementation(type)
return super.getSubTypesOf(type)
}
private fun ensureImplementation(clazz: Class<*>) {
require(APPLICABLE_CLASS_NAMES.contains(clazz.name)) {
("Unexpected class name "
+ clazz + ". Implementation error: the class finder "
+ "instance is not aware of this class. "
+ "Fix @HandlesTypes annotation value for "
+ DevModeInitializer::class.java.name)
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -25,6 +25,7 @@ import com.vaadin.flow.function.DeploymentConfiguration
import com.vaadin.flow.router.HasErrorParameter
import com.vaadin.flow.router.Route
import com.vaadin.flow.server.*
import com.vaadin.flow.server.DevModeHandler
import com.vaadin.flow.server.frontend.FrontendUtils
import com.vaadin.flow.server.frontend.NodeTasks
import com.vaadin.flow.server.startup.ClassLoaderAwareServletContainerInitializer
@ -44,6 +45,7 @@ import java.net.URLDecoder
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.nio.file.Path
import java.nio.file.Paths
import java.util.concurrent.CompletableFuture
import java.util.concurrent.CompletionException
import java.util.concurrent.Executor
@ -55,7 +57,7 @@ import javax.servlet.annotation.HandlesTypes
import javax.servlet.annotation.WebListener
/**
* THIS IS COPIED DIRECTLY FROM VAADIN 14.6.8 (flow 2.4.6)
* THIS IS COPIED DIRECTLY FROM VAADIN 14.7.1 (flow 2.7.1)
*
* CHANGES FROM DEFAULT ARE MANAGED AS DIFFERENT REVISIONS.
*
@ -65,10 +67,12 @@ import javax.servlet.annotation.WebListener
*
*
*
*
* Servlet initializer starting node updaters as well as the webpack-dev-mode
* server.
*
*
* For internal use only. May be renamed or removed in a future release.
*
* @since 2.0
*/
@HandlesTypes(
@ -91,6 +95,61 @@ import javax.servlet.annotation.WebListener
)
@WebListener
class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializable, ServletContextListener {
@Throws(ServletException::class)
override fun process(classes: Set<Class<*>?>?, context: ServletContext) {
val registrations: Collection<ServletRegistration> = context.servletRegistrations.values
var vaadinServletRegistration: ServletRegistration? = null
for (registration in registrations) {
if (registration.className != null
&& isVaadinServletSubClass(registration.className)
) {
vaadinServletRegistration = registration
break
}
}
val config: DeploymentConfiguration
config = if (vaadinServletRegistration != null) {
StubServletConfig.createDeploymentConfiguration(
context,
vaadinServletRegistration, VaadinServlet::class.java
)
} else {
StubServletConfig.createDeploymentConfiguration(
context,
VaadinServlet::class.java
)
}
initDevModeHandler(classes, context, config)
}
private fun isVaadinServletSubClass(className: String): Boolean {
return try {
VaadinServlet::class.java.isAssignableFrom(Class.forName(className))
} catch (exception: ClassNotFoundException) { // NOSONAR
log().debug(
String.format(
"Servlet class name (%s) can't be found!",
className
)
)
false
}
}
override fun contextInitialized(ctx: ServletContextEvent) {
// No need to do anything on init
}
override fun contextDestroyed(ctx: ServletContextEvent) {
val handler = DevModeHandler.getDevModeHandler()
if (handler != null && !handler.reuseDevServer()) {
handler.stop()
}
}
companion object {
private val JAR_FILE_REGEX = Pattern.compile(".*file:(.+\\.jar).*")
@ -105,11 +164,10 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
private val DIR_REGEX_FRONTEND_DEFAULT = Pattern.compile("^(?:file:0)?(.+)" + Constants.RESOURCES_FRONTEND_DEFAULT + "/?$")
// allow trailing slash
private val DIR_REGEX_COMPATIBILITY_FRONTEND_DEFAULT = Pattern.compile(
"^(?:file:)?(.+)"
+ Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT
+ "/?$"
)
private val DIR_REGEX_RESOURCES_JAR_DEFAULT = Pattern.compile("^(?:file:0)?(.+)" + Constants.RESOURCES_THEME_JAR_DEFAULT+ "/?$")
// allow trailing slash
private val DIR_REGEX_COMPATIBILITY_FRONTEND_DEFAULT = Pattern.compile("^(?:file:)?(.+)" + Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT+ "/?$")
/**
* Initialize the devmode server if not in production mode or compatibility
@ -127,7 +185,6 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
*/
@Throws(ServletException::class)
fun initDevModeHandler(classes: Set<Class<*>?>?, context: ServletContext?, config: DeploymentConfiguration) {
System.err.println("CUSTOM INIT!")
if (config.isProductionMode) {
log().debug("Skipping DEV MODE because PRODUCTION MODE is set.")
return
@ -142,15 +199,20 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
}
// these are BOTH set by VaadinApplication.kt
val baseDir = config.getStringProperty(FrontendUtils.PROJECT_BASEDIR, File("").absolutePath)
var baseDir = config.getStringProperty(FrontendUtils.PROJECT_BASEDIR, null)
if (baseDir == null) {
baseDir = baseDirectoryFallback
}
val generatedDir = System.getProperty(FrontendUtils.PARAM_GENERATED_DIR, FrontendUtils.DEFAULT_GENERATED_DIR)
val frontendFolder = config.getStringProperty(
FrontendUtils.PARAM_FRONTEND_DIR,
System.getProperty(FrontendUtils.PARAM_FRONTEND_DIR, FrontendUtils.DEFAULT_FRONTEND_DIR))
val frontendFolder = config.getStringProperty(FrontendUtils.PARAM_FRONTEND_DIR,
System.getProperty(FrontendUtils.PARAM_FRONTEND_DIR, FrontendUtils.DEFAULT_FRONTEND_DIR))
val builder = NodeTasks.Builder(DevModeClassFinder(classes), File(baseDir), File(generatedDir), File(frontendFolder))
val builder = NodeTasks.Builder(
DevModeClassFinder(classes),
File(baseDir), File(generatedDir),
File(frontendFolder)
)
log().info("Starting dev-mode updaters in {} folder.", builder.npmFolder)
if (!builder.generatedFolder.exists()) {
@ -168,11 +230,8 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
}
val generatedPackages = File(builder.generatedFolder, Constants.PACKAGE_JSON)
// If we are missing the generated webpack configuration then generate
// webpack configurations
if (!File(builder.npmFolder, FrontendUtils.WEBPACK_GENERATED).exists()) {
builder.withWebpack(builder.npmFolder, FrontendUtils.WEBPACK_CONFIG, FrontendUtils.WEBPACK_GENERATED)
}
// Always update to auto-generated webpack configuration
builder.withWebpack(builder.npmFolder, FrontendUtils.WEBPACK_CONFIG, FrontendUtils.WEBPACK_GENERATED)
// If we are missing either the base or generated package json files
// generate those
@ -180,7 +239,9 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
builder.createMissingPackageJson(true)
}
val frontendLocations = getFrontendLocationsFromClassloader(DevModeInitializer::class.java.classLoader)
val frontendLocations = getFrontendLocationsFromClassloader(
DevModeInitializer::class.java.classLoader
)
val useByteCodeScanner = config.getBooleanProperty(
InitParameters.SERVLET_PARAMETER_DEVMODE_OPTIMIZE_BUNDLE,
java.lang.Boolean.parseBoolean(
@ -190,7 +251,6 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
)
)
)
val enablePnpm = config.isPnpmEnabled
val useHomeNodeExec = config.getBooleanProperty(InitParameters.REQUIRE_HOME_NODE_EXECUTABLE, false)
val vaadinContext: VaadinContext = VaadinServletContext(context)
@ -233,16 +293,12 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
)
}
val nodeTasksFuture =
if (service is Executor) {
// if there is an executor use it to run the task
CompletableFuture.runAsync(
runnable,
service as Executor?
)
} else {
CompletableFuture.runAsync(runnable)
}
val nodeTasksFuture = if (service is Executor) {
// if there is an executor use it to run the task
CompletableFuture.runAsync(runnable, service as Executor?)
} else {
CompletableFuture.runAsync(runnable)
}
DevModeHandler.start(config, builder.npmFolder, nodeTasksFuture)
}
@ -251,10 +307,39 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
return LoggerFactory.getLogger(DevModeInitializer::class.java)
}
/*
/**
* Accept user.dir or cwd as a fallback only if the directory seems to be a
* Maven or Gradle project. Check to avoid cluttering server directories
* (see tickets #8249, #8403).
*/
private val baseDirectoryFallback: String
private get() {
val baseDirCandidate = System.getProperty("user.dir", ".")
val path = Paths.get(baseDirCandidate)
return if (path.toFile().isDirectory
&& (path.resolve("pom.xml").toFile().exists()
|| path.resolve("build.gradle").toFile().exists())
) {
path.toString()
} else {
throw IllegalStateException(
String.format(
"Failed to determine project directory for dev mode. "
+ "Directory '%s' does not look like a Maven or "
+ "Gradle project. Ensure that you have run the "
+ "prepare-frontend Maven goal, which generates "
+ "'flow-build-info.json', prior to deploying your "
+ "application",
path.toString()
)
)
}
}
/**
* This method returns all folders of jar files having files in the
* META-INF/resources/frontend folder. We don't use URLClassLoader because
* will fail in Java 9+
* META-INF/resources/frontend and META-INF/resources/themes folder. We
* don't use URLClassLoader because will fail in Java 9+
*/
@Throws(ServletException::class)
fun getFrontendLocationsFromClassloader(classLoader: ClassLoader): Set<File> {
@ -271,10 +356,15 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
Constants.COMPATIBILITY_RESOURCES_FRONTEND_DEFAULT
)
)
frontendFiles.addAll(
getFrontendLocationsFromClassloader(
classLoader,
Constants.RESOURCES_THEME_JAR_DEFAULT
)
)
return frontendFiles
}
private fun runNodeTasks(vaadinContext: VaadinContext, tokenFileData: JsonObject, tasks: NodeTasks) {
try {
tasks.execute()
@ -289,7 +379,9 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
}
@Throws(ServletException::class)
private fun getFrontendLocationsFromClassloader(classLoader: ClassLoader, resourcesFolder: String): Set<File> {
private fun getFrontendLocationsFromClassloader(
classLoader: ClassLoader, resourcesFolder: String
): Set<File> {
val frontendFiles: MutableSet<File> = HashSet()
try {
val en = classLoader.getResources(resourcesFolder) ?: return frontendFiles
@ -302,6 +394,7 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
val jarMatcher = JAR_FILE_REGEX.matcher(path)
val zipProtocolJarMatcher = ZIP_PROTOCOL_JAR_FILE_REGEX.matcher(path)
val dirMatcher = DIR_REGEX_FRONTEND_DEFAULT.matcher(path)
val dirResourcesMatcher = DIR_REGEX_RESOURCES_JAR_DEFAULT.matcher(path)
val dirCompatibilityMatcher = DIR_REGEX_COMPATIBILITY_FRONTEND_DEFAULT.matcher(path)
val jarVfsMatcher = VFS_FILE_REGEX.matcher(urlString)
val dirVfsMatcher = VFS_DIRECTORY_REGEX.matcher(urlString)
@ -312,10 +405,7 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
getPhysicalFileOfJBossVfsJar(URL(vfsJar))
)
} else if (dirVfsMatcher.find()) {
val vfsDirUrl = URL(
urlString.substring(0,urlString.lastIndexOf(resourcesFolder)
)
)
val vfsDirUrl = URL(urlString.substring(0, urlString.lastIndexOf(resourcesFolder)))
frontendFiles.add(getPhysicalFileOfJBossVfsDirectory(vfsDirUrl))
} else if (jarMatcher.find()) {
frontendFiles.add(File(jarMatcher.group(1)))
@ -324,6 +414,8 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
frontendFiles.add(File(zipProtocolJarMatcher.group(1)))
} else if (dirMatcher.find()) {
frontendFiles.add(File(dirMatcher.group(1)))
} else if (dirResourcesMatcher.find()) {
frontendFiles.add(File(dirResourcesMatcher.group(1)))
} else if (dirCompatibilityMatcher.find()) {
frontendFiles.add(File(dirCompatibilityMatcher.group(1)))
} else {
@ -404,9 +496,7 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
if (!(isFileMethod.invoke(child) as Boolean)) continue
val relativePath = getPathNameRelativeToMethod.invoke(child, jarVirtualFile) as String
val inputStream = openStreamMethod.invoke(child) as InputStream
val zipEntry = ZipEntry(relativePath)
zipOutputStream.putNextEntry(zipEntry)
IOUtils.copy(inputStream, zipOutputStream)
@ -415,47 +505,4 @@ class DevModeInitializer : ClassLoaderAwareServletContainerInitializer, Serializ
}
}
}
@Throws(ServletException::class)
override fun process(classes: Set<Class<*>?>?, context: ServletContext) {
val registrations: Collection<ServletRegistration> = context.servletRegistrations.values
var vaadinServletRegistration: ServletRegistration? = null
for (registration in registrations) {
try {
if (registration.className != null && isVaadinServletSubClass(registration.className)) {
vaadinServletRegistration = registration
break
}
} catch (e: ClassNotFoundException) {
throw ServletException(String.format("Servlet class name (%s) can't be found!", registration.className), e)
}
}
val config =
if (vaadinServletRegistration != null) {
StubServletConfig.createDeploymentConfiguration(context, vaadinServletRegistration, VaadinServlet::class.java)
} else {
StubServletConfig.createDeploymentConfiguration(context, VaadinServlet::class.java)
}
initDevModeHandler(classes, context, config)
}
@Throws(ClassNotFoundException::class)
private fun isVaadinServletSubClass(className: String): Boolean {
return VaadinServlet::class.java.isAssignableFrom(Class.forName(className))
}
override fun contextInitialized(ctx: ServletContextEvent) {
// No need to do anything on init
}
override fun contextDestroyed(ctx: ServletContextEvent) {
val handler = DevModeHandler.devModeHandler
if (handler != null && !handler.reuseDevServer()) {
handler.stop()
}
}
}

View File

@ -15,6 +15,7 @@
*/
package dorkbox.vaadin.devMode
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import java.io.BufferedReader
import java.io.IOException
@ -24,17 +25,13 @@ import java.net.Socket
import java.nio.charset.StandardCharsets
/**
* THIS IS COPIED DIRECTLY FROM VAADIN 14.6.8 (flow 2.4.6)
* THIS IS COPIED DIRECTLY FROM VAADIN 14.7.1 (flow 2.7.1)
*
* CHANGES FROM DEFAULT ARE MANAGED AS DIFFERENT REVISIONS.
*
* The initial commit is exactly as-is from vaadin.
*
* This file is NOT extensible/configurable AT-ALL, so this is required...
*
*
*
*
* Opens a server socket which is supposed to be opened until dev mode is active
* inside JVM.
*
@ -42,31 +39,20 @@ import java.nio.charset.StandardCharsets
* If this socket is closed then there is no anymore Java "client" for the
* webpack dev server and it should be stopped.
*
*
* For internal use only. May be renamed or removed in a future release.
*
* @author Vaadin Ltd
* @since 2.0
*/
internal class DevServerWatchDog {
private class WatchDogServer() : Runnable {
private val logger = LoggerFactory.getLogger(WatchDogServer::class.java)
var server: ServerSocket
init {
try {
server = ServerSocket(0)
server.soTimeout = 0
if (logger.isDebugEnabled) {
logger.debug("Watchdog server has started on port {}", server.localPort)
}
} catch (e: IOException) {
throw RuntimeException("Could not open a server socket", e)
}
}
private class WatchDogServer internal constructor() : Runnable {
internal var server: ServerSocket? = null
override fun run() {
while (!server.isClosed) {
while (!server!!.isClosed) {
try {
val accept = server.accept()
val accept = server!!.accept()
accept.soTimeout = 0
enterReloadMessageReadLoop(accept)
} catch (e: IOException) {
@ -78,46 +64,67 @@ internal class DevServerWatchDog {
}
fun stop() {
try {
server.close()
} catch (e: IOException) {
logger.debug(
"Error occurred during close the server socket", e
)
val server = server
if (server != null) {
try {
server.close()
} catch (e: IOException) {
logger.debug(
"Error occurred during close the server socket", e
)
}
}
}
private val logger: Logger
private get() = LoggerFactory.getLogger(WatchDogServer::class.java)
@Throws(IOException::class)
private fun enterReloadMessageReadLoop(accept: Socket) {
val lineIn = BufferedReader(InputStreamReader(accept.getInputStream(), StandardCharsets.UTF_8))
val `in` = BufferedReader(
InputStreamReader(
accept.getInputStream(), StandardCharsets.UTF_8
)
)
var line = lineIn.readLine()
while (line != null) {
val devModeHandler = DevModeHandler.devModeHandler
val liveReload = devModeHandler?.liveReload
if (liveReload != null && "reload" == line ) {
liveReload.reload()
var line: String
while (`in`.readLine().also { line = it } != null) {
val devModeHandler: DevModeHandler? = DevModeHandler.devModeHandler
if ("reload" == line) {
devModeHandler?.liveReload?.reload()
}
}
}
line = lineIn.readLine()
init {
try {
val server = ServerSocket(0)
this.server = server
server.soTimeout = 0
if (logger.isDebugEnabled) {
logger.debug(
"Watchdog server has started on port {}",
server.localPort
)
}
} catch (e: IOException) {
throw RuntimeException("Could not open a server socket", e)
}
}
}
private val watchDogServer: WatchDogServer = WatchDogServer()
private val watchDogServer: WatchDogServer
val watchDogPort: Int
get() = watchDogServer.server.localPort
get() = watchDogServer.server!!.localPort
fun stop() {
watchDogServer.stop()
}
init {
val serverThread = Thread(watchDogServer, "DevServer-Watchdog")
watchDogServer = WatchDogServer()
val serverThread = Thread(watchDogServer)
serverThread.isDaemon = true
serverThread.start()
}

View File

@ -1,12 +1,24 @@
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
/*
* Copyright 2000-2020 Vaadin Ltd.
*
* 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 dorkbox.vaadin.devMode
import com.vaadin.flow.server.VaadinRequest
import com.vaadin.flow.server.VaadinService
import com.vaadin.flow.server.VaadinSession
import com.vaadin.flow.shared.ApplicationConstants
import java.io.Serializable
import java.io.UnsupportedEncodingException
import java.net.URLDecoder
@ -16,31 +28,83 @@ import java.util.function.BiConsumer
import java.util.regex.Pattern
/**
* THIS IS COPIED DIRECTLY FROM VAADIN 14.6.8 (flow 2.4.6)
* THIS IS COPIED DIRECTLY FROM VAADIN 14.7.1 (flow 2.7.1)
*
* CHANGES FROM DEFAULT ARE MANAGED AS DIFFERENT REVISIONS.
*
* The initial commit is exactly as-is from vaadin.
*
* This file is NOT extensible/configurable AT-ALL, so this is required...
*
* Contains helper methods for [VaadinServlet] and generally for handling
* [VaadinRequests][VaadinRequest].
*
* @since 1.0
*/
object HandlerHelper : Serializable {
// static final SystemMessages DEFAULT_SYSTEM_MESSAGES = new SystemMessages();
/**
* The default SystemMessages (read-only).
*/
// static final SystemMessages DEFAULT_SYSTEM_MESSAGES = new SystemMessages(); // NOTE: not compatible with how we pull out this class
/**
* The pattern of error message shown when the URL path contains unsafe
* double encoding.
*/
const val UNSAFE_PATH_ERROR_MESSAGE_PATTERN = "Blocked attempt to access file: {}"
private val PARENT_DIRECTORY_REGEX = Pattern.compile("(/|\\\\)\\.\\.(/|\\\\)?", 2)
fun isRequestType(request: VaadinRequest, requestType: RequestType): Boolean {
return requestType.identifier == request.getParameter("v-r")
private val PARENT_DIRECTORY_REGEX = Pattern
.compile("(/|\\\\)\\.\\.(/|\\\\)?", Pattern.CASE_INSENSITIVE)
/**
* Returns whether the given request is of the given type.
*
* @param request
* the request to check
* @param requestType
* the type to check for
* @return `true` if the request is of the given type,
* `false` otherwise
*/
fun isRequestType(
request: VaadinRequest,
requestType: RequestType
): Boolean {
return requestType.identifier == request
.getParameter(ApplicationConstants.REQUEST_TYPE_PARAMETER)
}
fun findLocale(session: VaadinSession?, request: VaadinRequest?): Locale {
/**
* Helper to find the most most suitable Locale. These potential sources are
* checked in order until a Locale is found:
*
* 1. The passed component (or UI) if not null
* 1. [UI.getCurrent] if defined
* 1. The passed session if not null
* 1. [VaadinSession.getCurrent] if defined
* 1. The passed request if not null
* 1. [VaadinService.getCurrentRequest] if defined
* 1. [Locale.getDefault]
*
*
* @param session
* the session that is searched for locale or `null`
* if not available
* @param request
* the request that is searched for locale or `null`
* if not available
* @return the found locale
*/
fun findLocale(
session: VaadinSession?,
request: VaadinRequest?
): Locale {
var session = session
var request = request
if (session == null) {
session = VaadinSession.getCurrent()
}
var locale: Locale?
if (session != null) {
locale = session.locale
val locale = session.locale
if (locale != null) {
return locale
}
@ -49,7 +113,7 @@ object HandlerHelper : Serializable {
request = VaadinService.getCurrentRequest()
}
if (request != null) {
locale = request.locale
val locale = request.locale
if (locale != null) {
return locale
}
@ -57,14 +121,34 @@ object HandlerHelper : Serializable {
return Locale.getDefault()
}
fun setResponseNoCacheHeaders(headerSetter: BiConsumer<String?, String?>, longHeaderSetter: BiConsumer<String?, Long?>) {
/**
* Sets no cache headers to the specified response.
*
* @param headerSetter
* setter for string value headers
* @param longHeaderSetter
* setter for long value headers
*/
fun setResponseNoCacheHeaders(
headerSetter: BiConsumer<String?, String?>,
longHeaderSetter: BiConsumer<String?, Long?>
) {
headerSetter.accept("Cache-Control", "no-cache, no-store")
headerSetter.accept("Pragma", "no-cache")
longHeaderSetter.accept("Expires", 0L)
}
/**
* Gets a relative path that cancels the provided path. This essentially
* adds one .. for each part of the path to cancel.
*
* @param pathToCancel
* the path that should be canceled
* @return a relative path that cancels out the provided path segment
*/
fun getCancelingRelativePath(pathToCancel: String): String {
val sb = StringBuilder(".")
// Start from i = 1 to ignore first slash
for (i in 1 until pathToCancel.length) {
if (pathToCancel[i] == '/') {
sb.append("/..")
@ -73,19 +157,56 @@ object HandlerHelper : Serializable {
return sb.toString()
}
@JvmStatic
/**
* Checks if the given URL path contains the directory change instruction
* (dot-dot), taking into account possible double encoding in hexadecimal
* format, which can be injected maliciously.
*
* @param path
* the URL path to be verified.
* @return `true`, if the given path has a directory change
* instruction, `false` otherwise.
*/
fun isPathUnsafe(path: String?): Boolean {
// Check that the path does not have '/../', '\..\', %5C..%5C,
// %2F..%2F, nor '/..', '\..', %5C.., %2F..
var path = path
path = try {
URLDecoder.decode(path, StandardCharsets.UTF_8.name())
} catch (var2: UnsupportedEncodingException) {
throw RuntimeException("An error occurred during decoding URL.", var2)
} catch (e: UnsupportedEncodingException) {
throw RuntimeException(
"An error occurred during decoding URL.",
e
)
}
return PARENT_DIRECTORY_REGEX.matcher(path).find()
}
enum class RequestType(val identifier: String) {
UIDL("uidl"), HEARTBEAT("heartbeat"), PUSH("push");
/**
* Framework internal enum for tracking the type of a request.
*/
enum class RequestType(
/**
* Returns the identifier for the request type.
*
* @return the identifier
*/
val identifier: String
) {
/**
* UIDL requests.
*/
UIDL(ApplicationConstants.REQUEST_TYPE_UIDL),
/**
* Heartbeat requests.
*/
HEARTBEAT(ApplicationConstants.REQUEST_TYPE_HEARTBEAT),
/**
* Push requests (any transport).
*/
PUSH(ApplicationConstants.REQUEST_TYPE_PUSH);
}
}