Skip to content

Commit

Permalink
Add fine-grained filename encoding
Browse files Browse the repository at this point in the history
* Add entry encoding property based on the EFS flag of the general purpose bit flags.

* Add entry fileNameWithEncoding: methods to force a particular entry-level encoding for the file name.

* Remove archive-level encoding for file name.

* Default encoding is now CP-437, as per the Zip file format specification. This is a breaking change since previous default encoding was UTF-8.

* Update unit tests.

* Update podspec to 8.1.
  • Loading branch information
pixelglow committed Nov 7, 2015
1 parent 3e345d6 commit fe86f85
Show file tree
Hide file tree
Showing 14 changed files with 76 additions and 45 deletions.
7 changes: 1 addition & 6 deletions ZipZap/ZZArchive.mm
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,6 @@ - (BOOL)loadCanMiss:(BOOL)canMiss error:(out NSError**)error;
@implementation ZZArchive
{
id<ZZChannel> _channel;
NSStringEncoding _encoding;
}

+ (instancetype)archiveWithURL:(NSURL*)URL
Expand Down Expand Up @@ -80,9 +79,6 @@ - (instancetype)initWithChannel:(id<ZZChannel>)channel
{
_channel = channel;

NSNumber* encoding = options[ZZOpenOptionsEncodingKey];
_encoding = encoding ? encoding.unsignedIntegerValue : NSUTF8StringEncoding;

NSNumber* createIfMissing = options[ZZOpenOptionsCreateIfMissingKey];
if (![self loadCanMiss:createIfMissing.boolValue error:error])
return nil;
Expand Down Expand Up @@ -164,8 +160,7 @@ - (BOOL)loadCanMiss:(BOOL)canMiss error:(out NSError**)error
+ nextCentralFileHeader->relativeOffsetOfLocalHeader);

[entries addObject:[[ZZOldArchiveEntry alloc] initWithCentralFileHeader:nextCentralFileHeader
localFileHeader:nextLocalFileHeader
encoding:_encoding]];
localFileHeader:nextLocalFileHeader]];

nextCentralFileHeader = nextCentralFileHeader->nextCentralFileHeader();
}
Expand Down
14 changes: 13 additions & 1 deletion ZipZap/ZZArchiveEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (readonly, nonatomic) mode_t fileMode;

/**
* The file name of the entry.
* The file name of the entry. Encoding is either CP-437 or UTF-8, depending on the EFS flag of the general purpose bit flags.
*/
@property (readonly, nonatomic) NSString* fileName;

Expand All @@ -92,6 +92,11 @@ NS_ASSUME_NONNULL_BEGIN
*/
@property (readonly, nonatomic) NSData* rawFileName;

/**
* The encoding of filename and comment fields, according to the EFS flag of the general purpose bit flags.
*/
@property (readonly, nonatomic) NSStringEncoding encoding;

/**
* Creates a new file entry from a streaming callback.
*
Expand Down Expand Up @@ -169,6 +174,13 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (BOOL)check:(out NSError**)error;

/**
* The file name of the entry with the given encoding.
*
* @param encoding The encoding for the filename.
*/
- (NSString*)fileNameWithEncoding:(NSStringEncoding)encoding;

/**
* Creates a stream to represent the entry file.
*
Expand Down
12 changes: 11 additions & 1 deletion ZipZap/ZZArchiveEntry.m
Original file line number Diff line number Diff line change
Expand Up @@ -117,14 +117,19 @@ - (mode_t)fileMode

- (NSString*)fileName
{
return nil;
return [self fileNameWithEncoding:self.encoding];
}

- (NSData*)rawFileName
{
return nil;
}

- (NSStringEncoding)encoding
{
return (NSStringEncoding)0;
}

- (NSInputStream*)newStreamWithError:(NSError**)error
{
return [self newStreamWithPassword:nil error:error];
Expand All @@ -140,6 +145,11 @@ - (BOOL)check:(NSError**)error
return YES;
}

- (NSString*)fileNameWithEncoding:(NSStringEncoding)encoding
{
return nil;
}

- (NSData*)newDataWithError:(NSError**)error
{
return [self newDataWithPassword:nil error:error];
Expand Down
5 changes: 0 additions & 5 deletions ZipZap/ZZConstants.h
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,6 @@ typedef NS_ENUM(uint8_t, ZZAESEncryptionStrength)
ZZAESEncryptionStrength256 = 0x03
};

/**
* An NSNumber object that contains the string encoding to use for entry file names and comments. Default is NSUTF8StringEncoding.
*/
extern NSString* const ZZOpenOptionsEncodingKey;

