Skip to content

Instantly share code, notes, and snippets.

@jtbandes
Last active January 7, 2024 05:29
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save jtbandes/19646e7457208ae9b1ad to your computer and use it in GitHub Desktop.
Save jtbandes/19646e7457208ae9b1ad to your computer and use it in GitHub Desktop.
Dynamic UTI decoding
/// Decodes a dynamically-generated Uniform Type Identifier for inspection purposes. (**NOT FOR PRODUCTION USE!**)
/// Many, many thanks to http://alastairs-place.net/blog/2012/06/06/utis-are-better-than-you-think-and-heres-why/
///
/// <https://developer.apple.com/library/ios/documentation/FileManagement/Conceptual/understanding_utis/understand_utis_conc/understand_utis_conc.html#//apple_ref/doc/uid/TP40001319-CH202-BCGCDHIJ>
func decodeDynUTI(_ uti: String) -> String?
{
let vec = Array("abcdefghkmnpqrstuvwxyz0123456789")
let encoded = Array(uti).suffix(from: 5)
var result: [UInt8] = []
var decoded = 0
var decodedBits = 0
for char in encoded {
// Each encoded character represents 5 bits (by its position in the length-32 vector).
guard let pos = vec.firstIndex(of: char) else {
print("unrecognized encoded character '\(char)'")
return nil
}
decoded = (decoded << 5) | pos
decodedBits += 5
// Every 8 decoded bits represent a UTF-8 code unit.
if decodedBits >= 8 {
let extra = decodedBits - 8
result.append(UInt8(decoded >> extra))
decoded &= (1 << extra) - 1
decodedBits = extra
}
}
if decoded != 0 {
print("\(decodedBits) leftover bits: \(decoded)")
return nil
}
return String(decoding: result, as: UTF8.self)
}
// Examples
decodeDynUTI("dyn.ah62d4rv4gu8yc6durvwwa3xmrvw1gkdusm1044pxqyuha2pxsvw0e55bsmwca7d3sbwu") // "?0=6:4=Apple files promise pasteboard type"
decodeDynUTI("dyn.ah62d4rv4gu8y6y4usm1044pxqzb085xyqz1hk64uqm10c6xenv61a3k") // "?0=6:4=NSPromiseContentsPboardType"
decodeDynUTI("dyn.ah62d46dzqm0gw23srf4gn5m4ge8068dytf2gn3dgrf0gn25tr34gn5xysr2ge55bsmwhk8puqzsdy4xuq6") // "?0=public.item:1=nxtypedfilecontentspboardtype\:jpg"
decodeDynUTI("dyn.ah62d4uv4ge804550") // "?0=B:1=mov"
decodeDynUTI("dyn.agq80c6durvy0g2pyrf106p5rsa4a") // "3=application/mp4"
decodeDynUTI("dyn.ah62d4rv4gu8ykzwynr11n6xdqzngn8dyn3y0n74qqf1gs7pbq7wza2xtqf3gkzd3sbwu") // "?0=6:4=DVTSourceTextViewLanguagePboardType"
decodeDynUTI("dyn.ah62d4rv4gu8ykzwynr11n6xdqzngn8dyn3y0n74xqr11a3nqqf1gs7pbq7wyg55ssvw1u7cuqm10c6xenv61a3k") // "?0=6:4=DVTSourceTextViewScopeLanguageContextPboardType"
decodeDynUTI("dyn.ah62d4rv4gu8ykzwynr11n6xdqzngn8dyn3y0n74msra1kuwtmvkge55bsmwfk8puqy") // "?0=6:4=DVTSourceTextViewIsAtBOLPboardType"
decodeDynUTI("dyn.ah62d4rv4gk81n65yru") // "?0=6:2=ustl"
decodeDynUTI("dyn.ah62d4rv4gk81g7d3ru") // "?0=6:2=styl"
@kfix
Copy link

kfix commented Mar 11, 2016

Is there a LICENSE on this? I'd like to use it in http://github.com/kfix/MacPin :shipit:

@jtbandes
Copy link
Author

@kfix I'm so sorry, I didn't even see your comment until just now! I'm happy for this to be in the public domain; feel free to use it. But as this format is totally undocumented, I'm not sure it's something you really want to depend on for anything important...

@eternalstorms
Copy link

eternalstorms commented Sep 28, 2017

here's the Objective-C (ARC) version:

-(NSString *)decodedDynUTI:(NSString *)dynUTI
{
	if (dynUTI.length <= 5)
		return nil;
	if ([dynUTI rangeOfString:@"dyn.a" options:NSCaseInsensitiveSearch].location == NSNotFound)
		return nil;
	
	NSArray *arr = [@"a b c d e f g h k m n p q r s t u v w x y z 0 1 2 3 4 5 6 7 8 9" componentsSeparatedByString:@" "];
	NSString *encodedString = [dynUTI substringFromIndex:5];
	NSUInteger decoded = 0;
	NSUInteger decodedBits = 0;
	
	NSUInteger counter = 0;
	NSMutableData *dat = [NSMutableData data];
	for (counter = 0; counter < encodedString.length; counter++)
	{
		NSString *substring = [encodedString substringWithRange:NSMakeRange(counter, 1)];
		if (![arr containsObject:substring])
		{
			NSLog(@"unrecognized encoded character: %@",substring);
			return nil;
		}
		NSUInteger pos = [arr indexOfObject:substring];
		decoded = (decoded << 5) | pos;
		decodedBits += 5;
		
		if (decodedBits >= 8)
		{
			NSUInteger extra = decodedBits - 8;
			UInt8 data = (UInt8)(decoded >> extra);
			decoded &= (1 << extra) - 1;
			decodedBits = extra;
			[dat appendBytes:&data length:sizeof(UInt8)];
		}
	}
	counter += 1;
	
	if (decoded != 0)
	{
		NSLog(@"%ld leftover bits: %ld",decodedBits,decoded);
		return nil;
	}
	
	return [[NSString alloc] initWithData:dat encoding:NSUTF8StringEncoding];
}

@amomchilov
Copy link

This is neat!

Btw, you can replace your String initializer with a call to the standard library's own String.init(decoding:as:). IDK what language version that was introduced with, but it works great.

@jtbandes
Copy link
Author

Thanks, updated to use String(decoding:as:) :smiley:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment