src/main/kotlin/de/uapcore/lightpit/AbstractServlet.kt

changeset 298
1275eb652008
parent 254
55ca6cafc3dd
equal deleted inserted replaced
296:355c86eaeca5 298:1275eb652008
26 package de.uapcore.lightpit 26 package de.uapcore.lightpit
27 27
28 import de.uapcore.lightpit.DataSourceProvider.Companion.SC_ATTR_NAME 28 import de.uapcore.lightpit.DataSourceProvider.Companion.SC_ATTR_NAME
29 import de.uapcore.lightpit.dao.DataAccessObject 29 import de.uapcore.lightpit.dao.DataAccessObject
30 import de.uapcore.lightpit.dao.createDataAccessObject 30 import de.uapcore.lightpit.dao.createDataAccessObject
31 import jakarta.servlet.http.Cookie
31 import jakarta.servlet.http.HttpServlet 32 import jakarta.servlet.http.HttpServlet
32 import jakarta.servlet.http.HttpServletRequest 33 import jakarta.servlet.http.HttpServletRequest
33 import jakarta.servlet.http.HttpServletResponse 34 import jakarta.servlet.http.HttpServletResponse
34 import java.sql.SQLException 35 import java.sql.SQLException
35 import java.util.* 36 import java.util.*
36 37
37 abstract class AbstractServlet : HttpServlet() { 38 abstract class AbstractServlet : HttpServlet() {
39
40 companion object {
41 const val LANGUAGE_COOKIE_NAME = "lpit_language"
42 }
38 43
39 protected val logger = MyLogger() 44 protected val logger = MyLogger()
40 45
41 /** 46 /**
42 * Contains the GET request mappings. 47 * Contains the GET request mappings.
98 val session = req.session 103 val session = req.session
99 104
100 // the very first thing to do is to force UTF-8 105 // the very first thing to do is to force UTF-8
101 req.characterEncoding = "UTF-8" 106 req.characterEncoding = "UTF-8"
102 107
103 // choose the requested language as session language (if available) or fall back to english, otherwise
104 if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) {
105 val availableLanguages = availableLanguages()
106 val reqLocale = req.locale
107 val sessionLocale = if (availableLanguages.contains(reqLocale)) reqLocale else availableLanguages.first()
108 session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, sessionLocale)
109 resp.locale = sessionLocale
110 logger.debug(
111 "Setting language for new session {0}: {1}", session.id, sessionLocale.displayLanguage
112 )
113 } else {
114 val sessionLocale = session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) as Locale
115 resp.locale = sessionLocale
116 logger.trace("Continuing session {0} with language {1}", session.id, sessionLocale)
117 }
118
119 // set some internal request attributes 108 // set some internal request attributes
120 val http = HttpRequest(req, resp) 109 val http = HttpRequest(req, resp)
121 val fullPath = req.servletPath + Optional.ofNullable(req.pathInfo).orElse("") 110 val fullPath = req.servletPath + Optional.ofNullable(req.pathInfo).orElse("")
122 req.setAttribute(Constants.REQ_ATTR_BASE_HREF, http.baseHref) 111 req.setAttribute(Constants.REQ_ATTR_BASE_HREF, http.baseHref)
123 req.setAttribute(Constants.REQ_ATTR_PATH, fullPath) 112 req.setAttribute(Constants.REQ_ATTR_PATH, fullPath)
124 req.getHeader("Referer")?.let { 113 req.getHeader("Referer")?.let {
125 // TODO: add a sanity check to avoid link injection 114 // TODO: add a sanity check to avoid link injection
126 req.setAttribute(Constants.REQ_ATTR_REFERER, it) 115 req.setAttribute(Constants.REQ_ATTR_REFERER, it)
116 }
117
118 // choose the requested language as session language (if available)
119 if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) {
120 // language selection stored in cookie
121 val cookieLocale = cookieLanguage(http)
122
123 // if no cookie, fall back to request locale a.k.a "Browser Language"
124 val reqLocale = cookieLocale ?: req.locale
125
126 val availableLanguages = availableLanguages()
127 val sessionLocale = if (availableLanguages.contains(reqLocale)) reqLocale else availableLanguages.first()
128
129 // select the language (this will also refresh the cookie max-age)
130 selectLanguage(http, sessionLocale)
131
132 logger.debug(
133 "Setting language for new session {0}: {1}", session.id, sessionLocale.displayLanguage
134 )
135 } else {
136 val sessionLocale = session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) as Locale
137 resp.locale = sessionLocale
138 logger.trace("Continuing session {0} with language {1}", session.id, sessionLocale)
127 } 139 }
128 140
129 // if this is an error path, bypass the normal flow 141 // if this is an error path, bypass the normal flow
130 if (fullPath.startsWith("/error/")) { 142 if (fullPath.startsWith("/error/")) {
131 http.styleSheets = listOf("error") 143 http.styleSheets = listOf("error")
180 val langTags = servletContext.getInitParameter(Constants.CTX_ATTR_LANGUAGES)?.split(",")?.map(String::trim) ?: emptyList() 192 val langTags = servletContext.getInitParameter(Constants.CTX_ATTR_LANGUAGES)?.split(",")?.map(String::trim) ?: emptyList()
181 val locales = langTags.map(Locale::forLanguageTag).filter { it.language.isNotEmpty() } 193 val locales = langTags.map(Locale::forLanguageTag).filter { it.language.isNotEmpty() }
182 return locales.ifEmpty { listOf(Locale.ENGLISH) } 194 return locales.ifEmpty { listOf(Locale.ENGLISH) }
183 } 195 }
184 196
197 private fun cookieLanguage(http: HttpRequest): Locale? =
198 http.request.cookies?.firstOrNull { c -> c.name == LANGUAGE_COOKIE_NAME }
199 ?.runCatching {Locale.forLanguageTag(this.value)}?.getOrNull()
200
201 protected fun selectLanguage(http: HttpRequest, locale: Locale) {
202 http.response.locale = locale
203 http.session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, locale)
204 // delete cookie if language selection matches request locale, otherwise set cookie
205 val cookie = Cookie(LANGUAGE_COOKIE_NAME, "")
206 cookie.isHttpOnly = true
207 cookie.path = http.request.contextPath
208 if (http.request.locale.language == locale.language) {
209 cookie.maxAge = 0
210 } else {
211 cookie.value = locale.language
212 cookie.maxAge = 2592000 // 30 days
213 }
214 http.response.addCookie(cookie)
215 }
185 } 216 }

mercurial