Display encrypted file using QuickLook framework or UiDocumentInteractionController - iphone

I have an encrypted word/excel/pdf file locally stored which I need to preview in my iPad app. I understand that QLPreviewController or UiDocumentInteractionController could be used to preview these files. I can very well use this
- (id <QLPreviewItem>) previewController: (QLPreviewController *) controller previewItemAtIndex: (NSInteger) index {
return [NSURL fileURLWithPath:[[NSBundle mainBundle] pathForResource:[documents objectAtIndex:index] ofType:nil]];
}
But the file is encrypted and when I decrypt it I would get hold of NSData object. How do I go about loading NSData in either of these.
Also I understand that I can very well store the NSData back as a local file and load it in Preview. But there is a constraint of not storing the unencrypted file locally.
If someone has already accomplished this and can help me out here it will be greatly appreciated.
Thanks
AJ

Since you are using Quick Look, your options are limited. You must give Quick Look an NSURL, which means it must be on the file system (or the Internet). Fortunately, this shouldn't be much of a problem. iOS devices use hardware-level encryption. When your file is encrypted, only your app has the key to decrypt it. So, your file will still be encrypted, but it will also be readable by your app and only your app.
Here's what you do:
Decrypt your file into an NSData object, which you've already done.
Write the file to a location that will not get uploaded to iCloud nor backed up by iTunes. The tmp directory is probably the best choice. The code looks something like this:
NSData * data = // Your decrypted file data.
NSString * fileName = // Whatever you want to name your file.
NSString * path = [NSTemporaryDirectory() stringByAppendingPathComponent:fileName];
NSURL * url = [NSURL URLWithString:path];
NSError * error = nil;
BOOL success = [data writeToURL:url
options:NSDataWritingFileProtectionComplete
error:&error];
if (success) {
// Give the URL to Quick Look.
}
else {
// An error happened. See the 'error' object for the details.
}
At this point you have an NSURL which you can use with Quick Look. Don't forget to delete the decrypted file when you are done with it.
There are a few things to note about on-disk encryption:
It is only supported on iOS 4.0+.
It may not work on "older" devices.
The user must have an active passcode.
If you use NSDataWritingFileProtectionComplete, the file is not accessible while the device is locked. If you need to access the file while the app is locked, then you should use NSDataWritingFileProtectionCompleteUnlessOpen or NSFileProtectionCompleteUntilFirstUserAuthentication instead. This will still give you great protection, even if the device is stolen and jailbroken. Be aware, though, that these encryption options are only available on iOS 5.0+
For more details for on-disk encryption, check out the iOS App Programming Guide

