fix: encode filenames in UTF-8 when downloading (#941)

Co-authored-by: ahamilton <ahamilton@emissary.co.jp>
Co-authored-by: Gauthier Roebroeck <gauthier.roebroeck@gmail.com>
This commit is contained in:
Drew Hamilton 2022-08-09 12:41:55 +09:00 committed by GitHub
parent 115be0ab32
commit cf98e69374
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 88 additions and 3 deletions

View file

@ -81,6 +81,7 @@ import org.springframework.web.server.ResponseStatusException
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
import java.io.FileNotFoundException
import java.io.OutputStream
import java.nio.charset.StandardCharsets.UTF_8
import java.nio.file.NoSuchFileException
import java.time.LocalDate
import java.time.ZoneOffset
@ -386,7 +387,7 @@ class BookController(
.headers(
HttpHeaders().apply {
contentDisposition = ContentDisposition.builder("attachment")
.filename(book.path.name)
.filename(book.path.name, UTF_8)
.build()
},
)

View file

@ -73,6 +73,7 @@ import org.springframework.web.multipart.MultipartFile
import org.springframework.web.server.ResponseStatusException
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
import java.io.OutputStream
import java.nio.charset.StandardCharsets.UTF_8
import java.util.concurrent.TimeUnit
import java.util.zip.Deflater
import javax.validation.Valid
@ -422,7 +423,7 @@ class ReadListController(
.headers(
HttpHeaders().apply {
contentDisposition = ContentDisposition.builder("attachment")
.filename(readList.name + ".zip")
.filename(readList.name + ".zip", UTF_8)
.build()
},
)

View file

@ -88,6 +88,7 @@ import org.springframework.web.multipart.MultipartFile
import org.springframework.web.server.ResponseStatusException
import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody
import java.io.OutputStream
import java.nio.charset.StandardCharsets.UTF_8
import java.util.zip.Deflater
import javax.validation.Valid
@ -672,7 +673,7 @@ class SeriesController(
.headers(
HttpHeaders().apply {
contentDisposition = ContentDisposition.builder("attachment")
.filename(seriesMetadataRepository.findById(seriesId).title + ".zip")
.filename(seriesMetadataRepository.findById(seriesId).title + ".zip", UTF_8)
.build()
},
)

View file

@ -25,6 +25,7 @@ import org.gotson.komga.domain.service.LibraryLifecycle
import org.gotson.komga.domain.service.SeriesLifecycle
import org.gotson.komga.infrastructure.security.KomgaPrincipal
import org.hamcrest.Matchers
import org.hamcrest.Matchers.containsString
import org.hamcrest.core.IsNull
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
@ -49,6 +50,9 @@ import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.patch
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.time.LocalDate
import kotlin.random.Random
@ -1346,4 +1350,26 @@ class BookControllerTest(
jsonPath("$.totalElements").value(2),
)
}
@Test
@WithMockCustomUser
fun `given book with Unicode name when getting book file then attachment name is correct`() {
val bookName = "アキラ"
val tempFile = Files.createTempFile(bookName, ".cbz")
.also { it.toFile().deleteOnExit() }
makeSeries(name = "series", libraryId = library.id).let { series ->
seriesLifecycle.createSeries(series).let { created ->
val books = listOf(makeBook(bookName, libraryId = library.id, url = tempFile.toUri().toURL()))
seriesLifecycle.addBooks(created, books)
}
}
val book = bookRepository.findAll().first()
mockMvc.get("/api/v1/books/${book.id}/file")
.andExpect {
status { isOk() }
header { string("Content-Disposition", containsString(URLEncoder.encode(bookName, StandardCharsets.UTF_8.name()))) }
}
}
}

View file

@ -13,6 +13,7 @@ import org.gotson.komga.domain.service.LibraryLifecycle
import org.gotson.komga.domain.service.ReadListLifecycle
import org.gotson.komga.domain.service.SeriesLifecycle
import org.gotson.komga.language.toIndexedMap
import org.hamcrest.Matchers
import org.junit.jupiter.api.AfterAll
import org.junit.jupiter.api.AfterEach
import org.junit.jupiter.api.BeforeAll
@ -29,6 +30,9 @@ import org.springframework.test.web.servlet.delete
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.patch
import org.springframework.test.web.servlet.post
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.nio.file.Files
@ExtendWith(SpringExtension::class)
@SpringBootTest
@ -979,4 +983,32 @@ class ReadListControllerTest(
}
}
}
@Test
@WithMockCustomUser
fun `given readlist with Unicode name when getting readlist file then attachment name is correct`() {
val name = "アキラ"
val tempFile = Files.createTempFile(name, ".cbz")
.also { it.toFile().deleteOnExit() }
val book = makeBook(name, libraryId = library1.id, url = tempFile.toUri().toURL())
makeSeries(name = "series", libraryId = library1.id).let { series ->
seriesLifecycle.createSeries(series).let { created ->
val books = listOf(book)
seriesLifecycle.addBooks(created, books)
}
}
val readlist = readListLifecycle.addReadList(
ReadList(
name = name,
bookIds = listOf(book.id).toIndexedMap(),
),
)
mockMvc.get("/api/v1/readlists/${readlist.id}/file")
.andExpect {
status { isOk() }
header { string("Content-Disposition", Matchers.containsString(URLEncoder.encode(name, StandardCharsets.UTF_8.name()))) }
}
}
}

View file

@ -44,6 +44,9 @@ import org.springframework.test.web.servlet.delete
import org.springframework.test.web.servlet.get
import org.springframework.test.web.servlet.patch
import org.springframework.test.web.servlet.post
import java.net.URLEncoder
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import kotlin.random.Random
@ExtendWith(SpringExtension::class)
@ -1075,4 +1078,25 @@ class SeriesControllerTest(
}
}
}
@Test
@WithMockCustomUser
fun `given series with Unicode name when getting series file then attachment name is correct`() {
val name = "アキラ"
val tempFile = Files.createTempFile(name, ".cbz")
.also { it.toFile().deleteOnExit() }
val series = makeSeries(name = name, libraryId = library.id).let { series ->
seriesLifecycle.createSeries(series).let { created ->
val books = listOf(makeBook(name, libraryId = library.id, url = tempFile.toUri().toURL()))
seriesLifecycle.addBooks(created, books)
}
series
}
mockMvc.get("/api/v1/series/${series.id}/file")
.andExpect {
status { isOk() }
header { string("Content-Disposition", Matchers.containsString(URLEncoder.encode(name, StandardCharsets.UTF_8.name()))) }
}
}
}