How can I determine if a file is a zip file? - ios

I need to determine if a file in my app's documents directory is a zip file. The file name cannot be used in making this determination. So I will need to be able read the MIME type or find some other property that only applies to zips.
NOTE: A solution that requires putting the entire file into memory is not ideal as files could potentially be pretty large.

According to http://www.pkware.com/documents/casestudies/APPNOTE.TXT,
a ZIP file starts with the "local file header signature"
0x50, 0x4b, 0x03, 0x04
so it is sufficient to read the first 4 bytes to check if the file is possibly a ZIP file.
A definite decision can only be made if you actually try to extract the file.
There are many methods to read the first 4 bytes of a file. You can use NSFileHandle,
NSInputStream, open/read/close, ... . So this should only be taken as one possible example:
NSFileHandle *fh = [NSFileHandle fileHandleForReadingAtPath:#"/path/to/file"];
NSData *data = [fh readDataOfLength:4];
if ([data length] == 4) {
const char *bytes = [data bytes];
if (bytes[0] == 'P' && bytes[1] == 'K' && bytes[2] == 3 && bytes[3] == 4) {
// File starts with ZIP magic ...
}
}
Swift 4 version:
if let fh = FileHandle(forReadingAtPath: "/path/to/file") {
let data = fh.readData(ofLength: 4)
if data.starts(with: [0x50, 0x4b, 0x03, 0x04]) {
// File starts with ZIP magic ...
}
fh.closeFile()
}

Try this
NSWorkspace *ws = [NSWorkspace sharedWorkspace];
NSString *description = [ws localizedDescriptionForType:[ws typeOfFile:#"/full/path/to/file" error:nil]];
Or for mime this
+ (NSString*) mimeTypeForFileAtPath: (NSString *) path {
if (![[NSFileManager defaultManager] fileExistsAtPath:path]) {
return nil;
}
CFStringRef UTI = UTTypeCreatePreferredIdentifierForTag(kUTTagClassFilenameExtension, (CFStringRef)[path pathExtension], NULL);
CFStringRef mimeType = UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType);
CFRelease(UTI);
if (!mimeType) {
return #"application/octet-stream";
}
return [NSMakeCollectable((NSString *)mimeType) autorelease];
}

I'd just use file, then grep if it has the text "zip" or "Zip Archive" to be safe.
if file -q $FILENAME | grep "Zip archive"; then
echo "zip";
else
echo "not zip";
fi

Since both .zip and .xlsx having the same Magic number, I couldn't find the valid zip file (if renamed).
So, I have used Apache Tika to find the exact document type.
Even if renamed the file type as zip, it finds the exact type.
Reference Apache tika use cases

Related

Open file in .bundle using fopen()

I have built a custom framework and accompanying resource bundle to be used in other projects. The resource bundle includes various .sqlite and .bin files. I am trying to open a .bin file in another project using my framework with no success.
Let's say my bundle is called CustomFramework.bundle. I have a class ResourceHelper.cpp within my framework that is attempting to open mybin.bin that is located in the CustomFramework.bundle.
Here is how I am currently trying to open it:
void ResourceHelper::openBinFromResourceFolder(FILE **file) {
std::string path;
path = "CustomFramework.bundle/";
path.append("mybin.bin");
*file = fopen(path.c_str(), "rb");
}
file is NULL after this fopen() call.
How am i suppose to open a .bin file within my .bundle?
Thanks
Turns out you have to get the main bundle string and append the resource bundle to that string. Here is how I got it working.
void ResourceHelper::openBinFromResourceFolder(const char *binName, FILE **file) {
std::string path;
// split bin name into name and file type (bin)
std::string binStr = binName;
std::size_t pos = binStr.find(".");
std::string filename = binStr.substr(0, pos);
std::string type = binStr.substr(pos+1);
// get bundle and CFStrings
CFBundleRef mainBundle = CFBundleGetMainBundle();
CFStringRef cf_resource_path = CFStringCreateWithCString(NULL, resourcePath_.c_str(), kCFStringEncodingUTF8);
CFStringRef cf_filename = CFStringCreateWithCString(NULL, filename.c_str(), kCFStringEncodingUTF8);
CFStringRef cf_file_type = CFStringCreateWithCString(NULL, type.c_str(), kCFStringEncodingUTF8);
CFURLRef url_resource = CFBundleCopyResourceURL(mainBundle, cf_filename, cf_file_type, cf_resource_path);
CFStringRef urlString = CFURLCopyFileSystemPath(url_resource, kCFURLPOSIXPathStyle);
path = CFStringGetCStringPtr(urlString, kCFStringEncodingUTF8);
*file = fopen(path.c_str(), "rb");
}

Can't read custom file from NSInputStream

I am trying to read a custom file from my documents directory via NSInputStream to upload it to a FTP server. I'm using the exact same code as demonstrated in the SimpleFTPSample provided by Apple. It seems to work fine as long as the file is empty, but as soon as it contains data it fails.
Here's my code. This is how I create the input stream, I tried it both ways:
//Approach one: Init with path
self.fileStream = [NSInputStream inputStreamWithFileAtPath:filePath];
//Approach two: Init with data
NSData* fileData = [NSData dataWithContentsOfFile:filePath];
self.fileStream = [NSInputStream inputStreamWithData:fileData];
If I init the stream with data, I get EXC_BAD_ACCESS (code=1, address=0x0) when invoking read on the stream (code snipped below), if I use the path it jumps right to File read error.
filePath is #"/var/mobile/Applications/94292A2A-37FC-4D8E-BDA6-A26963932AE6/Documents/1395576645.cpn", NSData returns properly and has 806 bytes.
That's part of the stream:handleEvent: delegate method:
if (self.bufferOffset == self.bufferLimit) {
NSInteger bytesRead;
bytesRead = [self.fileStream read: self.buffer maxLength: kSendBufferSize];
if (bytesRead == -1) {
if (kPrintsToConsole) NSLog(#"File read error");
} else if (bytesRead == 0) {
[self stopSending];
} else {
self.bufferOffset = 0;
self.bufferLimit = bytesRead;
}
}
I'm kinda stuck. Hope you guys can help me out. Running iOS 7.1 & Xcode 5.1
you need to call [self.fileStream open] before read. For both file and data approaches.

Problems accessing a file on iOS device

I have a Problem with that code written in Object c++ and Xcode 5:
NSLog(#"%#",pathToFile);
NSString *outputFileName = [NSTemporaryDirectory() stringByAppendingPathComponent:#"output.txt"];
FILE* fileo = fopen([outputFileName UTF8String], "w");
if (fileo) fclose(fileo);
fileo = fopen([outputFileName UTF8String], "a");
if (!fileo) { perror("fopen"); NSLog(#" Wrong"); } else { NSLog(#"Nothing Wrong"); }
//Open the PDF source file:
// FILE* filei = fopen("c:\\pdf\\somepdf.pdf", "rb");
FILE* filei = fopen([pathToFile UTF8String], "rb");
if (!filei) { perror("fopen"); NSLog(#" Wrong"); } else { NSLog(#"Nothing Wrong"); }
if (filei && fileo)
{
the Problem is that filei is every time Nil and I down't know why,!
I think the Problem is the pathtoFile:
it is: pathToFile
__NSCFString * #"file:///private/var/mobile/Applications/3D02CBE0-6A8E-4709-B6EF-B6793E19F4F4/Documents/Inbox/Published_071032-8.pdf"
0x145c53e0
The code works great on Simulator but not on the iPhone!
Has someone an idea whats wrong?
Working Directory maybe but there is no way to change it in xcode 5.
Yes seems like the Path of your file is the problem. Im even sure priced that you saw something like #"file://..." because the unix file system is based on the path directly e.g /var/mobile/.../
The next I would recommend you is to check where you define the pathToFile string. Maybe it is already out of scope because it was created on the stack because you create it within the same function/method.
Thats maybe the reason why it is NULL/ nil.
maybe a solution: create the path based on NSBundle class + don't use file:// -paths.
Let me know if it works or any additional help is required.
Thank you
regards

encrypt or sign string with public key in iOS with Objective C

i have a private key. Text File that begins like "--- begin private key..."
i want to use that key to encrypt an NSString. since its the private key, better call it sign an NSString.
can this be done without any external frameworks?
the result should be equivalent to the php openssl_sign function.
The iOS SDK framework you will need to use is called CommonCrypto. Here's a very good article that describes the right way to go about it.
Edit: I missed the part about compatibility with the PHP function openssl_sign. The solution below resolves that.
The way to do this so that it's compatible with the PHP function openssl_sign is to use the OpenSSL library. The openssl_sign function uses OpenSSL's EVP API internally to encrypt the input string using the private key and compute the SHA-1 hash digest of that encrypted string. It's common then to convert this hash digest into a Base64-encoded string.
Unfortunately, the iOS SDK does not include OpenSSL, but it's easy to build it. The following instructions for building OpenSSL for iOS are taken from this blog post and are reproduced here to provide a complete solution to the question.
In Terminal, follow those steps to build the OpenSSL library for iOS:
# Make a directory in which to run the build
mkdir ~/openssl-ios
cd ~/openssl-ios
# Download the openssl source (verify the file before using it in production!)
curl -O http://www.openssl.org/source/openssl-1.0.1e.tar.gz
# Download the openssl iOS build script
curl -O https://raw.github.com/Raphaelios/raphaelios-scripts/master/openssl/build-openssl.sh
# Make the build script executable
chmod +x build-openssl.sh
# Run the script (takes about 3min on an Intel Core i5)
./build-openssl.sh
This will take a few minutes but once it's complete you can verify that the build library is a universal library that you can use on iOS devices and in the iOS Simulator using the following command:
lipo -info ~/openssl-ios/lib/*.a
Now that the OpenSSL library has been built, let's got on with writing the code to sign a string.
First, we need to setup the Xcode project to link against the OpenSSL library. Drag & drop both libcrypto.a and libssl.a to the Frameworks group in the Project Navigator of your iOS project. In your project's Build Settings, add the following to the Header Search Paths setting:
~/openssl-ios/include/include
Next, create a new Objective-C Category file called openssl_sign on the NSString class. In NSString+openssl_sign.h, define the following interface:
#interface NSString (openssl_sign)
- (NSString *)signStringWithPrivateKey:(NSData *)privateKey;
#end
In NSString+openssl_sign.m, add the following header imports:
#import <openssl/evp.h>
#import <openssl/pem.h>
And add the following implementation of signStringWithPrivateKey::
#implementation NSString (openssl_sign)
- (NSString *)signStringWithPrivateKey:(NSData *)privateKeyData
{
BIO *publicBIO = NULL;
EVP_PKEY *privateKey = NULL;
if ((publicBIO = BIO_new_mem_buf((unsigned char *)[privateKeyData bytes], [privateKeyData length])) == NO) {
NSLog(#"BIO_new_mem_buf() failed!");
return nil;
}
if (PEM_read_bio_PrivateKey(publicBIO, &privateKey, NULL, NULL) == NO) {
NSLog(#"PEM_read_bio_PrivateKey() failed!");
return nil;
}
const char * cString = [self cStringUsingEncoding:NSUTF8StringEncoding];
unsigned int stringLength = [self length];
unsigned char * signatureBuffer[EVP_MAX_MD_SIZE];
int signatureLength;
EVP_MD_CTX msgDigestContext;
const EVP_MD * msgDigest = EVP_sha1();
EVP_MD_CTX_init(&msgDigestContext);
EVP_SignInit(&msgDigestContext, msgDigest);
EVP_SignUpdate(&msgDigestContext, cString, stringLength);
if (EVP_SignFinal(&msgDigestContext, (unsigned char *)signatureBuffer, (unsigned int *)&signatureLength, privateKey) == NO) {
NSLog(#"Failed to sign string.");
return nil;
}
EVP_MD_CTX_cleanup(&msgDigestContext);
EVP_PKEY_free(privateKey);
NSData *signatureData = [NSData dataWithBytes:signatureBuffer length:signatureLength];
NSString *signature = [signatureData base64EncodedStringWithOptions:0];
return signature;
}
#end
In the class that will be signing the string, you can now import NSString+openssl_sign.h and sign the string like so:
NSData *privateKey = ...; // Read the .pem file into a NSData variable
NSString *helloSignature = [#"hello" signStringWithPrivateKey:privateKey];
You can verify that the signatures are the same using the following command in Terminal:
echo -n "hello" | openssl dgst -sha1 -sign priv.pem | openssl enc -base64 | tr -d '\n'
You can solve this much easier with no external sources or components.
I found out how and wanted to share it so i may help others.
You need to load the key file a SecKeyRef and safe the maxPlainLen as well
NSString *resourcePath = [[NSBundle mainBundle] pathForResource:privateKeyResourceName ofType:#"p12"];
NSData *p12Data = [NSData dataWithContentsOfFile:resourcePath];
NSMutableDictionary * options = [[NSMutableDictionary alloc] init];
SecKeyRef privateKeyRef = NULL;
//change to the actual password you used here
[options setObject:#"_YOURPASSWORDHERE__" forKey:(__bridge id)kSecImportExportPassphrase];
CFArrayRef items = CFArrayCreate(NULL, 0, 0, NULL);
OSStatus securityError = SecPKCS12Import((__bridge CFDataRef) p12Data,
(__bridge CFDictionaryRef)options, &items);
if (securityError == noErr && CFArrayGetCount(items) > 0) {
CFDictionaryRef identityDict = CFArrayGetValueAtIndex(items, 0);
SecIdentityRef identityApp =
(SecIdentityRef)CFDictionaryGetValue(identityDict,
kSecImportItemIdentity);
securityError = SecIdentityCopyPrivateKey(identityApp, &privateKeyRef);
if (securityError != noErr) {
privateKeyRef = NULL;
}
}
CFRelease(items);
privateKey = privateKeyRef;
maxPlainLen = SecKeyGetBlockSize(privateKey) - 12;
You can convert NSString with a category method to SHA1
- (NSData*)toSha1AsData {
// PHP uses ASCII encoding, not UTF
const char *s = [self cStringUsingEncoding:NSASCIIStringEncoding];
NSData *keyData = [NSData dataWithBytes:s length:strlen(s)];
// This is the destination
uint8_t digest[CC_SHA1_DIGEST_LENGTH] = {0};
// This one function does an unkeyed SHA1 hash of your hash data
CC_SHA1(keyData.bytes, keyData.length, digest);
// Now convert to NSData structure to make it usable again
NSData *out = [NSData dataWithBytes:digest length:CC_SHA1_DIGEST_LENGTH]
return out;
}
Now you can sign your SHA1 with this method
(NSData *)signSha1Data:(NSData *)data {
size_t plainLen = [data length];
if (plainLen > maxPlainLen)
{
NSLog(#"content(%ld) is too long, must < %ld", plainLen, maxPlainLen);
return nil;
}
void *plain = malloc(plainLen);
[data getBytes:plain
length:plainLen];
size_t cipherLen = 128; // currently RSA key length is set to 128 bytes
void *cipher = malloc(cipherLen);
OSStatus returnCode = SecKeyRawSign(privateKey, kSecPaddingPKCS1SHA1,
plain, plainLen, cipher, &cipherLen);
NSData *result = nil;
if (returnCode != 0) {
NSLog(#"SecKeyEncrypt fail. Error Code: %ld", returnCode);
}
else {
result = [NSData dataWithBytes:cipher
length:cipherLen];
}
free(plain);
free(cipher);
return result;
}
It works very well and without any external libs. There is no need to compile some wierd openssl stuff.

tmp directory path inside 'C' library

My cocoa application uses one library written in 'C' which is tryings write file at '/tmp' path. This creates sandbox violations. In Cocoa we can use 'NSTemporaryDirectory' API. To fix sandbox violation Is it safe to use 'tmpfile' API in sandboxed environment? Are there in any other solutions?
EDITED After actually testing it
No, tmpnam() won't work and I think the only way to get a temporary filename is to provide a .m file with your library specifically for use with iOS and OSX, which can be used return the temporary directory as a C-String:
apple.h:
#pragma once
extern size_t getTemporaryDirectory(char *buffer, size_t len);
apple.m:
size_t getTemporaryDirectory(char *buffer, size_t len)
{
#autoreleasepool
{
NSString *tempDir = NSTemporaryDirectory();
if (tempDir != nil)
{
const char *utf = [tempDir UTF8String];
strncpy(buffer, utf, len);
return strlen(utf);
}
}
return 0;
}

Resources