mirror of
https://github.com/paperless-ngx/paperless-ngx.git
synced 2025-04-21 10:29:29 -05:00
Fix: preserve non-ASCII filenames in document downloads (#9702)
This commit is contained in:
parent
abf910fd93
commit
f52ebc7bf0
@ -77,6 +77,7 @@ import { StoragePathService } from 'src/app/services/rest/storage-path.service'
|
||||
import { UserService } from 'src/app/services/rest/user.service'
|
||||
import { SettingsService } from 'src/app/services/settings.service'
|
||||
import { ToastService } from 'src/app/services/toast.service'
|
||||
import { getFilenameFromContentDisposition } from 'src/app/utils/http'
|
||||
import { ISODateAdapter } from 'src/app/utils/ngb-iso-date-adapter'
|
||||
import * as UTIF from 'utif'
|
||||
import { ConfirmDialogComponent } from '../common/confirm-dialog/confirm-dialog.component'
|
||||
@ -999,12 +1000,10 @@ export class DocumentDetailComponent
|
||||
.get(downloadUrl, { observe: 'response', responseType: 'blob' })
|
||||
.subscribe({
|
||||
next: (response: HttpResponse<Blob>) => {
|
||||
const filename = response.headers
|
||||
.get('Content-Disposition')
|
||||
?.split(';')
|
||||
?.find((part) => part.trim().startsWith('filename='))
|
||||
?.split('=')[1]
|
||||
?.replace(/['"]/g, '')
|
||||
const contentDisposition = response.headers.get('Content-Disposition')
|
||||
const filename =
|
||||
getFilenameFromContentDisposition(contentDisposition) ||
|
||||
this.document.title
|
||||
const blob = new Blob([response.body], {
|
||||
type: response.body.type,
|
||||
})
|
||||
|
31
src-ui/src/app/utils/http.spec.ts
Normal file
31
src-ui/src/app/utils/http.spec.ts
Normal file
@ -0,0 +1,31 @@
|
||||
import { getFilenameFromContentDisposition } from './http'
|
||||
|
||||
describe('getFilenameFromContentDisposition', () => {
|
||||
it('should extract filename from Content-Disposition header with filename*', () => {
|
||||
const header = "attachment; filename*=UTF-8''example%20file.txt"
|
||||
expect(getFilenameFromContentDisposition(header)).toBe('example file.txt')
|
||||
})
|
||||
|
||||
it('should extract filename from Content-Disposition header with filename=', () => {
|
||||
const header = 'attachment; filename="example-file.txt"'
|
||||
expect(getFilenameFromContentDisposition(header)).toBe('example-file.txt')
|
||||
})
|
||||
|
||||
it('should prioritize filename* over filename if both are present', () => {
|
||||
const header =
|
||||
'attachment; filename="fallback.txt"; filename*=UTF-8\'\'preferred%20file.txt'
|
||||
const result = getFilenameFromContentDisposition(header)
|
||||
expect(result).toBe('preferred file.txt')
|
||||
})
|
||||
|
||||
it('should gracefully fall back to null', () => {
|
||||
// invalid UTF-8 sequence
|
||||
expect(
|
||||
getFilenameFromContentDisposition("attachment; filename*=UTF-8''%E0%A4%A")
|
||||
).toBeNull()
|
||||
// missing filename
|
||||
expect(getFilenameFromContentDisposition('attachment;')).toBeNull()
|
||||
// empty header
|
||||
expect(getFilenameFromContentDisposition(null)).toBeNull()
|
||||
})
|
||||
})
|
23
src-ui/src/app/utils/http.ts
Normal file
23
src-ui/src/app/utils/http.ts
Normal file
@ -0,0 +1,23 @@
|
||||
export function getFilenameFromContentDisposition(header: string): string {
|
||||
if (!header) {
|
||||
return null
|
||||
}
|
||||
|
||||
// Try filename* (RFC 5987)
|
||||
const filenameStar = header.match(/filename\*=(?:UTF-\d['']*)?([^;]+)/i)
|
||||
if (filenameStar?.[1]) {
|
||||
try {
|
||||
return decodeURIComponent(filenameStar[1])
|
||||
} catch (e) {
|
||||
// Ignore decoding errors and fall through
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to filename=
|
||||
const filenameMatch = header.match(/filename="?([^"]+)"?/)
|
||||
if (filenameMatch?.[1]) {
|
||||
return filenameMatch[1]
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user