I'm working on an encryption application that for now encrypts text-only files. I need some help in the connections and how to go about the actual encryption. I received this snippet of code to encrypt a file, but I am a bit confused. What I need to do is have a button (encrypt) that takes this text file and encrypts it. Am I supposed to extract the contents of the file first, then encrypt it? How so? The program must know what file has been selected so it encrypts it, and I'm a complete noob right now, and I need some help.
Step by step instructions would be greatly appreciated.
This was the code:
- (NSData*) encryptString:(NSString*)plaintext withKey:(NSString*)key {
return [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
}
I've implemented a file chooser with the following snippet:
- (IBAction)fileChooser:(id)sender {
int i;
NSOpenPanel* openDlg = [NSOpenPanel openPanel];
[openDlg setCanChooseFiles:YES];
[openDlg setCanChooseDirectories:YES];
[openDlg setPrompt:#"Select"];
if ([openDlg runModalForDirectory:nil file:nil] == NSOKButton )
{
NSArray* files = [openDlg filenames];
for( i = 0; i < [files count]; i++ )
{
[files objectAtIndex:i];
}
}
}
This was the code:
- (NSData*) encryptString:(NSString*)plaintext withKey:(NSString*)key {
return [[plaintext dataUsingEncoding:NSUTF8StringEncoding] AES256EncryptWithKey:key];
}
First, look at the receiver of the AES256EncryptWithKey: message. This is another, nested message:
[plaintext dataUsingEncoding:NSUTF8StringEncoding]
What's plaintext? It's declared there in your encryptString:withKey: method: It is a variable holding a pointer to an NSString.
So you're sending the dataUsingEncoding: message to an NSString instance.
That's good if you intend to encrypt some plain-text user input, but it's not so good for encrypting files. NSStrings are for nothing but human characters; the user will probably want to encrypt files that are not plain text, such as images and video files.
I know you said that your application “for now encrypts text-only files”, but the solution is actually simpler when you throw out this restriction.
So you need to encrypt any data, not just a string. Delete your encryptString:withKey: method—it is useless.
From the implementation of that late method, we know that you were sending AES256EncryptWithKey: to an object obtained by sending dataUsingEncoding: to an NSString instance.
If you look in the documentation for NSString, you can see what dataUsingEncoding: returns. Spoiler: It returns an NSData object.
From there, it's one hyperlink away to the NSData documentation, where you will find two things:
It has no method by the selector AES256EncryptWithKey:.
It does have methods to create a data object from the contents of a file. (I'll let you find them.)
I'm assuming that you knew #1, and downloaded a category implementation from somewhere. I'm also assuming that, in fact, this category is on NSData; the category's #interface, in its header, will tell you.
In Objective-C, you should not use an index loop to iterate through an array unless you actually need the index for something, which you generally don't and you, specifically, don't. Instead, use fast enumeration:
for (NSString *path in [openPanel filenames]) {
}
You can see how, again, this makes the solution simpler. It's also faster.
Inside that loop, pass that path to the NSData class method that creates an NSData object from the contents of a file. Then, send that data object the AES256EncryptWithKey: message to obtain the ciphertext, which you should probably write out to a separate file. I'll refer you back to the NSString documentation for the path-manipulation methods you'll need to compute the output file path, and the NSData documentation for the method you'll need to write the ciphertext data out to the output file.
Related
I'd like to improve this method if possible: this is a small section whereby all of the textfield (eyepiece, objectivelenses etc) texts are saved. Unfortunately, having to do this lots of times for each part of my app is prone to error so I would like to improve it. I'm thinking some sort of fast enumeration with arguments for the method being the textfields etc. and I can have all the keys in a dictionary (which is already set up). Just a pointer to the right docs or, perhaps, some sort of process that has worked for you would be fantastic!
-(IBAction)saveUserEntries {
if (eyepiece.text != nil) {
eyepieceString = [[NSString alloc] initWithFormat:eyepiece.text];
[eyepiece setText:eyepieceString];
NSUserDefaults *eyepieceDefault = [NSUserDefaults standardUserDefaults];
[eyepieceDefault setObject:eyepieceString forKey:#"eyepieceKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"eyepieceKey"];
}
if (objectiveLenses.text != nil) {
objectiveLensString = [[NSString alloc] initWithFormat:objectiveLenses.text];
[objectiveLenses setText:objectiveLensString];
NSUserDefaults *objectiveDefault = [NSUserDefaults standardUserDefaults];
[objectiveDefault setObject:objectiveLensString forKey:#"objectiveKey"];
}
else {
[[NSUserDefaults standardUserDefaults] removeObjectForKey:#"objectiveKey"];
}
Thank you for taking the time to read this!
I will attempt to answer this question based on a OOP solution.
Create a method that accepts whatever type object these textboxes are as an argument, send the reference of said object to the method, and save the entry in a similar method you do know. This will avoid the "copy and paste" errors you are worried about.
You should be able to loop through every instance of said object that exists, if a cocoa application, works like similar to Java and .NET ( I really don't know ). I just know there must be a way to loop through every instance of a single object within the application domain.
If this was .NET I simply would suggest TextBox.Name and TextBox.String to make this a generic method that could be used to save the properties of any TextBox sent to it. If this doesn't anwer your question ( was a little long for a comment ) then I aplogize.
I have a basic NSTextView with rich text and graphics enabled (in IB). What I'd like to get is the path and filename of any images dragged in so I can pass those to another class.
I'm new to NSAttributedString but I've got a loop using enumerateAttributesInRange:options:usingBlock: looking for NSAttachmentAttributeName and that's all working fine. But going deeper, I get to the fileWrapper class and it's apparent inability to give me the path of the item.
How would I go about getting the name and path of the NSTextAttachment?
Related: Is there an easier way to get them all then stepping through the attributes?
Thanks much!
While I personally hold the design of NSFileWrapper in contempt, if you just need the data of each attachment you can access it as an NSData instance via NSFileWrapper's regularFileContents method. However, I needed a valid and explicit pathname to the attachment for my application. To get it is much more work than it should be:
You can subclass your NSTextView and override the NSDraggingDestination Protocol method draggingEntered: and you can traverse the NSPasteboardItem objects passed to your application during the dragging operation. I chose to keep the pathname and its inode number in an NSMutableDictionary, as NSFileWrapper can provide you with the inode of the referenced file. Later, when I access the NSTextView contents via an NSAttributedString, I can fetch the pathname of an attachment using the inode as an index.
- (NSDragOperation)draggingEntered:(id < NSDraggingInfo >)sender {
// get pasteboard from dragging operation
NSPasteboard *pasteboard = [sender draggingPasteboard];
NSArray *pasteboardItems = [pasteboard pasteboardItems];
for ( NSPasteboardItem *pasteboardItem in pasteboardItems ) {
// look for a file url type from the pasteboard item
NSString *draggedURLString = [pasteboardItem stringForType:#"public.file-url"];
if (draggedURLString != nil) {
NSURL *draggedURL = [NSURL URLWithString:draggedURLString];
NSString *draggedPath = [draggedURL path];
NSLog(#"pathname: %#", draggedPath);
// do something with the path
// get file attributes
NSDictionary *draggedAttributes = [[NSFileManager defaultManager] attributesOfItemAtPath:draggedPath error:nil];
if ( draggedAttributes == nil)
continue;
// the NSFileWrapper allows access to the absolute file via NSFileSystemFileNumber
// put the path and the inode (returned as an NSNumber) into a NSMutableDictionary
NSNumber *draggedInode = [draggedAttributes objectForKey:NSFileSystemFileNumber];
[draggedFiles setObject:draggedPath forKey:draggedInode];
}
}
return [super draggingEntered:sender];
}
One issue with my solution, that doesn't effect my application, is that multiple files dragged into the view (either singly or together) which are hard links to the same file, will only be indexed as the last pathname added to the dictionary which shares the inode. Depending on how the pathnames are utilized by your application this could be an issue.
I often use Transformable for Core Data attributes, so I can change them later.
However, it seems like, if I want to use NSPredicate to find a NSManagedObject, using "uniqueKey == %#", or "uniqueKey MATCHES[cd] %#", it's not working as it should.
It always misses matching objects, until I change the attributes of the uniqueKey of the matching object to have specific class like NSString, or NSNumber.
Can someone explain the limitation of using NSPredicate with Transformable attributes?
Note: I'm not sure when/if this has changed since 5/2011 (from Scott Ahten's accepted answer), but you can absolutely search with NSPredicate on transformable attributes. Scott correctly explained why your assumptions were broken, but if Can someone explain the limitation of using NSPredicate with Transformable attributes? was your question, he implied that it is not possible, and that is incorrect.
Since the is the first google hit for "Core Data transformable value search nspredicate" (what I searched for trying to find inspiration), I wanted to add my working answer.
How to use NSPredicate with transformable properties
Short, heady answer: you need to be smart about your data transformers. You need to transfrom the value to NSData that contains what I'll call "primitive identifying information", i.e. the smallest, most identifying set of bytes that can be used to reconstruct your object. Long answer, ...
Foremost, consider:
Did you actual mean to use a transformable attribute? If any supported data type -- even binary data -- will suffice, use it.
Do you understand what transformable attributes actually are? How they pack and unpack data to and from the store? Review Non-Standard Persistent Attributes in Apple's documentation.
After reading the above, ask: does custom code that hides a supported type "backing attribute" work for you? Possibly use that technique.
Now, past those considerations, transformable attributes are rather slick. Frankly, writing an NSValueTransformer "FooToData" for Foo instances to NSData seemed cleaner than writing a lot of adhoc custom code. I haven't found a case where Core Data doesn't know it needs to transform the data using the registered NSValueTransformer.
To proceed simply address these concerns:
Did you tell Core Data what transformer to use? Open the Core Data model in table view, click the entity, click the attribute, load the Data Model Inspector pane. Under "Attribute Type: Transformable", set "Name" to your transformer.
Use a default transformer (again, see the previous Apple docs) or write your own transformer -- transformedValue: must return NSData.
NSKeyedUnarchiveFromDataTransformerName is the default transformer and may not suffice, or may draw in somewhat-transient instance data that can make two similar objects be different when they are equal.
The transformed value should contain only -- what I'll call -- "primitive identifying information". The store is going to be comparing bytes, so every byte counts.
You may also register your transformer globally. I have to do this since I actually reuse them elsewhere in the app -- e.g. NSString *name = #"FooTrans"; [NSValueTransformer setValueTransformer:[NSClassFromString(name) new] forName:name];
You probably don't want to use transforms heavily queried data operations - e.g. a large import where the primary key information uses transformers - yikes!
And then in the end, I simply use this to test for equality for high-level object attributes on models with NSPredicates -- e.g. "%K == %#" -- and it works fine. I haven't tried some of the various matching terms, but I wouldn't be surprised if they worked sometimes, and others not.
Here's an example of an NSURL to NSData transformer. Why not just store the string? Yeah, that's fine -- that's a good example of custom code masking the stored attribute. This example illustrates that an extra byte is added to the stringified URL to record if it was a file URL or not -- allowing us to know what constructors to use when the object is unpacked.
// URLToDataTransformer.h - interface
extern NSString *const kURLToDataTransformerName;
#interface URLToDataTransformer : NSValueTransformer
#end
...
// URLToDataTransformer.m - implementation
#import "URLToDataTransformer.h"
NSString *const kURLToDataTransformerName = #"URLToDataTransformer";
#implementation URLToDataTransformer
+ (Class)transformedValueClass { return [NSData class]; }
+ (BOOL)allowsReverseTransformation { return YES; }
- (id)transformedValue:(id)value
{
if (![value isKindOfClass:[NSURL class]])
{
// Log error ...
return nil;
}
NSMutableData *data;
char fileType = 0;
if ([value isFileURL])
{
fileType = 1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value path] dataUsingEncoding:NSUTF8StringEncoding]];
}
else
{
fileType = -1;
data = [NSMutableData dataWithBytes:&fileType length:1];
[data appendData:[[(NSURL *)value absoluteString] dataUsingEncoding:NSUTF8StringEncoding]];
}
return data;
}
- (id)reverseTransformedValue:(id)value
{
if (![value isKindOfClass:[NSData class]])
{
// Log error ...
return nil;
}
NSURL *url = nil;
NSData *data = (NSData *)value;
char fileType = 0;
NSRange range = NSMakeRange(1, [data length]-1);
[data getBytes:&fileType length:1];
if (1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL fileURLWithPath:str];
}
else if (-1 == fileType)
{
NSData *actualData = [data subdataWithRange:range];
NSString *str = [[NSString alloc] initWithData:actualData encoding:NSUTF8StringEncoding];
url = [NSURL URLWithString:str];
}
else
{
// Log error ...
return nil;
}
return url;
}
#end
Transformable attributes are usually persisted as archived binary data. As such, you are attempting to compare an instance of NSData with an instance of NSString or NSNumber.
Since these classes interpret the same data in different ways, they are not considered a match.
you can try this way
NSExpression *exprPath = [NSExpression expressionForKeyPath:#"transformable_field"];
NSExpression *exprKeyword = [NSExpression expressionForConstantValue:nsdataValue];
NSPredicate *predicate = [NSComparisonPredicate predicateWithLeftExpression:exprPath rightExpression:exprKeyword modifier:NSDirectPredicateModifier type:NSEqualToPredicateOperatorType options:0];
I have problem with
NSString *filePaht = [[NSBundle mainBundle] pathForResource:(NSString *)name ofType:(NSString *)ext];
if I used
NSString *filePaht = [[NSBundle mainBundle] pathForResource:#"soundName" ofType:#"aiff"];
it's OK. but when I used
NSString *fileName = [[file.list objectAtIndex:index] objectForKey:#"soundName"];
NSString *filePaht = [[NSBundle mainBundle] pathForResource:fileName ofType:#"aiff"];
It's not work
have any idea !?
Thanks
I am going to guess that fileName from file.list includes the file extension. So you are searching for "soundName.aiff.aiff" which does not exist. Try passing #"" for type or stripping the extension from fileName:
fileName = [fileName stringByDeletingPathExtension];
Check your Debugger Console, as it may be telling what you're doing wrong.
[file.list objectAtIndex:index]
If you're getting an NSRangeException, it may be because index contains an index that is outside the bounds of the array. Remember that arrays in Cocoa are serial, not associative; if you remove an object, the indexes of all the objects that came after it will go down by 1, upholding the invariant that 0 ≤ (every valid index) < (count of objects in the array).
It could also be because you never declared a variable named index.
NSString *fileName = [[file.list objectAtIndex:index] objectForKey:#"soundName"];
NSString *filePaht = [[NSBundle mainBundle] pathForResource:fileName ofType:#"aiff"];
If nothing is happening or you get an NSInternalInconsistencyException, it could be one of:
fileList is nil.
The dictionary returned from [file.list objectAtIndex:index] does not have an object for the key soundName.
If you got a “does not respond to selector” message in the Console, it may be one of:
file.list is an object, but not an NSArray.
[file.list objectAtIndex:index] is not an NSDictionary.
fileName ([[file.list objectAtIndex:index] objectForKey:#"soundName"]) is not an NSString.
Remember that the class name you use when you declare the variable doesn't matter except to the compiler; at run time, it's just a variable holding a pointer to an object. The object can be of any class. It is perfectly valid to put something that isn't an NSString into an NSString * variable; it simply carries a very high (near certain) risk of wrong behavior and/or crashing shortly thereafter.
Such a crash will usually manifest in the form of a “does not respond to selector” exception (after something sends the object a message that NSString objects, for example, should respond to, but that the object doesn't respond to because it isn't an NSString).
Whichever problem you're having, you can use the Debugger to investigate.
Sorry with my fault.
I get data from XML file
and that data include "\n". yes I see "\n" so I replace with #""
but it not enough I must trim space value again.
Thanks for all advice ^_^
I am trying to create a new user playlist using the cocoa scripting bridge, but cannot seem to get it to work. I have so far:
iTunesApplication *iTunes = [SBApplication
applicationWithBundleIdentifier:#"com.apple.iTunes"];
SBElementArray *iSources = [iTunes sources];
iTunesSource *library = nil;
for (iTunesSource *source in iSources) {
if ([[source name] isEqualToString:#"Library"]) {
library = source;
break;
}
}
// could not find the itunes library
if (!library) {
NSLog(#"Could not connect to the iTunes library");
return;
}
// now look for our playlist
NSString *playlistName = #"new playlist";
SBElementArray *playlists = [library userPlaylists];
iTunesUserPlaylist *playlist = nil;
for (iTunesUserPlaylist *thisList in playlists) {
if ([[thisList name] isEqualToString:playlistName]) {
playlist = thisList;
break;
}
}
// if the playlist was not found, create it
if (!playlist) {
playlist = [[[iTunes classForScriptingClass:#"playlist"] alloc] init];
[playlist setName:playlistName];
[[library userPlaylists] insertObject:playlist atIndex:0];
}
When I try and add a name for the playlist, I get the error message:
iTunesBridge[630:80f] *** -[SBProxyByClass setName:]: object has not been added to a container yet; selector not recognized
Can anyone point me in the correct direction?
The error message is telling you that Scripting Bridge objects like your playlist can't receive messages until they've been added to the relevant SBElementArray, so your attempt to set a property on the playlist before adding it to the array fails.
The simplest solution is just to rearrange the last two lines of code, like this:
// if the playlist was not found, create it
if (!playlist) {
playlist = [[[iTunes classForScriptingClass:#"playlist"] alloc] init];
[[library userPlaylists] insertObject:playlist atIndex:0];
[playlist setName:playlistName];
}
The other option is to use initWithProperties: which according to your comment on another answer is what you ended up doing.
Making new application objects is dreadfully obfuscated in SB. The pseudo-Cocoa-ish alloc-init-insert procedure bears no resemblance to what's actually going on underneath. While the alloc-init appears to create a regular object that you can manipulate with subsequent method calls, the result is actually a shim whose only function is to be 'inserted' into an 'array', at which point SB sends an actual make event to the target process. (See also here and here for SB criticisms.)
IIRC, the only point you can actually specify initial properties is in -initWithProperties:. You can set them after the object has been 'inserted', but that is a completely different operation (manipulating an object that already exists rather than specifying initial state for an object being created) so can easily have unintended consequences if you aren't careful.
At any rate, here's how you'd normally create a new playlist if one doesn't already exist:
set playlistName to "new playlist"
tell application "iTunes"
if not (exists playlist playlistName) then
make new playlist with properties {name:playlistName}
end if
end tell
And, FWIW, here's how I'd do it in ObjC, using objc-appscript (which I wrote so I wouldn't have to use SB, natch):
#import "ITGlue/ITGlue.h"
NSString *playlistName = #"new playlist";
ITApplication *itunes = [ITApplication applicationWithName: #"iTunes"];
ITReference *playlist = [[itunes playlists] byName: playlistName];
if ([[[playlist exists] send] boolValue])
playlist = [playlist getItem];
else
playlist = [[[[itunes make] new_: [ITConstant playlist]]
withProperties: [NSDictionary dictionaryWithObject: playlistName
forKey: [ITConstant name]]] send];
(The downside of objc-appscript is that you have to build and embed a copy of the framework in your application bundle. The benefits are that it's more capable, less prone to application compatibility issues, and much less obfuscated. Plus you can use appscript's ASTranslate tool to convert the Apple events sent by the above AppleScript into ObjC syntax - very handy when figuring out how to construct your references and commands.)
Just a quick note that [[source name] isEqualToString:#"Library"] definitely does not work on non-english systems. It might be better to simply use iTunesSource *library = [[_iTunes sources] objectAtIndex: 0]; since the first source item is the one at the top, e.g. the main library.
This is what I've done to reliably identify the library. I could be doing it wrong.
- (iTunesSource *)iTunesLibrary
{
NSArray *librarySource = [[[self iTunes] sources] filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"kind == %#", [NSAppleEventDescriptor descriptorWithTypeCode:iTunesESrcLibrary]]];
if ([[librarySource lastObject] exists]) {
return [librarySource lastObject];
}
return nil;
}
You should look into EyeTunes. It's an open-source framework for interacting with iTunes using Objective-C. You code would look much more simple if you did it through EyeTunes.
http://www.liquidx.net/eyetunes/