All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.javalin.http.util.ContextUtil.kt Maven / Gradle / Ivy

The newest version!
/*
 * Javalin - https://javalin.io
 * Copyright 2017 David Åse
 * Licensed under Apache 2.0: https://github.com/tipsy/javalin/blob/master/LICENSE
 */

package io.javalin.http.util

import io.javalin.core.security.BasicAuthCredentials
import io.javalin.core.util.Header
import io.javalin.core.util.JavalinLogger
import io.javalin.http.ContentType
import io.javalin.http.Context
import io.javalin.http.HandlerEntry
import io.javalin.http.HandlerType
import io.javalin.http.HttpCode
import io.javalin.http.HttpResponseException
import java.io.InputStream
import java.net.URL
import java.net.URLDecoder
import java.nio.charset.Charset
import java.util.*
import javax.servlet.http.HttpServletRequest
import javax.servlet.http.HttpServletResponse

object ContextUtil {

    fun update(ctx: Context, handlerEntry: HandlerEntry, requestUri: String) = ctx.apply {
        matchedPath = handlerEntry.path
        pathParamMap = handlerEntry.extractPathParams(requestUri)
        handlerType = handlerEntry.type
        if (handlerType != HandlerType.AFTER) {
            endpointHandlerPath = handlerEntry.path
        }
    }

    // this header is semi-colon separated, like: "text/html; charset=UTF-8"
    fun getRequestCharset(ctx: Context) = ctx.req.getHeader(Header.CONTENT_TYPE)?.let { value ->
        value.split(";").find { it.trim().startsWith("charset", ignoreCase = true) }?.let { it.split("=")[1].trim() }
    }

    fun splitKeyValueStringAndGroupByKey(string: String, charset: String): Map> {
        return if (string.isEmpty()) mapOf() else string.split("&").map { it.split("=", limit = 2) }.groupBy(
            { URLDecoder.decode(it[0], charset) },
            { if (it.size > 1) URLDecoder.decode(it[1], charset) else "" }
        ).mapValues { it.value.toList() }
    }

    fun pathParamOrThrow(pathParams: Map, key: String, url: String) =
        pathParams[key.replaceFirst("{", "").replaceFirst("}", "")] ?: throw IllegalArgumentException("'$key' is not a valid path-param for '$url'.")

    fun urlDecode(s: String): String = URLDecoder.decode(s.replace("+", "%2B"), "UTF-8").replace("%2B", "+")

    fun hasBasicAuthCredentials(header: String?) = try {
        getBasicAuthCredentials(header)
        true
    } catch (e: Exception) {
        false
    }

    fun getBasicAuthCredentials(header: String?): BasicAuthCredentials {
        require(header?.startsWith("Basic ") == true) { "Invalid basicauth header. Value was '$header'." }
        val (username, password) = String(Base64.getDecoder().decode(header!!.removePrefix("Basic "))).split(':', limit = 2)
        return BasicAuthCredentials(username, password)
    }

    fun acceptsHtml(ctx: Context) =
        ctx.header(Header.ACCEPT)?.contains(ContentType.HTML) == true

    @JvmStatic
    @JvmOverloads
    fun init(
        request: HttpServletRequest,
        response: HttpServletResponse,
        matchedPath: String = "*",
        pathParamMap: Map = mapOf(),
        handlerType: HandlerType = HandlerType.INVALID,
        appAttributes: Map = mapOf()
    ) = Context(request, response, appAttributes).apply {
        this.matchedPath = matchedPath
        this.pathParamMap = pathParamMap
        this.handlerType = handlerType
    }

    fun Context.isLocalhost() = try {
        URL(this.url()).host.let { it == "localhost" || it == "127.0.0.1" }
    } catch (e: Exception) {
        false
    }

    fun changeBaseRequest(ctx: Context, req: HttpServletRequest) = Context(req, ctx.res, ctx.appAttributes).apply {
        this.pathParamMap = ctx.pathParamMap
        this.matchedPath = ctx.matchedPath
    }

    fun Context.throwPayloadTooLargeIfPayloadTooLarge() {
        val maxRequestSize = this.appAttribute(maxRequestSizeKey)
        if (this.req.contentLength > maxRequestSize) {
            JavalinLogger.warn("Body greater than max size ($maxRequestSize bytes)")
            throw HttpResponseException(HttpCode.PAYLOAD_TOO_LARGE.status, HttpCode.PAYLOAD_TOO_LARGE.message)
        }
    }

    const val maxRequestSizeKey = "javalin-max-request-size"

    const val sessionCacheKeyPrefix = "javalin-session-attribute-cache-"

    fun cacheAndSetSessionAttribute(key: String, value: Any?, req: HttpServletRequest) {
        req.setAttribute("$sessionCacheKeyPrefix$key", value)
        req.session.setAttribute(key, value)
    }

    fun  getCachedRequestAttributeOrSessionAttribute(key: String, req: HttpServletRequest): T? {
        val cachedAttribute = req.getAttribute("$sessionCacheKeyPrefix$key")
        if (cachedAttribute == null) {
            req.setAttribute("$sessionCacheKeyPrefix$key", req.session.getAttribute(key))
        }
        return req.getAttribute("$sessionCacheKeyPrefix$key") as T?
    }

    fun  cachedSessionAttributeOrCompute(callback: (Context) -> T, key: String, ctx: Context): T? {
        if (getCachedRequestAttributeOrSessionAttribute(key, ctx.req) == null) {
            cacheAndSetSessionAttribute(key, callback(ctx), ctx.req) // set new value from callback
        }
        return getCachedRequestAttributeOrSessionAttribute(key, ctx.req) // existing or computed (or null)
    }

    fun readAndResetStreamIfPossible(stream: InputStream?, charset: Charset) = try {
        stream?.apply { reset() }?.readBytes()?.toString(charset).also { stream?.reset() }
    } catch (e: Exception) {
        "resultString unavailable (resultStream couldn't be reset)"
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy