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 } |