/**
* An NSNumber object that determines whether to create the archive file if it is missing. Creation occurs during -[ZZArchive updateEntries:error:]. Default is @NO.
*/
Expand Down
2 changes: 0 additions & 2 deletions ZipZap/ZZConstants.m
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,4 @@

#import "ZZConstants.h"

NSString* const ZZOpenOptionsEncodingKey = @"ZZOpenOptionsEncodingKey";

NSString* const ZZOpenOptionsCreateIfMissingKey = @"ZZOpenOptionsCreateIfMissingKey";
2 changes: 1 addition & 1 deletion ZipZap/ZZHeaders.h
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ enum class ZZGeneralPurposeBitFlag: uint16_t
superFastCompression = (1 << 1) | (1 << 2),
sizeInDataDescriptor = 1 << 3,
encryptionStrong = 1 << 6,
fileNameUTF8Encoded = 1 << 11
languageEncoding = 1 << 11
};

inline ZZGeneralPurposeBitFlag operator|(ZZGeneralPurposeBitFlag lhs, ZZGeneralPurposeBitFlag rhs)
Expand Down
5 changes: 4 additions & 1 deletion ZipZap/ZZNewArchiveEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
@property (readonly, nonatomic) BOOL compressed;
@property (readonly, nonatomic) NSDate* lastModified;
@property (readonly, nonatomic) mode_t fileMode;
@property (readonly, nonatomic) NSString* fileName;
@property (readonly, nonatomic) NSData* rawFileName;
@property (readonly, nonatomic) NSStringEncoding encoding;

- (instancetype)init NS_UNAVAILABLE;

Expand All @@ -35,6 +36,8 @@ NS_ASSUME_NONNULL_BEGIN
streamBlock:(nullable BOOL(^)(NSOutputStream* stream, NSError** error))streamBlock
dataConsumerBlock:(nullable BOOL(^)(CGDataConsumerRef dataConsumer, NSError** error))dataConsumerBlock NS_DESIGNATED_INITIALIZER;

- (NSString*)fileNameWithEncoding:(NSStringEncoding)encoding;

@end

NS_ASSUME_NONNULL_END
14 changes: 12 additions & 2 deletions ZipZap/ZZNewArchiveEntry.mm
Original file line number Diff line number Diff line change
Expand Up @@ -56,9 +56,14 @@ - (mode_t)fileMode
return _fileMode;
}

- (NSString*)fileName
- (NSData*)rawFileName
{
return _fileName;
return [_fileName dataUsingEncoding:NSUTF8StringEncoding];
}

- (NSStringEncoding)encoding
{
return NSUTF8StringEncoding;
}

- (id<ZZArchiveEntryWriter>)newWriterCanSkipLocalFile:(BOOL)canSkipLocalFile
Expand All @@ -72,5 +77,10 @@ - (NSString*)fileName
dataConsumerBlock:_dataConsumerBlock];
}

- (NSString*)fileNameWithEncoding:(NSStringEncoding)encoding
{
return _fileName;
}

@end

2 changes: 1 addition & 1 deletion ZipZap/ZZNewArchiveEntryWriter.mm
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ - (instancetype)initWithFileName:(NSString*)fileName
compressionFlag = ZZGeneralPurposeBitFlag::maximumCompression;
break;
}
centralFileHeader->generalPurposeBitFlag = localFileHeader->generalPurposeBitFlag = compressionFlag | ZZGeneralPurposeBitFlag::sizeInDataDescriptor | ZZGeneralPurposeBitFlag::fileNameUTF8Encoded;
centralFileHeader->generalPurposeBitFlag = localFileHeader->generalPurposeBitFlag = compressionFlag | ZZGeneralPurposeBitFlag::sizeInDataDescriptor | ZZGeneralPurposeBitFlag::languageEncoding;