After doing some digging, I found out that QLPreviewController is using UIWebView underneath, and calls the loadRequest: to load the requested file.
Another way to accomplish what you desire is to make a private Category on UIWebView,
and use method swizzling to override the loadRequest: method, and call instead the loadData:MIMEType:textEncodingName:baseURL: method.
Beware that:
1) In low-memory scenarios (i.e. large files) a black screen with
"Error to load the document" appears, if that concerns you. (The
unhacked QLPreviewController knows how to handle these scenarios
very well and present the document).
2) I'm not sure Apple are going
to approve this kind of hack, although no private APIs are used
here.
code:
#implementation UIWebView (QLHack)
- (void)MyloadRequest:(NSURLRequest *)request
{
// Check somehow that it's the call of your QLPreviewController
// If not, just call the original method.
if (!insideQLPreviewController)
{
// Call original implementation
[self MyloadRequest:request];
}
else
{
// Load the real data you want
[self loadData:data MIMEType:mimeType textEncodingName:nil baseURL:someURL];
}
}
+ (void)load
{
method_exchangeImplementations(class_getInstanceMethod(self, #selector(loadRequest:)), class_getInstanceMethod(self, #selector(MyloadRequest:)));
}
#end

Actually, writing a file to a tmp directory is still insecure. The other alternative is to use UIWebView with NSURLProtocol and allow decrypting this data on the fly.

One way could be.
use Temp Dir , Save File in Temp , Make NSURL From that Temp File and Display and then Delete that temp Dir after that.
Thanks.

Related

Handling of temporary files with UIDocumentInteractionController

I'm using the UIDocumentInteractionController with a temporary file that resides in my cache after having downloaded it. I'm using a quite simple class that delivers md5-cache file names (ext = cache) to my app and the downloaded file is in this format. The reason is to have files locally and to only download them once (a session). Since the cache names are in a uniform format I can easily clean them up.
Now with UIDocumentInteractionController I need to rename these files back to their original name or they will not get recognized correctly.
When the UIDocumentInteractionController finishes handing off the file I thought to move the file back to its cache file name. The problem is, the method: - documentInteractionController:didEndSendingToApplication: never gets called - though the delegate is set correctly.
How I basically set up the controller:
interactionController = [UIDocumentInteractionController interactionControllerWithURL:[NSURL fileURLWithPath:temporaryFile]];
interactionController.delegate = self;
interactionController.annotation = [cacheURLString lastPathComponent]; // original name to move back to
Any suggestions on how to correctly find out that a file has been handed over to another application / or the UIDocumentInteractionController has been dismissed?
I found that documentInteractionController:didEndSendingToApplication: is called if the document is sent to another application, but isn't called when sending the document via email (and possibly other built in functions like AirDrop, copy, print, etc.). This seems like a bug to me, but there it is.
Tony's answer in the comments worked for me - use [[NSFileManager defaultManager] linkItemAtURL:toURL:error] to link the source file to a temporary file and pass the temporary file to the controller. This isn't taking up a lot of additional space and the temporary link will be removed after a certain amount of time.

Can't write on plist file after deploying on iPhone

Dear all.
I am facing a problem.
I can read and write on plist while working on simulator of xcode. but when I deploy the app in iPhone, i can't write on plists.
I have created a sample project having 2 button on it. By one button, I can display the text from plist. By second button, I try to write on that plist. But the writing doesn't happen. The app doesn't crash while clicking on the second button. I can't understand the problem in my code.
/*code is given below*/
-(void)writePlist:(NSString *)fname withArray:(NSMutableArray *) myArray
{
NSString * path = nil;
path = [(NSString *) [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory,NSUserDomainMask, YES) objectAtIndex:0] stringByAppendingPathComponent:fname];
[myArray writeToFile:path atomically:NO];
}
It could be that myArray variable contains non-serializable object values (probably custom objects or something like that).
Does the directory you are trying to write to exist? You may want to verify its existence (and create it if it isn't already there) before trying to write a file into it.

Add String Attribute to CoreDataBooks

I've been messing around with CoreDataBooks and trying to add another field to the sqlite file in addition to the current three. I've added the attribute string in the .xcdatamodel, and declared it in the book.h, book.m, and localized file (all without changing the sqlite file). However, these changes never add the field in sqlite, and the app never loads. Each time I am deleting the app in the simulator and performing a build--> clean, but to no avail. I've also tried changing the sqlite file to match the .xcdatamodel but the app still fails to load.
Is this a problem with CoreDataBooks or me? Do I need to version the app before doing this? Doesn't seem like I should have to as long as I'm deleting the app in the simulator.
Anyone know how I can add this forth string attribute (sqlite field) in CoreDataBooks?
Thanks in advance!
I've found such a nice piece of code in CoreDataBooks
NSString *storePath = [[self applicationDocumentsDirectory] stringByAppendingPathComponent: #"CoreDataBooks.sqlite"];
/*
Set up the store.
For the sake of illustration, provide a pre-populated default store.
*/
NSFileManager *fileManager = [NSFileManager defaultManager];
// If the expected store doesn't exist, copy the default store.
if (![fileManager fileExistsAtPath:storePath]) {
NSString *defaultStorePath = [[NSBundle mainBundle] pathForResource:#"CoreDataBooks" ofType:#"sqlite"];
if (defaultStorePath) {
[fileManager copyItemAtPath:defaultStorePath toPath:storePath error:NULL];
}
}
in CoreDataBooksAppDelegate.m:154, this means for me that every time apps does not find sqlite file it is copied from bundle where it was not changed by modification of CoreDataBooks.xcdatamodel.
Try to use another approach, or just modify bundled sqlite file.
If the app doesn't load then there might be something wrong with your datamodel. Check the console for error messages.
And you have to decide if you want to use sqlite or core-data. If you want to monitor sqlite files for changes or add fields to sqlite files you should use sqlite and not core-data.
If you want to use core-data you should ignore the sqlite file.
Figured it out. The application WAS creating the new sqlite file, I just needed to find it within the simulator files. Grrrr, so much time wasted when I was doing everything correctly. Lessons learned I guess :)

Link to pdf within app

I've got an iOS app that at one point opens a link to a website in a webview. These links are kept in a plist file (so it is easy to maintain them as the app evolves). What I want to do next is to also link to PDF's (or any picture of text file format, of even a html format, this is flexible) that are kept within the app. And I would like to do this as much as possible from within the existing app structure. So, is it possible to create a link that can be put in the plist as a web-link, but instead opens a file on the device itself (possibly in the webview)? And how would I go about that? Any ideas?
Thanx in advance for your help!
You will need to create the links at runtime. I would suggest having a certain prefix to a local url, such as mylocalfile:filename. Then, in the code that loads the plist, check for the prefix and create the link when necessary. You could also just create these links once and store them in a separate file, then load that instead of the original.
NSArray *links = nil; //I assumed your plist is an array. Change to dictionary if required
NSString *pathToStoredFile; //Get the path for the file you create with the updated links
if([[NSFileManager defaultManager] fileExistsAtPath:pathToStoredFile]) {
links = [NSArray arrayWithContentsOfFile:pathToStoredFile];
} else {
NSArray *tmp = [NSArray arrayWithContentsOfFile:[[NSBundle mainBundle] pathForResource:#"ListOfLinks" ofType:#"plist"]];
if(!tmp) {
//handle error
}
NSMutableArray *tmpLinks = [[NSMutableArray alloc] initWithCapacity:[tmp count]];
for(NSString *link in tmp) {
if([link hasPrefix:#"mylocalfile:"]) {
link = [link substringFromIndex:12]; //12 is the length of mylocalfile:
NSURL *url = [[NSBundle mainBundle] urlForResource:[link stringByDeletingPathExtension] withExtension:[link pathExtension]];
[tmpLinks addObject:[url absoluteString]];
} else [tmpLinks addObject:link];
}
links = [tmpLinks copy];
[tmpLinks release];
[links writeToFile:pathToStoredFile atomically:NO];
}
Yes, I would go with a UIWebView. iOS should be able automatically handle certain URL handlers and your app can register to handle the rest, as necessary.
iOS knows how to handle certain file types already. For example, if Safari (or a UIWebView) encounters http://somesite.com/afile.pdf, it know which apps can handle the file type. Another example is a phone number: skype://8005555555. iOS knows to open Skype and pass the number to it. iOS also knows that iBooks can handle PDf files.
Register your app for the appropriate file handlers and types. Then, users can tap and hold on the link to see a menu of available apps to handle the link. If it's a link that's only used by one app, the user doesn't even need to hold, a tap will suffice.
As far as making a link pointing to a local file, you can, and you would use the C function NSDocumentsDirectory() and append that to a url handler. (Example: http://NSDocumentsDirectory()/filename.pdf)

Using a UIWebView with loadHTMLString/loadData breaks the back and forward buttons, workaround?

There's a known problem with embedded UIWebViews that if you load data into them using loadHTMLString or loadData, the canGoBack/canGoForward properties and goBack/goForward methods don't work. These only work when using loadRequest.
Since Safari's normal app cache doesn't work in embedded UIWebViews, creating a native app that effectively caches otherwise live content becomes impossible/unusable. That is, I can cache the contents of the HTML, Javascript, images, etc. and load them via loadHTMLString or loadData, but then the back and forward buttons don't work.
I could also use loadRequest and specify a file URL, but that breaks when it comes to communicating with the live site -- even if I specify a tag (because of cookie domain issues).
I have a work around that involves basically re-implementing the app cache using local store (and not having the native app do any caching itself), which is OK, but not really ideal. Are there any other work arounds/something I missed?
I am using the UIWebView's canGoBack to check to see if I'm at the first page of the history. If I am then I just call the little method I used to load the first page (displayLocalResource sets up the HTMLString and loads it into the webView). Here is a snippet:
//Implementing a back button
- (void)backOne:(id)sender{
if ([webView canGoBack]) {
// There's a valid webpage to go back to, so go there
[webView goBack];
} else {
// You've reached the end of the line, so reload your own data
[self displayLocalResource];
}
}
So do you download the HTML yourself, then pass it to UIWebView as a string? Why so? Do you modify it on the fly or something?
Maybe a custom URL schema would help? You use loadRequest with a schema of your own, which in turn works with HTTP and then feeds the webview whatever data you want?
I had a same problem. I tried manage the history, but it is error prone. Now I have discovered a better solution of this.
What you want to do is simply add a loadRequest to about:blank and make that as a placeholder for you before you call loadHTMLString/loadData. Then you are totally free from monitoring the history. The webview.canGoBack and canGoForward will just work. Of course, you will need a hack to handle go back to the placeholder about:blank. You can do that in webViewDidFinishLoad. Here is the code highlight:
In the function when you call loadHTMLString:
[weakSelf.fbWebView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"about:blank"]]];
[weakSelf.fbWebView loadHTMLString:stringResponse baseURL:url];
Code to handle goBack:
- (void)webViewDidFinishLoad:(UIWebView *)webView
{
if ([webView.request.URL.absoluteString isEqualToString:#"about:blank"]
&& ![webView canGoBack] && [webView canGoForward]) {
[weakSelf.fbWebView loadHTMLString:stringResponse baseURL:url];
}
}
I think it is also possible expand this solution to handle those loadHTMLString that is not the first load. Just by having a Stack to record all the string response and insert an about:blank on each loadHTMLString. And pop the stack when each time go back to about:blank.
Could you fetch the content, save it to the local filesystem, point the webview to the local filesystem using file:// URLs, then intercept the link follows with shouldStartLoadWithRequest to fetch more to local fs, point webview at new local content, etc?
I've had good luck with UIWebView and file:/// URLs. Basically you'd be intercepting load requests, fetching stuff yourself, writing it to the local filesystem with rewritten URLs, then loading that into the browser.
There seems to be no way to load/save the browser history.
Loading the string into a temp file and using that as a URL request seems to cure this. It's something about loading the string directly that causes UIWebView not to see it as the home page you can navigate back to. This code worked for me:
//If you load the string like this, then "webView.canGoBack" never returns YES. It's documented frequently on the web.
//Loading the string as a URL instead seems to work better.
//[self.myWebView loadHTMLString:str baseURL:nil];
//write the string to a temp file
NSString *fileName = #"homepage.html";
NSURL *fileURL = [NSURL fileURLWithPath:[NSTemporaryDirectory() stringByAppendingPathComponent:fileName]];
NSData *data = [str dataUsingEncoding:NSUTF8StringEncoding];
[data writeToURL:fileURL atomically:NO];
//open that temp file in the UIWebView
[self.myWebView loadRequest:[NSURLRequest requestWithURL:fileURL]];
Use this to enable/disable the back button:
- (void)webViewDidFinishLoad:(UIWebView *)webView{
//this is to check if we're back at the root page.
if (webView.canGoBack == YES) {
self.backButton.enabled=YES;
}
else {
self.backButton.enabled=NO;
}
}