diff --git a/packages/executor-swift/Sources/Executor/AppleScriptRunner.swift b/packages/executor-swift/Sources/Executor/AppleScriptRunner.swift index 5da50da..d983162 100644 --- a/packages/executor-swift/Sources/Executor/AppleScriptRunner.swift +++ b/packages/executor-swift/Sources/Executor/AppleScriptRunner.swift @@ -23,7 +23,9 @@ enum AppleScriptRunner { /// Builds a script from a template ID and parameters, then executes it. static func executeTemplate(templateId: String, bundleId: String, parameters: [String: Any]) throws -> [String: Any] { let script = try buildTemplateScript(templateId: templateId, bundleId: bundleId, parameters: parameters) - return try execute(script: script) + let wrappedScript = JsonEscape.wrapScript(script) + let rawResult = try execute(script: wrappedScript) + return JsonEscape.reserialize(rawResult) } /// Converts an NSAppleEventDescriptor to a Swift dictionary representation. @@ -66,7 +68,8 @@ enum AppleScriptRunner { /// Builds an AppleScript string from a template identifier and parameters (public for dryRun). static func buildScript(templateId: String, bundleId: String, parameters: [String: Any]) throws -> String { - return try buildTemplateScript(templateId: templateId, bundleId: bundleId, parameters: parameters) + let script = try buildTemplateScript(templateId: templateId, bundleId: bundleId, parameters: parameters) + return JsonEscape.wrapScript(script) } /// Builds an AppleScript string from a template identifier and parameters. diff --git a/packages/executor-swift/Sources/Executor/CalendarTemplates.swift b/packages/executor-swift/Sources/Executor/CalendarTemplates.swift index e12b241..922060d 100644 --- a/packages/executor-swift/Sources/Executor/CalendarTemplates.swift +++ b/packages/executor-swift/Sources/Executor/CalendarTemplates.swift @@ -37,7 +37,7 @@ enum CalendarTemplates { set output to "[" repeat with i from 1 to count of calList set c to item i of calList - set output to output & "{\\"id\\":\\"" & calId of c & "\\",\\"name\\":\\"" & calName of c & "\\",\\"type\\":\\"calendar\\"}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(calId of c) & "\\",\\"name\\":\\"" & my jsonEsc(calName of c) & "\\",\\"type\\":\\"calendar\\"}" if i < (count of calList) then set output to output & "," end repeat set output to output & "]" @@ -70,7 +70,7 @@ enum CalendarTemplates { set eSummary to summary of e set eStart to start date of e as «class isot» as string set eEnd to end date of e as «class isot» as string - set output to output & "{\\"id\\":\\"" & eId & "\\",\\"name\\":\\"" & eSummary & "\\",\\"type\\":\\"event\\",\\"properties\\":{\\"startDate\\":\\"" & eStart & "\\",\\"endDate\\":\\"" & eEnd & "\\"}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(eId) & "\\",\\"name\\":\\"" & my jsonEsc(eSummary) & "\\",\\"type\\":\\"event\\",\\"properties\\":{\\"startDate\\":\\"" & my jsonEsc(eStart) & "\\",\\"endDate\\":\\"" & my jsonEsc(eEnd) & "\\"}}" if i < resultCount then set output to output & "," end repeat set output to output & "]}" @@ -101,7 +101,7 @@ enum CalendarTemplates { set eLoc to location of e set eDesc to description of e set eAllDay to allday event of e - return "{\\"id\\":\\"" & eId & "\\",\\"name\\":\\"" & eSummary & "\\",\\"type\\":\\"event\\",\\"properties\\":{\\"startDate\\":\\"" & eStart & "\\",\\"endDate\\":\\"" & eEnd & "\\",\\"location\\":\\"" & eLoc & "\\",\\"description\\":\\"" & eDesc & "\\",\\"allDay\\":" & eAllDay & "}}" + return "{\\"id\\":\\"" & my jsonEsc(eId) & "\\",\\"name\\":\\"" & my jsonEsc(eSummary) & "\\",\\"type\\":\\"event\\",\\"properties\\":{\\"startDate\\":\\"" & my jsonEsc(eStart) & "\\",\\"endDate\\":\\"" & my jsonEsc(eEnd) & "\\",\\"location\\":\\"" & my jsonEsc(eLoc) & "\\",\\"description\\":\\"" & my jsonEsc(eDesc) & "\\",\\"allDay\\":" & eAllDay & "}}" end tell """ } @@ -128,7 +128,7 @@ enum CalendarTemplates { set eId to uid of e set eSummary to summary of e set eStart to start date of e as «class isot» as string - set output to output & "{\\"id\\":\\"" & eId & "\\",\\"name\\":\\"" & eSummary & "\\",\\"type\\":\\"event\\",\\"properties\\":{\\"startDate\\":\\"" & eStart & "\\"}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(eId) & "\\",\\"name\\":\\"" & my jsonEsc(eSummary) & "\\",\\"type\\":\\"event\\",\\"properties\\":{\\"startDate\\":\\"" & my jsonEsc(eStart) & "\\"}}" if i < resultCount then set output to output & "," end repeat set output to output & "]" @@ -161,7 +161,7 @@ enum CalendarTemplates { set endDate to date "\(esc(endDate))" set newEvent to make new event at end of events of targetCalendar with properties {summary:"\(esc(title))", start date:startDate, end date:endDate, location:"\(location)", description:"\(notes)", allday event:\(allDay)} set eId to uid of newEvent - return "{\\"id\\":\\"" & eId & "\\",\\"name\\":\\"\(esc(title))\\",\\"type\\":\\"event\\"}" + return "{\\"id\\":\\"" & my jsonEsc(eId) & "\\",\\"name\\":\\"\(esc(title))\\",\\"type\\":\\"event\\"}" end tell """ } @@ -203,7 +203,7 @@ enum CalendarTemplates { if (count of flatEvents) is 0 then error "Event not found: \(esc(eventId))" set e to item 1 of flatEvents \(setStatements.joined(separator: "\n ")) - return "{\\"id\\":\\"" & (uid of e) & "\\",\\"name\\":\\"" & (summary of e) & "\\",\\"type\\":\\"event\\"}" + return "{\\"id\\":\\"" & my jsonEsc(uid of e) & "\\",\\"name\\":\\"" & my jsonEsc(summary of e) & "\\",\\"type\\":\\"event\\"}" end tell """ } @@ -225,7 +225,7 @@ enum CalendarTemplates { set e to item 1 of flatEvents set eName to summary of e delete e - return "{\\"deleted\\":true,\\"name\\":\\"" & eName & "\\"}" + return "{\\"deleted\\":true,\\"name\\":\\"" & my jsonEsc(eName) & "\\"}" end tell """ } diff --git a/packages/executor-swift/Sources/Executor/ContactsTemplates.swift b/packages/executor-swift/Sources/Executor/ContactsTemplates.swift index 3b9d7bd..92acc74 100644 --- a/packages/executor-swift/Sources/Executor/ContactsTemplates.swift +++ b/packages/executor-swift/Sources/Executor/ContactsTemplates.swift @@ -37,7 +37,7 @@ enum ContactsTemplates { set output to "[" repeat with i from 1 to count of groupList set g to item i of groupList - set output to output & "{\\"id\\":\\"" & groupId of g & "\\",\\"name\\":\\"" & groupName of g & "\\",\\"type\\":\\"group\\",\\"itemCount\\":" & (personCount of g as text) & "}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(groupId of g) & "\\",\\"name\\":\\"" & my jsonEsc(groupName of g) & "\\",\\"type\\":\\"group\\",\\"itemCount\\":" & (personCount of g as text) & "}" if i < (count of groupList) then set output to output & "," end repeat set output to output & "]" @@ -73,7 +73,7 @@ enum ContactsTemplates { set pFirst to first name of p set pLast to last name of p set pName to pFirst & " " & pLast - set output to output & "{\\"id\\":\\"" & pId & "\\",\\"name\\":\\"" & pName & "\\",\\"type\\":\\"person\\",\\"properties\\":{\\"firstName\\":\\"" & pFirst & "\\",\\"lastName\\":\\"" & pLast & "\\"}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(pId) & "\\",\\"name\\":\\"" & my jsonEsc(pName) & "\\",\\"type\\":\\"person\\",\\"properties\\":{\\"firstName\\":\\"" & my jsonEsc(pFirst) & "\\",\\"lastName\\":\\"" & my jsonEsc(pLast) & "\\"}}" if i < endIdx then set output to output & "," end repeat end if @@ -106,7 +106,7 @@ enum ContactsTemplates { if phoneList is not "" then set phoneList to phoneList & ", " set phoneList to phoneList & value of ph end repeat - return "{\\"id\\":\\"" & pId & "\\",\\"name\\":\\"" & pFirst & " " & pLast & "\\",\\"type\\":\\"person\\",\\"properties\\":{\\"firstName\\":\\"" & pFirst & "\\",\\"lastName\\":\\"" & pLast & "\\",\\"organization\\":\\"" & pOrg & "\\",\\"jobTitle\\":\\"" & pTitle & "\\",\\"note\\":\\"" & pNote & "\\",\\"emails\\":\\"" & emailList & "\\",\\"phones\\":\\"" & phoneList & "\\"}}" + return "{\\"id\\":\\"" & my jsonEsc(pId) & "\\",\\"name\\":\\"" & my jsonEsc(pFirst) & " " & my jsonEsc(pLast) & "\\",\\"type\\":\\"person\\",\\"properties\\":{\\"firstName\\":\\"" & my jsonEsc(pFirst) & "\\",\\"lastName\\":\\"" & my jsonEsc(pLast) & "\\",\\"organization\\":\\"" & my jsonEsc(pOrg) & "\\",\\"jobTitle\\":\\"" & my jsonEsc(pTitle) & "\\",\\"note\\":\\"" & my jsonEsc(pNote) & "\\",\\"emails\\":\\"" & my jsonEsc(emailList) & "\\",\\"phones\\":\\"" & my jsonEsc(phoneList) & "\\"}}" end tell """ } @@ -128,7 +128,7 @@ enum ContactsTemplates { set pId to id of p set pFirst to first name of p set pLast to last name of p - set output to output & "{\\"id\\":\\"" & pId & "\\",\\"name\\":\\"" & pFirst & " " & pLast & "\\",\\"type\\":\\"person\\"}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(pId) & "\\",\\"name\\":\\"" & my jsonEsc(pFirst) & " " & my jsonEsc(pLast) & "\\",\\"type\\":\\"person\\"}" if i < resultCount then set output to output & "," end repeat set output to output & "]" @@ -170,7 +170,7 @@ enum ContactsTemplates { \(extraLines.joined(separator: "\n ")) save set pId to id of newPerson - return "{\\"id\\":\\"" & pId & "\\",\\"name\\":\\"\(esc(firstName)) \(lastName)\\",\\"type\\":\\"person\\"}" + return "{\\"id\\":\\"" & my jsonEsc(pId) & "\\",\\"name\\":\\"\(esc(firstName)) \(lastName)\\",\\"type\\":\\"person\\"}" end tell """ } @@ -205,7 +205,7 @@ enum ContactsTemplates { set p to person id "\(esc(personId))" \(setStatements.joined(separator: "\n ")) save - return "{\\"id\\":\\"" & (id of p) & "\\",\\"name\\":\\"" & (first name of p) & " " & (last name of p) & "\\",\\"type\\":\\"person\\"}" + return "{\\"id\\":\\"" & my jsonEsc(id of p) & "\\",\\"name\\":\\"" & my jsonEsc(first name of p) & " " & my jsonEsc(last name of p) & "\\",\\"type\\":\\"person\\"}" end tell """ } @@ -220,7 +220,7 @@ enum ContactsTemplates { set pName to first name of p & " " & last name of p delete p save - return "{\\"deleted\\":true,\\"name\\":\\"" & pName & "\\"}" + return "{\\"deleted\\":true,\\"name\\":\\"" & my jsonEsc(pName) & "\\"}" end tell """ } diff --git a/packages/executor-swift/Sources/Executor/FinderTemplates.swift b/packages/executor-swift/Sources/Executor/FinderTemplates.swift index a1a6c6b..1ec3c95 100644 --- a/packages/executor-swift/Sources/Executor/FinderTemplates.swift +++ b/packages/executor-swift/Sources/Executor/FinderTemplates.swift @@ -43,7 +43,7 @@ enum FinderTemplates { set output to "[" repeat with i from 1 to count of folderList set f to item i of folderList - set output to output & "{\\"id\\":\\"" & fPath of f & "\\",\\"name\\":\\"" & fName of f & "\\",\\"type\\":\\"folder\\"}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(fPath of f) & "\\",\\"name\\":\\"" & my jsonEsc(fName of f) & "\\",\\"type\\":\\"folder\\"}" if i < (count of folderList) then set output to output & "," end repeat set output to output & "]" @@ -74,7 +74,7 @@ enum FinderTemplates { set fKind to kind of f set fSize to size of f set fDate to modification date of f as «class isot» as string - set output to output & "{\\"id\\":\\"" & fPath & "\\",\\"name\\":\\"" & fName & "\\",\\"type\\":\\"file\\",\\"modifiedAt\\":\\"" & fDate & "\\",\\"properties\\":{\\"kind\\":\\"" & fKind & "\\",\\"size\\":" & fSize & "}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(fPath) & "\\",\\"name\\":\\"" & my jsonEsc(fName) & "\\",\\"type\\":\\"file\\",\\"modifiedAt\\":\\"" & my jsonEsc(fDate) & "\\",\\"properties\\":{\\"kind\\":\\"" & my jsonEsc(fKind) & "\\",\\"size\\":" & fSize & "}}" if i < endIdx then set output to output & "," end repeat end if @@ -97,7 +97,7 @@ enum FinderTemplates { set fSize to size of fi set fCreated to creation date of fi as «class isot» as string set fModified to modification date of fi as «class isot» as string - return "{\\"id\\":\\"" & POSIX path of f & "\\",\\"name\\":\\"" & fName & "\\",\\"type\\":\\"file\\",\\"createdAt\\":\\"" & fCreated & "\\",\\"modifiedAt\\":\\"" & fModified & "\\",\\"properties\\":{\\"kind\\":\\"" & fKind & "\\",\\"size\\":" & fSize & "}}" + return "{\\"id\\":\\"" & my jsonEsc(POSIX path of f) & "\\",\\"name\\":\\"" & my jsonEsc(fName) & "\\",\\"type\\":\\"file\\",\\"createdAt\\":\\"" & my jsonEsc(fCreated) & "\\",\\"modifiedAt\\":\\"" & my jsonEsc(fModified) & "\\",\\"properties\\":{\\"kind\\":\\"" & my jsonEsc(fKind) & "\\",\\"size\\":" & fSize & "}}" end tell """ } @@ -120,7 +120,7 @@ enum FinderTemplates { set f to item i of matchingItems set fName to name of f set fPath to POSIX path of (f as alias) - set output to output & "{\\"id\\":\\"" & fPath & "\\",\\"name\\":\\"" & fName & "\\",\\"type\\":\\"file\\"}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(fPath) & "\\",\\"name\\":\\"" & my jsonEsc(fName) & "\\",\\"type\\":\\"file\\"}" if i < resultCount then set output to output & "," end repeat set output to output & "]" @@ -141,7 +141,7 @@ enum FinderTemplates { set parentFolder to POSIX file "\(esc(parentPath))" as alias set newFolder to make new folder at folder parentFolder with properties {name:"\(esc(name))"} set fPath to POSIX path of (newFolder as alias) - return "{\\"id\\":\\"" & fPath & "\\",\\"name\\":\\"\(esc(name))\\",\\"type\\":\\"folder\\"}" + return "{\\"id\\":\\"" & my jsonEsc(fPath) & "\\",\\"name\\":\\"\(esc(name))\\",\\"type\\":\\"folder\\"}" end tell """ } @@ -181,7 +181,7 @@ enum FinderTemplates { set sourceItem to POSIX file "\(esc(sourcePath))" as alias set dupItem to duplicate sourceItem\(destClause) set dupPath to POSIX path of (dupItem as alias) - return "{\\"duplicated\\":true,\\"path\\":\\"" & dupPath & "\\"}" + return "{\\"duplicated\\":true,\\"path\\":\\"" & my jsonEsc(dupPath) & "\\"}" end tell """ } @@ -195,7 +195,7 @@ enum FinderTemplates { set targetItem to POSIX file "\(esc(path))" as alias set itemName to name of targetItem delete targetItem - return "{\\"deleted\\":true,\\"name\\":\\"" & itemName & "\\"}" + return "{\\"deleted\\":true,\\"name\\":\\"" & my jsonEsc(itemName) & "\\"}" end tell """ } diff --git a/packages/executor-swift/Sources/Executor/JsonEscape.swift b/packages/executor-swift/Sources/Executor/JsonEscape.swift new file mode 100644 index 0000000..4335366 --- /dev/null +++ b/packages/executor-swift/Sources/Executor/JsonEscape.swift @@ -0,0 +1,69 @@ +import Foundation + +/// Shared AppleScript escape handlers and Swift-side JSON safety net. +enum JsonEscape { + + // MARK: - AppleScript Handlers + + /// AppleScript handlers that escape string values for safe JSON embedding. + /// Appended to every template script by AppleScriptRunner. + /// + /// `jsonEsc(s)` escapes: \ → \\, " → \", CR → \n, LF → \n, tab → \t + /// `replaceText(theString, old, new)` helper for text item delimiter-based replacement. + static let handlers: String = #""" + +on jsonEsc(s) + set s to s as text + set s to my replaceText(s, "\\", "\\\\") + set s to my replaceText(s, "\"", "\\" & quote) + set s to my replaceText(s, return, "\\n") + set s to my replaceText(s, linefeed, "\\n") + set s to my replaceText(s, tab, "\\t") + return s +end jsonEsc + +on replaceText(theString, old, new) + set AppleScript's text item delimiters to old + set theItems to every text item of theString + set AppleScript's text item delimiters to new + set theString to theItems as string + set AppleScript's text item delimiters to "" + return theString +end replaceText +"""# + + // MARK: - Script Wrapping + + /// Appends the jsonEsc handlers to a template script. + static func wrapScript(_ script: String) -> String { + script + handlers + } + + // MARK: - Swift Safety Net + + /// Attempts to parse the executor result's "value" as JSON and re-serialize it. + /// If the value is valid JSON, returns a dict with the parsed object under "value". + /// If not JSON (or parse fails), returns the original dict unchanged. + static func reserialize(_ result: [String: Any]) -> [String: Any] { + guard let stringValue = result["value"] as? String, + !stringValue.isEmpty, + let firstChar = stringValue.first, + (firstChar == "{" || firstChar == "[") else { + return result + } + + guard let data = stringValue.data(using: .utf8), + let parsed = try? JSONSerialization.jsonObject(with: data, options: []) else { + // JSON parse failed — return raw string; the MCP client will see the error + return result + } + + // Re-serialize to guarantee proper escaping + if let reData = try? JSONSerialization.data(withJSONObject: parsed, options: [.sortedKeys]), + let reString = String(data: reData, encoding: .utf8) { + return ["value": reString] + } + + return result + } +} diff --git a/packages/executor-swift/Sources/Executor/MailTemplates.swift b/packages/executor-swift/Sources/Executor/MailTemplates.swift index 97d91d5..a89f66e 100644 --- a/packages/executor-swift/Sources/Executor/MailTemplates.swift +++ b/packages/executor-swift/Sources/Executor/MailTemplates.swift @@ -41,7 +41,7 @@ enum MailTemplates { set output to "[" repeat with i from 1 to count of mboxList set m to item i of mboxList - set output to output & "{\\"id\\":\\"" & acctName of m & "/" & mboxName of m & "\\",\\"name\\":\\"" & mboxName of m & "\\",\\"type\\":\\"mailbox\\",\\"itemCount\\":" & (msgCount of m as text) & ",\\"properties\\":{\\"account\\":\\"" & acctName of m & "\\"}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(acctName of m) & "/" & my jsonEsc(mboxName of m) & "\\",\\"name\\":\\"" & my jsonEsc(mboxName of m) & "\\",\\"type\\":\\"mailbox\\",\\"itemCount\\":" & (msgCount of m as text) & ",\\"properties\\":{\\"account\\":\\"" & my jsonEsc(acctName of m) & "\\"}}" if i < (count of mboxList) then set output to output & "," end repeat set output to output & "]" @@ -72,7 +72,7 @@ enum MailTemplates { set mSender to sender of m set mDate to date received of m as «class isot» as string set mRead to read status of m - set output to output & "{\\"id\\":" & mId & ",\\"name\\":\\"" & mSubject & "\\",\\"type\\":\\"message\\",\\"properties\\":{\\"sender\\":\\"" & mSender & "\\",\\"dateReceived\\":\\"" & mDate & "\\",\\"read\\":" & mRead & "}}" + set output to output & "{\\"id\\":" & mId & ",\\"name\\":\\"" & my jsonEsc(mSubject) & "\\",\\"type\\":\\"message\\",\\"properties\\":{\\"sender\\":\\"" & my jsonEsc(mSender) & "\\",\\"dateReceived\\":\\"" & my jsonEsc(mDate) & "\\",\\"read\\":" & mRead & "}}" if i < endIdx then set output to output & "," end repeat end if @@ -100,7 +100,7 @@ enum MailTemplates { if recipList is not "" then set recipList to recipList & ", " set recipList to recipList & address of r end repeat - return "{\\"id\\":" & mId & ",\\"name\\":\\"" & mSubject & "\\",\\"type\\":\\"message\\",\\"properties\\":{\\"sender\\":\\"" & mSender & "\\",\\"to\\":\\"" & recipList & "\\",\\"dateReceived\\":\\"" & mDate & "\\",\\"read\\":" & mRead & ",\\"body\\":\\"" & mContent & "\\"}}" + return "{\\"id\\":" & mId & ",\\"name\\":\\"" & my jsonEsc(mSubject) & "\\",\\"type\\":\\"message\\",\\"properties\\":{\\"sender\\":\\"" & my jsonEsc(mSender) & "\\",\\"to\\":\\"" & my jsonEsc(recipList) & "\\",\\"dateReceived\\":\\"" & my jsonEsc(mDate) & "\\",\\"read\\":" & mRead & ",\\"body\\":\\"" & my jsonEsc(mContent) & "\\"}}" end tell """ } @@ -132,7 +132,7 @@ enum MailTemplates { set mSubject to subject of m set mSender to sender of m set mDate to date received of m as «class isot» as string - set output to output & "{\\"id\\":" & mId & ",\\"name\\":\\"" & mSubject & "\\",\\"type\\":\\"message\\",\\"properties\\":{\\"sender\\":\\"" & mSender & "\\",\\"dateReceived\\":\\"" & mDate & "\\"}}" + set output to output & "{\\"id\\":" & mId & ",\\"name\\":\\"" & my jsonEsc(mSubject) & "\\",\\"type\\":\\"message\\",\\"properties\\":{\\"sender\\":\\"" & my jsonEsc(mSender) & "\\",\\"dateReceived\\":\\"" & my jsonEsc(mDate) & "\\"}}" if i < resultCount then set output to output & "," end repeat set output to output & "]" @@ -156,7 +156,7 @@ enum MailTemplates { tell newMessage make new to recipient at end of to recipients with properties {address:"\(esc(to))"} end tell - return "{\\"id\\":\\"draft\\",\\"name\\":\\"" & subject of newMessage & "\\",\\"type\\":\\"message\\"}" + return "{\\"id\\":\\"draft\\",\\"name\\":\\"" & my jsonEsc(subject of newMessage) & "\\",\\"type\\":\\"message\\"}" end tell """ } @@ -181,7 +181,7 @@ enum MailTemplates { tell application id "\(bundleId)" set m to first message of mailboxes whose id is \(esc(messageId)) \(setStatements.joined(separator: "\n ")) - return "{\\"id\\":" & (id of m) & ",\\"name\\":\\"" & (subject of m) & "\\",\\"type\\":\\"message\\"}" + return "{\\"id\\":" & (id of m) & ",\\"name\\":\\"" & my jsonEsc(subject of m) & "\\",\\"type\\":\\"message\\"}" end tell """ } @@ -195,7 +195,7 @@ enum MailTemplates { set m to first message of mailboxes whose id is \(esc(messageId)) set mSubject to subject of m delete m - return "{\\"deleted\\":true,\\"name\\":\\"" & mSubject & "\\"}" + return "{\\"deleted\\":true,\\"name\\":\\"" & my jsonEsc(mSubject) & "\\"}" end tell """ } diff --git a/packages/executor-swift/Sources/Executor/MessagesTemplates.swift b/packages/executor-swift/Sources/Executor/MessagesTemplates.swift index eab7f2f..fe4d0c4 100644 --- a/packages/executor-swift/Sources/Executor/MessagesTemplates.swift +++ b/packages/executor-swift/Sources/Executor/MessagesTemplates.swift @@ -40,7 +40,7 @@ enum MessagesTemplates { set output to "[" repeat with i from 1 to count of chatList set c to item i of chatList - set output to output & "{\\"id\\":\\"" & chatId of c & "\\",\\"name\\":\\"" & chatName of c & "\\",\\"type\\":\\"chat\\",\\"properties\\":{\\"participants\\":\\"" & chatParticipants of c & "\\"}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(chatId of c) & "\\",\\"name\\":\\"" & my jsonEsc(chatName of c) & "\\",\\"type\\":\\"chat\\",\\"properties\\":{\\"participants\\":\\"" & my jsonEsc(chatParticipants of c) & "\\"}}" if i < (count of chatList) then set output to output & "," end repeat set output to output & "]" @@ -75,7 +75,7 @@ enum MessagesTemplates { set mSender to name of sender of m end try set mDate to date of m as «class isot» as string - set output to output & "{\\"id\\":" & mId & ",\\"name\\":\\"message\\",\\"type\\":\\"message\\",\\"properties\\":{\\"sender\\":\\"" & mSender & "\\",\\"date\\":\\"" & mDate & "\\"}}" + set output to output & "{\\"id\\":" & mId & ",\\"name\\":\\"message\\",\\"type\\":\\"message\\",\\"properties\\":{\\"sender\\":\\"" & my jsonEsc(mSender) & "\\",\\"date\\":\\"" & my jsonEsc(mDate) & "\\"}}" if i < resultCount then set output to output & "," end repeat set output to output & "]}" @@ -99,7 +99,7 @@ enum MessagesTemplates { set participantNames to participantNames & name of p end repeat set msgCount to count of messages of c - return "{\\"id\\":\\"" & cId & "\\",\\"name\\":\\"" & cName & "\\",\\"type\\":\\"chat\\",\\"properties\\":{\\"participants\\":\\"" & participantNames & "\\",\\"messageCount\\":" & (msgCount as text) & "}}" + return "{\\"id\\":\\"" & my jsonEsc(cId) & "\\",\\"name\\":\\"" & my jsonEsc(cName) & "\\",\\"type\\":\\"chat\\",\\"properties\\":{\\"participants\\":\\"" & my jsonEsc(participantNames) & "\\",\\"messageCount\\":" & (msgCount as text) & "}}" end tell """ } @@ -120,7 +120,7 @@ enum MessagesTemplates { set c to item i of matchingChats set cId to id of c set cName to name of c - set output to output & "{\\"id\\":\\"" & cId & "\\",\\"name\\":\\"" & cName & "\\",\\"type\\":\\"chat\\"}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(cId) & "\\",\\"name\\":\\"" & my jsonEsc(cName) & "\\",\\"type\\":\\"chat\\"}" if i < resultCount then set output to output & "," end repeat set output to output & "]" diff --git a/packages/executor-swift/Sources/Executor/MusicTemplates.swift b/packages/executor-swift/Sources/Executor/MusicTemplates.swift index ef17782..08a459a 100644 --- a/packages/executor-swift/Sources/Executor/MusicTemplates.swift +++ b/packages/executor-swift/Sources/Executor/MusicTemplates.swift @@ -44,7 +44,7 @@ enum MusicTemplates { set output to "[" repeat with i from 1 to count of playlistList set p to item i of playlistList - set output to output & "{\\"id\\":\\"" & pId of p & "\\",\\"name\\":\\"" & pName of p & "\\",\\"type\\":\\"playlist\\",\\"itemCount\\":" & (trackCount of p as text) & "}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(pId of p) & "\\",\\"name\\":\\"" & my jsonEsc(pName of p) & "\\",\\"type\\":\\"playlist\\",\\"itemCount\\":" & (trackCount of p as text) & "}" if i < (count of playlistList) then set output to output & "," end repeat set output to output & "]" @@ -81,7 +81,7 @@ enum MusicTemplates { set tArtist to artist of t set tAlbum to album of t set tDuration to duration of t - set output to output & "{\\"id\\":\\"" & tId & "\\",\\"name\\":\\"" & tName & "\\",\\"type\\":\\"track\\",\\"properties\\":{\\"artist\\":\\"" & tArtist & "\\",\\"album\\":\\"" & tAlbum & "\\",\\"duration\\":" & tDuration & "}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(tId) & "\\",\\"name\\":\\"" & my jsonEsc(tName) & "\\",\\"type\\":\\"track\\",\\"properties\\":{\\"artist\\":\\"" & my jsonEsc(tArtist) & "\\",\\"album\\":\\"" & my jsonEsc(tAlbum) & "\\",\\"duration\\":" & tDuration & "}}" if i < endIdx then set output to output & "," end repeat end if @@ -107,7 +107,7 @@ enum MusicTemplates { set tYear to year of t set tRating to rating of t set tPlays to played count of t - return "{\\"id\\":\\"" & tId & "\\",\\"name\\":\\"" & tName & "\\",\\"type\\":\\"track\\",\\"properties\\":{\\"artist\\":\\"" & tArtist & "\\",\\"album\\":\\"" & tAlbum & "\\",\\"duration\\":" & tDuration & ",\\"genre\\":\\"" & tGenre & "\\",\\"year\\":" & tYear & ",\\"rating\\":" & tRating & ",\\"playCount\\":" & tPlays & "}}" + return "{\\"id\\":\\"" & my jsonEsc(tId) & "\\",\\"name\\":\\"" & my jsonEsc(tName) & "\\",\\"type\\":\\"track\\",\\"properties\\":{\\"artist\\":\\"" & my jsonEsc(tArtist) & "\\",\\"album\\":\\"" & my jsonEsc(tAlbum) & "\\",\\"duration\\":" & tDuration & ",\\"genre\\":\\"" & my jsonEsc(tGenre) & "\\",\\"year\\":" & tYear & ",\\"rating\\":" & tRating & ",\\"playCount\\":" & tPlays & "}}" end tell """ } @@ -129,7 +129,7 @@ enum MusicTemplates { set tId to id of t set tName to name of t set tArtist to artist of t - set output to output & "{\\"id\\":\\"" & tId & "\\",\\"name\\":\\"" & tName & "\\",\\"type\\":\\"track\\",\\"properties\\":{\\"artist\\":\\"" & tArtist & "\\"}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(tId) & "\\",\\"name\\":\\"" & my jsonEsc(tName) & "\\",\\"type\\":\\"track\\",\\"properties\\":{\\"artist\\":\\"" & my jsonEsc(tArtist) & "\\"}}" if i < resultCount then set output to output & "," end repeat set output to output & "]" @@ -149,7 +149,7 @@ enum MusicTemplates { tell application id "\(bundleId)" set newPlaylist to make new playlist with properties {name:"\(esc(name))", description:"\(description)"} set pId to id of newPlaylist - return "{\\"id\\":\\"" & pId & "\\",\\"name\\":\\"\(esc(name))\\",\\"type\\":\\"playlist\\"}" + return "{\\"id\\":\\"" & my jsonEsc(pId) & "\\",\\"name\\":\\"\(esc(name))\\",\\"type\\":\\"playlist\\"}" end tell """ } @@ -163,7 +163,7 @@ enum MusicTemplates { tell application id "\(bundleId)" set t to first track of library playlist 1 whose id is "\(esc(trackId))" play t - return "{\\"playing\\":true,\\"track\\":\\"" & (name of t) & "\\"}" + return "{\\"playing\\":true,\\"track\\":\\"" & my jsonEsc(name of t) & "\\"}" end tell """ } @@ -190,7 +190,7 @@ enum MusicTemplates { next track delay 0.5 set t to current track - return "{\\"track\\":\\"" & (name of t) & "\\",\\"artist\\":\\"" & (artist of t) & "\\"}" + return "{\\"track\\":\\"" & my jsonEsc(name of t) & "\\",\\"artist\\":\\"" & my jsonEsc(artist of t) & "\\"}" end tell """ } @@ -201,7 +201,7 @@ enum MusicTemplates { previous track delay 0.5 set t to current track - return "{\\"track\\":\\"" & (name of t) & "\\",\\"artist\\":\\"" & (artist of t) & "\\"}" + return "{\\"track\\":\\"" & my jsonEsc(name of t) & "\\",\\"artist\\":\\"" & my jsonEsc(artist of t) & "\\"}" end tell """ } @@ -216,7 +216,7 @@ enum MusicTemplates { set tAlbum to album of t set tPos to player position set tDur to duration of t - return "{\\"playing\\":true,\\"track\\":\\"" & tName & "\\",\\"artist\\":\\"" & tArtist & "\\",\\"album\\":\\"" & tAlbum & "\\",\\"position\\":" & tPos & ",\\"duration\\":" & tDur & "}" + return "{\\"playing\\":true,\\"track\\":\\"" & my jsonEsc(tName) & "\\",\\"artist\\":\\"" & my jsonEsc(tArtist) & "\\",\\"album\\":\\"" & my jsonEsc(tAlbum) & "\\",\\"position\\":" & tPos & ",\\"duration\\":" & tDur & "}" else return "{\\"playing\\":false}" end if diff --git a/packages/executor-swift/Sources/Executor/NotesTemplates.swift b/packages/executor-swift/Sources/Executor/NotesTemplates.swift index 52627cf..6fddd1e 100644 --- a/packages/executor-swift/Sources/Executor/NotesTemplates.swift +++ b/packages/executor-swift/Sources/Executor/NotesTemplates.swift @@ -37,7 +37,7 @@ enum NotesTemplates { set output to "[" repeat with i from 1 to count of folderList set f to item i of folderList - set output to output & "{\\"id\\":\\"" & id of f & "\\",\\"name\\":\\"" & name of f & "\\",\\"type\\":\\"folder\\",\\"itemCount\\":" & (itemCount of f as text) & "}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(id of f) & "\\",\\"name\\":\\"" & my jsonEsc(name of f) & "\\",\\"type\\":\\"folder\\",\\"itemCount\\":" & (itemCount of f as text) & "}" if i < (count of folderList) then set output to output & "," end repeat set output to output & "]" @@ -72,7 +72,7 @@ enum NotesTemplates { set nId to id of n set nName to name of n set nDate to modification date of n as «class isot» as string - set output to output & "{\\"id\\":\\"" & nId & "\\",\\"name\\":\\"" & nName & "\\",\\"type\\":\\"note\\",\\"modifiedAt\\":\\"" & nDate & "\\"}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(nId) & "\\",\\"name\\":\\"" & my jsonEsc(nName) & "\\",\\"type\\":\\"note\\",\\"modifiedAt\\":\\"" & my jsonEsc(nDate) & "\\"}" if i < endIdx then set output to output & "," end repeat end if @@ -95,7 +95,7 @@ enum NotesTemplates { set nCreated to creation date of n as «class isot» as string set nModified to modification date of n as «class isot» as string set cName to name of container of n - return "{\\"id\\":\\"" & nId & "\\",\\"name\\":\\"" & nName & "\\",\\"type\\":\\"note\\",\\"containerName\\":\\"" & cName & "\\",\\"createdAt\\":\\"" & nCreated & "\\",\\"modifiedAt\\":\\"" & nModified & "\\",\\"properties\\":{\\"body\\":\\"" & nBody & "\\"}}" + return "{\\"id\\":\\"" & my jsonEsc(nId) & "\\",\\"name\\":\\"" & my jsonEsc(nName) & "\\",\\"type\\":\\"note\\",\\"containerName\\":\\"" & my jsonEsc(cName) & "\\",\\"createdAt\\":\\"" & my jsonEsc(nCreated) & "\\",\\"modifiedAt\\":\\"" & my jsonEsc(nModified) & "\\",\\"properties\\":{\\"body\\":\\"" & my jsonEsc(nBody) & "\\"}}" end tell """ } @@ -117,7 +117,7 @@ enum NotesTemplates { set nId to id of n set nName to name of n set nDate to modification date of n as «class isot» as string - set output to output & "{\\"id\\":\\"" & nId & "\\",\\"name\\":\\"" & nName & "\\",\\"type\\":\\"note\\",\\"modifiedAt\\":\\"" & nDate & "\\"}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(nId) & "\\",\\"name\\":\\"" & my jsonEsc(nName) & "\\",\\"type\\":\\"note\\",\\"modifiedAt\\":\\"" & my jsonEsc(nDate) & "\\"}" if i < resultCount then set output to output & "," end repeat set output to output & "]" @@ -145,7 +145,7 @@ enum NotesTemplates { set newNote to make new note \(targetClause) with properties {name:"\(title)", body:"\(body)"} set nId to id of newNote set nName to name of newNote - return "{\\"id\\":\\"" & nId & "\\",\\"name\\":\\"" & nName & "\\",\\"type\\":\\"note\\"}" + return "{\\"id\\":\\"" & my jsonEsc(nId) & "\\",\\"name\\":\\"" & my jsonEsc(nName) & "\\",\\"type\\":\\"note\\"}" end tell """ } @@ -170,7 +170,7 @@ enum NotesTemplates { tell application id "\(bundleId)" set n to note id "\(esc(noteId))" \(setStatements.joined(separator: "\n ")) - return "{\\"id\\":\\"" & (id of n) & "\\",\\"name\\":\\"" & (name of n) & "\\",\\"type\\":\\"note\\"}" + return "{\\"id\\":\\"" & my jsonEsc(id of n) & "\\",\\"name\\":\\"" & my jsonEsc(name of n) & "\\",\\"type\\":\\"note\\"}" end tell """ } @@ -184,7 +184,7 @@ enum NotesTemplates { set n to note id "\(esc(noteId))" set nName to name of n delete n - return "{\\"deleted\\":true,\\"name\\":\\"" & nName & "\\"}" + return "{\\"deleted\\":true,\\"name\\":\\"" & my jsonEsc(nName) & "\\"}" end tell """ } diff --git a/packages/executor-swift/Sources/Executor/PhotosTemplates.swift b/packages/executor-swift/Sources/Executor/PhotosTemplates.swift index e355937..3e96b5e 100644 --- a/packages/executor-swift/Sources/Executor/PhotosTemplates.swift +++ b/packages/executor-swift/Sources/Executor/PhotosTemplates.swift @@ -37,7 +37,7 @@ enum PhotosTemplates { set output to "[" repeat with i from 1 to count of albumList set a to item i of albumList - set output to output & "{\\"id\\":\\"" & albumId of a & "\\",\\"name\\":\\"" & albumName of a & "\\",\\"type\\":\\"album\\",\\"itemCount\\":" & (mediaCount of a as text) & "}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(albumId of a) & "\\",\\"name\\":\\"" & my jsonEsc(albumName of a) & "\\",\\"type\\":\\"album\\",\\"itemCount\\":" & (mediaCount of a as text) & "}" if i < (count of albumList) then set output to output & "," end repeat set output to output & "]" @@ -74,7 +74,7 @@ enum PhotosTemplates { set mDate to date of m as «class isot» as string set mWidth to width of m set mHeight to height of m - set output to output & "{\\"id\\":\\"" & mId & "\\",\\"name\\":\\"" & mName & "\\",\\"type\\":\\"media\\",\\"properties\\":{\\"date\\":\\"" & mDate & "\\",\\"width\\":" & mWidth & ",\\"height\\":" & mHeight & "}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(mId) & "\\",\\"name\\":\\"" & my jsonEsc(mName) & "\\",\\"type\\":\\"media\\",\\"properties\\":{\\"date\\":\\"" & my jsonEsc(mDate) & "\\",\\"width\\":" & mWidth & ",\\"height\\":" & mHeight & "}}" if i < endIdx then set output to output & "," end repeat end if @@ -99,7 +99,7 @@ enum PhotosTemplates { set mFav to favorite of m set mDesc to description of m set mLoc to location of m - return "{\\"id\\":\\"" & mId & "\\",\\"name\\":\\"" & mName & "\\",\\"type\\":\\"media\\",\\"properties\\":{\\"date\\":\\"" & mDate & "\\",\\"width\\":" & mWidth & ",\\"height\\":" & mHeight & ",\\"favorite\\":" & mFav & ",\\"description\\":\\"" & mDesc & "\\"}}" + return "{\\"id\\":\\"" & my jsonEsc(mId) & "\\",\\"name\\":\\"" & my jsonEsc(mName) & "\\",\\"type\\":\\"media\\",\\"properties\\":{\\"date\\":\\"" & my jsonEsc(mDate) & "\\",\\"width\\":" & mWidth & ",\\"height\\":" & mHeight & ",\\"favorite\\":" & mFav & ",\\"description\\":\\"" & my jsonEsc(mDesc) & "\\"}}" end tell """ } @@ -121,7 +121,7 @@ enum PhotosTemplates { set mId to id of m set mName to filename of m set mDate to date of m as «class isot» as string - set output to output & "{\\"id\\":\\"" & mId & "\\",\\"name\\":\\"" & mName & "\\",\\"type\\":\\"media\\",\\"properties\\":{\\"date\\":\\"" & mDate & "\\"}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(mId) & "\\",\\"name\\":\\"" & my jsonEsc(mName) & "\\",\\"type\\":\\"media\\",\\"properties\\":{\\"date\\":\\"" & my jsonEsc(mDate) & "\\"}}" if i < resultCount then set output to output & "," end repeat set output to output & "]" @@ -140,7 +140,7 @@ enum PhotosTemplates { tell application id "\(bundleId)" set newAlbum to make new album named "\(esc(name))" set aId to id of newAlbum - return "{\\"id\\":\\"" & aId & "\\",\\"name\\":\\"\(esc(name))\\",\\"type\\":\\"album\\"}" + return "{\\"id\\":\\"" & my jsonEsc(aId) & "\\",\\"name\\":\\"\(esc(name))\\",\\"type\\":\\"album\\"}" end tell """ } diff --git a/packages/executor-swift/Sources/Executor/RemindersTemplates.swift b/packages/executor-swift/Sources/Executor/RemindersTemplates.swift index a1c4099..2ca9250 100644 --- a/packages/executor-swift/Sources/Executor/RemindersTemplates.swift +++ b/packages/executor-swift/Sources/Executor/RemindersTemplates.swift @@ -39,7 +39,7 @@ enum RemindersTemplates { set output to "[" repeat with i from 1 to count of listList set l to item i of listList - set output to output & "{\\"id\\":\\"" & listId of l & "\\",\\"name\\":\\"" & listName of l & "\\",\\"type\\":\\"list\\",\\"itemCount\\":" & (listCount of l as text) & "}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(listId of l) & "\\",\\"name\\":\\"" & my jsonEsc(listName of l) & "\\",\\"type\\":\\"list\\",\\"itemCount\\":" & (listCount of l as text) & "}" if i < (count of listList) then set output to output & "," end repeat set output to output & "]" @@ -78,7 +78,7 @@ enum RemindersTemplates { try set rDueDate to due date of r as «class isot» as string end try - set output to output & "{\\"id\\":\\"" & rId & "\\",\\"name\\":\\"" & rName & "\\",\\"type\\":\\"reminder\\",\\"properties\\":{\\"completed\\":" & rCompleted & ",\\"dueDate\\":\\"" & rDueDate & "\\"}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(rId) & "\\",\\"name\\":\\"" & my jsonEsc(rName) & "\\",\\"type\\":\\"reminder\\",\\"properties\\":{\\"completed\\":" & rCompleted & ",\\"dueDate\\":\\"" & my jsonEsc(rDueDate) & "\\"}}" if i < endIdx then set output to output & "," end repeat end if @@ -108,7 +108,7 @@ enum RemindersTemplates { try set rDueDate to due date of r as «class isot» as string end try - return "{\\"id\\":\\"" & rId & "\\",\\"name\\":\\"" & rName & "\\",\\"type\\":\\"reminder\\",\\"containerName\\":\\"" & cName & "\\",\\"createdAt\\":\\"" & rCreated & "\\",\\"modifiedAt\\":\\"" & rModified & "\\",\\"properties\\":{\\"body\\":\\"" & rBody & "\\",\\"completed\\":" & rCompleted & ",\\"priority\\":" & rPriority & ",\\"flagged\\":" & rFlagged & ",\\"dueDate\\":\\"" & rDueDate & "\\"}}" + return "{\\"id\\":\\"" & my jsonEsc(rId) & "\\",\\"name\\":\\"" & my jsonEsc(rName) & "\\",\\"type\\":\\"reminder\\",\\"containerName\\":\\"" & my jsonEsc(cName) & "\\",\\"createdAt\\":\\"" & my jsonEsc(rCreated) & "\\",\\"modifiedAt\\":\\"" & my jsonEsc(rModified) & "\\",\\"properties\\":{\\"body\\":\\"" & my jsonEsc(rBody) & "\\",\\"completed\\":" & rCompleted & ",\\"priority\\":" & rPriority & ",\\"flagged\\":" & rFlagged & ",\\"dueDate\\":\\"" & my jsonEsc(rDueDate) & "\\"}}" end tell """ } @@ -130,7 +130,7 @@ enum RemindersTemplates { set rId to id of r set rName to name of r set rCompleted to completed of r - set output to output & "{\\"id\\":\\"" & rId & "\\",\\"name\\":\\"" & rName & "\\",\\"type\\":\\"reminder\\",\\"properties\\":{\\"completed\\":" & rCompleted & "}}" + set output to output & "{\\"id\\":\\"" & my jsonEsc(rId) & "\\",\\"name\\":\\"" & my jsonEsc(rName) & "\\",\\"type\\":\\"reminder\\",\\"properties\\":{\\"completed\\":" & rCompleted & "}}" if i < resultCount then set output to output & "," end repeat set output to output & "]" @@ -172,7 +172,7 @@ enum RemindersTemplates { tell application id "\(bundleId)" set newReminder to make new reminder \(targetClause) with properties {\(props)}\(dueDateLine) set rId to id of newReminder - return "{\\"id\\":\\"" & rId & "\\",\\"name\\":\\"\(esc(name))\\",\\"type\\":\\"reminder\\"}" + return "{\\"id\\":\\"" & my jsonEsc(rId) & "\\",\\"name\\":\\"\(esc(name))\\",\\"type\\":\\"reminder\\"}" end tell """ } @@ -209,7 +209,7 @@ enum RemindersTemplates { tell application id "\(bundleId)" set r to reminder id "\(esc(reminderId))" \(setStatements.joined(separator: "\n ")) - return "{\\"id\\":\\"" & (id of r) & "\\",\\"name\\":\\"" & (name of r) & "\\",\\"type\\":\\"reminder\\"}" + return "{\\"id\\":\\"" & my jsonEsc(id of r) & "\\",\\"name\\":\\"" & my jsonEsc(name of r) & "\\",\\"type\\":\\"reminder\\"}" end tell """ } @@ -223,7 +223,7 @@ enum RemindersTemplates { set r to reminder id "\(esc(reminderId))" set rName to name of r delete r - return "{\\"deleted\\":true,\\"name\\":\\"" & rName & "\\"}" + return "{\\"deleted\\":true,\\"name\\":\\"" & my jsonEsc(rName) & "\\"}" end tell """ } @@ -245,7 +245,7 @@ enum RemindersTemplates { tell application id "\(bundleId)" set r to reminder id "\(esc(reminderId))" set completed of r to true - return "{\\"id\\":\\"" & (id of r) & "\\",\\"completed\\":true}" + return "{\\"id\\":\\"" & my jsonEsc(id of r) & "\\",\\"completed\\":true}" end tell """ } diff --git a/packages/executor-swift/Sources/Executor/SafariTemplates.swift b/packages/executor-swift/Sources/Executor/SafariTemplates.swift index c6b020f..03d4c57 100644 --- a/packages/executor-swift/Sources/Executor/SafariTemplates.swift +++ b/packages/executor-swift/Sources/Executor/SafariTemplates.swift @@ -44,7 +44,7 @@ enum SafariTemplates { set output to "[" repeat with i from 1 to count of windowList set w to item i of windowList - set output to output & "{\\"id\\":" & (windowId of w) & ",\\"name\\":\\"" & windowName of w & "\\",\\"type\\":\\"window\\",\\"itemCount\\":" & (tabCount of w as text) & "}" + set output to output & "{\\"id\\":" & (windowId of w) & ",\\"name\\":\\"" & my jsonEsc(windowName of w) & "\\",\\"type\\":\\"window\\",\\"itemCount\\":" & (tabCount of w as text) & "}" if i < (count of windowList) then set output to output & "," end repeat set output to output & "]" @@ -72,7 +72,7 @@ enum SafariTemplates { set t to item i of allTabs set tName to name of t set tUrl to URL of t - set output to output & "{\\"id\\":" & i & ",\\"name\\":\\"" & tName & "\\",\\"type\\":\\"tab\\",\\"properties\\":{\\"url\\":\\"" & tUrl & "\\"}}" + set output to output & "{\\"id\\":" & i & ",\\"name\\":\\"" & my jsonEsc(tName) & "\\",\\"type\\":\\"tab\\",\\"properties\\":{\\"url\\":\\"" & my jsonEsc(tUrl) & "\\"}}" if i < totalCount then set output to output & "," end repeat set output to output & "]}" @@ -98,7 +98,7 @@ enum SafariTemplates { set tName to name of t set tUrl to URL of t set tSource to source of t - return "{\\"id\\":\(tabIndex),\\"name\\":\\"" & tName & "\\",\\"type\\":\\"tab\\",\\"properties\\":{\\"url\\":\\"" & tUrl & "\\",\\"source\\":\\"" & tSource & "\\"}}" + return "{\\"id\\":\(tabIndex),\\"name\\":\\"" & my jsonEsc(tName) & "\\",\\"type\\":\\"tab\\",\\"properties\\":{\\"url\\":\\"" & my jsonEsc(tUrl) & "\\",\\"source\\":\\"" & my jsonEsc(tSource) & "\\"}}" end tell """ } @@ -124,7 +124,7 @@ enum SafariTemplates { set output to "[" repeat with i from 1 to count of matchingTabs set t to item i of matchingTabs - set output to output & "{\\"id\\":" & i & ",\\"name\\":\\"" & tName of t & "\\",\\"type\\":\\"tab\\",\\"properties\\":{\\"url\\":\\"" & tUrl of t & "\\",\\"windowId\\":" & (tWindow of t) & "}}" + set output to output & "{\\"id\\":" & i & ",\\"name\\":\\"" & my jsonEsc(tName of t) & "\\",\\"type\\":\\"tab\\",\\"properties\\":{\\"url\\":\\"" & my jsonEsc(tUrl of t) & "\\",\\"windowId\\":" & (tWindow of t) & "}}" if i < (count of matchingTabs) then set output to output & "," end repeat set output to output & "]" @@ -178,7 +178,7 @@ enum SafariTemplates { set t to tab \(tabIndex) of \(windowClause) set tName to name of t close t - return "{\\"closed\\":true,\\"name\\":\\"" & tName & "\\"}" + return "{\\"closed\\":true,\\"name\\":\\"" & my jsonEsc(tName) & "\\"}" end tell """ } @@ -211,7 +211,7 @@ enum SafariTemplates { return """ tell application id "\(bundleId)" set result to do JavaScript "\(esc(script))" in tab \(tabIndex) of \(windowClause) - return "{\\"result\\":\\"" & result & "\\"}" + return "{\\"result\\":\\"" & my jsonEsc(result) & "\\"}" end tell """ } diff --git a/packages/executor-swift/Tests/ExecutorTests/ExecutorTests.swift b/packages/executor-swift/Tests/ExecutorTests/ExecutorTests.swift index 29796b8..5b77582 100644 --- a/packages/executor-swift/Tests/ExecutorTests/ExecutorTests.swift +++ b/packages/executor-swift/Tests/ExecutorTests/ExecutorTests.swift @@ -2,7 +2,78 @@ import XCTest @testable import Executor final class ExecutorTests: XCTestCase { - func testPlaceholder() throws { - XCTAssertTrue(true, "Tests are working") + + // MARK: - JsonEscape.handlers + + func testHandlersContainsJsonEsc() { + XCTAssertTrue(JsonEscape.handlers.contains("on jsonEsc(")) + XCTAssertTrue(JsonEscape.handlers.contains("on replaceText(")) + } + + // MARK: - JsonEscape.wrapScript + + func testWrapScriptAppendsHandlers() { + let script = "tell application \"Finder\"\nend tell" + let wrapped = JsonEscape.wrapScript(script) + XCTAssertTrue(wrapped.hasPrefix(script)) + XCTAssertTrue(wrapped.contains("on jsonEsc(")) + } + + // MARK: - JsonEscape.reserialize + + func testReserializeValidJsonObject() { + let input: [String: Any] = ["value": "{\"name\":\"hello\",\"count\":42}"] + let result = JsonEscape.reserialize(input) + let value = result["value"] as? String ?? "" + // Re-serialized JSON should still be valid and contain the same data + let data = value.data(using: .utf8)! + let parsed = try! JSONSerialization.jsonObject(with: data) as! [String: Any] + XCTAssertEqual(parsed["name"] as? String, "hello") + XCTAssertEqual(parsed["count"] as? Int, 42) + } + + func testReserializeValidJsonArray() { + let input: [String: Any] = ["value": "[{\"id\":\"1\"},{\"id\":\"2\"}]"] + let result = JsonEscape.reserialize(input) + let value = result["value"] as? String ?? "" + let data = value.data(using: .utf8)! + let parsed = try! JSONSerialization.jsonObject(with: data) as! [[String: Any]] + XCTAssertEqual(parsed.count, 2) + } + + func testReserializeNonJsonPassesThrough() { + let input: [String: Any] = ["value": "just a plain string"] + let result = JsonEscape.reserialize(input) + XCTAssertEqual(result["value"] as? String, "just a plain string") + } + + func testReserializeEmptyStringPassesThrough() { + let input: [String: Any] = ["value": ""] + let result = JsonEscape.reserialize(input) + XCTAssertEqual(result["value"] as? String, "") + } + + func testReserializeNoValueKeyPassesThrough() { + let input: [String: Any] = ["error": "something"] + let result = JsonEscape.reserialize(input) + XCTAssertEqual(result["error"] as? String, "something") + } + + func testReserializeMalformedJsonPassesThrough() { + // Malformed JSON (unescaped quote in value) should pass through unchanged + let input: [String: Any] = ["value": "{\"name\":\"bad\"quote\"}"] + let result = JsonEscape.reserialize(input) + XCTAssertEqual(result["value"] as? String, "{\"name\":\"bad\"quote\"}") + } + + func testReserializePreservesSpecialChars() { + // JSON with properly escaped special chars should round-trip + let json = "{\"text\":\"line1\\nline2\\ttab\\\\back\\\"quote\"}" + let input: [String: Any] = ["value": json] + let result = JsonEscape.reserialize(input) + let value = result["value"] as? String ?? "" + let data = value.data(using: .utf8)! + let parsed = try! JSONSerialization.jsonObject(with: data) as! [String: Any] + XCTAssertEqual(parsed["text"] as? String, "line1\nline2\ttab\\back\"quote") } }