Update:Max filename to 255 bytes in utf-16 #1261

This commit is contained in:
advplyr 2022-12-13 17:46:18 -06:00
parent 2af7b6b6f1
commit 4d8e2a1279
2 changed files with 64 additions and 33 deletions

View File

@ -30,23 +30,26 @@ Vue.prototype.$addDaysToDate = (jsdate, daysToAdd) => {
return date return date
} }
Vue.prototype.$sanitizeFilename = (input, colonReplacement = ' - ') => { Vue.prototype.$sanitizeFilename = (filename, colonReplacement = ' - ') => {
if (typeof input !== 'string') { if (typeof filename !== 'string') {
return false return false
} }
// Max is actually 255-260 for windows but this leaves padding incase ext wasnt put on yet // Most file systems use number of bytes for max filename
const MAX_FILENAME_LEN = 240 // to support most filesystems we will use max of 255 bytes in utf-16
// Ref: https://doc.owncloud.com/server/next/admin_manual/troubleshooting/path_filename_length.html
// Issue: https://github.com/advplyr/audiobookshelf/issues/1261
const MAX_FILENAME_BYTES = 255
var replacement = '' const replacement = ''
var illegalRe = /[\/\?<>\\:\*\|"]/g const illegalRe = /[\/\?<>\\:\*\|"]/g
var controlRe = /[\x00-\x1f\x80-\x9f]/g const controlRe = /[\x00-\x1f\x80-\x9f]/g
var reservedRe = /^\.+$/ const reservedRe = /^\.+$/
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i
var windowsTrailingRe = /[\. ]+$/ const windowsTrailingRe = /[\. ]+$/
var lineBreaks = /[\n\r]/g const lineBreaks = /[\n\r]/g
var sanitized = input sanitized = filename
.replace(':', colonReplacement) // Replace first occurrence of a colon .replace(':', colonReplacement) // Replace first occurrence of a colon
.replace(illegalRe, replacement) .replace(illegalRe, replacement)
.replace(controlRe, replacement) .replace(controlRe, replacement)
@ -55,13 +58,25 @@ Vue.prototype.$sanitizeFilename = (input, colonReplacement = ' - ') => {
.replace(windowsReservedRe, replacement) .replace(windowsReservedRe, replacement)
.replace(windowsTrailingRe, replacement) .replace(windowsTrailingRe, replacement)
// Check if basename is too many bytes
const ext = Path.extname(sanitized) // separate out file extension
const basename = Path.basename(sanitized, ext)
const extByteLength = Buffer.byteLength(ext, 'utf16le')
const basenameByteLength = Buffer.byteLength(basename, 'utf16le')
if (basenameByteLength + extByteLength > MAX_FILENAME_BYTES) {
const MaxBytesForBasename = MAX_FILENAME_BYTES - extByteLength
let totalBytes = 0
let trimmedBasename = ''
if (sanitized.length > MAX_FILENAME_LEN) { // Add chars until max bytes is reached
var lenToRemove = sanitized.length - MAX_FILENAME_LEN for (const char of basename) {
var ext = Path.extname(sanitized) totalBytes += Buffer.byteLength(char, 'utf16le')
var basename = Path.basename(sanitized, ext) if (totalBytes > MaxBytesForBasename) break
basename = basename.slice(0, basename.length - lenToRemove) else trimmedBasename += char
sanitized = basename + ext }
trimmedBasename = trimmedBasename.trim()
sanitized = trimmedBasename + ext
} }
return sanitized return sanitized

View File

@ -183,16 +183,19 @@ module.exports.sanitizeFilename = (filename, colonReplacement = ' - ') => {
return false return false
} }
// Max is actually 255-260 for windows but this leaves padding incase ext wasnt put on yet // Most file systems use number of bytes for max filename
const MAX_FILENAME_LEN = 240 // to support most filesystems we will use max of 255 bytes in utf-16
// Ref: https://doc.owncloud.com/server/next/admin_manual/troubleshooting/path_filename_length.html
// Issue: https://github.com/advplyr/audiobookshelf/issues/1261
const MAX_FILENAME_BYTES = 255
var replacement = '' const replacement = ''
var illegalRe = /[\/\?<>\\:\*\|"]/g const illegalRe = /[\/\?<>\\:\*\|"]/g
var controlRe = /[\x00-\x1f\x80-\x9f]/g const controlRe = /[\x00-\x1f\x80-\x9f]/g
var reservedRe = /^\.+$/ const reservedRe = /^\.+$/
var windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i const windowsReservedRe = /^(con|prn|aux|nul|com[0-9]|lpt[0-9])(\..*)?$/i
var windowsTrailingRe = /[\. ]+$/ const windowsTrailingRe = /[\. ]+$/
var lineBreaks = /[\n\r]/g const lineBreaks = /[\n\r]/g
sanitized = filename sanitized = filename
.replace(':', colonReplacement) // Replace first occurrence of a colon .replace(':', colonReplacement) // Replace first occurrence of a colon
@ -203,12 +206,25 @@ module.exports.sanitizeFilename = (filename, colonReplacement = ' - ') => {
.replace(windowsReservedRe, replacement) .replace(windowsReservedRe, replacement)
.replace(windowsTrailingRe, replacement) .replace(windowsTrailingRe, replacement)
if (sanitized.length > MAX_FILENAME_LEN) { // Check if basename is too many bytes
var lenToRemove = sanitized.length - MAX_FILENAME_LEN const ext = Path.extname(sanitized) // separate out file extension
var ext = Path.extname(sanitized) const basename = Path.basename(sanitized, ext)
var basename = Path.basename(sanitized, ext) const extByteLength = Buffer.byteLength(ext, 'utf16le')
basename = basename.slice(0, basename.length - lenToRemove) const basenameByteLength = Buffer.byteLength(basename, 'utf16le')
sanitized = basename + ext if (basenameByteLength + extByteLength > MAX_FILENAME_BYTES) {
const MaxBytesForBasename = MAX_FILENAME_BYTES - extByteLength
let totalBytes = 0
let trimmedBasename = ''
// Add chars until max bytes is reached
for (const char of basename) {
totalBytes += Buffer.byteLength(char, 'utf16le')
if (totalBytes > MaxBytesForBasename) break
else trimmedBasename += char
}
trimmedBasename = trimmedBasename.trim()
sanitized = trimmedBasename + ext
} }
return sanitized return sanitized