Skip to content

Fix bug where FileItem returns empty bytearray #130

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Original file line number Diff line number Diff line change
Expand Up @@ -10,20 +10,21 @@ import io.ktor.http.HeadersBuilder
import io.ktor.http.HttpStatusCode
import io.ktor.http.content.PartData
import io.ktor.http.content.forEachPart
import io.ktor.http.content.streamProvider
import io.ktor.server.application.ApplicationCall
import io.ktor.server.request.contentType
import io.ktor.server.request.header
import io.ktor.server.request.receive
import io.ktor.server.request.receiveMultipart
import io.ktor.server.response.respond
import io.ktor.server.response.respondText
import io.ktor.utils.io.ByteChannel
import io.ktor.utils.io.copyAndClose
import io.ktor.utils.io.toByteArray
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import no.nav.emottak.constants.SMTPHeaders
import no.nav.emottak.ebms.validation.MimeHeaders
import no.nav.emottak.ebms.validation.MimeValidationException
import no.nav.emottak.ebms.validation.validateMimeAttachment
import no.nav.emottak.ebms.validation.validateMimeSoapEnvelope
import no.nav.emottak.message.model.DokumentType
import no.nav.emottak.message.model.EbMSDocument
Expand All @@ -38,25 +39,25 @@ import java.util.Base64
import kotlin.uuid.ExperimentalUuidApi
import kotlin.uuid.Uuid

