Friday, July 02, 2010

Using SDK 3.1.3 with iPhone SDK 4

In iPhone SDK 4 Apple has removed all 3.x SDKs. I think that was not a very good idea to say the least. I can understand Apple wants developers to adopt the new features of iOS 4, but breaking zillions of Xcode projects is probably not a good way to win the hearts of those developers. Well, this is probably not Apple's goal anyway.

Sure there is a way to get your projects working again with the iPhone SDK 4, just set the Base SDK of your project to iOS 4:

Base SDK 4

and set the Deployment Target to iOS 3:

Deployment Target 3

This will get your projects compile and run again on both iOS 3 and 4. That's fine. Well, except when you want to make sure your project do not use any new iOS 4 only API.

The problem is, code like this will now compile without any warning or error:

[[NSUserDefaults standardUserDefaults] setURL:defaultURL forKey:@"DefaultURL"];

It's easier than you think to use a shiny new API without even noticing you are using something not available on iOS 3. This setURL:forKey: method is new in iOS 4 so this code will fail with an unrecognized selector exception when run on iOS 3. Unfortunately, the only way to catch this kind of oversight is to run it on an iOS 3 device. Or you can read the documentation of every single method and function you are calling to make sure it was not introduced in iOS 4. But this does not seem very reasonable, does it? The tools should help us detecting these problems at compile time, not runtime!

Here is how to get Xcode 3.2.3 and iPhone SDK 4 working with SDK 3.1.3.

  1. Log in to the iOS Dev Center
  2. Download the iPhone SDK 3.1.3 into ~/Downloads
  3. Run the install-iphone-sdk-3.1.3.sh script
  4. Quit and relaunch Xcode
  5. Duplicate your Debug configuration and rename it to SDK 3.1.3 Check
  6. Set the Base SDK of this new configuration to iPhone Simulator 3.1.3
  7. Add a User-Defined Setting GCC_OBJC_ABI_VERSION and set its value to 1 (one)
  8. Select GCC_OBJC_ABI_VERSION and choose Add Build Setting Condition from the gear pop-up button at the lower left of the window.
  9. Select Any iPhone Simulator and Any Architecture

There you go! By selecting the SDK 3.1.3 Check configuration, you get compile time check for misusing iOS 4 only APIs when targeting iOS 3.

Note that compiling with the iPhone Simulator 3.1.3 SDK and running in Xcode 3.2.3 simulator is not supported. What you get with this hack is just a compile time check of the APIs you are using. So do not expect to run your app on a 3.1.3 simulator.

Wednesday, June 02, 2010

Fixing -[NSMutableURLRequest setValue:forHTTPHeaderField:]

UPDATE

This problem is fixed as of Mac OS X 10.8.3+ and iOS 5.1+. I have no idea when it was actually fixed since radar sucks so much.

The problem

From NSMutableURLRequest setValue:forHTTPHeaderField: documentation:
In keeping with the HTTP RFC, HTTP header field names are case-insensitive.

I see three problems with this.
  1. Assuming all HTTP implementations are RFC compliant is foolish to say the least.
  2. Enforcing case-insensitivity is nonsense.
  3. This bit of documentation is accurate, the case of header fields is actually changed.
Trying this
    NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:@"http://www.example.com"]];
[request setValue:@"MyValue" forHTTPHeaderField:@"MyField"];
NSLog(@"result: %@", [request allHTTPHeaderFields]);
outputs
    result: {
Myfield = MyValue;
}
Notice how the 'F' of MyField was lowercased, that sucks! If the HTTP server you are trying to communicate with is case-sensitive, you are screwed. I filed radar #8029516, which is a duplicate of radar #3131623, which means there is almost no chance to have this fixed anytime soon.

When this happens, you should contact the server administrator asking for a case-insensitive implementation. But in the meantime, if you badly need to preserve the case of your headers, read on.

The investigation

First, let's fire otx to disassemble the Foundation framework in order to have a look at setValue:forHTTPHeaderField: implementation. I strongly suggest you to use otx version from subversion trunk, as it is better at resolving symbols in tail call optimizations (fixed in r553).

