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"
@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