Неправильная кодировка имен файлов после распаковки
У меня следующая проблема: я извлек zip-файл через SSZipArchive (в приложении Swift), и есть некоторые имена файлов с "недопустимыми" символами.
Я думаю, причина в том, что я заархивировал файлы под Windows, и поэтому имена теперь закодированы в ANSI.
Есть ли способ конвертировать все "поврежденные" имена папок и файлов во время процесса распаковки?
Или позже? Было бы не проблема, если бы мне пришлось перебирать дерево папок и переименовывать файлы.
Но я не знаю, как узнать, какие имена установлены в ANSI, и я также не знаю, как исправить кодировку.
2 ответа
Официальная спецификация говорит, что путь должен быть либо закодирован в коде MS-DOS Latin US, либо в UTF-8 (если установлен бит 11 поля общего назначения):
D.1 Формат ZIP исторически поддерживал только исходный набор кодировки символов IBM PC, обычно называемый кодовой страницей IBM 437. Это ограничивает хранение символов имени файла только теми, которые находятся в исходном диапазоне значений MS-DOS, и не поддерживает должным образом имена файлов в других кодировках символов или языках. Чтобы устранить это ограничение, данная спецификация будет поддерживать следующее изменение.
D.2 Если бит 11 общего назначения не установлен, имя файла и комментарий должны соответствовать исходной кодировке символов ZIP. Если установлен бит 11 общего назначения, имя файла и комментарий должны поддерживать стандарт Unicode версии 4.1.0 или выше с использованием формы кодировки символов, определенной в спецификации хранения UTF-8. Стандарт Unicode опубликован Консорциумом Unicode (www.unicode.org). Ожидается, что данные в кодировке UTF-8, хранящиеся в ZIP-файлах, не будут содержать метки порядка байтов (BOM).
Недавно я выпустил Swift-реализацию формата ZIP, называемую ZIPFoundation. Он соответствует стандарту и должен иметь возможность определять пути Windows и правильно их декодировать.
Вероятно, исправлено в последней версии SSZipArchive (в настоящее время 2.1.1). Я реализовал поддержку имен файлов, отличных от Unicode, аналогично приведенному ниже коду, поэтому вы можете использовать его для самостоятельной обработки ваших имен файлов, если хотите.
Хорошо, это в Objective-C, но поскольку SSZipArchive уже имеет исправление само по себе, оно вам больше не нужно. В противном случае, либо создайте мостовой заголовок, чтобы включить в ваш swift-приложение код target-c, либо преобразуйте его в Swift (это должно быть легко).
@implementation NSString (SSZipArchive)
+ (NSString *)filenameStringWithCString:(const char *)filename size:(uint16_t)size_filename
{
// unicode conversion attempt
NSString *strPath = @(filename);
if (strPath) {
return strPath;
}
// if filename is non-unicode, detect and transform Encoding
NSData *data = [NSData dataWithBytes:(const void *)filename length:sizeof(unsigned char) * size_filename];
// supported encodings are in [NSString availableStringEncodings]
[NSString stringEncodingForData:data encodingOptions:nil convertedString:&strPath usedLossyConversion:nil];
if (strPath) {
return strPath;
}
// if filename encoding is non-detected, we default to something based on data
// note: hexString is more readable than base64RFC4648 for debugging unknown encodings
strPath = [data hexString];
return strPath;
}
@end
@implementation NSData (SSZipArchive)
// initWithBytesNoCopy from NSProgrammer, Jan 25 '12: https://stackru.com/a/9009321/1033581
// hexChars from Peter, Aug 19 '14: https://stackru.com/a/25378464/1033581
// not implemented as too lengthy: a potential mapping improvement from Moose, Nov 3 '15: https://stackru.com/a/33501154/1033581
- (NSString *)hexString
{
const char *hexChars = "0123456789ABCDEF";
NSUInteger length = self.length;
const unsigned char *bytes = self.bytes;
char *chars = malloc(length * 2);
// TODO: check for NULL
char *s = chars;
NSUInteger i = length;
while (i--) {
*s++ = hexChars[*bytes >> 4];
*s++ = hexChars[*bytes & 0xF];
bytes++;
}
NSString *str = [[NSString alloc] initWithBytesNoCopy:chars
length:length * 2
encoding:NSASCIIStringEncoding
freeWhenDone:YES];
return str;
}
@end