Using SecKeychainCreate from Swift - macos

I am writing an OS X app that should maintain a custom Keychain, I am trying to use the Security framework's API to create the Keychain, however, I can't seem to get it to compile under Swift.
Here's what I have, assume that path contains a path to a potentially existing Keychain:
let pathName = (path as NSString).UTF8String
var keychain: Unmanaged<SecKeychain>?
var status = withUnsafeMutablePointer(&keychain) { pointer in
SecKeychainOpen(pathName, pointer)
}
if status != errSecSuccess {
status = withUnsafeMutablePointer(&keychain) { pointer in
SecKeychainCreate(pathName, UInt32(0), nil, false, nil, pointer)
}
}
The compiler is complaining about the types in the SecKeychainCreate call, however, I fail to understand what am I doing wrong.
Cannot invoke 'withUnsafeMutablePointer' with an argument list of type '(inout Unmanaged<SecKeychain>?, (_) -> _)'
If I modify the second closure slightly, I get this compiler error:
Cannot invoke 'SecKeychainCreate' with an argument list of type '(UnsafePointer<Int8>, UInt32, nil, Bool, nil, (UnsafeMutablePointer<Unmanaged<SecKeychain>?>))'
I appreciate all suggestions.

The promptUser parameter of SecKeychainCreate() has the type
Boolean, which is a "Mac OS historic type" and an alias to UInt8,
so it is different from the Swift Bool in Swift 1.2.
(Compare Type 'Boolean' does not conform to protocol 'BooleanType' for a similar issue.)
This means that you have to
pass Boolean(0) instead of false:
SecKeychainCreate(pathName, UInt32(0), nil, Boolean(0), nil, pointer)
Additional remarks:
withUnsafeMutablePointer() is not needed, you can pass &keychain
to the keychain functions.
(path as NSString).UTF8String is not needed, you can pass a Swift
string to a C function expecting a const char * parameter,
compare String value to UnsafePointer<UInt8> function parameter behavior.
Passing nil as password to SecKeychainCreate() is only allowed
if promptUser is TRUE, otherwise it causes a
"parameter error (-50)".
SecKeychainOpen() succeeds even if the keychain file does not
exists. According to the documentation, you have to check
SecKeychainGetStatus(). Alternatively, you can try to create
the keychain file first, as for example in Open Local Items Keychain?.
Together:
let path = "/path/to/my.keychain"
var keychain: Unmanaged<SecKeychain>?
var status = SecKeychainCreate(path, 0, "", Boolean(0), nil, &keychain)
if status == OSStatus(errSecDuplicateKeychain) {
status = SecKeychainOpen(path, &keychain)
}
As of Swift 2 / Xcode 7 beta 5, the Mac type Boolean is mapped
to Swift as Bool, and the key chain functions do no longer return
unmanaged objects:
let path = "/path/to/my.keychain"
var keychain: SecKeychain?
var status = SecKeychainCreate(path, 0, "", false, nil, &keychain)
if status == OSStatus(errSecDuplicateKeychain) {
status = SecKeychainOpen(path, &keychain)
}

Related

Why function GetLastError returns 2?

