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

changeset 367
0a9065936aac
parent 358
e46bef1bdddd
equal deleted inserted replaced
366:b351e70ab325 367:0a9065936aac
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 de.uapcore.lightpit.entities.User
31 import jakarta.servlet.http.Cookie 32 import jakarta.servlet.http.Cookie
32 import jakarta.servlet.http.HttpServlet 33 import jakarta.servlet.http.HttpServlet
33 import jakarta.servlet.http.HttpServletRequest 34 import jakarta.servlet.http.HttpServletRequest
34 import jakarta.servlet.http.HttpServletResponse 35 import jakarta.servlet.http.HttpServletResponse
35 import java.sql.SQLException 36 import java.sql.SQLException
36 import java.time.ZoneId 37 import java.time.ZoneId
37 import java.util.* 38 import java.util.*
39 import java.sql.Date as SqlDate
38 40
39 abstract class AbstractServlet : HttpServlet() { 41 abstract class AbstractServlet : HttpServlet() {
40 42
41 companion object { 43 companion object {
42 const val COOKIE_MAX_AGE = 2592000 // 30 days 44 const val COOKIE_MAX_AGE = 2592000 // 30 days
87 resp: HttpServletResponse, 89 resp: HttpServletResponse,
88 dao: DataAccessObject 90 dao: DataAccessObject
89 ) { 91 ) {
90 val params = mapping.first.obtainPathParameters(sanitizedRequestPath(req)) 92 val params = mapping.first.obtainPathParameters(sanitizedRequestPath(req))
91 val method = mapping.second 93 val method = mapping.second
94 val authenticatedUser = req.remoteUser?.let(dao::findUserByName)
95 showWhatsNewPopup(authenticatedUser, req, dao)
92 logger.trace("invoke {0}", method) 96 logger.trace("invoke {0}", method)
93 method(HttpRequest(req, resp, params), dao) 97 method(HttpRequest(authenticatedUser, req, resp, params), dao)
94 } 98 }
95 99
96 private fun sanitizedRequestPath(req: HttpServletRequest) = req.pathInfo ?: "/" 100 private fun sanitizedRequestPath(req: HttpServletRequest) = req.pathInfo ?: "/"
97 101
98 protected fun sanitizeJson(str: String): String { 102 protected fun sanitizeJson(str: String): String {
122 126
123 // the very first thing to do is to force UTF-8 127 // the very first thing to do is to force UTF-8
124 req.characterEncoding = "UTF-8" 128 req.characterEncoding = "UTF-8"
125 129
126 // set some internal request attributes 130 // set some internal request attributes
127 val http = HttpRequest(req, resp)
128 val fullPath = req.servletPath + Optional.ofNullable(req.pathInfo).orElse("") 131 val fullPath = req.servletPath + Optional.ofNullable(req.pathInfo).orElse("")
129 req.setAttribute(Constants.REQ_ATTR_BASE_HREF, http.baseHref)
130 req.setAttribute(Constants.REQ_ATTR_PATH, fullPath) 132 req.setAttribute(Constants.REQ_ATTR_PATH, fullPath)
131 req.getHeader("Referer")?.let { 133 req.getHeader("Referer")?.let {
132 // TODO: add a sanity check to avoid link injection 134 // TODO: add a sanity check to avoid link injection
133 req.setAttribute(Constants.REQ_ATTR_REFERER, it) 135 req.setAttribute(Constants.REQ_ATTR_REFERER, it)
134 } 136 }
135 137
136 // choose the requested language as session language (if available) 138 // choose the requested language as session language (if available)
137 if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) { 139 if (session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) == null) {
138 // language selection stored in cookie 140 // language selection stored in cookie
139 val cookieLocale = cookieLanguage(http) 141 val cookieLocale = cookieLanguage(req)
140 142
141 // if no cookie, fall back to request locale a.k.a "Browser Language" 143 // if no cookie, fall back to request locale a.k.a "Browser Language"
142 val reqLocale = cookieLocale ?: req.locale 144 val reqLocale = cookieLocale ?: req.locale
143 145
144 val availableLanguages = availableLanguages() 146 val availableLanguages = availableLanguages()
145 val sessionLocale = if (availableLanguages.contains(reqLocale)) reqLocale else availableLanguages.first() 147 val sessionLocale = if (availableLanguages.contains(reqLocale)) reqLocale else availableLanguages.first()
146 148
147 // select the language (this will also refresh the cookie max-age) 149 // select the language (this will also refresh the cookie max-age)
148 selectLanguage(http, sessionLocale) 150 selectLanguage(req, resp, sessionLocale)
149 151
150 logger.debug( 152 logger.debug(
151 "Setting language for new session {0}: {1}", session.id, sessionLocale.displayLanguage 153 "Setting language for new session {0}: {1}", session.id, sessionLocale.displayLanguage
152 ) 154 )
153 } else { 155 } else {
157 } 159 }
158 160
159 // determine the timezone 161 // determine the timezone
160 if (session.getAttribute(Constants.SESSION_ATTR_TIMEZONE) == null) { 162 if (session.getAttribute(Constants.SESSION_ATTR_TIMEZONE) == null) {
161 // timezone selection stored in cookie 163 // timezone selection stored in cookie
162 val cookieTimezone = cookieTimezone(http) 164 val cookieTimezone = cookieTimezone(req)
163 165
164 // if no cookie, fall back to server's timezone (the browser does not transmit one) 166 // if no cookie, fall back to server's timezone (the browser does not transmit one)
165 val timezone = cookieTimezone ?: ZoneId.systemDefault() 167 val timezone = cookieTimezone ?: ZoneId.systemDefault()
166 168
167 selectTimezone(http, timezone) 169 selectTimezone(req, resp, timezone)
168 logger.debug("Timezone for session {0} set to {1}", session.id, timezone) 170 logger.debug("Timezone for session {0} set to {1}", session.id, timezone)
169 } 171 }
170 172
171 // if this is an error path, bypass the normal flow 173 // if this is an error path, bypass the normal flow
172 if (fullPath.startsWith("/error/")) { 174 if (fullPath.startsWith("/error/")) {
175 val http = HttpRequest(null, req, resp)
173 http.styleSheets = listOf("error") 176 http.styleSheets = listOf("error")
174 http.render("error") 177 http.render("error")
175 return 178 return
176 } 179 }
177 180
208 logger.debug("Details: ", ex) 211 logger.debug("Details: ", ex)
209 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Database Error - Code: " + ex.errorCode) 212 resp.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "Database Error - Code: " + ex.errorCode)
210 } 213 }
211 } 214 }
212 215
216 private fun showWhatsNewPopup(user: User?, req: HttpServletRequest, dao: DataAccessObject) {
217 if (user == null) return
218 logger.trace("show user with ID {0} what's new", user.id)
219 val userKnowsUpdatesUntil = dao.untilWhenUserKnowsUpdates(user)
220 if (userKnowsUpdatesUntil == null || userKnowsUpdatesUntil.before(SqlDate.valueOf(Constants.VERSION_DATE))) {
221 dao.updateUserKnowsUpdates(user)
222 req.setAttribute("showWhatsNew", true)
223 }
224 }
225
213 override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) { 226 override fun doGet(req: HttpServletRequest, resp: HttpServletResponse) {
214 doProcess(req, resp, getMappings) 227 doProcess(req, resp, getMappings)
215 } 228 }
216 229
217 override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) { 230 override fun doPost(req: HttpServletRequest, resp: HttpServletResponse) {
222 val langTags = servletContext.getInitParameter(Constants.CTX_ATTR_LANGUAGES)?.split(",")?.map(String::trim) ?: emptyList() 235 val langTags = servletContext.getInitParameter(Constants.CTX_ATTR_LANGUAGES)?.split(",")?.map(String::trim) ?: emptyList()
223 val locales = langTags.map(Locale::forLanguageTag).filter { it.language.isNotEmpty() } 236 val locales = langTags.map(Locale::forLanguageTag).filter { it.language.isNotEmpty() }
224 return locales.ifEmpty { listOf(Locale.ENGLISH) } 237 return locales.ifEmpty { listOf(Locale.ENGLISH) }
225 } 238 }
226 239
227 private fun cookieLanguage(http: HttpRequest): Locale? = 240 private fun cookieLanguage(request: HttpServletRequest): Locale? =
228 http.request.cookies?.firstOrNull { c -> c.name == LANGUAGE_COOKIE_NAME } 241 request.cookies?.firstOrNull { c -> c.name == LANGUAGE_COOKIE_NAME }
229 ?.runCatching {Locale.forLanguageTag(this.value)}?.getOrNull() 242 ?.runCatching {Locale.forLanguageTag(this.value)}?.getOrNull()
230 243
231 protected fun sessionLanguage(http: HttpRequest) = http.session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) as Locale 244 protected fun sessionLanguage(http: HttpRequest) = http.session.getAttribute(Constants.SESSION_ATTR_LANGUAGE) as Locale
232 245
233 private fun cookieTimezone(http: HttpRequest): ZoneId? = 246 private fun cookieTimezone(request: HttpServletRequest): ZoneId? =
234 http.request.cookies?.firstOrNull { c -> c.name == TIMEZONE_COOKIE_NAME } 247 request.cookies?.firstOrNull { c -> c.name == TIMEZONE_COOKIE_NAME }
235 ?.runCatching { ZoneId.of(this.value)}?.getOrNull() 248 ?.runCatching { ZoneId.of(this.value)}?.getOrNull()
236 249
237 protected fun sessionTimezone(http: HttpRequest) = http.session.getAttribute(Constants.SESSION_ATTR_TIMEZONE) as String 250 protected fun sessionTimezone(http: HttpRequest) = http.session.getAttribute(Constants.SESSION_ATTR_TIMEZONE) as String
238 251
239 protected fun selectTimezone(http: HttpRequest, zoneId: ZoneId) { 252 protected fun selectTimezone(http: HttpRequest, zoneId: ZoneId) {
240 http.session.setAttribute(Constants.SESSION_ATTR_TIMEZONE, zoneId.id) 253 selectTimezone(http.request, http.response, zoneId)
254 }
255
256 private fun selectTimezone(request: HttpServletRequest, response: HttpServletResponse, zoneId: ZoneId) {
257 request.session.setAttribute(Constants.SESSION_ATTR_TIMEZONE, zoneId.id)
241 val cookie = Cookie(TIMEZONE_COOKIE_NAME, zoneId.id) 258 val cookie = Cookie(TIMEZONE_COOKIE_NAME, zoneId.id)
242 cookie.isHttpOnly = true 259 cookie.isHttpOnly = true
243 cookie.path = http.request.contextPath 260 cookie.path = request.contextPath
244 cookie.maxAge = COOKIE_MAX_AGE 261 cookie.maxAge = COOKIE_MAX_AGE
245 http.response.addCookie(cookie) 262 response.addCookie(cookie)
246 } 263 }
247 264
248 protected fun selectLanguage(http: HttpRequest, locale: Locale) { 265 protected fun selectLanguage(http: HttpRequest, locale: Locale) {
249 http.response.locale = locale 266 selectLanguage(http.request, http.response, locale)
250 http.session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, locale) 267 }
268
269 private fun selectLanguage(request: HttpServletRequest, response: HttpServletResponse, locale: Locale) {
270 response.locale = locale
271 request.session.setAttribute(Constants.SESSION_ATTR_LANGUAGE, locale)
251 // delete cookie if language selection matches request locale, otherwise set cookie 272 // delete cookie if language selection matches request locale, otherwise set cookie
252 val cookie = Cookie(LANGUAGE_COOKIE_NAME, "") 273 val cookie = Cookie(LANGUAGE_COOKIE_NAME, "")
253 cookie.isHttpOnly = true 274 cookie.isHttpOnly = true
254 cookie.path = http.request.contextPath 275 cookie.path = request.contextPath
255 if (http.request.locale.language == locale.language) { 276 if (request.locale.language == locale.language) {
256 cookie.maxAge = 0 277 cookie.maxAge = 0
257 } else { 278 } else {
258 cookie.value = locale.language 279 cookie.value = locale.language
259 cookie.maxAge = COOKIE_MAX_AGE 280 cookie.maxAge = COOKIE_MAX_AGE
260 } 281 }
261 http.response.addCookie(cookie) 282 response.addCookie(cookie)
262 } 283 }
263 } 284 }

mercurial