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
2019-09-29 08:17:31 +02:00
import io.ktor.http.content.MultiPartData.Empty.readPart
2019-09-19 22:04:23 +02:00
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
2019-09-29 08:17:31 +02:00
import io.ktor.http.content.readAllParts
2019-09-19 22:04:23 +02:00
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 08:17:31 +02:00
import io.ktor.request.receive
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
import io.ktor.routing.post
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
}
get < PasteRequest > { req ->
2019-09-22 18:26:28 +02:00
call . handleGet ( req )
2019-09-19 22:04:23 +02:00
}
2019-09-29 08:17:31 +02:00
get < RawPasteRequest > { req ->
call . handleRaw ( req )
}
2019-09-19 22:04:23 +02:00
}
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 ( ) }
respondToUpload ( content ) ?. let { id ->
Log . info ( " Saving new file paste with ID $id " )
2019-09-29 08:17:31 +02:00
}
2019-09-22 18:26:28 +02:00
}
is PartData . FormItem -> {
val content = part . value
respondToUpload ( content ) ?. let { id ->
Log . info ( " Saving new text paste with ID $id " )
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-22 18:26:28 +02:00
private suspend fun ApplicationCall . respondToUpload ( content : String ) : Long ? {
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
}
if ( content . length > 1 * 1024 * 1024 ) {
Log . info ( " Rejecting paste over 1MB " )
respond ( HttpStatusCode . BadRequest , " Pastes are limited to 1MB each " )
2019-09-22 18:26:28 +02:00
return null
}
2019-09-29 08:17:31 +02:00
val id = PasteDao . insert ( Paste ( content , DateTime . now ( ) ) ) . id
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.
response . headers . append ( HttpHeaders . Location , " $id " )
respond ( HttpStatusCode . Found , " $id " )
return id
}
private suspend fun ApplicationCall . handleGet ( req : PasteRequest ) {
val id = req . id
Log . info ( " Retrieving paste $id " )
PasteDao . select ( id ) ?. data ?. let { paste ->
2019-09-29 08:17:31 +02:00
respond (
HttpStatusCode . OK ,
PastePage . build ( paste . content )
)
} ?: respond ( HttpStatusCode . NotFound , " nothing found for id $id " )
}
private suspend fun ApplicationCall . handleRaw ( req : RawPasteRequest ) {
val id = req . id
Log . info ( " Retrieving raw paste $id " )
PasteDao . select ( id ) ?. data ?. let { paste ->
respondText ( paste . content , ContentType . Text . Plain )
2019-09-22 18:26:28 +02:00
} ?: respond ( HttpStatusCode . NotFound , " nothing found for id $id " )
}
2019-09-19 22:04:23 +02:00
}
2019-09-29 08:17:31 +02:00
@KtorExperimentalLocationsAPI
@Location ( " /r/{id} " )
data class RawPasteRequest ( val id : Long )
2019-09-22 18:26:28 +02:00
@KtorExperimentalLocationsAPI
2019-09-19 22:04:23 +02:00
@Location ( " /{id} " )
2019-09-22 18:26:28 +02:00
data class PasteRequest ( val id : Long )