Today, I decided to practice by creating a simple program that creates a new file. But I got a problem that the file is not being created. How to solve this?
Firstly, I'm importing the required crate, then creating the err() function for printing errors after creating the file, if they will.
Secondly, I'm creating a function to_u16(), because LPCWSTR type needs *const u16.
Thirdly, I'm calling the CreateFileW() function for creating file.rs, and passing all necessary arguments (according to this and this).
createfile.rs
use winapi::shared::minwindef::DWORD;
use winapi::um::winnt;
use std::ffi::CString;
use winapi::um::errhandlingapi;
use winapi::ctypes::*;
use winapi::um::minwinbase::*;
use winapi::um::fileapi;
use std::io;
fn err(){//print the last error
unsafe{
let xui =errhandlingapi::GetLastError();
print!("{:?}",xui) ;// 2
}
}
fn to_u16(s: &str) ->*const u16 {
let c_str = CString::new(s).unwrap();
c_str.as_ptr() as *const u16
}
fn main() {
unsafe{
let name:winnt::LPCWSTR =to_u16("file.rs") ; //lpFileName
let acces:DWORD = winnt::GENERIC_WRITE; //dwDesiredAccess
let share = winnt::FILE_SHARE_DELETE|winnt::FILE_SHARE_READ|winnt::FILE_SHARE_WRITE;//dwShareMode
let security = 0 as *mut SECURITY_ATTRIBUTES ;//lpSecurityAttributes
let disposition = fileapi::OPEN_EXISTING; //dwCreationDisposition
let atr = winnt::FILE_ATTRIBUTE_NORMAL;//dwFlagsAndAttributes
let handle = 0 as *mut c_void;//hTemplateFile
fileapi::CreateFileW(name,acces,share,security,disposition,atr,handle);
err();
}
}
cargo.toml
[package]
name = "createfile"
version = "0.1.0"
edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
kernel32-sys = "0.2.2"
winapi = "0.3.9"
[target.'cfg(windows)'.dependencies]
winapi = { version = "0.3.9", features = ["winuser", "fileapi", "errhandlingapi"] }
Error 2 is ERROR_FILE_NOT_FOUND.
You say you want to create a new file, but you are setting the disposition argument to OPEN_EXISTING, so if file.rs does not already exist then CreateFileW() will fail with this error.
Try using CREATE_ALWAYS, CREATE_NEW, or OPEN_ALWAYS, per the documentation:
[in] dwCreationDisposition
An action to take on a file or device that exists or does not exist.
For devices other than files, this parameter is usually set to OPEN_EXISTING.
For more information, see the Remarks section.
This parameter must be one of the following values, which cannot be combined:
Value
Meaning
CREATE_ALWAYS2
Creates a new file, always.If the specified file exists and is writable, the function overwrites the file, the function succeeds, and last-error code is set to ERROR_ALREADY_EXISTS (183).If the specified file does not exist and is a valid path, a new file is created, the function succeeds, and the last-error code is set to zero.For more information, see the Remarks section of this topic.
CREATE_NEW1
Creates a new file, only if it does not already exist.If the specified file exists, the function fails and the last-error code is set to ERROR_FILE_EXISTS (80).If the specified file does not exist and is a valid path to a writable location, a new file is created.
OPEN_ALWAYS4
Opens a file, always.If the specified file exists, the function succeeds and the last-error code is set to ERROR_ALREADY_EXISTS (183).If the specified file does not exist and is a valid path to a writable location, the function creates a file and the last-error code is set to zero.
OPEN_EXISTING3
Opens a file or device, only if it exists.If the specified file or device does not exist, the function fails and the last-error code is set to ERROR_FILE_NOT_FOUND (2).For more information about devices, see the Remarks section.
TRUNCATE_EXISTING5
Opens a file and truncates it so that its size is zero bytes, only if it exists.If the specified file does not exist, the function fails and the last-error code is set to ERROR_FILE_NOT_FOUND (2).The calling process must open the file with the GENERIC_WRITE bit set as part of the dwDesiredAccess parameter.

Using custom class in XPC protocol

