Неправильная кодировка имен файлов после распаковки

У меня следующая проблема: я извлек 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
Другие вопросы по тегам