Excerpt from otx 0.16b:
   +18  [...]  movl     %eax,0x08(%ebp)
+21 [...] leave
+22 [...] jmpl 0x002c52bb

Excerpt from otx trunk:
   +18  [...]  movl     %eax,0x08(%ebp)
+21 [...] leave
+22 [...] jmpl 0x002c52bb _CFURLRequestSetHTTPHeaderFieldValue

Much better, isn't it?

So let's go through the function calls of setValue:forHTTPHeaderField:. With a basic static analysis of the Foundation and CFNetwork disassemblies, we can draw the following call path:
-[NSMutableURLRequest(NSMutableHTTPURLRequest) setValue:forHTTPHeaderField:]
CFURLRequestSetHTTPHeaderFieldValue()
URLRequest::setHTTPHeaderFieldValue()
HTTPRequest::setHeaderFieldValue()
CFHTTPMessageSetHeaderFieldValue()
HTTPMessage::setHeaderFieldValue()
_CFCapitalizeHeader()

_CFCapitalizeHeader looks like a very good candidate for being the bastard changing the case of our headers. A quick search reveals the source code of CFNetwork that was open source long time ago. Although the open source implementation does not exactly match what we see in the disassembly (it is now using toupper instead of ch + 'A' - 'a' for example), we are now absolutely sure that _CFCapitalizeHeader is the function responsible for messing with our headers.

Now, let's check is if there is a path that will not call _CFCapitalizeHeader and if we can somehow influence the condition that would avoid the call to _CFCapitalizeHeader. This is quickly checked, especially if you enabled the Separate logical blocks option of otx (-b option for cli).
HTTPMessage::setHeaderFieldValue(__CFString const*, __CFString const*)
+0 000515a4 55 pushl %ebp
+1 000515a5 89e5 movl %esp,%ebp
+3 000515a7 83ec28 subl $0x28,%esp
+6 000515aa 8b4508 movl 0x08(%ebp),%eax
+9 000515ad 8975f8 movl %esi,0xf8(%ebp)
+12 000515b0 8b7510 movl 0x10(%ebp),%esi
+15 000515b3 897dfc movl %edi,0xfc(%ebp)
+18 000515b6 8945f4 movl %eax,0xf4(%ebp)
+21 000515b9 8b450c movl 0x0c(%ebp),%eax
+24 000515bc 890424 movl %eax,(%esp)
+27 000515bf e88a26fbff calll __CFCapitalizeHeader
+32 000515c4 c744240cffffffff movl $0xffffffff,0x0c(%esp)
+40 000515cc 89742408 movl %esi,0x08(%esp)
+44 000515d0 89c7 movl %eax,%edi

We see that there is no path that avoid the call to _CFCapitalizeHeader. So we are left with the last resort solution: patching _CFCapitalizeHeader. With APE Lite, function patching is very easy. You first use APEFindSymbol() to find the address of a non-exported symbol (i.e. __CFCapitalizeHeader), then APEPatchCreate() to replace a function implementation with your own, while still keeping a reference to the original implementation. On iPhone OS, you can use APE Lite+arm (my implementation of the APE Lite API using MobileSubstrate).

The solution

NSMutableURLRequest+CaseSensitive is a category on NSMutableURLRequest that adds these three methods:
   - (void) setAllHTTPHeaderFields:(NSDictionary *)headerFields caseSensitive:(BOOL)caseSensitive;
- (void) setValue:(NSString *)value forHTTPHeaderField:(NSString *)field caseSensitive:(BOOL)caseSensitive;
- (void) addValue:(NSString *)value forHTTPHeaderField:(NSString *)field caseSensitive:(BOOL)caseSensitive;

Just pass caseSensitive:YES for preserving the case of your header fields.

Warning: you SHOULD NOT use this in production code. But hey, HTTP implementations SHOULD be case-insensitive ;-)

Thursday, April 08, 2010

ABGetMe with the iPhone SDK

