2019-09-19 22:04:23 +02:00
package moe.kageru.kodeshare
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.http.ContentType
2019-09-22 18:26:28 +02:00
import io.ktor.http.HttpHeaders
2019-09-19 22:04:23 +02:00
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.http.content.streamProvider
2019-09-22 18:26:28 +02:00
import io.ktor.locations.KtorExperimentalLocationsAPI
2019-09-19 22:04:23 +02:00
import io.ktor.locations.Location
import io.ktor.locations.get
2019-09-29 16:20:50 +02:00
import io.ktor.locations.head
2019-09-19 22:04:23 +02:00
import io.ktor.request.receiveMultipart
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Routing
import io.ktor.routing.get
2019-09-29 16:20:50 +02:00
import io.ktor.routing.head
2019-09-19 22:04:23 +02:00
import io.ktor.routing.post
2019-09-29 11:50:14 +02:00
import moe.kageru.kodeshare.config.ServerSpec
import moe.kageru.kodeshare.config.config
import moe.kageru.kodeshare.pages.AboutPage
2019-09-22 18:26:28 +02:00
import moe.kageru.kodeshare.pages.Css
import moe.kageru.kodeshare.pages.Homepage
2019-09-29 08:17:31 +02:00
import moe.kageru.kodeshare.pages.PastePage
2019-09-19 22:04:23 +02:00
import moe.kageru.kodeshare.persistence.PasteDao
2019-09-29 08:17:31 +02:00
import org.joda.time.DateTime
2019-09-19 22:04:23 +02:00
2019-09-22 18:26:28 +02:00
@KtorExperimentalLocationsAPI
2019-09-19 22:04:23 +02:00
@ExperimentalStdlibApi
object Routes {
fun Routing . createRoutes ( ) {
get ( " / " ) {
2019-09-29 08:17:31 +02:00
call . respond ( HttpStatusCode . OK , Homepage . content )
2019-09-22 18:26:28 +02:00
}
get ( " /style.css " ) {
call . respondText ( Css . default , ContentType . Text . CSS )
2019-09-19 22:04:23 +02:00
}
post ( " / " ) {
2019-09-22 18:26:28 +02:00
call . handlePost ( )
2019-09-19 22:04:23 +02:00
}
2019-09-29 10:15:42 +02:00
get ( " /favicon.ico " ) {
call . respond ( HttpStatusCode . NotFound )
}
2019-09-29 11:50:14 +02:00
get ( " /about " ) {
call . respond ( HttpStatusCode . OK , AboutPage . content )
}
2019-09-29 17:03:44 +02:00
get < HtmlPasteRequest > { req ->
2019-09-29 17:10:02 +02:00
call . handleGet ( req , raw = false )
2019-09-19 22:04:23 +02:00
}
2019-09-29 08:17:31 +02:00
get < RawPasteRequest > { req ->
2019-09-29 17:10:02 +02:00
call . handleGet ( req , raw = true )
2019-09-29 08:17:31 +02:00
}
2019-09-29 16:20:50 +02:00
head ( " / " ) {
call . respond ( HttpStatusCode . OK )
}
head < RawPasteRequest > { req ->
2019-09-29 17:03:44 +02:00
call . handleHead ( req , true )
2019-09-29 16:20:50 +02:00
}
2019-09-29 17:03:44 +02:00
head < HtmlPasteRequest > { req ->
2019-09-29 16:20:50 +02:00
call . handleHead ( req )
}
}
2019-09-29 17:03:44 +02:00
private fun splitPath ( uri : String ) : Pair < String , String ? > {
return if ( uri . contains ( '.' ) ) {
val ( name , ext ) = uri . split ( " . " , limit = 2 )
Pair ( name , ext )
} else {
Pair ( uri , null )
}
}
2019-09-29 17:10:02 +02:00
private suspend fun ApplicationCall . handleHead ( paste : PasteRequest , raw : Boolean = false ) {
val uri = splitPath ( paste . uri ) . first
if ( PasteDao . selectByUri ( uri ) != null ) {
respond ( HttpStatusCode . OK , if ( raw ) ContentType . Text . Plain else ContentType . Text . Html )
} else {
respond ( HttpStatusCode . NotFound )
}
}
2019-09-22 18:26:28 +02:00
@ExperimentalStdlibApi
private suspend fun ApplicationCall . handlePost ( ) {
receiveMultipart ( ) . forEachPart { part ->
when ( part ) {
is PartData . FileItem -> {
val content = part . streamProvider ( ) . use { it . readBytes ( ) . decodeToString ( ) }
2019-09-29 17:10:02 +02:00
processUpload ( content ) ?. let { uri ->
2019-09-29 09:51:15 +02:00
Log . info ( " Saving new file paste with uri $uri " )
2019-09-29 08:17:31 +02:00
}
2019-09-22 18:26:28 +02:00
}
is PartData . FormItem -> {
val content = part . value
2019-09-29 17:10:02 +02:00
processUpload ( content ) ?. let { uri ->
2019-09-29 09:51:15 +02:00
Log . info ( " Saving new text paste with uri $uri " )
2019-09-29 08:17:31 +02:00
}
2019-09-22 18:26:28 +02:00
}
is PartData . BinaryItem -> {
Log . warn ( " Received binary item from upload form. This shouldn’t happen. " )
}
2019-09-19 22:04:23 +02:00
}
}
}
2019-09-29 17:10:02 +02:00
private suspend fun ApplicationCall . processUpload ( content : String ) : String ? {
2019-09-22 18:26:28 +02:00
content . ifBlank {
2019-09-29 08:17:31 +02:00
Log . info ( " Rejecting blank paste " )
respond ( HttpStatusCode . BadRequest , " Empty pastes are not allowed " )
return null
}
2019-09-29 13:26:05 +02:00
if ( content . length > 1024 * 1024 ) {
Log . info ( " Rejecting paste over 1M " )
respond ( HttpStatusCode . BadRequest , " Pastes are limited to 1 MiB " )
2019-09-22 18:26:28 +02:00
return null
}
2019-09-29 09:51:15 +02:00
val uri = PasteDao . insert ( Paste ( content , DateTime . now ( ) , Paste . randomUri ( ) ) ) . data . uri
2019-09-22 18:26:28 +02:00
// This will show the URL in a terminal when uploading via curl,
// while also redirecting browser uploads to the newly created paste.
// May seem odd to return code 302, but it seems to be the only way.
2019-09-29 09:51:15 +02:00
response . headers . append ( HttpHeaders . Location , uri )
2019-10-25 22:48:19 +02:00
respond ( HttpStatusCode . Found , " ${config[ServerSpec.pasteurl].ifBlank { config[ServerSpec.domain] } } $uri " )
2019-09-29 09:51:15 +02:00
return uri
2019-09-22 18:26:28 +02:00
}
2019-09-29 17:10:02 +02:00
private suspend fun ApplicationCall . handleGet ( req : PasteRequest , raw : Boolean ) {
2019-09-29 17:03:44 +02:00
val ( uri , ext ) = splitPath ( req . uri )
2019-09-29 09:51:15 +02:00
Log . info ( " Retrieving paste $uri " )
PasteDao . selectByUri ( uri ) ?. data ?. let { paste ->
2019-09-29 17:10:02 +02:00
if ( raw ) {
respondText ( paste . content , ContentType . Text . Plain )
} else {
2020-03-12 10:57:28 +01:00
respond ( HttpStatusCode . OK , PastePage . build ( paste . content , paste . uri , ext ) )
2019-09-29 17:10:02 +02:00
}
2019-09-29 09:51:15 +02:00
} ?: respond ( HttpStatusCode . NotFound , " nothing found for id $uri " )
2019-09-22 18:26:28 +02:00
}
2019-09-19 22:04:23 +02:00
}
2019-09-29 09:51:15 +02:00
@KtorExperimentalLocationsAPI
@Location ( " /r/{uri} " )
2019-09-29 17:03:44 +02:00
data class RawPasteRequest ( override val uri : String ) : PasteRequest
2019-09-29 08:17:31 +02:00
2019-09-22 18:26:28 +02:00
@KtorExperimentalLocationsAPI
2019-09-29 09:51:15 +02:00
@Location ( " /{uri} " )
2019-09-29 17:03:44 +02:00
data class HtmlPasteRequest ( override val uri : String ) : PasteRequest
2019-09-29 09:51:15 +02:00
2019-09-29 17:03:44 +02:00
interface PasteRequest {
val uri : String
2019-09-29 09:51:15 +02:00
}