Advertisement
11Rick23

Edited version of program

Jun 16th, 2021
221
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. -- Exports Safari's saved passwords to a CSV file formatted for use with the csv converter in the mrc-converter-suite
  2. --
  3. -- Copyright 2021 Mike Cappella (mike at cappella dot us)
  4.  
  5. use AppleScript version "2.5" -- runs on 10.11 (El Capitan) and later              
  6. use scripting additions
  7.  
  8. -- Maximum amount of time to allow Safari to process password data
  9. property extractionTimeout : 30 * 60 -- 30 minutes
  10.  
  11. -- Maximum number of tries (once per second) for reading password table row data.
  12. -- It can take a few seconds for Safari to populate the password table, esp. when VoiceOver
  13. -- is enabled, or the system is running slowly.
  14. -- The table's row count is sampled until two consistent row counts are obtained, or maxAttempts
  15. property maxAttempts : 10
  16.  
  17. -- For testing: force processing a given number of password rows.  If this quantity is
  18. -- greater than the number of rows in Safari, the row processor will cycle through the
  19. -- rows until forcedRowCount rows have been processed.  A value of 0 disables
  20. -- forced processing.
  21. property forcedRowCount : 0
  22.  
  23. -- Logging support
  24. property logEnabled : true -- set to true to enable logging to a file
  25. property logFilename : "get_safari12_passwords.log" as string
  26. property logFile : ""
  27.  
  28. set logFile to ((path to desktop folder) as string) & logFilename as string
  29.  
  30. global logStartTimestamp
  31. set logStartTimestamp to time of (current date)
  32.  
  33. if logEnabled is true then
  34.     Debug("Log start: " & (time string of (current date)))
  35.     display dialog "Logging enabled.  Log file is at: " & return & return & (POSIX path of logFile)
  36. end if
  37.  
  38. -- Avoid a bug in Safari 13.x that prematurely closes the passwords dialog while active
  39. if application "Safari" is running then
  40.     set theResult to display dialog "Safari must be quit before this script can run.
  41.  
  42. Ok to quit Safari now?"
  43.     if (button returned of theResult) is "Cancel" then
  44.         error number -128
  45.     end if
  46.     tell application "Safari" to quit
  47.     delay 1
  48. end if
  49.  
  50. set csv_filepath to (path to desktop as text) & "pm_export.csv"
  51.  
  52. set invokedBy to ""
  53. tell application "System Events"
  54.     set invokedBy to get the name of the current application
  55.     set theResult to display dialog "Safari will be launched, and its Passwords dialog opened.  Enter your password in that dialog to continue.
  56.  
  57. Allow the script to run to completion before disturbing your system.
  58.    
  59.     Ok to launch Safari now?"
  60.     if (button returned of theResult) is "Cancel" then
  61.         error number -128
  62.     end if
  63.    
  64.     activate "Safari"
  65. end tell
  66.  
  67. set Entries to {}
  68. set Entries to FetchEntries()
  69.  
  70. set theResult to write_csv_file(Entries, csv_filepath)
  71. if not theResult then
  72.     display dialog "There was an error writing the CSV data!"
  73.     error number -128
  74. end if
  75.  
  76. tell application "System Events" to tell application process "Safari"
  77.     keystroke "w" using {command down}
  78. end tell
  79.  
  80. tell application "Finder"
  81.     reveal file csv_filepath
  82.     activate
  83.     --make new Finder window
  84.     --set target of Finder window 1 to path to desktop
  85.     --reveal csv_filepath
  86. end tell
  87.  
  88. with timeout of 300 seconds
  89.     tell application "System Events" to tell application process invokedBy
  90.         set frontmost to true
  91.         set dialogText to "All done!
  92.  
  93. There is now a file on your Desktop named:
  94.  
  95.    pm_export.csv
  96.  
  97. You may now convert it to a 1PIF file using the converter suite's \"csv\" converter.  The resulting 1PIF file may be imported into 1Password."
  98.         try
  99.             display dialog dialogText buttons {"Ok"} default button "Ok"
  100.         end try
  101.     end tell
  102. end timeout
  103.  
  104. -- handlers
  105.  
  106. on UnlockPasswords()
  107.     my Debug(" Opening Safari passwords dialog")
  108.     tell application "System Events" to tell application process "Safari"
  109.         set frontmost to true
  110.         keystroke "," using {command down}
  111.         set tb to toolbar 1 of window 1
  112.         set buttonName to (name of button 4 of tb as string)
  113.         click button 4 of tb
  114.        
  115.         set isLocked to true
  116.         repeat while isLocked is true
  117.             try
  118.                 set t to get value of static text "Passwords Are Locked" of group 1 of group 1 of window "Passwords"
  119.                 set isLocked to true
  120.                 my Debug("   passwords are *locked* - waiting...")
  121.                 delay 1
  122.             on error
  123.                 set isLocked to false
  124.                 my Debug("   passwords are now unlocked")
  125.             end try
  126.         end repeat
  127.     end tell
  128.    
  129. end UnlockPasswords
  130.  
  131. on FetchEntries()
  132.     Debug("Entering FetchEntries()")
  133.    
  134.     tell application "Safari"
  135.         activate
  136.     end tell
  137.     UnlockPasswords()
  138.    
  139.     set tableEntries to {}
  140.     with timeout of extractionTimeout seconds
  141.         try
  142.             tell application "System Events" to tell application process "Safari"
  143.                 set frontmost to true
  144.                 local prefsWin
  145.                
  146.                 set nRows to 0
  147.                 set nRowsPrev to -1
  148.                 set attempt to 1
  149.                 my Debug(" Sampling password data...")
  150.                 repeat while attempt = maxAttempts
  151.                     set prefsWin to window 1
  152.                     set theTable to table 1 of scroll area 1 of group 1 of group 1 of prefsWin
  153.                     set nRows to the count of rows of table 1 of scroll area 1 of group 1 of group 1 of prefsWin
  154.                     if nRows is 0 then
  155.                         -- nothing found, try again
  156.                         my Debug("   attempt " & attempt & ": row count: 0")
  157.                     else if nRows is not nRowsPrev then
  158.                         my Debug("   attempt " & attempt & ": current row count: " & nRows & ", previous row count: " & nRowsPrev)
  159.                         set nRowsPrev to nRows
  160.                     else
  161.                         my Debug("   attempt " & attempt & ":  two consecutive attempts found " & nRows & " rows")
  162.                         exit repeat
  163.                     end if
  164.                     set attempt to attempt + 1
  165.                     delay 1
  166.                 end repeat
  167.                
  168.                 -- bail: nothing found after maxAttemptss
  169.                 if nRows is 0 then
  170.                     my Debug("  No rows found in password table")
  171.                     display dialog "The password table appears to be empty" buttons {"Quit"} default button "Quit"
  172.                     error number -128
  173.                 end if
  174.                
  175.                 -- bail: could not get consistent row data after two consecutive attempts
  176.                 if nRows is not nRowsPrev then
  177.                     set msg to "Failed to get identical password row data after " & maxAttempts & " tries"
  178.                     my Debug("    " & msg)
  179.                     display dialog msg buttons {"Quit"} default button "Quit"
  180.                     error number -128
  181.                 end if
  182.                
  183.                 local rowIndex, currentRow, failedRows
  184.                 set currentRow to 1
  185.                 set failedRows to 0
  186.                
  187.                 my Debug(" Processing " & nRows & " rows from the passwords dialog")
  188.                 if (forcedRowCount is not 0) then
  189.                     set nRowsToProcess to forcedRowCount
  190.                     my Debug("   Forced processing of " & forcedRowCount & " rows")
  191.                 else
  192.                     set nRowsToProcess to nRows
  193.                 end if
  194.                
  195.                 repeat while currentRow = nRowsToProcess
  196.                     if (forcedRowCount is not 0) then
  197.                         set rowIndex to 1 + ((currentRow - 1) mod nRows)
  198.                         my Debug("    ROW: " & currentRow & " (index used: " & rowIndex & ")")
  199.                     else
  200.                         set rowIndex to currentRow
  201.                         my Debug("    ROW: " & rowIndex)
  202.                     end if
  203.                    
  204.                     local myRow, row_open_attempts
  205.                     local theSite, theName, theUser, thePass, theURLs, urlList, rowValues, theSheet
  206.                     set {theTitle, theSite, theUser, thePass, theURLs} to {"Untitled", "", "", "", ""}
  207.                     set urlList to {}
  208.                     set theSheet to 0
  209.                     set row_open_attempts to 2
  210.                    
  211.                     -- Sheet entries w/out a title will not open with the first keypress of Return, but a 2nd attempt will
  212.                     repeat while row_open_attempts > 0
  213.                         tell theTable
  214.                             set myRow to row rowIndex
  215.                             select row rowIndex
  216.                             --delay 1
  217.                             set focused to true
  218.                             -- open the sheet
  219.                             keystroke return
  220.                             set focused to true
  221.                         end tell
  222.                        
  223.                         try
  224.                             set theSheet to sheet 1 of prefsWin
  225.                             set row_open_attempts to 0
  226.                             if theSheet is not 0 then
  227.                                 -- Any of the URL, Username or Password values be empty                
  228.                                 set theURLtable to table 1 of scroll area 1 of theSheet
  229.                                 set nURLs to the count of rows of theURLtable
  230.                                 my Debug("     URL count: " & nURLs)
  231.                                 set url_index to 1
  232.                                 repeat while url_index = nURLs
  233.                                     local aURL
  234.                                     set aURL to (the value of static text of item 1 of UI element 1 of row url_index of theURLtable) as text
  235.                                     if url_index is equal to 1 then
  236.                                         if not (aURL is missing value or aURL is equal to "") then
  237.                                             -- For the Title, just duplicate the URL field
  238.                                             copy aURL to theSite
  239.                                             copy aURL to theTitle
  240.                                         end if
  241.                                     else
  242.                                         -- push extra URLs to the notes area
  243.                                         set the end of urlList to aURL
  244.                                     end if
  245.                                    
  246.                                     if aURL is missing value or aURL is equal to "" then
  247.                                         my Debug("        URL " & url_index & ": " & "<site missing or empty>")
  248.                                     else
  249.                                        
  250.                                         my Debug("        URL " & url_index & ": " & aURL)
  251.                                     end if
  252.                                    
  253.                                     set url_index to url_index + 1
  254.                                 end repeat
  255.                                
  256.                                 try
  257.                                     set theUser to value of attribute "AXValue" of text field 1 of theSheet
  258.                                     my Debug("        Username: " & theUser)
  259.                                 end try
  260.                                 try
  261.                                     set thePass to value of attribute "AXValue" of text field 2 of theSheet
  262.                                 end try
  263.                                
  264.                                 --if (count of urlList) is greater than 0 then
  265.                                 --  set beginning of urlList to "Extra URLs"
  266.                                 --end if
  267.                                
  268.                                 set theURLs to Join(character id 59, urlList) of me
  269.                                 local tmpList
  270.                                 set tmpList to {theTitle, theSite, theUser, thePass, theURLs}
  271.                                 copy tmpList to rowValues
  272.                                 set the end of tableEntries to rowValues
  273.                                
  274.                                 -- close the sheet
  275.                                 keystroke return
  276.                             end if
  277.                            
  278.                         on error
  279.                             my Debug("      Sheet failed to open - Skipping Entry")
  280.                             set failedRows to failedRows + 1
  281.                             set row_open_attempts to row_open_attempts - 1
  282.                         end try
  283.                     end repeat
  284.                     set currentRow to currentRow + 1
  285.                 end repeat
  286.             end tell
  287.            
  288.         on error errMsg number errNum
  289.             if (errNum is -1712) then
  290.                 display dialog "Script time out while extracting Safari passwords
  291.         " & errMsg
  292.             else if errNum is -10006 then
  293.                 display dialog "Safari was quit - aborting"
  294.             else
  295.                 display dialog "Error" & errNum & "
  296. " & errMsg
  297.             end if
  298.             error number -128
  299.         end try
  300.     end timeout
  301.    
  302.     Debug("Leaving FetchEntries(): entries found: " & (count of tableEntries))
  303.    
  304.     return tableEntries
  305. end FetchEntries
  306.  
  307. on write_csv_file(Entries, fpath)
  308.     local rowdata
  309.    
  310.     Debug("Entering write_csv_file()")
  311.    
  312.     set beginning of Entries to {"Title", "Login URL", "Login Username", "Login Password", "Additional URLs"}
  313.    
  314.     set csvstr to ""
  315.     set i to 1
  316.     repeat while i = (count of Entries)
  317.         --say "Row " & i
  318.         set rowdata to item i of Entries
  319.        
  320.         if csvstr is not "" then
  321.             set csvstr to csvstr & character id 10
  322.         end if
  323.        
  324.         set j to 1
  325.         repeat while j = (count of rowdata)
  326.             --say "Column " & j
  327.             set encoded to CSVCellEncode(item j of rowdata)
  328.             if csvstr is "" then
  329.                 set csvstr to encoded
  330.             else if j is 1 then
  331.                 set csvstr to csvstr & encoded
  332.             else
  333.                 set csvstr to csvstr & "," & encoded
  334.             end if
  335.             set j to j + 1
  336.         end repeat
  337.         set i to i + 1
  338.     end repeat
  339.    
  340.     Debug("  Writing CSV string to file: length: " & length of csvstr)
  341.     set theResult to WriteTo(fpath, csvstr, «class utf8», false)
  342.     Debug("  Done")
  343.     Debug("Leaving write_csv_file()")
  344.    
  345.     return theResult
  346. end write_csv_file
  347.  
  348. on WriteTo(targetFile, theData, dataType, append)
  349.     try
  350.         set targetFile to targetFile as text
  351.         set openFile to open for access file targetFile with write permission
  352.         if append is false then set eof of openFile to 0
  353.         write theData to openFile starting at eof as dataType
  354.         close access openFile
  355.         return true
  356.     on error
  357.         try
  358.             close access file targetFile
  359.         end try
  360.         return false
  361.     end try
  362. end WriteTo
  363.  
  364. on Debug(str)
  365.     if logEnabled is not true then
  366.         return
  367.     end if
  368.     set secs to ((time of (current date)) - logStartTimestamp) as string
  369.     set secsPadded to text -4 thru -1 of ("0000" & secs)
  370.     set msg to ((secsPadded & ": " & str & linefeed) as string)
  371.     WriteTo(logFile, msg, «class utf8», true)
  372. end Debug
  373.  
  374. on CSVCellEncode(cellstr)
  375.     --say cellstr
  376.     if cellstr is "" then return ""
  377.     set orig to cellstr
  378.     set cellstr to ""
  379.     repeat with c in the characters of orig
  380.         set c to c as text
  381.         if c is "\"" then
  382.             set cellstr to cellstr & "\"\""
  383.         else
  384.             set cellstr to cellstr & c
  385.         end if
  386.     end repeat
  387.    
  388.     if (cellstr contains "," or cellstr contains " " or cellstr contains "\"" or cellstr contains return or cellstr contains character id 10) then set cellstr to quote & cellstr & quote
  389.    
  390.     return cellstr
  391. end CSVCellEncode
  392.  
  393. on SpeakList(l, name)
  394.     say "List named " & name
  395.     repeat with theItem in l
  396.         say theItem
  397.     end repeat
  398. end SpeakList
  399.  
  400. on GetTitleFromURL(val)
  401.     copy val to title
  402.     -- applescript's lack of RE's sucks
  403.     set pats to {"http://", "https://"}
  404.     repeat with pat in pats
  405.         set title to my ReplaceText(pat, "", title)
  406.     end repeat
  407.     return item 1 of my Split(title, "/")
  408. end GetTitleFromURL
  409.  
  410. on ReplaceText(find, replace, subject)
  411.     set prevTIDs to text item delimiters of AppleScript
  412.     set text item delimiters of AppleScript to find
  413.     set subject to text items of subject
  414.    
  415.     set text item delimiters of AppleScript to replace
  416.     set subject to subject as text
  417.     set text item delimiters of AppleScript to prevTIDs
  418.     return subject
  419. end ReplaceText
  420.  
  421. on Split(theString, theDelimiter)
  422.     set oldDelimiters to AppleScript's text item delimiters
  423.     set AppleScript's text item delimiters to theDelimiter
  424.     set theArray to every text item of theString
  425.     set AppleScript's text item delimiters to oldDelimiters
  426.     return theArray
  427. end Split
  428.  
  429. on Join(delims, l)
  430.     local ret
  431.     set prevTIDs to AppleScript's text item delimiters
  432.     set AppleScript's text item delimiters to delims
  433.     set ret to items of l as text
  434.     set AppleScript's text item delimiters to prevTIDs
  435.     return ret
  436. end Join
  437.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement