Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import java.util.UUID;

import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
Expand Down Expand Up @@ -35,15 +36,34 @@ public ResponseEntity<String> home() {
* @param docId document identifier
* @return HTML payload or redirect
*/
@GetMapping(value = "/viewer/{docId:[0-9a-fA-F\u002d]{36}}", produces = MediaType.TEXT_HTML_VALUE)
public ResponseEntity<String> viewer(@PathVariable UUID docId) {
return ResponseEntity.ok()
.contentType(MediaType.TEXT_HTML)
.body(viewerShellHtml(docId, "LOADING"));
@GetMapping(value = "/viewer/{docId}", produces = MediaType.TEXT_HTML_VALUE)
public ResponseEntity<String> viewer(@PathVariable String docId) {
try {
UUID parsed = UUID.fromString(docId);
return ResponseEntity.ok()
.contentType(MediaType.TEXT_HTML)
.body(viewerShellHtml(parsed.toString(), "LOADING"));
} catch (IllegalArgumentException ex) {
// Friendly invalid-document shell (instead of a raw framework 404)
// so integrator deep links, e.g. an admin console linking
// /viewer/{docId}, land on a readable page.
return ResponseEntity.status(HttpStatus.NOT_FOUND)
.contentType(MediaType.TEXT_HTML)
.body(viewerShellHtml(docId, "NOT_FOUND"));
}
}

private static String escapeHtmlAttribute(String value) {
return value
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#39;");
}

private static String viewerShellHtml(UUID docId, String initialState) {
String docIdString = docId.toString();
private static String viewerShellHtml(String docId, String initialState) {
String docIdString = escapeHtmlAttribute(docId);
String template = """
<!doctype html>
<html lang="en">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -149,4 +149,45 @@ void viewerReturnsHtmlWithDocIdMeta() {
.expectBody(String.class)
.value(body -> assertTrue(body.contains("clearfolio-doc-id\" content=\"" + docId)));
}

@Test
void viewerRejectsNonUuidDocIdWithFriendlyShell() {
webTestClient.get()
.uri("/viewer/not-a-uuid")
.exchange()
.expectStatus().isNotFound()
.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)
.expectBody(String.class)
.value(body -> {
assertTrue(body.contains("clearfolio-doc-id"));
assertTrue(body.contains("not-a-uuid"));
assertTrue(body.contains("clearfolio-initial-state\" content=\"NOT_FOUND"));
assertTrue(body.contains("/assets/viewer/viewer.js"));
});
}

@Test
void viewerEscapesHtmlInInvalidDocId() {
webTestClient.get()
.uri("/viewer/{docId}", "\"><script>alert(1)</script>")
.exchange()
.expectStatus().isNotFound()
.expectBody(String.class)
.value(body -> {
assertTrue(!body.contains("<script>alert(1)</script>"));
assertTrue(body.contains("&lt;script&gt;"));
});
}

@Test
void viewerStillServesUuidDocId() {
UUID docId = UUID.randomUUID();
webTestClient.get()
.uri("/viewer/" + docId)
.exchange()
.expectStatus().isOk()
.expectHeader().contentTypeCompatibleWith(MediaType.TEXT_HTML)
.expectBody(String.class)
.value(body -> assertTrue(body.contains(docId.toString())));
}
}
Loading