Skip to content
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

fix plaintext files encoding #106

Merged
merged 6 commits into from
Oct 19, 2020
Merged
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
Empty file.
2 changes: 2 additions & 0 deletions spring-app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ dependencies {
implementation("commons-fileupload:commons-fileupload:1.3.3")
implementation("io.jsonwebtoken:jjwt:0.9.1")
runtimeOnly("com.h2database:h2:1.4.200")
implementation("com.ibm.icu:icu4j:67.1")
runtimeOnly("com.h2database:h2:1.4.200")
runtimeOnly("mysql:mysql-connector-java")
runtimeOnly("org.mariadb.jdbc:mariadb-java-client")
runtimeOnly(files("../gatsby-app/build/artifact/gatsby-app.jar"))
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package pl.starchasers.up.service

import com.ibm.icu.text.CharsetDetector
import org.springframework.stereotype.Service
import java.io.InputStream

interface CharsetDetectionService {
fun detect(inputStream: InputStream): String
}

@Service
class CharsetDetectionServiceImpl : CharsetDetectionService {
override fun detect(inputStream: InputStream): String {
val detector = CharsetDetector()
detector.setDeclaredEncoding("utf-8")
detector.setText(inputStream)
return detector.detect().name
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,8 @@ interface FileService {
class FileServiceImpl(
private val fileStorageService: FileStorageService,
private val fileEntryRepository: FileEntryRepository,
private val configurationService: ConfigurationService
private val configurationService: ConfigurationService,
private val charsetDetectionService: CharsetDetectionService
) : FileService {

@Value("\${up.max-file-size}")
Expand All @@ -55,6 +56,11 @@ class FileServiceImpl(
size: Long,
user: User?
): UploadCompleteResponseDTO {
val actualContentType = when {
contentType.isBlank() -> "application/octet-stream"
contentType == "text/plain" -> "text/plain; charset=" + charsetDetectionService.detect(tmpFile)
else -> contentType
}
val personalLimit: Long = user?.maxTemporaryFileSize?.value
?: configurationService.getConfigurationOption(ANONYMOUS_MAX_FILE_SIZE).toLong()

Expand All @@ -67,7 +73,7 @@ class FileServiceImpl(
val fileEntry = FileEntry(0,
key,
filename,
contentType.let { if (contentType.isBlank()) "application/octet-stream" else contentType },
actualContentType,
null,
false,
Timestamp.valueOf(LocalDateTime.now()),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ internal class UploadControllerTest : MockMvcTestBase() {
fun `Given valid request, should upload and store file`() {
val exampleTextFile = MockMultipartFile("file",
"exampleTextFile.txt",
"text/plain",
"text/plain; charset=UTF-8",
"example content".toByteArray())

mockMvc.multipart(path = uploadFileRequestPath,
Expand All @@ -68,7 +68,7 @@ internal class UploadControllerTest : MockMvcTestBase() {
}

fileEntryRepository.findAll()[0].let { fileEntry ->
assertEquals("text/plain", fileEntry.contentType)
assertEquals("text/plain; charset=UTF-8", fileEntry.contentType)
assertEquals(false, fileEntry.encrypted)
assertEquals("exampleTextFile.txt", fileEntry.filename)
assertEquals(null, fileEntry.password)
Expand Down Expand Up @@ -158,20 +158,22 @@ internal class UploadControllerTest : MockMvcTestBase() {
) : MockMvcTestBase() {
private val content = "example content"

private fun createFile(contentType: String, fileContent:String=content): String = fileService.createFile(
fileContent.byteInputStream(),
"fileName.txt",
contentType,
fileContent.byteInputStream().readAllBytes().size.toLong(),
null
).key

@Test
@DocumentResponse
fun `Given valid key, should return raw file`() {
val key = fileService.createFile(
content.byteInputStream(),
"fileName.txt",
"text/plain",
content.byteInputStream().readAllBytes().size.toLong(),
null
).key
val key = createFile("application/octet-stream")

mockMvc.get(path = Path("/u/$key")) {
responseJsonPath("$").equalsValue("example content")
responseHeader(HttpHeaders.CONTENT_TYPE).equals("text/plain")
responseHeader(HttpHeaders.CONTENT_TYPE).equals("application/octet-stream")
responseHeader(HttpHeaders.CONTENT_LENGTH).equals("${content.length}")
}

Expand All @@ -180,13 +182,7 @@ internal class UploadControllerTest : MockMvcTestBase() {
@Test
fun `Given valid Range header, should return 206`() {
val contentSize = content.byteInputStream().readAllBytes().size.toLong()
val key = fileService.createFile(
content.byteInputStream(),
"fileName.txt",
"text/plain",
contentSize,
null
).key
val key = createFile("text/plain")

val headers = HttpHeaders()
headers.set(HttpHeaders.RANGE, "bytes=0-")
Expand All @@ -201,12 +197,7 @@ internal class UploadControllerTest : MockMvcTestBase() {
@Test
fun `Given invalid Range header, should return 200`() {
val contentSize = content.byteInputStream().readAllBytes().size.toLong()
val key = fileService.createFile(
content.byteInputStream(),
"fileName.txt",
"text/plain",
contentSize,
null).key
val key = createFile("text/plain")

val headers = HttpHeaders()
headers.set(HttpHeaders.RANGE, "mb=-1024")
Expand All @@ -222,6 +213,29 @@ internal class UploadControllerTest : MockMvcTestBase() {
isError(HttpStatus.NOT_FOUND)
}
}

@Test
fun `Given unspecified text file encoding, should guess based on content`() {
val key = createFile("text/plain", fileContent = "Ā ā Ă অ আ ই ঈ উ")

mockMvc.get(path = Path("/u/$key")) {
isSuccess()
responseJsonPath("$").equalsValue("Ā ā Ă অ আ ই ঈ উ")
responseHeader("Content-Type").equals("text/plain; charset=UTF-8")
}
}

@Test
fun `Given specified text file encoding, should preserve it`() {
val contentType = "text/plain; charset=us-ascii"
val key = createFile(contentType)

mockMvc.get(path = Path("/u/$key")) {
isSuccess()
responseJsonPath("$").equalsValue("example content")
responseHeader("Content-Type").equals(contentType)
}
}
}

@Transactional
Expand Down Expand Up @@ -300,15 +314,14 @@ internal class UploadControllerTest : MockMvcTestBase() {
@Nested
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
inner class GetFileDetails(
@Autowired val fileService: FileService,
@Autowired val fileEntryRepository: FileEntryRepository
@Autowired val fileService: FileService
) : MockMvcTestBase() {

private fun getRequestPath(key: String): Path = Path("/api/u/$key/details")
private val content = "example content"
private lateinit var fileKey: String
private val filename: String = "filename.txt"
private val contentType: String = "text/plain"
private val contentType: String = "text/plain; charset=UTF-8"

@BeforeAll
fun setup() {
Expand Down