centralFileHeader->compressionMethod = localFileHeader->compressionMethod = compressionLevel ? ZZCompressionMethod::deflated : ZZCompressionMethod::stored;

Expand Down
7 changes: 4 additions & 3 deletions ZipZap/ZZOldArchiveEntry.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,15 @@ NS_ASSUME_NONNULL_BEGIN
@property (readonly, nonatomic) NSUInteger compressedSize;
@property (readonly, nonatomic) NSUInteger uncompressedSize;
@property (readonly, nonatomic) mode_t fileMode;
@property (readonly, nonatomic) NSString* fileName;
@property (readonly, nonatomic) NSData* rawFileName;
@property (readonly, nonatomic) NSStringEncoding encoding;

- (instancetype)init NS_UNAVAILABLE;

- (instancetype)initWithCentralFileHeader:(struct ZZCentralFileHeader*)centralFileHeader
localFileHeader:(struct ZZLocalFileHeader*)localFileHeader
encoding:(NSStringEncoding)encoding NS_DESIGNATED_INITIALIZER;
localFileHeader:(struct ZZLocalFileHeader*)localFileHeader NS_DESIGNATED_INITIALIZER;

- (NSString*)fileNameWithEncoding:(NSStringEncoding)encoding;

@end

Expand Down
29 changes: 13 additions & 16 deletions ZipZap/ZZOldArchiveEntry.mm
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
@interface ZZOldArchiveEntry ()

- (NSData*)fileData;
- (NSString*)stringWithBytes:(uint8_t*)bytes length:(NSUInteger)length;

- (BOOL)checkEncryptionAndCompression:(out NSError**)error;
- (NSInputStream*)streamForData:(NSData*)data withPassword:(NSString*)password error:(out NSError**)error;
Expand All @@ -35,19 +34,16 @@ @implementation ZZOldArchiveEntry
{
ZZCentralFileHeader* _centralFileHeader;
ZZLocalFileHeader* _localFileHeader;
NSStringEncoding _encoding;
ZZEncryptionMode _encryptionMode;
}

