Skip to content

Commit

Permalink
support file://
Browse files Browse the repository at this point in the history
  • Loading branch information
wandmagic committed Oct 8, 2024
1 parent b9a7c12 commit 6cf6ca6
Show file tree
Hide file tree
Showing 2 changed files with 155 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,33 @@ class OscalVerticle : CoroutineVerticle() {
router.route("/*").handler(StaticHandler.create("webroot"))
return router
}
private fun processUrl(url: String): String {
return if (url.startsWith("file://")) {
try {
val uri = URI(url)
val path = when {
uri.authority != null -> {
// Remove the authority component
Paths.get(uri.authority + uri.path)
}
uri.path.startsWith("/") -> {
// Absolute path
Paths.get(uri.path)
}
else -> {
// Relative path
Paths.get(uri.path).toAbsolutePath()
}
}
path.toString()
} catch (e: Exception) {
logger.error("Error processing file URL: $url", e)
url // Return original URL if processing fails
}
} else {
url
}
}
private fun handleValidateFileUpload(ctx: RoutingContext) {
logger.info("Handling file upload request!")
launch {
Expand All @@ -98,11 +125,15 @@ class OscalVerticle : CoroutineVerticle() {

// Use async for parallelism
val result = async {
executeCommand(listOf("validate", tempFilePath.toString()))
executeCommand(listOf("validate", tempFilePath.toString(),"--show-stack-trace"))
}.await() // Wait for the result of the async execution

logger.info("Validation result: ${result.second}")
sendSuccessResponse(ctx, result.first, result.second)
if(result.first.exitCode.toString()==="OK"){
sendSuccessResponse(ctx, result.first, result.second)
}else{
sendErrorResponse(ctx, 400, result.first.exitCode.toString())
}

// Clean up the temporary file
} else {
Expand Down Expand Up @@ -131,33 +162,36 @@ class OscalVerticle : CoroutineVerticle() {
try {
logger.info("Handling Validate request")
val encodedContent = ctx.queryParam("content").firstOrNull()
val content = encodedContent?.let { URLDecoder.decode(it, StandardCharsets.UTF_8.name()) }
if (content != null) {
if (encodedContent != null) {
val content = processUrl(encodedContent)
// Use async for parallelism
val result = async {
executeCommand(parseCommandToArgs("validate "+content))
try{
executeCommand(listOf("validate",content,"--show-stack-trace"))
}catch (e: Exception) {
logger.error("Error handling request", e)
executeCommand(listOf("validate",content,"--show-stack-trace"))
}
}.await() // Wait for the result of the async execution
logger.info(result.second)
sendSuccessResponse(ctx, result.first, result.second)
} else {
sendErrorResponse(ctx, 400, "content parameter is missing")
}
} catch (e: Exception) {
logger.error("Error handling CLI request", e)
logger.error("Error handling request", e)
sendErrorResponse(ctx, 500, "Internal server error")
}
}
}
private fun handleResolveRequest(ctx: RoutingContext) {
launch {
try {
logger.info("Handling Resolve request")
val encodedContent = ctx.queryParam("content").firstOrNull()
val content = encodedContent?.let { URLDecoder.decode(it, StandardCharsets.UTF_8.name()) }
val acceptHeader = ctx.request().getHeader("Accept")
val format = mapMimeTypeToFormat(acceptHeader)

if (content != null) {
val encodedContent = ctx.queryParam("content").firstOrNull()
if (encodedContent != null) {
val content = processUrl(encodedContent)
val acceptHeader = ctx.request().getHeader("Accept")
val format = mapMimeTypeToFormat(acceptHeader)
// Use async for parallelism
val result = async {
executeCommand(parseCommandToArgs("resolve-profile $content --to=$format"))
Expand All @@ -184,14 +218,11 @@ class OscalVerticle : CoroutineVerticle() {
private fun handleConvertRequest(ctx: RoutingContext) {
launch {
try {
logger.info("Handling Convert request")
val encodedContent = ctx.queryParam("content").firstOrNull()
val content = encodedContent?.let { URLDecoder.decode(it, StandardCharsets.UTF_8.name()) }
val acceptHeader = ctx.request().getHeader("Accept")
val format = mapMimeTypeToFormat(acceptHeader)

if (content != null) {
// Use async for parallelism
val encodedContent = ctx.queryParam("content").firstOrNull()
if (encodedContent != null) {
val content = processUrl(encodedContent)
val acceptHeader = ctx.request().getHeader("Accept")
val format = mapMimeTypeToFormat(acceptHeader)
val result = async {
executeCommand(parseCommandToArgs("convert $content --to=$format"))
}.await() // Wait for the result of the async execution
Expand Down Expand Up @@ -281,13 +312,14 @@ class OscalVerticle : CoroutineVerticle() {
ctx.response()
.setStatusCode(200) // HTTP 200 OK
.putHeader("Content-Type", "application/json")
.putHeader("Exit-Status", exitStatus.toString())
.putHeader("Exit-Status", exitStatus.exitCode.toString())
.end(fileContent)
}

private fun sendErrorResponse(ctx: RoutingContext, statusCode: Int, message: String) {
ctx.response()
.setStatusCode(statusCode)
.putHeader("Exit-Status", statusCode.toString())
.putHeader("content-type", "application/json")
.end(JsonObject().put("error", message).encode())
}
Expand Down
113 changes: 101 additions & 12 deletions src/test/kotlin/gov/nist/secauto/oscal/tools/server/TestOscalVerticle.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,5 @@
/*
* SPDX-FileCopyrightText: none
* SPDX-License-Identifier: CC0-1.0
*/

package gov.nist.secauto.oscal.tools.server

import java.nio.file.Files
import io.vertx.core.Vertx
import io.vertx.ext.web.client.WebClient
import io.vertx.ext.web.client.WebClientOptions
Expand All @@ -14,19 +9,28 @@ import org.junit.After
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

import java.nio.file.Path
import java.nio.file.Paths
import java.io.File
import java.net.URL
import java.net.URLEncoder
import java.net.URI
import kotlinx.coroutines.runBlocking
import org.apache.logging.log4j.Logger
import org.apache.logging.log4j.LogManager

@RunWith(VertxUnitRunner::class)
class TestOscalVerticle {

private val logger: Logger = LogManager.getLogger(TestOscalVerticle::class.java)
private lateinit var vertx: Vertx
private lateinit var webClient: WebClient

@Before
fun setUp(testContext: TestContext) {
vertx = Vertx.vertx()
webClient = WebClient.create(vertx, WebClientOptions().setDefaultPort(8888))

initializeOscalDirectory()

val async = testContext.async()
vertx.deployVerticle(OscalVerticle()) { ar ->
if (ar.succeeded()) {
Expand All @@ -43,23 +47,108 @@ class TestOscalVerticle {
}

@Test
fun test_oscal_command(testContext: TestContext) {
fun test_oscal_command_remote(testContext: TestContext) {
val async = testContext.async()

webClient.get("/validate")
.addQueryParam("content", "https://raw.githubusercontent.com/GSA/fedramp-automation/refs/heads/develop/src/validations/constraints/content/ssp-all-VALID.xml")
.addQueryParam("content", "https://raw.githubusercontent.com/usnistgov/oscal-content/refs/heads/main/examples/ssp/xml/ssp-example.xml")
.send { ar ->
if (ar.succeeded()) {
val response = ar.result()
testContext.assertEquals(200, response.statusCode())
val body = response.bodyAsJsonObject()
testContext.assertNotNull(body)
testContext.assertTrue(body.containsKey("runs"))
// New assertion to check output length
async.complete()
} else {
testContext.fail(ar.cause())
}
}
}
private fun initializeOscalDirectory() {
val homeDir = System.getProperty("user.home")
val oscalDir = Paths.get(homeDir, ".oscal")
val oscalTmpDir = oscalDir.resolve("tmp")
if (!Files.exists(oscalDir)) {
Files.createDirectory(oscalDir)
}
if (!Files.exists(oscalTmpDir)) {
Files.createDirectory(oscalTmpDir)
}
}

@Test
fun test_oscal_command_local_file(testContext: TestContext) {
val async = testContext.async()

try {
// Download the file
val url = URL("https://raw.githubusercontent.com/usnistgov/oscal-content/refs/heads/main/examples/ssp/xml/ssp-example.xml")
val homeDir = System.getProperty("user.home")
val oscalDir = Paths.get(homeDir, ".oscal")

// Ensure the OSCAL directory exists
if (!Files.exists(oscalDir)) {
Files.createDirectories(oscalDir)
logger.info("Created OSCAL directory: $oscalDir")
}

var tempFile: Path? = null
try {
tempFile = Files.createTempFile(oscalDir, "ssp", ".xml")
logger.info("Created temporary file: $tempFile")
} catch (e: Exception) {
logger.error("Failed to create temporary file in $oscalDir", e)
testContext.fail("Failed to create temporary file: ${e.message}")
return@test_oscal_command_local_file
}

val tempFilePath = tempFile.toAbsolutePath()

runBlocking<Unit> {
try {
url.openStream().use { input ->
Files.newOutputStream(tempFile).use { output ->
input.copyTo(output)
}
}
logger.info("Successfully downloaded content to $tempFile")
} catch (e: Exception) {
logger.error("Failed to download or write content", e)
testContext.fail("Failed to download or write content: ${e.message}")
return@runBlocking
}
}

val fileUrl = "file://" + URLEncoder.encode(tempFilePath.toString(), "UTF-8")

webClient.get("/validate")
.addQueryParam("content", fileUrl)
.send { ar ->
if (ar.succeeded()) {
val response = ar.result()
testContext.assertEquals(200, response.statusCode())
val body = response.bodyAsJsonObject()
testContext.assertEquals("OK", response.getHeader("Exit-Status"))
testContext.assertNotNull(body)
testContext.assertTrue(body.containsKey("runs"))
async.complete()
} else {
logger.error("Validation request failed", ar.cause())
testContext.fail(ar.cause())
}

// Clean up the temporary file
try {
Files.deleteIfExists(tempFile)
logger.info("Deleted temporary file: $tempFile")
} catch (e: Exception) {
logger.warn("Failed to delete temporary file: $tempFile", e)
}
}
} catch (e: Exception) {
logger.error("Unexpected error in test", e)
testContext.fail(e)
}
}
}

0 comments on commit 6cf6ca6

Please sign in to comment.