Unlike the Mac Address Book API, the iPhone Address Book API does not come with the ABGetMe function. That's a pity, but we can do something about it.

Recently, I used MFMailComposeViewController for the first time and I realized that it was displaying my e-mail address. Nicolas Seriot already demonstrated how to retrieve all your e-mail account information in SpyPhone, but I felt there was a more lightweight method for retrieving the e-mail addresses by taking advantage of the MessageUI framework. A class-dump and five minutes later, here is my solution:

Sunday, January 03, 2010

Jasscore pour iPhone

Ma première application iPhone, Jasscore (lien iTunes) est enfin disponible sur l'AppStore au prix de 1.10 CHF. C'est une ardoise virtuelle qui vous permet de compter vos points et ceux de votre adversaire au jass (chibre, mise, etc.)

Les features

  • En français et en allemand
  • Editez le nom des équipes et les scores à atteindre
  • Notez les scores et les annonces
  • Multiplication des scores de 1x à 7x
  • Grand affichage afin que tous les joueurs puissent suivre les scores

Les captures d'écran


Score
Réglage
Grand Affichage

Je suis ouvert à vos suggestions, alors n'hésitez pas à laisser un commentaire ou à m'envoyer un e-mail.

Saturday, November 07, 2009

CLAlert: NSAlert done right

From NSAlert documentation:

Currently, there is no visual difference between informational and warning alerts.

This was written at the time of Panther and this is still true in Snow Leopard. So why the hell is the setAlertStyle: method provided?

NSCriticalAlertStyle
This style causes the icon to be badged with a caution icon.

A caution icon, for a critical alert? That does not make sense!

Here are screenshots of the default behaviours with the three different alert styles:







The documentation is unfortunately right: there is no difference between the informational and the warning style. The critical style is indeed badged with a caution icon.

As you probably have guessed from the title of this post, I am not happy with this behavior, so I have written a class, CLAlert (MIT License) that displays alerts the way I think it should be done. I.e. a note icon for an informational alert, a caution icon for a warning alert and a stop icon for a critical alert as you can see on these screenshots.







CLAlert is a drop-in replacement for NSAlert. It requires at lest Mac OS X 10.5.

Thursday, November 05, 2009

Watch a large number of icons with Preview.app

Browsing .icns files in Preview.app can be painful. If you try to open all the icons of the CoreTypes bundle for example with the following command

open /System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/*icns

you will get this view:


All the different sizes of the .icns are displayed, which makes it quite unpleasant to browse.

F-Script Anywhere to the rescue!

Install F-Script Anywhere into Preview, then do

  1. FSA → New F-Script Workspace

  2. Click the Browser For Target… button

  3. Click on one of the icons

  4. Choose the PVIKImageBrowserView object

  5. In the F-Script Object Browser, name the PVIKImageBrowserView browserView


Finally, in the F-Script interpreter, paste the following code:
groups := browserView layoutManager groups.
count := groups count.
0 to:count-1 do: [:i | browserView collapseGroup:(groups objectAtIndex:i)]


Tada, a nice browsable view of your icons!

Wednesday, August 19, 2009

Booting from a dmg

I was pointed by Jean-Francois Roy that it is possible to boot off a dmg.

It's a simple two steps process using bootfolder:

./makebootfolder /Path/to/OperatingSystem.dmg
sudo ./blessbootfolder /Path/to/OperatingSystem.bootfolder

But beware: you better not have a space in the path to the dmg or in the dmg itself. If you happen to try this trick with a space in the path to the boot folder, then your Mac will brick. In verbose startup, you will get

Error loading kernel 'mach_kernel' (0x9)

If you can't boot anymore, download rEFIt and burn it onto a CD. Then boot on the CD by pressing the C key. You should see something looking like this (maybe without the linux and windows partitions):



Just press enter and you should be able to boot your Mac.

Wednesday, April 08, 2009

Extract UIKit artwork

While writing an iPhone application (more about it on this blog soon), I wanted to tint my UIAlertView with the same color I tinted my other controls. Maybe this is not a good idea, but I wanted to try anyway. Unfortunately, only UISearchBar, UISegmentedControl, UINavigationBar and UIToolbar have the tintColor property. So I started digging into UIKit. The first place to look was obviously the -(void)[UIAlertView(Private) drawRect:] method. To draw its background, a UIAlertView calls _popupAlertBackground which in turn call _UIImageWithName(@"UIPopupAlertSheetBackground.png"). This file probably lies inside the UIKit.framework would think a Mac developer. In fact it is not there. Only two pngs are there: DefaultWallpaper-iPhone.png and DefaultWallpaper-iPod.png. So, where is it?

It is in fact somehow embedded in the Other.artwork file which contains UIKit artwork. This file is memory mapped at application startup, probably for performance reasons and the content is easily accessed with the private _UIImageWithName() function. _UIImageWithName looks up in a dictionary that associates a file name to some memory location that contains the actual image data.

Now, it would be cool to extract all this content to see what artwork UIKit contains. It turns out to be quite easy. The only trick is to find a reference to the dictionary containing all the file names. Check my UIKit Artwork Extractor project on github to see how it works.

In an attempt to encourage comments, I'm offering a beer at NSConference to the first person who will clearly explain how to find the magic address of the images dictionary (except for Nicolas who knows the answer already) and I'm offering two beers to the one who provides a cleaner way to obtain a reference to the dictionary that is not UIKit version specific ;-)

Edit: I have updated the code so that it is not UIKit version specific, so I won the beers myself that I'm going to drink right away.

Monday, March 23, 2009

chmod, ACL and symbolic links

Let's say you want to set an ACL on a symbolic link. So you try the -h option of chmod that is documented to change the mode of the link itself rather than the file that the link points to.

  $ ln -s aFileThatDoesNotExist myLink
$ /bin/chmod -h +a "everyone allow delete" myLink
chmod: Failed to set ACL on file 'myLink': No such file or directory

So, it seems myLink was followed even though we passed the -h option. Let's try with a link that points to an existing file:

  $ touch aFileThatExist
$ ln -fs aFileThatExist myLink
$ /bin/chmod -h +a "everyone allow delete" myLink
$ ls -le myLink aFileThatExist
-rw-r--r--+ 1 cluthi staff 0 5 mar 18:11 aFileThatExist
0: group:everyone allow delete
lrwxr-xr-x 1 cluthi staff 14 5 mar 18:12 myLink -> aFileThatExist

Our hypothesis is confirmed, the link was followed. Let's investigate and have a look at chmod source code (file_cmds-188 on darwinsource).

chmod.c:125 variable hflag is set to true if the -h argument is passed
chmod.c:388 function modify_file_acl() is called, the hflag is not passed to this function

Doh, lazy boys! Let's patch it and add a follow argument to the modify_file_acl() function:
int modify_file_acl(unsigned int optflags, const char *path, acl_t modifier, int position, int inheritance_level, int follow);
and call it with modify_file_acl(..., !hflag)

From acl_set(3) man: The acl_set_link_np() function acts on a symlink rather than its target, if the target of the path is a symlink. Perfect, that's what we need: we must call acl_set_link_np instead of acl_set_file in order not to follow the link.

chmod_acl.c:807 acl_set_file is called: (0 != acl_set_file(path, ACL_TYPE_EXTENDED, oacl))
Replace with (0 != (follow ? acl_set_file(path, ACL_TYPE_EXTENDED, oacl) : acl_set_link_np(path, ACL_TYPE_EXTENDED, oacl)))

With this small modifications, the -h option should be respected. Rebuild chmod and try again:

  $ ./chmod -h +a "everyone allow delete" myLink
chmod: Failed to set ACL on file 'myLink': Operation not supported
$ ln -fs aFileThatDoesNotExist myLink
$ ./chmod -h +a "everyone allow delete" myLink
chmod: Failed to set ACL on file 'myLink': Operation not supported

Now, whether the link points to an existing file or not, we get the Operation not supported error. That's better diagnostic, but not exactly what we expected :-( So, why is it not supported? Let's dig a bit more. acl_set_link_np implementation is found at Libc-498.1.5/posix1e/acl_file.c:175 and is:

  return(acl_set_file1(path, acl_type, acl, 0));

The last argument (follow) passed to acl_set_file1 is 0 and the first lines of acl_set_file1 implementation reads:

  if (follow == 0) {
/* XXX this requires some thought - can links have ACLs? */
errno = ENOTSUP;
return(-1);
}

We have the explanation of the Operation not supported error we got earlier. Note that the comment is not mine, it is actually in the libc source code!

Does it mean it is impossible to set an ACL on a symlink? Does it mean our only option is to file a bug asking some Apple engineer to think harder if links can have ACLs? Fortunately no. There is a third function in the acl_set(3) API: acl_set_fd, which acts on a file descriptor. Hopefully, getting the file descriptor of a symlink is as simple as open(path, O_SYMLINK).

That's it. With this patch, you'll be able to run ./chmod -h +a "everyone allow delete" myLink and have the ACL to be set on the symlink!

If you need to set ACLs on symlinks on a daily basis, I suggest you do not overwrite /bin/chmod but install the patched version of chmod in /usr/local/bin. Well, if you read that post till there, you probably know that already.

This bug has been reported to Apple and is known as radar #6264303 which is a duplicate of radar #5684438.

Sunday, January 04, 2009

Using your own address book in the iPhone Simulator

Jailbroken iPhone with openssh:

scp mobile@iPhone:~/Library/AddressBook/* ~/Library/Application\ Support/iPhone\ Simulator/User/Library/AddressBook

Non-jailbroken iPhone:

Say bye bye to John Appleseed & Co.

Wednesday, October 15, 2008

Kagi Registration Module (lack of) security

I'm in the process of choosing an eCommerce partner for selling my future shareware. I have narrowed down to eSellerate and Kagi as they are widely adopted by Mac shareware developers. After reading their respecting obfuscated pricing policies, I decided to have a look at what they offer for integrating the purchasing process into the application.

Kagi offers the Kagi Registration Module (KRM) which is basically a library that provides an in-application one click purchase experience. Sounds pretty good. I start reading the KRM developer documentation and stumble on the Security section:

The ZonicKRM submits orders through an SSL connection for security, however pricing information is currently passed from the application to the ZonicKRM as an XML string.

If this data is not checksummed, or otherwise protected, a malicious user may be able to edit the XML string within an application's executable and submit an order with an invalid price.

In the long term, this attack will be denied by moving the responsibility for pricing information from the KRM library to the KRM server. When this process is complete, vendors will be able to override the pricing information in shipping copies of an application using their Kagi database entry.


WHAT THE FUCK ? The user is able to choose the price he wants to pay ? Can't be true, this part of the documentation must be outdated. Guess what... it's not, long term is long term!

Note that checksumming or protecting is pure bullshit as long as the price comes from the application and not Kagi's server.

I searched for the first shareware using KRM I found, opened it with an hex editor, did search and replace of the string 30.00 to 01.00 and I indeed successfully ordered the shareware for $1.

This is totally irresponsible from Kagi. I don't know how this registration scheme could have been designed this way in the first place. No sensible person can design an ordering system where the price is set by the client.

Please don't flame me, I'm a good guy. I contacted the $30 shareware author and offered to pay the remaining $29 I owe him. I should have searched a bit longer for a cheaper shareware. $1 + $29 is a bit expensive to demonstrate that KRM sucks ;-)

The Bottom Line
Don't use KRM as long as you can't set the price of your shareware on Kagi's server.

Wednesday, September 03, 2008

QuietXcode

Have you ever noticed the Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x319a380, has non-zero refcount = 1 messages in the console ? Chris Espinosa replied about these warnings to Matt Neuburg:


The second one is an error deep in some system framework when running under Garbage Collection that we have not been able to track down yet. It simply means that somebody has neglected to do a final release on a memory block that nobody has kept a pointer to (making the final release technically impossible). The Garbage Collector knows the block is inaccessible and is freeing it, but is warning us that somebody forgot to formally release it before the pointer to it went out of scope. Bad form, but no actual harm.

I think it actually harms. These messages are filling the console so much it becomes unusable. I'm a big fan of GeekTool and I always have the tail of the console on the desktop. Now, it looks always the same and interesting messages from various applications are lost in the mass of free_garbage messages:

Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x319a380, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x31bf6e0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3263da0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3274eb0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x319a380, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x31bf6e0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3263da0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3274eb0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x201c8b0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3256110, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x201c8b0, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x3256110, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x2045540, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x208ca10, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x2045540, has non-zero refcount = 1
Xcode(19838,0xb0103000) malloc: free_garbage: garbage ptr = 0x208ca10, has non-zero refcount = 1

So I decided to tackle the problem and here is my Solution: QuietXcode 1.1.4 (5 KB). This is an Xcode plugin that patches the culpable call to malloc_printf("free_garbage: garbage ptr = %p, has non-zero refcount = %d", ...).

You can build the plugin either by typing xcodebuild in a terminal or by building it (⌘ + B) in Xcode. Building the plugin will also automatically install it into your ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins folder.

Once it's installed, you have to relaunch Xcode. You should see the message <QuietXcode> loaded successfully in the console and no more free_garbage messages.

The plugin performs a safe patch, that is, if it does not find the expected call to malloc_printf and the expected Xcode version (greater than or equal to 3.1/1099), it logs a more or less comprehensive error to the console. Have fun browsing the source code, it demonstrates how to use the dyld and mach-o apis to locate non exported symbols and the mach api to dynamically patch code.

Note that the plugin is for i386 only, porting it to ppc and/or 64 bits is left as an exercise to the reader.

Friday, August 22, 2008

Exploring iPhone OS 2 files

Update: This technique also works with iPhone OS 3.x. You will find the VFDecrypt keys on The iPhone Wiki Firmware page. Just select the appropriate iPhone model and Version/Build of your firmware.

It turns out to be pretty simple:


  1. Download iPhone OS 2.0.2 (5C1)
    curl -O http://appldnld.apple.com.edgesuite.net/content.info.apple.com/iPhone/061-5241.20080818.t5Fv3/iPhone1,2_2.0.2_5C1_Restore.ipsw

  2. Unzip the ipsw which is actually a zip file
    unzip iPhone1,2_2.0.2_5C1_Restore.ipsw

  3. Download vfdecrypt
    svn co http://iphone-elite.googlecode.com/svn/trunk iphone-elite

  4. Compile vfdecrypt
    make -C iphone-elite/vfdecrypt_win32

  5. Decrypt the dmg (key from The iPhone Wiki)
    ./iphone-elite/vfdecrypt_win32/vfdecrypt -i 018-3978-1.dmg -k 31e3ff09ff046d5237187346ee893015354d2135e3f0f39480be63dd2a18444961c2da5d -o iPhoneOS-2.0.2.dmg

  6. Mount iPhone OS dmg and start exploring
    open iPhoneOS-2.0.2.dmg

Do not buy iQuarantine X

From iQuarantine X website:


  • iQuarantine X is not a background script or a script that gets attached to files or folders.
  • iQuarantine X is the first application to make the LEOPARD FILE QUARANTINE ALERTS go away.
  • iQuarantine X is the easiest way to rid LEOPARD of all FILE QUARANTINE ALERTS.

So, if it's not a script, what is it (beside a scam) ?
It's a hack that binary patches a system framework (/System/Library/Frameworks/CoreServices.framework/Versions/A/Frameworks/CarbonCore.framework/Versions/A/CarbonCore) by short-circuiting the private function _FSAllocateQuarantineData.

So we have four good reasons not to buy it:
  1. It has an unacceptable upgrade policy.
  2. It binary patches a system framework.
  3. Developer does not reply to e-mails.
  4. You can disable the Leopard quarantine for free with an official technique.

Thursday, July 10, 2008

The missing NSLocale documentation

+ (NSArray *)preferredLanguages
Return Value
The user's language preference order as an array of NSString objects, each of which is a canonicalized IETF BCP 47 language identifier. This is defined by the user in System Preferences → International → Language.

For your information: this is stored in the gloal domain in the AppleLanguages key.

+ (id)currentLocale
Return Value
The logical locale for the current user. The locale is formed from the settings for the current user’s chosen system locale overlaid with any custom settings the user has specified in System Preferences → International → Formats.
This method may return a retained cached object.


+ (id)autoupdatingCurrentLocale
This one I have not understood how it is supposed to be used. If someone knows, please let us know in the comments.

Now, if what you want is the current language, you should not use either of these methods. Instead you should use something like NSLocalizedString(@"ISOLanguageCode", @"iso language code"). Then you have to define "ISOLanguageCode" = "en"; etc. in all your Localizable.strings files.

Tuesday, June 10, 2008

Mac OS X bug on non english systems

Mac OS X 10.5 (Japanese): Disk Utility "internal error" alert with 7-pass erase or 35-pass erase

Symptoms
In the Japanese language version of Mac OS X 10.5, when performing a Secure Erase (7-pass or 35-pass erase) in Disk Utility, this alert may appear: "Disk Utility internal error. Disk Utility has lost its connection with Disk Management Tool."

Resolution
Change the Mac OS X language version to English before performing the secure erase.

I thought this kind of problem was Mac OS 9/Classic history.

Thursday, February 14, 2008

Upgrading a System Preference pane

The System Preferences application provides a convenient way to install a preference pane. Double-clicking the preference pane will prompt the user if he wants to install it for the current user only or for all users of the computer. Then System Preferences will copy the preference pane to ~/Library/PreferencePanes or /Library/PreferencePanes according to what was chosen asking for administrator password if necessary. Finally the preference pane will be loaded and presented to the user.

Now, let's see what happens when a preference pane is upgraded. Again, System Preferences is smart: it is able to detect if an older version of the same preference pane is installed and proposes to replace it [1]. Everything seems alright, but it is actually not! Things are more complicated if the preference pane to upgrade has already been loaded. That is, if the user already clicked the preference pane.

Indeed, preference panes are just a special kind of bundle which is loaded into System Preferences with the -(BOOL)[NSBundle load] method (cf. -(BOOL)[NSPrefPaneBundle instantiatePrefPaneObject] method of the PreferencePanes framework). The problem is that on Tiger, a NSBundle can not be unloaded. So when upgrading an opened preference pane, the old code is not unloaded and as a consequence the new code is not loaded. This is because System Preferences calls the -(BOOL)[NSBundle load] method which returns YES, meaning that the bundle was successfully loaded or that the code has already been loaded. In the case of an already opened preference pane, that's how the result of the load method should have been interpreted. Unfortunately, it is interpreted as if the bundle was successfully loaded and System Preferences thinks it has loaded the new bundle, but it has not.

This is very problematic because at this point, the resources (nib files, pictures etc.) of the new bundle have already been copied. So we have the old code which is accessing the new resources. I let you imagine the numerous problems this situation can cause. At best, exceptions will raise and your preference pane will be half working. At worst, your preference pane will simply crash.

So, how do we fix this problem?

First, the preference pane must detect itself when it's upgrading over an older already loaded version as System Preferences does not detect it [2]. This must be done as early as possible, i.e. at the very beginning of the - (id)initWithBundle:(NSBundle *) bundle method. It is possible to detect this situation with the help of the version of your NSPreferencePane subclass. See my detection snippet to understand how detection works.
Once this is detected, we must properly reload the new preference pane. This must be achieved by quitting System Preferences and relaunching System Preferences. This is the not that elegant solution to unload the old preference pane. The elegant solution would be to unload the bundle. This is left as an exercise to the Apple engineers for a future version of System Preferences.

Relaunching System Preferences and selecting the preference pane is quite tricky. A second executable must be responsible for relaunching the System Preferences application. Also, it is nicer for the user if the preference pane he just upgraded is automatically selected. Automatic selection of the pref pane is achieved through Apple Script. Please refer to my reload snippet for implementation details. Note that once you have compiled the reload executable, you have to place it inside the resources directory of your preference pane. Do not place it inside the executable directory (Contents/MacOS) if you do not want to see the reload application popping up in the Dock.

With this reload code in place, if the user ever happens to upgrade a preference pane while the older one was loaded, he will experience a System Preferences flicker as it will quit and reopen right away. While this might be surprising to him, this is still better than a half working preference pane or a crash.

If anything is unclear, just say it so in the comments and I will try to elaborate. If everything is clear, just pick up my code snippets and implement them in your preference pane as soon as possible ;-)



1. System Preferences uses the CFBundleGetVersionNumber function to retrieve the version numbers of the new and old bundles as an UInt32 in order to compare them. The documentation says If the bundle’s version number is a number, it is interpreted as the unsigned long integer format defined by the vers resource on Mac OS 9. What every developer understands is that if your Info.plist CFBundleVersion key represents a number (e.g. "519"), the value returned by CFBundleGetVersionNumber will be 519. This is not what actually happens. If you want CFBundleGetVersionNumber to return 519, you have to add an undocumented key in your Info.plist file: CFBundleNumericVersion. Make sure you define it as <integer>519</integer> and not <string>519</string>.

2. Note that on Leopard, this situation is actually detected and a dialog is presented to the user telling he must quit System Preferences and then open it again. Unfortunately, no automatic action is taken to circumvent this annoying behavior. System Preferences could restart itself or unload the bundle (this is possible since Leopard) but as of Mac OS X 10.5.2, none of this action is performed.

Friday, January 25, 2008

QuickTime 7.4 and Perian subtitles fix

With QuickTime 7.4, subtitles automatically added by Perian have stopped working. In order to get them back, download and install Front Row Trailers, go to the QuickTime Components tab, and install Perian 1.0.0.2. If the proposed version is below 1.0.0.2, hold the alt (option) key while clicking the Refresh button.

For thoses wondering, this is an unofficial build of Perian 1.0 onto which two patches have been applied. Note that the future Perian 1.1 release will also be able to read subtitles.

Enjoy, QuickTime 7.4 can read subtitles again, no need to downgrade to version 7.3.

UPDATE: Perian 1.1 is now released and has addressed the problem. Note that subtitles still do not work in Front Row on Leopard.

keywords: QuickTime 7.4 Perian subtitles srt

Thursday, November 29, 2007

BetterAuthorizationSample

Finally, Apple posted BetterAuthorizationSample, a sample project that demonstrates how to securely use Mac OS X authorization API.

Apple's older sample code (AuthSample and MoreAuthSample) used a setuid root privileged helper tool. BAS uses launchd because it's more secure. In the BAS design, an attacker can't directly control the environment which the helper tool inherits, and that prevents a variety of potential attacks.

This sample code supersedes the four years old Project Builder MoreIsBetter/MoreSecurity sample code that warned: No matter what you do, the current AuthorizationExecuteWithPrivileges model allows for security violations [3093666]. It comes as a Xcode project that compiles without tweaking and with three documentation files that look quite complete: Design and Implementation Rationale, Performing Privileged Operations With BetterAuthorizationSampleLib and Read Me About BetterAuthorizationSample.

Monday, November 19, 2007

Front Row for Tiger

Leopard users have the Front Row application in their Applications folder. It may be useful if you want to automatically launch front Row when your computer starts up by adding a login item for example.

Now, Tiger users can also use this convenient Front Row application. Leopard users who have accidentally deleted their Front Row application can also use it.

Front Row



I have not tested it on unsupported Macs, i.e. those without an Apple Remote. If you have such a Mac, please report in the comments if it works or if it still requires Front Row Enabler.

This Front Row launcher has been written from scratch. Here is the source code:

int main(int argc, char *argv[])
{
    BSRemoteUIToggle();
    return 0;
}