I’m attempting to write an XPC service using my own type on the withReply signature. The type/class has Xcode’s “target membership” of both the main app and the XPC service. However I am getting incompatible reply block signature in the debug output even though the same class is being used in the withReply signature however the Xcode target differs as I will explain below.
Note: This is being done in Swift using this project to get me started. Except there they use NSData instead of a custom type.
Details
For the purposes of this question I’ll use the following as an example
Custom class - Tweet - This class conforms to the NSSecureCoding protocol so that it can be passed between the main app and the XPC service
XPC Protocol - TweetTransfer with one method required func take(_ count: Int, withReply: ((Tweet) -> Void))
and then all the usual XPC boilerplate where I export an object conforming to TweetTransfer. The XPC service appears to launch but then transfer between it and the main app fails with
XPCWorker[11069:402719] <NSXPCConnection: 0x61800010e220> connection from pid 11066 received an undecodable message
The full message is below[1] but the only difference between the “wire” and “local” is that argument one is
wire - _TtC17MainApp5Tweet
local - _TtC23XPCWorker5Tweet
Where the Xcode target is different. Is that enough to throw it off? How then do I share code between an app and it's XPC service?
[1] Full error text
<NSXPCConnection: 0x61800010e220> connection from pid 11066 received an undecodable message (incompatible reply block signature (wire: <NSMethodSignature: 0x618000074ec0>
number of arguments = 2
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (#) '#?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (#) '#"_TtC17MainApp5Tweet"'
flags {isObject}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
class '_TtC17MainApp5Tweet'
vs local: <NSMethodSignature: 0x610000074740>
number of arguments = 2
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (#) '#?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
argument 1: -------- -------- -------- --------
type encoding (#) '#"_TtC23XPCWorker5Tweet"'
flags {isObject}
modifiers {}
frame {offset = 8, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
class '_TtC23XPCWorker5Tweet'
)
Update
Some more info regarding the protocol, remoteObjectProxy connection and Tweet object. This is the protocol used for the XPC calls:
#objc(TweetTransfer)
protocol TweetTransfer {
func take(_ count: Int, withReply: replyType)
}
typealias replyType = ((Tweet) -> Void)
I'm using a type alias for convenience. And then the Tweet object is very simple and just for testing (although somewhat complicated by supporting NSSecureCoding):
final class Tweet: NSObject, NSSecureCoding {
var name: String
var text: String
static var supportsSecureCoding = true
init(name: String, text: String) {
self.name = name
self.text = text
}
init?(coder aDecoder: NSCoder) {
guard let name = aDecoder.decodeObject(forKey: "name") as? String else {
fatalError("Could not deserialise name!")
}
guard let text = aDecoder.decodeObject(forKey: "text") as? String else {
fatalError("Could not deseralise text!")
}
self.name = name
self.text = text
}
func encode(with aCoder: NSCoder) {
aCoder.encode(name, forKey: "name")
aCoder.encode(text, forKey: "text")
}
}
and finally the point at which we call the remoteObjectProxy
guard let loader = workerConnection.remoteObjectProxyWithErrorHandler(handler) as? TweetTransfer else {
fatalError("Could not map worker to TweetTransfer protocol!")
}
var tweets = [Tweet]()
loader.take(1) { tweet in
tweets.append(tweet)
}
The full message is below but the only difference between the “wire” and “local” is that argument one is
wire - _TtC17MainApp5Tweet
local - _TtC23XPCWorker5Tweet
Where the Xcode target is different. Is that enough to throw it off? How then do I share code between an app and it's XPC service?
That is indeed enough to throw it off. Swift's namespacing makes the archived object appear as a different class. You can disable name spacing by declaring your Tweet object with;
#objc(Tweet) class Tweet: NSObject, NSSecureCoding { ... }
The name in #objc(name) is often presented as a way to present a different name in objc vs Swift, but it also has the effect of disabling Swift's name spacing.
From Using Swift with Cocoa and Objective-C
When you use the #objc(name) attribute on a Swift class, the class is made available in Objective-C without any namespacing. As a result, this attribute can also be useful when migrating an archivable Objective-C class to Swift. Because archived objects store the name of their class in the archive, you should use the #objc(name) attribute to specify the same name as your Objective-C class so that older archives can be unarchived by your new Swift class.
Another alternative is to move your custom object to a Framework. That framework target then becomes the namespace, and both the XPC and App would refer to the same namespace/class in the framework.

Xcode 8.1 swift 3 take forever to compile this code

I have this class in a project which previously use swift 2.3. When i migrated the project to swift 3, xcode took forever to compile and i saw it stuck at this class. I can not build the whole project because of this class. Is there a way to modify this class so the project can be built, it took Xcode forever to compile this piece of code. If i removed several properties from MyClass, Xcode will quickly compile again. Anyone has any idea on how to solve this problem?
import Foundation
class MyClass: NSObject {
var id: String = ""
var uid: String = ""
var uname: String = ""
var fname: String = ""
var txt: String = ""
var hay: Float = 0
var flag = false
var long: Double = 0
var lat: Double = 0
var altitude: Double = 0
var course: Double = 0
var speed: Double = 0
var lname: String = ""
var city: String = ""
var country: String = ""
var sublocal: String = ""
var subarea: String = ""
var thumb: String = ""
var trash = false
var date: Double = 0
var updated: Double = 0
var furl: String = ""
func toAnyObject() -> Any {
return [
"id": id,
"uid": uid,
"uname": uname,
"fname": fname,
"txt": txt,
"hay": hay,
"flag": flag,
"long": long,
"lat": lat,
"altitude": altitude,
"course": course,
"speed": speed,
"lname": lname,
"city": city,
"country": country,
"sublocal": sublocal,
"trash": trash,
"subarea": subarea,
"thumb": thumb,
"date": date,
"updated": updated,
"furl": furl
]
}
}
Rewrite without the big dictionary literal. So:
func toAnyObject() -> Any {
var d = [String:Any]()
d["id"] = id
d["uid"] = uid
// ... and so on ...
return d
}
If you're not doing so already adding a -Xfrontend -debug-time-function-bodies compiler flag to your project will list the time required to compile each function. That can be a useful way to identify what exactly is slow in your build. (See http://irace.me/swift-profiling or https://thatthinginswift.com/debug-long-compile-times-swift/).
In your case the compiler is probably struggling to determine the type of your dictionary literal. Looking at each key and value and then trying to find the most appropriate common type for all of them. If you specified a type then I expect the compiler will only need to verify that your literal matches that type and compile much more quickly:
let result: [String: Any] = ["id": id, ...]
return result
Sometimes the compiler slows down when you do implicit typing. If you Explicitly add the type information then the compiler will not need to calculate it. I can see that the properties of your class mostly have type information but not all of them. In your toAnyObject method, it seems like you want your object represented as a dictionary, yet you are converting it to type Any.
You are making a dictionary literal and offering no type information. Explicitly casting with "as" can help a lot.
When you convert something to type Any, Objective-C interprets that as id. Normally a swift dictionary would be bridged to an NSDictionary for Objective-c, but you are forcing it to be of type Any. What reason would the compiler have to bridge this to an NSDictionary? It probably boxes it since it thinks it is a struct and objective-c can't use swift structs.
Try to not confuse the compiler.

unable to use optional int "possibleNumber" in optional binding

I'm new to Swift and is trying to learn the concept of optional binding. I have came up with the following code:
let possibleNumber = Int("123")
possibleNumber.dynamicType
if let actualNumber = Int(possibleNumber){
print("\(possibleNumber) has an integer value of \(actualNumber)")
} else {
print("\(possibleNumber) could not be converted to an int")
}
Xcode playground output error message:
value of optional type "int?" not unwrapped, did you mean to use "!" or "?"
However, when I added the "!" to if let actualNumber = Int(possibleNumber!){
let possibleNumber = Int("123")
possibleNumber.dynamicType
if let actualNumber = Int(possibleNumber!){
print("\(possibleNumber) has an integer value of \(actualNumber)")
} else {
print("\(possibleNumber) could not be converted to an int")
}
Xcode display another error message:
initialiser for conditional binding must have Optional type, not int
Why is this happening?
The result of
let possibleNumber = Int("123")
is an optional Int - Int?
Then you're trying to create another Int with
Int(possibleNumber)
which does not work because the initializer expects a non-optional type.
The error message is related to the initializer rather than to the optional binding.
Try this to get the same error message.
let possibleNumber = Int("123")
let x = Int(possibleNumber)
In your second example when you initialize an Int with an implicit unwrapped Int! argument you get a non-optional Int and the compiler complains about the missing optional.
In the if let construct
if let actualNumber = Int(possibleNumber!){
print("\(possibleNumber) has an integer value of \(actualNumber)")
}
you don't need to use the Int initializer. You simply need to write
if let actualNumber = possibleNumber {
print("\(possibleNumber) has an integer value of \(actualNumber)")
}
Now Swift will try to unwrap possibleNumber. If the operation does succeed the unwrapped value is put inside actualNumber and the THEN block executed.

Temporary file path using swift

How to get a unique temporary file path using Swift/Cocoa on OS X?
Cocoa does not seem to provide a function for this, only NSTemporaryDirectory() which returns the path of the temporary directory. Using the BSD mktemp function requires a mutable C-string as argument.
Apple has been trying to move away from path-as-string and into NSURL. Here's one way:
Swift 3:
let directory = NSTemporaryDirectory()
let fileName = NSUUID().uuidString
// This returns a URL? even though it is an NSURL class method
let fullURL = NSURL.fileURL(withPathComponents: [directory, fileName])
Swift 2:
let directory = NSTemporaryDirectory()
let fileName = NSUUID().UUIDString
let fullURL = NSURL.fileURLWithPathComponents([directory, fileName])
Here is a possible method to use mkstemp() from Swift 3 and later. URL methods
are used to convert between URL instances and C strings representing the file system path:
// The template string:
let template = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("file.XXXXXX") as NSURL
// Fill buffer with a C string representing the local file system path.
var buffer = [Int8](repeating: 0, count: Int(PATH_MAX))
template.getFileSystemRepresentation(&buffer, maxLength: buffer.count)
// Create unique file name (and open file):
let fd = mkstemp(&buffer)
if fd != -1 {
// Create URL from file system string:
let url = URL(fileURLWithFileSystemRepresentation: buffer, isDirectory: false, relativeTo: nil)
print(url.path)
} else {
print("Error: " + String(cString: strerror(errno)))
}
Older code for Swift 2:
// The template string:
let template = NSURL(fileURLWithPath: NSTemporaryDirectory()).URLByAppendingPathComponent("file.XXXXXX")
// Fill buffer with a C string representing the local file system path.
var buffer = [Int8](count: Int(PATH_MAX), repeatedValue: 0)
template.getFileSystemRepresentation(&buffer, maxLength: buffer.count)
// Create unique file name (and open file):
let fd = mkstemp(&buffer)
if fd != -1 {
// Create URL from file system string:
let url = NSURL(fileURLWithFileSystemRepresentation: buffer, isDirectory: false, relativeToURL: nil)
print(url.path!)
} else {
print("Error: " + String(strerror(errno)))
}
Although NSTemporaryDirectory() does indeed return a temporary directory path for the current user, the documentation includes the following caveat:
See the FileManager method url(for:in:appropriateFor:create:) for the preferred means of finding the correct temporary directory.
Following that link, we are presented with the following:
You can use this method to create a new temporary directory. To do so, specify FileManager.SearchPathDirectory.itemReplacementDirectory for the directory parameter, userDomainMask for the domain parameter, and a URL for the url parameter which determines the volume of the returned URL.
For example, the following code results in a new temporary directory with a path in the form of /private/var/folders/d0/h37cw8ns3h1bfr_2gnwq2yyc0000gn/T/TemporaryItems/Untitled/:
let desktop = URL(fileURLWithPath: "/Users/jappleseed/Desktop/")
do {
let temporaryDirectory = try FileManager.default.url(
for: .itemReplacementDirectory,
in: .userDomainMask,
appropriateFor: desktop,
create: true
)
print(temporaryDirectory)
} catch {
// Handle the error.
}
(Note the the create parameter is ignored when creating a temporary directory.)
So what exactly is the difference between these two approaches? Well, here's what I get when I call the two different methods from the Swift REPL:
1> import Foundation
2> NSTemporaryDirectory()
$R0: String = "/var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/"
3> let desktop = URL(fileURLWithPath: "/Users/chris/Desktop/")
desktop: URL = "file:///Users/chris/Desktop/"
4> let temporaryDirectory = try FileManager.default.url(
5. for: .itemReplacementDirectory,
6. in: .userDomainMask,
7. appropriateFor: desktop,
8. create: true
9. )
temporaryDirectory: URL = "file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20repl_swift)/"
It appears that NSTemporaryDirectory() will always return the temporary directory path for the current user whereas FileManager's url(for:appropriateFor:create) will return a new temporary subdirectory each time it is called. For example, here are the directories returned by consecutive calls to url(for:in:appropriateFor:create:) from the Swift REPL:
file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20repl_swift)/
file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20repl_swift%202)/
file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20repl_swift%203)/
And here are the directories returned by consecutive calls to the same method from a Swift Playground:
file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20Xcode)/
file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20Xcode%202)/
file:///var/folders/n_/0_9q7d2d1ls5v9kx599y_tj00000gn/T/TemporaryItems/(A%20Document%20Being%20Saved%20By%20Xcode%203)/
The NSHipster article on temporary files seems to suggest that the FileManager method url(for:in:appropriateFor:create:) is intended to be used when staging a file to be moved to a more permanent location (such as the user's desktop in the example above), but I don't see why it couldn't also be used to simply get a unique subdirectory that will automatically be removed when you're done with it and where you shouldn't have to worry about files getting accidentally clobbered by other processes writing to the same temporary directory.
A Swift 3 one-liner inspired by the UUID based Swift 2 answer:
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(UUID().uuidString)
FileManager extension in Swift to get a temporary file URL. You can pass your own file name and extension, if needed.
public extension FileManager {
func temporaryFileURL(fileName: String = UUID().uuidString) -> URL? {
return URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(fileName)
}
}
Usage:
let tempURL = FileManager.default.temporaryFileURL()
let tempJPG = FileManager.default.temporaryFileURL(fileName: "temp.jpg")
Use a GUID (Globally Unique Identifier):
let directory :NSString = "directory"
let randomName = NSProcessInfo().globallyUniqueString
let path = directory.stringByAppendingPathComponent(randomName)
directory/3B635E49-813A-4324-B4B8-56279B42BEAB-36687-0002D962615DAE5F
I like the idea of this article: NSTemporary​Directory - NSHipster
This uses the NSTemporaryDirectory() for the temporary folder and ProcessInfo.processInfo.globallyUniqueString to generate a unique string.
Swift 4:
func uniqueTempFolderURL() -> URL
{
let folderName = ProcessInfo.processInfo.globallyUniqueString
return URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).appendingPathComponent(folderName)
}
Swift3
I came here looking for something like boost::filesystem::unique_path()
So I made this extension to the URL class.
extension URL {
func appendingUniquePathComponent(pathExtension: String? = nil) -> URL {
var pathComponent = UUID().uuidString
if let pathExtension = pathExtension {
pathComponent += ".\(pathExtension)"
}
return appendingPathComponent(pathComponent)
}
}
Usage:
let url0 = URL(fileURLWithPath: "/tmp/some/dir")
let url1 = url0.appendingUniquePathComponent(pathExtension: "jpg")
print("url1: \(url1)")
// url1: file:///tmp/some/dir/936324FF-EEDB-410E-AD09-E24D5EB4A24F.jpg

Resources