- (instancetype)initWithCentralFileHeader:(struct ZZCentralFileHeader*)centralFileHeader
localFileHeader:(struct ZZLocalFileHeader*)localFileHeader
encoding:(NSStringEncoding)encoding
{
if ((self = [super init]))
{
_centralFileHeader = centralFileHeader;
_localFileHeader = localFileHeader;
_encoding = encoding;

if ((_centralFileHeader->generalPurposeBitFlag & ZZGeneralPurposeBitFlag::encrypted) != ZZGeneralPurposeBitFlag::none)
{
Expand Down Expand Up @@ -91,14 +87,6 @@ - (NSData*)fileData
return [NSData dataWithBytesNoCopy:dataStart length:dataLength freeWhenDone:NO];
}

- (NSString*)stringWithBytes:(uint8_t*)bytes length:(NSUInteger)length
{
// if EFS bit is set, use UTF-8; otherwise use fallback encoding
return [[NSString alloc] initWithBytes:bytes
length:length
encoding:(_centralFileHeader->generalPurposeBitFlag & ZZGeneralPurposeBitFlag::fileNameUTF8Encoded) == ZZGeneralPurposeBitFlag::none ? _encoding :NSUTF8StringEncoding];
}

- (ZZCompressionMethod)compressionMethod
{
if (_encryptionMode == ZZEncryptionModeWinZipAES)
Expand Down Expand Up @@ -169,15 +157,17 @@ - (mode_t)fileMode
}
}

- (NSString*)fileName
- (NSData*)rawFileName
{
return [self stringWithBytes:_centralFileHeader->fileName()
return [NSData dataWithBytes:_centralFileHeader->fileName()
length:_centralFileHeader->fileNameLength];
}

- (NSData*)rawFileName
- (NSStringEncoding)encoding
{
return [NSData dataWithBytes:_centralFileHeader->fileName() length:_centralFileHeader->fileNameLength];
return (_centralFileHeader->generalPurposeBitFlag & ZZGeneralPurposeBitFlag::languageEncoding) == ZZGeneralPurposeBitFlag::none ?
CFStringConvertEncodingToNSStringEncoding(kCFStringEncodingDOSLatinUS) : // CP-437
NSUTF8StringEncoding; // UTF-8
}

- (BOOL)check:(out NSError**)error
Expand Down Expand Up @@ -258,6 +248,13 @@ - (BOOL)check:(out NSError**)error
return YES;
}

- (NSString*)fileNameWithEncoding:(NSStringEncoding)encoding
{
return [[NSString alloc] initWithBytes:_centralFileHeader->fileName()
length:_centralFileHeader->fileNameLength
encoding:encoding];
}

- (BOOL)checkEncryptionAndCompression:(out NSError**)error
{
switch (_encryptionMode)
Expand Down
9 changes: 7 additions & 2 deletions ZipZapTests/ZZUnzipTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -138,11 +138,16 @@ - (void)testZipEntryConsistentWithOriginalFile
ZZArchiveEntry* nextEntry = _zipFile.entries[index];
NSString* nextEntryFilePath = _entryFilePaths[index];

XCTAssertEqualObjects(nextEntry.fileName,
XCTAssertEqualObjects([nextEntry fileNameWithEncoding:NSUTF8StringEncoding],
nextEntryFilePath,
@"zipFile.entries[%lu].fileName must match the original file name.",
(unsigned long)index);


XCTAssertEqualObjects([nextEntry rawFileName],
[nextEntryFilePath dataUsingEncoding:NSUTF8StringEncoding],
@"zipFile.entries[%lu].rawFileName must match the original raw file name.",
(unsigned long)index);

NSData* fileData = [self dataAtFilePath:nextEntryFilePath];
XCTAssertEqual(nextEntry.crc32,
crc32(0, (const Bytef*)fileData.bytes, (uInt)fileData.length),
Expand Down
9 changes: 7 additions & 2 deletions ZipZapTests/ZZZipTests.m
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@ - (NSArray*)recordsForZipEntries:(NSArray*)zipEntries
@"fileMode": @(zipEntry.fileMode),
@"compressed": @(zipEntry.compressed),
@"lastModified": zipEntry.lastModified,
@"fileName": zipEntry.fileName
@"fileName": [zipEntry fileNameWithEncoding:NSUTF8StringEncoding],
@"rawFileName": zipEntry.rawFileName
}];
return records;
}
Expand Down Expand Up @@ -124,7 +125,11 @@ - (void)checkZipEntryRecords:(NSArray*)newEntryRecords
nextNewEntry[@"fileName"],
@"Zip entry #%lu file name must match new entry file name.",
(unsigned long)index);


XCTAssertEqualObjects([nextZipInfo[8] dataUsingEncoding:NSUTF8StringEncoding],
nextNewEntry[@"rawFileName"],
@"Zip entry #%lu raw file name must match new entry raw file name.",
(unsigned long)index);

if (checkerBlock)
{
Expand Down
4 changes: 2 additions & 2 deletions zipzap.podspec.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
"name": "zipzap",
"version": "8.0.6",
"version": "8.1",
"authors": {
"Pixelglow Software": "[email protected]"
},
"license": "BSD",
"homepage": "https://github.com/pixelglow/ZipZap",
"source": {
"git": "https://github.com/pixelglow/ZipZap.git",
"tag": "8.0.6"
"tag": "8.1"
},
"summary": "ZipZap is a zip file I/O library for Mac OS X and iOS.",
"description": "The zip file is an ideal container for compound Objective-C documents. Zip files are widely used and well understood. You can randomly access their parts. The format compresses decently and has extensive operating system and tool support. So we want to make this format an even easier choice for you. Thus, the library features:\n\n- Easy-to-use interface: The public API offers just three classes! Yet you can look through zip files using familiar NSArray collections and properties. And you can zip, unzip and rezip zip files through familiar NSData, NSStream and Image I/O classes.\n- Efficient implementation: We've optimized zip file reading and writing to reduce virtual memory pressure and disk file thrashing. Depending on how your compound document is organized, updating a single entry can be faster than writing the same data to a separate file.\n- File format compatibility: Since ZipZap closely follows the zip file format specification, it is works with most Mac, Linux and Windows zip tools.\n",
Expand Down

0 comments on commit fe86f85

Please sign in to comment.