fun PartData.payload(clearText: Boolean = false): ByteArray {
suspend fun PartData.payload(isBase64: Boolean = true): ByteArray {
return when (this) {
is PartData.FormItem -> if (clearText) {
return this.value.toByteArray()
} else {
is PartData.FormItem -> if (isBase64) {
try {
Base64.getMimeDecoder().decode(this.value.trim())
} catch (e: IllegalArgumentException) {
log.warn("First characters in failing string: <${this.value.substring(0,50)}>", e)
log.warn("Last characters in failing string: <${this.value.takeLast(50)}>", e)
throw e
}
} else {
this.value.toByteArray()
}

is PartData.FileItem -> {
val stream = this.streamProvider.invoke()
val bytes = stream.readAllBytes()
stream.close()
if (clearText) return bytes else Base64.getMimeDecoder().decode(bytes)
val byteReadChannel = this.provider()
val byteWriteChannel = ByteChannel()
byteReadChannel.copyAndClose(byteWriteChannel)
if (isBase64) Base64.getMimeDecoder().decode(byteWriteChannel.toByteArray()) else byteWriteChannel.toByteArray()
}
else -> byteArrayOf()
}
Expand All @@ -74,61 +75,50 @@ fun Headers.actuallyUsefulToString(): String {
@Throws(MimeValidationException::class)
suspend fun ApplicationCall.receiveEbmsDokument(): EbMSDocument {
log.info("Parsing message with Message-Id: ${request.header(SMTPHeaders.MESSAGE_ID)}")
val debugClearText = !request.header("cleartext").isNullOrBlank()
return when (val contentType = this.request.contentType().withoutParameters()) {
ContentType.parse("multipart/related") -> {
val allParts = mutableListOf<PartData>().apply {
[email protected]().forEachPart {
var partDataToAdd = it
if (it is PartData.FileItem) it.streamProvider.invoke()
if (it is PartData.FormItem) {
val boundary = [email protected]().parameter("boundary")
if (it.value.contains("--$boundary--")) {
logger().warn("Encountered KTOR bug, trimming boundary")
partDataToAdd = PartData.FormItem(it.value.substringBefore("--$boundary--").trim(), {}, it.headers)
}
}
this.add(partDataToAdd)
partDataToAdd.dispose.invoke()
it.dispose.invoke()
var start = contentType.parameter("start")
lateinit var ebmsEnvelopeHeaders: Headers
var ebmsEnvelope: Pair<String, ByteArray>? = null
val attachments = mutableListOf<EbmsAttachment>()
[email protected]().forEachPart { partData ->
if (start == null && ebmsEnvelope == null) start = partData.headers[MimeHeaders.CONTENT_ID]

val isBase64 = "base64".equals(partData.headers[MimeHeaders.CONTENT_TRANSFER_ENCODING], true)
if (partData.headers[MimeHeaders.CONTENT_ID] == start) {
ebmsEnvelopeHeaders = partData.headers
ebmsEnvelope = Pair(
partData.headers[MimeHeaders.CONTENT_ID]?.convertToValidatedContentID() ?: "GENERERT-${Uuid.random()}",
partData.payload(isBase64)
)
} else {
attachments.add(
EbmsAttachment(
partData.payload(isBase64),
partData.contentType!!.contentType,
partData.headers[MimeHeaders.CONTENT_ID]!!.convertToValidatedContentID()
)
)
}
partData.dispose.invoke()
}
val start = contentType.parameter("start") ?: allParts.first().headers[MimeHeaders.CONTENT_ID]
val dokument = allParts.find {
it.headers[MimeHeaders.CONTENT_ID] == start
}!!.also {
it.validateMimeSoapEnvelope()
}.let {
val contentID = it.headers[MimeHeaders.CONTENT_ID]?.convertToValidatedContentID() ?: "GENERERT-${Uuid.random()}"
val isBase64 = "base64".equals(it.headers[MimeHeaders.CONTENT_TRANSFER_ENCODING], true)
Pair(contentID, it.payload(debugClearText || !isBase64))
}
val attachments =
allParts.filter { it.headers[MimeHeaders.CONTENT_ID] != start }
attachments.forEach {
it.validateMimeAttachment()
}
if (ebmsEnvelope == null) throw MimeValidationException("Failed to extract soap envelope from multipartdata")
ebmsEnvelopeHeaders.validateMimeSoapEnvelope()
EbMSDocument(
dokument.first,
getDocumentBuilder().parse(ByteArrayInputStream(dokument.second)),
attachments.map {
val isBase64 = "base64".equals(it.headers[MimeHeaders.CONTENT_TRANSFER_ENCODING], true)
EbmsAttachment(
it.payload(debugClearText || !isBase64),
it.contentType!!.contentType,
it.headers[MimeHeaders.CONTENT_ID]!!.convertToValidatedContentID()
)
}
ebmsEnvelope!!.first,
withContext(Dispatchers.IO) {
getDocumentBuilder().parse(ByteArrayInputStream(ebmsEnvelope!!.second))
},
attachments
)
}

ContentType.parse("text/xml") -> {
val dokument = withContext(Dispatchers.IO) {
if (debugClearText || "base64" != request.header(MimeHeaders.CONTENT_TRANSFER_ENCODING)?.lowercase()) {
[email protected]<ByteArray>()
if ("base64" == request.header(MimeHeaders.CONTENT_TRANSFER_ENCODING)?.lowercase()) {
Base64.getMimeDecoder().decode([email protected]<ByteArray>())
} else {
Base64.getMimeDecoder()
.decode([email protected]<ByteArray>())
[email protected]<ByteArray>()
}
}
EbMSDocument(
Expand All @@ -140,8 +130,6 @@ suspend fun ApplicationCall.receiveEbmsDokument(): EbMSDocument {

else -> {
throw MimeValidationException("Ukjent request body med Content-Type $contentType")
// call.respond(HttpStatusCode.BadRequest, "Ukjent request body med Content-Type $contentType")
// return@post
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ fun Route.postEbmsSync(
"Message-Id ${call.request.header(SMTPHeaders.MESSAGE_ID)}",
ex
)
// @TODO done only for demo fiks!
call.respond(
HttpStatusCode.InternalServerError,
ex.parseAsSoapFault()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package no.nav.emottak.ebms.model

import no.nav.emottak.ebms.util.marker
import no.nav.emottak.ebms.validation.SignaturValidator
import no.nav.emottak.message.model.EbmsMessage
import no.nav.emottak.message.model.PayloadMessage
Expand All @@ -8,5 +9,5 @@ import no.nav.emottak.message.model.log

fun EbmsMessage.sjekkSignature(signatureDetails: SignatureDetails) {
SignaturValidator.validate(signatureDetails, this.dokument!!, if (this is PayloadMessage) listOf(this.payload) else listOf())
log.info("Signatur OK")
log.info(this.marker(), "Signatur OK")
}
Original file line number Diff line number Diff line change
Expand Up @@ -47,15 +47,17 @@ fun ApplicationRequest.validateContentType() {
if (contentType.parameter("type") != null && contentType.parameter("type") != "text/xml") throw MimeValidationException("Type of multipart related should be text/xml")
}

// KRAV 5.5.2.3 Valideringsdokument
fun PartData.validateMimeSoapEnvelope() {
this.contentType?.withoutParameters().takeIf { it == ContentType.parse("text/xml") } ?: throw MimeValidationException("Content type is missing or wrong ")
// TODO Kontakt EPJ der Content ID mangler
// if (this.headers[MimeHeaders.CONTENT_ID].isNullOrBlank()) {
// throw MimeValidationException("Content ID is missing")
// }
this.headers[MimeHeaders.CONTENT_TRANSFER_ENCODING].takeUnless { it.isNullOrBlank() }?.let {
it.takeIf { listOf("8bit", "base64", "binary", "quoted-printable").contains(it.lowercase()) } ?: throw MimeValidationException("Unrecognised Content-Transfer-Encoding: $it")
this.headers.validateMimeSoapEnvelope()
}

// KRAV 5.5.2.3 Valideringsdokument
fun Headers.validateMimeSoapEnvelope() {
this[MimeHeaders.CONTENT_TYPE]?.let { ContentType.parse(it) }?.withoutParameters()
.takeIf { it == ContentType.parse("text/xml") } ?: throw MimeValidationException("Content type is missing or wrong ")
this[MimeHeaders.CONTENT_TRANSFER_ENCODING].takeUnless { it.isNullOrBlank() }?.let {
it.takeIf { listOf("8bit", "base64", "binary", "quoted-printable").contains(it.lowercase()) }
?: throw MimeValidationException("Unrecognised Content-Transfer-Encoding: $it")
} ?: throw MimeValidationException("Mandatory header Content-Transfer-Encoding is undefined")
}

Expand Down
Loading